diff --git a/nano/lib/stats.cpp b/nano/lib/stats.cpp index 4f7c8fa660..437936004a 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -130,10 +131,31 @@ nano::stats::stats (nano::stats_config config) : { } +nano::stats::~stats () +{ + // Thread must be stopped before destruction + debug_assert (!thread.joinable ()); +} + +void nano::stats::start () +{ + thread = std::thread ([this] { + nano::thread_role::set (nano::thread_role::name::stats); + run (); + }); +} + void nano::stats::stop () { - std::lock_guard guard{ mutex }; - stopped = true; + { + std::lock_guard guard{ mutex }; + stopped = true; + } + condition.notify_all (); + if (thread.joinable ()) + { + thread.join (); + } } void nano::stats::clear () @@ -318,39 +340,73 @@ void nano::stats::log_samples_impl (stat_log_sink & sink, tm & tm) sink.finalize (); } -// TODO: Run periodically in a separate thread -void nano::stats::update () +std::chrono::milliseconds nano::stats::calculate_run_interval () const { - static file_writer log_count (config.log_counters_filename); - static file_writer log_sample (config.log_samples_filename); + std::chrono::milliseconds interval = std::chrono::milliseconds::max (); + if (config.log_counters_interval.count () > 0) + { + interval = std::min (interval, config.log_counters_interval); + } + if (config.log_samples_interval.count () > 0) + { + interval = std::min (interval, config.log_samples_interval); + } + return interval; +} - std::lock_guard guard{ mutex }; - if (!stopped) +void nano::stats::run () +{ + auto const interval = calculate_run_interval (); + + if (interval == std::chrono::milliseconds::max ()) { - auto now = std::chrono::steady_clock::now (); // Only sample clock if necessary as this impacts node performance due to frequent usage + return; + } - // TODO: Replace with a proper std::chrono time - std::time_t time = std::chrono::system_clock::to_time_t (std::chrono::system_clock::now ()); - tm local_tm = *localtime (&time); + std::unique_lock lock{ mutex }; + while (!stopped) + { + condition.wait_for (lock, interval); - // Counters - if (config.log_counters_interval.count () > 0) + if (!stopped) { - if (nano::elapsed (log_last_count_writeout, config.log_counters_interval)) - { - log_counters_impl (log_count, local_tm); - log_last_count_writeout = now; - } + run_one (lock); + debug_assert (lock.owns_lock ()); } + } +} - // Samples - if (config.log_samples_interval.count () > 0) +void nano::stats::run_one (std::unique_lock & lock) +{ + static file_writer log_count{ config.log_counters_filename }; + static file_writer log_sample{ config.log_samples_filename }; + + debug_assert (!mutex.try_lock ()); + debug_assert (lock.owns_lock ()); + + auto now = std::chrono::steady_clock::now (); // Only sample clock if necessary as this impacts node performance due to frequent usage + + // TODO: Replace with a proper std::chrono time + std::time_t time = std::chrono::system_clock::to_time_t (std::chrono::system_clock::now ()); + tm local_tm = *localtime (&time); + + // Counters + if (config.log_counters_interval.count () > 0) + { + if (nano::elapsed (log_last_count_writeout, config.log_counters_interval)) { - if (nano::elapsed (log_last_sample_writeout, config.log_samples_interval)) - { - log_samples_impl (log_sample, local_tm); - log_last_sample_writeout = now; - } + log_counters_impl (log_count, local_tm); + log_last_count_writeout = now; + } + } + + // Samples + if (config.log_samples_interval.count () > 0) + { + if (nano::elapsed (log_last_sample_writeout, config.log_samples_interval)) + { + log_samples_impl (log_sample, local_tm); + log_last_sample_writeout = now; } } } @@ -364,20 +420,20 @@ std::chrono::seconds nano::stats::last_reset () std::string nano::stats::dump (category category) { - auto sink = log_sink_json (); + json_writer sink; switch (category) { case category::counters: - log_counters (*sink); + log_counters (sink); break; case category::samples: - log_samples (*sink); + log_samples (sink); break; default: debug_assert (false, "missing stat_category case"); break; } - return sink->to_string (); + return sink.to_string (); } /* diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index 865fc331fd..c3e65a72c9 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace nano { @@ -70,15 +71,10 @@ class stats final using sampler_value_t = int64_t; public: - /** Constructor using the default config values */ - stats () = default; + explicit stats (nano::stats_config = {}); + ~stats (); - /** - * Initialize stats with a config. - */ - explicit stats (nano::stats_config); - - /** Stop stats being output */ + void start (); void stop (); /** Clear all stats */ @@ -203,11 +199,9 @@ class stats final void update_sampler (sampler_key key, std::function const & updater); private: - /** - * Update count and sample and call any observers on the key - * @value Amount to add to the counter - */ - void update (); + void run (); + void run_one (std::unique_lock & lock); + std::chrono::milliseconds calculate_run_interval () const; /** Unlocked implementation of log_counters() to avoid using recursive locking */ void log_counters_impl (stat_log_sink & sink, tm & tm); @@ -226,6 +220,8 @@ class stats final /** Whether stats should be output */ bool stopped{ false }; + std::thread thread; + nano::condition_variable condition; mutable std::shared_mutex mutex; }; diff --git a/nano/lib/thread_roles.cpp b/nano/lib/thread_roles.cpp index 0824d23733..b0d3825b2d 100644 --- a/nano/lib/thread_roles.cpp +++ b/nano/lib/thread_roles.cpp @@ -100,6 +100,9 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::scheduler_priority: thread_role_name_string = "Sched Priority"; break; + case nano::thread_role::name::stats: + thread_role_name_string = "Stats"; + break; default: debug_assert (false && "nano::thread_role::get_string unhandled thread role"); } diff --git a/nano/lib/thread_roles.hpp b/nano/lib/thread_roles.hpp index 311ae58d1b..a02f7da6b3 100644 --- a/nano/lib/thread_roles.hpp +++ b/nano/lib/thread_roles.hpp @@ -42,6 +42,7 @@ enum class name scheduler_manual, scheduler_optimistic, scheduler_priority, + stats, }; /* diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 64bf414bd9..b32a167b13 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -666,6 +666,7 @@ void nano::node::start () } websocket.start (); telemetry.start (); + stats.start (); } void nano::node::stop () diff --git a/nano/test_common/system.cpp b/nano/test_common/system.cpp index e8fccf2200..e0f5d1f772 100644 --- a/nano/test_common/system.cpp +++ b/nano/test_common/system.cpp @@ -570,6 +570,7 @@ void nano::test::system::stop () { i->stop (); } + stats.stop (); work.stop (); }