13 #include <unordered_map>
14 #include <sys/types.h>
16 #include <sys/ioctl.h>
24 #include <uavcan_posix/dynamic_node_id_server/file_storage_backend.hpp>
25 #include <uavcan_posix/dynamic_node_id_server/file_event_tracer.hpp>
30 constexpr
int MaxNumLastEvents = 30;
31 constexpr
int MinUpdateInterval = 100;
37 uavcan::protocol::HardwareVersion hwver;
38 std::copy(app_id.begin(), app_id.end(), hwver.unique_id.begin());
39 std::cout << hwver << std::endl;
43 node->getLogger().setLevel(uavcan::protocol::debug::LogLevel::DEBUG);
44 node->setModeOperational();
50 class EventTracer :
public uavcan_posix::dynamic_node_id_server::FileEventTracer
57 const uavcan::dynamic_node_id_server::TraceCode code;
62 uavcan::dynamic_node_id_server::TraceCode arg_code,
64 : time_since_startup(arg_time_since_startup)
65 , utc_timestamp(arg_utc_timestamp)
67 , argument(arg_argument)
72 char timebuf[12] = { };
74 const std::time_t rawtime = utc_timestamp.
toUSec() * 1e-6;
75 const auto tm = std::localtime(&rawtime);
76 std::strftime(timebuf, 10,
"%H:%M:%S.", tm);
77 std::snprintf(&timebuf[9], 3,
"%02u",
static_cast<unsigned>((utc_timestamp.
toMSec() % 1000U) / 10U));
81 out.resize(out.capacity());
83 (
void)
std::
snprintf(reinterpret_cast<
char*>(out.begin()), out.size() - 1U,
84 "%-11
s %-28
s %-20lld %016llx",
87 static_cast<
long long>(argument),
88 static_cast<
long long>(argument));
92 static const
char* getTableHeader()
95 return "Timestamp Event name Argument (dec) Argument (hex)";
99 struct EventStatisticsRecord
104 EventStatisticsRecord()
118 template <
typename T>
124 const unsigned num_last_events_;
126 std::deque<RecentEvent> last_events_;
127 std::unordered_map<uavcan::dynamic_node_id_server::TraceCode, EventStatisticsRecord, EnumKeyHash> event_counters_;
129 bool had_events_ =
false;
131 void onEvent(uavcan::dynamic_node_id_server::TraceCode code,
std::int64_t argument)
override
133 uavcan_posix::dynamic_node_id_server::FileEventTracer::onEvent(code, argument);
138 const auto ts_utc = clock_.
getUtc();
139 const auto time_since_startup = ts_m - started_at_;
141 last_events_.emplace_front(time_since_startup, ts_utc, code, argument);
142 if (last_events_.size() > num_last_events_)
144 last_events_.pop_back();
147 event_counters_[code].hit(ts_m);
152 : num_last_events_(num_last_events_to_keep)
157 const RecentEvent& getEventByIndex(
unsigned index)
const {
return last_events_.at(index); }
159 unsigned getNumEvents()
const {
return last_events_.size(); }
161 const decltype(event_counters_)& getEventCounters()
const {
return event_counters_; }
175 ::winsize getTerminalSize()
177 auto w = ::winsize();
178 ENFORCE(0 >= ioctl(STDOUT_FILENO, TIOCGWINSZ, &w));
179 ENFORCE(w.ws_col > 0 && w.ws_row > 0);
184 std::vector<std::pair<uavcan::dynamic_node_id_server::TraceCode, EventTracer::EventStatisticsRecord>>
185 collectRelevantEvents(
const EventTracer& event_tracer,
const unsigned num_events)
188 typedef std::pair<uavcan::dynamic_node_id_server::TraceCode, EventTracer::EventStatisticsRecord> Pair;
189 const auto counters = event_tracer.getEventCounters();
190 std::vector<Pair> pairs(counters.size());
191 std::copy(counters.begin(), counters.end(), pairs.begin());
194 std::sort(pairs.begin(), pairs.end(), [](
const Pair& a,
const Pair& b) {
195 return a.second.last_occurence > b.second.last_occurence;
199 pairs.resize(
std::min(num_events,
unsigned(pairs.size())));
202 std::stable_sort(pairs.begin(), pairs.end(), [](
const Pair& a,
const Pair& b) {
203 return a.second.count > b.second.count;
227 explicit CLIColorizer(
CLIColor c) : color_(c)
229 std::printf(
"\033[%um",
static_cast<unsigned>(color_));
249 constexpr
unsigned NumRelevantEvents = 17;
250 constexpr
unsigned NumRowsWithoutEvents = 3;
255 const unsigned num_rows = getTerminalSize().ws_row;
257 const auto relevant_events = collectRelevantEvents(event_tracer, NumRelevantEvents);
261 const auto time_since_last_activity = timestamp - report.last_activity_timestamp;
266 unsigned next_relevant_event_index = 0;
268 const auto render_next_event_counter = [&]()
270 const char* event_name =
"";
271 char event_count_str[10] = { };
274 if (next_relevant_event_index < relevant_events.size())
276 const auto e = relevant_events[next_relevant_event_index];
277 event_name = uavcan::dynamic_node_id_server::IEventTracer::getEventName(e.first);
278 std::snprintf(event_count_str,
sizeof(event_count_str) - 1U,
"%llu",
279 static_cast<unsigned long long>(e.second.count));
280 event_color = getColorHash(
static_cast<unsigned>(e.first));
282 next_relevant_event_index++;
285 CLIColorizer izer(event_color);
286 std::printf(
"%-29s %-9s\n", event_name, event_count_str);
289 const auto render_top_str = [&](
const char* local_state_name,
const char* local_state_value,
CLIColor color)
292 CLIColorizer izer(color);
293 std::printf(
"%-20s %-16s", local_state_name, local_state_value);
295 render_next_event_counter();
298 const auto render_top_int = [&](
const char* local_state_name,
long long local_state_value,
CLIColor color)
301 std::snprintf(buf,
sizeof(buf) - 1U,
"%lld", local_state_value);
302 render_top_str(local_state_name, buf, color);
309 case RaftCore::ServerStateFollower:
return "Follower";
310 case RaftCore::ServerStateCandidate:
return "Candidate";
311 case RaftCore::ServerStateLeader:
return "Leader";
312 default:
return "BADSTATE";
319 str.appendFormatted(
"%.1f", dur.toUSec() / 1e6);
323 const auto colorize_if = [](
bool condition,
CLIColor color)
331 std::printf(
"\x1b[1J");
332 std::printf(
"\x1b[H");
335 std::printf(
" Local state | Event counters\n");
337 render_top_int(
"Node ID",
338 node->getNodeID().get(),
341 render_top_str(
"State",
342 raft_state_to_string(report.state),
347 render_top_int(
"Last log index",
348 report.last_log_index,
351 render_top_int(
"Commit index",
355 render_top_int(
"Last log term",
356 report.last_log_term,
359 render_top_int(
"Current term",
363 render_top_int(
"Voted for",
364 report.voted_for.get(),
367 render_top_str(
"Since activity",
368 duration_to_string(time_since_last_activity).c_str(),
371 render_top_str(
"Random timeout",
372 duration_to_string(report.randomized_timeout).c_str(),
375 render_top_int(
"Unknown nodes",
376 report.num_unknown_nodes,
379 render_top_int(
"Node failures",
380 node->getInternalFailureCount(),
384 render_top_str(
"All allocated",
385 all_allocated ?
"Yes":
"No",
390 render_next_event_counter();
393 std::printf(
" Followers ");
394 render_next_event_counter();
396 const auto render_followers_state = [&](
const char* name,
400 std::printf(
"%-17s", name);
403 if (i < (report.cluster_size - 1))
405 CLIColorizer colorizer(color_getter(i));
406 const auto value = value_getter(i);
409 std::printf(
"%-5d", value);
421 render_next_event_counter();
427 if (!report.followers[i].node_id.isValid()) {
return CLIColor::Red; }
428 if (report.followers[i].match_index != report.last_log_index ||
429 report.followers[i].next_index <= report.last_log_index)
438 const auto nid = report.followers[i].node_id;
441 follower_color_getter);
443 render_followers_state(
"Next index",
444 [&](
std::uint8_t i) {
return report.followers[i].next_index; },
445 follower_color_getter);
447 render_followers_state(
"Match index",
448 [&](
std::uint8_t i) {
return report.followers[i].match_index; },
449 follower_color_getter);
451 assert(next_relevant_event_index == NumRelevantEvents);
454 std::printf(
"--------------------------------------+----------------------------------------\n");
457 std::printf(
"%s\n", EventTracer::RecentEvent::getTableHeader());
458 const int num_events_to_render =
static_cast<int>(num_rows) -
459 static_cast<int>(next_relevant_event_index) -
460 static_cast<int>(NumRowsWithoutEvents) -
463 i < num_events_to_render && i < static_cast<int>(event_tracer.
getNumEvents());
466 const auto e = event_tracer.getEventByIndex(i);
467 CLIColorizer colorizer(getColorHash(
static_cast<unsigned>(e.code)));
468 std::printf(
"%s\n", e.toString().c_str());
477 const std::string& event_log_file,
478 const std::string& persistent_storage_path)
484 ENFORCE(0 <= event_tracer.init(event_log_file.c_str()));
489 uavcan_posix::dynamic_node_id_server::FileStorageBackend storage_backend;
490 ENFORCE(0 <= storage_backend.init(persistent_storage_path.c_str()));
497 const int server_init_res = server.
init(
node->getNodeStatusProvider().getHardwareVersion().unique_id, cluster_size);
498 if (server_init_res < 0)
500 throw std::runtime_error(
"Failed to start the server; error " + std::to_string(server_init_res));
506 std::printf(
"\x1b[2J");
518 std::cerr <<
"Spin error: " << res << std::endl;
521 const auto ts =
node->getMonotonicTime();
523 if (event_tracer.hadEvents() || (ts - last_redraw_at).toMSec() > 1000)
526 redraw(
node, ts, event_tracer, server);
534 std::vector<std::string> ifaces;
536 std::string storage_path;
539 Options parseOptions(
int argc,
const char** argv)
541 const char*
const executable_name = *argv++;
544 const auto enforce = [executable_name](
bool condition,
const char* error_text) {
547 std::cerr << error_text <<
"\n"
550 <<
" <node-id> <can-iface-name-1> [can-iface-name-N...] [-c <cluster-size>] -s <storage-path>"
556 enforce(argc >= 3,
"Not enough arguments");
562 const int node_id = std::stoi(*argv++);
563 enforce(node_id >= 1 && node_id <= 127,
"Invalid node ID");
570 const std::string token(*argv++);
574 out.ifaces.push_back(token);
576 else if (token[1] ==
'c')
578 int cluster_size = 0;
579 if (token.length() > 2)
581 cluster_size = std::stoi(token.c_str() + 2);
585 enforce(argc --> 0,
"Expected cluster size");
586 cluster_size = std::stoi(*argv++);
590 "Invalid cluster size");
593 else if (token[1] ==
's')
595 if (token.length() > 2)
597 out.storage_path = token.c_str() + 2;
601 enforce(argc --> 0,
"Expected storage path");
602 out.storage_path = *argv++;
607 enforce(
false,
"Unexpected argument");
611 enforce(!out.storage_path.empty(),
"Invalid storage path");
618 int main(
int argc,
const char** argv)
622 std::srand(std::time(
nullptr));
624 if (isatty(STDOUT_FILENO) != 1)
626 std::cerr <<
"This application cannot run if stdout is not associated with a terminal" << std::endl;
630 auto options = parseOptions(argc, argv);
632 std::cout <<
"Self node ID: " <<
int(options.node_id.get()) <<
"\n"
633 "Cluster size: " <<
int(options.cluster_size) <<
"\n"
634 "Storage path: " << options.storage_path <<
"\n"
635 "Num ifaces: " << options.ifaces.size() <<
"\n"
637 "Build mode: Release"
646 options.storage_path +=
"/node_" + std::to_string(options.node_id.get());
648 int system_res = std::system((
"mkdir -p '" + options.storage_path +
"' &>/dev/null").c_str());
651 const auto event_log_file = options.storage_path +
"/events.log";
652 const auto storage_path = options.storage_path +
"/storage/";
657 auto node =
initNode(options.ifaces, options.node_id,
"org.uavcan.linux_app.dynamic_node_id_server");
658 runForever(
node, options.cluster_size, event_log_file, storage_path);
661 catch (
const std::exception& ex)
663 std::cerr <<
"Error: " << ex.what() << std::endl;