Allow wildcard to select all events in a group

The config can now have fields with wildcards e.g sched/*
Any events that have dedicated protos will use those and
the rest will become generic events.

This CL also implements indexing the events by group
and name of an event, rather than just the name.
This helps to avoid duplicating events when they are
specified in the config in different ways (sched/*,
sched_switch, sched/sched_switch)

Bug:119662403
Change-Id: I1c2eed4ae8e27a7bf3b99413b12f2bc245aac7d0
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 210dad2..602bcc2 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -81,18 +81,6 @@
   return true;
 }
 
-const std::vector<bool> BuildEnabledVector(const ProtoTranslationTable& table,
-                                           const std::set<std::string>& names) {
-  std::vector<bool> enabled(table.largest_id() + 1);
-  for (const std::string& name : names) {
-    const Event* event = table.GetEventByName(name);
-    if (!event)
-      continue;
-    enabled[event->ftrace_event_id] = true;
-  }
-  return enabled;
-}
-
 void SetBlocking(int fd, bool is_blocking) {
   int flags = fcntl(fd, F_GETFL, 0);
   flags = (is_blocking) ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
@@ -128,12 +116,6 @@
 
 using protos::pbzero::GenericFtraceEvent;
 
-EventFilter::EventFilter(const ProtoTranslationTable& table,
-                         std::set<std::string> names)
-    : enabled_ids_(BuildEnabledVector(table, names)),
-      enabled_names_(std::move(names)) {}
-EventFilter::~EventFilter() = default;
-
 CpuReader::CpuReader(const ProtoTranslationTable* table,
                      size_t cpu,
                      base::ScopedFile fd,
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index 7cedd50..e1ff39b 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -49,7 +49,6 @@
 }  // namespace pbzero
 }  // namespace protos
 
-class EventFilter;  // Declared down below.
 
 // Reads raw ftrace data for a cpu and writes that into the perfetto userspace
 // buffer.
@@ -201,30 +200,6 @@
   PERFETTO_THREAD_CHECKER(thread_checker_)
 };
 
-// Class for efficient 'is event with id x enabled?' tests.
-// Mirrors the data in a FtraceConfig but in a format better suited
-// to be consumed by CpuReader.
-class EventFilter {
- public:
-  EventFilter(const ProtoTranslationTable&, std::set<std::string>);
-  ~EventFilter();
-
-  bool IsEventEnabled(size_t ftrace_event_id) const {
-    if (ftrace_event_id == 0 || ftrace_event_id > enabled_ids_.size()) {
-      return false;
-    }
-    return enabled_ids_[ftrace_event_id];
-  }
-
-  const std::set<std::string>& enabled_names() const { return enabled_names_; }
-
- private:
-  EventFilter(const EventFilter&) = delete;
-  EventFilter& operator=(const EventFilter&) = delete;
-
-  const std::vector<bool> enabled_ids_;
-  std::set<std::string> enabled_names_;
-};
 
 }  // namespace perfetto
 
diff --git a/src/traced/probes/ftrace/cpu_reader_benchmark.cc b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
index 4f99c1e..363f66d 100644
--- a/src/traced/probes/ftrace/cpu_reader_benchmark.cc
+++ b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
@@ -15,6 +15,7 @@
 #include "benchmark/benchmark.h"
 
 #include "src/traced/probes/ftrace/cpu_reader.h"
+#include "src/traced/probes/ftrace/proto_translation_table.h"
 
 #include "perfetto/base/utils.h"
 #include "perfetto/protozero/scattered_stream_null_delegate.h"
@@ -299,6 +300,7 @@
 using perfetto::protos::pbzero::FtraceEventBundle;
 using perfetto::CpuReader;
 using perfetto::FtraceMetadata;
+using perfetto::GroupAndName;
 
 static void BM_ParsePageFullOfSchedSwitch(benchmark::State& state) {
   const ExamplePage* test_case = &g_full_page_sched_switch;
@@ -310,7 +312,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, std::set<std::string>({"sched_switch"}));
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
   FtraceMetadata metadata{};
   while (state.KeepRunning()) {
diff --git a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
index 173d1de..0f79442 100644
--- a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
+++ b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
@@ -52,7 +52,11 @@
   memset(g_page, 0, base::kPageSize);
   memcpy(g_page, data, std::min(base::kPageSize, size));
 
-  EventFilter filter(*table, {"sched_switch", "print"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("ftrace", "print")));
 
   writer.Reset(&stream);
   FtraceMetadata metadata{};
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 9d582fb..855ce02 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -218,36 +218,6 @@
   EXPECT_EQ(buffer.get()[8], 2);
 }
 
-TEST_F(CpuReaderTableTest, EventFilter) {
-  std::vector<Field> common_fields;
-  std::vector<Event> events;
-
-  {
-    Event event;
-    event.name = "foo";
-    event.group = "foo_group";
-    event.ftrace_event_id = 1;
-    events.push_back(event);
-  }
-
-  {
-    Event event;
-    event.name = "bar";
-    event.group = "bar_group";
-    event.ftrace_event_id = 10;
-    events.push_back(event);
-  }
-
-  ProtoTranslationTable table(
-      &ftrace_, events, std::move(common_fields),
-      ProtoTranslationTable::DefaultPageHeaderSpecForTesting());
-  EventFilter filter(table, {"foo"});
-
-  EXPECT_TRUE(filter.IsEventEnabled(1));
-  EXPECT_FALSE(filter.IsEventEnabled(2));
-  EXPECT_FALSE(filter.IsEventEnabled(10));
-}
-
 TEST(ReadAndAdvanceTest, Number) {
   uint64_t expected = 42;
   uint64_t actual = 0;
@@ -364,7 +334,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"print"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("ftrace", "print")));
 
   FtraceMetadata metadata{};
   size_t bytes = CpuReader::ParsePage(
@@ -477,7 +449,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"print"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("ftrace", "print")));
 
   FtraceMetadata metadata{};
   CpuReader::ParsePage(page.get(), &filter, bundle_provider.writer(), table,
@@ -512,7 +486,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"print"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("ftrace", "print")));
 
   FtraceMetadata metadata{};
   ASSERT_FALSE(CpuReader::ParsePage(
@@ -537,7 +513,7 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {});
+  EventFilter filter;
 
   FtraceMetadata metadata{};
   ASSERT_TRUE(CpuReader::ParsePage(page.get(), &filter,
@@ -590,7 +566,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"print"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("ftrace", "print")));
 
   FtraceMetadata metadata{};
   ASSERT_TRUE(CpuReader::ParsePage(page.get(), &filter,
@@ -684,7 +662,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"sched_switch"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
   FtraceMetadata metadata{};
   ASSERT_TRUE(CpuReader::ParsePage(page.get(), &filter,
@@ -1352,7 +1332,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"sched_switch"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
   FtraceMetadata metadata{};
   ASSERT_TRUE(CpuReader::ParsePage(page.get(), &filter,
@@ -1781,7 +1763,9 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  EventFilter filter(*table, {"sched_switch"});
+  EventFilter filter;
+  filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
   FtraceMetadata metadata{};
   ASSERT_TRUE(CpuReader::ParsePage(page.get(), &filter,
diff --git a/src/traced/probes/ftrace/ftrace_config.cc b/src/traced/probes/ftrace/ftrace_config.cc
index e780298..b31af26 100644
--- a/src/traced/probes/ftrace/ftrace_config.cc
+++ b/src/traced/probes/ftrace/ftrace_config.cc
@@ -22,7 +22,7 @@
 namespace {
 
 bool IsGoodFtracePunctuation(char c) {
-  return c == '_' || c == '/';
+  return c == '_' || c == '/' || c == '*';
 }
 
 bool IsGoodAtracePunctuation(char c) {
@@ -48,19 +48,14 @@
       if (slash_count > 1 || i == 0 || i == str.size() - 1)
         return false;
     }
+    if (str[i] == '*' && i != str.size() - 1)
+      return false;
   }
   return true;
 }
 
 }  // namespace
 
-std::set<std::string> FtraceEventsAsSet(const FtraceConfig& config) {
-  std::set<std::string> events;
-  for (const std::string& event : config.ftrace_events())
-    events.insert(event);
-  return events;
-}
-
 FtraceConfig CreateFtraceConfig(std::set<std::string> names) {
   FtraceConfig config;
   for (const std::string& name : names)
diff --git a/src/traced/probes/ftrace/ftrace_config.h b/src/traced/probes/ftrace/ftrace_config.h
index d8d55ea..f43ac1f 100644
--- a/src/traced/probes/ftrace/ftrace_config.h
+++ b/src/traced/probes/ftrace/ftrace_config.h
@@ -30,9 +30,6 @@
 // Utility method for the common case where we don't care about atrace events.
 FtraceConfig CreateFtraceConfig(std::set<std::string> names);
 
-// Get the ftrace events for a config as a set.
-std::set<std::string> FtraceEventsAsSet(const FtraceConfig&);
-
 // Returns true iff the config has any atrace categories or apps.
 bool RequiresAtrace(const FtraceConfig&);
 
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 7a2f20f..6a4c97f 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -25,7 +25,6 @@
 
 #include "perfetto/base/utils.h"
 #include "src/traced/probes/ftrace/atrace_wrapper.h"
-#include "src/traced/probes/ftrace/proto_translation_table.h"
 
 namespace perfetto {
 namespace {
@@ -36,26 +35,28 @@
 constexpr int kDefaultPerCpuBufferSizeKb = 512;    // 512kb
 constexpr int kMaxPerCpuBufferSizeKb = 64 * 1024;  // 64mb
 
-std::vector<std::string> difference(const std::set<std::string>& a,
-                                    const std::set<std::string>& b) {
-  std::vector<std::string> result;
-  result.reserve(std::max(b.size(), a.size()));
-  std::set_difference(a.begin(), a.end(), b.begin(), b.end(),
-                      std::inserter(result, result.begin()));
-  return result;
-}
-
 void AddEventGroup(const ProtoTranslationTable* table,
                    const std::string& group,
-                   std::set<std::string>* to) {
+                   std::set<GroupAndName>* to) {
   const std::vector<const Event*>* events = table->GetEventsByGroup(group);
   if (!events)
     return;
   for (const Event* event : *events)
-    to->insert(event->name);
+    to->insert(GroupAndName(group, event->name));
 }
 
-std::pair<std::string, std::string> EventToGroupAndName(
+std::set<GroupAndName> ReadEventsInGroupFromFs(
+    const FtraceProcfs& ftrace_procfs,
+    const std::string& group) {
+  std::set<std::string> names =
+      ftrace_procfs.GetEventNamesForGroup("events/" + group);
+  std::set<GroupAndName> events;
+  for (const auto& name : names)
+    events.insert(GroupAndName(group, name));
+  return events;
+}
+
+std::pair<std::string, std::string> EventToStringGroupAndName(
     const std::string& event) {
   auto slash_pos = event.find("/");
   if (slash_pos == std::string::npos)
@@ -66,12 +67,34 @@
 
 }  // namespace
 
-std::set<std::string> GetFtraceEvents(const FtraceConfig& request,
-                                      const ProtoTranslationTable* table) {
-  std::set<std::string> events;
-  events.insert(request.ftrace_events().begin(), request.ftrace_events().end());
+std::set<GroupAndName> FtraceConfigMuxer::GetFtraceEvents(
+    const FtraceConfig& request,
+    const ProtoTranslationTable* table) {
+  std::set<GroupAndName> events;
+  for (const auto& config_value : request.ftrace_events()) {
+    std::string group;
+    std::string name;
+    std::tie(group, name) = EventToStringGroupAndName(config_value);
+    if (name == "*") {
+      events = ReadEventsInGroupFromFs(*ftrace_, group);
+    } else if (group.empty()) {
+      // If there is no group specified, find an event with that name and
+      // use it's group.
+      const Event* e = table->GetEventByName(name);
+      if (!e) {
+        PERFETTO_DLOG(
+            "Event doesn't exist: %s. Include the group in the config to allow "
+            "the event to be output as a generic event.",
+            name.c_str());
+        continue;
+      }
+      events.insert(GroupAndName(e->group, e->name));
+    } else {
+      events.insert(GroupAndName(group, name));
+    }
+  }
   if (RequiresAtrace(request)) {
-    events.insert("print");
+    events.insert(GroupAndName("ftrace", "print"));
 
     // Ideally we should keep this code in sync with:
     // platform/frameworks/native/cmds/atrace/atrace.cpp
@@ -86,11 +109,11 @@
       }
 
       if (category == "sched") {
-        events.insert("sched_switch");
-        events.insert("sched_wakeup");
-        events.insert("sched_waking");
-        events.insert("sched_blocked_reason");
-        events.insert("sched_cpu_hotplug");
+        events.insert(GroupAndName(category, "sched_switch"));
+        events.insert(GroupAndName(category, "sched_wakeup"));
+        events.insert(GroupAndName(category, "sched_waking"));
+        events.insert(GroupAndName(category, "sched_blocked_reason"));
+        events.insert(GroupAndName(category, "sched_cpu_hotplug"));
         AddEventGroup(table, "cgroup", &events);
         continue;
       }
@@ -102,14 +125,14 @@
       }
 
       if (category == "irqoff") {
-        events.insert("irq_enable");
-        events.insert("irq_disable");
+        events.insert(GroupAndName(category, "irq_enable"));
+        events.insert(GroupAndName(category, "irq_disable"));
         continue;
       }
 
       if (category == "preemptoff") {
-        events.insert("preempt_enable");
-        events.insert("preempt_disable");
+        events.insert(GroupAndName(category, "preempt_enable"));
+        events.insert(GroupAndName(category, "preempt_disable"));
         continue;
       }
 
@@ -119,14 +142,14 @@
       }
 
       if (category == "freq") {
-        events.insert("cpu_frequency");
-        events.insert("clock_set_rate");
-        events.insert("clock_disable");
-        events.insert("clock_enable");
-        events.insert("clk_set_rate");
-        events.insert("clk_disable");
-        events.insert("clk_enable");
-        events.insert("cpu_frequency_limits");
+        events.insert(GroupAndName("power", "cpu_frequency"));
+        events.insert(GroupAndName("power", "clock_set_rate"));
+        events.insert(GroupAndName("power", "clock_disable"));
+        events.insert(GroupAndName("power", "clock_enable"));
+        events.insert(GroupAndName("clk", "clk_set_rate"));
+        events.insert(GroupAndName("clk", "clk_disable"));
+        events.insert(GroupAndName("clk", "clk_enable"));
+        events.insert(GroupAndName("power", "cpu_frequency_limits"));
         continue;
       }
 
@@ -136,21 +159,21 @@
       }
 
       if (category == "idle") {
-        events.insert("cpu_idle");
+        events.insert(GroupAndName("power", "cpu_idle"));
         continue;
       }
 
       if (category == "disk") {
-        events.insert("f2fs_sync_file_enter");
-        events.insert("f2fs_sync_file_exit");
-        events.insert("f2fs_write_begin");
-        events.insert("f2fs_write_end");
-        events.insert("ext4_da_write_begin");
-        events.insert("ext4_da_write_end");
-        events.insert("ext4_sync_file_enter");
-        events.insert("ext4_sync_file_exit");
-        events.insert("block_rq_issue");
-        events.insert("block_rq_complete");
+        events.insert(GroupAndName("f2fs", "f2fs_sync_file_enter"));
+        events.insert(GroupAndName("f2fs", "f2fs_sync_file_exit"));
+        events.insert(GroupAndName("f2fs", "f2fs_write_begin"));
+        events.insert(GroupAndName("f2fs", "f2fs_write_end"));
+        events.insert(GroupAndName("ext4", "ext4_da_write_begin"));
+        events.insert(GroupAndName("ext4", "ext4_da_write_end"));
+        events.insert(GroupAndName("ext4", "ext4_sync_file_enter"));
+        events.insert(GroupAndName("ext4", "ext4_sync_file_exit"));
+        events.insert(GroupAndName("block", "block_rq_issue"));
+        events.insert(GroupAndName("block", "block_rq_complete"));
         continue;
       }
 
@@ -175,10 +198,10 @@
       }
 
       if (category == "memreclaim") {
-        events.insert("mm_vmscan_direct_reclaim_begin");
-        events.insert("mm_vmscan_direct_reclaim_end");
-        events.insert("mm_vmscan_kswapd_wake");
-        events.insert("mm_vmscan_kswapd_sleep");
+        events.insert(GroupAndName("vmscan", "mm_vmscan_direct_reclaim_begin"));
+        events.insert(GroupAndName("vmscan", "mm_vmscan_direct_reclaim_end"));
+        events.insert(GroupAndName("vmscan", "mm_vmscan_kswapd_wake"));
+        events.insert(GroupAndName("vmscan", "mm_vmscan_kswapd_sleep"));
         AddEventGroup(table, "lowmemorykiller", &events);
         continue;
       }
@@ -189,16 +212,16 @@
       }
 
       if (category == "binder_driver") {
-        events.insert("binder_transaction");
-        events.insert("binder_transaction_received");
-        events.insert("binder_set_priority");
+        events.insert(GroupAndName("binder", "binder_transaction"));
+        events.insert(GroupAndName("binder", "binder_transaction_received"));
+        events.insert(GroupAndName("binder", "binder_set_priority"));
         continue;
       }
 
       if (category == "binder_lock") {
-        events.insert("binder_lock");
-        events.insert("binder_locked");
-        events.insert("binder_unlock");
+        events.insert(GroupAndName("binder", "binder_lock"));
+        events.insert(GroupAndName("binder", "binder_locked"));
+        events.insert(GroupAndName("binder", "binder_unlock"));
         continue;
       }
 
@@ -234,10 +257,15 @@
 
 FtraceConfigMuxer::FtraceConfigMuxer(FtraceProcfs* ftrace,
                                      ProtoTranslationTable* table)
-    : ftrace_(ftrace), table_(table), current_state_(), configs_() {}
+    : ftrace_(ftrace),
+      table_(table),
+      current_state_(),
+      filters_(),
+      configs_() {}
 FtraceConfigMuxer::~FtraceConfigMuxer() = default;
 
 FtraceConfigId FtraceConfigMuxer::SetupConfig(const FtraceConfig& request) {
+  EventFilter filter;
   FtraceConfig actual;
   bool is_ftrace_enabled = ftrace_->IsTracingEnabled();
   if (configs_.empty()) {
@@ -258,36 +286,36 @@
       return 0;
   }
 
-  std::set<std::string> events = GetFtraceEvents(request, table_);
+  std::set<GroupAndName> events = GetFtraceEvents(request, table_);
 
   if (RequiresAtrace(request))
     UpdateAtrace(request);
 
-  for (auto& name : events) {
-    std::string group;
-    std::string event_name;
-    std::tie(group, event_name) = EventToGroupAndName(name);
-    // TODO(taylori): Add all events for group if name is * e.g sched/*
-    const Event* event = table_->GetOrCreateEvent(group, event_name);
+  for (const auto& group_and_name : events) {
+    const Event* event = table_->GetOrCreateEvent(group_and_name);
     if (!event) {
-      PERFETTO_DLOG("Can't enable %s, event not known", name.c_str());
+      PERFETTO_DLOG("Can't enable %s, event not known",
+                    group_and_name.ToString().c_str());
       continue;
     }
-    if (current_state_.ftrace_events.count(event->name) ||
+    if (current_state_.ftrace_events.IsEventEnabled(event->ftrace_event_id) ||
         std::string("ftrace") == event->group) {
-      *actual.add_ftrace_events() = name;
+      filter.AddEnabledEvent(event->ftrace_event_id);
+      *actual.add_ftrace_events() = group_and_name.ToString();
       continue;
     }
     if (ftrace_->EnableEvent(event->group, event->name)) {
-      current_state_.ftrace_events.insert(event->name);
-      *actual.add_ftrace_events() = event->name;
+      current_state_.ftrace_events.AddEnabledEvent(event->ftrace_event_id);
+      filter.AddEnabledEvent(event->ftrace_event_id);
+      *actual.add_ftrace_events() = group_and_name.ToString();
     } else {
-      PERFETTO_DPLOG("Failed to enable %s.", name.c_str());
+      PERFETTO_DPLOG("Failed to enable %s.", group_and_name.ToString().c_str());
     }
   }
 
   FtraceConfigId id = ++last_id_;
   configs_.emplace(id, std::move(actual));
+  filters_.emplace(id, std::move(filter));
   return id;
 }
 
@@ -314,30 +342,29 @@
   return true;
 }
 
-bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId id) {
-  if (!id || !configs_.erase(id))
+bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId config_id) {
+  if (!config_id || !filters_.erase(config_id) || !configs_.erase(config_id))
     return false;
-
-  std::set<std::string> expected_ftrace_events;
-  for (const auto& id_config : configs_) {
-    const FtraceConfig& config = id_config.second;
-    expected_ftrace_events.insert(config.ftrace_events().begin(),
-                                  config.ftrace_events().end());
+  EventFilter expected_ftrace_events;
+  for (const auto& id_filter : filters_) {
+    expected_ftrace_events.EnableEventsFrom(id_filter.second);
   }
 
-  std::vector<std::string> events_to_disable =
-      difference(current_state_.ftrace_events, expected_ftrace_events);
-
-  for (auto& name : events_to_disable) {
-    const Event* event = table_->GetEventByName(name);
-    if (!event)
+  // Disable any events that are currently enabled, but are not in any configs
+  // anymore.
+  std::set<size_t> event_ids = current_state_.ftrace_events.GetEnabledEvents();
+  for (size_t id : event_ids) {
+    if (expected_ftrace_events.IsEventEnabled(id))
       continue;
+    const Event* event = table_->GetEventById(id);
+    // Any event that was enabled must exist.
+    PERFETTO_DCHECK(event);
     if (ftrace_->DisableEvent(event->group, event->name))
-      current_state_.ftrace_events.erase(name);
+      current_state_.ftrace_events.DisableEvent(event->ftrace_event_id);
   }
 
   // If there aren't any more active configs, disable ftrace.
-  auto active_it = active_configs_.find(id);
+  auto active_it = active_configs_.find(config_id);
   if (active_it != active_configs_.end()) {
     PERFETTO_DCHECK(current_state_.tracing_on);
     active_configs_.erase(active_it);
@@ -362,12 +389,18 @@
   return true;
 }
 
-const FtraceConfig* FtraceConfigMuxer::GetConfig(FtraceConfigId id) {
+const FtraceConfig* FtraceConfigMuxer::GetConfigForTesting(FtraceConfigId id) {
   if (!configs_.count(id))
     return nullptr;
   return &configs_.at(id);
 }
 
+const EventFilter* FtraceConfigMuxer::GetEventFilter(FtraceConfigId id) {
+  if (!filters_.count(id))
+    return nullptr;
+  return &filters_.at(id);
+}
+
 void FtraceConfigMuxer::SetupClock(const FtraceConfig&) {
   std::string current_clock = ftrace_->GetClock();
   std::set<std::string> clocks = ftrace_->AvailableClocks();
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index 7b25540..7c70720 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -23,6 +23,7 @@
 #include "src/traced/probes/ftrace/ftrace_config.h"
 #include "src/traced/probes/ftrace/ftrace_controller.h"
 #include "src/traced/probes/ftrace/ftrace_procfs.h"
+#include "src/traced/probes/ftrace/proto_translation_table.h"
 
 namespace perfetto {
 
@@ -65,16 +66,24 @@
   // or already removed.
   bool RemoveConfig(FtraceConfigId);
 
+  const EventFilter* GetEventFilter(FtraceConfigId id);
+
   // public for testing
   void SetupClockForTesting(const FtraceConfig& request) {
     SetupClock(request);
   }
 
-  const FtraceConfig* GetConfig(FtraceConfigId id);
+  const FtraceConfig* GetConfigForTesting(FtraceConfigId id);
+
+  std::set<GroupAndName> GetFtraceEventsForTesting(
+      const FtraceConfig& request,
+      const ProtoTranslationTable* table) {
+    return GetFtraceEvents(request, table);
+  }
 
  private:
   struct FtraceState {
-    std::set<std::string> ftrace_events;
+    EventFilter ftrace_events;
     std::set<std::string> atrace_categories;
     std::set<std::string> atrace_apps;
     bool tracing_on = false;
@@ -90,6 +99,13 @@
   void UpdateAtrace(const FtraceConfig& request);
   void DisableAtrace();
 
+  // This processes the config to get the exact events.
+  // group/* -> Will read the fs and add all events in group.
+  // event -> Will look up the event to find the group.
+  // atrace category -> Will add events in that category.
+  std::set<GroupAndName> GetFtraceEvents(const FtraceConfig& request,
+                                         const ProtoTranslationTable*);
+
   FtraceConfigId GetNextId();
 
   FtraceConfigId last_id_ = 1;
@@ -98,6 +114,10 @@
 
   FtraceState current_state_;
 
+  // There is a filter per config. These filters allow a quick way
+  // to check if a certain ftrace event with id x is enabled.
+  std::map<FtraceConfigId, EventFilter> filters_;
+
   // Set of all configurations. Note that not all of them might be active.
   // When a config is present but not active, we do setup buffer sizes and
   // events, but don't enable ftrace (i.e. tracing_on).
@@ -108,8 +128,6 @@
   std::set<FtraceConfigId> active_configs_;
 };
 
-std::set<std::string> GetFtraceEvents(const FtraceConfig& request,
-                                      const ProtoTranslationTable*);
 size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb);
 
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index 1a6ea60..399af68 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -54,6 +54,8 @@
   MOCK_METHOD1(ClearFile, bool(const std::string& path));
   MOCK_CONST_METHOD1(ReadFileIntoString, std::string(const std::string& path));
   MOCK_CONST_METHOD0(NumberOfCpus, size_t());
+  MOCK_CONST_METHOD1(GetEventNamesForGroup,
+                     const std::set<std::string>(const std::string& path));
 };
 
 struct MockRunAtrace {
@@ -80,8 +82,9 @@
                               events,
                               common_fields,
                               ftrace_page_header_spec) {}
-  MOCK_METHOD2(GetOrCreateEvent,
-               Event*(const std::string& group, const std::string& event));
+  MOCK_METHOD1(GetOrCreateEvent, Event*(const GroupAndName& group_and_name));
+  MOCK_CONST_METHOD1(GetEvent,
+                     const Event*(const GroupAndName& group_and_name));
 };
 
 class FtraceConfigMuxerTest : public ::testing::Test {
@@ -197,19 +200,113 @@
   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
   EXPECT_CALL(ftrace,
               WriteToFile("/root/events/power/cpu_frequency/enable", "1"));
+  EXPECT_CALL(*mock_table, GetEvent(GroupAndName("power", "cpu_frequency")))
+      .Times(AnyNumber());
 
   Event event_to_return;
   event_to_return.name = "cpu_frequency";
   event_to_return.group = "power";
-  ON_CALL(*mock_table, GetOrCreateEvent("power", "cpu_frequency"))
+  ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("power", "cpu_frequency")))
       .WillByDefault(Return(&event_to_return));
-  EXPECT_CALL(*mock_table, GetOrCreateEvent("power", "cpu_frequency"));
+  EXPECT_CALL(*mock_table,
+              GetOrCreateEvent(GroupAndName("power", "cpu_frequency")));
 
   FtraceConfigId id = model.SetupConfig(config);
   ASSERT_TRUE(model.ActivateConfig(id));
-  const FtraceConfig* actual_config = model.GetConfig(id);
+  const FtraceConfig* actual_config = model.GetConfigForTesting(id);
   EXPECT_TRUE(actual_config);
-  EXPECT_THAT(actual_config->ftrace_events(), Contains("cpu_frequency"));
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("power/cpu_frequency"));
+}
+
+TEST_F(FtraceConfigMuxerTest, AddSameNameEvents) {
+  auto mock_table = GetMockTable();
+  NiceMock<MockFtraceProcfs> ftrace;
+
+  FtraceConfig config = CreateFtraceConfig({"group_one/foo", "group_two/foo"});
+
+  FtraceConfigMuxer model(&ftrace, mock_table.get());
+
+  Event event1;
+  event1.name = "foo";
+  event1.group = "group_one";
+  event1.ftrace_event_id = 1;
+  ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_one", "foo")))
+      .WillByDefault(Return(&event1));
+  EXPECT_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_one", "foo")));
+
+  Event event2;
+  event2.name = "foo";
+  event2.group = "group_two";
+  event2.ftrace_event_id = 2;
+  ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_two", "foo")))
+      .WillByDefault(Return(&event2));
+  EXPECT_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_two", "foo")));
+
+  FtraceConfigId id = model.SetupConfig(config);
+  ASSERT_TRUE(model.ActivateConfig(id));
+  const FtraceConfig* actual_config = model.GetConfigForTesting(id);
+  EXPECT_TRUE(actual_config);
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("group_one/foo"));
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("group_two/foo"));
+}
+
+TEST_F(FtraceConfigMuxerTest, AddAllEvents) {
+  auto mock_table = GetMockTable();
+  MockFtraceProcfs ftrace;
+
+  FtraceConfig config = CreateFtraceConfig({"sched/*"});
+
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return("[local] global boot"));
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .Times(AnyNumber());
+
+  EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
+      .Times(2)
+      .WillRepeatedly(Return('0'));
+  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "512"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
+  EXPECT_CALL(ftrace,
+              WriteToFile("/root/events/sched/sched_switch/enable", "1"));
+  EXPECT_CALL(ftrace,
+              WriteToFile("/root/events/sched/sched_new_event/enable", "1"));
+
+  FtraceConfigMuxer model(&ftrace, mock_table.get());
+  std::set<std::string> n = {"sched_switch", "sched_new_event"};
+  ON_CALL(ftrace, GetEventNamesForGroup("events/sched"))
+      .WillByDefault(Return(n));
+  EXPECT_CALL(ftrace, GetEventNamesForGroup("events/sched")).Times(1);
+
+  // Non-generic event.
+  std::map<std::string, const Event*> events;
+  Event sched_switch = {"sched_switch", "sched"};
+  sched_switch.ftrace_event_id = 1;
+  ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("sched", "sched_switch")))
+      .WillByDefault(Return(&sched_switch));
+  EXPECT_CALL(*mock_table,
+              GetOrCreateEvent(GroupAndName("sched", "sched_switch")))
+      .Times(AnyNumber());
+
+  // Generic event.
+  Event event_to_return;
+  event_to_return.name = "sched_new_event";
+  event_to_return.group = "sched";
+  event_to_return.ftrace_event_id = 2;
+  ON_CALL(*mock_table,
+          GetOrCreateEvent(GroupAndName("sched", "sched_new_event")))
+      .WillByDefault(Return(&event_to_return));
+  EXPECT_CALL(*mock_table,
+              GetOrCreateEvent(GroupAndName("sched", "sched_new_event")));
+
+  FtraceConfigId id = model.SetupConfig(config);
+  ASSERT_TRUE(id);
+  ASSERT_TRUE(model.ActivateConfig(id));
+
+  const FtraceConfig* actual_config = model.GetConfigForTesting(id);
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("sched/sched_switch"));
+  EXPECT_THAT(actual_config->ftrace_events(),
+              Contains("sched/sched_new_event"));
 }
 
 TEST_F(FtraceConfigMuxerTest, TurnFtraceOnOff) {
@@ -236,9 +333,9 @@
   ASSERT_TRUE(id);
   ASSERT_TRUE(model.ActivateConfig(id));
 
-  const FtraceConfig* actual_config = model.GetConfig(id);
+  const FtraceConfig* actual_config = model.GetConfigForTesting(id);
   EXPECT_TRUE(actual_config);
-  EXPECT_THAT(actual_config->ftrace_events(), Contains("sched_switch"));
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("sched/sched_switch"));
   EXPECT_THAT(actual_config->ftrace_events(), Not(Contains("foo")));
 
   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
@@ -254,7 +351,7 @@
 TEST_F(FtraceConfigMuxerTest, FtraceIsAlreadyOn) {
   MockFtraceProcfs ftrace;
 
-  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
+  FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
 
   FtraceConfigMuxer model(&ftrace, table_.get());
 
@@ -269,7 +366,7 @@
   NiceMock<MockFtraceProcfs> ftrace;
   MockRunAtrace atrace;
 
-  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
+  FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
   *config.add_atrace_categories() = "sched";
 
   FtraceConfigMuxer model(&ftrace, table_.get());
@@ -284,10 +381,10 @@
   FtraceConfigId id = model.SetupConfig(config);
   ASSERT_TRUE(id);
 
-  const FtraceConfig* actual_config = model.GetConfig(id);
+  const FtraceConfig* actual_config = model.GetConfigForTesting(id);
   EXPECT_TRUE(actual_config);
-  EXPECT_THAT(actual_config->ftrace_events(), Contains("sched_switch"));
-  EXPECT_THAT(actual_config->ftrace_events(), Contains("print"));
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("sched/sched_switch"));
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("ftrace/print"));
 
   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
                           {"atrace", "--async_stop", "--only_userspace"})))
@@ -317,9 +414,9 @@
   FtraceConfigId id = model.SetupConfig(config);
   ASSERT_TRUE(id);
 
-  const FtraceConfig* actual_config = model.GetConfig(id);
+  const FtraceConfig* actual_config = model.GetConfigForTesting(id);
   EXPECT_TRUE(actual_config);
-  EXPECT_THAT(actual_config->ftrace_events(), Contains("print"));
+  EXPECT_THAT(actual_config->ftrace_events(), Contains("ftrace/print"));
 
   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
                           {"atrace", "--async_stop", "--only_userspace"})))
@@ -356,35 +453,49 @@
 }
 
 TEST_F(FtraceConfigMuxerTest, GetFtraceEvents) {
-  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
-  std::set<std::string> events = GetFtraceEvents(config, table_.get());
+  MockFtraceProcfs ftrace;
+  FtraceConfigMuxer model(&ftrace, table_.get());
 
-  EXPECT_THAT(events, Contains("sched_switch"));
-  EXPECT_THAT(events, Not(Contains("print")));
+  FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
+  std::set<GroupAndName> events =
+      model.GetFtraceEventsForTesting(config, table_.get());
+
+  EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
+  EXPECT_THAT(events, Not(Contains(GroupAndName("ftrace", "print"))));
 }
 
 TEST_F(FtraceConfigMuxerTest, GetFtraceEventsAtrace) {
+  MockFtraceProcfs ftrace;
+  FtraceConfigMuxer model(&ftrace, table_.get());
+
   FtraceConfig config = CreateFtraceConfig({});
   *config.add_atrace_categories() = "sched";
-  std::set<std::string> events = GetFtraceEvents(config, table_.get());
+  std::set<GroupAndName> events =
+      model.GetFtraceEventsForTesting(config, table_.get());
 
-  EXPECT_THAT(events, Contains("sched_switch"));
-  EXPECT_THAT(events, Contains("sched_cpu_hotplug"));
-  EXPECT_THAT(events, Contains("print"));
+  EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
+  EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_cpu_hotplug")));
+  EXPECT_THAT(events, Contains(GroupAndName("ftrace", "print")));
 }
 
 TEST_F(FtraceConfigMuxerTest, GetFtraceEventsAtraceCategories) {
+  MockFtraceProcfs ftrace;
+  FtraceConfigMuxer model(&ftrace, table_.get());
+
   FtraceConfig config = CreateFtraceConfig({});
   *config.add_atrace_categories() = "sched";
   *config.add_atrace_categories() = "memreclaim";
-  std::set<std::string> events = GetFtraceEvents(config, table_.get());
+  std::set<GroupAndName> events =
+      model.GetFtraceEventsForTesting(config, table_.get());
 
-  EXPECT_THAT(events, Contains("sched_switch"));
-  EXPECT_THAT(events, Contains("sched_cpu_hotplug"));
-  EXPECT_THAT(events, Contains("cgroup_mkdir"));
-  EXPECT_THAT(events, Contains("mm_vmscan_direct_reclaim_begin"));
-  EXPECT_THAT(events, Contains("lowmemory_kill"));
-  EXPECT_THAT(events, Contains("print"));
+  EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
+  EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_cpu_hotplug")));
+  EXPECT_THAT(events, Contains(GroupAndName("cgroup", "cgroup_mkdir")));
+  EXPECT_THAT(events, Contains(GroupAndName("vmscan",
+                                            "mm_vmscan_direct_reclaim_begin")));
+  EXPECT_THAT(events,
+              Contains(GroupAndName("lowmemorykiller", "lowmemory_kill")));
+  EXPECT_THAT(events, Contains(GroupAndName("ftrace", "print")));
 }
 
 }  // namespace
diff --git a/src/traced/probes/ftrace/ftrace_config_unittest.cc b/src/traced/probes/ftrace/ftrace_config_unittest.cc
index f1a17b7..d1a5c0b 100644
--- a/src/traced/probes/ftrace/ftrace_config_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_unittest.cc
@@ -24,17 +24,6 @@
 namespace perfetto {
 namespace {
 
-TEST(ConfigTest, FtraceEventsAsSet) {
-  FtraceConfig config;
-  *config.add_ftrace_events() = "aaa";
-  *config.add_ftrace_events() = "bbb";
-  *config.add_ftrace_events() = "aaa";
-
-  EXPECT_EQ(FtraceEventsAsSet(config), std::set<std::string>({
-                                           "aaa", "bbb",
-                                       }));
-}
-
 TEST(ConfigTest, CreateFtraceConfig) {
   FtraceConfig config = CreateFtraceConfig({
       "aaa", "bbb",
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index abc5d09..707482f 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -301,11 +301,10 @@
   if (!config_id)
     return false;
 
-  std::unique_ptr<EventFilter> filter(new EventFilter(
-      *table_, FtraceEventsAsSet(*ftrace_config_muxer_->GetConfig(config_id))));
+  const EventFilter* filter = ftrace_config_muxer_->GetEventFilter(config_id);
   auto it_and_inserted = data_sources_.insert(data_source);
   PERFETTO_DCHECK(it_and_inserted.second);
-  data_source->Initialize(config_id, std::move(filter));
+  data_source->Initialize(config_id, filter);
   return true;
 }
 
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index 5146708..7e71737 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -341,8 +341,8 @@
   auto controller =
       CreateTestController(false /* nice runner */, false /* nice procfs */);
 
