blob: 9a2c7384b2b408eb9f93aab5d26e5d5952c2fd07 [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 Schuh8e2dfc62022-08-17 16:36:00 -070020DEFINE_string(config, "aos_config.json", "File path of aos configuration");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080021
22DEFINE_bool(
23 _bash_autocomplete, false,
24 "Internal use: Outputs channel list for use with autocomplete script.");
Brennan Coslett275965f2023-02-20 15:15:46 -060025
26DEFINE_bool(_zsh_compatability, false,
27 "Internal use: Force completion to complete either channels or "
28 "message_types, zsh doesn't handle spaces well.");
29
Brian Silvermanea2c95f2021-02-10 18:10:26 -080030DEFINE_string(_bash_autocomplete_word, "",
31 "Internal use: Current word being autocompleted");
32
33DEFINE_bool(all, false,
34 "If true, print out the channels for all nodes, not just the "
35 "channels which are visible on this node.");
36
Sanjay Narayanane5f382a2024-06-23 18:17:23 -070037DEFINE_bool(
38 canonical, false,
39 "If true, print out the canonical channel names instead of the aliases.");
40
Brian Silvermanea2c95f2021-02-10 18:10:26 -080041namespace aos {
42namespace {
43
Austin Schuh893d7f42022-09-16 15:01:35 -070044namespace chrono = std::chrono;
45
Brian Silvermanea2c95f2021-02-10 18:10:26 -080046bool EndsWith(std::string_view str, std::string_view ending) {
47 const std::size_t offset = str.size() - ending.size();
48 return str.size() >= ending.size() &&
49 std::equal(str.begin() + offset, str.end(), ending.begin(),
50 ending.end());
51}
52
Austin Schuh893d7f42022-09-16 15:01:35 -070053void StreamSeconds(std::ostream &stream,
54 const aos::monotonic_clock::time_point now) {
55 if (now < monotonic_clock::epoch()) {
56 chrono::seconds seconds =
57 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
58
59 stream << "-" << -seconds.count() << "." << std::setfill('0')
60 << std::setw(9)
61 << chrono::duration_cast<chrono::nanoseconds>(seconds -
62 now.time_since_epoch())
63 .count();
64 } else {
65 chrono::seconds seconds =
66 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
67 stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
68 << chrono::duration_cast<chrono::nanoseconds>(
69 now.time_since_epoch() - seconds)
70 .count();
71 }
72}
73
Brian Silvermanea2c95f2021-02-10 18:10:26 -080074} // namespace
75
76bool CliUtilInfo::Initialize(
77 int *argc, char ***argv,
Austin Schuh59f3b0f2021-07-31 20:50:40 -070078 std::function<bool(const aos::Channel *)> channel_filter,
Austin Schuhba2c8652022-08-17 14:56:08 -070079 std::string_view channel_filter_description, bool expect_args) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080080 // Don't generate failure output if the config doesn't exist while attempting
81 // to autocomplete.
Milind Upadhyay17098ba2022-04-15 22:18:50 -070082 if (FLAGS__bash_autocomplete &&
83 (!(EndsWith(FLAGS_config, ".json") || EndsWith(FLAGS_config, ".bfbs")))) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080084 std::cout << "COMPREPLY=()";
85 return true;
86 }
87
Milind Upadhyay17098ba2022-04-15 22:18:50 -070088 config = aos::configuration::MaybeReadConfig(FLAGS_config);
89 if (FLAGS__bash_autocomplete && !config.has_value()) {
90 std::cout << "COMPREPLY=()";
91 return true;
92 }
93 CHECK(config.has_value()) << "Could not read config. See above errors.";
94
Brian Silvermanea2c95f2021-02-10 18:10:26 -080095 event_loop.emplace(&config->message());
96 event_loop->SkipTimingReport();
97 event_loop->SkipAosLog();
98
Austin Schuhd30a5ca2021-07-31 20:49:35 -070099 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800100 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700101
102 do {
103 std::string channel_name;
104 std::string message_type;
105 if (*argc > 1) {
106 channel_name = (*argv)[1];
107 ShiftArgs(argc, argv);
108 }
109 if (*argc > 1) {
110 message_type = (*argv)[1];
111 ShiftArgs(argc, argv);
112 }
113
114 if (FLAGS__bash_autocomplete) {
115 Autocomplete(channel_name, message_type, channel_filter);
116 return true;
117 }
118
119 if (channel_name.empty() && message_type.empty()) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700120 // Just print all the channels (or the ones that pass through the filter).
121 // Sort them before doing so.
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700122 std::cout << "Channels:\n";
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700123 std::set<std::pair<std::string, std::string>> channels_to_print;
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700124 for (const aos::Channel *channel : *channels) {
125 if (FLAGS_all || channel_filter(channel)) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700126 if (FLAGS_canonical) {
127 channels_to_print.emplace(channel->name()->c_str(),
128 channel->type()->c_str());
129 } else {
130 std::set<std::string> aliases = configuration::GetChannelAliases(
131 event_loop->configuration(), channel->name()->string_view(),
132 channel->type()->string_view(), "", event_loop->node());
133 CHECK_GT(aliases.size(), 0u);
134 if (aliases.size() == 1) {
135 // There were no aliases. Just print the canonical name.
136 channels_to_print.emplace(channel->name()->str(),
137 channel->type()->str());
138 } else {
139 // Assume that the shortest alias (excluding the input name) is
140 // the base alias, and use that.
141 // TODO(Sanjay): Consider having GetChannelAliases return a
142 // hierarchical list instead.
143 CHECK_EQ(aliases.erase(channel->name()->str()), 1u);
144 auto it = aliases.begin();
145 std::string_view shortest_alias = *it;
146 while (it != aliases.end()) {
147 if (shortest_alias.size() > it->size()) {
148 shortest_alias = *it;
149 }
150 ++it;
151 }
152 channels_to_print.emplace(std::string(shortest_alias),
153 channel->type()->c_str());
154 }
155 }
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700156 }
157 }
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700158 for (const auto &[name, type] : channels_to_print) {
159 std::cout << name << ' ' << type << '\n';
160 }
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700161 return true;
162 }
163
164 std::vector<const aos::Channel *> found_channels_now;
165 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800166 for (const aos::Channel *channel : *channels) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700167 const std::set<std::string> aliases =
168 aos::configuration::GetChannelAliases(
169 event_loop->configuration(), channel->name()->string_view(),
170 channel->type()->string_view(), "", event_loop->node());
171 if (auto it = std::find_if(aliases.begin(), aliases.end(),
172 [channel_name](const std::string &alias) {
173 return alias == channel_name;
174 });
175 it == aliases.end()) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700176 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800177 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700178
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700179 if (channel->type()->string_view() == message_type) {
180 if (!found_exact) {
181 found_channels_now.clear();
182 found_exact = true;
183 }
184 } else if (!found_exact && channel->type()->string_view().find(
185 message_type) != std::string_view::npos) {
186 } else {
187 continue;
188 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700189
190 if (!FLAGS_all && !channel_filter(channel)) {
191 LOG(FATAL) << "matched channel does not pass the channel filter: \""
192 << channel_filter_description
193 << "\" [matched channel info]: "
194 << configuration::CleanedChannelToString(channel);
195 }
196
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700197 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800198 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800199
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700200 if (found_channels_now.empty()) {
201 LOG(FATAL)
202 << "Could not find any channels with the given name and type for "
203 << channel_name << " " << message_type;
204 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
205 LOG(FATAL) << "Multiple channels found with same type for "
206 << channel_name << " " << message_type;
207 }
208 for (const aos::Channel *channel : found_channels_now) {
209 found_channels.push_back(channel);
210 }
Austin Schuh59f3b0f2021-07-31 20:50:40 -0700211 } while (expect_args && *argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800212
213 return false;
214}
215
216void CliUtilInfo::Autocomplete(
217 std::string_view channel_name, std::string_view message_type,
218 std::function<bool(const aos::Channel *)> channel_filter) {
219 const aos::Configuration *const config_msg = event_loop->configuration();
220 const bool unique_match =
221 std::count_if(config_msg->channels()->begin(),
222 config_msg->channels()->end(),
223 [channel_name, message_type](const aos::Channel *channel) {
224 return channel->name()->string_view() == channel_name &&
225 channel->type()->string_view() == message_type;
226 }) == 1;
227
228 const bool editing_message =
229 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
230 const bool editing_channel =
231 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
232
233 std::cout << "COMPREPLY=(";
234
235 // If we have a unique match, don't provide any suggestions. Otherwise, check
236 // that were're editing one of the two positional arguments.
237 if (!unique_match && (editing_message || editing_channel)) {
238 for (const aos::Channel *channel : *config_msg->channels()) {
239 if (FLAGS_all || channel_filter(channel)) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700240 const std::set<std::string> aliases =
241 aos::configuration::GetChannelAliases(
242 config_msg, channel->name()->string_view(),
243 channel->type()->string_view(), "", event_loop->node());
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800244 // Suggest only message types if the message type argument is being
245 // entered.
246 if (editing_message) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700247 // Then, filter for channel names that exactly match one of the
248 // aliases and types that begin with message_type.
249 if (aliases.contains(std::string(channel_name)) &&
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800250 channel->type()->string_view().find(message_type) == 0) {
251 std::cout << '\'' << channel->type()->c_str() << "' ";
252 }
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700253 } else if (auto it =
254 std::find_if(aliases.begin(), aliases.end(),
255 [channel_name](const std::string &alias) {
256 return alias.find(channel_name) == 0;
257 });
258 it != aliases.end()) {
259 // If the message type is empty, then return full autocomplete.
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800260 // Otherwise, since the message type is poulated yet not being edited,
261 // the user must be editing the channel name alone, in which case only
262 // suggest channel names, not pairs.
Brennan Coslett275965f2023-02-20 15:15:46 -0600263 if (!FLAGS__zsh_compatability && message_type.empty()) {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700264 std::cout << '\'' << *it << ' ' << channel->type()->c_str() << "' ";
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800265 } else {
Sanjay Narayanane5f382a2024-06-23 18:17:23 -0700266 std::cout << '\'' << *it << "' ";
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800267 }
268 }
269 }
270 }
271 }
272 std::cout << ')';
273}
274
Austin Schuh893d7f42022-09-16 15:01:35 -0700275void PrintMessage(const std::string_view node_name, const aos::Channel *channel,
276 const aos::Context &context, aos::FastStringBuilder *builder,
277 PrintOptions options) {
278 // Print the flatbuffer out to stdout, both to remove the
279 // unnecessary cruft from glog and to allow the user to readily
280 // redirect just the logged output independent of any debugging
281 // information on stderr.
282
283 builder->Reset();
284
285 CHECK(flatbuffers::Verify(*channel->schema(),
286 *channel->schema()->root_table(),
287 static_cast<const uint8_t *>(context.data),
288 static_cast<size_t>(context.size)))
289 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
290 << channel->type()->c_str();
291
292 aos::FlatbufferToJson(
293 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
294 {options.pretty, static_cast<size_t>(options.max_vector_size),
Austin Schuhbe6d2962022-11-01 09:24:56 -0700295 options.pretty_max, options.hex});
Austin Schuh893d7f42022-09-16 15:01:35 -0700296
297 if (options.json) {
298 std::cout << "{";
299 if (!node_name.empty()) {
300 std::cout << "\"node\": \"" << node_name << "\", ";
301 }
302 std::cout << "\"monotonic_event_time\": ";
303 StreamSeconds(std::cout, context.monotonic_event_time);
304 std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
305 << "\", ";
306
307 if (context.monotonic_remote_time != context.monotonic_event_time) {
308 std::cout << "\"monotonic_remote_time\": ";
309 StreamSeconds(std::cout, context.monotonic_remote_time);
310 std::cout << ", \"realtime_remote_time\": \""
311 << context.realtime_remote_time << "\", ";
312 }
313
314 std::cout << "\"channel\": "
315 << aos::configuration::StrippedChannelToString(channel)
Naman Gupta54047212022-09-23 14:10:30 -0700316 << ", \"data\": " << *builder << "}";
Austin Schuh893d7f42022-09-16 15:01:35 -0700317 } else {
318 if (!node_name.empty()) {
319 std::cout << node_name << " ";
320 }
321
322 if (options.print_timestamps) {
323 if (context.monotonic_remote_time != context.monotonic_event_time) {
324 std::cout << context.realtime_event_time << " ("
325 << context.monotonic_event_time << ") sent "
326 << context.realtime_remote_time << " ("
327 << context.monotonic_remote_time << ") "
328 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Naman Gupta54047212022-09-23 14:10:30 -0700329 << ": " << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700330 } else {
331 std::cout << context.realtime_event_time << " ("
332 << context.monotonic_event_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 }
336 } else {
Naman Gupta54047212022-09-23 14:10:30 -0700337 std::cout << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700338 }
339 }
340}
341
342void PrintMessage(const aos::Channel *channel, const aos::Context &context,
343 aos::FastStringBuilder *builder, PrintOptions options) {
344 PrintMessage("", channel, context, builder, options);
345}
346
347void PrintMessage(const std::string_view node_name,
348 aos::NodeEventLoopFactory *node_factory,
349 const aos::Channel *channel, const aos::Context &context,
350 aos::FastStringBuilder *builder, PrintOptions options) {
351 if (!options.json && options.distributed_clock) {
352 std::cout << node_factory->ToDistributedClock(context.monotonic_event_time)
353 << " ";
354 }
355 PrintMessage(node_name, channel, context, builder, options);
356}
357
Naman Gupta54047212022-09-23 14:10:30 -0700358Printer::Printer(PrintOptions options, bool flush)
359 : options_(options), flush_(flush) {
360 if (options_.json) {
361 std::cout << "[";
362 }
363}
364
365Printer::~Printer() {
366 if (options_.json) {
367 if (message_count_ > 0) {
368 std::cout << "\n]\n";
369 } else {
370 std::cout << "]\n";
371 }
372 }
373}
374
375void Printer::PrintMessage(const std::string_view node_name,
376 aos::NodeEventLoopFactory *node_factory,
377 const aos::Channel *channel,
378 const aos::Context &context) {
379 if (options_.json) {
380 if (message_count_ != 0) {
381 std::cout << ",\n ";
382 } else {
383 std::cout << "\n ";
384 }
385 }
386
387 aos::PrintMessage(node_name, node_factory, channel, context, &str_builder_,
388 options_);
389
390 if (!options_.json) {
391 if (flush_) {
392 std::cout << std::endl;
393 } else {
394 std::cout << "\n";
395 }
396 }
397 ++message_count_;
398}
399
400void Printer::PrintMessage(const aos::Channel *channel,
401 const aos::Context &context) {
402 if (options_.json) {
403 if (message_count_ != 0) {
404 std::cout << ",\n ";
405 } else {
406 std::cout << "\n ";
407 }
408 }
409
410 aos::PrintMessage(channel, context, &str_builder_, options_);
411
412 if (!options_.json) {
413 if (flush_) {
414 std::cout << std::endl;
415 } else {
416 std::cout << "\n";
417 }
418 }
419 ++message_count_;
420}
421
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800422} // namespace aos