diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..c43735154 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,45 @@ +Checks: + - core* + - cppcoreguidelines-* + - modernize* + - bugprone-* + - performance-* + - readability-* + - portability-* + - clang-diagnostic-* + - misc-* + - google-build-namespaces + - google-build-using-namespace + - google-default-arguments + - google-explicit-constructor + - google-readability-casting + - -modernize-use-trailing-return-type + - -modernize-use-nodiscard + - -modernize-concat-nested-namespaces + - -modernize-use-default-member-init + - -modernize-use-ranges + - -modernize-use-designated-initializers + - -misc-non-private-member-variables-in-classes + - -misc-use-internal-linkage + - -misc-use-anonymous-namespace + - -cppcoreguidelines-pro-type-vararg + - -cppcoreguidelines-non-private-member-variables-in-classes + - -cppcoreguidelines-special-member-functions + - -cppcoreguidelines-pro-type-reinterpret-cast + - -cppcoreguidelines-pro-type-union-access + - -cppcoreguidelines-pro-bounds-pointer-arithmetic + - -cppcoreguidelines-pro-type-member-init + - -cppcoreguidelines-owning-memory + - -cppcoreguidelines-avoid-const-or-ref-data-members + - -cppcoreguidelines-macro-usage + - -cppcoreguidelines-pro-bounds-constant-array-index + - -cppcoreguidelines-pro-bounds-array-to-pointer-decay + - -cppcoreguidelines-pro-type-static-cast-downcast + - -bugprone-easily-swappable-parameters + - -readability-named-parameter + - -readability-identifier-length + +CheckOptions: + bugprone-assert-side-effect.CheckFunctionCalls: true + performance-move-const-arg.CheckTriviallyCopyableMove: false + cppcoreguidelines-avoid-do-while.IgnoreMacros: true diff --git a/aether/aether_app.cpp b/aether/aether_app.cpp index cf33c7e0f..4bccdb12f 100644 --- a/aether/aether_app.cpp +++ b/aether/aether_app.cpp @@ -17,19 +17,19 @@ #include "aether/aether_app.h" // IWYU pragma: begin_keeps -#include "aether/types/literal_array.h" #include "aether/types/address_parser.h" +#include "aether/types/literal_array.h" +#include "aether/adapters/ethernet.h" +#include "aether/adapters/wifi_adapter.h" #include "aether/crypto.h" #include "aether/crypto/key.h" #include "aether/global_ids.h" -#include "aether/adapters/ethernet.h" -#include "aether/registration_cloud.h" -#include "aether/adapters/wifi_adapter.h" -#include "aether/poller/win_poller.h" #include "aether/poller/epoll_poller.h" -#include "aether/poller/kqueue_poller.h" #include "aether/poller/freertos_poller.h" +#include "aether/poller/kqueue_poller.h" +#include "aether/poller/win_poller.h" +#include "aether/registration_cloud.h" #include "aether/dns/dns_c_ares.h" #include "aether/dns/esp32_dns_resolve.h" @@ -299,6 +299,7 @@ void AetherAppContext::InitComponentContext() { RcPtr AetherApp::Construct(AetherAppContext context) { // init all the components in context context.InitComponentContext(); + context.init_tele_(context); auto app = MakeRcPtr(); diff --git a/aether/api_protocol/protocol_context.cpp b/aether/api_protocol/protocol_context.cpp index 6c50d5b44..d78525a8c 100644 --- a/aether/api_protocol/protocol_context.cpp +++ b/aether/api_protocol/protocol_context.cpp @@ -17,6 +17,7 @@ #include "aether/api_protocol/protocol_context.h" #include +#include #include "aether/api_protocol/api_protocol.h" @@ -58,9 +59,9 @@ void ProtocolContext::SetSendErrorResponse(RequestId req_id, send_error_events_.erase(it); } else { AE_TELED_DEBUG("No callback for error with request id {}", req_id); - std::cerr << "SendError: id " << req_id - << " type: " << static_cast(error_type) - << " error code: " << error_code << std::endl; + fprintf(stderr, "SendError: id %zu, type: %d, error_code %zu\n", + static_cast(req_id.id), static_cast(error_type), + static_cast(error_code)); parser()->Cancel(); } send_result_events_.erase(req_id); diff --git a/aether/api_protocol/request_id.h b/aether/api_protocol/request_id.h index bf5aa5045..f53c81dc0 100644 --- a/aether/api_protocol/request_id.h +++ b/aether/api_protocol/request_id.h @@ -19,6 +19,7 @@ #include +#include "aether-miscpp/format/format.h" #include "aether-miscpp/reflect/reflect.h" namespace ae { @@ -40,6 +41,14 @@ struct RequestId { AE_REFLECT_MEMBERS(id) std::uint32_t id{}; }; + +template <> +struct Formatter { + template + void Format(RequestId const& value, FormatContext& ctx) const { + Formatter{}.Format(value.id, ctx); + } +}; } // namespace ae #endif // AETHER_API_PROTOCOL_REQUEST_ID_H_ diff --git a/aether/client_messages/p2p_message_stream_manager.cpp b/aether/client_messages/p2p_message_stream_manager.cpp index a45e25521..aa9df6c14 100644 --- a/aether/client_messages/p2p_message_stream_manager.cpp +++ b/aether/client_messages/p2p_message_stream_manager.cpp @@ -20,8 +20,8 @@ #include #include "aether/client.h" -#include "aether/work_cloud_api/client_api/client_api_safe.h" #include "aether/client_messages/client_messages_tele.h" +#include "aether/work_cloud_api/client_api/client_api_safe.h" namespace ae { @@ -57,9 +57,12 @@ P2pMessageStreamManager::GetOrCreatePort(Uid const& destination) { return {std::move(existing), false}; } } - auto port = - std::make_shared(destination); - ports_[destination] = port; + // Do not use std::make_shared here. GCC 16/libstdc++ emits a false + // -Warray-bounds warning while destroying P2pReceivePort from + // _Sp_counted_ptr_inplace because P2pReceivePort owns Event/RcPtr storage. + auto port = std::shared_ptr{ + std::make_unique(destination)}; + ports_.emplace(destination, port); return {port, true}; } diff --git a/aether/crypto/key.h b/aether/crypto/key.h index a0892f8dc..0a35a0292 100644 --- a/aether/crypto/key.h +++ b/aether/crypto/key.h @@ -23,11 +23,9 @@ AE_SIGNATURE != AE_NONE # include -# include +# include # include # include -# include -# include # if AE_CRYPTO_ASYNC == AE_SODIUM_BOX_SEAL # include @@ -47,6 +45,7 @@ # endif # include "aether-miscpp/reflect/reflect.h" +# include "aether-miscpp/format/format.h" # include "aether/types/variant_type.h" namespace ae { @@ -176,19 +175,16 @@ class Key static_cast(*this)); } - static std::string text(Key const& v) { - std::stringstream ss; - ss << "["; - std::visit( - [&ss](auto const& k) { - for (auto s : k.key) { - ss << std::hex << std::setw(2) << std::setfill('0') - << static_cast(s); - } - }, - static_cast(v)); - ss << "]"; - return ss.str(); +}; + +template <> +struct Formatter { + template + void Format(Key const& value, FormatContext& ctx) const { + auto data = std::span{value.Data(), value.Size()}; + ctx.out().write('['); + Formatter{}.Format(data, ctx); + ctx.out().write(']'); } }; diff --git a/aether/crypto/sign.h b/aether/crypto/sign.h index d071ad111..489870853 100644 --- a/aether/crypto/sign.h +++ b/aether/crypto/sign.h @@ -24,8 +24,7 @@ # include # include # include -# include -# include +# include # if AE_SIGNATURE == AE_ED25519 # include @@ -34,6 +33,7 @@ # endif # include "aether-miscpp/reflect/reflect.h" +# include "aether-miscpp/format/format.h" # include "aether/types/variant_type.h" namespace ae { @@ -86,18 +86,16 @@ struct Sign : public VariantType(*this)); } - static std::string text(Sign const& v) { - std::stringstream ss; - ss << "["; - std::visit( - [&ss](auto const& k) { - for (auto s : k.signature) { - ss << std::hex << static_cast(s); - } - }, - static_cast(v)); - ss << "]"; - return ss.str(); +}; + +template <> +struct Formatter { + template + void Format(Sign const& value, FormatContext& ctx) const { + auto data = std::span{value.Data(), value.Size()}; + ctx.out().write('['); + Formatter{}.Format(data, ctx); + ctx.out().write(']'); } }; } // namespace ae diff --git a/aether/poller/poller_types.h b/aether/poller/poller_types.h index 7e4131b54..66bf1e451 100644 --- a/aether/poller/poller_types.h +++ b/aether/poller/poller_types.h @@ -89,6 +89,19 @@ static constexpr auto kInvalidDescriptor = static constexpr auto kInvalidDescriptor = -1; #endif +template <> +struct Formatter { + template + void Format(DescriptorType const& value, FormatContext& ctx) const { +#ifdef _WIN32 + Formatter{}.Format( + static_cast(value), ctx); +#else + Formatter{}.Format(value.descriptor, ctx); +#endif + } +}; + template <> struct Formatter { template diff --git a/aether/tasks/details/manual_task_scheduler.h b/aether/tasks/details/manual_task_scheduler.h index 8156f650b..63bbcfd35 100644 --- a/aether/tasks/details/manual_task_scheduler.h +++ b/aether/tasks/details/manual_task_scheduler.h @@ -17,13 +17,12 @@ #ifndef AETHER_TASKS_DETAILS_MANUAL_TASK_SCHEDULER_H_ #define AETHER_TASKS_DETAILS_MANUAL_TASK_SCHEDULER_H_ -#include #include -#include #include -#include // IWYU pragma: keeps -#include // IWYU pragma: keeps +#include #include +#include // // IWYU pragma: keep +#include #include "aether/tasks/details/task_manager.h" @@ -128,8 +127,8 @@ class ManualTaskScheduler { void CheckOverflows() { #ifndef NDEBUG if (overflow_counter_ != 0) { - std::cerr << std::format("!!!!!> ManualTaskScheduler: got overflow: {}\n", - overflow_counter_); + fprintf(stderr, "!!!!!> ManualTaskScheduler: got overflow: %zu\n", + overflow_counter_); overflow_counter_ = 0; } #endif diff --git a/aether/tele/collectors.h b/aether/tele/collectors.h index f9c078df3..92a6f88a0 100644 --- a/aether/tele/collectors.h +++ b/aether/tele/collectors.h @@ -21,20 +21,20 @@ # error "Include tele.h instead" #endif -#include #include #include -#include #include +#include #include +#include -#include "aether/clock.h" #include "aether-miscpp/format/format.h" +#include "aether/clock.h" -#include "aether/tele/tags.h" #include "aether/tele/itrap.h" #include "aether/tele/levels.h" #include "aether/tele/modules.h" +#include "aether/tele/tags.h" namespace ae::tele { struct Timer { @@ -145,7 +145,8 @@ struct TeleLogCollector - void Blob(std::string_view format, TArgs&&... args) { + void Blob([[maybe_unused]] FormatScheme const& format, + [[maybe_unused]] TArgs&&... args) { if constexpr (TSinkConfig::kBlobLogs) { if (trap_ == nullptr) { return; diff --git a/aether/tele/env_collectors.h b/aether/tele/env_collectors.h index 92551e600..bda92b070 100644 --- a/aether/tele/env_collectors.h +++ b/aether/tele/env_collectors.h @@ -26,13 +26,13 @@ #include #include "aether/env.h" -#include "aether/tele/itrap.h" -#include "aether/tele/env/compiler.h" #include "aether/tele/compile_option.h" -#include "aether/tele/env/platform_type.h" -#include "aether/tele/env/library_version.h" -#include "aether/tele/env/cpu_architecture.h" #include "aether/tele/env/compilation_options.h" +#include "aether/tele/env/compiler.h" +#include "aether/tele/env/cpu_architecture.h" +#include "aether/tele/env/library_version.h" +#include "aether/tele/env/platform_type.h" +#include "aether/tele/itrap.h" namespace ae::tele { /** diff --git a/aether/tele/modules.h b/aether/tele/modules.h index c99b9d004..f49e5dd34 100644 --- a/aether/tele/modules.h +++ b/aether/tele/modules.h @@ -27,10 +27,6 @@ namespace ae::tele { struct Module { - static std::string text(Module const& value) { - return std::string{value.name}; - } - std::uint32_t id; std::uint32_t index_start; std::uint32_t index_end; diff --git a/aether/tele/tele_init.cpp b/aether/tele/tele_init.cpp index 16f4774e3..862dbf42d 100644 --- a/aether/tele/tele_init.cpp +++ b/aether/tele/tele_init.cpp @@ -16,8 +16,8 @@ #include "aether/tele/tele_init.h" -#include #include +#include #include "aether/config.h" diff --git a/aether/tele/traps/io_stream_traps.cpp b/aether/tele/traps/io_stream_traps.cpp index 74ab91857..3ea5b8076 100644 --- a/aether/tele/traps/io_stream_traps.cpp +++ b/aether/tele/traps/io_stream_traps.cpp @@ -18,34 +18,40 @@ #include "aether/tele/traps/io_stream_traps.h" -#include -#include #include #include "aether-miscpp/format/format.h" namespace ae::tele { +namespace { + +void WriteUnformatted(std::ostream& stream, std::string_view value) { + stream.write(value.data(), static_cast(value.size())); +} + +} // namespace IoStreamTrap::IoStreamTrap(std::ostream& stream) : stream_{stream} {} IoStreamTrap::~IoStreamTrap() { - auto lock_guard = std::lock_guard{sync_lock_}; + auto lock = std::scoped_lock{sync_lock_}; stream_ << "Metrics:\n"; - Format(stream_, "Index,Invocations,Max Duration,Sum Duration,Min Duration\n"); + stream_ << "Index,Invocations,Max Duration,Sum Duration,Min Duration\n"; for (auto const& [index, ms] : metrics_) { Format(stream_, "{},{},{},{},{}\n", index, ms.invocations_count, ms.max_duration, ms.sum_duration, ms.min_duration); } - stream_ << std::endl; + stream_ << '\n'; + stream_.flush(); } void IoStreamTrap::AddInvoke(Tag const& tag, std::uint32_t count) { - auto lock_guard = std::lock_guard{sync_lock_}; + auto lock = std::scoped_lock{sync_lock_}; metrics_[tag.index()].invocations_count += count; } void IoStreamTrap::AddInvokeDuration(Tag const& tag, Duration duration) { - auto lock_guard = std::lock_guard{sync_lock_}; + auto lock = std::scoped_lock{sync_lock_}; auto& metric = metrics_[tag.index()]; metric.max_duration = std::max(metric.max_duration, duration.count()); metric.sum_duration += duration.count(); @@ -58,74 +64,78 @@ void IoStreamTrap::AddInvokeDuration(Tag const& tag, Duration duration) { void IoStreamTrap::OpenLogLine(Tag const& tag) { sync_lock_.lock(); - stream_ << std::setw(3) << std::right << std::dec << tag.index() << std::right - << std::setw(0); + WritePaddedIndex(tag.index()); } void IoStreamTrap::InvokeTime(TimePoint time) { - delimiter(); - Format(stream_, "[{:%H:%M:%S}]", time); + Format(stream_, ":[{:time}]", time); } void IoStreamTrap::WriteLevel(Level level) { - delimiter(); - Format(stream_, "{}", level); + stream_.put(':'); + WriteUnformatted(stream_, Level::text(level.value_)); } void IoStreamTrap::WriteModule(Module const& module) { - delimiter(); - Format(stream_, "{}", module); + stream_.put(':'); + WriteUnformatted(stream_, module.name); } void IoStreamTrap::Location(std::string_view file, std::uint32_t line) { - delimiter(); auto pos = file.find_last_of("/\\"); - if (pos == std::string_view::npos) { + if (pos != std::string_view::npos) { + file = file.substr(pos + 1, file.size() - pos); + } else { file = "UNKNOWN FILE"; } - file = file.substr(pos + 1, file.size() - pos); - stream_.write(file.data(), static_cast(file.size())); - delimiter(); - stream_ << line; + Format(stream_, ":{}:{}", file, line); } void IoStreamTrap::TagName(std::string_view name) { - delimiter(); - stream_.write(name.data(), static_cast(name.size())); + stream_.put(':'); + WriteUnformatted(stream_, name); } void IoStreamTrap::Blob(std::uint8_t const* data, std::size_t size) { - delimiter(); + stream_.put(':'); stream_.write(reinterpret_cast(data), static_cast(size)); } void IoStreamTrap::CloseLogLine(Tag const&) { - sync_lock_.unlock(); stream_ << std::endl; + sync_lock_.unlock(); } void IoStreamTrap::WriteEnvData(EnvData const& env_data) { - auto lock_guard = std::lock_guard{sync_lock_}; - Format(stream_, - "Platform:{}\nCompiler:{}\nCompiler version:{}\nLibrary " - "version:{}\nApi version:{}\nCPU arch:{}\nEndianness:{}\nUTMid:{}\n", - env_data.platform_type, env_data.compiler, env_data.compiler_version, - env_data.library_version, env_data.api_version, env_data.cpu_arch, - (env_data.endianness == static_cast(AE_LITTLE_ENDIAN) - ? "LittleEndian" - : "BigEndian"), - env_data.utm_id); + auto lock = std::scoped_lock{sync_lock_}; + Format( + stream_, + "Platform:{}\nCompiler:{}\nCompiler version:{}\nLibrary version:{}\nApi " + "version:{}\nCPU arch:{}\nEndianness:{}\nUTMid:{}\n", + env_data.platform_type, env_data.compiler, env_data.compiler_version, + env_data.library_version, env_data.api_version, env_data.cpu_arch, + (env_data.endianness == static_cast(AE_LITTLE_ENDIAN) + ? "LittleEndian" + : "BigEndian"), + env_data.utm_id); + constexpr auto option_format = FormatScheme{"{}:{}\n"}; for (auto const& opt : env_data.compile_options) { - Format(stream_, "{}:{}\n", opt.name, opt.value); + Format(stream_, option_format, opt.name, opt.value); } for (auto const& opt : env_data.custom_options) { - Format(stream_, "{}:{}\n", opt.name, opt.value); + Format(stream_, option_format, opt.name, opt.value); } - stream_ << std::endl; + stream_ << '\n'; + stream_.flush(); } -void IoStreamTrap::delimiter() { - stream_ << std::setw(1) << ':' << std::setw(0); +void IoStreamTrap::WritePaddedIndex(std::uint32_t index) { + if (index < 10) { + stream_ << " "; + } else if (index < 100) { + stream_ << ' '; + } + Format(stream_, "{}", index); } } // namespace ae::tele diff --git a/aether/tele/traps/io_stream_traps.h b/aether/tele/traps/io_stream_traps.h index f998d8b5d..41a33675c 100644 --- a/aether/tele/traps/io_stream_traps.h +++ b/aether/tele/traps/io_stream_traps.h @@ -17,8 +17,9 @@ #ifndef AETHER_TELE_TRAPS_IO_STREAM_TRAPS_H_ #define AETHER_TELE_TRAPS_IO_STREAM_TRAPS_H_ -#include +#include #include +#include #include #include #include @@ -58,7 +59,7 @@ class IoStreamTrap final : public ITrap { } private: - void delimiter(); + void WritePaddedIndex(std::uint32_t index); std::mutex sync_lock_; std::ostream& stream_; diff --git a/aether/tele/traps/statistics_trap.cpp b/aether/tele/traps/statistics_trap.cpp index d9faf55cb..71ab5067c 100644 --- a/aether/tele/traps/statistics_trap.cpp +++ b/aether/tele/traps/statistics_trap.cpp @@ -17,11 +17,10 @@ #include "aether/tele/traps/statistics_trap.h" -#include +#include #include -#include #include -#include +#include #include "aether/mstream.h" #include "aether/mstream_buffers.h" @@ -30,7 +29,6 @@ namespace ae::tele::statistics { RuntimeLog::RuntimeLog(SavedLog&& saved) : size{saved.data.size()} { logs.push_back(std::move(std::move(saved).data)); - auto ss = std::ostringstream{}; } void RuntimeLog::Append(RuntimeLog&& newer) { diff --git a/aether/types/address.h b/aether/types/address.h index a60bbc419..6a972355a 100644 --- a/aether/types/address.h +++ b/aether/types/address.h @@ -17,8 +17,12 @@ #ifndef AETHER_TYPES_ADDRESS_H_ #define AETHER_TYPES_ADDRESS_H_ +#include +#include #include #include +#include +#include #include "aether/config.h" #include "aether-miscpp/reflect/reflect.h" @@ -131,10 +135,13 @@ struct Formatter { void Format([[maybe_unused]] IpV4Addr const& value, [[maybe_unused]] FormatContext& ctx) const { #if AE_SUPPORT_IPV4 == 1 - ae::Format(ctx.out(), "{}.{}.{}.{}", static_cast(value.ipv4_value[0]), - static_cast(value.ipv4_value[1]), - static_cast(value.ipv4_value[2]), - static_cast(value.ipv4_value[3])); + Formatter{}.Format(static_cast(value.ipv4_value[0]), ctx); + ctx.out().write('.'); + Formatter{}.Format(static_cast(value.ipv4_value[1]), ctx); + ctx.out().write('.'); + Formatter{}.Format(static_cast(value.ipv4_value[2]), ctx); + ctx.out().write('.'); + Formatter{}.Format(static_cast(value.ipv4_value[3]), ctx); #endif } }; @@ -145,14 +152,17 @@ struct Formatter { void Format([[maybe_unused]] IpV6Addr const& value, [[maybe_unused]] FormatContext& ctx) const { #if AE_SUPPORT_IPV6 == 1 - ctx.out().stream() << std::hex; + char buffer[2]{}; for (std::size_t i = 0; i < 16; i++) { - ctx.out().stream() << static_cast(value.ipv6_value[i]); + auto result = std::to_chars(buffer, buffer + 2, + static_cast(value.ipv6_value[i]), + 16); + ctx.out().write(std::string_view{ + buffer, static_cast(result.ptr - buffer)}); if (i < 15) { - ctx.out().stream() << ':'; + ctx.out().write(':'); } } - ctx.out().stream() << std::dec; #endif } }; @@ -163,7 +173,7 @@ struct Formatter { void Format([[maybe_unused]] NamedAddr const& value, [[maybe_unused]] FormatContext& ctx) const { #if AE_SUPPORT_CLOUD_DNS == 1 - ctx.out().stream() << value.name; + Formatter{}.Format(value.name, ctx); #endif } }; @@ -172,8 +182,12 @@ template <> struct Formatter
{ template void Format(Address const& value, FormatContext& ctx) const { - std::visit([&](auto const& addr) { ae::Format(ctx.out(), "{}", addr); }, - value); + std::visit( + [&](auto const& addr) { + using Addr = std::decay_t; + Formatter{}.Format(addr, ctx); + }, + value); } }; @@ -181,7 +195,9 @@ template <> struct Formatter { template void Format(AddressPort const& value, FormatContext& ctx) const { - ae::Format(ctx.out(), "{}:{}", value.address, value.port); + Formatter
{}.Format(value.address, ctx); + ctx.out().write(':'); + Formatter{}.Format(value.port, ctx); } }; @@ -189,8 +205,11 @@ template <> struct Formatter { template void Format(Endpoint const& value, FormatContext& ctx) const { - ae::Format(ctx.out(), "{}:{} protocol:{}", value.address, value.port, - static_cast(value.protocol)); + Formatter
{}.Format(value.address, ctx); + ctx.out().write(':'); + Formatter{}.Format(value.port, ctx); + ctx.out().write(" protocol:"); + Formatter{}.Format(static_cast(value.protocol), ctx); } }; } // namespace ae diff --git a/aether/types/client_config.h b/aether/types/client_config.h index a53ab7a80..d7c9160fb 100644 --- a/aether/types/client_config.h +++ b/aether/types/client_config.h @@ -40,11 +40,12 @@ template <> struct Formatter { template void Format(ClientConfig const& value, FormatContext& ctx) const { - ae::Format(ctx.out(), - "parent_uid={},\nuid={},\nephemeral_uid={}\nmaster_key={}:{}," - "\ncloud=[\n{}\n]", - value.parent_uid, value.uid, value.ephemeral_uid, - value.master_key.Index(), value.master_key, value.cloud); + FormatTo(ctx.out(), + FormatScheme{ + "parent_uid={},\nuid={},\nephemeral_uid={}\nmaster_key={}:{}," + "\ncloud=[\n{}\n]"}, + value.parent_uid, value.uid, value.ephemeral_uid, + value.master_key.Index(), value.master_key, value.cloud); } }; } // namespace ae diff --git a/aether/types/ring_index.h b/aether/types/ring_index.h index 7dba8c976..89e008a37 100644 --- a/aether/types/ring_index.h +++ b/aether/types/ring_index.h @@ -120,9 +120,11 @@ struct RingIndex { template struct Formatter> : Formatter { + using Base = Formatter; + template void Format(RingIndex const& index, FormatContext& ctx) const { - Formatter::Format(static_cast(index), ctx); + static_cast(*this).Format(static_cast(index), ctx); } }; diff --git a/aether/types/server_config.h b/aether/types/server_config.h index 945b76151..3301d9fa9 100644 --- a/aether/types/server_config.h +++ b/aether/types/server_config.h @@ -36,8 +36,8 @@ template <> struct Formatter { template void Format(ServerConfig const& value, FormatContext& ctx) const { - ae::Format(ctx.out(), "server_id={}, endpoints=[{}]", value.id, - value.endpoints); + FormatTo(ctx.out(), FormatScheme{"server_id={}, endpoints=[{}]"}, value.id, + value.endpoints); } }; } // namespace ae diff --git a/aether/types/statistic_counter.h b/aether/types/statistic_counter.h index 3fc5b2af7..a223d9ea7 100644 --- a/aether/types/statistic_counter.h +++ b/aether/types/statistic_counter.h @@ -132,11 +132,12 @@ class StatisticsCounter final { template struct Formatter> : public Formatter> { + using Base = Formatter>; + template void Format(StatisticsCounter const& value, FormatContext& ctx) const { - Formatter>{}.Format(value.value_buffer_, - ctx); + static_cast(*this).Format(value.value_buffer_, ctx); } }; diff --git a/aether/types/uid.h b/aether/types/uid.h index 85bdaa3cb..11251dcca 100644 --- a/aether/types/uid.h +++ b/aether/types/uid.h @@ -130,7 +130,7 @@ struct Formatter { } wp += 2; } - ctx.out().stream().write(buff.data(), buff.size()); + ctx.out().write(std::string_view{buff.data(), buff.size()}); } }; diff --git a/aether/wifi/esp_wifi_driver.cpp b/aether/wifi/esp_wifi_driver.cpp index 50d451ac1..bf8044b99 100644 --- a/aether/wifi/esp_wifi_driver.cpp +++ b/aether/wifi/esp_wifi_driver.cpp @@ -20,18 +20,17 @@ # include -# include "nvs_flash.h" - -# include "esp_log.h" -# include "esp_wifi.h" # include "esp_event.h" -# include "esp_system.h" +# include "esp_log.h" # include "esp_private/wifi.h" +# include "esp_system.h" +# include "esp_wifi.h" +# include "nvs_flash.h" # include "lwip/err.h" -# include "lwip/sys.h" # include "lwip/ip4_addr.h" # include "lwip/ip6_addr.h" +# include "lwip/sys.h" # include "aether/tele/tele.h" diff --git a/aether/write_action/buffer_write.h b/aether/write_action/buffer_write.h index 3a18370e1..3404a694a 100644 --- a/aether/write_action/buffer_write.h +++ b/aether/write_action/buffer_write.h @@ -24,11 +24,12 @@ IGNORE_IMPLICIT_CONVERSION() #include DISABLE_WARNING_POP() -#include "aether/ae_context.h" #include "aether-miscpp/types/small_function.h" + +#include "aether/ae_context.h" #include "aether/events/multi_subscription.h" -#include "aether/write_action/write_action.h" #include "aether/write_action/failed_write_action.h" +#include "aether/write_action/write_action.h" #if DEBUG // include tele only in debug mode # include "aether/tele/tele.h" @@ -36,8 +37,14 @@ DISABLE_WARNING_POP() namespace ae { #if DEBUG -# define BW_LOG_DEBUG(...) AE_TELED_DEBUG(__VA_ARGS__) -# define BW_LOG_WARNING(...) AE_TELED_WARNING(__VA_ARGS__) +# define BW_LOG_DEBUG(...) \ + do { \ + AE_TELED_DEBUG(__VA_ARGS__); \ + } while (false) +# define BW_LOG_WARNING(...) \ + do { \ + AE_TELED_WARNING(__VA_ARGS__); \ + } while (false) #else # define BW_LOG_DEBUG(...) # define BW_LOG_WARNING(...) diff --git a/examples/benches/send_message_delays/statistics_write.h b/examples/benches/send_message_delays/statistics_write.h index fb7db7cdb..441684ca0 100644 --- a/examples/benches/send_message_delays/statistics_write.h +++ b/examples/benches/send_message_delays/statistics_write.h @@ -49,38 +49,38 @@ struct Formatter { ctx.out().write( std::string_view{"test name,max us,99% us,50% us,min us\n"}); for (auto const& [name, statistics] : value.statistics_) { - ae::Format(ctx.out(), "{},{},{},{},{}\n", name, - statistics.max_value().count(), - statistics.get_99th_percentile().count(), - statistics.get_50th_percentile().count(), - statistics.min_value().count()); + FormatTo(ctx.out(), FormatScheme{"{},{},{},{},{}\n"}, name, + statistics.max_value().count(), + statistics.get_99th_percentile().count(), + statistics.get_50th_percentile().count(), + statistics.min_value().count()); } // print legend ctx.out().write(std::string_view{"raw results\nmessage num"}); for (auto [name, _] : value.statistics_) { - ae::Format(ctx.out(), ",{}", name); + FormatTo(ctx.out(), FormatScheme{",{}"}, name); } ctx.out().write(std::string_view{"\n"}); // print data for (std::size_t i = 0; i < value.test_msg_count_; ++i) { - ctx.out().stream() << i; + Formatter{}.Format(i, ctx); for (auto const& [_, statistics] : value.statistics_) { auto const& i_data = std::find_if( std::begin(statistics.raw_data()), std::end(statistics.raw_data()), [i](auto const& data) { return data.first == i; }); if (i_data == std::end(statistics.raw_data())) { - ctx.out().stream() << ",NA"; + ctx.out().write(std::string_view{",NA"}); } else if (!i_data->second) { - ctx.out().stream() << ",missed"; + ctx.out().write(std::string_view{",missed"}); } else { - ctx.out().stream() << ',' << i_data->second->count(); + FormatTo(ctx.out(), FormatScheme{",{}"}, i_data->second->count()); } } - ctx.out().stream() << '\n'; + ctx.out().write('\n'); } - ctx.out().stream() << '\n'; + ctx.out().write('\n'); } }; } // namespace ae diff --git a/examples/benches/send_messages_bandwidth/common/bandwidth.h b/examples/benches/send_messages_bandwidth/common/bandwidth.h index c00d230be..a747a419e 100644 --- a/examples/benches/send_messages_bandwidth/common/bandwidth.h +++ b/examples/benches/send_messages_bandwidth/common/bandwidth.h @@ -43,9 +43,10 @@ template <> struct Formatter { template void Format(bench::Bandwidth const& b, FormatContext& ctx) const { - ae::Format(ctx.out(), - "Bandwidth: {} bits/s Message count: {} Duration: {} ns", - b.bandwidth, b.message_count, b.duration.count()); + FormatTo(ctx.out(), + FormatScheme{ + "Bandwidth: {} bits/s Message count: {} Duration: {} ns"}, + b.bandwidth, b.message_count, b.duration.count()); } }; } // namespace ae diff --git a/examples/benches/send_messages_bandwidth/receiver/receiver_main.cpp b/examples/benches/send_messages_bandwidth/receiver/receiver_main.cpp index 86cf628f7..536312603 100644 --- a/examples/benches/send_messages_bandwidth/receiver/receiver_main.cpp +++ b/examples/benches/send_messages_bandwidth/receiver/receiver_main.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include "aether/all.h" #include "send_messages_bandwidth/receiver/receiver.h" diff --git a/examples/benches/send_messages_bandwidth/sender/sender_main.cpp b/examples/benches/send_messages_bandwidth/sender/sender_main.cpp index a61680491..3ec044dfd 100644 --- a/examples/benches/send_messages_bandwidth/sender/sender_main.cpp +++ b/examples/benches/send_messages_bandwidth/sender/sender_main.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include "aether/all.h" #include "send_messages_bandwidth/sender/sender.h" diff --git a/examples/cloud/cloud_test.cpp b/examples/cloud/cloud_test.cpp index 9b1a7e19e..0fd3eeac5 100644 --- a/examples/cloud/cloud_test.cpp +++ b/examples/cloud/cloud_test.cpp @@ -51,10 +51,9 @@ int AetherCloudExample() { /** * Construct a main aether application class. * It's include a Domain and Aether instances accessible by getter methods. - * It has Update, WaitUntil, Exit, IsExit, ExitCode methods to integrate it in - * your update loop. - * Also it has action context protocol implementation \see Action. - * To configure its creation \see AetherAppContext. + * It has Update, WaitUntil, Exit, IsExit, ExitCode methods to integrate it + * in your update loop. Also it has action context protocol implementation + * \see Action. To configure its creation \see AetherAppContext. */ auto aether_app = ae::cloud_test::construct_aether_app(); diff --git a/examples/cloud/main.cpp b/examples/cloud/main.cpp index 2dc33752e..fec5e656b 100644 --- a/examples/cloud/main.cpp +++ b/examples/cloud/main.cpp @@ -30,16 +30,16 @@ * \author Aether authors */ -#include - #include "aether/config.h" #include "aether/tele/tele.h" #include "aether/tele/tele_init.h" #if (defined(CM_ESP32)) -# include +# include + # include # include +# include #endif extern "C" void app_main(); diff --git a/opencode.json b/opencode.json index dfedfffa3..bbe8a84de 100644 --- a/opencode.json +++ b/opencode.json @@ -9,7 +9,7 @@ "model": "openai/gpt-5.5-fast", "reasoningEffort": "medium", "description": "The main agent to rule the others on the way to work on code.", - "prompt": "You are team-manager. You do not write code, build, or test directly. You coordinate specialized agents: @explorer finds relevant files and facts; @architect designs the solution and writes implementation instructions; @coder edits code from precise instructions; @builder validates CMake/Ninja builds and reports compiler or linker errors; @tester runs unit/smoke tests and reports results; @sanity-reviewer performs a fast task/architecture match check; @code-reviewer performs deep final code review. Coordination rule: run workflow stages sequentially. Never start @tester in the same step or same tool batch as @builder. Start @tester only after you have received a successful @builder report for the current implementation. Workflow: analyze the request; use @explorer when code context is needed; ask @architect for design and coder checklist; require user approval for medium/high-risk changes; ask @coder to implement; ask @builder to validate the build; if @builder reports build failure, send the report back to @coder and repeat; after build validation succeeds, ask @tester to run tests; if tests fail, send the report back to @coder and repeat build and tests; after tests pass, ask @sanity-reviewer for a fast task/architecture match check using the changed-file list reported by @coder, and tell @sanity-reviewer to ignore unrelated worktree changes outside that list; if sanity review blocks, route the issue to @coder for implementation mismatch or @architect for architecture mismatch, then rebuild and retest; only after sanity review approves, ask @code-reviewer for deep final review; if deep review blocks, route directly to @architect for revised instructions, then continue with @coder, @builder, @tester, @sanity-reviewer, and @code-reviewer again. Loop control: allow at most three implementation fix cycles for the same task. A fix cycle is @coder -> @builder -> @tester -> @sanity-reviewer -> @code-reviewer. After three failed cycles, or immediately when failures indicate design/API/ownership/lifetime/CMake/requirement mismatch, escalate to @architect to rethink the solution before more coding. If @code-reviewer reports the same issue twice, escalate to @architect. If @coder says the instructions are ambiguous or the fix would change the approved design, escalate to @architect.", + "prompt": "You are team-manager. You do not write code, build, or test directly. You coordinate specialized agents: @explorer finds relevant files and facts; @architect designs the solution and writes implementation instructions; @coder edits code from precise instructions; @builder validates CMake/Ninja builds and reports compiler or linker errors; @tester runs unit/smoke tests and reports results; @sanity-reviewer performs a fast task/architecture match check and clang-tidy check; @code-reviewer performs deep final code review. Coordination rule: run workflow stages sequentially. Never start @tester in the same step or same tool batch as @builder. Start @tester only after you have received a successful @builder report for the current implementation. Workflow: analyze the request; use @explorer when code context is needed; ask @architect for design and coder checklist; require user approval for medium/high-risk changes; ask @coder to implement; ask @builder to validate the build; if @builder reports build failure, send the report back to @coder and repeat; after build validation succeeds, ask @tester to run tests; if tests fail, send the report back to @coder and repeat build and tests; after tests pass, ask @sanity-reviewer for a fast task/architecture match check and clang-tidy check using the changed-file list reported by @coder, and tell @sanity-reviewer to ignore unrelated worktree changes outside that list; if sanity review blocks because of implementation mismatch or clang-tidy findings, route the issue to @coder; if sanity review blocks because of architecture mismatch, route the issue to @architect, then rebuild and retest; only after sanity review approves, ask @code-reviewer for deep final review; if deep review blocks, route directly to @architect for revised instructions, then continue with @coder, @builder, @tester, @sanity-reviewer, and @code-reviewer again. Loop control: allow at most three implementation fix cycles for the same task. A fix cycle is @coder -> @builder -> @tester -> @sanity-reviewer -> @code-reviewer. After three failed cycles, or immediately when failures indicate design/API/ownership/lifetime/CMake/requirement mismatch, escalate to @architect to rethink the solution before more coding. If @code-reviewer reports the same issue twice, escalate to @architect. If @coder says the instructions are ambiguous or the fix would change the approved design, escalate to @architect.", "permission": { "edit": "deny", "bash": "deny", @@ -32,7 +32,7 @@ "read": "allow", "external_directory": "deny" }, - "temperature": 0.1, + "temperature": 0.1 }, "architect": { "mode": "all", @@ -58,7 +58,7 @@ "model": "openai/gpt-5.5-fast", "reasoningEffort": "low", "description": "Write c++ code", - "prompt": "You are a focused C++ implementation agent. You receive precise instructions from @team-lead or @architect and implement only those instructions. Follow AGENTS.md, preserve existing style, avoid unrelated refactors, and keep changes minimal. If instructions are ambiguous, ask for clarification instead of inventing architecture. If a fix requires changing the architect-approved design or you are making a repeated attempt at the same failed issue, stop and ask @architect for revised instructions. Do not run or request build/test validation. After implementation, report what changed and let @team-lead coordinate validation. At the end of your response, report the actual files you changed under: Changed files. Include added, modified, deleted, or renamed files. Do not include files changed by other agents or the user.", + "prompt": "You are a focused C++ implementation agent. You receive precise instructions from @team-lead or @architect and implement only those instructions. Follow AGENTS.md, preserve existing style, avoid unrelated refactors, and keep changes minimal. If instructions are ambiguous, ask for clarification instead of inventing architecture. If a fix requires changing the architect-approved design or you are making a repeated attempt at the same failed issue, stop and ask @architect for revised instructions. When fixing clang-tidy findings from @sanity-reviewer, evaluate each warning before changing code. Fix warnings that indicate correctness, safety, portability, performance, or maintainability problems. Suppress or decline warnings only when the fix would harm performance, API ergonomics, intended behavior, or project conventions; explain each suppression or declined warning briefly. Do not run or request build/test validation. After implementation, report what changed and let @team-lead coordinate validation. At the end of your response, report the actual files you changed under: Changed files. Include added, modified, deleted, or renamed files. Do not include files changed by other agents or the user.", "permission": { "edit": "allow", "grep": "allow", @@ -119,12 +119,15 @@ "model": "openai/gpt-5.4-mini", "reasoningEffort": "low", "description": "Fast check that implementation matches the task and proposed architecture", - "prompt": "You are a fast implementation sanity reviewer. Review only the files listed in the changed-file list provided by @coder for the current task. Use the actual diff for those files to verify what changed. Ignore other modified files in the worktree unless they are explicitly included in @coder's changed-file list or explicitly assigned to this task. Check whether the reviewed changes match the user request and architect instructions. Check for missing requested behavior, unrelated changes within the reviewed files, obvious mismatches with the proposed architecture, and incomplete implementation. Do not perform deep C++ review. Do not review generated build artifacts, temporary files, logs, or unrelated files. Never edit files. Report: Reviewed files, Matches task, Matches architecture, Blocking mismatches, Approve or Block.", + "prompt": "You are a fast implementation sanity reviewer. Review only the files listed in the changed-file list provided by @coder for the current task. Use the actual diff for those files to verify what changed. Ignore other modified files in the worktree unless they are explicitly included in @coder's changed-file list or explicitly assigned to this task. Check whether the reviewed changes match the user request and architect instructions. Check for missing requested behavior, unrelated changes within the reviewed files, obvious mismatches with the proposed architecture, and incomplete implementation. Run clang-tidy on changed C++ source/header files from the reviewed file list using the project's .clang-tidy configuration. Use AGENTS.md or project instructions to find the build directory or compile_commands.json. Prefer running clang-tidy with -p pointing to the configured build directory when available. Report clang-tidy findings ordered from critical correctness issues to style/readability issues. Group repeated or cascaded warnings by root cause. Do not perform deep C++ design review. Do not review generated build artifacts, temporary files, logs, or unrelated files. Never edit files. Report: Reviewed files, Matches task, Matches architecture, Clang-tidy findings, Blocking mismatches, Approve or Block.", "permission": { + "read": "allow", + "glob": "allow", "grep": "allow", "edit": "deny", "bash": { "*": "deny", + "clang-tidy*": "allow", "git log*": "allow", "git diff*": "allow" } @@ -136,7 +139,7 @@ "model": "openai/gpt-5.5", "reasoningEffort": "high", "description": "Review code and validate if it solves the problem", - "prompt": "You are a strict C++ code reviewer. Review the current code diff against the user request and architect instructions. Focus on correctness, undefined behavior, object lifetime, ownership, async/task usage, persistence, CMake target propagation, cross-platform desktop/IoT behavior, performance, and security. Do not review generated build artifacts, temporary files, logs, or unrelated files. Never edit files. Mark repeated or design-level issues as Block. When you Block, route the issue to @architect for revised instructions rather than recommending a local coder fix. Report: Findings, Missing tests, Risk assessment, Approve or Block.", + "prompt": "You are a strict C++ code reviewer. Review the current code diff against the user request and architect instructions. Focus on correctness, undefined behavior, object lifetime, ownership, async/task usage, persistence, CMake target propagation, cross-platform desktop/IoT behavior, performance, and security. Review any clang-tidy suppressions, NOLINT comments, disabled checks, or declined clang-tidy findings. Verify each suppression is justified by performance, API ergonomics, intended behavior, or project conventions. Treat unjustified suppressions as Findings. Do not review generated build artifacts, temporary files, logs, or unrelated files. Never edit files. Mark repeated or design-level issues as Block. When you Block, route the issue to @architect for revised instructions rather than recommending a local coder fix. Report: Findings, Missing tests, Risk assessment, Approve or Block.", "permission": { "grep": "allow", "edit": "deny", diff --git a/tests/test-tele/test-tele.cpp b/tests/test-tele/test-tele.cpp index c31698e75..6c6110a0f 100644 --- a/tests/test-tele/test-tele.cpp +++ b/tests/test-tele/test-tele.cpp @@ -16,12 +16,15 @@ #include +#include #include +#include #include #include +#include +#include #include #include -#include #include "aether/config.h" @@ -86,6 +89,23 @@ AE_TAG(Test2, TestObj) AE_TAG(Test3, TestObj) namespace ae::tele::test_tele { +namespace { + +void AssertTimestampShape(std::string const& timestamp) { + TEST_ASSERT_EQUAL(15U, timestamp.size()); + TEST_ASSERT_EQUAL(':', timestamp[2]); + TEST_ASSERT_EQUAL(':', timestamp[5]); + TEST_ASSERT_EQUAL('.', timestamp[8]); + for (auto i = std::size_t{}; i < timestamp.size(); ++i) { + if ((i == 2) || (i == 5) || (i == 8)) { + continue; + } + TEST_ASSERT(std::isdigit(static_cast(timestamp[i])) != 0); + } +} + +} // namespace + void test_Register() { TEST_ASSERT_EQUAL(1, One.offset); TEST_ASSERT_EQUAL(2, Two.offset); @@ -126,13 +146,13 @@ struct TeleTrap final : public ITrap { log_lines_.front().emplace_back(std::to_string(tag.index())); } void InvokeTime(TimePoint time) override { - log_lines_.front().emplace_back(Format("{:%H:%M:%S}", time)); + log_lines_.front().emplace_back(Format("{:time}", time)); } void WriteLevel(Level level) override { - log_lines_.front().emplace_back(Format("{}", level)); + log_lines_.front().emplace_back(Level::text(level.value_)); } void WriteModule(Module const& module) override { - log_lines_.front().emplace_back(Format("{}", module)); + log_lines_.front().emplace_back(module.name); } void Location(std::string_view file, std::uint32_t line) override { auto pos = file.find_last_of("/\\"); @@ -220,6 +240,7 @@ void test_TeleConfigurations() { auto& log_line = tele_trap->log_lines_.front(); TEST_ASSERT_EQUAL(7, log_line.size()); TEST_ASSERT_EQUAL_STRING("12", log_line[0].c_str()); + AssertTimestampShape(log_line[1]); TEST_ASSERT_EQUAL_STRING("kDebug", log_line[2].c_str()); TEST_ASSERT_EQUAL_STRING("TestObj", log_line[3].c_str()); TEST_ASSERT_EQUAL_STRING(Format("test-tele.cpp:{}", remember_line).c_str(), @@ -446,6 +467,44 @@ void test_MergeStatisticsTrap() { #undef TELE_SINK #define TELE_SINK SinkType } + +void test_IoStreamTrapFullOutput() { + auto stream = std::ostringstream{}; + auto trap = IoStreamTrap{stream}; + auto const fixed_time = TimePoint{std::chrono::hours{3} + + std::chrono::minutes{4} + + std::chrono::seconds{5} + + std::chrono::microseconds{123456}}; + auto const tag = Tag{11, TestObj, "Test"}; + + trap.OpenLogLine(tag); + trap.InvokeTime(fixed_time); + trap.WriteLevel(Level{Level::kDebug}); + trap.WriteModule(TestObj); + trap.Location("src/test-tele.cpp", 42); + trap.TagName(tag.name); + trap.Blob(reinterpret_cast("message 12"), 10); + trap.CloseLogLine(tag); + + auto const output = stream.str(); + TEST_ASSERT_EQUAL_STRING( + " 12:[03:04:05.123456]:kDebug:TestObj:test-tele.cpp:42:Test:message " + "12\n", + output.c_str()); +} + +void test_IoStreamTrapLocationWithoutSeparatorUsesUnknownFile() { + auto stream = std::ostringstream{}; + auto trap = IoStreamTrap{stream}; + auto const tag = Tag{11, TestObj, "Test"}; + + trap.OpenLogLine(tag); + trap.Location("test-tele.cpp", 42); + trap.CloseLogLine(tag); + + auto const output = stream.str(); + TEST_ASSERT_EQUAL_STRING(" 12:UNKNOWN FILE:42\n", output.c_str()); +} void test_EnvTele() { AE_TELE_ENV(); } } // namespace ae::tele::test_tele @@ -457,6 +516,8 @@ int main() { RUN_TEST(ae::tele::test_tele::test_TeleConfigurations); RUN_TEST(ae::tele::test_tele::test_TeleProxyTrap); RUN_TEST(ae::tele::test_tele::test_MergeStatisticsTrap); + RUN_TEST(ae::tele::test_tele::test_IoStreamTrapFullOutput); + RUN_TEST(ae::tele::test_tele::test_IoStreamTrapLocationWithoutSeparatorUsesUnknownFile); RUN_TEST(ae::tele::test_tele::test_EnvTele); return UNITY_END(); diff --git a/tools/registrator/registrator.cpp b/tools/registrator/registrator.cpp index db51add1e..79d5dceca 100644 --- a/tools/registrator/registrator.cpp +++ b/tools/registrator/registrator.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include "aether/all.h" @@ -168,9 +169,9 @@ int AetherRegistrator(const std::string& ini_file, ae::AeContext{*aether_app}, aether_app, registrator_config.clients()}; registrator_action.registered_event().Subscribe([&](auto const& clients) { + constexpr auto format_client = ae::FormatScheme{"\nclient={},\n{}\n"}; for (auto const& c_conf : clients) { - std::cout << ae::Format("\nclient={},\n{}\n", c_conf.client_id, - c_conf.config); + ae::Format(std::cout, format_client, c_conf.client_id, c_conf.config); aether_app->aether()->CreateClient(c_conf.config, c_conf.client_id); } aether_app->Exit(0);