-  FtraceConfig configA = CreateFtraceConfig({"foo"});
-  FtraceConfig configB = CreateFtraceConfig({"foo", "bar"});
+  FtraceConfig configA = CreateFtraceConfig({"group/foo"});
+  FtraceConfig configB = CreateFtraceConfig({"group/foo", "group/bar"});
 
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _));
   EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1"));
@@ -371,7 +371,7 @@
   auto controller =
       CreateTestController(false /* nice runner */, false /* nice procfs */);
 
-  FtraceConfig config = CreateFtraceConfig({"foo"});
+  FtraceConfig config = CreateFtraceConfig({"group/foo"});
 
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _));
   EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1"));
@@ -401,7 +401,7 @@
   EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
   EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
 
-  FtraceConfig config = CreateFtraceConfig({"foo"});
+  FtraceConfig config = CreateFtraceConfig({"group/foo"});
   auto data_source = controller->AddFakeDataSource(config);
   ASSERT_TRUE(controller->StartDataSource(data_source.get()));
 
@@ -446,7 +446,7 @@
   EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
   EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
 
-  FtraceConfig config = CreateFtraceConfig({"foo"});
+  FtraceConfig config = CreateFtraceConfig({"group/foo"});
   auto data_source = controller->AddFakeDataSource(config);
   ASSERT_TRUE(controller->StartDataSource(data_source.get()));
 
