blob: bc1fb80d10672bda189d41d2d32b89e4b3ab8a02 [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>
10
Austin Schuh893d7f42022-09-16 15:01:35 -070011#include "aos/configuration.h"
12#include "aos/events/shm_event_loop.h"
13#include "aos/events/simulated_event_loop.h"
14#include "aos/time/time.h"
15
Austin Schuh8e2dfc62022-08-17 16:36:00 -070016DEFINE_string(config, "aos_config.json", "File path of aos configuration");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080017
18DEFINE_bool(
19 _bash_autocomplete, false,
20 "Internal use: Outputs channel list for use with autocomplete script.");
Brennan Coslett275965f2023-02-20 15:15:46 -060021
22DEFINE_bool(_zsh_compatability, false,
23 "Internal use: Force completion to complete either channels or "
24 "message_types, zsh doesn't handle spaces well.");
25
Brian Silvermanea2c95f2021-02-10 18:10:26 -080026DEFINE_string(_bash_autocomplete_word, "",
27 "Internal use: Current word being autocompleted");
28
29DEFINE_bool(all, false,
30 "If true, print out the channels for all nodes, not just the "
31 "channels which are visible on this node.");
32
33namespace aos {
34namespace {
35
Austin Schuh893d7f42022-09-16 15:01:35 -070036namespace chrono = std::chrono;
37
Brian Silvermanea2c95f2021-02-10 18:10:26 -080038bool EndsWith(std::string_view str, std::string_view ending) {
39 const std::size_t offset = str.size() - ending.size();
40 return str.size() >= ending.size() &&
41 std::equal(str.begin() + offset, str.end(), ending.begin(),
42 ending.end());
43}
44
Austin Schuh893d7f42022-09-16 15:01:35 -070045void StreamSeconds(std::ostream &stream,
46 const aos::monotonic_clock::time_point now) {
47 if (now < monotonic_clock::epoch()) {
48 chrono::seconds seconds =
49 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
50
51 stream << "-" << -seconds.count() << "." << std::setfill('0')
52 << std::setw(9)
53 << chrono::duration_cast<chrono::nanoseconds>(seconds -
54 now.time_since_epoch())
55 .count();
56 } else {
57 chrono::seconds seconds =
58 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
59 stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
60 << chrono::duration_cast<chrono::nanoseconds>(
61 now.time_since_epoch() - seconds)
62 .count();
63 }
64}
65
Brian Silvermanea2c95f2021-02-10 18:10:26 -080066} // namespace
67
68bool CliUtilInfo::Initialize(
69 int *argc, char ***argv,
Austin Schuh59f3b0f2021-07-31 20:50:40 -070070 std::function<bool(const aos::Channel *)> channel_filter,
Austin Schuhba2c8652022-08-17 14:56:08 -070071 std::string_view channel_filter_description, bool expect_args) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080072 // Don't generate failure output if the config doesn't exist while attempting
73 // to autocomplete.
Milind Upadhyay17098ba2022-04-15 22:18:50 -070074 if (FLAGS__bash_autocomplete &&
75 (!(EndsWith(FLAGS_config, ".json") || EndsWith(FLAGS_config, ".bfbs")))) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080076 std::cout << "COMPREPLY=()";
77 return true;
78 }
79
Milind Upadhyay17098ba2022-04-15 22:18:50 -070080 config = aos::configuration::MaybeReadConfig(FLAGS_config);
81 if (FLAGS__bash_autocomplete && !config.has_value()) {
82 std::cout << "COMPREPLY=()";
83 return true;
84 }
85 CHECK(config.has_value()) << "Could not read config. See above errors.";
86
Brian Silvermanea2c95f2021-02-10 18:10:26 -080087 event_loop.emplace(&config->message());
88 event_loop->SkipTimingReport();
89 event_loop->SkipAosLog();
90
Austin Schuhd30a5ca2021-07-31 20:49:35 -070091 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -080092 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -070093
94 do {
95 std::string channel_name;
96 std::string message_type;
97 if (*argc > 1) {
98 channel_name = (*argv)[1];
99 ShiftArgs(argc, argv);
100 }
101 if (*argc > 1) {
102 message_type = (*argv)[1];
103 ShiftArgs(argc, argv);
104 }
105
106 if (FLAGS__bash_autocomplete) {
107 Autocomplete(channel_name, message_type, channel_filter);
108 return true;
109 }
110
111 if (channel_name.empty() && message_type.empty()) {
112 std::cout << "Channels:\n";
113 for (const aos::Channel *channel : *channels) {
114 if (FLAGS_all || channel_filter(channel)) {
115 std::cout << channel->name()->c_str() << ' '
116 << channel->type()->c_str() << '\n';
117 }
118 }
119 return true;
120 }
121
122 std::vector<const aos::Channel *> found_channels_now;
123 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800124 for (const aos::Channel *channel : *channels) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700125 if (channel->name()->c_str() != channel_name) {
126 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800127 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700128
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700129 if (channel->type()->string_view() == message_type) {
130 if (!found_exact) {
131 found_channels_now.clear();
132 found_exact = true;
133 }
134 } else if (!found_exact && channel->type()->string_view().find(
135 message_type) != std::string_view::npos) {
136 } else {
137 continue;
138 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700139
140 if (!FLAGS_all && !channel_filter(channel)) {
141 LOG(FATAL) << "matched channel does not pass the channel filter: \""
142 << channel_filter_description
143 << "\" [matched channel info]: "
144 << configuration::CleanedChannelToString(channel);
145 }
146
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700147 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800148 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800149
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700150 if (found_channels_now.empty()) {
151 LOG(FATAL)
152 << "Could not find any channels with the given name and type for "
153 << channel_name << " " << message_type;
154 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
155 LOG(FATAL) << "Multiple channels found with same type for "
156 << channel_name << " " << message_type;
157 }
158 for (const aos::Channel *channel : found_channels_now) {
159 found_channels.push_back(channel);
160 }
Austin Schuh59f3b0f2021-07-31 20:50:40 -0700161 } while (expect_args && *argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800162
163 return false;
164}
165
166void CliUtilInfo::Autocomplete(
167 std::string_view channel_name, std::string_view message_type,
168 std::function<bool(const aos::Channel *)> channel_filter) {
169 const aos::Configuration *const config_msg = event_loop->configuration();
170 const bool unique_match =
171 std::count_if(config_msg->channels()->begin(),
172 config_msg->channels()->end(),
173 [channel_name, message_type](const aos::Channel *channel) {
174 return channel->name()->string_view() == channel_name &&
175 channel->type()->string_view() == message_type;
176 }) == 1;
177
178 const bool editing_message =
179 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
180 const bool editing_channel =
181 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
182
183 std::cout << "COMPREPLY=(";
184
185 // If we have a unique match, don't provide any suggestions. Otherwise, check
186 // that were're editing one of the two positional arguments.
187 if (!unique_match && (editing_message || editing_channel)) {
188 for (const aos::Channel *channel : *config_msg->channels()) {
189 if (FLAGS_all || channel_filter(channel)) {
190 // Suggest only message types if the message type argument is being
191 // entered.
192 if (editing_message) {
193 // Then, filter for only channel names that match exactly and types
194 // that begin with message_type.
195 if (channel->name()->string_view() == channel_name &&
196 channel->type()->string_view().find(message_type) == 0) {
197 std::cout << '\'' << channel->type()->c_str() << "' ";
198 }
199 } else if (channel->name()->string_view().find(channel_name) == 0) {
200 // If the message type empty, then return full autocomplete.
201 // Otherwise, since the message type is poulated yet not being edited,
202 // the user must be editing the channel name alone, in which case only
203 // suggest channel names, not pairs.
Brennan Coslett275965f2023-02-20 15:15:46 -0600204 // If _split_complete flag is set then dont return
205 // pairs of values
206 if (!FLAGS__zsh_compatability && message_type.empty()) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800207 std::cout << '\'' << channel->name()->c_str() << ' '
208 << channel->type()->c_str() << "' ";
209 } else {
210 std::cout << '\'' << channel->name()->c_str() << "' ";
211 }
212 }
213 }
214 }
215 }
216 std::cout << ')';
217}
218
Austin Schuh893d7f42022-09-16 15:01:35 -0700219void PrintMessage(const std::string_view node_name, const aos::Channel *channel,
220 const aos::Context &context, aos::FastStringBuilder *builder,
221 PrintOptions options) {
222 // Print the flatbuffer out to stdout, both to remove the
223 // unnecessary cruft from glog and to allow the user to readily
224 // redirect just the logged output independent of any debugging
225 // information on stderr.
226
227 builder->Reset();
228
229 CHECK(flatbuffers::Verify(*channel->schema(),
230 *channel->schema()->root_table(),
231 static_cast<const uint8_t *>(context.data),
232 static_cast<size_t>(context.size)))
233 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
234 << channel->type()->c_str();
235
236 aos::FlatbufferToJson(
237 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
238 {options.pretty, static_cast<size_t>(options.max_vector_size),
Austin Schuhbe6d2962022-11-01 09:24:56 -0700239 options.pretty_max, options.hex});
Austin Schuh893d7f42022-09-16 15:01:35 -0700240
241 if (options.json) {
242 std::cout << "{";
243 if (!node_name.empty()) {
244 std::cout << "\"node\": \"" << node_name << "\", ";
245 }
246 std::cout << "\"monotonic_event_time\": ";
247 StreamSeconds(std::cout, context.monotonic_event_time);
248 std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
249 << "\", ";
250
251 if (context.monotonic_remote_time != context.monotonic_event_time) {
252 std::cout << "\"monotonic_remote_time\": ";
253 StreamSeconds(std::cout, context.monotonic_remote_time);
254 std::cout << ", \"realtime_remote_time\": \""
255 << context.realtime_remote_time << "\", ";
256 }
257
258 std::cout << "\"channel\": "
259 << aos::configuration::StrippedChannelToString(channel)
Naman Gupta54047212022-09-23 14:10:30 -0700260 << ", \"data\": " << *builder << "}";
Austin Schuh893d7f42022-09-16 15:01:35 -0700261 } else {
262 if (!node_name.empty()) {
263 std::cout << node_name << " ";
264 }
265
266 if (options.print_timestamps) {
267 if (context.monotonic_remote_time != context.monotonic_event_time) {
268 std::cout << context.realtime_event_time << " ("
269 << context.monotonic_event_time << ") sent "
270 << context.realtime_remote_time << " ("
271 << context.monotonic_remote_time << ") "
272 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Naman Gupta54047212022-09-23 14:10:30 -0700273 << ": " << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700274 } else {
275 std::cout << context.realtime_event_time << " ("
276 << context.monotonic_event_time << ") "
277 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Naman Gupta54047212022-09-23 14:10:30 -0700278 << ": " << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700279 }
280 } else {
Naman Gupta54047212022-09-23 14:10:30 -0700281 std::cout << *builder;
Austin Schuh893d7f42022-09-16 15:01:35 -0700282 }
283 }
284}
285
286void PrintMessage(const aos::Channel *channel, const aos::Context &context,
287 aos::FastStringBuilder *builder, PrintOptions options) {
288 PrintMessage("", channel, context, builder, options);
289}
290
291void PrintMessage(const std::string_view node_name,
292 aos::NodeEventLoopFactory *node_factory,
293 const aos::Channel *channel, const aos::Context &context,
294 aos::FastStringBuilder *builder, PrintOptions options) {
295 if (!options.json && options.distributed_clock) {
296 std::cout << node_factory->ToDistributedClock(context.monotonic_event_time)
297 << " ";
298 }
299 PrintMessage(node_name, channel, context, builder, options);
300}
301
Naman Gupta54047212022-09-23 14:10:30 -0700302Printer::Printer(PrintOptions options, bool flush)
303 : options_(options), flush_(flush) {
304 if (options_.json) {
305 std::cout << "[";
306 }
307}
308
309Printer::~Printer() {
310 if (options_.json) {
311 if (message_count_ > 0) {
312 std::cout << "\n]\n";
313 } else {
314 std::cout << "]\n";
315 }
316 }
317}
318
319void Printer::PrintMessage(const std::string_view node_name,
320 aos::NodeEventLoopFactory *node_factory,
321 const aos::Channel *channel,
322 const aos::Context &context) {
323 if (options_.json) {
324 if (message_count_ != 0) {
325 std::cout << ",\n ";
326 } else {
327 std::cout << "\n ";
328 }
329 }
330
331 aos::PrintMessage(node_name, node_factory, channel, context, &str_builder_,
332 options_);
333
334 if (!options_.json) {
335 if (flush_) {
336 std::cout << std::endl;
337 } else {
338 std::cout << "\n";
339 }
340 }
341 ++message_count_;
342}
343
344void Printer::PrintMessage(const aos::Channel *channel,
345 const aos::Context &context) {
346 if (options_.json) {
347 if (message_count_ != 0) {
348 std::cout << ",\n ";
349 } else {
350 std::cout << "\n ";
351 }
352 }
353
354 aos::PrintMessage(channel, context, &str_builder_, options_);
355
356 if (!options_.json) {
357 if (flush_) {
358 std::cout << std::endl;
359 } else {
360 std::cout << "\n";
361 }
362 }
363 ++message_count_;
364}
365
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800366} // namespace aos