diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..c4373515 --- /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 cf33c7e0..4bccdb12 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 6c50d5b4..d78525a8 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 bf5aa504..f53c81dc 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 a45e2552..aa9df6c1 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 a0892f8d..0a35a029 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 d071ad11..48987085 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 7e4131b5..66bf1e45 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 8156f650..63bbcfd3 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 f9c078df..92a6f88a 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 92551e60..bda92b07 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 c99b9d00..f49e5dd3 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 16f4774e..862dbf42 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 74ab9185..3ea5b807 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 f998d8b5..41a33675 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 d9faf55c..71ab5067 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 a60bbc41..6a972355 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 a53ab7a8..d7c9160f 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 7dba8c97..89e008a3 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 945b7615..3301d9fa 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 3fc5b2af..a223d9ea 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 85bdaa3c..11251dcc 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 50d451ac..bf8044b9 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 3a18370e..3404a694 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 fb7db7cd..441684ca 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 c00d230b..a747a419 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 86cf628f..53631260 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 a6168049..3ec044df 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 9b1a7e19..0fd3eeac 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 2dc33752..fec5e656 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 dfedfffa..bbe8a84d 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 c31698e7..6c6110a0 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 db51add1..79d5dcec 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);