@@ -489,7 +489,7 @@
       .Times(AnyNumber());
 
   EXPECT_CALL(*controller->runner(), PostDelayedTask(_, 100)).Times(2);
-  FtraceConfig config = CreateFtraceConfig({"foo"});
+  FtraceConfig config = CreateFtraceConfig({"group/foo"});
   auto data_source = controller->AddFakeDataSource(config);
   ASSERT_TRUE(controller->StartDataSource(data_source.get()));
 
@@ -528,7 +528,7 @@
     // 8192kb = 8mb
     EXPECT_CALL(*controller->procfs(),
                 WriteToFile("/root/buffer_size_kb", "512"));
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     auto data_source = controller->AddFakeDataSource(config);
     ASSERT_TRUE(controller->StartDataSource(data_source.get()));
   }
@@ -537,7 +537,7 @@
     // Way too big buffer size -> max size.
     EXPECT_CALL(*controller->procfs(),
                 WriteToFile("/root/buffer_size_kb", "65536"));
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     config.set_buffer_size_kb(10 * 1024 * 1024);
     auto data_source = controller->AddFakeDataSource(config);
     ASSERT_TRUE(controller->StartDataSource(data_source.get()));
@@ -547,7 +547,7 @@
     // The limit is 64mb, 65mb is too much.
     EXPECT_CALL(*controller->procfs(),
                 WriteToFile("/root/buffer_size_kb", "65536"));
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     ON_CALL(*controller->procfs(), NumberOfCpus()).WillByDefault(Return(2));
     config.set_buffer_size_kb(65 * 1024);
     auto data_source = controller->AddFakeDataSource(config);
