Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 1 | #include <stdlib.h> |
| 2 | |
| 3 | #include <iostream> |
| 4 | #include <optional> |
| 5 | #include <ostream> |
| 6 | #include <sstream> |
| 7 | #include <string_view> |
| 8 | #include <vector> |
| 9 | |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame] | 10 | #include "flatbuffers/flatbuffers.h" |
| 11 | #include "gflags/gflags.h" |
| 12 | #include "glog/logging.h" |
| 13 | |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 14 | #include "aos/configuration_generated.h" |
| 15 | #include "aos/events/event_loop.h" |
| 16 | #include "aos/events/logging/log_reader.h" |
| 17 | #include "aos/events/logging/log_reader_utils.h" |
| 18 | #include "aos/events/logging/log_replayer_config_generated.h" |
Eric Schmiedeberg | bd1c9da | 2023-07-14 14:46:59 -0400 | [diff] [blame] | 19 | #include "aos/events/logging/log_replayer_stats_generated.h" |
| 20 | #include "aos/events/logging/log_replayer_stats_schema.h" |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 21 | #include "aos/events/logging/logfile_sorting.h" |
| 22 | #include "aos/events/logging/logfile_utils.h" |
| 23 | #include "aos/events/logging/replay_timing_generated.h" |
| 24 | #include "aos/events/logging/replay_timing_schema.h" |
| 25 | #include "aos/events/shm_event_loop.h" |
| 26 | #include "aos/flatbuffer_merge.h" |
| 27 | #include "aos/init.h" |
| 28 | #include "aos/json_to_flatbuffer.h" |
| 29 | #include "aos/util/file.h" |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 30 | |
| 31 | DEFINE_string(config, "", "If specified, overrides logged configuration."); |
| 32 | DEFINE_bool( |
| 33 | plot_timing, true, |
| 34 | "If set, generates a plot of the replay timing--namely, the errors between " |
| 35 | "when we " |
| 36 | "should've sent messages and when we actually sent replayed messages."); |
| 37 | DEFINE_bool(skip_sender_channels, true, |
| 38 | "If set, skips replay of the channels applications replay on"); |
| 39 | DEFINE_bool(skip_replay, false, |
| 40 | "If set, skips actually running the replay. Useful for writing a " |
| 41 | "config without running replay"); |
| 42 | DEFINE_bool( |
| 43 | print_config, false, |
| 44 | "If set, prints the config that will be used for replay to stdout as json"); |
| 45 | DEFINE_string( |
| 46 | replay_config, "", |
| 47 | "Path to the configuration used for log replay which includes items such " |
| 48 | "as channels to remap, and applications to target for replay. If not set, " |
| 49 | "log_reader will run on shm event loop. "); |
| 50 | DEFINE_string(merge_with_config, "", |
| 51 | "A valid json string to be merged with config. This is used to " |
| 52 | "add extra applications needed to run only for log_replayer"); |
Eric Schmiedeberg | bd1c9da | 2023-07-14 14:46:59 -0400 | [diff] [blame] | 53 | DEFINE_bool(print_stats, true, |
| 54 | "if set, prints the LogReplayerStats message as JSON to stdout"); |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 55 | |
| 56 | namespace aos::logger { |
| 57 | |
| 58 | int Main(int argc, char *argv[]) { |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 59 | const std::vector<aos::logger::LogFile> logfiles = |
Austin Schuh | c160973 | 2023-06-26 11:51:28 -0700 | [diff] [blame] | 60 | aos::logger::SortParts(aos::logger::FindLogs(argc, argv)); |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 61 | |
Naman Gupta | 7eabb3a | 2023-03-01 11:49:44 -0800 | [diff] [blame] | 62 | aos::logger::LogReader config_reader(logfiles); |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 63 | aos::FlatbufferDetachedBuffer<aos::Configuration> config = |
| 64 | FLAGS_config.empty() |
Naman Gupta | 7eabb3a | 2023-03-01 11:49:44 -0800 | [diff] [blame] | 65 | ? CopyFlatBuffer<aos::Configuration>(config_reader.configuration()) |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 66 | : aos::configuration::ReadConfig(FLAGS_config); |
| 67 | |
| 68 | if (FLAGS_plot_timing) { |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 69 | // Go through the effort to add a ReplayTiming channel to ensure that we |
| 70 | // can capture timing information from the replay. |
Naman Gupta | 7eabb3a | 2023-03-01 11:49:44 -0800 | [diff] [blame] | 71 | const aos::Configuration *raw_config = &config.message(); |
James Kuszmaul | 741a4d0 | 2023-01-05 14:59:21 -0800 | [diff] [blame] | 72 | aos::ChannelT channel_overrides; |
| 73 | channel_overrides.max_size = 10000; |
| 74 | channel_overrides.frequency = 10000; |
| 75 | config = aos::configuration::AddChannelToConfiguration( |
| 76 | raw_config, "/timing", |
| 77 | aos::FlatbufferSpan<reflection::Schema>( |
| 78 | aos::timing::ReplayTimingSchema()), |
| 79 | aos::configuration::GetMyNode(raw_config), channel_overrides); |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 80 | } |
| 81 | |
Eric Schmiedeberg | bd1c9da | 2023-07-14 14:46:59 -0400 | [diff] [blame] | 82 | // Add the LogReplayerStats channel |
| 83 | const aos::Configuration *raw_config = &config.message(); |
| 84 | aos::ChannelT channel_overrides; |
| 85 | channel_overrides.max_size = 10000; |
| 86 | channel_overrides.frequency = 1; |
| 87 | config = aos::configuration::AddChannelToConfiguration( |
| 88 | raw_config, "/replay", |
| 89 | aos::FlatbufferSpan<reflection::Schema>(aos::LogReplayerStatsSchema()), |
| 90 | aos::configuration::GetMyNode(raw_config), channel_overrides); |
| 91 | |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 92 | if (!FLAGS_merge_with_config.empty()) { |
| 93 | config = aos::configuration::MergeWithConfig(&config.message(), |
| 94 | FLAGS_merge_with_config); |
| 95 | } |
| 96 | |
| 97 | std::optional<aos::FlatbufferDetachedBuffer<ReplayConfig>> replay_config = |
| 98 | FLAGS_replay_config.empty() |
| 99 | ? std::nullopt |
| 100 | : std::make_optional(aos::JsonToFlatbuffer<ReplayConfig>( |
| 101 | aos::util::ReadFileToStringOrDie(FLAGS_replay_config.data()))); |
Naman Gupta | 3dcab96 | 2023-03-01 15:09:53 -0800 | [diff] [blame] | 102 | std::vector<std::pair<std::string, std::string>> message_filter; |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 103 | if (FLAGS_skip_sender_channels && replay_config.has_value()) { |
| 104 | CHECK(replay_config.value().message().has_active_nodes()); |
| 105 | std::vector<const Node *> active_nodes; |
| 106 | for (const auto &node : *replay_config.value().message().active_nodes()) { |
| 107 | active_nodes.emplace_back(configuration::GetNode( |
| 108 | &config.message(), node->name()->string_view())); |
| 109 | } |
| 110 | |
| 111 | std::vector<std::string> applications; |
| 112 | for (const auto &application : |
| 113 | *replay_config.value().message().applications()) { |
| 114 | if (application->name()->string_view() != "camera_message_interceptor") { |
| 115 | applications.emplace_back(application->name()->string_view()); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | aos::logger::ChannelsInLogResult channels = |
| 120 | ChannelsInLog(logfiles, active_nodes, applications); |
| 121 | for (auto const &channel : |
| 122 | channels.watchers_and_fetchers_without_senders.value()) { |
Naman Gupta | 7eabb3a | 2023-03-01 11:49:44 -0800 | [diff] [blame] | 123 | message_filter.emplace_back(channel.name, channel.type); |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 124 | } |
| 125 | } |
| 126 | |
| 127 | aos::logger::LogReader reader( |
| 128 | logfiles, &config.message(), |
| 129 | message_filter.empty() ? nullptr : &message_filter); |
| 130 | |
Naman Gupta | 7eabb3a | 2023-03-01 11:49:44 -0800 | [diff] [blame] | 131 | if (replay_config.has_value() && |
| 132 | replay_config.value().message().has_remap_channels()) { |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 133 | for (auto const &remap_channel : |
| 134 | *replay_config.value().message().remap_channels()) { |
| 135 | auto const &channel = remap_channel->channel(); |
| 136 | std::string_view new_type = remap_channel->has_new_type() |
| 137 | ? remap_channel->new_type()->string_view() |
| 138 | : channel->type()->string_view(); |
| 139 | reader.RemapLoggedChannel( |
| 140 | channel->name()->string_view(), channel->type()->string_view(), |
| 141 | remap_channel->prefix()->string_view(), new_type); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | if (FLAGS_print_config) { |
| 146 | // TODO(Naman): Replace with config writer if it will be cleaner |
| 147 | std::cout << FlatbufferToJson(reader.configuration()) << std::endl; |
| 148 | } |
| 149 | |
| 150 | if (!FLAGS_skip_replay) { |
| 151 | aos::ShmEventLoop event_loop(reader.configuration()); |
| 152 | |
| 153 | event_loop.SkipAosLog(); |
| 154 | event_loop.SkipTimingReport(); |
| 155 | |
Eric Schmiedeberg | bd1c9da | 2023-07-14 14:46:59 -0400 | [diff] [blame] | 156 | aos::Sender<aos::LogReplayerStats> stats_sender = |
| 157 | event_loop.MakeSender<aos::LogReplayerStats>("/replay"); |
| 158 | auto builder = stats_sender.MakeBuilder(); |
| 159 | auto node_name = builder.fbb()->CreateString(event_loop.node()->name()); |
| 160 | flatbuffers::Offset<aos::ReplayConfig> replay_config_offset; |
| 161 | if (replay_config.has_value()) { |
| 162 | replay_config_offset = |
| 163 | aos::CopyFlatBuffer(&replay_config.value().message(), builder.fbb()); |
| 164 | } |
| 165 | |
| 166 | auto stats_builder = builder.MakeBuilder<aos::LogReplayerStats>(); |
| 167 | if (replay_config.has_value()) { |
| 168 | stats_builder.add_replay_config(replay_config_offset); |
| 169 | } |
| 170 | |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 171 | reader.Register(&event_loop); |
Eric Schmiedeberg | bd1c9da | 2023-07-14 14:46:59 -0400 | [diff] [blame] | 172 | |
| 173 | // Save off the start and end times of replay. |
| 174 | reader.OnStart(event_loop.node(), [&event_loop, &stats_builder, |
| 175 | &node_name]() { |
| 176 | stats_builder.add_node(node_name); |
| 177 | stats_builder.add_realtime_start_time( |
| 178 | std::chrono::nanoseconds(event_loop.realtime_now().time_since_epoch()) |
| 179 | .count()); |
| 180 | |
| 181 | stats_builder.add_monotonic_start_time( |
| 182 | std::chrono::nanoseconds( |
| 183 | event_loop.monotonic_now().time_since_epoch()) |
| 184 | .count()); |
| 185 | }); |
| 186 | |
| 187 | reader.OnEnd(event_loop.node(), [&event_loop, &stats_builder, &builder]() { |
| 188 | stats_builder.add_realtime_end_time( |
| 189 | std::chrono::nanoseconds(event_loop.realtime_now().time_since_epoch()) |
| 190 | .count()); |
| 191 | |
| 192 | stats_builder.add_monotonic_end_time( |
| 193 | std::chrono::nanoseconds( |
| 194 | event_loop.monotonic_now().time_since_epoch()) |
| 195 | .count()); |
| 196 | builder.CheckOk(builder.Send(stats_builder.Finish())); |
| 197 | }); |
| 198 | |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 199 | reader.OnEnd(event_loop.node(), [&event_loop]() { event_loop.Exit(); }); |
| 200 | |
| 201 | if (FLAGS_plot_timing) { |
| 202 | aos::Sender<aos::timing::ReplayTiming> replay_timing_sender = |
| 203 | event_loop.MakeSender<aos::timing::ReplayTiming>("/timing"); |
| 204 | reader.set_timing_accuracy_sender(event_loop.node(), |
| 205 | std::move(replay_timing_sender)); |
| 206 | } |
| 207 | |
| 208 | event_loop.Run(); |
| 209 | |
| 210 | reader.Deregister(); |
Eric Schmiedeberg | bd1c9da | 2023-07-14 14:46:59 -0400 | [diff] [blame] | 211 | |
| 212 | if (FLAGS_print_stats) { |
| 213 | aos::Fetcher<aos::LogReplayerStats> stats_fetcher = |
| 214 | event_loop.MakeFetcher<aos::LogReplayerStats>("/replay"); |
| 215 | CHECK(stats_fetcher.Fetch()) << "Failed to fetch LogReplayerStats!"; |
| 216 | std::cout << aos::FlatbufferToJson(stats_fetcher.get()); |
| 217 | } |
Naman Gupta | e7fe355 | 2022-11-23 13:52:03 -0800 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | return EXIT_SUCCESS; |
| 221 | } |
| 222 | |
| 223 | } // namespace aos::logger |
| 224 | |
| 225 | int main(int argc, char *argv[]) { |
| 226 | gflags::SetUsageMessage( |
| 227 | R"message(Binary to replay the full contents of a logfile into shared memory. |
| 228 | #replay_config should be set in order to replay a set of nodes, applications and channels |
| 229 | #print config and skip replay, if you only want to print the config and not do log replay |
| 230 | Use case #1: log_replayer <log_dir> --print_config --replay_config=<path_to_config> --skip_replay |
| 231 | Use case #2: log_replayer <log_dir> --nofatal_sent_too_fast --replay_config=<path_to_config> |
| 232 | )message"); |
| 233 | |
| 234 | aos::InitGoogle(&argc, &argv); |
| 235 | return aos::logger::Main(argc, argv); |
| 236 | } |