blob: 2016a7132b675a0434ee2a1a25eb2ab711a08b7f [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>
Brian Silvermanea2c95f2021-02-10 18:10:26 -08008#include <iostream>
9
Austin Schuh893d7f42022-09-16 15:01:35 -070010#include "aos/configuration.h"
11#include "aos/events/shm_event_loop.h"
12#include "aos/events/simulated_event_loop.h"
13#include "aos/time/time.h"
14
Austin Schuh8e2dfc62022-08-17 16:36:00 -070015DEFINE_string(config, "aos_config.json", "File path of aos configuration");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080016
17DEFINE_bool(
18 _bash_autocomplete, false,
19 "Internal use: Outputs channel list for use with autocomplete script.");
20DEFINE_string(_bash_autocomplete_word, "",
21 "Internal use: Current word being autocompleted");
22
23DEFINE_bool(all, false,
24 "If true, print out the channels for all nodes, not just the "
25 "channels which are visible on this node.");
26
27namespace aos {
28namespace {
29
Austin Schuh893d7f42022-09-16 15:01:35 -070030namespace chrono = std::chrono;
31
Brian Silvermanea2c95f2021-02-10 18:10:26 -080032bool EndsWith(std::string_view str, std::string_view ending) {
33 const std::size_t offset = str.size() - ending.size();
34 return str.size() >= ending.size() &&
35 std::equal(str.begin() + offset, str.end(), ending.begin(),
36 ending.end());
37}
38
Austin Schuh893d7f42022-09-16 15:01:35 -070039void StreamSeconds(std::ostream &stream,
40 const aos::monotonic_clock::time_point now) {
41 if (now < monotonic_clock::epoch()) {
42 chrono::seconds seconds =
43 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
44
45 stream << "-" << -seconds.count() << "." << std::setfill('0')
46 << std::setw(9)
47 << chrono::duration_cast<chrono::nanoseconds>(seconds -
48 now.time_since_epoch())
49 .count();
50 } else {
51 chrono::seconds seconds =
52 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
53 stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
54 << chrono::duration_cast<chrono::nanoseconds>(
55 now.time_since_epoch() - seconds)
56 .count();
57 }
58}
59
Brian Silvermanea2c95f2021-02-10 18:10:26 -080060} // namespace
61
62bool CliUtilInfo::Initialize(
63 int *argc, char ***argv,
Austin Schuh59f3b0f2021-07-31 20:50:40 -070064 std::function<bool(const aos::Channel *)> channel_filter,
Austin Schuhba2c8652022-08-17 14:56:08 -070065 std::string_view channel_filter_description, bool expect_args) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080066 // Don't generate failure output if the config doesn't exist while attempting
67 // to autocomplete.
Milind Upadhyay17098ba2022-04-15 22:18:50 -070068 if (FLAGS__bash_autocomplete &&
69 (!(EndsWith(FLAGS_config, ".json") || EndsWith(FLAGS_config, ".bfbs")))) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080070 std::cout << "COMPREPLY=()";
71 return true;
72 }
73
Milind Upadhyay17098ba2022-04-15 22:18:50 -070074 config = aos::configuration::MaybeReadConfig(FLAGS_config);
75 if (FLAGS__bash_autocomplete && !config.has_value()) {
76 std::cout << "COMPREPLY=()";
77 return true;
78 }
79 CHECK(config.has_value()) << "Could not read config. See above errors.";
80
Brian Silvermanea2c95f2021-02-10 18:10:26 -080081 event_loop.emplace(&config->message());
82 event_loop->SkipTimingReport();
83 event_loop->SkipAosLog();
84
Austin Schuhd30a5ca2021-07-31 20:49:35 -070085 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -080086 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -070087
88 do {
89 std::string channel_name;
90 std::string message_type;
91 if (*argc > 1) {
92 channel_name = (*argv)[1];
93 ShiftArgs(argc, argv);
94 }
95 if (*argc > 1) {
96 message_type = (*argv)[1];
97 ShiftArgs(argc, argv);
98 }
99
100 if (FLAGS__bash_autocomplete) {
101 Autocomplete(channel_name, message_type, channel_filter);
102 return true;
103 }
104
105 if (channel_name.empty() && message_type.empty()) {
106 std::cout << "Channels:\n";
107 for (const aos::Channel *channel : *channels) {
108 if (FLAGS_all || channel_filter(channel)) {
109 std::cout << channel->name()->c_str() << ' '
110 << channel->type()->c_str() << '\n';
111 }
112 }
113 return true;
114 }
115
116 std::vector<const aos::Channel *> found_channels_now;
117 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800118 for (const aos::Channel *channel : *channels) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700119 if (channel->name()->c_str() != channel_name) {
120 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800121 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700122
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700123 if (channel->type()->string_view() == message_type) {
124 if (!found_exact) {
125 found_channels_now.clear();
126 found_exact = true;
127 }
128 } else if (!found_exact && channel->type()->string_view().find(
129 message_type) != std::string_view::npos) {
130 } else {
131 continue;
132 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700133
134 if (!FLAGS_all && !channel_filter(channel)) {
135 LOG(FATAL) << "matched channel does not pass the channel filter: \""
136 << channel_filter_description
137 << "\" [matched channel info]: "
138 << configuration::CleanedChannelToString(channel);
139 }
140
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700141 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800142 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800143
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700144 if (found_channels_now.empty()) {
145 LOG(FATAL)
146 << "Could not find any channels with the given name and type for "
147 << channel_name << " " << message_type;
148 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
149 LOG(FATAL) << "Multiple channels found with same type for "
150 << channel_name << " " << message_type;
151 }
152 for (const aos::Channel *channel : found_channels_now) {
153 found_channels.push_back(channel);
154 }
Austin Schuh59f3b0f2021-07-31 20:50:40 -0700155 } while (expect_args && *argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800156
157 return false;
158}
159
160void CliUtilInfo::Autocomplete(
161 std::string_view channel_name, std::string_view message_type,
162 std::function<bool(const aos::Channel *)> channel_filter) {
163 const aos::Configuration *const config_msg = event_loop->configuration();
164 const bool unique_match =
165 std::count_if(config_msg->channels()->begin(),
166 config_msg->channels()->end(),
167 [channel_name, message_type](const aos::Channel *channel) {
168 return channel->name()->string_view() == channel_name &&
169 channel->type()->string_view() == message_type;
170 }) == 1;
171
172 const bool editing_message =
173 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
174 const bool editing_channel =
175 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
176
177 std::cout << "COMPREPLY=(";
178
179 // If we have a unique match, don't provide any suggestions. Otherwise, check
180 // that were're editing one of the two positional arguments.
181 if (!unique_match && (editing_message || editing_channel)) {
182 for (const aos::Channel *channel : *config_msg->channels()) {
183 if (FLAGS_all || channel_filter(channel)) {
184 // Suggest only message types if the message type argument is being
185 // entered.
186 if (editing_message) {
187 // Then, filter for only channel names that match exactly and types
188 // that begin with message_type.
189 if (channel->name()->string_view() == channel_name &&
190 channel->type()->string_view().find(message_type) == 0) {
191 std::cout << '\'' << channel->type()->c_str() << "' ";
192 }
193 } else if (channel->name()->string_view().find(channel_name) == 0) {
194 // If the message type empty, then return full autocomplete.
195 // Otherwise, since the message type is poulated yet not being edited,
196 // the user must be editing the channel name alone, in which case only
197 // suggest channel names, not pairs.
198 if (message_type.empty()) {
199 std::cout << '\'' << channel->name()->c_str() << ' '
200 << channel->type()->c_str() << "' ";
201 } else {
202 std::cout << '\'' << channel->name()->c_str() << "' ";
203 }
204 }
205 }
206 }
207 }
208 std::cout << ')';
209}
210
Austin Schuh893d7f42022-09-16 15:01:35 -0700211void PrintMessage(const std::string_view node_name, const aos::Channel *channel,
212 const aos::Context &context, aos::FastStringBuilder *builder,
213 PrintOptions options) {
214 // Print the flatbuffer out to stdout, both to remove the
215 // unnecessary cruft from glog and to allow the user to readily
216 // redirect just the logged output independent of any debugging
217 // information on stderr.
218
219 builder->Reset();
220
221 CHECK(flatbuffers::Verify(*channel->schema(),
222 *channel->schema()->root_table(),
223 static_cast<const uint8_t *>(context.data),
224 static_cast<size_t>(context.size)))
225 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
226 << channel->type()->c_str();
227
228 aos::FlatbufferToJson(
229 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
230 {options.pretty, static_cast<size_t>(options.max_vector_size),
231 options.pretty_max, options.use_hex});
232
233 if (options.json) {
234 std::cout << "{";
235 if (!node_name.empty()) {
236 std::cout << "\"node\": \"" << node_name << "\", ";
237 }
238 std::cout << "\"monotonic_event_time\": ";
239 StreamSeconds(std::cout, context.monotonic_event_time);
240 std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
241 << "\", ";
242
243 if (context.monotonic_remote_time != context.monotonic_event_time) {
244 std::cout << "\"monotonic_remote_time\": ";
245 StreamSeconds(std::cout, context.monotonic_remote_time);
246 std::cout << ", \"realtime_remote_time\": \""
247 << context.realtime_remote_time << "\", ";
248 }
249
250 std::cout << "\"channel\": "
251 << aos::configuration::StrippedChannelToString(channel)
252 << ", \"data\": " << *builder << "}\n";
253 } else {
254 if (!node_name.empty()) {
255 std::cout << node_name << " ";
256 }
257
258 if (options.print_timestamps) {
259 if (context.monotonic_remote_time != context.monotonic_event_time) {
260 std::cout << context.realtime_event_time << " ("
261 << context.monotonic_event_time << ") sent "
262 << context.realtime_remote_time << " ("
263 << context.monotonic_remote_time << ") "
264 << channel->name()->c_str() << ' ' << channel->type()->c_str()
265 << ": " << *builder << "\n";
266 } else {
267 std::cout << context.realtime_event_time << " ("
268 << context.monotonic_event_time << ") "
269 << channel->name()->c_str() << ' ' << channel->type()->c_str()
270 << ": " << *builder << "\n";
271 }
272 } else {
273 std::cout << *builder << '\n';
274 }
275 }
276}
277
278void PrintMessage(const aos::Channel *channel, const aos::Context &context,
279 aos::FastStringBuilder *builder, PrintOptions options) {
280 PrintMessage("", channel, context, builder, options);
281}
282
283void PrintMessage(const std::string_view node_name,
284 aos::NodeEventLoopFactory *node_factory,
285 const aos::Channel *channel, const aos::Context &context,
286 aos::FastStringBuilder *builder, PrintOptions options) {
287 if (!options.json && options.distributed_clock) {
288 std::cout << node_factory->ToDistributedClock(context.monotonic_event_time)
289 << " ";
290 }
291 PrintMessage(node_name, channel, context, builder, options);
292}
293
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800294} // namespace aos