529 lines
13 KiB
C++
529 lines
13 KiB
C++
#include <nano/node/stats.hpp>
|
|
|
|
#include <boost/asio.hpp>
|
|
#include <boost/format.hpp>
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <tuple>
|
|
|
|
nano::error nano::stat_config::deserialize_json (nano::jsonconfig & json)
|
|
{
|
|
auto sampling_l (json.get_optional_child ("sampling"));
|
|
if (sampling_l)
|
|
{
|
|
sampling_l->get<bool> ("enabled", sampling_enabled);
|
|
sampling_l->get<size_t> ("capacity", capacity);
|
|
sampling_l->get<size_t> ("interval", interval);
|
|
}
|
|
|
|
auto log_l (json.get_optional_child ("log"));
|
|
if (log_l)
|
|
{
|
|
log_l->get<bool> ("headers", log_headers);
|
|
log_l->get<size_t> ("interval_counters", log_interval_counters);
|
|
log_l->get<size_t> ("interval_samples", log_interval_samples);
|
|
log_l->get<size_t> ("rotation_count", log_rotation_count);
|
|
log_l->get<std::string> ("filename_counters", log_counters_filename);
|
|
log_l->get<std::string> ("filename_samples", log_samples_filename);
|
|
|
|
// Don't allow specifying the same file name for counter and samples logs
|
|
if (log_counters_filename == log_samples_filename)
|
|
{
|
|
json.get_error ().set ("The statistics counter and samples config values must be different");
|
|
}
|
|
}
|
|
|
|
return json.get_error ();
|
|
}
|
|
|
|
std::string nano::stat_log_sink::tm_to_string (tm & tm)
|
|
{
|
|
return (boost::format ("%04d.%02d.%02d %02d:%02d:%02d") % (1900 + tm.tm_year) % (tm.tm_mon + 1) % tm.tm_mday % tm.tm_hour % tm.tm_min % tm.tm_sec).str ();
|
|
}
|
|
|
|
/** JSON sink. The resulting JSON object is provided as both a property_tree::ptree (to_object) and a string (to_string) */
|
|
class json_writer : public nano::stat_log_sink
|
|
{
|
|
boost::property_tree::ptree tree;
|
|
boost::property_tree::ptree entries;
|
|
|
|
public:
|
|
std::ostream & out () override
|
|
{
|
|
return sstr;
|
|
}
|
|
|
|
void begin () override
|
|
{
|
|
tree.clear ();
|
|
}
|
|
|
|
void write_header (std::string header, std::chrono::system_clock::time_point & walltime) override
|
|
{
|
|
std::time_t now = std::chrono::system_clock::to_time_t (walltime);
|
|
tm tm = *localtime (&now);
|
|
tree.put ("type", header);
|
|
tree.put ("created", tm_to_string (tm));
|
|
}
|
|
|
|
void write_entry (tm & tm, std::string type, std::string detail, std::string dir, uint64_t value) override
|
|
{
|
|
boost::property_tree::ptree entry;
|
|
entry.put ("time", boost::format ("%02d:%02d:%02d") % tm.tm_hour % tm.tm_min % tm.tm_sec);
|
|
entry.put ("type", type);
|
|
entry.put ("detail", detail);
|
|
entry.put ("dir", dir);
|
|
entry.put ("value", value);
|
|
entries.push_back (std::make_pair ("", entry));
|
|
}
|
|
|
|
void finalize () override
|
|
{
|
|
tree.add_child ("entries", entries);
|
|
}
|
|
|
|
void * to_object () override
|
|
{
|
|
return &tree;
|
|
}
|
|
|
|
std::string to_string () override
|
|
{
|
|
boost::property_tree::write_json (sstr, tree);
|
|
return sstr.str ();
|
|
}
|
|
|
|
private:
|
|
std::ostringstream sstr;
|
|
};
|
|
|
|
/** File sink with rotation support */
|
|
class file_writer : public nano::stat_log_sink
|
|
{
|
|
public:
|
|
std::ofstream log;
|
|
std::string filename;
|
|
|
|
file_writer (std::string filename) :
|
|
filename (filename)
|
|
{
|
|
log.open (filename.c_str (), std::ofstream::out);
|
|
}
|
|
virtual ~file_writer ()
|
|
{
|
|
log.close ();
|
|
}
|
|
std::ostream & out () override
|
|
{
|
|
return log;
|
|
}
|
|
|
|
void write_header (std::string header, std::chrono::system_clock::time_point & walltime) override
|
|
{
|
|
std::time_t now = std::chrono::system_clock::to_time_t (walltime);
|
|
tm tm = *localtime (&now);
|
|
log << header << "," << boost::format ("%04d.%02d.%02d %02d:%02d:%02d") % (1900 + tm.tm_year) % (tm.tm_mon + 1) % tm.tm_mday % tm.tm_hour % tm.tm_min % tm.tm_sec << std::endl;
|
|
}
|
|
|
|
void write_entry (tm & tm, std::string type, std::string detail, std::string dir, uint64_t value) override
|
|
{
|
|
log << boost::format ("%02d:%02d:%02d") % tm.tm_hour % tm.tm_min % tm.tm_sec << "," << type << "," << detail << "," << dir << "," << value << std::endl;
|
|
}
|
|
|
|
void rotate () override
|
|
{
|
|
log.close ();
|
|
log.open (filename.c_str (), std::ofstream::out);
|
|
log_entries = 0;
|
|
}
|
|
};
|
|
|
|
nano::stat::stat (nano::stat_config config) :
|
|
config (config)
|
|
{
|
|
}
|
|
|
|
std::shared_ptr<nano::stat_entry> nano::stat::get_entry (uint32_t key)
|
|
{
|
|
return get_entry (key, config.interval, config.capacity);
|
|
}
|
|
|
|
std::shared_ptr<nano::stat_entry> nano::stat::get_entry (uint32_t key, size_t interval, size_t capacity)
|
|
{
|
|
std::unique_lock<std::mutex> lock (stat_mutex);
|
|
return get_entry_impl (key, interval, capacity);
|
|
}
|
|
|
|
std::shared_ptr<nano::stat_entry> nano::stat::get_entry_impl (uint32_t key, size_t interval, size_t capacity)
|
|
{
|
|
std::shared_ptr<nano::stat_entry> res;
|
|
auto entry = entries.find (key);
|
|
if (entry == entries.end ())
|
|
{
|
|
res = entries.insert (std::make_pair (key, std::make_shared<nano::stat_entry> (capacity, interval))).first->second;
|
|
}
|
|
else
|
|
{
|
|
res = entry->second;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
std::unique_ptr<nano::stat_log_sink> nano::stat::log_sink_json ()
|
|
{
|
|
return std::make_unique<json_writer> ();
|
|
}
|
|
|
|
std::unique_ptr<nano::stat_log_sink> log_sink_file (std::string filename)
|
|
{
|
|
return std::make_unique<file_writer> (filename);
|
|
}
|
|
|
|
void nano::stat::log_counters (stat_log_sink & sink)
|
|
{
|
|
std::unique_lock<std::mutex> lock (stat_mutex);
|
|
log_counters_impl (sink);
|
|
}
|
|
|
|
void nano::stat::log_counters_impl (stat_log_sink & sink)
|
|
{
|
|
sink.begin ();
|
|
if (sink.entries () >= config.log_rotation_count)
|
|
{
|
|
sink.rotate ();
|
|
}
|
|
|
|
if (config.log_headers)
|
|
{
|
|
auto walltime (std::chrono::system_clock::now ());
|
|
sink.write_header ("counters", walltime);
|
|
}
|
|
|
|
for (auto & it : entries)
|
|
{
|
|
std::time_t time = std::chrono::system_clock::to_time_t (it.second->counter.get_timestamp ());
|
|
tm local_tm = *localtime (&time);
|
|
|
|
auto key = it.first;
|
|
std::string type = type_to_string (key);
|
|
std::string detail = detail_to_string (key);
|
|
std::string dir = dir_to_string (key);
|
|
sink.write_entry (local_tm, type, detail, dir, it.second->counter.get_value ());
|
|
}
|
|
sink.entries ()++;
|
|
sink.finalize ();
|
|
}
|
|
|
|
void nano::stat::log_samples (stat_log_sink & sink)
|
|
{
|
|
std::unique_lock<std::mutex> lock (stat_mutex);
|
|
log_samples_impl (sink);
|
|
}
|
|
|
|
void nano::stat::log_samples_impl (stat_log_sink & sink)
|
|
{
|
|
sink.begin ();
|
|
if (sink.entries () >= config.log_rotation_count)
|
|
{
|
|
sink.rotate ();
|
|
}
|
|
|
|
if (config.log_headers)
|
|
{
|
|
auto walltime (std::chrono::system_clock::now ());
|
|
sink.write_header ("samples", walltime);
|
|
}
|
|
|
|
for (auto & it : entries)
|
|
{
|
|
auto key = it.first;
|
|
std::string type = type_to_string (key);
|
|
std::string detail = detail_to_string (key);
|
|
std::string dir = dir_to_string (key);
|
|
|
|
for (auto & datapoint : it.second->samples)
|
|
{
|
|
std::time_t time = std::chrono::system_clock::to_time_t (datapoint.get_timestamp ());
|
|
tm local_tm = *localtime (&time);
|
|
sink.write_entry (local_tm, type, detail, dir, datapoint.get_value ());
|
|
}
|
|
}
|
|
sink.entries ()++;
|
|
sink.finalize ();
|
|
}
|
|
|
|
void nano::stat::update (uint32_t key_a, uint64_t value)
|
|
{
|
|
static file_writer log_count (config.log_counters_filename);
|
|
static file_writer log_sample (config.log_samples_filename);
|
|
|
|
auto now (std::chrono::steady_clock::now ());
|
|
|
|
std::unique_lock<std::mutex> lock (stat_mutex);
|
|
auto entry (get_entry_impl (key_a, config.interval, config.capacity));
|
|
|
|
// Counters
|
|
auto old (entry->counter.get_value ());
|
|
entry->counter.add (value);
|
|
entry->count_observers.notify (old, entry->counter.get_value ());
|
|
|
|
std::chrono::duration<double, std::milli> duration = now - log_last_count_writeout;
|
|
if (config.log_interval_counters > 0 && duration.count () > config.log_interval_counters)
|
|
{
|
|
log_counters_impl (log_count);
|
|
log_last_count_writeout = now;
|
|
}
|
|
|
|
// Samples
|
|
if (config.sampling_enabled && entry->sample_interval > 0)
|
|
{
|
|
entry->sample_current.add (value, false);
|
|
|
|
std::chrono::duration<double, std::milli> duration = now - entry->sample_start_time;
|
|
if (duration.count () > entry->sample_interval)
|
|
{
|
|
entry->sample_start_time = now;
|
|
|
|
// Make a snapshot of samples for thread safety and to get a stable container
|
|
entry->sample_current.set_timestamp (std::chrono::system_clock::now ());
|
|
entry->samples.push_back (entry->sample_current);
|
|
entry->sample_current.set_value (0);
|
|
|
|
if (!entry->sample_observers.observers.empty ())
|
|
{
|
|
auto snapshot (entry->samples);
|
|
entry->sample_observers.notify (snapshot);
|
|
}
|
|
|
|
// Log sink
|
|
duration = now - log_last_sample_writeout;
|
|
if (config.log_interval_samples > 0 && duration.count () > config.log_interval_samples)
|
|
{
|
|
log_samples_impl (log_sample);
|
|
log_last_sample_writeout = now;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::chrono::seconds nano::stat::last_reset ()
|
|
{
|
|
std::unique_lock<std::mutex> lock (stat_mutex);
|
|
auto now (std::chrono::steady_clock::now ());
|
|
return std::chrono::duration_cast<std::chrono::seconds> (now - timestamp);
|
|
}
|
|
|
|
void nano::stat::clear ()
|
|
{
|
|
std::unique_lock<std::mutex> lock (stat_mutex);
|
|
entries.clear ();
|
|
timestamp = std::chrono::steady_clock::now ();
|
|
}
|
|
|
|
std::string nano::stat::type_to_string (uint32_t key)
|
|
{
|
|
auto type = static_cast<stat::type> (key >> 16 & 0x000000ff);
|
|
std::string res;
|
|
switch (type)
|
|
{
|
|
case nano::stat::type::ipc:
|
|
res = "ipc";
|
|
break;
|
|
case nano::stat::type::block:
|
|
res = "block";
|
|
break;
|
|
case nano::stat::type::bootstrap:
|
|
res = "bootstrap";
|
|
break;
|
|
case nano::stat::type::error:
|
|
res = "error";
|
|
break;
|
|
case nano::stat::type::http_callback:
|
|
res = "http_callback";
|
|
break;
|
|
case nano::stat::type::ledger:
|
|
res = "ledger";
|
|
break;
|
|
case nano::stat::type::udp:
|
|
res = "udp";
|
|
break;
|
|
case nano::stat::type::peering:
|
|
res = "peering";
|
|
break;
|
|
case nano::stat::type::rollback:
|
|
res = "rollback";
|
|
break;
|
|
case nano::stat::type::traffic:
|
|
res = "traffic";
|
|
break;
|
|
case nano::stat::type::traffic_bootstrap:
|
|
res = "traffic_bootstrap";
|
|
break;
|
|
case nano::stat::type::vote:
|
|
res = "vote";
|
|
break;
|
|
case nano::stat::type::message:
|
|
res = "message";
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::string nano::stat::detail_to_string (uint32_t key)
|
|
{
|
|
auto detail = static_cast<stat::detail> (key >> 8 & 0x000000ff);
|
|
std::string res;
|
|
switch (detail)
|
|
{
|
|
case nano::stat::detail::all:
|
|
res = "all";
|
|
break;
|
|
case nano::stat::detail::bad_sender:
|
|
res = "bad_sender";
|
|
break;
|
|
case nano::stat::detail::bulk_pull:
|
|
res = "bulk_pull";
|
|
break;
|
|
case nano::stat::detail::bulk_pull_account:
|
|
res = "bulk_pull_account";
|
|
break;
|
|
case nano::stat::detail::bulk_push:
|
|
res = "bulk_push";
|
|
break;
|
|
case nano::stat::detail::change:
|
|
res = "change";
|
|
break;
|
|
case nano::stat::detail::confirm_ack:
|
|
res = "confirm_ack";
|
|
break;
|
|
case nano::stat::detail::node_id_handshake:
|
|
res = "node_id_handshake";
|
|
break;
|
|
case nano::stat::detail::confirm_req:
|
|
res = "confirm_req";
|
|
break;
|
|
case nano::stat::detail::fork:
|
|
res = "fork";
|
|
break;
|
|
case nano::stat::detail::frontier_req:
|
|
res = "frontier_req";
|
|
break;
|
|
case nano::stat::detail::handshake:
|
|
res = "handshake";
|
|
break;
|
|
case nano::stat::detail::http_callback:
|
|
res = "http_callback";
|
|
break;
|
|
case nano::stat::detail::initiate:
|
|
res = "initiate";
|
|
break;
|
|
case nano::stat::detail::initiate_lazy:
|
|
res = "initiate_lazy";
|
|
break;
|
|
case nano::stat::detail::initiate_wallet_lazy:
|
|
res = "initiate_wallet_lazy";
|
|
break;
|
|
case nano::stat::detail::insufficient_work:
|
|
res = "insufficient_work";
|
|
break;
|
|
case nano::stat::detail::invocations:
|
|
res = "invocations";
|
|
break;
|
|
case nano::stat::detail::keepalive:
|
|
res = "keepalive";
|
|
break;
|
|
case nano::stat::detail::open:
|
|
res = "open";
|
|
break;
|
|
case nano::stat::detail::publish:
|
|
res = "publish";
|
|
break;
|
|
case nano::stat::detail::receive:
|
|
res = "receive";
|
|
break;
|
|
case nano::stat::detail::republish_vote:
|
|
res = "republish_vote";
|
|
break;
|
|
case nano::stat::detail::send:
|
|
res = "send";
|
|
break;
|
|
case nano::stat::detail::state_block:
|
|
res = "state_block";
|
|
break;
|
|
case nano::stat::detail::epoch_block:
|
|
res = "epoch_block";
|
|
break;
|
|
case nano::stat::detail::vote_valid:
|
|
res = "vote_valid";
|
|
break;
|
|
case nano::stat::detail::vote_replay:
|
|
res = "vote_replay";
|
|
break;
|
|
case nano::stat::detail::vote_invalid:
|
|
res = "vote_invalid";
|
|
break;
|
|
case nano::stat::detail::vote_overflow:
|
|
res = "vote_overflow";
|
|
break;
|
|
case nano::stat::detail::blocking:
|
|
res = "blocking";
|
|
break;
|
|
case nano::stat::detail::overflow:
|
|
res = "overflow";
|
|
break;
|
|
case nano::stat::detail::unreachable_host:
|
|
res = "unreachable_host";
|
|
break;
|
|
case nano::stat::detail::invalid_magic:
|
|
res = "invalid_magic";
|
|
break;
|
|
case nano::stat::detail::invalid_network:
|
|
res = "invalid_network";
|
|
break;
|
|
case nano::stat::detail::invalid_header:
|
|
res = "invalid_header";
|
|
break;
|
|
case nano::stat::detail::invalid_message_type:
|
|
res = "invalid_message_type";
|
|
break;
|
|
case nano::stat::detail::invalid_keepalive_message:
|
|
res = "invalid_keepalive_message";
|
|
break;
|
|
case nano::stat::detail::invalid_publish_message:
|
|
res = "invalid_publish_message";
|
|
break;
|
|
case nano::stat::detail::invalid_confirm_req_message:
|
|
res = "invalid_confirm_req_message";
|
|
break;
|
|
case nano::stat::detail::invalid_confirm_ack_message:
|
|
res = "invalid_confirm_ack_message";
|
|
break;
|
|
case nano::stat::detail::invalid_node_id_handshake_message:
|
|
res = "invalid_node_id_handshake_message";
|
|
break;
|
|
case nano::stat::detail::outdated_version:
|
|
res = "outdated_version";
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::string nano::stat::dir_to_string (uint32_t key)
|
|
{
|
|
auto dir = static_cast<stat::dir> (key & 0x000000ff);
|
|
std::string res;
|
|
switch (dir)
|
|
{
|
|
case nano::stat::dir::in:
|
|
res = "in";
|
|
break;
|
|
case nano::stat::dir::out:
|
|
res = "out";
|
|
break;
|
|
}
|
|
return res;
|
|
}
|