@@ -558,7 +558,7 @@
     // Your size ends up with less than 1 page per cpu -> 1 page.
     EXPECT_CALL(*controller->procfs(),
                 WriteToFile("/root/buffer_size_kb", "4"));
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     config.set_buffer_size_kb(1);
     auto data_source = controller->AddFakeDataSource(config);
     ASSERT_TRUE(controller->StartDataSource(data_source.get()));
@@ -568,7 +568,7 @@
     // You picked a good size -> your size rounded to nearest page.
     EXPECT_CALL(*controller->procfs(),
                 WriteToFile("/root/buffer_size_kb", "40"));
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     config.set_buffer_size_kb(42);
     auto data_source = controller->AddFakeDataSource(config);
     ASSERT_TRUE(controller->StartDataSource(data_source.get()));
@@ -578,7 +578,7 @@
     // You picked a good size -> your size rounded to nearest page.
     EXPECT_CALL(*controller->procfs(),
                 WriteToFile("/root/buffer_size_kb", "40"));
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     ON_CALL(*controller->procfs(), NumberOfCpus()).WillByDefault(Return(2));
     config.set_buffer_size_kb(42);
     auto data_source = controller->AddFakeDataSource(config);
@@ -596,14 +596,14 @@
 
   {
     // No period -> good default.
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     auto data_source = controller->AddFakeDataSource(config);
     EXPECT_EQ(100u, controller->drain_period_ms());
   }
 
   {
     // Pick a tiny value -> good default.
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     config.set_drain_period_ms(0);
     auto data_source = controller->AddFakeDataSource(config);
     EXPECT_EQ(100u, controller->drain_period_ms());
@@ -611,7 +611,7 @@
 
   {
     // Pick a huge value -> good default.
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     config.set_drain_period_ms(1000 * 60 * 60);
     auto data_source = controller->AddFakeDataSource(config);
     EXPECT_EQ(100u, controller->drain_period_ms());
@@ -619,7 +619,7 @@
 
   {
     // Pick a resonable value -> get that value.
-    FtraceConfig config = CreateFtraceConfig({"foo"});
+    FtraceConfig config = CreateFtraceConfig({"group/foo"});
     config.set_drain_period_ms(200);
     auto data_source = controller->AddFakeDataSource(config);
     EXPECT_EQ(200u, controller->drain_period_ms());
diff --git a/src/traced/probes/ftrace/ftrace_data_source.cc b/src/traced/probes/ftrace/ftrace_data_source.cc
index 0272cd4..4dfaed3 100644
--- a/src/traced/probes/ftrace/ftrace_data_source.cc
+++ b/src/traced/probes/ftrace/ftrace_data_source.cc
@@ -44,10 +44,10 @@
 };
 
 void FtraceDataSource::Initialize(FtraceConfigId config_id,
-                                  std::unique_ptr<EventFilter> event_filter) {
+                                  const EventFilter* event_filter) {
   PERFETTO_CHECK(config_id);
   config_id_ = config_id;
-  event_filter_ = std::move(event_filter);
+  event_filter_ = event_filter;
 }
 
 void FtraceDataSource::Start() {
diff --git a/src/traced/probes/ftrace/ftrace_data_source.h b/src/traced/probes/ftrace/ftrace_data_source.h
index f15c1d1..8fdbdc3 100644
--- a/src/traced/probes/ftrace/ftrace_data_source.h
+++ b/src/traced/probes/ftrace/ftrace_data_source.h
@@ -60,7 +60,7 @@
 
   // Called by FtraceController soon after ProbesProducer creates the data
   // source, to inject ftrace dependencies.
-  void Initialize(FtraceConfigId, std::unique_ptr<EventFilter>);
+  void Initialize(FtraceConfigId, const EventFilter* event_filter);
 
   // ProbesDataSource implementation.
   void Start() override;
@@ -71,7 +71,7 @@
 
   FtraceConfigId config_id() const { return config_id_; }
   const FtraceConfig& config() const { return config_; }
-  EventFilter* event_filter() { return event_filter_.get(); }
+  const EventFilter* event_filter() { return event_filter_; }
   FtraceMetadata* mutable_metadata() { return &metadata_; }
   TraceWriter* trace_writer() { return writer_.get(); }
 
@@ -90,7 +90,7 @@
   FtraceConfigId config_id_ = 0;
   std::unique_ptr<TraceWriter> writer_;
   base::WeakPtr<FtraceController> controller_weak_;
-  std::unique_ptr<EventFilter> event_filter_;
+  const EventFilter* event_filter_;
 };
 
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index 105b678..53c11ba 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -261,6 +261,33 @@
   return str;
 }
 
+const std::set<std::string> FtraceProcfs::GetEventNamesForGroup(
+    const std::string& path) const {
+  std::set<std::string> names;
+  std::string full_path = root_ + path;
+  base::ScopedDir dir(opendir(full_path.c_str()));
+  if (!dir) {
+    PERFETTO_DLOG("Unable to read events from %s", full_path.c_str());
+    return names;
+  }
+  struct dirent* ent;
+  while ((ent = readdir(*dir)) != nullptr) {
+    if (strncmp(ent->d_name, ".", 1) == 0 ||
+        strncmp(ent->d_name, "..", 2) == 0) {
+      continue;
+    }
+    // Check ent is a directory.
+    struct stat statbuf;
+    std::string dir_path = full_path + "/" + ent->d_name;
+    if (stat(dir_path.c_str(), &statbuf) == 0) {
+      if (S_ISDIR(statbuf.st_mode)) {
+        names.insert(ent->d_name);
+      }
+    }
+  }
+  return names;
+}
+
 // static
 bool FtraceProcfs::CheckRootPath(const std::string& root) {
   base::ScopedFile fd = base::OpenFile(root + "trace", O_RDONLY);
diff --git a/src/traced/probes/ftrace/ftrace_procfs.h b/src/traced/probes/ftrace/ftrace_procfs.h
index 348236e..b79cd16 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.h
+++ b/src/traced/probes/ftrace/ftrace_procfs.h
@@ -94,6 +94,9 @@
   // Open the raw pipe for |cpu|.
   virtual base::ScopedFile OpenPipeForCpu(size_t cpu);
 
+  virtual const std::set<std::string> GetEventNamesForGroup(
+      const std::string& path) const;
+
  protected:
   // virtual and public for testing.
   virtual bool WriteToFile(const std::string& path, const std::string& str);
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 89096a4..c158aa5 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -97,7 +97,7 @@
   if (!SetTranslationStrategy(field->ftrace_type, field->proto_field_type,
                               &field->strategy)) {
     PERFETTO_DLOG(
-        "Failed to find translation stratagy for ftrace field \"%s.%s\" (%s -> "
+        "Failed to find translation strategy for ftrace field \"%s.%s\" (%s -> "
         "%s)",
         event_name_for_debug, field->ftrace_name, ToString(field->ftrace_type),
         ToString(field->proto_field_type));
@@ -399,23 +399,22 @@
       common_fields_(std::move(common_fields)),
       ftrace_page_header_spec_(ftrace_page_header_spec) {
   for (const Event& event : events) {
-    name_to_event_[event.name] = &events_.at(event.ftrace_event_id);
+    group_and_name_to_event_[GroupAndName(event.group, event.name)] =
+        &events_.at(event.ftrace_event_id);
+    name_to_events_[event.name].push_back(&events_.at(event.ftrace_event_id));
     group_to_events_[event.group].push_back(&events_.at(event.ftrace_event_id));
   }
 }
 
 const Event* ProtoTranslationTable::GetOrCreateEvent(
-    const std::string& group,
-    const std::string& event_name) {
-  // TODO(taylori): Index events by group and name to avoid future conflicts.
-  const Event* event = GetEventByName(event_name);
+    const GroupAndName& group_and_name) {
+  const Event* event = GetEvent(group_and_name);
   if (event)
     return event;
   // The ftrace event does not already exist so a new one will be created
   // by parsing the format file.
-  if (group.empty() || event_name.empty())
-    return nullptr;
-  std::string contents = ftrace_procfs_->ReadEventFormat(group, event_name);
+  std::string contents = ftrace_procfs_->ReadEventFormat(group_and_name.group(),
+                                                         group_and_name.name());
   if (contents.empty())
     return nullptr;
   FtraceEvent ftrace_event = {};
@@ -431,8 +430,8 @@
   Event* e = &events_.at(ftrace_event.id);
   e->ftrace_event_id = ftrace_event.id;
   e->proto_field_id = protos::pbzero::FtraceEvent::kGenericFieldNumber;
-  e->name = InternString(event_name);
-  e->group = InternString(group);
+  e->name = InternString(group_and_name.name());
+  e->group = InternString(group_and_name.group());
 
   // Calculate size of common fields.
   for (const FtraceEvent::Field& ftrace_field : ftrace_event.common_fields) {
@@ -444,7 +443,8 @@
   for (const FtraceEvent::Field& ftrace_field : ftrace_event.fields)
     e->size = std::max(CreateGenericEventField(ftrace_field, *e), e->size);
 
-  name_to_event_[e->name] = &events_.at(e->ftrace_event_id);
+  group_and_name_to_event_[group_and_name] = &events_.at(e->ftrace_event_id);
+  name_to_events_[e->name].push_back(&events_.at(e->ftrace_event_id));
   group_to_events_[e->group].push_back(&events_.at(e->ftrace_event_id));
 
   return e;
@@ -490,6 +490,46 @@
   return field_end;
 }
 
+EventFilter::EventFilter() = default;
+EventFilter::~EventFilter() = default;
+
+void EventFilter::AddEnabledEvent(size_t ftrace_event_id) {
+  if (ftrace_event_id >= enabled_ids_.size())
+    enabled_ids_.resize(ftrace_event_id + 1);
+  enabled_ids_[ftrace_event_id] = true;
+}
+
+void EventFilter::DisableEvent(size_t ftrace_event_id) {
+  if (ftrace_event_id >= enabled_ids_.size())
+    return;
+  enabled_ids_[ftrace_event_id] = false;
+}
+
+bool EventFilter::IsEventEnabled(size_t ftrace_event_id) const {
+  if (ftrace_event_id == 0 || ftrace_event_id > enabled_ids_.size())
+    return false;
+  return enabled_ids_[ftrace_event_id];
+}
+
+std::set<size_t> EventFilter::GetEnabledEvents() const {
+  std::set<size_t> enabled;
+  for (size_t i = 0; i < enabled_ids_.size(); i++) {
+    if (enabled_ids_[i]) {
+      enabled.insert(i);
+    }
+  }
+  return enabled;
+}
+
+void EventFilter::EnableEventsFrom(const EventFilter& other) {
+  size_t max_length = std::max(enabled_ids_.size(), other.enabled_ids_.size());
+  enabled_ids_.resize(max_length);
+  for (size_t i = 0; i < other.enabled_ids_.size(); i++) {
+    if (other.enabled_ids_[i])
+      enabled_ids_[i] = true;
+  }
+}
+
 ProtoTranslationTable::~ProtoTranslationTable() = default;
 
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/proto_translation_table.h b/src/traced/probes/ftrace/proto_translation_table.h
index 31763e4..592629f 100644
--- a/src/traced/probes/ftrace/proto_translation_table.h
+++ b/src/traced/probes/ftrace/proto_translation_table.h
@@ -39,6 +39,31 @@
 }  // namespace pbzero
 }  // namespace protos
 
+// Used when reading the config to store the group and name info for the
+// ftrace event.
+class GroupAndName {
+ public:
+  GroupAndName(const std::string& group, const std::string& name)
+      : group_(group), name_(name) {}
+
+  bool operator==(const GroupAndName& other) const {
+    return std::tie(group_, name_) == std::tie(other.group(), other.name());
+  }
+
+  bool operator<(const GroupAndName& other) const {
+    return std::tie(group_, name_) < std::tie(other.group(), other.name());
+  }
+
+  const std::string& name() const { return name_; }
+  const std::string& group() const { return group_; }
+
+  std::string ToString() const { return group_ + "/" + name_; }
+
+ private:
+  std::string group_;
+  std::string name_;
+};
+
 bool InferFtraceType(const std::string& type_and_name,
                      size_t size,
                      bool is_signed,
@@ -72,10 +97,11 @@
 
   const std::vector<Field>& common_fields() const { return common_fields_; }
 
-  const Event* GetEventByName(const std::string& name) const {
-    if (!name_to_event_.count(name))
+  // Virtual for testing.
+  virtual const Event* GetEvent(const GroupAndName& group_and_name) const {
+    if (!group_and_name_to_event_.count(group_and_name))
       return nullptr;
-    return name_to_event_.at(name);
+    return group_and_name_to_event_.at(group_and_name);
   }
 
   const std::vector<const Event*>* GetEventsByGroup(
@@ -93,10 +119,10 @@
     return &events_.at(id);
   }
 
-  size_t EventNameToFtraceId(const std::string& name) const {
-    if (!name_to_event_.count(name))
+  size_t EventToFtraceId(const GroupAndName& group_and_name) const {
+    if (!group_and_name_to_event_.count(group_and_name))
       return 0;
-    return name_to_event_.at(name)->ftrace_event_id;
+    return group_and_name_to_event_.at(group_and_name)->ftrace_event_id;
   }
 
   const std::vector<Event>& events() { return events_; }
@@ -107,8 +133,15 @@
   // Retrieves the ftrace event from the proto translation
   // table. If it does not exist, reads the format file and creates a
   // new event with the proto id set to generic. Virtual for testing.
-  virtual const Event* GetOrCreateEvent(const std::string& group,
-                                        const std::string& event_name);
+  virtual const Event* GetOrCreateEvent(const GroupAndName&);
+
+  // This is for backwards compatibility. If a group is not specified in the
+  // config then the first event with that name will be returned.
+  const Event* GetEventByName(const std::string& name) const {
+    if (!name_to_events_.count(name))
+      return nullptr;
+    return name_to_events_.at(name)[0];
+  }
 
  private:
   ProtoTranslationTable(const ProtoTranslationTable&) = delete;
@@ -123,13 +156,37 @@
   const FtraceProcfs* ftrace_procfs_;
   std::vector<Event> events_;
   size_t largest_id_;
-  std::map<std::string, const Event*> name_to_event_;
+  std::map<GroupAndName, const Event*> group_and_name_to_event_;
+  std::map<std::string, std::vector<const Event*>> name_to_events_;
   std::map<std::string, std::vector<const Event*>> group_to_events_;
   std::vector<Field> common_fields_;
   FtracePageHeaderSpec ftrace_page_header_spec_{};
   std::set<std::string> interned_strings_;
 };
 
+// Class for efficient 'is event with id x enabled?' checks.
+// Mirrors the data in a FtraceConfig but in a format better suited
+// to be consumed by CpuReader.
+class EventFilter {
+ public:
+  EventFilter();
+  ~EventFilter();
+  EventFilter(EventFilter&&) = default;
+  EventFilter& operator=(EventFilter&&) = default;
+
+  void AddEnabledEvent(size_t ftrace_event_id);
+  void DisableEvent(size_t ftrace_event_id);
+  bool IsEventEnabled(size_t ftrace_event_id) const;
+  std::set<size_t> GetEnabledEvents() const;
+  void EnableEventsFrom(const EventFilter&);
+
+ private:
+  EventFilter(const EventFilter&) = delete;
+  EventFilter& operator=(const EventFilter&) = delete;
+
+  std::vector<bool> enabled_ids_;
+};
+
 }  // namespace perfetto
 
 #endif  // SRC_TRACED_PROBES_FTRACE_PROTO_TRANSLATION_TABLE_H_
diff --git a/src/traced/probes/ftrace/proto_translation_table_unittest.cc b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
index 94fbb3d..13b1b07 100644
--- a/src/traced/probes/ftrace/proto_translation_table_unittest.cc
+++ b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
@@ -69,10 +69,10 @@
 
 TEST_P(AllTranslationTableTest, Create) {
   EXPECT_TRUE(table_);
-  EXPECT_TRUE(table_->GetEventByName("print"));
-  EXPECT_TRUE(table_->GetEventByName("sched_switch"));
-  EXPECT_TRUE(table_->GetEventByName("sched_wakeup"));
-  EXPECT_TRUE(table_->GetEventByName("ext4_da_write_begin"));
+  EXPECT_TRUE(table_->GetEvent(GroupAndName("ftrace", "print")));
+  EXPECT_TRUE(table_->GetEvent(GroupAndName("sched", "sched_switch")));
+  EXPECT_TRUE(table_->GetEvent(GroupAndName("sched", "sched_wakeup")));
+  EXPECT_TRUE(table_->GetEvent(GroupAndName("ext4", "ext4_da_write_begin")));
   for (const Event& event : table_->events()) {
     if (!event.ftrace_event_id)
       continue;
@@ -91,7 +91,7 @@
   EXPECT_EQ(pid_field.proto_field_id, 2u);
 
   {
-    auto event = table_->GetEventByName("print");
+    auto event = table_->GetEvent(GroupAndName("ftrace", "print"));
     EXPECT_TRUE(event);
     EXPECT_EQ(std::string(event->name), "print");
     EXPECT_EQ(std::string(event->group), "ftrace");
@@ -116,7 +116,7 @@
   EXPECT_EQ(pid_field.ftrace_size, 4u);
 
   {
-    auto event = table->GetEventByName("sched_switch");
+    auto event = table->GetEvent(GroupAndName("sched", "sched_switch"));
     EXPECT_EQ(std::string(event->name), "sched_switch");
     EXPECT_EQ(std::string(event->group), "sched");
     EXPECT_EQ(event->ftrace_event_id, 68ul);
@@ -125,7 +125,7 @@
   }
 
   {
-    auto event = table->GetEventByName("sched_wakeup");
+    auto event = table->GetEvent(GroupAndName("sched", "sched_wakeup"));
     EXPECT_EQ(std::string(event->name), "sched_wakeup");
     EXPECT_EQ(std::string(event->group), "sched");
     EXPECT_EQ(event->ftrace_event_id, 70ul);
@@ -134,7 +134,7 @@
   }
 
   {
-    auto event = table->GetEventByName("ext4_da_write_begin");
+    auto event = table->GetEvent(GroupAndName("ext4", "ext4_da_write_begin"));
     EXPECT_EQ(std::string(event->name), "ext4_da_write_begin");
     EXPECT_EQ(std::string(event->group), "ext4");
     EXPECT_EQ(event->ftrace_event_id, 303ul);
@@ -229,9 +229,8 @@
   auto table = ProtoTranslationTable::Create(&ftrace, std::move(events),
                                              std::move(common_fields));
   EXPECT_EQ(table->largest_id(), 42ul);
-  EXPECT_EQ(table->EventNameToFtraceId("foo"), 42ul);
-  EXPECT_EQ(table->EventNameToFtraceId("bar"), 0ul);
-  EXPECT_EQ(table->EventNameToFtraceId("bar"), 0ul);
+  EXPECT_EQ(table->EventToFtraceId(GroupAndName("group", "foo")), 42ul);
+  EXPECT_EQ(table->EventToFtraceId(GroupAndName("group", "bar")), 0ul);
   EXPECT_FALSE(table->GetEventById(43ul));
   ASSERT_TRUE(table->GetEventById(42ul));
   EXPECT_EQ(table->ftrace_page_header_spec().timestamp.size, 8);
@@ -341,14 +340,16 @@
       &ftrace, events, std::move(common_fields),
       ProtoTranslationTable::DefaultPageHeaderSpecForTesting());
   EXPECT_EQ(table.largest_id(), 100ul);
-  EXPECT_EQ(table.EventNameToFtraceId("foo"), 1ul);
-  EXPECT_EQ(table.EventNameToFtraceId("baz"), 100ul);
-  EXPECT_EQ(table.EventNameToFtraceId("no_such_event"), 0ul);
+  EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_one", "foo")), 1ul);
+  EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_two", "baz")), 100ul);
+  EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_one", "no_such_event")),
+            0ul);
   EXPECT_EQ(table.GetEventById(1)->name, "foo");
   EXPECT_EQ(table.GetEventById(3), nullptr);
   EXPECT_EQ(table.GetEventById(200), nullptr);
   EXPECT_EQ(table.GetEventById(0), nullptr);
-  EXPECT_EQ(table.GetEventByName("foo")->ftrace_event_id, 1u);
+  EXPECT_EQ(table.GetEvent(GroupAndName("group_one", "foo"))->ftrace_event_id,
+            1u);
   EXPECT_THAT(*table.GetEventsByGroup("group_one"),
               Contains(testing::Field(&Event::name, "foo")));
   EXPECT_THAT(*table.GetEventsByGroup("group_one"),
@@ -390,17 +391,18 @@
   auto table = ProtoTranslationTable::Create(&ftrace, std::move(events),
                                              std::move(common_fields));
   EXPECT_EQ(table->largest_id(), 0ul);
-  std::string event_name = "foo";
-  const Event* e = table->GetOrCreateEvent("group", event_name);
+  GroupAndName group_and_name("group", "foo");
+  const Event* e = table->GetOrCreateEvent(group_and_name);
   EXPECT_EQ(table->largest_id(), 42ul);
-  EXPECT_EQ(table->EventNameToFtraceId(event_name), 42ul);
+  EXPECT_EQ(table->EventToFtraceId(group_and_name), 42ul);
 
   // Check getters
   EXPECT_EQ(table->GetEventById(42)->proto_field_id,
             protos::pbzero::FtraceEvent::kGenericFieldNumber);
-  EXPECT_EQ(table->GetEventByName(event_name)->proto_field_id,
+  EXPECT_EQ(table->GetEvent(group_and_name)->proto_field_id,
             protos::pbzero::FtraceEvent::kGenericFieldNumber);
-  EXPECT_EQ(table->GetEventsByGroup("group")->front()->name, event_name);
+  EXPECT_EQ(table->GetEventsByGroup("group")->front()->name,
+            group_and_name.name());
 
   EXPECT_EQ(e->fields.size(), 4ul);
   const std::vector<Field>& fields = e->fields;
@@ -445,5 +447,32 @@
   EXPECT_EQ(uint_field.ftrace_offset, 33);
 }
 
+TEST(EventFilterTest, EnableEventsFrom) {
+  EventFilter filter;
+  filter.AddEnabledEvent(1);
+  filter.AddEnabledEvent(17);
+
+  EventFilter or_filter;
+  or_filter.AddEnabledEvent(4);
+  or_filter.AddEnabledEvent(17);
+
+  filter.EnableEventsFrom(or_filter);
+  EXPECT_TRUE(filter.IsEventEnabled(4));
+  EXPECT_TRUE(filter.IsEventEnabled(17));
+  EXPECT_TRUE(filter.IsEventEnabled(1));
+  EXPECT_FALSE(filter.IsEventEnabled(2));
+
+  EventFilter empty_filter;
+  filter.EnableEventsFrom(empty_filter);
+  EXPECT_TRUE(filter.IsEventEnabled(4));
+  EXPECT_TRUE(filter.IsEventEnabled(17));
+  EXPECT_TRUE(filter.IsEventEnabled(1));
+
+  empty_filter.EnableEventsFrom(filter);
+  EXPECT_TRUE(empty_filter.IsEventEnabled(4));
+  EXPECT_TRUE(empty_filter.IsEventEnabled(17));
+  EXPECT_TRUE(empty_filter.IsEventEnabled(1));
+}
+
 }  // namespace
 }  // namespace perfetto