Add reverse name lookup for channels
This makes it so that if you know the fully-resolved channel name you
can figure out all the possible ways that it can be referred to. This is
potentially useful for a variety of things, including:
* Config validation (e.g. identifying all the channels that exist
on the /aos/remote_timestamp/* namespace, to determine if there are
any extraneous channels).
* Improving auto-complete for command-line utilities.
* Adding more options for how channel names are presented in
log-analysis tools (e.g., currently foxglove doesn't support the
concept of channel maps/aliases/whatever, so making node-agnostic
plots is irritating).
Change-Id: I334881199ad6b300a97f96d4a427ae25e55fa15f
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/aos/configuration.cc b/aos/configuration.cc
index ed99e2a..d05cba8 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -458,6 +458,58 @@
}
}
+void HandleReverseMaps(
+ const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
+ std::string_view type, const Node *node, std::set<std::string> *names) {
+ for (const Map *map : *maps) {
+ CHECK_NOTNULL(map);
+ const Channel *const match = CHECK_NOTNULL(map->match());
+ const Channel *const rename = CHECK_NOTNULL(map->rename());
+
+ // Handle type specific maps.
+ const flatbuffers::String *const match_type_string = match->type();
+ if (match_type_string != nullptr &&
+ match_type_string->string_view() != type) {
+ continue;
+ }
+
+ // Now handle node specific maps.
+ const flatbuffers::String *const match_source_node_string =
+ match->source_node();
+ if (node != nullptr && match_source_node_string != nullptr &&
+ match_source_node_string->string_view() !=
+ node->name()->string_view()) {
+ continue;
+ }
+
+ const flatbuffers::String *const match_name_string = match->name();
+ const flatbuffers::String *const rename_name_string = rename->name();
+ if (match_name_string == nullptr || rename_name_string == nullptr) {
+ continue;
+ }
+
+ const std::string rename_name = rename_name_string->str();
+ const std::string_view match_name = match_name_string->string_view();
+
+ std::set<std::string> possible_renames;
+
+ // Check if the current name(s) could have been reached using the provided
+ // rename.
+ if (match_name.back() == '*') {
+ for (const std::string &option : *names) {
+ if (option.substr(0, rename_name.size()) == rename_name) {
+ possible_renames.insert(
+ absl::StrCat(match_name.substr(0, match_name.size() - 1),
+ option.substr(rename_name.size())));
+ }
+ }
+ names->insert(possible_renames.begin(), possible_renames.end());
+ } else if (names->count(rename_name) != 0) {
+ names->insert(std::string(match_name));
+ }
+ }
+}
+
} // namespace
// Maps name for the provided maps. Modifies name.
@@ -466,6 +518,9 @@
// pointers. These combine to make it a performance hotspot during many tests
// under msan, so there is some optimizing around caching intermediates instead
// of dereferencing the pointer multiple times.
+//
+// Deliberately not in an anonymous namespace so that the log-reading code can
+// reference it.
void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
std::string *name, std::string_view type, const Node *node) {
// For the same reason we merge configs in reverse order, we want to process
@@ -527,6 +582,25 @@
}
}
+std::set<std::string> GetChannelAliases(const Configuration *config,
+ std::string_view name,
+ std::string_view type,
+ const std::string_view application_name,
+ const Node *node) {
+ std::set<std::string> names{std::string(name)};
+ if (config->has_maps()) {
+ HandleReverseMaps(config->maps(), type, node, &names);
+ }
+ {
+ const Application *application =
+ GetApplication(config, node, application_name);
+ if (application != nullptr && application->has_maps()) {
+ HandleReverseMaps(application->maps(), type, node, &names);
+ }
+ }
+ return names;
+}
+
FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
const Flatbuffer<Configuration> &config) {
// auto_merge_config will contain all the fields of the Configuration that are
diff --git a/aos/configuration.h b/aos/configuration.h
index cb52d09..31603fe 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -91,6 +91,23 @@
channel->type()->string_view(), application_name, node);
}
+// Returns a list of all the channel names that can be used to refer to the
+// specified channel on the given node/application. this allows a reverse-lookup
+// of any renames that happen.
+// Does not perform forwards-lookup first.
+std::set<std::string> GetChannelAliases(const Configuration *config,
+ std::string_view name,
+ std::string_view type,
+ const std::string_view application_name,
+ const Node *node);
+inline std::set<std::string> GetChannelAliases(
+ const Configuration *config, const Channel *channel,
+ const std::string_view application_name, const Node *node) {
+ return GetChannelAliases(config, channel->name()->string_view(),
+ channel->type()->string_view(), application_name,
+ node);
+}
+
// Returns the channel index (or dies) of channel in the provided config.
size_t ChannelIndex(const Configuration *config, const Channel *channel);
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index b493058..242ee17 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -190,6 +190,31 @@
aos::testing::FlatbufferEq(ExpectedLocation()));
}
+// Tests that we can do reverse-lookups of channel names.
+TEST_F(ConfigurationTest, GetChannelAliases) {
+ FlatbufferDetachedBuffer<Configuration> config =
+ ReadConfig(ArtifactPath("aos/testdata/config1.json"));
+
+ // Test a basic lookup first.
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app1", nullptr),
+ ::testing::UnorderedElementsAre("/foo", "/batman", "/bar"));
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/bar", ".aos.bar", "app1", nullptr),
+ ::testing::UnorderedElementsAre("/batman", "/bar"));
+ EXPECT_THAT(GetChannelAliases(&config.message(), "/batman", ".aos.bar",
+ "app1", nullptr),
+ ::testing::UnorderedElementsAre("/batman"));
+ // /bar (deliberately) does not get included because of the ordering in the
+ // map.
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo", ".aos.bar", "", nullptr),
+ ::testing::UnorderedElementsAre("/foo", "/batman"));
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app2", nullptr),
+ ::testing::UnorderedElementsAre("/foo", "/batman", "/baz"));
+}
+
// Tests that we can lookup a location with node specific maps.
TEST_F(ConfigurationTest, GetChannelMultinode) {
FlatbufferDetachedBuffer<Configuration> config =
@@ -221,6 +246,34 @@
EXPECT_EQ(GetChannel(config, "/batman3", ".aos.bar", "app1", pi2), nullptr);
}
+// Tests that reverse channel lookup on a multi-node config (including with
+// wildcards) works.
+TEST_F(ConfigurationTest, GetChannelAliasesMultinode) {
+ FlatbufferDetachedBuffer<Configuration> config =
+ ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
+
+ const Node *pi1 = GetNode(&config.message(), "pi1");
+ const Node *pi2 = GetNode(&config.message(), "pi2");
+
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app1", pi1),
+ ::testing::UnorderedElementsAre("/foo", "/batman", "/batman2", "/batman3",
+ "/magic/string"));
+
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo", ".aos.baz", "app1", pi1),
+ ::testing::UnorderedElementsAre("/foo", "/batman3", "/magic/string"));
+
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo/testing", ".aos.bar", "", pi1),
+ ::testing::UnorderedElementsAre("/foo/testing", "/magic/string/testing"));
+
+ EXPECT_THAT(
+ GetChannelAliases(&config.message(), "/foo/testing", ".aos.bar", "app1",
+ pi2),
+ ::testing::UnorderedElementsAre("/foo/testing", "/magic/string/testing"));
+}
+
// Tests that we can lookup a location with type specific maps.
TEST_F(ConfigurationTest, GetChannelTypedMultinode) {
FlatbufferDetachedBuffer<Configuration> config =