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);
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";
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",
351 render_top_int(
"Commit index",
355 render_top_int(
"Last log term",
359 render_top_int(
"Current term",
363 render_top_int(
"Voted for",
367 render_top_str(
"Since activity",
368 duration_to_string(time_since_last_activity).c_str(),
371 render_top_str(
"Random timeout",
375 render_top_int(
"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,
397 const std::function<int (std::uint8_t)> value_getter,
398 const std::function<CLIColor (std::uint8_t)> color_getter)
400 std::printf(
"%-17s", name);
405 CLIColorizer colorizer(color_getter(i));
406 const auto value = value_getter(i);
409 std::printf(
"%-5d", value);
421 render_next_event_counter();
441 follower_color_getter);
443 render_followers_state(
"Next index",
445 follower_color_getter);
447 render_followers_state(
"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;
static uavcan_linux::NodePtr initNode(const std::vector< std::string > &ifaces, uavcan::NodeID nid, const std::string &name)
Log::Index last_log_index
void appendFormatted(const char *const format, const A value)
virtual void onEvent(uavcan::dynamic_node_id_server::TraceCode code, uavcan::int64_t argument)
uavcan::UtcTime getUtc() const override
uavcan::MonotonicTime getMonotonic() const override
int init(const UniqueID &own_unique_id, const uint8_t cluster_size=ClusterManager::ClusterSizeUnknown, const TransferPriority priority=TransferPriority::OneHigherThanLowest)
MonotonicDuration randomized_timeout
static std::string toString(long x)
int main(int argc, const char **argv)
Implicitly convertible to/from uavcan.Timestamp.
UAVCAN_EXPORT OutputIt copy(InputIt first, InputIt last, OutputIt result)
std::shared_ptr< Node > NodePtr
MonotonicTime last_activity_timestamp
RaftCore::ServerState state
struct uavcan::dynamic_node_id_server::distributed::StateReport::FollowerState followers[ClusterManager::MaxClusterSize - 1]
UAVCAN_EXPORT const T & min(const T &a, const T &b)
uint8_t num_unknown_nodes
unsigned getNumEvents() const
bool guessIfAllDynamicNodesAreAllocated(const MonotonicDuration &allocation_activity_timeout=MonotonicDuration::fromMSec(Allocation::MAX_REQUEST_PERIOD_MS *2), const MonotonicDuration &min_uptime=MonotonicDuration::fromMSec(6000)) const
static NodePtr makeNode(const std::vector< std::string > &iface_names, ClockAdjustmentMode clock_adjustment_mode=SystemClock::detectPreferredClockAdjustmentMode())
static void runForever(const uavcan_linux::NodePtr &node)
std::array< std::uint8_t, 16 > makeApplicationID(const MachineIDReader::MachineID &machine_id, const std::string &node_name, const std::uint8_t instance_id=0)
static MonotonicDuration fromMSec(int64_t ms)
def enforce(cond, fmt, args)
int snprintf(char *out, std::size_t maxlen, const char *format,...)