ftrace: Add filter for ftrace/print buf

This commit adds a filter that discards some ftrace/print events based
on the content of their buffer.

The filtered ftrace/print events still take space in the ftrace per-cpu
kernel buffer (they're discarded later, in userspace), but do not take
space in the perfetto buffers.

The filter can be configured with multiple rules, processed in order.
Each rule matches the prefix of the ftrace/print buffer. If an
ftrace/print event doesn't match any rule, it will be allowed by the
filter.

For example, the following config discards all the atrace begin and end
slices:

```
ftrace_config
  ...
  print_filter {
    rules {
      prefix: "B|"
      allow: false
    }
    rules {
      prefix: "E|"
      allow: false
    }
  }
  ...
}

```

Tested by sideloading on Android, capturing a trace with the prefix "C|"
disallowed, and confirming that counters disappear from the UI.

Benchmarks before:

```
------------------------------------------------------------------------
Benchmark                              Time             CPU   Iterations
------------------------------------------------------------------------
BM_ParsePageFullOfSchedSwitch      15029 ns        15028 ns        44778
BM_ParsePageFullOfPrint            15130 ns        15130 ns        45195
```

Benchmarks after:

```
------------------------------------------------------------------------------------
Benchmark                                          Time             CPU   Iterations
------------------------------------------------------------------------------------
BM_ParsePageFullOfSchedSwitch                  15241 ns        15240 ns        44093
BM_ParsePageFullOfPrint                        15077 ns        15077 ns        45144
BM_ParsePageFullOfPrintWithFilterRules/0       14869 ns        14869 ns        46117
BM_ParsePageFullOfPrintWithFilterRules/1       14801 ns        14801 ns        46633
BM_ParsePageFullOfPrintWithFilterRules/2       15103 ns        15103 ns        46043
BM_ParsePageFullOfPrintWithFilterRules/3       15244 ns        15243 ns        45705
BM_ParsePageFullOfPrintWithFilterRules/4       15662 ns        15662 ns        44557
BM_ParsePageFullOfPrintWithFilterRules/5       16014 ns        16012 ns        43686
BM_ParsePageFullOfPrintWithFilterRules/6       16250 ns        16249 ns        42996
BM_ParsePageFullOfPrintWithFilterRules/7       16552 ns        16551 ns        42189
BM_ParsePageFullOfPrintWithFilterRules/8       16872 ns        16871 ns        41442
BM_ParsePageFullOfPrintWithFilterRules/9       17239 ns        17239 ns        40532
BM_ParsePageFullOfPrintWithFilterRules/10      17608 ns        17608 ns        39784
BM_ParsePageFullOfPrintWithFilterRules/11      17872 ns        17871 ns        39043
BM_ParsePageFullOfPrintWithFilterRules/12      18180 ns        18179 ns        38410
BM_ParsePageFullOfPrintWithFilterRules/13      18543 ns        18543 ns        37765
BM_ParsePageFullOfPrintWithFilterRules/14      18842 ns        18841 ns        37027
BM_ParsePageFullOfPrintWithFilterRules/15      19190 ns        19188 ns        36293
BM_ParsePageFullOfPrintWithFilterRules/16      19506 ns        19505 ns        35820
```

Change-Id: I3c61a0ca614b518d96ea7476c5e94d1404d6aabe
diff --git a/Android.bp b/Android.bp
index 580d90f..110c652 100644
--- a/Android.bp
+++ b/Android.bp
@@ -9949,6 +9949,7 @@
         "src/traced/probes/ftrace/ftrace_config_utils.cc",
         "src/traced/probes/ftrace/ftrace_controller.cc",
         "src/traced/probes/ftrace/ftrace_data_source.cc",
+        "src/traced/probes/ftrace/ftrace_print_filter.cc",
         "src/traced/probes/ftrace/ftrace_stats.cc",
         "src/traced/probes/ftrace/printk_formats_parser.cc",
         "src/traced/probes/ftrace/proto_translation_table.cc",
@@ -10096,6 +10097,7 @@
         "src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc",
         "src/traced/probes/ftrace/ftrace_config_unittest.cc",
         "src/traced/probes/ftrace/ftrace_controller_unittest.cc",
+        "src/traced/probes/ftrace/ftrace_print_filter_unittest.cc",
         "src/traced/probes/ftrace/ftrace_procfs_unittest.cc",
         "src/traced/probes/ftrace/printk_formats_parser_unittest.cc",
         "src/traced/probes/ftrace/proto_translation_table_unittest.cc",
diff --git a/BUILD b/BUILD
index 6ca0711..ae79652 100644
--- a/BUILD
+++ b/BUILD
@@ -1995,6 +1995,8 @@
         "src/traced/probes/ftrace/ftrace_data_source.cc",
         "src/traced/probes/ftrace/ftrace_data_source.h",
         "src/traced/probes/ftrace/ftrace_metadata.h",
+        "src/traced/probes/ftrace/ftrace_print_filter.cc",
+        "src/traced/probes/ftrace/ftrace_print_filter.h",
         "src/traced/probes/ftrace/ftrace_stats.cc",
         "src/traced/probes/ftrace/ftrace_stats.h",
         "src/traced/probes/ftrace/printk_formats_parser.cc",
diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto
index c5c2de1..24727ed 100644
--- a/protos/perfetto/config/ftrace/ftrace_config.proto
+++ b/protos/perfetto/config/ftrace/ftrace_config.proto
@@ -18,7 +18,7 @@
 
 package perfetto.protos;
 
-// Next id: 22.
+// Next id: 23.
 message FtraceConfig {
   repeated string ftrace_events = 1;
   repeated string atrace_categories = 2;
@@ -37,6 +37,24 @@
   }
   optional CompactSchedConfig compact_sched = 12;
 
+  // Optional filter for "ftrace/print" events.
+  //
+  // The filter consists of multiple rules. A rule matches if its prefix matches
+  // exactly with the beginning of the "ftrace/print" "buf" field. As soon as a
+  // rule matches (the rules are processed in order), its `allow` field will be
+  // used as the outcome: if `allow` is true, the event will be included in the
+  // trace, otherwise it will be discarded. If an event does not match any rule,
+  // it will be allowed by default (a rule with an empty prefix and allow=false,
+  // disallows everything by default).
+  message PrintFilter {
+    message Rule {
+      optional string prefix = 1;
+      optional bool allow = 2;
+    }
+    repeated Rule rules = 1;
+  }
+  optional PrintFilter print_filter = 22;
+
   // Enables symbol name resolution against /proc/kallsyms.
   // It requires that either traced_probes is running as root or that
   // kptr_restrict has been manually lowered.
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index fb3d49e..e06cb31 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -466,7 +466,7 @@
 
 // Begin of protos/perfetto/config/ftrace/ftrace_config.proto
 
-// Next id: 22.
+// Next id: 23.
 message FtraceConfig {
   repeated string ftrace_events = 1;
   repeated string atrace_categories = 2;
@@ -485,6 +485,24 @@
   }
   optional CompactSchedConfig compact_sched = 12;
 
+  // Optional filter for "ftrace/print" events.
+  //
+  // The filter consists of multiple rules. A rule matches if its prefix matches
+  // exactly with the beginning of the "ftrace/print" "buf" field. As soon as a
+  // rule matches (the rules are processed in order), its `allow` field will be
+  // used as the outcome: if `allow` is true, the event will be included in the
+  // trace, otherwise it will be discarded. If an event does not match any rule,
+  // it will be allowed by default (a rule with an empty prefix and allow=false,
+  // disallows everything by default).
+  message PrintFilter {
+    message Rule {
+      optional string prefix = 1;
+      optional bool allow = 2;
+    }
+    repeated Rule rules = 1;
+  }
+  optional PrintFilter print_filter = 22;
+
   // Enables symbol name resolution against /proc/kallsyms.
   // It requires that either traced_probes is running as root or that
   // kptr_restrict has been manually lowered.
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 1d5f841..04df8b1 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -466,7 +466,7 @@
 
 // Begin of protos/perfetto/config/ftrace/ftrace_config.proto
 
-// Next id: 22.
+// Next id: 23.
 message FtraceConfig {
   repeated string ftrace_events = 1;
   repeated string atrace_categories = 2;
@@ -485,6 +485,24 @@
   }
   optional CompactSchedConfig compact_sched = 12;
 
+  // Optional filter for "ftrace/print" events.
+  //
+  // The filter consists of multiple rules. A rule matches if its prefix matches
+  // exactly with the beginning of the "ftrace/print" "buf" field. As soon as a
+  // rule matches (the rules are processed in order), its `allow` field will be
+  // used as the outcome: if `allow` is true, the event will be included in the
+  // trace, otherwise it will be discarded. If an event does not match any rule,
+  // it will be allowed by default (a rule with an empty prefix and allow=false,
+  // disallows everything by default).
+  message PrintFilter {
+    message Rule {
+      optional string prefix = 1;
+      optional bool allow = 2;
+    }
+    repeated Rule rules = 1;
+  }
+  optional PrintFilter print_filter = 22;
+
   // Enables symbol name resolution against /proc/kallsyms.
   // It requires that either traced_probes is running as root or that
   // kptr_restrict has been manually lowered.
diff --git a/src/traced/probes/ftrace/BUILD.gn b/src/traced/probes/ftrace/BUILD.gn
index 9a6a7d1..91b68f6 100644
--- a/src/traced/probes/ftrace/BUILD.gn
+++ b/src/traced/probes/ftrace/BUILD.gn
@@ -66,14 +66,15 @@
   sources = [
     "cpu_reader_unittest.cc",
     "cpu_stats_parser_unittest.cc",
-    "vendor_tracepoints_unittest.cc",
     "event_info_unittest.cc",
     "ftrace_config_muxer_unittest.cc",
     "ftrace_config_unittest.cc",
     "ftrace_controller_unittest.cc",
+    "ftrace_print_filter_unittest.cc",
     "ftrace_procfs_unittest.cc",
     "printk_formats_parser_unittest.cc",
     "proto_translation_table_unittest.cc",
+    "vendor_tracepoints_unittest.cc",
   ]
 }
 
