Eric Schmiedeberg | e279b53 | 2023-04-19 16:36:02 -0600 | [diff] [blame] | 1 | #ifndef AOS_EVENTS_LOGGING_CONFIG_REMAPPER_H_ |
| 2 | #define AOS_EVENTS_LOGGING_CONFIG_REMAPPER_H_ |
| 3 | |
| 4 | #include <map> |
| 5 | #include <string_view> |
| 6 | #include <tuple> |
| 7 | #include <vector> |
| 8 | |
| 9 | #include "flatbuffers/flatbuffers.h" |
| 10 | |
| 11 | #include "aos/events/event_loop.h" |
| 12 | #include "aos/events/logging/logger_generated.h" |
| 13 | #include "aos/events/logging/replay_channels.h" |
| 14 | |
| 15 | namespace aos { |
| 16 | |
| 17 | // This class is used for remapping and renaming channels with the passed in |
| 18 | // configuration to the constructor. Typically, the templated versions of |
| 19 | // RemapOriginalChannel and RenameOriginalChannel are the main functions to use |
| 20 | // for type safety. After remapping and renaming, the remapped configuration can |
| 21 | // be accessed through remapped_configuration(), and the original configuration |
| 22 | // that is not mutated can be accessed through original_configuration. |
| 23 | // |
| 24 | // This class assumes no ownership over any pointers provided to it. |
| 25 | // |
| 26 | // Timestamp channels are automatically remapped on construction |
| 27 | // |
| 28 | // Note: This class does not need logfiles to function unlike LogReader. This |
| 29 | // logic originally lived in LogReader and was refactored out into this class. |
| 30 | // The same API for remapping and renaming still exists in LogReader which now |
| 31 | // just passes along the args to this class. |
| 32 | class ConfigRemapper { |
| 33 | public: |
| 34 | ConfigRemapper(const Configuration *config, |
| 35 | const Configuration *replay_config = nullptr, |
| 36 | const logger::ReplayChannels *replay_channels = nullptr); |
| 37 | ~ConfigRemapper(); |
| 38 | |
| 39 | // Map of channel indices to new name. The channel index will be an index into |
| 40 | // original_configuration(), and the string key will be the name of the |
| 41 | // channel to send on instead of the orignal channel name. |
| 42 | struct RemappedChannel { |
| 43 | std::string remapped_name; |
| 44 | std::string new_type; |
| 45 | }; |
| 46 | |
| 47 | // Enum to use for indicating how RemapOriginalChannel behaves when there is |
| 48 | // already a channel with the remapped name (e.g., as may happen when |
| 49 | // replaying a logfile that was itself generated from replay). |
| 50 | enum class RemapConflict { |
| 51 | // LOG(FATAL) on conflicts in remappings. |
| 52 | kDisallow, |
| 53 | // If we run into a conflict, attempt to remap the channel we would be |
| 54 | // overriding (and continue to do so if remapping *that* channel also |
| 55 | // generates a conflict). |
| 56 | // This will mean that if we repeatedly replay a log, we will end up |
| 57 | // stacking more and more /original's on the start of the oldest version |
| 58 | // of the channels. |
| 59 | kCascade |
| 60 | }; |
| 61 | |
| 62 | // Remaps a channel from the original configuration passed to the constructor |
| 63 | // to the given one. This operates on raw channel names, without any node or |
| 64 | // application specific mappings. |
| 65 | void RemapOriginalChannel( |
| 66 | std::string_view name, std::string_view type, |
| 67 | std::string_view add_prefix = "/original", std::string_view new_type = "", |
| 68 | RemapConflict conflict_handling = RemapConflict::kCascade); |
| 69 | template <typename T> |
| 70 | void RemapOriginalChannel( |
| 71 | std::string_view name, std::string_view add_prefix = "/original", |
| 72 | std::string_view new_type = "", |
| 73 | RemapConflict conflict_handling = RemapConflict::kCascade) { |
| 74 | RemapOriginalChannel(name, T::GetFullyQualifiedName(), add_prefix, new_type, |
| 75 | conflict_handling); |
| 76 | } |
| 77 | |
| 78 | // Remaps the provided channel, though this respects node mappings, and |
| 79 | // preserves them too. This makes it so if /aos -> /pi1/aos on one node, |
| 80 | // /original/aos -> /original/pi1/aos on the same node after renaming, just |
| 81 | // like you would hope. If new_type is not empty, the new channel will use |
| 82 | // the provided type instead. This allows for renaming messages. |
| 83 | // |
| 84 | // TODO(austin): If you have 2 nodes remapping something to the same channel, |
| 85 | // this doesn't handle that. No use cases exist yet for that, so it isn't |
| 86 | // being done yet. |
| 87 | void RemapOriginalChannel( |
| 88 | std::string_view name, std::string_view type, const Node *node, |
| 89 | std::string_view add_prefix = "/original", std::string_view new_type = "", |
| 90 | RemapConflict conflict_handling = RemapConflict::kCascade); |
| 91 | |
| 92 | template <typename T> |
| 93 | void RemapOriginalChannel( |
| 94 | std::string_view name, const Node *node, |
| 95 | std::string_view add_prefix = "/original", std::string_view new_type = "", |
| 96 | RemapConflict conflict_handling = RemapConflict::kCascade) { |
| 97 | RemapOriginalChannel(name, T::GetFullyQualifiedName(), node, add_prefix, |
| 98 | new_type, conflict_handling); |
| 99 | } |
| 100 | |
| 101 | // Similar to RemapOriginalChannel(), but lets you specify a name for the new |
| 102 | // channel without constraints. By default, this will not add any maps for the |
| 103 | // new channel. Use add_maps to specify any maps you'd like added. |
| 104 | void RenameOriginalChannel(std::string_view name, std::string_view type, |
| 105 | std::string_view new_name, |
| 106 | const std::vector<MapT> &add_maps = {}); |
| 107 | template <typename T> |
| 108 | void RenameOriginalChannel(std::string_view name, std::string_view new_name, |
| 109 | const std::vector<MapT> &add_maps = {}) { |
| 110 | RenameOriginalChannel(name, T::GetFullyQualifiedName(), new_name, add_maps); |
| 111 | } |
| 112 | // The following overloads are more suitable for multi-node configurations, |
| 113 | // and let you rename a channel on a specific node. |
| 114 | void RenameOriginalChannel(std::string_view name, std::string_view type, |
| 115 | const Node *node, std::string_view new_name, |
| 116 | const std::vector<MapT> &add_maps = {}); |
| 117 | template <typename T> |
| 118 | void RenameOriginalChannel(std::string_view name, const Node *node, |
| 119 | std::string_view new_name, |
| 120 | const std::vector<MapT> &add_maps = {}) { |
| 121 | RenameOriginalChannel(name, T::GetFullyQualifiedName(), node, new_name, |
| 122 | add_maps); |
| 123 | } |
| 124 | |
| 125 | template <typename T> |
| 126 | bool HasChannel(std::string_view name, const Node *node = nullptr) { |
| 127 | return HasChannel(name, T::GetFullyQualifiedName(), node); |
| 128 | } |
| 129 | bool HasChannel(std::string_view name, std::string_view type, |
| 130 | const Node *node) { |
| 131 | return configuration::GetChannel(original_configuration(), name, type, "", |
| 132 | node, true) != nullptr; |
| 133 | } |
| 134 | |
| 135 | // Returns true if the channel exists on the node and was in the original |
| 136 | // config |
| 137 | template <typename T> |
| 138 | bool HasOriginalChannel(std::string_view name, const Node *node = nullptr) { |
| 139 | const Channel *channel = |
| 140 | configuration::GetChannel(original_configuration(), name, |
| 141 | T::GetFullyQualifiedName(), "", node, true); |
| 142 | if (channel == nullptr) return false; |
| 143 | return channel->logger() != LoggerConfig::NOT_LOGGED; |
| 144 | } |
| 145 | |
| 146 | template <typename T> |
| 147 | void MaybeRemapOriginalChannel(std::string_view name, |
| 148 | const Node *node = nullptr) { |
| 149 | if (HasChannel<T>(name, node)) { |
| 150 | RemapOriginalChannel<T>(name, node); |
| 151 | } |
| 152 | } |
| 153 | template <typename T> |
| 154 | void MaybeRenameOriginalChannel(std::string_view name, const Node *node, |
| 155 | std::string_view new_name, |
| 156 | const std::vector<MapT> &add_maps = {}) { |
| 157 | if (HasChannel<T>(name, node)) { |
| 158 | RenameOriginalChannel<T>(name, node, new_name, add_maps); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | const Channel *RemapChannel(const EventLoop *event_loop, const Node *node, |
| 163 | const Channel *channel); |
| 164 | // Returns a list of all the original channels from remapping. |
| 165 | std::vector<const Channel *> RemappedChannels() const; |
| 166 | |
| 167 | void set_configuration(const Configuration *configuration); |
| 168 | |
| 169 | // Returns the configuration that was originally passed to the constructor. |
| 170 | // This class does not own this pointer. |
| 171 | const Configuration *original_configuration() const; |
| 172 | |
| 173 | // Returns the configuration that contains the remapping and renamings done on |
| 174 | // the original configuration. The pointer is invalidated whenever |
| 175 | // RemapOriginalChannel is called. |
| 176 | const Configuration *remapped_configuration() const; |
| 177 | |
| 178 | private: |
| 179 | // Handle constructing a configuration with all the additional remapped |
| 180 | // channels from calls to RemapOriginalChannel. |
| 181 | void MakeRemappedConfig(); |
| 182 | |
| 183 | std::map<size_t, RemappedChannel> remapped_channels_; |
| 184 | std::vector<MapT> maps_; |
| 185 | std::unique_ptr<FlatbufferDetachedBuffer<Configuration>> |
| 186 | remapped_configuration_buffer_; |
| 187 | |
| 188 | const Configuration *remapped_configuration_ = nullptr; |
| 189 | const Configuration *original_configuration_ = nullptr; |
| 190 | const Configuration *replay_configuration_ = nullptr; |
| 191 | |
| 192 | const logger::ReplayChannels *replay_channels_ = nullptr; |
| 193 | }; // class ConfigRemapper |
| 194 | |
| 195 | } // namespace aos |
| 196 | #endif // AOS_EVENTS_LOGGING_CONFIG_REMAPPER_H_ |