diff --git a/.github/workflows/ci-cd-multi-platforms.yml b/.github/workflows/ci-cd-multi-platforms.yml index a7364a39..d9af08e0 100644 --- a/.github/workflows/ci-cd-multi-platforms.yml +++ b/.github/workflows/ci-cd-multi-platforms.yml @@ -82,13 +82,13 @@ jobs: platforms: - { name: "Hydrogen", - user_config: "../config/user_config_hydrogen.h", - fs_init: "../../../../config/file_system_init.h", + user_config: "./config/user_config_hydrogen.h", + fs_init: "./config/file_system_init.h", } - { name: "Sodium", - user_config: "../config/user_config_sodium.h", - fs_init: "../../../../config/file_system_init.h", + user_config: "./config/user_config_sodium.h", + fs_init: "./config/file_system_init.h", } steps: @@ -138,6 +138,8 @@ jobs: git apply "../libhydrogen.patch" cd ../libsodium git apply "../libsodium.patch" + cd ../etl + git apply "../etl.patch" cd ../ cp "CMakeLists.libbcrypt" "libbcrypt/CMakeLists.txt" cp "CMakeLists.libhydrogen" "libhydrogen/CMakeLists.txt" @@ -192,8 +194,8 @@ jobs: platforms: - { name: "Optimized", - user_config: "../config/user_config_optimized.h", - fs_init: "../../../../config/file_system_init.h", + user_config: "./config/user_config_optimized.h", + fs_init: "./config/file_system_init.h", } steps: @@ -212,6 +214,8 @@ jobs: git apply "../libhydrogen.patch" cd ../libsodium git apply "../libsodium.patch" + cd ../etl + git apply "../etl.patch" cd ../ cp "CMakeLists.libbcrypt" "libbcrypt/CMakeLists.txt" cp "CMakeLists.libhydrogen" "libhydrogen/CMakeLists.txt" @@ -227,8 +231,8 @@ jobs: path: "projects/xtensa_lx6/vscode/aether-client-cpp" command: idf.py build -DCOMPILE_EXAMPLE=${{ matrix.compile_example }} - -DUSER_CONFIG=${{ matrix.platforms.user_config }} - -DFS_INIT="${{ matrix.platforms.fs_init }}" + -DUSER_CONFIG=../../../../../${{ matrix.platforms.user_config }} + -DFS_INIT="../../../../../${{ matrix.platforms.fs_init }}" -DAE_DISTILLATION=Off - name: Rename artifact diff --git a/.gitmodules b/.gitmodules index 1a1f776c..642d4137 100644 --- a/.gitmodules +++ b/.gitmodules @@ -50,4 +50,10 @@ path = third_party/ini.h url = https://github.com/giosali/ini.h branch = main - ignore = dirty \ No newline at end of file + ignore = dirty + +[submodule "third_party/etl"] + path = third_party/etl + url = https://github.com/ETLCPP/etl.git + branch = master + ignore = dirty diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index 5ca5fc71..3d1cbe98 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -19,22 +19,9 @@ set(AE_PROJECT_VERSION "1.0.0") option(AE_NO_STRIP_ALL "Do not apply --strip_all, useful for bloaty and similar tools " Off) option(AE_DISTILLATION "Build aether in distillation mode" OFF) -option(USER_CONFIG "Path to user provided configuration header file" "") -find_program(GIT_COMMAND git) -if ( GIT_COMMAND ) - # get current git version - execute_process( - COMMAND ${GIT_COMMAND} -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse --verify HEAD - OUTPUT_VARIABLE GIT_VERSION - ) - if ( NOT GIT_VERSION ) - message(WARNING "Not a git repo") - else() - string(STRIP ${GIT_VERSION} GIT_VERSION) - message(STATUS "get aether git version ${GIT_VERSION}") - endif() -endif() +set(USER_CONFIG "" CACHE PATH "Path to user provided configuration header file") +set(FS_INIT "" CACHE PATH "Path to user provided saved state header file") # list a common dependencies list(APPEND common_dependencies @@ -42,8 +29,12 @@ list(APPEND common_dependencies "../third_party/libhydrogen" "../third_party/libsodium" "../third_party/gcem" + "../third_party/etl" ) +# for etl +set(GIT_DIR_LOOKUP_POLICY ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) + list(APPEND aether_srcs "aether_app.cpp" "aether.cpp" @@ -55,7 +46,6 @@ list(APPEND aether_srcs "registration_cloud.cpp" "work_cloud.cpp" "server.cpp" - "statistics.cpp" "uid.cpp" "proof_of_work.cpp" "server_keys.cpp" @@ -206,6 +196,9 @@ list(APPEND port_srcs "port/file_systems/drivers/driver_header.cpp" "port/file_systems/drivers/driver_spifs.cpp") +list(APPEND statistics_srcs + "statistics/channel_statistics.cpp") + list(APPEND tele_srcs "tele/traps/io_stream_traps.cpp" "tele/traps/statistics_trap.cpp" @@ -213,13 +206,11 @@ list(APPEND tele_srcs message(STATUS "Cmake system name is ${CMAKE_SYSTEM_NAME}") -if(NOT CM_PLATFORM AND ( - CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR - CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - CMAKE_SYSTEM_NAME STREQUAL "Windows" OR - CMAKE_SYSTEM_NAME MATCHES ".*BSD")) - message(STATUS "Build for regular cmake project") +if(NOT CM_PLATFORM) + message(STATUS "Aether build for regular cmake project") set(REGULAR_CMAKE_PROJECT On) +else() + message(STATUS "Aether build for CM_PLATFORM=${CM_PLATFORM}") endif() if(REGULAR_CMAKE_PROJECT) @@ -252,6 +243,7 @@ if(REGULAR_CMAKE_PROJECT) ${client_messages_srcs} ${client_connections_srcs} ${port_srcs} + ${statistics_srcs} ${tele_srcs} ) @@ -264,7 +256,8 @@ if(REGULAR_CMAKE_PROJECT) bcrypt sodium hydrogen - gcem) + gcem + etl) target_link_libraries(${PROJECT_NAME} PRIVATE c-ares ) set(TARGET_NAME "${PROJECT_NAME}") @@ -292,6 +285,7 @@ else() ${client_messages_srcs} ${client_connections_srcs} ${port_srcs} + ${statistics_srcs} ${tele_srcs} INCLUDE_DIRS ${include_dirs} REQUIRES esp_wifi nvs_flash spiffs) @@ -307,7 +301,8 @@ else() bcrypt sodium hydrogen - gcem) + gcem + etl) else() #ERROR message(SEND_ERROR "You must specify the CMAKE version!") @@ -325,16 +320,31 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") target_link_libraries(${TARGET_NAME} PRIVATE ws2_32) endif() +find_program(GIT_COMMAND git) +if ( GIT_COMMAND ) + # get current git version + execute_process( + COMMAND ${GIT_COMMAND} -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse --verify HEAD + OUTPUT_VARIABLE GIT_VERSION + ) + if ( NOT GIT_VERSION ) + message(WARNING "Not a git repo") + else() + string(STRIP ${GIT_VERSION} GIT_VERSION) + message(STATUS "get aether git version ${GIT_VERSION}") + endif() +endif() + if (GIT_VERSION) target_compile_definitions(${TARGET_NAME} PUBLIC "AE_GIT_VERSION=\"${GIT_VERSION}\"") endif() target_compile_definitions(${TARGET_NAME} PUBLIC "AE_PROJECT_VERSION=\"${AE_PROJECT_VERSION}\"") -if (USER_CONFIG) +if (NOT "${USER_CONFIG}" STREQUAL "") target_compile_definitions(${TARGET_NAME} PUBLIC "USER_CONFIG=\"${USER_CONFIG}\"") endif() -if (FS_INIT) +if (NOT "${FS_INIT}" STREQUAL "") target_compile_definitions(${TARGET_NAME} PUBLIC "FS_INIT=\"${FS_INIT}\"") endif() @@ -342,7 +352,8 @@ if (AE_DISTILLATION) target_compile_definitions(${TARGET_NAME} PUBLIC "AE_DISTILLATION=1") endif() -if(_AE_REG_CLOUD_IP) +# for debug purposes only, set registration server ip address +if(NOT "${_AE_REG_CLOUD_IP}" STREQUAL "") target_compile_definitions(${TARGET_NAME} PUBLIC "_AE_REG_CLOUD_IP=\"${_AE_REG_CLOUD_IP}\"") endif() diff --git a/aether/ae_actions/ae_actions_tele.h b/aether/ae_actions/ae_actions_tele.h index 2520717a..79aa6c8e 100644 --- a/aether/ae_actions/ae_actions_tele.h +++ b/aether/ae_actions/ae_actions_tele.h @@ -56,5 +56,7 @@ AE_TAG(kGetClientCloudConnectionServerListIsOver, kAeActions) AE_TAG(kPing, kAeActions) AE_TAG(kPingSend, kAeActions) AE_TAG(kPingWriteError, kAeActions) +AE_TAG(kPingTimeout, kAeActions) +AE_TAG(kPingTimeoutError, kAeActions) #endif // AETHER_AE_ACTIONS_AE_ACTIONS_TELE_H_ diff --git a/aether/ae_actions/ping.cpp b/aether/ae_actions/ping.cpp index 84d6e8c7..7775f170 100644 --- a/aether/ae_actions/ping.cpp +++ b/aether/ae_actions/ping.cpp @@ -17,6 +17,7 @@ #include "aether/ae_actions/ping.h" #include +#include #include "aether/api_protocol/packet_builder.h" #include "aether/methods/work_server_api/authorized_api.h" @@ -33,6 +34,7 @@ Ping::Ping(ActionContext action_context, Server::ptr const& server, server_stream_{&server_stream}, ping_interval_{ping_interval}, read_client_safe_api_gate_{protocol_context_, client_safe_api_}, + repeat_count_{}, state_{State::kWaitLink}, state_changed_sub_{state_.changed_event().Subscribe( [this](auto) { Action::Trigger(); })}, @@ -56,8 +58,7 @@ TimePoint Ping::Update(TimePoint current_time) { case State::kSendPing: SendPing(current_time); break; - case State::kWaitResponse: // TODO: response timeout and response - // statistics + case State::kWaitResponse: case State::kWaitInterval: break; case State::kError: @@ -66,12 +67,11 @@ TimePoint Ping::Update(TimePoint current_time) { } } + if (state_.get() == State::kWaitResponse) { + return WaitResponse(current_time); + } if (state_.get() == State::kWaitInterval) { - if ((last_ping_time_ + ping_interval_) <= current_time) { - state_ = State::kSendPing; - } else { - return last_ping_time_ + ping_interval_; - } + return WaitInterval(current_time); } return current_time; @@ -79,8 +79,8 @@ TimePoint Ping::Update(TimePoint current_time) { void Ping::SendPing(TimePoint current_time) { AE_TELE_DEBUG(kPingSend, "Send ping"); - last_ping_time_ = current_time; auto request_id = RequestId::GenRequestId(); + ping_times_.push(std::make_pair(request_id, current_time)); auto packet = PacketBuilder{ protocol_context_, PackMessage{AuthorizedApi{}, @@ -100,18 +100,69 @@ void Ping::SendPing(TimePoint current_time) { }); // Wait for response - SendResult::OnResponse(protocol_context_, request_id, - [&](ApiParser&) { PingResponse(); }); + SendResult::OnResponse( + protocol_context_, request_id, + [&, req_id{request_id}](ApiParser&) { PingResponse(req_id); }); state_ = State::kWaitResponse; } -void Ping::PingResponse() { +TimePoint Ping::WaitInterval(TimePoint current_time) { + auto const& ping_time = ping_times_.back().second; + if ((ping_time + ping_interval_) > current_time) { + return ping_time + ping_interval_; + } + state_ = State::kSendPing; + return current_time; +} + +TimePoint Ping::WaitResponse(TimePoint current_time) { + auto channel_ptr = channel_.Lock(); + assert(channel_ptr); + auto timeout = channel_ptr->expected_ping_time(); + + auto const& ping_time = ping_times_.back().second; + if ((ping_time + timeout) > current_time) { + return ping_time + timeout; + } + // timeout + AE_TELE_INFO(kPingTimeout, "Timeout is {:%S}", timeout); + if (repeat_count_ >= kMaxRepeatPingCount) { + AE_TELE_ERROR(kPingTimeoutError, "Ping repeat count exceeded"); + state_ = State::kError; + } else { + repeat_count_++; + state_ = State::kSendPing; + } + + return current_time; +} + +void Ping::PingResponse(RequestId request_id) { + auto request_time = [&]() -> std::optional { + for (auto const& [req_id, time] : ping_times_) { + if (req_id == request_id) { + return time; + } + } + return std::nullopt; + }(); + + if (!request_time) { + AE_TELED_DEBUG("Got lost, or not our ping request"); + return; + } + + repeat_count_ = 0; auto current_time = Now(); auto ping_duration = - std::chrono::duration_cast(current_time - last_ping_time_); + std::chrono::duration_cast(current_time - *request_time); AE_TELED_DEBUG("Ping received by {:%S} s", ping_duration); + auto channel_ptr = channel_.Lock(); + assert(channel_ptr); + channel_ptr->AddPingTime(ping_duration); + state_ = State::kWaitInterval; } diff --git a/aether/ae_actions/ping.h b/aether/ae_actions/ping.h index 0e4c29ee..50f4df08 100644 --- a/aether/ae_actions/ping.h +++ b/aether/ae_actions/ping.h @@ -19,6 +19,8 @@ #include +#include "etl/circular_buffer.h" + #include "aether/common.h" #include "aether/server.h" #include "aether/channel.h" @@ -41,6 +43,9 @@ class Ping : public Action { kError, }; + static constexpr std::uint8_t kMaxRepeatPingCount = 5; + static constexpr std::uint8_t kMaxStorePingTimes = kMaxRepeatPingCount * 2; + public: Ping(ActionContext action_context, Server::ptr const& server, Channel::ptr const& channel, ByteStream& server_stream, @@ -53,7 +58,9 @@ class Ping : public Action { private: void SendPing(TimePoint current_time); - void PingResponse(); + TimePoint WaitInterval(TimePoint current_time); + TimePoint WaitResponse(TimePoint current_time); + void PingResponse(RequestId request_id); PtrView server_; PtrView channel_; @@ -64,7 +71,10 @@ class Ping : public Action { ClientSafeApi client_safe_api_; ProtocolReadGate read_client_safe_api_gate_; - TimePoint last_ping_time_; + std::size_t repeat_count_; + etl::circular_buffer, kMaxStorePingTimes> + ping_times_; + Subscription write_subscription_; StateMachine state_; Subscription state_changed_sub_; diff --git a/aether/channel.cpp b/aether/channel.cpp index c5b07af6..f5ec9c88 100644 --- a/aether/channel.cpp +++ b/aether/channel.cpp @@ -18,24 +18,29 @@ namespace ae { -Channel::Channel(Domain* domain) : Obj{domain} {} +Channel::Channel(Domain* domain) + : Obj{domain}, channel_statistics{domain->CreateObj()} {} -// TODO: find a right place for this -Duration Channel::FirstRequestDuration(TokenType /*adapter_token*/, - TokenType /*location_token*/, - float /*percentile*/) const { - return std::chrono::milliseconds(100); +void Channel::AddConnectionTime(Duration connection_time) { + channel_statistics->AddConnectionTime(std::move(connection_time)); } -Duration Channel::RequestDuration(TokenType /*adapter_token*/, - TokenType /*location_token*/, - float /*percentile*/) const { - return std::chrono::milliseconds(100); +void Channel::AddPingTime(Duration ping_time) { + channel_statistics->AddPingTime(std::move(ping_time)); } -Duration Channel::ConnectionDuration(TokenType /*adapter_token*/, - TokenType /*location_token*/, - float /*percentile*/) const { - return std::chrono::milliseconds(100); +Duration Channel::expected_connection_time() const { + if (channel_statistics->connection_time_statistics().empty()) { + return default_connection_time; + } + return channel_statistics->connection_time_statistics().percentile<99>(); } + +Duration Channel::expected_ping_time() const { + if (channel_statistics->ping_time_statistics().empty()) { + return default_ping_time; + } + return channel_statistics->ping_time_statistics().percentile<99>(); +} + } // namespace ae diff --git a/aether/channel.h b/aether/channel.h index 0a59f44a..259d43de 100644 --- a/aether/channel.h +++ b/aether/channel.h @@ -17,12 +17,9 @@ #ifndef AETHER_CHANNEL_H_ #define AETHER_CHANNEL_H_ -#include -#include - #include "aether/address.h" #include "aether/obj/obj.h" -#include "aether/statistics.h" +#include "aether/statistics/channel_statistics.h" namespace ae { class Aether; @@ -33,23 +30,23 @@ class Channel : public Obj { Channel() = default; public: - using TokenType = std::uint32_t; - explicit Channel(Domain* domain); - AE_OBJECT_REFLECT(AE_MMBRS(address, statistics_)) + AE_OBJECT_REFLECT(AE_MMBRS(address, default_connection_time, + default_ping_time, channel_statistics)) - Duration FirstRequestDuration(TokenType adapter_token, - TokenType location_token, - float percentile) const; - Duration RequestDuration(TokenType adapter_token, TokenType location_token, - float percentile) const; - Duration ConnectionDuration(TokenType adapter_token, TokenType location_token, - float percentile) const; + void AddConnectionTime(Duration connection_time); + void AddPingTime(Duration ping_time); + + Duration expected_connection_time() const; + Duration expected_ping_time() const; - // Serializable UnifiedAddress address; - std::map statistics_; + Duration default_connection_time = std::chrono::seconds{5}; + Duration default_ping_time = std::chrono::seconds{1}; + + private: + ChannelStatistics::ptr channel_statistics; }; } // namespace ae diff --git a/aether/config.h b/aether/config.h index 099e3de4..1a220db1 100644 --- a/aether/config.h +++ b/aether/config.h @@ -162,6 +162,16 @@ # define AE_SAFE_STREAM_RTO_GROW_FACTOR 1.5 #endif // AE_SAFE_STREAM_RTO_GROW_FACTOR +// window size for connection statistics +#ifndef AE_STATISTICS_CONNECTION_WINDOW_SIZE +# define AE_STATISTICS_CONNECTION_WINDOW_SIZE 10 +#endif + +// window size for server answear to ping statistics +#ifndef AE_STATISTICS_PING_WINDOW_SIZE +# define AE_STATISTICS_PING_WINDOW_SIZE 100 +#endif + // Telemetry configuration // Compilation info // Environment info diff --git a/aether/statistics.h b/aether/statistics.h deleted file mode 100644 index 99e60676..00000000 --- a/aether/statistics.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_STATISTICS_H_ -#define AETHER_STATISTICS_H_ - -#include -#include - -#include "aether/obj/obj.h" - -namespace ae { -class Adapter; - -// template -// ostream_impl& operator&(ostream_impl& s, -// const Value1& t) { -// return s << t.time_point << t.duration; -// } -// template -// istream_impl& operator&(istream_impl& s, -// Statistics::Value& t) { -// return s >> t.time_point >> t.duration; -// } -// -// template -// ostream_impl& operator<<(ostream_impl& s, -// const Statistics::Duration& t) { -// std::uint32_t v = 0;//t.duration; -// return s << v; -// } -// template -// istream_impl& operator>>(istream_impl& s, -// Statistics::Duration& t) { -// std::uint32_t v; -// s >> v; -// // t = v; -// return s; -// } - -class Statistics : public Obj { - AE_OBJECT(Statistics, Obj, 0) - Statistics() = default; - - public: - ae::Duration FirstRequestDuration(float percentile) const; - ae::Duration RequestDuration(float percentile) const; - ae::Duration ConnectionDuration(float percentile) const; - -#ifdef AE_DISTILLATION - Statistics(Domain* domain); -#endif // AE_DISTILLATION - - AE_OBJECT_REFLECT(AE_MMBRS(first_requests_, requests_)) - - struct Value1 { - AE_REFLECT_MEMBERS(time_point, duration) - TimePoint time_point; - Duration duration; - }; - std::vector first_requests_; - std::vector requests_; -}; - -} // namespace ae - -#endif // AETHER_STATISTICS_H_ */ diff --git a/aether/statistics.cpp b/aether/statistics/channel_statistics.cpp similarity index 50% rename from aether/statistics.cpp rename to aether/statistics/channel_statistics.cpp index 6677aa6f..29aa31ad 100644 --- a/aether/statistics.cpp +++ b/aether/statistics/channel_statistics.cpp @@ -14,27 +14,16 @@ * limitations under the License. */ -#include "aether/statistics.h" +#include "aether/statistics/channel_statistics.h" namespace ae { +ChannelStatistics::ChannelStatistics(Domain* domain) : Base{domain} {} -Duration Statistics::FirstRequestDuration(float /*percentile*/) const { - return std::chrono::milliseconds(100); +void ChannelStatistics::AddConnectionTime(Duration duration) { + connection_time_statistics_.Add(std::move(duration)); } -Duration Statistics::RequestDuration(float /*percentile*/) const { - return std::chrono::milliseconds(100); +void ChannelStatistics::AddPingTime(Duration duration) { + ping_time_statistics_.Add(std::move(duration)); } - -Duration Statistics::ConnectionDuration(float /*percentile*/) const { - return std::chrono::milliseconds(100); -} - -#ifdef AE_DISTILLATION -Statistics::Statistics(Domain* domain) : Obj{domain} { - first_requests_.push_back({ClockType::now(), std::chrono::milliseconds(200)}); - requests_.push_back({ClockType::now(), std::chrono::milliseconds(100)}); -} -#endif // AE_DISTILLATION - } // namespace ae diff --git a/aether/statistics/channel_statistics.h b/aether/statistics/channel_statistics.h new file mode 100644 index 00000000..e98ba74e --- /dev/null +++ b/aether/statistics/channel_statistics.h @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_STATISTICS_CHANNEL_STATISTICS_H_ +#define AETHER_STATISTICS_CHANNEL_STATISTICS_H_ + +#include "aether/config.h" +#include "aether/common.h" +#include "aether/obj/obj.h" + +#include "aether/statistics/statistic_counter.h" + +namespace ae { +class ChannelStatistics final : public Obj { + AE_OBJECT(ChannelStatistics, Obj, 0) + ChannelStatistics() = default; + + static constexpr std::size_t kConnectionWindowSize = + AE_STATISTICS_CONNECTION_WINDOW_SIZE; + static constexpr std::size_t kPingWindowSize = AE_STATISTICS_PING_WINDOW_SIZE; + + using ConnectionTimeStatistics = + StatisticsCounter; + using PingTimeStatistics = StatisticsCounter; + + public: + ChannelStatistics(Domain* domain); + + AE_OBJECT_REFLECT(AE_MMBRS(connection_time_statistics_, + ping_time_statistics_)) + + void AddConnectionTime(Duration duration); + void AddPingTime(Duration duration); + + ConnectionTimeStatistics const& connection_time_statistics() const { + return connection_time_statistics_; + } + PingTimeStatistics const& ping_time_statistics() const { + return ping_time_statistics_; + } + + private: + ConnectionTimeStatistics connection_time_statistics_; + PingTimeStatistics ping_time_statistics_; +}; + +} // namespace ae + +#endif // AETHER_STATISTICS_CHANNEL_STATISTICS_H_ diff --git a/aether/statistics/statistic_counter.h b/aether/statistics/statistic_counter.h new file mode 100644 index 00000000..4b3209c1 --- /dev/null +++ b/aether/statistics/statistic_counter.h @@ -0,0 +1,138 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_STATISTICS_STATISTIC_COUNTER_H_ +#define AETHER_STATISTICS_STATISTIC_COUNTER_H_ + +#include +#include +#include +#include + +#include "etl/circular_buffer.h" + +#include "aether/common.h" +#include "aether/mstream.h" +#include "aether/format/format.h" + +namespace ae { +template > +class StatisticsCounter final { + friend struct Formatter>; + + public: + StatisticsCounter() noexcept = default; + + AE_CLASS_COPY_MOVE(StatisticsCounter) + + template ())>>>> + void Insert(TIterator const& begin, TIterator const& end) { + value_buffer_.push(begin, end); + // keep buffer sorted for easier percentile calculation + std::sort(std::begin(value_buffer_), std::end(value_buffer_), Comparator{}); + } + + void Add(TValue&& value) { + value_buffer_.push(std::move(value)); + // TODO: maybe implement insert method for etl::icircular_buffer and use it + // with std::lower_bound + + // keep buffer sorted for easier percentile calculation + std::sort(std::begin(value_buffer_), std::end(value_buffer_), Comparator{}); + } + + TValue const& max() const { + assert(!value_buffer_.empty()); + return value_buffer_.back(); + } + + TValue const& min() const { + assert(!value_buffer_.empty()); + return value_buffer_.front(); + } + + /** + * \brief Get a particular percentile value. + * Percentile must be in range [0, 100]. + */ + template + TValue const& percentile() const { + static_assert((Percentile >= 0) && (Percentile <= 100), + "Percentile must be in [0,100]% range"); + + if constexpr (Percentile == 0) { + return min(); + } else if constexpr (Percentile == 100) { + return max; + } else { + assert(!value_buffer_.empty()); + auto index = static_cast( // + std::round( // + Percentile * static_cast(value_buffer_.size() - 1) / + 100.0)); + return value_buffer_[index]; + } + } + + std::size_t size() const { return value_buffer_.size(); } + bool empty() const { return value_buffer_.empty(); } + + template + friend omstream& operator<<( + omstream& os, + StatisticsCounter const& value) { + os << static_cast(value.value_buffer_.size()); + for (auto const& v : value.value_buffer_) { + os << v; + } + return os; + } + + template + friend imstream& operator>>( + imstream& is, + StatisticsCounter& value) { + typename Ib::size_type size; + is >> size; + for (std::size_t i = 0; (i < size) && (i < value.value_buffer_.max_size()); + ++i) { + TValue temp; + is >> temp; + value.value_buffer_.push(std::move(temp)); + } + return is; + } + + private: + etl::circular_buffer value_buffer_; +}; + +template +struct Formatter> + : public Formatter> { + template + void Format(StatisticsCounter const& value, + FormatContext& ctx) const { + Formatter>{}.Format(value.value_buffer_, + ctx); + } +}; + +} // namespace ae +#endif // AETHER_STATISTICS_STATISTIC_COUNTER_H_ diff --git a/aether/tele/env/compilation_options.h b/aether/tele/env/compilation_options.h index 88026305..a308e41b 100644 --- a/aether/tele/env/compilation_options.h +++ b/aether/tele/env/compilation_options.h @@ -107,6 +107,8 @@ constexpr inline auto _compile_options_list = std::array{ _OPTION(AE_SUPPORT_SPIFS_V1_FS), _OPTION(AE_SUPPORT_SPIFS_V2_FS), _OPTION(AE_SAFE_STREAM_RTO_GROW_FACTOR), + _OPTION(AE_STATISTICS_CONNECTION_WINDOW_SIZE), + _OPTION(AE_STATISTICS_PING_WINDOW_SIZE), _OPTION(AE_TELE_ENABLED), _OPTION(AE_TELE_COMPILATION_INFO), _OPTION(AE_TELE_RUNTIME_INFO), diff --git a/aether/transport/low_level/tcp/unix_tcp.cpp b/aether/transport/low_level/tcp/unix_tcp.cpp index 06c56cf4..066ba353 100644 --- a/aether/transport/low_level/tcp/unix_tcp.cpp +++ b/aether/transport/low_level/tcp/unix_tcp.cpp @@ -318,7 +318,6 @@ void UnixTcpTransport::UnixPacketSendAction::Send() { } return; } - AE_TELED_DEBUG("Data was sent"); state_ = State::kSuccess; } diff --git a/git_init.bat b/git_init.bat index 5d9f0a50..6553b044 100644 --- a/git_init.bat +++ b/git_init.bat @@ -23,6 +23,8 @@ cd ../libhydrogen git apply "../libhydrogen.patch" cd ../libsodium git apply "../libsodium.patch" +cd ../etl +git apply "../etl.patch" cd ../ copy "CMakeLists.libbcrypt" "libbcrypt/CMakeLists.txt" copy "CMakeLists.libhydrogen" "libhydrogen/CMakeLists.txt" diff --git a/git_init.ps1 b/git_init.ps1 index 7e3d141d..b69a7bb7 100644 --- a/git_init.ps1 +++ b/git_init.ps1 @@ -24,6 +24,8 @@ cd ../libhydrogen git apply "../libhydrogen.patch" cd ../libsodium git apply "../libsodium.patch" +cd ../etl +git apply "../etl.patch" cd ../ Copy-Item -Path "CMakeLists.libbcrypt" -Destination "libbcrypt/CMakeLists.txt" -Force Copy-Item -Path "CMakeLists.libhydrogen" -Destination "libhydrogen/CMakeLists.txt" -Force diff --git a/git_init.sh b/git_init.sh index dbbc0742..acc93a08 100755 --- a/git_init.sh +++ b/git_init.sh @@ -25,6 +25,8 @@ cd ../libhydrogen git apply "../libhydrogen.patch" cd ../libsodium git apply "../libsodium.patch" +cd ../etl +git apply "../etl.patch" cd ../ cp "CMakeLists.libbcrypt" "libbcrypt/CMakeLists.txt" cp "CMakeLists.libhydrogen" "libhydrogen/CMakeLists.txt" diff --git a/projects/espressif_riscv/platformio/aether-client-cpp/CMakeLists.txt b/projects/espressif_riscv/platformio/aether-client-cpp/CMakeLists.txt index 7154b12a..0f2259a8 100644 --- a/projects/espressif_riscv/platformio/aether-client-cpp/CMakeLists.txt +++ b/projects/espressif_riscv/platformio/aether-client-cpp/CMakeLists.txt @@ -28,11 +28,11 @@ set(CMAKE_BUILD_TYPE "MinSizeRel") add_compile_definitions(CM_ESP32) if (NOT USER_CONFIG) - set(USER_CONFIG "../config/user_config_optimized.h") + set(USER_CONFIG "${CMAKE_CURRENT_LIST_DIR}/../../../config/user_config_optimized.h") endif() if (NOT FS_INIT) - set(FS_INIT "../../../../config/file_system_init.h") + set(FS_INIT "${CMAKE_CURRENT_LIST_DIR}/../../../../config/file_system_init.h") endif() # enable doubles in unity tests diff --git a/projects/espressif_riscv/vscode/aether-client-cpp/CMakeLists.txt b/projects/espressif_riscv/vscode/aether-client-cpp/CMakeLists.txt index 483860aa..d094b776 100644 --- a/projects/espressif_riscv/vscode/aether-client-cpp/CMakeLists.txt +++ b/projects/espressif_riscv/vscode/aether-client-cpp/CMakeLists.txt @@ -30,11 +30,11 @@ if(NOT COMPILE_EXAMPLE ) endif() if (NOT USER_CONFIG) - set(USER_CONFIG "../config/user_config_optimized.h") + set(USER_CONFIG "${CMAKE_CURRENT_LIST_DIR}/../../../config/user_config_optimized.h") endif() if (NOT FS_INIT) - set(FS_INIT "../../../../config/file_system_init.h") + set(FS_INIT "${CMAKE_CURRENT_LIST_DIR}/../../../../config/file_system_init.h") endif() # enable doubles in unity tests diff --git a/projects/espressif_riscv/vscode/aether-client-cpp/sdkconfig b/projects/espressif_riscv/vscode/aether-client-cpp/sdkconfig index 7744c1e2..8474c006 100644 --- a/projects/espressif_riscv/vscode/aether-client-cpp/sdkconfig +++ b/projects/espressif_riscv/vscode/aether-client-cpp/sdkconfig @@ -1398,10 +1398,6 @@ CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y # CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF -CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 -CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 -CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 -# CONFIG_LWIP_PPP_SUPPORT is not set CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 @@ -1748,18 +1744,6 @@ CONFIG_SPIFFS_USE_MTIME=y # end of Debug Configuration # end of SPIFFS Configuration -# -# Unity unit testing library -# -CONFIG_UNITY_ENABLE_FLOAT=y -CONFIG_UNITY_ENABLE_DOUBLE=y -# CONFIG_UNITY_ENABLE_64BIT is not set -# CONFIG_UNITY_ENABLE_COLOR is not set -CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y -# CONFIG_UNITY_ENABLE_FIXTURE is not set -# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set -# end of Unity unit testing library - # # Virtual file system # diff --git a/projects/xtensa_lx6/platformio/aether-client-cpp/CMakeLists.txt b/projects/xtensa_lx6/platformio/aether-client-cpp/CMakeLists.txt index 7154b12a..98e25df1 100644 --- a/projects/xtensa_lx6/platformio/aether-client-cpp/CMakeLists.txt +++ b/projects/xtensa_lx6/platformio/aether-client-cpp/CMakeLists.txt @@ -32,7 +32,7 @@ if (NOT USER_CONFIG) endif() if (NOT FS_INIT) - set(FS_INIT "../../../../config/file_system_init.h") + set(FS_INIT "${CMAKE_CURRENT_LIST_DIR}/../../../../config/file_system_init.h") endif() # enable doubles in unity tests diff --git a/projects/xtensa_lx6/vscode/aether-client-cpp/CMakeLists.txt b/projects/xtensa_lx6/vscode/aether-client-cpp/CMakeLists.txt index 483860aa..d094b776 100644 --- a/projects/xtensa_lx6/vscode/aether-client-cpp/CMakeLists.txt +++ b/projects/xtensa_lx6/vscode/aether-client-cpp/CMakeLists.txt @@ -30,11 +30,11 @@ if(NOT COMPILE_EXAMPLE ) endif() if (NOT USER_CONFIG) - set(USER_CONFIG "../config/user_config_optimized.h") + set(USER_CONFIG "${CMAKE_CURRENT_LIST_DIR}/../../../config/user_config_optimized.h") endif() if (NOT FS_INIT) - set(FS_INIT "../../../../config/file_system_init.h") + set(FS_INIT "${CMAKE_CURRENT_LIST_DIR}/../../../../config/file_system_init.h") endif() # enable doubles in unity tests diff --git a/third_party/etl b/third_party/etl new file mode 160000 index 00000000..a12dbbd9 --- /dev/null +++ b/third_party/etl @@ -0,0 +1 @@ +Subproject commit a12dbbd91174e895bdd5492826b0226b9668fc5f diff --git a/third_party/etl.patch b/third_party/etl.patch new file mode 100644 index 00000000..ce99f068 --- /dev/null +++ b/third_party/etl.patch @@ -0,0 +1,21 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 9f81b8bb..f1b05245 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -39,8 +39,6 @@ target_include_directories(${PROJECT_NAME} ${INCLUDE_SPECIFIER} INTERFACE + + target_link_libraries(${PROJECT_NAME} INTERFACE) + +-# only install if top level project +-if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) + # Steps here based on excellent guide: https://dominikberner.ch/cmake-interface-lib/ + # Which also details all steps + include(CMakePackageConfigHelpers) +@@ -74,7 +72,6 @@ if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/etl DESTINATION include) + +-endif() + + if (BUILD_TESTS) + enable_testing()