@@ -137,8 +138,6 @@
     "cpu_reader.h",
     "cpu_stats_parser.cc",
     "cpu_stats_parser.h",
-    "vendor_tracepoints.cc",
-    "vendor_tracepoints.h",
     "event_info.cc",
     "event_info.h",
     "event_info_constants.cc",
@@ -152,12 +151,16 @@
     "ftrace_data_source.cc",
     "ftrace_data_source.h",
     "ftrace_metadata.h",
+    "ftrace_print_filter.cc",
+    "ftrace_print_filter.h",
     "ftrace_stats.cc",
     "ftrace_stats.h",
     "printk_formats_parser.cc",
     "printk_formats_parser.h",
     "proto_translation_table.cc",
     "proto_translation_table.h",
+    "vendor_tracepoints.cc",
+    "vendor_tracepoints.h",
   ]
 }
 
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index baed3c7..d407aad 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -37,6 +37,7 @@
 #include "src/traced/probes/ftrace/ftrace_config_muxer.h"
 #include "src/traced/probes/ftrace/ftrace_controller.h"
 #include "src/traced/probes/ftrace/ftrace_data_source.h"
+#include "src/traced/probes/ftrace/ftrace_print_filter.h"
 #include "src/traced/probes/ftrace/proto_translation_table.h"
 
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -622,6 +623,9 @@
           const CompactSchedWakingFormat& sched_waking_format =
               table->compact_sched_format().sched_waking;
 
