Merge "Allow embedders to set custom process IDs"
diff --git a/CHANGELOG b/CHANGELOG
index 526a152..8f2d129 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,9 @@
     * Added prebuilts for mac-arm64.
     * Removed merged trace and config protos from Bazel. Embedder should
       instead depend on the non-merged proto targets.
+    * Added FtraceConfig.disable_generic_events. If set, the ftrace data source
+      will not emit events for which it doesn't have a compile-time proto
+      message.
   Trace Processor:
     * Added prebuilts for mac-arm64.
     *
diff --git a/docs/images/cpu-profile-diamond.png b/docs/images/cpu-profile-diamond.png
new file mode 100644
index 0000000..97bd44b
--- /dev/null
+++ b/docs/images/cpu-profile-diamond.png
Binary files differ
diff --git a/docs/images/cpu-profile-flame.png b/docs/images/cpu-profile-flame.png
new file mode 100644
index 0000000..b5da446
--- /dev/null
+++ b/docs/images/cpu-profile-flame.png
Binary files differ
diff --git a/docs/images/enable-profile-flame-graph.png b/docs/images/enable-profile-flame-graph.png
new file mode 100644
index 0000000..642ce73
--- /dev/null
+++ b/docs/images/enable-profile-flame-graph.png
Binary files differ
diff --git a/docs/quickstart/callstack-sampling.md b/docs/quickstart/callstack-sampling.md
new file mode 100644
index 0000000..d2bf266
--- /dev/null
+++ b/docs/quickstart/callstack-sampling.md
@@ -0,0 +1,138 @@
+# Quickstart: Callstack sampling on Android
+
+## Prerequisites
+
+*   [ADB](https://developer.android.com/studio/command-line/adb) installed.
+*   A device running Android R+.
+*   Either a debuggable (`userdebug`/`eng`) Android image, or the apps to be
+    profiled need to be
+    [marked as profileable or debuggable](https://developer.android.com/guide/topics/manifest/profileable-element)
+    in their manifests.
+
+## Capture a CPU profile
+
+### Linux or macOS
+
+Make sure `adb` is installed and in your `PATH`.
+
+```bash
+adb devices -l
+```
+
+If more than one device or emulator is reported you must select one upfront as
+follows:
+
+```bash
+export ANDROID_SERIAL=SER123456
+```
+
+Download `cpu_profile` (if you don't have a Perfetto checkout):
+
+```bash
+curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/cpu_profile
+chmod +x cpu_profile
+```
+
+Then, start profiling. For example, to profile the processes `com.android.foo`
+and `com.android.bar`, use:
+
+```bash
+./cpu_profile -n "com.android.foo,com.android.bar"
+```
+
+By default, profiling runs until manually terminated manually. To set a specific
+duration for recording (e.g. 30 seconds), use:
+
+```bash
+./cpu_profile -n "com.android.foo,com.android.bar" -d 30000
+```
+
+To change how frequently stack samples are recorded (e.g. 120 samples per
+second), set the `-f` argument:
+
+```bash
+./cpu_profile -n "com.android.foo,com.android.bar" -f 120
+```
+
+You can also pass in parts of the names of the processes you want to profile by
+enabling `--partial-matching/-p`. This matches processes that are already
+running when profiling is started. For instance, to profile the processes
+`com.android.foo` and `com.android.bar`, run:
+
+```bash
+./cpu_profile -n "foo,bar" -p
+```
+
+You can also pass in a custom [Perfetto config](/docs/concepts/config.md), which
+overrides all of the options above, using the `-c` argument:
+
+```bash
+./cpu_profile -c "path/to/perfetto.config"
+```
+
+To change where profiles are output, use the `-o` argument:
+
+```bash
+./cpu_profile -n "com.android.foo,com.android.bar" -o "path/to/output/directory"
+```
+
+### Windows
+
+Make sure that the downloaded `adb.exe` is in the `PATH`.
+
+```bash
+set PATH=%PATH%;%USERPROFILE%\Downloads\platform-tools
+
+adb devices -l
+```
+
+If more than one device or emulator is reported you must select one upfront as
+follows:
+
+```bash
+set ANDROID_SERIAL=SER123456
+```
+
+Download the
+[`cpu_profile`](https://raw.githubusercontent.com/google/perfetto/master/tools/cpu_profile)
+script. Then, start profiling. For example, to profile the processes
+`com.android.foo` and `com.android.bar`, use:
+
+```bash
+python3 /path/to/cpu_profile -n "com.android.foo,com.android.bar"
+```
+
+Please see the [Linux or maxOS section](#linux-or-macos) for more examples.
+
+## Symbolization
+
+You may need to symbolize the collected profiles if they are missing symbols.
+See [this](/docs/data-sources/native-heap-profiler#symbolize-your-profile) for
+more details on how to do this.
+
+For example, to profile and symbolize the profiles for the process
+`com.android.foo`, run:
+
+```bash
+PERFETTO_SYMBOLIZER_MODE=index PERFETTO_BINARY_PATH=path/to/directory/with/symbols/ ./cpu_profile -n "com.android.foo"
+```
+
+## View profile
+
+NOTE: Visualizing callstacks in the Perfetto UI is currently disabled behind a
+flag. Please [enable it](/docs/images/enable-profile-flame-graph.png) before
+proceeding further.
+
+Upload the `raw-trace` or `symbolized-trace` file from the output directory to
+the [Perfetto UI](https://ui.perfetto.dev) and click on one of the diamond
+markers in the UI track named "Perf Samples" for the processes that you selected
+for profiling. Each diamond marker represents a profile with stack samples
+recorded from the beginning of the trace up until that diamond marker on the
+timeline.
+
+![Profile Diamond](/docs/images/cpu-profile-diamond.png)
+![Native Flamegraph](/docs/images/cpu-profile-flame.png)
+
+`cpu_profile` will also write separate profiles for each process that it
+profiled in the output directory, and those can be visualized using
+[`pprof`](https://github.com/google/pprof).
diff --git a/docs/toc.md b/docs/toc.md
index 6ddd809..0142029 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -6,6 +6,7 @@
   * [SQL analysis and metrics](quickstart/trace-analysis.md)
   * [Trace conversion](quickstart/traceconv.md)
   * [Heap profiling](quickstart/heap-profiling.md)
+  * [Callstack sampling on Android](quickstart/callstack-sampling.md)
 
 * [Case studies](#)
   * [Android boot tracing](case-studies/android-boot-tracing.md)
diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto
index 7a38d74..917ac68 100644
--- a/protos/perfetto/config/ftrace/ftrace_config.proto
+++ b/protos/perfetto/config/ftrace/ftrace_config.proto
@@ -64,4 +64,13 @@
   // TODO(kaleshsingh): implement the logic behind this. Right now this flag
   // does nothing.
   optional bool throttle_rss_stat = 15;
+
+  // If true, avoid enabling events that aren't statically known by
+  // traced_probes. Otherwise, the default is to emit such events as
+  // GenericFtraceEvent protos.
+  // Prefer to keep this flag at its default. This was added for Android
+  // tracing, where atrace categories and/or atrace HAL requested events can
+  // expand to events that aren't of interest to the tracing user.
+  // Introduced in: Android T.
+  optional bool disable_generic_events = 16;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index b36ee1d..f20da0d 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -484,6 +484,15 @@
   // TODO(kaleshsingh): implement the logic behind this. Right now this flag
   // does nothing.
   optional bool throttle_rss_stat = 15;
+
+  // If true, avoid enabling events that aren't statically known by
+  // traced_probes. Otherwise, the default is to emit such events as
+  // GenericFtraceEvent protos.
+  // Prefer to keep this flag at its default. This was added for Android
+  // tracing, where atrace categories and/or atrace HAL requested events can
+  // expand to events that aren't of interest to the tracing user.
+  // Introduced in: Android T.
+  optional bool disable_generic_events = 16;
 }
 
 // End of protos/perfetto/config/ftrace/ftrace_config.proto
diff --git a/protos/perfetto/trace/ftrace/ftrace_stats.proto b/protos/perfetto/trace/ftrace/ftrace_stats.proto
index 11b7cbc..9f26fa3 100644
--- a/protos/perfetto/trace/ftrace/ftrace_stats.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -88,6 +88,8 @@
   // Ftrace events requested by the config but not present on device.
   repeated string unknown_ftrace_events = 6;
 
-  // Ftrace events requested by the config, present, which we failed to enable.
+  // Ftrace events requested by the config and present on device, but which we
+  // failed to enable due to permissions, or due to a conflicting option
+  // (currently FtraceConfig.disable_generic_events).
   repeated string failed_ftrace_events = 7;
 }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index ea46bd2..406a6cc 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -484,6 +484,15 @@
   // TODO(kaleshsingh): implement the logic behind this. Right now this flag
   // does nothing.
   optional bool throttle_rss_stat = 15;
+
+  // If true, avoid enabling events that aren't statically known by
+  // traced_probes. Otherwise, the default is to emit such events as
+  // GenericFtraceEvent protos.
+  // Prefer to keep this flag at its default. This was added for Android
+  // tracing, where atrace categories and/or atrace HAL requested events can
+  // expand to events that aren't of interest to the tracing user.
+  // Introduced in: Android T.
+  optional bool disable_generic_events = 16;
 }
 
 // End of protos/perfetto/config/ftrace/ftrace_config.proto
@@ -6524,7 +6533,9 @@
   // Ftrace events requested by the config but not present on device.
   repeated string unknown_ftrace_events = 6;
 
-  // Ftrace events requested by the config, present, which we failed to enable.
+  // Ftrace events requested by the config and present on device, but which we
+  // failed to enable due to permissions, or due to a conflicting option
+  // (currently FtraceConfig.disable_generic_events).
   repeated string failed_ftrace_events = 7;
 }
 
diff --git a/src/trace_processor/importers/common/flow_tracker.cc b/src/trace_processor/importers/common/flow_tracker.cc
index 6e7dd2f..e9ba05b 100644
--- a/src/trace_processor/importers/common/flow_tracker.cc
+++ b/src/trace_processor/importers/common/flow_tracker.cc
@@ -46,7 +46,11 @@
     context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
     return;
   }
-  auto it_and_ins = flow_to_slice_map_.Insert(flow_id, open_slice_id.value());
+  Begin(open_slice_id.value(), flow_id);
+}
+
+void FlowTracker::Begin(SliceId slice_id, FlowId flow_id) {
+  auto it_and_ins = flow_to_slice_map_.Insert(flow_id, slice_id);
   if (!it_and_ins.second) {
     context_->storage->IncrementStats(stats::flow_duplicate_id);
     return;
@@ -60,14 +64,18 @@
     context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
     return;
   }
+  Step(open_slice_id.value(), flow_id);
+}
+
+void FlowTracker::Step(SliceId slice_id, FlowId flow_id) {
   auto* it = flow_to_slice_map_.Find(flow_id);
   if (!it) {
     context_->storage->IncrementStats(stats::flow_step_without_start);
     return;
   }
   SliceId slice_out_id = *it;
-  InsertFlow(flow_id, slice_out_id, open_slice_id.value());
-  *it = open_slice_id.value();
+  InsertFlow(flow_id, slice_out_id, slice_id);
+  *it = slice_id;
 }
 
 void FlowTracker::End(TrackId track_id,
@@ -84,6 +92,10 @@
     context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
     return;
   }
+  End(open_slice_id.value(), flow_id, close_flow);
+}
+
+void FlowTracker::End(SliceId slice_id, FlowId flow_id, bool close_flow) {
   auto* it = flow_to_slice_map_.Find(flow_id);
   if (!it) {
     context_->storage->IncrementStats(stats::flow_end_without_start);
@@ -92,7 +104,7 @@
   SliceId slice_out_id = *it;
   if (close_flow)
     flow_to_slice_map_.Erase(flow_id);
-  InsertFlow(flow_id, slice_out_id, open_slice_id.value());
+  InsertFlow(flow_id, slice_out_id, slice_id);
 }
 
 bool FlowTracker::IsActive(FlowId flow_id) const {
diff --git a/src/trace_processor/importers/common/flow_tracker.h b/src/trace_processor/importers/common/flow_tracker.h
index 5def9b6..1e635d8 100644
--- a/src/trace_processor/importers/common/flow_tracker.h
+++ b/src/trace_processor/importers/common/flow_tracker.h
@@ -32,14 +32,23 @@
 class FlowTracker {
  public:
   explicit FlowTracker(TraceProcessorContext*);
-  virtual ~FlowTracker();
+  ~FlowTracker();
 
   void InsertFlow(SliceId slice_out_id, SliceId slice_in_id);
 
-  // These methods assume you have created a FlowId via GetFlowIdForV1Event.
-  // If you don't have a v1 event you should use the InsertFlow method above.
-  virtual void Begin(TrackId track_id, FlowId flow_id);
-  virtual void Step(TrackId track_id, FlowId flow_id);
+  // These methods track flow ids associated with slices and create flows as
+  // needed.
+  // If you don't have flow ids associated with slices, you should use the
+  // InsertFlow method above.
+  void Begin(SliceId slice_id, FlowId flow_id);
+  void Step(SliceId slice_id, FlowId flow_id);
+  void End(SliceId track_id, FlowId flow_id, bool close_flow);
+
+  // These methods assume you have created a FlowId via GetFlowIdForV1Event and
+  // tie the flow id to the currently open slice on a given track. If you don't
+  // have a v1 event you should use the methods above.
+  void Begin(TrackId track_id, FlowId flow_id);
+  void Step(TrackId track_id, FlowId flow_id);
 
   // When |bind_enclosing_slice| is true we will connect the flow to the
   // currently open slice on the track, when false we will connect the flow to
@@ -47,10 +56,10 @@
   // When |close_flow| is true it will mark this as the singular end of the
   // flow, however if there are multiple end points this should be set to
   // false. Both parameters are only needed for v1 flow events support
-  virtual void End(TrackId track_id,
-                   FlowId flow_id,
-                   bool bind_enclosing_slice,
-                   bool close_flow);
+  void End(TrackId track_id,
+           FlowId flow_id,
+           bool bind_enclosing_slice,
+           bool close_flow);
 
   bool IsActive(FlowId flow_id) const;
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 581ca37..304a481 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -217,19 +217,6 @@
                                        std::function<SliceId()> inserter));
 };
 
-class MockFlowTracker : public FlowTracker {
- public:
-  MockFlowTracker(TraceProcessorContext* context) : FlowTracker(context) {}
-
-  MOCK_METHOD2(Begin, void(TrackId track_id, FlowId flow_id));
-  MOCK_METHOD2(Step, void(TrackId track_id, FlowId flow_id));
-  MOCK_METHOD4(End,
-               void(TrackId track_id,
-                    FlowId flow_id,
-                    bool bind_enclosing,
-                    bool close_flow));
-};
-
 class ProtoTraceParserTest : public ::testing::Test {
  public:
   ProtoTraceParserTest() {
@@ -249,10 +236,9 @@
     context_.process_tracker.reset(process_);
     slice_ = new MockSliceTracker(&context_);
     context_.slice_tracker.reset(slice_);
-    flow_ = new MockFlowTracker(&context_);
-    context_.flow_tracker.reset(flow_);
     clock_ = new ClockTracker(&context_);
     context_.clock_tracker.reset(clock_);
+    context_.flow_tracker.reset(new FlowTracker(&context_));
     context_.sorter.reset(new TraceSorter(&context_, CreateParser(),
                                           TraceSorter::SortingMode::kFullSort));
     context_.descriptor_pool_.reset(new DescriptorPool());
@@ -305,7 +291,6 @@
   MockSchedEventTracker* sched_;
   MockProcessTracker* process_;
   MockSliceTracker* slice_;
-  MockFlowTracker* flow_;
   ClockTracker* clock_;
   TraceStorage* storage_;
 };
@@ -1135,12 +1120,6 @@
       .WillOnce(DoAll(IgnoreResult(InvokeArgument<3>()),
                       InvokeArgument<2>(&inserter), Return(SliceId(0u))));
 
-  EXPECT_CALL(*flow_, Begin(_, _));
-
-  EXPECT_CALL(*flow_, Step(_, _));
-
-  EXPECT_CALL(*flow_, End(_, _, false, false));
-
   EXPECT_CALL(*event_, PushCounter(1010000, testing::DoubleEq(2005000),
                                    thread_time_track));
   EXPECT_CALL(*event_, PushCounter(1010000, testing::DoubleEq(3020),
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 136c5de..f264d27 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -653,7 +653,7 @@
         [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
 
     if (opt_slice_id.has_value()) {
-      MaybeParseFlowEvents();
+      MaybeParseFlowEvents(opt_slice_id.value());
     }
 
     return util::OkStatus();
@@ -684,6 +684,7 @@
         thread_slices->mutable_thread_instruction_delta()->Set(
             *maybe_row, *event_data_->thread_instruction_count - *tic);
       }
+      MaybeParseFlowEvents(opt_slice_id.value());
     }
 
     return util::OkStatus();
@@ -713,7 +714,7 @@
         [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
 
     if (opt_slice_id.has_value()) {
-      MaybeParseFlowEvents();
+      MaybeParseFlowEvents(opt_slice_id.value());
     }
 
     return util::OkStatus();
@@ -754,16 +755,16 @@
     return util::OkStatus();
   }
 
-  void MaybeParseTrackEventFlows() {
+  void MaybeParseTrackEventFlows(SliceId slice_id) {
     if (event_.has_flow_ids()) {
       auto it = event_.flow_ids();
       for (; it; ++it) {
         FlowId flow_id = *it;
         if (!context_->flow_tracker->IsActive(flow_id)) {
-          context_->flow_tracker->Begin(track_id_, flow_id);
+          context_->flow_tracker->Begin(slice_id, flow_id);
           continue;
         }
-        context_->flow_tracker->Step(track_id_, flow_id);
+        context_->flow_tracker->Step(slice_id, flow_id);
       }
     }
     if (event_.has_terminating_flow_ids()) {
@@ -775,14 +776,13 @@
           // active already.
           continue;
         }
-        context_->flow_tracker->End(track_id_, flow_id,
-                                    /* bind_enclosing_slice = */ true,
+        context_->flow_tracker->End(slice_id, flow_id,
                                     /* close_flow = */ true);
       }
     }
   }
 
-  void MaybeParseFlowEventV2() {
+  void MaybeParseFlowEventV2(SliceId slice_id) {
     if (!legacy_event_.has_bind_id()) {
       return;
     }
@@ -794,14 +794,13 @@
     auto bind_id = legacy_event_.bind_id();
     switch (legacy_event_.flow_direction()) {
       case LegacyEvent::FLOW_OUT:
-        context_->flow_tracker->Begin(track_id_, bind_id);
+        context_->flow_tracker->Begin(slice_id, bind_id);
         break;
       case LegacyEvent::FLOW_INOUT:
-        context_->flow_tracker->Step(track_id_, bind_id);
+        context_->flow_tracker->Step(slice_id, bind_id);
         break;
       case LegacyEvent::FLOW_IN:
-        context_->flow_tracker->End(track_id_, bind_id,
-                                    /* bind_enclosing_slice = */ true,
+        context_->flow_tracker->End(slice_id, bind_id,
                                     /* close_flow = */ false);
         break;
       default:
@@ -809,9 +808,9 @@
     }
   }
 
-  void MaybeParseFlowEvents() {
-    MaybeParseFlowEventV2();
-    MaybeParseTrackEventFlows();
+  void MaybeParseFlowEvents(SliceId slice_id) {
+    MaybeParseFlowEventV2(slice_id);
+    MaybeParseTrackEventFlows(slice_id);
   }
 
   util::Status ParseThreadInstantEvent(char phase) {
@@ -850,7 +849,7 @@
     if (!opt_slice_id.has_value()) {
       return util::OkStatus();
     }
-    MaybeParseFlowEvents();
+    MaybeParseFlowEvents(opt_slice_id.value());
     return util::OkStatus();
   }
 
@@ -872,7 +871,7 @@
     if (!opt_slice_id.has_value()) {
       return util::OkStatus();
     }
-    MaybeParseFlowEvents();
+    MaybeParseFlowEvents(opt_slice_id.value());
     // For the time being, we only create vtrack slice rows if we need to
     // store thread timestamps/counters.
     if (legacy_event_.use_async_tts()) {
@@ -895,7 +894,10 @@
     auto opt_slice_id = context_->slice_tracker->End(
         ts_, track_id_, category_id_, name_id_,
         [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
-    if (legacy_event_.use_async_tts() && opt_slice_id.has_value()) {
+    if (!opt_slice_id.has_value())
+      return util::OkStatus();
+    MaybeParseFlowEvents(opt_slice_id.value());
+    if (legacy_event_.use_async_tts()) {
       auto* vtrack_slices = storage_->mutable_virtual_track_slices();
       int64_t tts =
           event_data_->thread_timestamp ? *event_data_->thread_timestamp : 0;
@@ -940,7 +942,7 @@
     if (!opt_slice_id.has_value()) {
       return util::OkStatus();
     }
-    MaybeParseFlowEvents();
+    MaybeParseFlowEvents(opt_slice_id.value());
     if (legacy_event_.use_async_tts()) {
       auto* vtrack_slices = storage_->mutable_virtual_track_slices();
       PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 7867ff6..16e8e3d 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -674,7 +674,6 @@
     for (const Field& field : info.fields) {
       auto generic_field = nested->BeginNestedMessage<protozero::Message>(
           GenericFtraceEvent::kFieldFieldNumber);
-      // TODO(hjd): Avoid outputting field names every time.
       generic_field->AppendString(GenericFtraceEvent::Field::kNameFieldNumber,
                                   field.ftrace_name);
       success &= ParseField(field, start, end, table, generic_field, metadata);
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index ba48c44..6d3a546 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -26,11 +26,12 @@
 
 #include "perfetto/base/compiler.h"
 #include "perfetto/ext/base/utils.h"
-#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
 #include "src/traced/probes/ftrace/atrace_wrapper.h"
 #include "src/traced/probes/ftrace/compact_sched.h"
 #include "src/traced/probes/ftrace/ftrace_stats.h"
 
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+
 namespace perfetto {
 namespace {
 
@@ -544,10 +545,20 @@
         errors->unknown_ftrace_events.push_back(group_and_name.ToString());
       continue;
     }
+    // Niche option to skip events that are in the config, but don't have a
+    // dedicated proto for the event in perfetto. Otherwise such events will be
+    // encoded as GenericFtraceEvent.
+    if (request.disable_generic_events() &&
+        event->proto_field_id ==
+            protos::pbzero::FtraceEvent::kGenericFieldNumber) {
+      if (errors)
+        errors->failed_ftrace_events.push_back(group_and_name.ToString());
+      continue;
+    }
     // Note: ftrace events are always implicitly enabled (and don't have an
     // "enable" file). So they aren't tracked by the central event filter (but
-    // still need to be added to the per data source event filter to retain the
-    // events during parsing).
+    // still need to be added to the per data source event filter to retain
+    // the events during parsing).
     if (current_state_.ftrace_events.IsEventEnabled(event->ftrace_event_id) ||
         std::string("ftrace") == event->group) {
       filter.AddEnabledEvent(event->ftrace_event_id);
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index af01bd9..4012763 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -37,10 +37,15 @@
 using testing::NiceMock;
 using testing::Not;
 using testing::Return;
+using testing::UnorderedElementsAre;
 
 namespace perfetto {
 namespace {
 
+constexpr int kFakeSchedSwitchEventId = 1;
+constexpr int kCgroupMkdirEventId = 12;
+constexpr int kFakePrintEventId = 20;
+
 class MockFtraceProcfs : public FtraceProcfs {
  public:
   MockFtraceProcfs() : FtraceProcfs("/root/") {
@@ -60,6 +65,9 @@
   MOCK_CONST_METHOD0(NumberOfCpus, size_t());
   MOCK_CONST_METHOD1(GetEventNamesForGroup,
                      const std::set<std::string>(const std::string& path));
+  MOCK_CONST_METHOD2(ReadEventFormat,
+                     std::string(const std::string& group,
+                                 const std::string& name));
 };
 
 struct MockRunAtrace {
@@ -115,17 +123,13 @@
             InvalidCompactSchedEventFormatForTesting()));
   }
 
-  static constexpr int kFakeSchedSwitchEventId = 1;
-  static constexpr int kCgroupMkdirEventId = 12;
-  static constexpr int kFakePrintEventId = 20;
-
   std::unique_ptr<ProtoTranslationTable> CreateFakeTable(
       CompactSchedEventFormat compact_format =
           InvalidCompactSchedEventFormatForTesting()) {
     std::vector<Field> common_fields;
     std::vector<Event> events;
     {
-      Event event;
+      Event event = {};
       event.name = "sched_switch";
       event.group = "sched";
       event.ftrace_event_id = kFakeSchedSwitchEventId;
@@ -133,7 +137,7 @@
     }
 
     {
-      Event event;
+      Event event = {};
       event.name = "sched_wakeup";
       event.group = "sched";
       event.ftrace_event_id = 10;
@@ -141,7 +145,7 @@
     }
 
     {
-      Event event;
+      Event event = {};
       event.name = "sched_new";
       event.group = "sched";
       event.ftrace_event_id = 11;
@@ -149,7 +153,7 @@
     }
 
     {
-      Event event;
+      Event event = {};
       event.name = "cgroup_mkdir";
       event.group = "cgroup";
       event.ftrace_event_id = kCgroupMkdirEventId;
@@ -157,7 +161,7 @@
     }
 
     {
-      Event event;
+      Event event = {};
       event.name = "mm_vmscan_direct_reclaim_begin";
       event.group = "vmscan";
       event.ftrace_event_id = 13;
@@ -165,7 +169,7 @@
     }
 
     {
-      Event event;
+      Event event = {};
       event.name = "lowmemory_kill";
       event.group = "lowmemorykiller";
       event.ftrace_event_id = 14;
@@ -173,7 +177,7 @@
     }
 
     {
-      Event event;
+      Event event = {};
       event.name = "print";
       event.group = "ftrace";
       event.ftrace_event_id = kFakePrintEventId;
@@ -430,14 +434,12 @@
 
   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
   ASSERT_TRUE(ds_config);
-  ASSERT_THAT(
-      ds_config->event_filter.GetEnabledEvents(),
-      ElementsAreArray({FtraceConfigMuxerTest::kFakeSchedSwitchEventId}));
+  ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
+              ElementsAreArray({kFakeSchedSwitchEventId}));
 
   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
-  ASSERT_THAT(
-      central_filter->GetEnabledEvents(),
-      ElementsAreArray({FtraceConfigMuxerTest::kFakeSchedSwitchEventId}));
+  ASSERT_THAT(central_filter->GetEnabledEvents(),
+              ElementsAreArray({kFakeSchedSwitchEventId}));
 
   ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace));
   EXPECT_CALL(ftrace, NumberOfCpus()).Times(AnyNumber());
@@ -492,13 +494,13 @@
   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
   ASSERT_TRUE(ds_config);
   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+              Contains(kFakeSchedSwitchEventId));
   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakePrintEventId));
+              Contains(kFakePrintEventId));
 
   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
   EXPECT_THAT(central_filter->GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+              Contains(kFakeSchedSwitchEventId));
 
   EXPECT_CALL(
       atrace,
@@ -535,7 +537,7 @@
   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
   ASSERT_TRUE(ds_config);
   ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakePrintEventId));
+              Contains(kFakePrintEventId));
 
   EXPECT_CALL(
       atrace,
@@ -915,15 +917,15 @@
   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
   ASSERT_TRUE(ds_config);
   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+              Contains(kFakeSchedSwitchEventId));
   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kCgroupMkdirEventId));
+              Contains(kCgroupMkdirEventId));
 
   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
   EXPECT_THAT(central_filter->GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+              Contains(kFakeSchedSwitchEventId));
   EXPECT_THAT(central_filter->GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kCgroupMkdirEventId));
+              Contains(kCgroupMkdirEventId));
 
   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "4"));
@@ -964,7 +966,7 @@
     const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
     ASSERT_TRUE(ds_config);
     EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-                Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+                Contains(kFakeSchedSwitchEventId));
     EXPECT_TRUE(ds_config->compact_sched.enabled);
   }
   {
@@ -973,7 +975,7 @@
     const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
     ASSERT_TRUE(ds_config);
     EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-                Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+                Contains(kFakeSchedSwitchEventId));
     EXPECT_FALSE(ds_config->compact_sched.enabled);
   }
 }
