blob: 1a9e1d67d41387817173d68b31b266ee662c2e34 [file] [log] [blame]
Brian Silvermanea2c95f2021-02-10 18:10:26 -08001#include "aos/aos_cli_utils.h"
2
3#include <sys/stat.h>
4#include <sys/types.h>
5#include <unistd.h>
6
Austin Schuh893d7f42022-09-16 15:01:35 -07007#include <chrono>
Stephan Pleines6191f1d2024-05-30 20:44:45 -07008#include <iomanip>
Brian Silvermanea2c95f2021-02-10 18:10:26 -08009#include <iostream>
Sanjay Narayanane5f382a2024-06-23 18:17:23 -070010#include <set>
11#include <string>
12#include <string_view>
13#include <utility>
Brian Silvermanea2c95f2021-02-10 18:10:26 -080014
Austin Schuh893d7f42022-09-16 15:01:35 -070015#include "aos/configuration.h"
16#include "aos/events/shm_event_loop.h"
17#include "aos/events/simulated_event_loop.h"
18#include "aos/time/time.h"
19
Austin Schuh99f7c6a2024-06-25 22:07:44 -070020ABSL_FLAG(std::string, config, "aos_config.json",
21 "File path of aos configuration");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080022
Austin Schuh99f7c6a2024-06-25 22:07:44 -070023ABSL_FLAG(
24 bool, _bash_autocomplete, false,
Brian Silvermanea2c95f2021-02-10 18:10:26 -080025 "Internal use: Outputs channel list for use with autocomplete script.");
Brennan Coslett275965f2023-02-20 15:15:46 -060026
Austin Schuh99f7c6a2024-06-25 22:07:44 -070027ABSL_FLAG(bool, _zsh_compatability, false,
28 "Internal use: Force completion to complete either channels or "
29 "message_types, zsh doesn't handle spaces well.");
Brennan Coslett275965f2023-02-20 15:15:46 -060030
Austin Schuh99f7c6a2024-06-25 22:07:44 -070031ABSL_FLAG(std::string, _bash_autocomplete_word, "",
32 "Internal use: Current word being autocompleted");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080033
Austin Schuh99f7c6a2024-06-25 22:07:44 -070034ABSL_FLAG(bool, all, false,
35 "If true, print out the channels for all nodes, not just the "
36 "channels which are visible on this node.");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080037
Austin Schuh99f7c6a2024-06-25 22:07:44 -070038ABSL_FLAG(
39 bool, canonical, false,
Sanjay Narayanane5f382a2024-06-23 18:17:23 -070040 "If true, print out the canonical channel names instead of the aliases.");
41
Brian Silvermanea2c95f2021-02-10 18:10:26 -080042namespace aos {
43namespace {
44
Austin Schuh893d7f42022-09-16 15:01:35 -070045namespace chrono = std::chrono;
46
Brian Silvermanea2c95f2021-02-10 18:10:26 -080047bool EndsWith(std::string_view str, std::string_view ending) {
48 const std::size_t offset = str.size() - ending.size();
49 return str.size() >= ending.size() &&
50 std::equal(str.begin() + offset, str.end(), ending.begin(),
51 ending.end());
52}
53
Austin Schuh893d7f42022-09-16 15:01:35 -070054void StreamSeconds(std::ostream &stream,
55 const aos::monotonic_clock::time_point now) {
56 if (now < monotonic_clock::epoch()) {
57 chrono::seconds seconds =
58 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
59
60 stream << "-" << -seconds.count() << "." << std::setfill('0')
61 << std::setw(9)
62 << chrono::duration_cast<chrono::nanoseconds>(seconds -
63 now.time_since_epoch())
64 .count();
65 } else {
66 chrono::seconds seconds =
67 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
68 stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
69 << chrono::duration_cast<chrono::nanoseconds>(
70 now.time_since_epoch() - seconds)
71 .count();
72 }
73}
74
Brian Silvermanea2c95f2021-02-10 18:10:26 -080075} // namespace
76
77bool CliUtilInfo::Initialize(
78 int *argc, char ***argv,
Austin Schuh59f3b0f2021-07-31 20:50:40 -070079 std::function<bool(const aos::Channel *)> channel_filter,
Austin Schuhba2c8652022-08-17 14:56:08 -070080 std::string_view channel_filter_description, bool expect_args) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080081 // Don't generate failure output if the config doesn't exist while attempting
82 // to autocomplete.
Austin Schuh99f7c6a2024-06-25 22:07:44 -070083 if (absl::GetFlag(FLAGS__bash_autocomplete) &&
84 (!(EndsWith(absl::GetFlag(FLAGS_config), ".json") ||
85 EndsWith(absl::GetFlag(FLAGS_config), ".bfbs")))) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080086 std::cout << "COMPREPLY=()";
87 return true;
88 }
89
Austin Schuh99f7c6a2024-06-25 22:07:44 -070090 config = aos::configuration::MaybeReadConfig(absl::GetFlag(FLAGS_config));
91 if (absl::GetFlag(FLAGS__bash_autocomplete) && !config.has_value()) {
Milind Upadhyay17098ba2022-04-15 22:18:50 -070092 std::cout << "COMPREPLY=()";
93 return true;
94 }
95 CHECK(config.has_value()) << "Could not read config. See above errors.";
96
Brian Silvermanea2c95f2021-02-10 18:10:26 -080097 event_loop.emplace(&config->message());
98 event_loop->SkipTimingReport();
99 event_loop->SkipAosLog();
100
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700101 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800102 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700103
104 do {
105 std::string channel_name;
106 std::string message_type;
107 if (*argc > 1) {
108 channel_name = (*argv)[1];
109 ShiftArgs(argc, argv);
110 }
111 if (*argc > 1) {
112 message_type = (*argv)[1];
113 ShiftArgs(argc, argv);
114 }
115
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700116 if (absl::GetFlag(FLAGS__bash_autocomplete)) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700117 Autocomplete(channel_name, message_type, channel_filter);
118 return true;
119 }
120
121 if (channel_name.empty() && message_type.empty()) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700122 // Just print all the channels (or the ones that pass through the filter).
123 // Sort them before doing so.
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700124 std::cout << "Channels:\n";
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700125 std::set<std::pair<std::string, std::string>> channels_to_print;
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700126 for (const aos::Channel *channel : *channels) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700127 if (absl::GetFlag(FLAGS_all) || channel_filter(channel)) {
128 if (absl::GetFlag(FLAGS_canonical)) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700129 channels_to_print.emplace(channel->name()->c_str(),
130 channel->type()->c_str());
131 } else {
132 std::set<std::string> aliases = configuration::GetChannelAliases(
133 event_loop->configuration(), channel->name()->string_view(),
134 channel->type()->string_view(), "", event_loop->node());
135 CHECK_GT(aliases.size(), 0u);
136 if (aliases.size() == 1) {
137 // There were no aliases. Just print the canonical name.
138 channels_to_print.emplace(channel->name()->str(),
139 channel->type()->str());
140 } else {
141 // Assume that the shortest alias (excluding the input name) is
142 // the base alias, and use that.
143 // TODO(Sanjay): Consider having GetChannelAliases return a
144 // hierarchical list instead.
145 CHECK_EQ(aliases.erase(channel->name()->str()), 1u);
146 auto it = aliases.begin();
147 std::string_view shortest_alias = *it;
148 while (it != aliases.end()) {
149 if (shortest_alias.size() > it->size()) {
150 shortest_alias = *it;
151 }
152 ++it;
153 }
154 channels_to_print.emplace(std::string(shortest_alias),
155 channel->type()->c_str());
156 }
157 }
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700158 }
159 }
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700160 for (const auto &[name, type] : channels_to_print) {
161 std::cout << name << ' ' << type << '\n';
162 }
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700163 return true;
164 }
165
166 std::vector<const aos::Channel *> found_channels_now;
167 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800168 for (const aos::Channel *channel : *channels) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700169 const std::set<std::string> aliases =
170 aos::configuration::GetChannelAliases(
171 event_loop->configuration(), channel->name()->string_view(),
172 channel->type()->string_view(), "", event_loop->node());
173 if (auto it = std::find_if(aliases.begin(), aliases.end(),
174 [channel_name](const std::string &alias) {
175 return alias == channel_name;
176 });
177 it == aliases.end()) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700178 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800179 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700180
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700181 if (channel->type()->string_view() == message_type) {
182 if (!found_exact) {
183 found_channels_now.clear();
184 found_exact = true;
185 }
186 } else if (!found_exact && channel->type()->string_view().find(
187 message_type) != std::string_view::npos) {
188 } else {
189 continue;
190 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700191
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700192 if (!absl::GetFlag(FLAGS_all) && !channel_filter(channel)) {
Austin Schuhba2c8652022-08-17 14:56:08 -0700193 LOG(FATAL) << "matched channel does not pass the channel filter: \""
194 << channel_filter_description
195 << "\" [matched channel info]: "
196 << configuration::CleanedChannelToString(channel);
197 }
198
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700199 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800200 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800201
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700202 if (found_channels_now.empty()) {
203 LOG(FATAL)
204 << "Could not find any channels with the given name and type for "
205 << channel_name << " " << message_type;
206 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
207 LOG(FATAL) << "Multiple channels found with same type for "
208 << channel_name << " " << message_type;
209 }
210 for (const aos::Channel *channel : found_channels_now) {
211 found_channels.push_back(channel);
212 }
Austin Schuh59f3b0f2021-07-31 20:50:40 -0700213 } while (expect_args && *argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800214
215 return false;
216}
217
218void CliUtilInfo::Autocomplete(
219 std::string_view channel_name, std::string_view message_type,
220 std::function<bool(const aos::Channel *)> channel_filter) {
221 const aos::Configuration *const config_msg = event_loop->configuration();
222 const bool unique_match =
223 std::count_if(config_msg->channels()->begin(),
224 config_msg->channels()->end(),
225 [channel_name, message_type](const aos::Channel *channel) {
226 return channel->name()->string_view() == channel_name &&
227 channel->type()->string_view() == message_type;
228 }) == 1;
229
230 const bool editing_message =
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700231 !channel_name.empty() &&
232 absl::GetFlag(FLAGS__bash_autocomplete_word) == message_type;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800233 const bool editing_channel =
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700234 !editing_message &&
235 absl::GetFlag(FLAGS__bash_autocomplete_word) == channel_name;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800236
237 std::cout << "COMPREPLY=(";
238
239 // If we have a unique match, don't provide any suggestions. Otherwise, check
240 // that were're editing one of the two positional arguments.
241 if (!unique_match && (editing_message || editing_channel)) {
242 for (const aos::Channel *channel : *config_msg->channels()) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700243 if (absl::GetFlag(FLAGS_all) || channel_filter(channel)) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700244 const std::set<std::string> aliases =
245 aos::configuration::GetChannelAliases(
246 config_msg, channel->name()->string_view(),
247 channel->type()->string_view(), "", event_loop->node());
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800248 // Suggest only message types if the message type argument is being
249 // entered.
250 if (editing_message) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700251 // Then, filter for channel names that exactly match one of the
252 // aliases and types that begin with message_type.
253 if (aliases.contains(std::string(channel_name)) &&
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800254 channel->type()->string_view().find(message_type) == 0) {
255 std::cout << '\'' << channel->type()->c_str() << "' ";
256 }
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700257 } else if (auto it =
258 std::find_if(aliases.begin(), aliases.end(),
259 [channel_name](const std::string &alias) {
260 return alias.find(channel_name) == 0;
261 });
262 it != aliases.end()) {
263 // If the message type is empty, then return full autocomplete.
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800264 // Otherwise, since the message type is poulated yet not being edited,
265 // the user must be editing the channel name alone, in which case only
266 // suggest channel names, not pairs.
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700267 if (!absl::GetFlag(FLAGS__zsh_compatability) &&
268 message_type.empty()) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700269 std::cout << '\'' << *it << ' ' << channel->type()->c_str() << "' ";
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800270 } else {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700271 std::cout << '\'' << *it << "' ";
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800272 }
273 }
274 }
275 }
276 }
277 std::cout << ')';
278}
279
Austin Schuh893d7f42022-09-16 15:01:35 -0700280void PrintMessage(const std::string_view node_name, const aos::Channel *channel,
281 const aos::Context &context, aos::FastStringBuilder *builder,
282 PrintOptions options) {
283 // Print the flatbuffer out to stdout, both to remove the
284 // unnecessary cruft from glog and to allow the user to readily
285 // redirect just the logged output independent of any debugging
286 // information on stderr.
287
288 builder->Reset();
289
290 CHECK(flatbuffers::Verify(*channel->schema(),
291 *channel->schema()->root_table(),
292 static_cast<const uint8_t *>(context.data),
293 static_cast<size_t>(context.size)))
294 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
295 << channel->type()->c_str();
296
297 aos::FlatbufferToJson(
298 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
299 {options.pretty, static_cast<size_t>(options.max_vector_size),
Austin Schuhbe6d2962022-11-01 09:24:56 -0700300 options.pretty_max, options.hex});
Austin Schuh893d7f42022-09-16 15:01:35 -0700301
302 if (options.json) {
303 std::cout << "{";
304 if (!node_name.empty()) {
305 std::cout << "\"node\": \"" << node_name << "\", ";
306 }
307 std::cout << "\"monotonic_event_time\": ";
308 StreamSeconds(std::cout, context.monotonic_event_time);
309 std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
310 << "\", ";
311
312 if (context.monotonic_remote_time != context.monotonic_event_time) {
313 std::cout << "\"monotonic_remote_time\": ";
314 StreamSeconds(std::cout, context.monotonic_remote_time);
315 std::cout << ", \"realtime_remote_time\": \""
316 << context.realtime_remote_time << "\", ";
317 }
318
319 std::cout << "\"channel\": "
320 << aos::configuration::StrippedChannelToString(channel)
Naman Gupta54047212022-09-23 14:10:30 -0700321 << ", \"data\": " << *builder << "}";
Austin Schuh893d7f42022-09-16 15:01:35 -0700322 } else {
323 if (!node_name.empty()) {
324 std::cout << node_name << " ";
325 }
326
327 if (options.print_timestamps) {
328 if (context.monotonic_remote_time != context.monotonic_event_time) {
329 std::cout << context.realtime_event_time << " ("
330 << context.monotonic_event_time << ") sent "
331 << context.realtime_remote_time << " ("
332 << context.monotonic_remote_time << ") "
333 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Naman Gupta54047212022-09-23 14:10:30 -0700334 << ": " << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700335 } else {
336 std::cout << context.realtime_event_time << " ("
337 << context.monotonic_event_time << ") "
338 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Naman Gupta54047212022-09-23 14:10:30 -0700339 << ": " << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700340 }
341 } else {
Naman Gupta54047212022-09-23 14:10:30 -0700342 std::cout << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700343 }
344 }
345}
346
347void PrintMessage(const aos::Channel *channel, const aos::Context &context,
348 aos::FastStringBuilder *builder, PrintOptions options) {
349 PrintMessage("", channel, context, builder, options);
350}
351
352void PrintMessage(const std::string_view node_name,
353 aos::NodeEventLoopFactory *node_factory,
354 const aos::Channel *channel, const aos::Context &context,
355 aos::FastStringBuilder *builder, PrintOptions options) {
356 if (!options.json && options.distributed_clock) {
357 std::cout << node_factory->ToDistributedClock(context.monotonic_event_time)
358 << " ";
359 }
360 PrintMessage(node_name, channel, context, builder, options);
361}
362
Naman Gupta54047212022-09-23 14:10:30 -0700363Printer::Printer(PrintOptions options, bool flush)
364 : options_(options), flush_(flush) {
365 if (options_.json) {
366 std::cout << "[";
367 }
368}
369
370Printer::~Printer() {
371 if (options_.json) {
372 if (message_count_ > 0) {
373 std::cout << "\n]\n";
374 } else {
375 std::cout << "]\n";
376 }
377 }
378}
379
380void Printer::PrintMessage(const std::string_view node_name,
381 aos::NodeEventLoopFactory *node_factory,
382 const aos::Channel *channel,
383 const aos::Context &context) {
384 if (options_.json) {
385 if (message_count_ != 0) {
386 std::cout << ",\n ";
387 } else {
388 std::cout << "\n ";
389 }
390 }
391
392 aos::PrintMessage(node_name, node_factory, channel, context, &str_builder_,
393 options_);
394
395 if (!options_.json) {
396 if (flush_) {
397 std::cout << std::endl;
398 } else {
399 std::cout << "\n";
400 }
401 }
402 ++message_count_;
403}
404
405void Printer::PrintMessage(const aos::Channel *channel,
406 const aos::Context &context) {
407 if (options_.json) {
408 if (message_count_ != 0) {
409 std::cout << ",\n ";
410 } else {
411 std::cout << "\n ";
412 }
413 }
414
415 aos::PrintMessage(channel, context, &str_builder_, options_);
416
417 if (!options_.json) {
418 if (flush_) {
419 std::cout << std::endl;
420 } else {
421 std::cout << "\n";
422 }
423 }
424 ++message_count_;
425}
426
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800427} // namespace aos