+          bool ftrace_print_filter_enabled =
+              ds_config->print_filter.has_value();
+
           // compact sched_switch
           if (compact_sched_enabled &&
               ftrace_event_id == sched_switch_format.event_id) {
@@ -640,6 +644,15 @@
             ParseSchedWakingCompact(start, timestamp, &sched_waking_format,
                                     compact_sched_buffer, metadata);
 
+          } else if (ftrace_print_filter_enabled &&
+                     ftrace_event_id == ds_config->print_filter->event_id()) {
+            if (ds_config->print_filter->IsEventInteresting(start, next)) {
+              protos::pbzero::FtraceEvent* event = bundle->add_event();
+              event->set_timestamp(timestamp);
+              if (!ParseEvent(ftrace_event_id, start, next, table, event,
+                              metadata))
+                return 0;
+            }
           } else {
             // Common case: parse all other types of enabled events.
             protos::pbzero::FtraceEvent* event = bundle->add_event();
diff --git a/src/traced/probes/ftrace/cpu_reader_benchmark.cc b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
index 527804d..a22fc1b 100644
--- a/src/traced/probes/ftrace/cpu_reader_benchmark.cc
+++ b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
@@ -21,6 +21,7 @@
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "src/traced/probes/ftrace/cpu_reader.h"
 #include "src/traced/probes/ftrace/ftrace_config_muxer.h"
+#include "src/traced/probes/ftrace/ftrace_print_filter.h"
 #include "src/traced/probes/ftrace/proto_translation_table.h"
 #include "src/traced/probes/ftrace/test/cpu_reader_support.h"
 
@@ -559,6 +560,7 @@
 
 void DoParse(const ExamplePage& test_case,
              const std::vector<GroupAndName>& enabled_events,
+             base::Optional<FtraceConfig::PrintFilter> print_filter,
              benchmark::State& state) {
   ScatteredStreamWriterNullDelegate delegate(base::kPageSize);
   ScatteredStreamWriter stream(&delegate);
@@ -570,9 +572,15 @@
   FtraceDataSourceConfig ds_config{EventFilter{},
                                    EventFilter{},
                                    DisabledCompactSchedConfigForTesting(),
+                                   base::nullopt,
                                    {},
                                    {},
                                    false /*symbolize_ksyms*/};
+  if (print_filter.has_value()) {
+    ds_config.print_filter =
+        FtracePrintFilterConfig::Create(print_filter.value(), table);
+    PERFETTO_CHECK(ds_config.print_filter.has_value());
+  }
   for (const GroupAndName& enabled_event : enabled_events) {
     ds_config.event_filter.AddEnabledEvent(
         table->EventToFtraceId(enabled_event));
@@ -601,14 +609,27 @@
 
 void BM_ParsePageFullOfSchedSwitch(benchmark::State& state) {
   DoParse(g_full_page_sched_switch, {GroupAndName("sched", "sched_switch")},
-          state);
+          base::nullopt, state);
 }
 BENCHMARK(BM_ParsePageFullOfSchedSwitch);
 
 void BM_ParsePageFullOfPrint(benchmark::State& state) {
-  DoParse(g_full_page_print, {GroupAndName("ftrace", "print")}, state);
+  DoParse(g_full_page_print, {GroupAndName("ftrace", "print")}, base::nullopt,
+          state);
 }
 BENCHMARK(BM_ParsePageFullOfPrint);
 
+void BM_ParsePageFullOfPrintWithFilterRules(benchmark::State& state) {
+  FtraceConfig::PrintFilter filter_conf;
+  for (int i = 0; i < state.range(0); i++) {
+    auto* rule = filter_conf.add_rules();
+    rule->set_prefix("X");  // This rule will not match
+    rule->set_allow(false);
+  }
+  DoParse(g_full_page_print, {GroupAndName("ftrace", "print")}, filter_conf,
+          state);
+}
+BENCHMARK(BM_ParsePageFullOfPrintWithFilterRules)->DenseRange(0, 16, 1);
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
index 8cb4353..04891a3 100644
--- a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
+++ b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
@@ -57,6 +57,7 @@
   FtraceDataSourceConfig ds_config{EventFilter{},
                                    EventFilter{},
                                    DisabledCompactSchedConfigForTesting(),
+                                   base::nullopt,
                                    {},
                                    {},
                                    /*symbolize_ksyms=*/false};
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 2b16ff2..2f8061f 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -60,13 +60,13 @@
 using testing::StartsWith;
 
 namespace perfetto {
-
 namespace {
 
 FtraceDataSourceConfig EmptyConfig() {
   return FtraceDataSourceConfig{EventFilter{},
                                 EventFilter{},
                                 DisabledCompactSchedConfigForTesting(),
+                                base::nullopt,
                                 {},
                                 {},
                                 false /*symbolize_ksyms*/};
@@ -186,8 +186,6 @@
   uint8_t* ptr_;
 };
 
-}  // namespace
-
 TEST(PageFromXxdTest, OneLine) {
   std::string text = R"(
     00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
@@ -756,6 +754,87 @@
   }
 }
 
+TEST(CpuReaderTest, ParsePrintWithAndWithoutFilter) {
+  const ExamplePage* test_case = &g_three_prints;
+
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceMetadata metadata{};
+  std::unique_ptr<CompactSchedBuffer> compact_buffer(new CompactSchedBuffer());
+  const uint8_t* parse_pos = page.get();
+  base::Optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+
+  const uint8_t* page_end = page.get() + base::kPageSize;
+  ASSERT_TRUE(page_header.has_value());
+  ASSERT_FALSE(page_header->lost_events);
+  ASSERT_LE(parse_pos + page_header->size, page_end);
+
+  {
+    FtraceDataSourceConfig ds_config_no_filter = EmptyConfig();
+    ds_config_no_filter.event_filter.AddEnabledEvent(
+        table->EventToFtraceId(GroupAndName("ftrace", "print")));
+
+    BundleProvider bundle_provider(base::kPageSize);
+    size_t evt_bytes = CpuReader::ParsePagePayload(
+        parse_pos, &page_header.value(), table, &ds_config_no_filter,
+        compact_buffer.get(), bundle_provider.writer(), &metadata);
+    ASSERT_GE(evt_bytes, 0u);
+
+    auto bundle = bundle_provider.ParseProto();
+    using ::testing::Pointee;
+    using ::testing::Property;
+    EXPECT_THAT(
+        bundle,
+        Pointee(Property(
+            &protos::gen::FtraceEventBundle::event,
+            ElementsAre(Property(&protos::gen::FtraceEvent::print,
+                                 Property(&protos::gen::PrintFtraceEvent::buf,
+                                          "Hello, world!\n")),
+                        Property(&protos::gen::FtraceEvent::print,
+                                 Property(&protos::gen::PrintFtraceEvent::buf,
+                                          "Good afternoon, world!\n")),
+                        Property(&protos::gen::FtraceEvent::print,
+                                 Property(&protos::gen::PrintFtraceEvent::buf,
+                                          "Goodbye, world!\n"))))));
+  }
+
+  {
+    FtraceDataSourceConfig ds_config_with_filter = EmptyConfig();
+    ds_config_with_filter.event_filter.AddEnabledEvent(
+        table->EventToFtraceId(GroupAndName("ftrace", "print")));
+
+    FtraceConfig::PrintFilter conf;
+    auto* rule = conf.add_rules();
+    rule->set_prefix("Good ");
+    rule->set_allow(false);
+    ds_config_with_filter.print_filter =
+        FtracePrintFilterConfig::Create(conf, table);
+    ASSERT_TRUE(ds_config_with_filter.print_filter.has_value());
+
+    BundleProvider bundle_provider(base::kPageSize);
+    size_t evt_bytes = CpuReader::ParsePagePayload(
+        parse_pos, &page_header.value(), table, &ds_config_with_filter,
+        compact_buffer.get(), bundle_provider.writer(), &metadata);
+    ASSERT_GE(evt_bytes, 0u);
+
+    auto bundle = bundle_provider.ParseProto();
+    using ::testing::Pointee;
+    using ::testing::Property;
+    EXPECT_THAT(
+        bundle,
+        Pointee(Property(
+            &protos::gen::FtraceEventBundle::event,
+            ElementsAre(Property(&protos::gen::FtraceEvent::print,
+                                 Property(&protos::gen::PrintFtraceEvent::buf,
+                                          "Hello, world!\n")),
+                        Property(&protos::gen::FtraceEvent::print,
+                                 Property(&protos::gen::PrintFtraceEvent::buf,
+                                          "Goodbye, world!\n"))))));
+  }
+}
+
 // clang-format off
 // # tracer: nop
 // #
@@ -865,6 +944,7 @@
   FtraceDataSourceConfig ds_config{EventFilter{},
                                    EventFilter{},
                                    EnabledCompactSchedConfigForTesting(),
+                                   base::nullopt,
                                    {},
                                    {},
                                    false /* symbolize_ksyms*/};
@@ -2982,4 +3062,5 @@
   ASSERT_TRUE(bundle);
 }
 
+}  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 5039c5a..ff807bc 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -689,14 +689,27 @@
   auto compact_sched =
       CreateCompactSchedConfig(request, table_->compact_sched_format());
 
