diff --git a/.gitmodules b/.gitmodules index 18e3dc04b..f8462c270 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ path = external/loki-mq url = https://github.com/loki-project/loki-mq branch = dev +[submodule "external/sqlite_orm"] + path = external/sqlite_orm + url = https://github.com/fnc12/sqlite_orm diff --git a/CMakeLists.txt b/CMakeLists.txt index a0a9bc955..04ef7ba82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,6 +305,7 @@ if(SUBMODULE_CHECK) check_submodule(external/ghc-filesystem) check_submodule(external/date) check_submodule(external/pybind11) + check_submodule(external/sqlite_orm) if (NOT WIN32) # we grab libuv for windows separately in win32-setup/libuv. see note in cmake/win32.cmake. check_submodule(external/libuv) endif() @@ -324,6 +325,8 @@ add_subdirectory(external/nlohmann EXCLUDE_FROM_ALL) add_subdirectory(external/cxxopts EXCLUDE_FROM_ALL) add_subdirectory(external/date EXCLUDE_FROM_ALL) +include_directories(SYSTEM external/sqlite_orm/include) + add_subdirectory(vendor) if(ANDROID) diff --git a/external/sqlite_orm b/external/sqlite_orm new file mode 160000 index 000000000..f7ef17a6b --- /dev/null +++ b/external/sqlite_orm @@ -0,0 +1 @@ +Subproject commit f7ef17a6bde6162e8b487deb36519bace412920a diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index e87cb6673..8192b3303 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -34,13 +34,13 @@ target_link_libraries(lokinet-util PUBLIC filesystem date::date lokimq + sqlite3 ) if(ANDROID) target_link_libraries(lokinet-util PUBLIC log) endif() - add_library(lokinet-platform # for networking ev/ev.cpp @@ -156,6 +156,7 @@ add_library(liblokinet path/pathbuilder.cpp path/pathset.cpp path/transit_hop.cpp + peerstats/peer_db.cpp pow.cpp profiling.cpp router/outbound_message_handler.cpp diff --git a/llarp/peerstats/peer_db.cpp b/llarp/peerstats/peer_db.cpp new file mode 100644 index 000000000..a15356d2e --- /dev/null +++ b/llarp/peerstats/peer_db.cpp @@ -0,0 +1,67 @@ +#include + +namespace llarp +{ + PeerStats& + PeerStats::operator+=(const PeerStats& other) + { + numConnectionAttempts += other.numConnectionAttempts; + numConnectionSuccesses += other.numConnectionSuccesses; + numConnectionRejections += other.numConnectionRejections; + numConnectionTimeouts += other.numConnectionTimeouts; + + numPathBuilds += other.numPathBuilds; + numPacketsAttempted += other.numPacketsAttempted; + numPacketsSent += other.numPacketsSent; + numPacketsDropped += other.numPacketsDropped; + numPacketsResent += other.numPacketsResent; + + numDistinctRCsReceived += other.numDistinctRCsReceived; + numLateRCs += other.numLateRCs; + + peakBandwidthBytesPerSec = std::max(peakBandwidthBytesPerSec, other.peakBandwidthBytesPerSec); + longestRCReceiveInterval = std::max(longestRCReceiveInterval, other.longestRCReceiveInterval); + mostExpiredRC = std::max(mostExpiredRC, other.mostExpiredRC); + + return *this; + } + + bool + PeerStats::operator==(const PeerStats& other) + { + return numConnectionAttempts == other.numConnectionAttempts + and numConnectionSuccesses == other.numConnectionSuccesses + and numConnectionRejections == other.numConnectionRejections + and numConnectionTimeouts == other.numConnectionTimeouts + + and numPathBuilds == other.numPathBuilds + and numPacketsAttempted == other.numPacketsAttempted + and numPacketsSent == other.numPacketsSent and numPacketsDropped == other.numPacketsDropped + and numPacketsResent == other.numPacketsResent + + and numDistinctRCsReceived == other.numDistinctRCsReceived + and numLateRCs == other.numLateRCs + + and peakBandwidthBytesPerSec == peakBandwidthBytesPerSec + and longestRCReceiveInterval == longestRCReceiveInterval and mostExpiredRC == mostExpiredRC; + } + + void + PeerDb::accumulatePeerStats(const RouterID& routerId, const PeerStats& delta) + { + std::lock_guard gaurd(m_statsLock); + m_peerStats[routerId] += delta; + } + + PeerStats + PeerDb::getCurrentPeerStats(const RouterID& routerId) const + { + std::lock_guard gaurd(m_statsLock); + auto itr = m_peerStats.find(routerId); + if (itr == m_peerStats.end()) + return {}; + else + return itr->second; + } + +}; // namespace llarp diff --git a/llarp/peerstats/peer_db.hpp b/llarp/peerstats/peer_db.hpp new file mode 100644 index 000000000..cd22dc8df --- /dev/null +++ b/llarp/peerstats/peer_db.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace llarp +{ + // Struct containing stats we know about a peer + struct PeerStats + { + int32_t numConnectionAttempts = 0; + int32_t numConnectionSuccesses = 0; + int32_t numConnectionRejections = 0; + int32_t numConnectionTimeouts = 0; + + int32_t numPathBuilds = 0; + int64_t numPacketsAttempted = 0; + int64_t numPacketsSent = 0; + int64_t numPacketsDropped = 0; + int64_t numPacketsResent = 0; + + int64_t numDistinctRCsReceived = 0; + int64_t numLateRCs = 0; + + double peakBandwidthBytesPerSec = 0; + std::chrono::milliseconds longestRCReceiveInterval = 0ms; + std::chrono::milliseconds mostExpiredRC = 0ms; + + PeerStats& + operator+=(const PeerStats& other); + bool + operator==(const PeerStats& other); + }; + + /// Maintains a database of stats collected about the connections with our Service Node peers + struct PeerDb + { + /// Add the given stats to the cummulative stats for the given peer. For cummulative stats, the + /// stats are added together; for watermark stats, the max is kept. + /// + /// This is intended to be used in the following pattern: + /// + /// 1) Initialize an empty PeerStats + /// 2) Collect relevant stats + /// 3) Call accumulatePeerStats() with the stats + /// 4) Reset the stats to 0 + /// 5) + void + accumulatePeerStats(const RouterID& routerId, const PeerStats& delta); + + /// Provides a snapshot of the most recent PeerStats we have for the given peer. If we don't + /// have any stats for the peer, an empty PeerStats is returned. + /// + /// @param routerId is the RouterID of the requested peer + /// @return a copy of the most recent peer stats or an empty one if no such peer is known + PeerStats + getCurrentPeerStats(const RouterID& routerId) const; + + private: + std::unordered_map m_peerStats; + std::mutex m_statsLock; + }; + +} // namespace llarp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bd3714233..a6239d941 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -68,6 +68,7 @@ add_executable(catchAll util/test_llarp_util_printer.cpp util/test_llarp_util_str.cpp util/test_llarp_util_decaying_hashset.cpp + peerstats/peer_db.cpp config/test_llarp_config_definition.cpp config/test_llarp_config_output.cpp net/test_ip_address.cpp diff --git a/test/peerstats/peer_db.cpp b/test/peerstats/peer_db.cpp new file mode 100644 index 000000000..66e5837ea --- /dev/null +++ b/test/peerstats/peer_db.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include + +TEST_CASE("Test PeerStats operator+=", "[PeerStats]") +{ + // TODO: test all members + llarp::PeerStats stats; + stats.numConnectionAttempts = 1; + stats.peakBandwidthBytesPerSec = 12; + + llarp::PeerStats delta; + delta.numConnectionAttempts = 2; + delta.peakBandwidthBytesPerSec = 4; + + stats += delta; + + CHECK(stats.numConnectionAttempts == 3); + CHECK(stats.peakBandwidthBytesPerSec == 12); // should take max(), not add +} + +TEST_CASE("Test PeerDb PeerStats memory storage", "[PeerDb]") +{ + const llarp::PeerStats empty = {}; + const llarp::RouterID id = {}; + + llarp::PeerDb db; + CHECK(db.getCurrentPeerStats(id) == empty); + + llarp::PeerStats delta; + delta.numConnectionAttempts = 4; + delta.peakBandwidthBytesPerSec = 5; + db.accumulatePeerStats(id, delta); + CHECK(db.getCurrentPeerStats(id) == delta); + + delta = {}; + delta.numConnectionAttempts = 5; + delta.peakBandwidthBytesPerSec = 6; + db.accumulatePeerStats(id, delta); + + llarp::PeerStats expected; + expected.numConnectionAttempts = 9; + expected.peakBandwidthBytesPerSec = 6; + CHECK(db.getCurrentPeerStats(id) == expected); +}