@@ -995,9 +997,55 @@
   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
   ASSERT_TRUE(ds_config);
   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
-              Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
+              Contains(kFakeSchedSwitchEventId));
   EXPECT_FALSE(ds_config->compact_sched.enabled);
 }
 
+TEST_F(FtraceConfigMuxerTest, SkipGenericEventsOption) {
+  NiceMock<MockFtraceProcfs> ftrace;
+  FtraceConfigMuxer model(&ftrace, table_.get(), {});
+
+  static constexpr int kFtraceGenericEventId = 42;
+  ON_CALL(table_procfs_, ReadEventFormat("sched", "generic"))
+      .WillByDefault(Return(R"(name: generic
+ID: 42
+format:
+	field:int common_pid;	offset:0;	size:4;	signed:1;
+
+	field:u32 field_a;	offset:4;	size:4;	signed:0;
+	field:int field_b;	offset:8;	size:4;	signed:1;
+
+print fmt: "unused")"));
+
+  // Data source asking for one known and one generic event.
+  FtraceConfig config_default =
+      CreateFtraceConfig({"sched/sched_switch", "sched/generic"});
+
+  // As above, but with an option to suppress generic events.
+  FtraceConfig config_with_disable =
+      CreateFtraceConfig({"sched/sched_switch", "sched/generic"});
+  config_with_disable.set_disable_generic_events(true);
+
+  {
+    FtraceConfigId id = model.SetupConfig(config_default);
+    ASSERT_TRUE(id);
+    const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
+    ASSERT_TRUE(ds_config);
+    // Both events enabled for the data source by default.
+    EXPECT_THAT(
+        ds_config->event_filter.GetEnabledEvents(),
+        UnorderedElementsAre(kFakeSchedSwitchEventId, kFtraceGenericEventId));
+  }
+  {
+    FtraceConfigId id = model.SetupConfig(config_with_disable);
+    ASSERT_TRUE(id);
+    const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
+    ASSERT_TRUE(ds_config);
+    // Only the statically known event is enabled.
+    EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
+                UnorderedElementsAre(kFakeSchedSwitchEventId));
+  }
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/test/trace_processor/track_event/flow_events_proto_v1.textproto b/test/trace_processor/track_event/flow_events_proto_v1.textproto
index 19c1265..1ed6906 100644
--- a/test/trace_processor/track_event/flow_events_proto_v1.textproto
+++ b/test/trace_processor/track_event/flow_events_proto_v1.textproto
@@ -139,4 +139,4 @@
       unscoped_id: 331
     }
   }