+  base::Optional<FtracePrintFilterConfig> ftrace_print_filter;
+  if (request.has_print_filter()) {
+    ftrace_print_filter =
+        FtracePrintFilterConfig::Create(request.print_filter(), table_);
+    if (!ftrace_print_filter.has_value()) {
+      if (errors) {
+        errors->failed_ftrace_events.push_back(
+            "ftrace/print (unexpected format for filtering)");
+      }
+    }
+  }
+
   std::vector<std::string> apps(request.atrace_apps());
   std::vector<std::string> categories(request.atrace_categories());
   FtraceConfigId id = ++last_id_;
   ds_configs_.emplace(
       std::piecewise_construct, std::forward_as_tuple(id),
       std::forward_as_tuple(std::move(filter), std::move(syscall_filter),
-                            compact_sched, std::move(apps),
-                            std::move(categories), request.symbolize_ksyms()));
+                            compact_sched, std::move(ftrace_print_filter),
+                            std::move(apps), std::move(categories),
+                            request.symbolize_ksyms()));
   return id;
 }
 
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index 58f1b03..5fe66ae 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -20,10 +20,11 @@
 #include <map>
 #include <set>
 
+#include "perfetto/ext/base/optional.h"
 #include "src/kernel_utils/syscall_table.h"
 #include "src/traced/probes/ftrace/compact_sched.h"
 #include "src/traced/probes/ftrace/ftrace_config_utils.h"
-#include "src/traced/probes/ftrace/ftrace_controller.h"
+#include "src/traced/probes/ftrace/ftrace_print_filter.h"
 #include "src/traced/probes/ftrace/ftrace_procfs.h"
 #include "src/traced/probes/ftrace/proto_translation_table.h"
 
@@ -43,12 +44,14 @@
   FtraceDataSourceConfig(EventFilter _event_filter,
                          EventFilter _syscall_filter,
                          CompactSchedConfig _compact_sched,
+                         base::Optional<FtracePrintFilterConfig> _print_filter,
                          std::vector<std::string> _atrace_apps,
                          std::vector<std::string> _atrace_categories,
                          bool _symbolize_ksyms)
       : event_filter(std::move(_event_filter)),
         syscall_filter(std::move(_syscall_filter)),
         compact_sched(_compact_sched),
+        print_filter(std::move(_print_filter)),
         atrace_apps(std::move(_atrace_apps)),
         atrace_categories(std::move(_atrace_categories)),
         symbolize_ksyms(_symbolize_ksyms) {}
@@ -64,6 +67,10 @@
   // Configuration of the optional compact encoding of scheduling events.
   const CompactSchedConfig compact_sched;
 
+  // Optional configuration that's used to filter "ftrace/print" events based on
+  // the content of their "buf" field.
+  base::Optional<FtracePrintFilterConfig> print_filter;
+
   // Used only in Android for ATRACE_EVENT/os.Trace() userspace annotations.
   std::vector<std::string> atrace_apps;
   std::vector<std::string> atrace_categories;