-}
\ No newline at end of file
+}
diff --git a/test/trace_processor/track_event/flow_events_track_event.out b/test/trace_processor/track_event/flow_events_track_event.out
index 895f2a1..d44237e 100644
--- a/test/trace_processor/track_event/flow_events_track_event.out
+++ b/test/trace_processor/track_event/flow_events_track_event.out
@@ -4,4 +4,5 @@
 "FlowSlice1Start2Start","FlowSlice2End"
 "FlowSlice3Begin","FlowSlice3End4Begin"
 "FlowSlice3End4Begin","FlowSlice4Step"
-"FlowSlice4Step","FlowSlice4End"
+"FlowSlice4Step","FlowSlice4Step2_FlowIdOnAsyncEndEvent"
+"FlowSlice4Step2_FlowIdOnAsyncEndEvent","FlowSlice4End"
diff --git a/test/trace_processor/track_event/flow_events_track_event.textproto b/test/trace_processor/track_event/flow_events_track_event.textproto
index af20150..60d9336 100644
--- a/test/trace_processor/track_event/flow_events_track_event.textproto
+++ b/test/trace_processor/track_event/flow_events_track_event.textproto
@@ -28,8 +28,8 @@
   track_event {
     name: "FlowSlice1Start"
     categories: "test"
-    track_uuid: 1,
-    flow_ids: 1,
+    track_uuid: 1
+    flow_ids: 1
     legacy_event {
       duration_us: 10
       phase: 88 # 'X'
@@ -42,8 +42,8 @@
   track_event {
     name: "FlowSlice1End"
     categories: "test"
-    track_uuid: 2,
-    terminating_flow_ids: 1,
+    track_uuid: 2
+    terminating_flow_ids: 1
     legacy_event {
       duration_us: 10
       phase: 88 # 'X'
@@ -56,9 +56,9 @@
   track_event {
     name: "FlowSlice1Start2Start"
     categories: "test"
-    track_uuid: 1,
-    flow_ids: 1,
-    flow_ids: 2,
+    track_uuid: 1
+    flow_ids: 1
+    flow_ids: 2
     legacy_event {
       duration_us: 10
       phase: 88 # 'X'
@@ -71,8 +71,8 @@
   track_event {
     name: "FlowSlice1End"
     categories: "test"
-    track_uuid: 2,
-    flow_ids: 1,
+    track_uuid: 2
+    flow_ids: 1
     legacy_event {
       duration_us: 10
       phase: 88 # 'X'
@@ -86,8 +86,8 @@
   track_event {
     name: "FlowSlice3Begin"
     categories: "test"
-    track_uuid: 2,
-    flow_ids: 3,
+    track_uuid: 2
+    flow_ids: 3
     legacy_event {
       phase: 73 # 'I'
     }
@@ -99,8 +99,8 @@
   track_event {
     name: "FlowSlice2End"
     categories: "test"
-    track_uuid: 2,
-    flow_ids: 2,
+    track_uuid: 2
+    flow_ids: 2
     legacy_event {
       duration_us: 10
       phase: 88 # 'X'
@@ -121,9 +121,9 @@
   track_event {
     name: "FlowSlice3End4Begin"
     categories: "test"
-    track_uuid: 11,
-    terminating_flow_ids: 3,
-    flow_ids: 4,
+    track_uuid: 11
+    terminating_flow_ids: 3
+    flow_ids: 4
     type: 1, # 'TYPE_SLICE_BEGIN'
   }
 }
@@ -133,8 +133,8 @@
   track_event {
     name: "FlowSlice4Step"
     categories: "test"
-    track_uuid: 11,
-    flow_ids: 4,
+    track_uuid: 11
+    flow_ids: 4
     type: 3, # 'TYPE_SLICE_INSTANT'
   }
 }
@@ -144,8 +144,50 @@
   track_event {
     name: "FlowSlice3End4Begin"
     categories: "test"
-    track_uuid: 11,
-    type: 2, # 'TYPE_SLICE_END'
+    track_uuid: 11
+    type: 2 # 'TYPE_SLICE_END'
+  }
+}
+packet {
+  timestamp: 63000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice4Step2_FlowIdOnAsyncEndEvent"
+    categories: "test"
+    track_uuid: 11
+    type: 1 # 'TYPE_SLICE_BEGIN'
+  }
+}
+packet {
+  timestamp: 64000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice4Step2_FlowIdOnAsyncEndEvent"
+    categories: "test"
+    track_uuid: 11
+    flow_ids: 4
+    type: 2 # 'TYPE_SLICE_END'
+  }
+}
+packet {
+  timestamp: 65000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice4Step2_FlowIdOnEndEvent"
+    categories: "test"
+    track_uuid: 2
+    type: 1 # 'TYPE_SLICE_BEGIN'
+  }
+}
+packet {
+  timestamp: 66000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice4Step2_FlowIdOnEndEvent"
+    categories: "test"
+    track_uuid: 2
+    flow_ids: 4
+    type: 2 # 'TYPE_SLICE_END'
   }
 }
 packet {
@@ -161,13 +203,13 @@
   }
 }
 packet {
-  timestamp: 65000
+  timestamp: 67000
   trusted_packet_sequence_id: 1
   track_event {
     name: "FlowSlice4End"
     categories: "test"
-    track_uuid: 13,
-    terminating_flow_ids: 4,
+    track_uuid: 13
+    terminating_flow_ids: 4
     legacy_event {
       phase: 105  # 'i'
       instant_event_scope: 2  # SCOPE_PROCESS