diff --git a/src/traced/probes/ftrace/ftrace_print_filter.cc b/src/traced/probes/ftrace/ftrace_print_filter.cc
new file mode 100644
index 0000000..13aeff7
--- /dev/null
+++ b/src/traced/probes/ftrace/ftrace_print_filter.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/ftrace/ftrace_print_filter.h"
+
+#include <string.h>
+
+#include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
+#include "src/traced/probes/ftrace/event_info_constants.h"
+
+namespace perfetto {
+namespace {
+using ::perfetto::protos::gen::FtraceConfig;
+
+bool Matches(const std::string& prefix, const char* start, size_t size) {
+  if (prefix.size() > size) {
+    return false;
+  }
+  return strncmp(prefix.c_str(), start, prefix.size()) == 0;
+}
+
+}  // namespace
+
+FtracePrintFilter::FtracePrintFilter(const FtraceConfig::PrintFilter& conf) {
+  rules_.reserve(conf.rules().size());
+  for (const FtraceConfig::PrintFilter::Rule& conf_rule : conf.rules()) {
+    Rule rule;
+    rule.allow = conf_rule.allow();
+    rule.prefix = conf_rule.prefix();
+    rules_.push_back(std::move(rule));
+  }
+}
+
+bool FtracePrintFilter::IsAllowed(const char* start, size_t size) const {
+  for (const Rule& rule : rules_) {
+    if (Matches(rule.prefix, start, size)) {
+      return rule.allow;
+    }
+  }
+  return true;
+}
+
+// static
+base::Optional<FtracePrintFilterConfig> FtracePrintFilterConfig::Create(
+    const protos::gen::FtraceConfig_PrintFilter& config,
+    ProtoTranslationTable* table) {
+  const Event* print_event = table->GetEvent(GroupAndName("ftrace", "print"));
+  if (!print_event) {
+    return base::nullopt;
+  }
+  const Field* buf_field = nullptr;
+  for (const Field& field : print_event->fields) {
+    if (strcmp(field.ftrace_name, "buf") == 0) {
+      buf_field = &field;
+      break;
+    }
+  }
+  if (!buf_field) {
+    return base::nullopt;
+  }
+
+  if (buf_field->strategy != kCStringToString) {
+    return base::nullopt;
+  }
+  FtracePrintFilterConfig ret{FtracePrintFilter{config}};
+  ret.event_id_ = print_event->ftrace_event_id;
+  ret.event_size_ = print_event->size;
+  ret.buf_field_offset_ = buf_field->ftrace_offset;
+  return std::move(ret);
+}
+
+FtracePrintFilterConfig::FtracePrintFilterConfig(FtracePrintFilter filter)
+    : filter_(filter) {}
+
+bool FtracePrintFilterConfig::IsEventInteresting(const uint8_t* start,
+                                                 const uint8_t* end) const {
+  PERFETTO_DCHECK(start < end);
+  const size_t length = static_cast<size_t>(end - start);
+
+  // If the end of the buffer is before the end of the event, give up.
+  if (event_size_ >= length) {
+    PERFETTO_DFATAL("Buffer overflowed.");
+    return true;
+  }
+
+  const uint8_t* field_start = start + buf_field_offset_;
+  return filter_.IsAllowed(reinterpret_cast<const char*>(field_start),
+                           static_cast<size_t>(end - field_start));
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_print_filter.h b/src/traced/probes/ftrace/ftrace_print_filter.h
new file mode 100644
index 0000000..ff06e2e
--- /dev/null
+++ b/src/traced/probes/ftrace/ftrace_print_filter.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACED_PROBES_FTRACE_FTRACE_PRINT_FILTER_H_
+#define SRC_TRACED_PROBES_FTRACE_FTRACE_PRINT_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/optional.h"
+#include "src/traced/probes/ftrace/proto_translation_table.h"
+
+namespace perfetto {
+
+struct Event;
+
+namespace protos {
+namespace gen {
+class FtraceConfig_PrintFilter;
+}  // namespace gen
+}  // namespace protos
+
+class FtracePrintFilter {
+ public:
+  // Builds a filter from a proto config.
+  explicit FtracePrintFilter(const protos::gen::FtraceConfig_PrintFilter&);
+
+  // Returns true if a string is allowed by this filter, false otherwise.
+  // The string begins at `start` and terminates after `size` bytes, or at the
+  // first '\0' byte, whichever comes first.
+  bool IsAllowed(const char* start, size_t size) const;
+
+ private:
+  struct Rule {
+    std::string prefix;
+    bool allow;
+  };
+  std::vector<Rule> rules_;
+};
+
+class FtracePrintFilterConfig {
+ public:
+  static base::Optional<FtracePrintFilterConfig> Create(
+      const protos::gen::FtraceConfig_PrintFilter&,
+      ProtoTranslationTable* table);
+
+  uint32_t event_id() const { return event_id_; }
+
+  // Returns true if the "ftrace/print" event (encoded from `start` to `end`)
+  // should be allowed.
+  //
+  // If the event should be allowed, or **if there was a problem parsing it**
+  // returns true. If the event should be disallowed (i.e. ignored), returns
+  // false.
+  bool IsEventInteresting(const uint8_t* start, const uint8_t* end) const;
+
+ private:
+  explicit FtracePrintFilterConfig(FtracePrintFilter filter);
+  FtracePrintFilter filter_;
+  uint32_t event_id_;
+  uint16_t event_size_;
+  uint16_t buf_field_offset_;
+};
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_FTRACE_FTRACE_PRINT_FILTER_H_
diff --git a/src/traced/probes/ftrace/ftrace_print_filter_unittest.cc b/src/traced/probes/ftrace/ftrace_print_filter_unittest.cc
new file mode 100644
index 0000000..863b56f
--- /dev/null
+++ b/src/traced/probes/ftrace/ftrace_print_filter_unittest.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/ftrace/ftrace_print_filter.h"
+
+#include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace {
+
+using perfetto::protos::gen::FtraceConfig;
+
+TEST(FtracePrintFilterTest, EmptyConfigDefaultAllows) {
+  FtraceConfig::PrintFilter conf;
+  FtracePrintFilter filter(conf);
+
+  EXPECT_TRUE(filter.IsAllowed("word", 4));
+}
+
+TEST(FtracePrintFilterTest, OneRuleMatchesAllows) {
+  FtraceConfig::PrintFilter conf;
+  auto* rule = conf.add_rules();
+  rule->set_prefix("w");
+  rule->set_allow(true);
+  FtracePrintFilter filter(conf);
+
+  EXPECT_TRUE(filter.IsAllowed("word", 4));
+}
+
+TEST(FtracePrintFilterTest, OneRuleMatchesDenies) {
+  FtraceConfig::PrintFilter conf;
+  auto* rule = conf.add_rules();
+  rule->set_prefix("w");
+  rule->set_allow(false);
+  FtracePrintFilter filter(conf);
+
+  EXPECT_FALSE(filter.IsAllowed("word", 4));
+}
+
+TEST(FtracePrintFilterTest, OneRuleMatchesLongSize) {
+  FtraceConfig::PrintFilter conf;
+  auto* rule = conf.add_rules();
+  rule->set_prefix("w");
+  rule->set_allow(false);
+  FtracePrintFilter filter(conf);
+
+  EXPECT_FALSE(filter.IsAllowed("word", 120));
+}
+
+TEST(FtracePrintFilterTest, OneRuleMatchesShortSize) {
+  FtraceConfig::PrintFilter conf;
+  auto* rule = conf.add_rules();
+  rule->set_prefix("w");
+  rule->set_allow(false);
+  FtracePrintFilter filter(conf);
+
+  EXPECT_FALSE(filter.IsAllowed("word", 1));
+}
+
+TEST(FtracePrintFilterTest, OneRuleDoesntMatchLongSize) {
+  FtraceConfig::PrintFilter conf;
+  auto* rule = conf.add_rules();
+  rule->set_prefix("verylongprefix");
+  rule->set_allow(false);
+  FtracePrintFilter filter(conf);
+
+  EXPECT_TRUE(filter.IsAllowed("short", 120));
+}
+
+TEST(FtracePrintFilterTest, OneRuleWildcard) {
+  FtraceConfig::PrintFilter conf;
+  auto* rule = conf.add_rules();
+  rule->set_prefix("");
+  rule->set_allow(false);
+  FtracePrintFilter filter(conf);
+
+  EXPECT_FALSE(filter.IsAllowed("anything", 8));
+}
+
+TEST(FtracePrintFilterTest, TwoRulesMatchFirst) {
+  FtraceConfig::PrintFilter conf;
+  {
+    auto* rule = conf.add_rules();
+    rule->set_prefix("word");
+    rule->set_allow(false);
+  }
+  {
+    auto* rule = conf.add_rules();
+    rule->set_prefix("doesntmatch");
+    rule->set_allow(true);
+  }
+  FtracePrintFilter filter(conf);
+
+  EXPECT_FALSE(filter.IsAllowed("word", 120));
+}
+
+TEST(FtracePrintFilterTest, TwoRulesMatchesSecond) {
+  FtraceConfig::PrintFilter conf;
+  {
+    auto* rule = conf.add_rules();
+    rule->set_prefix("doesntmatch");
+    rule->set_allow(true);
+  }
+  {
+    auto* rule = conf.add_rules();
+    rule->set_prefix("word");
+    rule->set_allow(false);
+  }
+  FtracePrintFilter filter(conf);
+
+  EXPECT_FALSE(filter.IsAllowed("word", 120));
+}
+
+}  // namespace
+}  // namespace perfetto