Merge "ui: Set stable to current canary" into main
diff --git a/include/perfetto/public/protos/BUILD.gn b/include/perfetto/public/protos/BUILD.gn
index cd583ad..f134d35 100644
--- a/include/perfetto/public/protos/BUILD.gn
+++ b/include/perfetto/public/protos/BUILD.gn
@@ -15,6 +15,7 @@
 source_set("protos") {
   sources = [
     "common/builtin_clock.pzc.h",
+    "common/data_source_descriptor.pzc.h",
     "config/data_source_config.pzc.h",
     "config/trace_config.pzc.h",
     "config/track_event/track_event_config.pzc.h",
diff --git a/include/perfetto/public/protos/common/builtin_clock.pzc.h b/include/perfetto/public/protos/common/builtin_clock.pzc.h
index 2800419..51218e4 100644
--- a/include/perfetto/public/protos/common/builtin_clock.pzc.h
+++ b/include/perfetto/public/protos/common/builtin_clock.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_COMMON_BUILTIN_CLOCK_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_COMMON_BUILTIN_CLOCK_PZC_H_
 
diff --git a/include/perfetto/public/protos/config/data_source_config.pzc.h b/include/perfetto/public/protos/config/data_source_config.pzc.h
index cd75632..718a3bc 100644
--- a/include/perfetto/public/protos/config/data_source_config.pzc.h
+++ b/include/perfetto/public/protos/config/data_source_config.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_DATA_SOURCE_CONFIG_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_DATA_SOURCE_CONFIG_PZC_H_
 
@@ -26,6 +29,7 @@
 PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidLogConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidPolledStateConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidPowerConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidSdkSyspropGuardConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidSystemPropertyConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_FtraceConfig);
@@ -39,6 +43,8 @@
 PERFETTO_PB_MSG_DECL(perfetto_protos_PerfEventConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessStatsConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_StatsdTracingConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_SurfaceFlingerLayersConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_SurfaceFlingerTransactionsConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_SysStatsConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_SystemInfoConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TestConfig);
@@ -199,6 +205,21 @@
                   network_packet_trace_config,
                   120);
 PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+                  MSG,
+                  perfetto_protos_SurfaceFlingerLayersConfig,
+                  surfaceflinger_layers_config,
+                  121);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+                  MSG,
+                  perfetto_protos_SurfaceFlingerTransactionsConfig,
+                  surfaceflinger_transactions_config,
+                  123);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+                  MSG,
+                  perfetto_protos_AndroidSdkSyspropGuardConfig,
+                  android_sdk_sysprop_guard_config,
+                  124);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
                   STRING,
                   const char*,
                   legacy_config,
diff --git a/include/perfetto/public/protos/config/trace_config.pzc.h b/include/perfetto/public/protos/config/trace_config.pzc.h
index cd14242..a2b8432 100644
--- a/include/perfetto/public/protos/config/trace_config.pzc.h
+++ b/include/perfetto/public/protos/config/trace_config.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_TRACE_CONFIG_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_TRACE_CONFIG_PZC_H_
 
@@ -21,7 +24,6 @@
 #include <stdint.h>
 
 #include "perfetto/public/pb_macros.h"
-
 #include "perfetto/public/protos/common/builtin_clock.pzc.h"
 
 PERFETTO_PB_MSG_DECL(perfetto_protos_DataSourceConfig);
@@ -36,6 +38,8 @@
 PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_ProducerConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_StatsdMetadata);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TraceFilter);
+PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TraceFilter_StringFilterChain);
+PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TraceFilter_StringFilterRule);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TriggerConfig);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TriggerConfig_Trigger);
 
@@ -64,6 +68,20 @@
                                   STATSD_LOGGING_DISABLED) = 2,
 };
 
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TraceConfig_TraceFilter,
+                        StringFilterPolicy){
+    PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
+                                  SFP_UNSPECIFIED) = 0,
+    PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
+                                  SFP_MATCH_REDACT_GROUPS) = 1,
+    PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
+                                  SFP_ATRACE_MATCH_REDACT_GROUPS) = 2,
+    PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
+                                  SFP_MATCH_BREAK) = 3,
+    PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
+                                  SFP_ATRACE_MATCH_BREAK) = 4,
+};
+
 PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TraceConfig_TriggerConfig, TriggerMode){
     PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TriggerConfig,
                                   UNSPECIFIED) = 0,
@@ -71,6 +89,8 @@
                                   START_TRACING) = 1,
     PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TriggerConfig,
                                   STOP_TRACING) = 2,
+    PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TriggerConfig,
+                                  CLONE_SNAPSHOT) = 4,
 };
 
 PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TraceConfig_BufferConfig, FillPolicy){
@@ -214,6 +234,11 @@
                   compression_type,
                   24);
 PERFETTO_PB_FIELD(perfetto_protos_TraceConfig,
+                  VARINT,
+                  bool,
+                  compress_from_cli,
+                  37);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig,
                   MSG,
                   perfetto_protos_TraceConfig_IncidentReportConfig,
                   incident_report_config,
@@ -289,6 +314,41 @@
                   const char*,
                   bytecode,
                   1);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TraceFilter,
+                  STRING,
+                  const char*,
+                  bytecode_v2,
+                  2);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TraceFilter,
+                  MSG,
+                  perfetto_protos_TraceConfig_TraceFilter_StringFilterChain,
+                  string_filter_chain,
+                  3);
+
+PERFETTO_PB_MSG(perfetto_protos_TraceConfig_TraceFilter_StringFilterChain);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TraceFilter_StringFilterChain,
+                  MSG,
+                  perfetto_protos_TraceConfig_TraceFilter_StringFilterRule,
+                  rules,
+                  1);
+
+PERFETTO_PB_MSG(perfetto_protos_TraceConfig_TraceFilter_StringFilterRule);
+PERFETTO_PB_FIELD(
+    perfetto_protos_TraceConfig_TraceFilter_StringFilterRule,
+    VARINT,
+    enum perfetto_protos_TraceConfig_TraceFilter_StringFilterPolicy,
+    policy,
+    1);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TraceFilter_StringFilterRule,
+                  STRING,
+                  const char*,
+                  regex_pattern,
+                  2);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TraceFilter_StringFilterRule,
+                  STRING,
+                  const char*,
+                  atrace_payload_starts_with,
+                  3);
 
 PERFETTO_PB_MSG(perfetto_protos_TraceConfig_IncidentReportConfig);
 PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_IncidentReportConfig,
@@ -331,6 +391,11 @@
                   trigger_mode,
                   1);
 PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TriggerConfig,
+                  VARINT,
+                  bool,
+                  use_clone_snapshot_if_available,
+                  5);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_TriggerConfig,
                   MSG,
                   perfetto_protos_TraceConfig_TriggerConfig_Trigger,
                   triggers,
@@ -455,6 +520,11 @@
                   bool,
                   prefer_suspend_clock_for_snapshot,
                   7);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_BuiltinDataSource,
+                  VARINT,
+                  bool,
+                  disable_chunk_usage_histograms,
+                  8);
 
 PERFETTO_PB_MSG(perfetto_protos_TraceConfig_DataSource);
 PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_DataSource,
@@ -484,5 +554,15 @@
                   enum perfetto_protos_TraceConfig_BufferConfig_FillPolicy,
                   fill_policy,
                   4);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_BufferConfig,
+                  VARINT,
+                  bool,
+                  transfer_on_clone,
+                  5);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_BufferConfig,
+                  VARINT,
+                  bool,
+                  clear_before_clone,
+                  6);
 
 #endif  // INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_TRACE_CONFIG_PZC_H_
diff --git a/include/perfetto/public/protos/config/track_event/track_event_config.pzc.h b/include/perfetto/public/protos/config/track_event/track_event_config.pzc.h
index 271cd24..aede7d5 100644
--- a/include/perfetto/public/protos/config/track_event/track_event_config.pzc.h
+++ b/include/perfetto/public/protos/config/track_event/track_event_config.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_TRACK_EVENT_TRACK_EVENT_CONFIG_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_CONFIG_TRACK_EVENT_TRACK_EVENT_CONFIG_PZC_H_
 
diff --git a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
index b5147a5..3c8f88a 100644
--- a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
+++ b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
 
@@ -34,6 +37,7 @@
 PERFETTO_PB_MSG_DECL(perfetto_protos_InternedString);
 PERFETTO_PB_MSG_DECL(perfetto_protos_LogMessageBody);
 PERFETTO_PB_MSG_DECL(perfetto_protos_Mapping);
+PERFETTO_PB_MSG_DECL(perfetto_protos_NetworkPacketContext);
 PERFETTO_PB_MSG_DECL(perfetto_protos_ProfiledFrameSymbols);
 PERFETTO_PB_MSG_DECL(perfetto_protos_SourceLocation);
 PERFETTO_PB_MSG_DECL(perfetto_protos_UnsymbolizedSourceLocation);
@@ -144,5 +148,10 @@
                   perfetto_protos_InternedString,
                   debug_annotation_string_values,
                   29);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+                  MSG,
+                  perfetto_protos_NetworkPacketContext,
+                  packet_context,
+                  30);
 
 #endif  // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
diff --git a/include/perfetto/public/protos/trace/test_event.pzc.h b/include/perfetto/public/protos/trace/test_event.pzc.h
index 2706934..b4e483f 100644
--- a/include/perfetto/public/protos/trace/test_event.pzc.h
+++ b/include/perfetto/public/protos/trace/test_event.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TEST_EVENT_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TEST_EVENT_PZC_H_
 
diff --git a/include/perfetto/public/protos/trace/trace.pzc.h b/include/perfetto/public/protos/trace/trace.pzc.h
index a40f46b..9625f6e 100644
--- a/include/perfetto/public/protos/trace/trace.pzc.h
+++ b/include/perfetto/public/protos/trace/trace.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACE_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACE_PZC_H_
 
diff --git a/include/perfetto/public/protos/trace/trace_packet.pzc.h b/include/perfetto/public/protos/trace/trace_packet.pzc.h
index 8c0efe4..52df003 100644
--- a/include/perfetto/public/protos/trace/trace_packet.pzc.h
+++ b/include/perfetto/public/protos/trace/trace_packet.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACE_PACKET_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACE_PACKET_PZC_H_
 
@@ -49,8 +52,10 @@
 PERFETTO_PB_MSG_DECL(perfetto_protos_InitialDisplayState);
 PERFETTO_PB_MSG_DECL(perfetto_protos_InodeFileMap);
 PERFETTO_PB_MSG_DECL(perfetto_protos_InternedData);
+PERFETTO_PB_MSG_DECL(perfetto_protos_LayersSnapshotProto);
 PERFETTO_PB_MSG_DECL(perfetto_protos_MemoryTrackerSnapshot);
 PERFETTO_PB_MSG_DECL(perfetto_protos_ModuleSymbols);
+PERFETTO_PB_MSG_DECL(perfetto_protos_NetworkPacketBundle);
 PERFETTO_PB_MSG_DECL(perfetto_protos_NetworkPacketEvent);
 PERFETTO_PB_MSG_DECL(perfetto_protos_PackagesList);
 PERFETTO_PB_MSG_DECL(perfetto_protos_PerfSample);
@@ -78,6 +83,7 @@
 PERFETTO_PB_MSG_DECL(perfetto_protos_TrackDescriptor);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TrackEvent);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TrackEventRangeOfInterest);
+PERFETTO_PB_MSG_DECL(perfetto_protos_TransactionTraceEntry);
 PERFETTO_PB_MSG_DECL(perfetto_protos_TranslationTable);
 PERFETTO_PB_MSG_DECL(perfetto_protos_Trigger);
 PERFETTO_PB_MSG_DECL(perfetto_protos_UiState);
@@ -397,11 +403,26 @@
                   88);
 PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
                   MSG,
+                  perfetto_protos_NetworkPacketBundle,
+                  network_packet_bundle,
+                  92);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+                  MSG,
                   perfetto_protos_TrackEventRangeOfInterest,
                   track_event_range_of_interest,
                   90);
 PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
                   MSG,
+                  perfetto_protos_LayersSnapshotProto,
+                  surfaceflinger_layers_snapshot,
+                  93);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+                  MSG,
+                  perfetto_protos_TransactionTraceEntry,
+                  surfaceflinger_transactions,
+                  94);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+                  MSG,
                   perfetto_protos_TestEvent,
                   for_testing,
                   900);
diff --git a/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h b/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h
index a5d075a..322ee8e 100644
--- a/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h
+++ b/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_COUNTER_DESCRIPTOR_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_COUNTER_DESCRIPTOR_PZC_H_
 
diff --git a/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h b/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h
index d4152ae..3183f24 100644
--- a/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h
+++ b/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_DEBUG_ANNOTATION_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_DEBUG_ANNOTATION_PZC_H_
 
diff --git a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
index 0069082..ee3d978 100644
--- a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
+++ b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_DESCRIPTOR_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_DESCRIPTOR_PZC_H_
 
@@ -65,5 +68,10 @@
                   perfetto_protos_CounterDescriptor,
                   counter,
                   8);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+                  VARINT,
+                  bool,
+                  disallow_merging_with_system_tracks,
+                  9);
 
 #endif  // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_DESCRIPTOR_PZC_H_
diff --git a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
index 5811154..5553376 100644
--- a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
+++ b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_EVENT_PZC_H_
 #define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_EVENT_PZC_H_
 
diff --git a/src/protozero/protoc_plugin/BUILD.gn b/src/protozero/protoc_plugin/BUILD.gn
index a628be7..b9139db 100644
--- a/src/protozero/protoc_plugin/BUILD.gn
+++ b/src/protozero/protoc_plugin/BUILD.gn
@@ -36,3 +36,14 @@
     "../../../src/base",
   ]
 }
+
+# The plugin that generates standalone C macros and function from protos
+# (xxx.pzc.h). This is used to generate the API headers of the shared library.
+perfetto_host_executable("protozero_c_plugin") {
+  sources = [ "protozero_c_plugin.cc" ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../../gn:protoc_lib",
+    "../../../src/base",
+  ]
+}
diff --git a/src/protozero/protoc_plugin/protozero_c_plugin.cc b/src/protozero/protoc_plugin/protozero_c_plugin.cc
new file mode 100644
index 0000000..461b71d
--- /dev/null
+++ b/src/protozero/protoc_plugin/protozero_c_plugin.cc
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2023 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 <stdlib.h>
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include <google/protobuf/compiler/code_generator.h>
+#include <google/protobuf/compiler/plugin.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+#include <google/protobuf/io/printer.h>
+#include <google/protobuf/io/zero_copy_stream.h>
+
+#include "perfetto/ext/base/string_utils.h"
+
+namespace protozero {
+namespace {
+
+using google::protobuf::Descriptor;
+using google::protobuf::EnumDescriptor;
+using google::protobuf::EnumValueDescriptor;
+using google::protobuf::FieldDescriptor;
+using google::protobuf::FileDescriptor;
+using google::protobuf::compiler::GeneratorContext;
+using google::protobuf::io::Printer;
+using google::protobuf::io::ZeroCopyOutputStream;
+using perfetto::base::SplitString;
+using perfetto::base::StripChars;
+using perfetto::base::StripPrefix;
+using perfetto::base::StripSuffix;
+using perfetto::base::ToUpper;
+using perfetto::base::Uppercase;
+
+void Assert(bool condition) {
+  if (!condition)
+    abort();
+}
+
+struct FileDescriptorComp {
+  bool operator()(const FileDescriptor* lhs, const FileDescriptor* rhs) const {
+    int comp = lhs->name().compare(rhs->name());
+    Assert(comp != 0 || lhs == rhs);
+    return comp < 0;
+  }
+};
+
+struct DescriptorComp {
+  bool operator()(const Descriptor* lhs, const Descriptor* rhs) const {
+    int comp = lhs->full_name().compare(rhs->full_name());
+    Assert(comp != 0 || lhs == rhs);
+    return comp < 0;
+  }
+};
+
+struct EnumDescriptorComp {
+  bool operator()(const EnumDescriptor* lhs, const EnumDescriptor* rhs) const {
+    int comp = lhs->full_name().compare(rhs->full_name());
+    Assert(comp != 0 || lhs == rhs);
+    return comp < 0;
+  }
+};
+
+inline std::string ProtoStubName(const FileDescriptor* proto) {
+  return StripSuffix(proto->name(), ".proto") + ".pzc";
+}
+
+class GeneratorJob {
+ public:
+  GeneratorJob(const FileDescriptor* file, Printer* stub_h_printer)
+      : source_(file), stub_h_(stub_h_printer) {}
+
+  bool GenerateStubs() {
+    Preprocess();
+    GeneratePrologue();
+    for (const EnumDescriptor* enumeration : enums_)
+      GenerateEnumDescriptor(enumeration);
+    for (const Descriptor* message : messages_)
+      GenerateMessageDescriptor(message);
+    GenerateEpilogue();
+    return error_.empty();
+  }
+
+  void SetOption(const std::string& name, const std::string& value) {
+    if (name == "wrapper_namespace") {
+      wrapper_namespace_ = value;
+    } else if (name == "guard_strip_prefix") {
+      guard_strip_prefix_ = value;
+    } else if (name == "guard_add_prefix") {
+      guard_add_prefix_ = value;
+    } else if (name == "path_strip_prefix") {
+      path_strip_prefix_ = value;
+    } else if (name == "path_add_prefix") {
+      path_add_prefix_ = value;
+    } else if (name == "invoker") {
+      invoker_ = value;
+    } else {
+      Abort(std::string() + "Unknown plugin option '" + name + "'.");
+    }
+  }
+
+  // If generator fails to produce stubs for a particular proto definitions
+  // it finishes with undefined output and writes the first error occured.
+  const std::string& GetFirstError() const { return error_; }
+
+ private:
+  // Only the first error will be recorded.
+  void Abort(const std::string& reason) {
+    if (error_.empty())
+      error_ = reason;
+  }
+
+  // Get full name (including outer descriptors) of proto descriptor.
+  template <class T>
+  inline std::string GetDescriptorName(const T* descriptor) {
+    if (!package_.empty()) {
+      return StripPrefix(descriptor->full_name(), package_ + ".");
+    }
+    return descriptor->full_name();
+  }
+
+  // Get C++ class name corresponding to proto descriptor.
+  // Nested names are splitted by underscores. Underscores in type names aren't
+  // prohibited but not recommended in order to avoid name collisions.
+  template <class T>
+  inline std::string GetCppClassName(const T* descriptor, bool full = false) {
+    std::string name = StripChars(GetDescriptorName(descriptor), ".", '_');
+    if (full)
+      name = full_namespace_prefix_ + "_" + name;
+    return name;
+  }
+
+  const char* FieldTypeToPackedBufferType(FieldDescriptor::Type type) {
+    switch (type) {
+      case FieldDescriptor::TYPE_ENUM:
+      case FieldDescriptor::TYPE_INT32:
+        return "Int32";
+      case FieldDescriptor::TYPE_INT64:
+        return "Int64";
+      case FieldDescriptor::TYPE_UINT32:
+        return "Uint32";
+      case FieldDescriptor::TYPE_UINT64:
+        return "Uint64";
+      case FieldDescriptor::TYPE_SINT32:
+        return "Sint32";
+      case FieldDescriptor::TYPE_SINT64:
+        return "Sint64";
+      case FieldDescriptor::TYPE_FIXED32:
+        return "Fixed32";
+      case FieldDescriptor::TYPE_FIXED64:
+        return "Fixed64";
+      case FieldDescriptor::TYPE_SFIXED32:
+        return "Sfixed32";
+      case FieldDescriptor::TYPE_SFIXED64:
+        return "Sfixed64";
+      case FieldDescriptor::TYPE_FLOAT:
+        return "Float";
+      case FieldDescriptor::TYPE_DOUBLE:
+        return "Double";
+      case FieldDescriptor::TYPE_BOOL:
+      case FieldDescriptor::TYPE_STRING:
+      case FieldDescriptor::TYPE_BYTES:
+      case FieldDescriptor::TYPE_MESSAGE:
+      case FieldDescriptor::TYPE_GROUP:
+        break;
+    }
+    Abort("Unsupported packed type");
+    return "";
+  }
+  std::string FieldToCppTypeName(const FieldDescriptor* field) {
+    switch (field->type()) {
+      case FieldDescriptor::TYPE_BOOL:
+        return "bool";
+      case FieldDescriptor::TYPE_INT32:
+        return "int32_t";
+      case FieldDescriptor::TYPE_INT64:
+        return "int64_t";
+      case FieldDescriptor::TYPE_UINT32:
+        return "uint32_t";
+      case FieldDescriptor::TYPE_UINT64:
+        return "uint64_t";
+      case FieldDescriptor::TYPE_SINT32:
+        return "int32_t";
+      case FieldDescriptor::TYPE_SINT64:
+        return "int64_t";
+      case FieldDescriptor::TYPE_FIXED32:
+        return "uint32_t";
+      case FieldDescriptor::TYPE_FIXED64:
+        return "uint64_t";
+      case FieldDescriptor::TYPE_SFIXED32:
+        return "int32_t";
+      case FieldDescriptor::TYPE_SFIXED64:
+        return "int64_t";
+      case FieldDescriptor::TYPE_FLOAT:
+        return "float";
+      case FieldDescriptor::TYPE_DOUBLE:
+        return "double";
+      case FieldDescriptor::TYPE_ENUM:
+        return "enum " + GetCppClassName(field->enum_type(), true);
+      case FieldDescriptor::TYPE_STRING:
+      case FieldDescriptor::TYPE_BYTES:
+        return "const char*";
+      case FieldDescriptor::TYPE_MESSAGE:
+        return GetCppClassName(field->message_type());
+      case FieldDescriptor::TYPE_GROUP:
+        Abort("Groups not supported.");
+        return "";
+    }
+    Abort("Unrecognized FieldDescriptor::Type.");
+    return "";
+  }
+
+  void CollectDescriptors() {
+    // Collect message descriptors in DFS order.
+    std::vector<const Descriptor*> stack;
+    stack.reserve(static_cast<size_t>(source_->message_type_count()));
+    for (int i = 0; i < source_->message_type_count(); ++i)
+      stack.push_back(source_->message_type(i));
+
+    while (!stack.empty()) {
+      const Descriptor* message = stack.back();
+      stack.pop_back();
+
+      if (message->extension_count() > 0) {
+        if (message->field_count() > 0 || message->nested_type_count() > 0 ||
+            message->enum_type_count() > 0) {
+          Abort("message with extend blocks shouldn't contain anything else");
+        }
+
+        // Iterate over all fields in "extend" blocks.
+        for (int i = 0; i < message->extension_count(); ++i) {
+          const FieldDescriptor* extension = message->extension(i);
+
+          // Protoc plugin API does not group fields in "extend" blocks.
+          // As the support for extensions in protozero is limited, the code
+          // assumes that extend blocks are located inside a wrapper message and
+          // name of this message is used to group them.
+          std::string extension_name = extension->extension_scope()->name();
+          extensions_[extension_name].push_back(extension);
+        }
+      } else {
+        messages_.push_back(message);
+        for (int i = 0; i < message->nested_type_count(); ++i) {
+          stack.push_back(message->nested_type(i));
+          // Emit a forward declaration of nested message types, as the outer
+          // class will refer to them when creating type aliases.
+          referenced_messages_.insert(message->nested_type(i));
+        }
+      }
+    }
+
+    // Collect enums.
+    for (int i = 0; i < source_->enum_type_count(); ++i)
+      enums_.push_back(source_->enum_type(i));
+
+    if (source_->extension_count() > 0)
+      Abort("top-level extension blocks are not supported");
+
+    for (const Descriptor* message : messages_) {
+      for (int i = 0; i < message->enum_type_count(); ++i) {
+        enums_.push_back(message->enum_type(i));
+      }
+    }
+  }
+
+  void CollectDependencies() {
+    // Public import basically means that callers only need to import this
+    // proto in order to use the stuff publicly imported by this proto.
+    for (int i = 0; i < source_->public_dependency_count(); ++i)
+      public_imports_.insert(source_->public_dependency(i));
+
+    if (source_->weak_dependency_count() > 0)
+      Abort("Weak imports are not supported.");
+
+    // Validations. Collect public imports (of collected imports) in DFS order.
+    // Visibilty for current proto:
+    // - all imports listed in current proto,
+    // - public imports of everything imported (recursive).
+    std::vector<const FileDescriptor*> stack;
+    for (int i = 0; i < source_->dependency_count(); ++i) {
+      const FileDescriptor* imp = source_->dependency(i);
+      stack.push_back(imp);
+      if (public_imports_.count(imp) == 0) {
+        private_imports_.insert(imp);
+      }
+    }
+
+    while (!stack.empty()) {
+      const FileDescriptor* imp = stack.back();
+      stack.pop_back();
+      // Having imports under different packages leads to unnecessary
+      // complexity with namespaces.
+      if (imp->package() != package_)
+        Abort("Imported proto must be in the same package.");
+
+      for (int i = 0; i < imp->public_dependency_count(); ++i) {
+        stack.push_back(imp->public_dependency(i));
+      }
+    }
+
+    // Collect descriptors of messages and enums used in current proto.
+    // It will be used to generate necessary forward declarations and
+    // check that everything lays in the same namespace.
+    for (const Descriptor* message : messages_) {
+      for (int i = 0; i < message->field_count(); ++i) {
+        const FieldDescriptor* field = message->field(i);
+
+        if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
+          if (public_imports_.count(field->message_type()->file()) == 0) {
+            // Avoid multiple forward declarations since
+            // public imports have been already included.
+            referenced_messages_.insert(field->message_type());
+          }
+        } else if (field->type() == FieldDescriptor::TYPE_ENUM) {
+          if (public_imports_.count(field->enum_type()->file()) == 0) {
+            referenced_enums_.insert(field->enum_type());
+          }
+        }
+      }
+    }
+  }
+
+  void Preprocess() {
+    // Package name maps to a series of namespaces.
+    package_ = source_->package();
+    namespaces_ = SplitString(package_, ".");
+    if (!wrapper_namespace_.empty())
+      namespaces_.push_back(wrapper_namespace_);
+
+    full_namespace_prefix_ = "";
+    for (size_t i = 0; i < namespaces_.size(); i++) {
+      full_namespace_prefix_ += namespaces_[i];
+      if (i + 1 != namespaces_.size()) {
+        full_namespace_prefix_ += "_";
+      }
+    }
+
+    CollectDescriptors();
+    CollectDependencies();
+  }
+
+  std::string GenerateGuard() {
+    std::string guard = StripSuffix(source_->name(), ".proto");
+    guard = ToUpper(guard);
+    guard = StripChars(guard, ".-/\\", '_');
+    guard = StripPrefix(guard, guard_strip_prefix_);
+    guard = guard_add_prefix_ + guard + "_PZC_H_";
+    return guard;
+  }
+
+  // Print top header, namespaces and forward declarations.
+  void GeneratePrologue() {
+    stub_h_->Print(
+        R"(/*
+ * Copyright (C) 2023 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.
+ */
+
+)");
+    stub_h_->Print("// Autogenerated by the ProtoZero C compiler plugin.\n");
+    if (!invoker_.empty()) {
+      stub_h_->Print("// Invoked by $invoker$\n", "invoker", invoker_);
+    }
+    stub_h_->Print("// DO NOT EDIT.\n");
+
+    stub_h_->Print(
+        "#ifndef $guard$\n"
+        "#define $guard$\n\n"
+        "#include <stdbool.h>\n"
+        "#include <stdint.h>\n\n"
+        "#include \"perfetto/public/pb_macros.h\"\n",
+        "guard", GenerateGuard());
+
+    // Print includes for public imports and enums which cannot be forward
+    // declared.
+    std::vector<std::string> imports;
+    for (const FileDescriptor* dependency : public_imports_) {
+      imports.push_back(ProtoStubName(dependency));
+    }
+    for (const EnumDescriptor* e : referenced_enums_) {
+      if (e->file() != source_) {
+        imports.push_back(ProtoStubName(e->file()));
+      }
+    }
+
+    std::sort(imports.begin(), imports.end());
+
+    for (const std::string& imp : imports) {
+      std::string include_path = imp;
+      if (!path_strip_prefix_.empty()) {
+        include_path = StripPrefix(imp, path_strip_prefix_);
+      }
+      include_path = path_add_prefix_ + include_path;
+
+      stub_h_->Print("#include \"$name$.h\"\n", "name", include_path);
+    }
+    stub_h_->Print("\n");
+
+    // Print forward declarations.
+    for (const Descriptor* message : referenced_messages_) {
+      stub_h_->Print("PERFETTO_PB_MSG_DECL($class$);\n", "class",
+                     GetCppClassName(message, true));
+    }
+
+    stub_h_->Print("\n");
+  }
+
+  void GenerateEnumDescriptor(const EnumDescriptor* enumeration) {
+    if (enumeration->containing_type()) {
+      stub_h_->Print("PERFETTO_PB_ENUM_IN_MSG($msg$, $class$){\n", "msg",
+                     GetCppClassName(enumeration->containing_type(), true),
+                     "class", enumeration->name());
+    } else {
+      stub_h_->Print("PERFETTO_PB_ENUM($class$){\n", "class",
+                     GetCppClassName(enumeration, true));
+    }
+    stub_h_->Indent();
+
+    for (int i = 0; i < enumeration->value_count(); ++i) {
+      const EnumValueDescriptor* value = enumeration->value(i);
+      const std::string value_name = value->name();
+
+      if (enumeration->containing_type()) {
+        stub_h_->Print(
+            "PERFETTO_PB_ENUM_IN_MSG_ENTRY($msg$, $val$) = $number$,\n", "msg",
+            GetCppClassName(enumeration->containing_type(), true), "val",
+            value_name, "number", std::to_string(value->number()));
+      } else {
+        stub_h_->Print("PERFETTO_PB_ENUM_ENTRY($val$) = $number$, \n", "val",
+                       full_namespace_prefix_ + "_" + value_name, "number",
+                       std::to_string(value->number()));
+      }
+    }
+    stub_h_->Outdent();
+    stub_h_->Print("};\n\n");
+  }
+
+  // Packed repeated fields are encoded as a length-delimited field on the wire,
+  // where the payload is the concatenation of invidually encoded elements.
+  void GeneratePackedRepeatedFieldDescriptor(
+      const std::string& message_cpp_type,
+      const FieldDescriptor* field) {
+    std::map<std::string, std::string> setter;
+    setter["id"] = std::to_string(field->number());
+    setter["name"] = field->lowercase_name();
+    setter["class"] = message_cpp_type;
+    setter["buffer_type"] = FieldTypeToPackedBufferType(field->type());
+    stub_h_->Print(
+        setter,
+        "PERFETTO_PB_FIELD($class$, PACKED, $buffer_type$, $name$, $id$);\n");
+  }
+
+  void GenerateSimpleFieldDescriptor(const std::string& message_cpp_type,
+                                     const FieldDescriptor* field) {
+    std::map<std::string, std::string> setter;
+    setter["id"] = std::to_string(field->number());
+    setter["name"] = field->lowercase_name();
+    setter["ctype"] = FieldToCppTypeName(field);
+    setter["class"] = message_cpp_type;
+
+    switch (field->type()) {
+      case FieldDescriptor::TYPE_BYTES:
+      case FieldDescriptor::TYPE_STRING:
+        stub_h_->Print(
+            setter,
+            "PERFETTO_PB_FIELD($class$, STRING, const char*, $name$, $id$);\n");
+        break;
+      case FieldDescriptor::TYPE_UINT64:
+      case FieldDescriptor::TYPE_UINT32:
+      case FieldDescriptor::TYPE_INT64:
+      case FieldDescriptor::TYPE_INT32:
+      case FieldDescriptor::TYPE_BOOL:
+      case FieldDescriptor::TYPE_ENUM:
+        stub_h_->Print(
+            setter,
+            "PERFETTO_PB_FIELD($class$, VARINT, $ctype$, $name$, $id$);\n");
+        break;
+      case FieldDescriptor::TYPE_SINT64:
+      case FieldDescriptor::TYPE_SINT32:
+        stub_h_->Print(
+            setter,
+            "PERFETTO_PB_FIELD($class$, ZIGZAG, $ctype$, $name$, $id$);\n");
+        break;
+      case FieldDescriptor::TYPE_SFIXED32:
+      case FieldDescriptor::TYPE_FIXED32:
+      case FieldDescriptor::TYPE_FLOAT:
+        stub_h_->Print(
+            setter,
+            "PERFETTO_PB_FIELD($class$, FIXED32, $ctype$, $name$, $id$);\n");
+        break;
+      case FieldDescriptor::TYPE_SFIXED64:
+      case FieldDescriptor::TYPE_FIXED64:
+      case FieldDescriptor::TYPE_DOUBLE:
+        stub_h_->Print(
+            setter,
+            "PERFETTO_PB_FIELD($class$, FIXED64, $ctype$, $name$, $id$);\n");
+        break;
+      case FieldDescriptor::TYPE_MESSAGE:
+      case FieldDescriptor::TYPE_GROUP:
+        Abort("Groups not supported.");
+        break;
+    }
+  }
+
+  void GenerateNestedMessageFieldDescriptor(const std::string& message_cpp_type,
+                                            const FieldDescriptor* field) {
+    std::string inner_class =
+        full_namespace_prefix_ + "_" + GetCppClassName(field->message_type());
+    stub_h_->Print(
+        "PERFETTO_PB_FIELD($class$, MSG, $inner_class$, $name$, $id$);\n",
+        "class", message_cpp_type, "id", std::to_string(field->number()),
+        "name", field->lowercase_name(), "inner_class", inner_class);
+  }
+
+  void GenerateMessageDescriptor(const Descriptor* message) {
+    stub_h_->Print("PERFETTO_PB_MSG($name$);\n", "name",
+                   GetCppClassName(message, true));
+
+    // Field descriptors.
+    for (int i = 0; i < message->field_count(); ++i) {
+      GenerateFieldDescriptor(GetCppClassName(message, true),
+                              message->field(i));
+    }
+    stub_h_->Print("\n");
+  }
+
+  void GenerateFieldDescriptor(const std::string& message_cpp_type,
+                               const FieldDescriptor* field) {
+    // GenerateFieldMetadata(message_cpp_type, field);
+    if (field->is_packed()) {
+      GeneratePackedRepeatedFieldDescriptor(message_cpp_type, field);
+    } else if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+      GenerateSimpleFieldDescriptor(message_cpp_type, field);
+    } else {
+      GenerateNestedMessageFieldDescriptor(message_cpp_type, field);
+    }
+  }
+
+  void GenerateEpilogue() {
+    stub_h_->Print("#endif  // $guard$\n", "guard", GenerateGuard());
+  }
+
+  const FileDescriptor* const source_;
+  Printer* const stub_h_;
+  std::string error_;
+
+  std::string package_;
+  std::string wrapper_namespace_;
+  std::string guard_strip_prefix_;
+  std::string guard_add_prefix_;
+  std::string path_strip_prefix_;
+  std::string path_add_prefix_;
+  std::string invoker_;
+  std::vector<std::string> namespaces_;
+  std::string full_namespace_prefix_;
+  std::vector<const Descriptor*> messages_;
+  std::vector<const EnumDescriptor*> enums_;
+  std::map<std::string, std::vector<const FieldDescriptor*>> extensions_;
+
+  // The custom *Comp comparators are to ensure determinism of the generator.
+  std::set<const FileDescriptor*, FileDescriptorComp> public_imports_;
+  std::set<const FileDescriptor*, FileDescriptorComp> private_imports_;
+  std::set<const Descriptor*, DescriptorComp> referenced_messages_;
+  std::set<const EnumDescriptor*, EnumDescriptorComp> referenced_enums_;
+};
+
+class ProtoZeroCGenerator : public google::protobuf::compiler::CodeGenerator {
+ public:
+  explicit ProtoZeroCGenerator();
+  ~ProtoZeroCGenerator() override;
+
+  // CodeGenerator implementation
+  bool Generate(const google::protobuf::FileDescriptor* file,
+                const std::string& options,
+                GeneratorContext* context,
+                std::string* error) const override;
+};
+
+ProtoZeroCGenerator::ProtoZeroCGenerator() {}
+
+ProtoZeroCGenerator::~ProtoZeroCGenerator() {}
+
+bool ProtoZeroCGenerator::Generate(const FileDescriptor* file,
+                                   const std::string& options,
+                                   GeneratorContext* context,
+                                   std::string* error) const {
+  const std::unique_ptr<ZeroCopyOutputStream> stub_h_file_stream(
+      context->Open(ProtoStubName(file) + ".h"));
+
+  // Variables are delimited by $.
+  Printer stub_h_printer(stub_h_file_stream.get(), '$');
+  GeneratorJob job(file, &stub_h_printer);
+
+  // Parse additional options.
+  for (const std::string& option : SplitString(options, ",")) {
+    std::vector<std::string> option_pair = SplitString(option, "=");
+    job.SetOption(option_pair[0], option_pair[1]);
+  }
+
+  if (!job.GenerateStubs()) {
+    *error = job.GetFirstError();
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+}  // namespace protozero
+
+int main(int argc, char* argv[]) {
+  protozero::ProtoZeroCGenerator generator;
+  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
+}
diff --git a/src/shared_lib/test/protos/library.pzc.h b/src/shared_lib/test/protos/library.pzc.h
index 9238101..94a54cd 100644
--- a/src/shared_lib/test/protos/library.pzc.h
+++ b/src/shared_lib/test/protos/library.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef SRC_SHARED_LIB_TEST_PROTOS_LIBRARY_PZC_H_
 #define SRC_SHARED_LIB_TEST_PROTOS_LIBRARY_PZC_H_
 
diff --git a/src/shared_lib/test/protos/library_internals/galaxies.pzc.h b/src/shared_lib/test/protos/library_internals/galaxies.pzc.h
index 0823642..1a3e0ce 100644
--- a/src/shared_lib/test/protos/library_internals/galaxies.pzc.h
+++ b/src/shared_lib/test/protos/library_internals/galaxies.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef SRC_SHARED_LIB_TEST_PROTOS_LIBRARY_INTERNALS_GALAXIES_PZC_H_
 #define SRC_SHARED_LIB_TEST_PROTOS_LIBRARY_INTERNALS_GALAXIES_PZC_H_
 
diff --git a/src/shared_lib/test/protos/test_messages.pzc.h b/src/shared_lib/test/protos/test_messages.pzc.h
index 5f8b6b3..5198ee7 100644
--- a/src/shared_lib/test/protos/test_messages.pzc.h
+++ b/src/shared_lib/test/protos/test_messages.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef SRC_SHARED_LIB_TEST_PROTOS_TEST_MESSAGES_PZC_H_
 #define SRC_SHARED_LIB_TEST_PROTOS_TEST_MESSAGES_PZC_H_
 
diff --git a/src/shared_lib/test/protos/upper_import.pzc.h b/src/shared_lib/test/protos/upper_import.pzc.h
index 37e55c6..a7f86cf 100644
--- a/src/shared_lib/test/protos/upper_import.pzc.h
+++ b/src/shared_lib/test/protos/upper_import.pzc.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
 #ifndef SRC_SHARED_LIB_TEST_PROTOS_UPPER_IMPORT_PZC_H_
 #define SRC_SHARED_LIB_TEST_PROTOS_UPPER_IMPORT_PZC_H_
 
diff --git a/tools/gen_c_protos b/tools/gen_c_protos
new file mode 100755
index 0000000..38a7491
--- /dev/null
+++ b/tools/gen_c_protos
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 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.
+
+import argparse
+import filecmp
+import os
+import pathlib
+import shutil
+import subprocess
+import sys
+import tempfile
+
+SOURCE_FILES = [
+  {
+    'files': [
+      'protos/perfetto/common/builtin_clock.proto',
+      'protos/perfetto/common/data_source_descriptor.proto',
+      'protos/perfetto/config/data_source_config.proto',
+      'protos/perfetto/config/trace_config.proto',
+      'protos/perfetto/config/track_event/track_event_config.proto',
+      'protos/perfetto/trace/interned_data/interned_data.proto',
+      'protos/perfetto/trace/test_event.proto',
+      'protos/perfetto/trace/trace.proto',
+      'protos/perfetto/trace/trace_packet.proto',
+      'protos/perfetto/trace/track_event/counter_descriptor.proto',
+      'protos/perfetto/trace/track_event/debug_annotation.proto',
+      'protos/perfetto/trace/track_event/track_descriptor.proto',
+      'protos/perfetto/trace/track_event/track_event.proto',
+    ],
+    'guard_strip_prefix': 'PROTOS_PERFETTO_',
+    'guard_add_prefix':'INCLUDE_PERFETTO_PUBLIC_PROTOS_',
+    'path_strip_prefix': 'protos/perfetto',
+    'path_add_prefix': 'perfetto/public/protos',
+    'include_prefix': 'include/',
+  },
+  {
+    'files': [
+      'src/protozero/test/example_proto/library.proto',
+      'src/protozero/test/example_proto/library_internals/galaxies.proto',
+      'src/protozero/test/example_proto/test_messages.proto',
+      'src/protozero/test/example_proto/upper_import.proto',
+    ],
+    'guard_strip_prefix': 'SRC_PROTOZERO_TEST_EXAMPLE_PROTO_',
+    'guard_add_prefix':'SRC_SHARED_LIB_TEST_PROTOS_',
+    'path_strip_prefix': 'src/protozero/test/example_proto',
+    'path_add_prefix': 'src/shared_lib/test/protos',
+  },
+]
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+IS_WIN = sys.platform.startswith('win')
+
+SCRIPT_PATH = 'tools/gen_c_protos'
+
+
+def protozero_c_plugin_path(out_directory):
+  path = os.path.join(out_directory,
+                      'protozero_c_plugin') + ('.exe' if IS_WIN else '')
+  assert os.path.isfile(path)
+  return path
+
+
+def protoc_path(out_directory):
+  path = os.path.join(out_directory, 'protoc') + ('.exe' if IS_WIN else '')
+  assert os.path.isfile(path)
+  return path
+
+
+def call(cmd, *args):
+  path = os.path.join('tools', cmd)
+  command = ['python3', path] + list(args)
+  print('Running', ' '.join(command))
+  try:
+    subprocess.check_call(command, cwd=ROOT_DIR)
+  except subprocess.CalledProcessError as e:
+    assert False, 'Command: {} failed'.format(' '.join(command))
+
+
+# Reformats filename
+def clang_format(filename):
+  path = os.path.join(ROOT_DIR, 'third_party', 'clang-format',
+                      'clang-format') + ('.exe' if IS_WIN else '')
+  assert os.path.isfile(
+      path), "clang-format not found. Run tools/install-build-deps"
+  subprocess.check_call([
+      path, '--style=file:{}'.format(os.path.join(ROOT_DIR, '.clang-format')),
+      '-i', filename
+  ],
+                        cwd=ROOT_DIR)
+
+
+# Transforms filename extension like the ProtoZero C plugin
+def transform_extension(filename):
+  old_suffix = ".proto"
+  new_suffix = ".pzc.h"
+  if filename.endswith(old_suffix):
+    return filename[:-len(old_suffix)] + new_suffix
+  return filename
+
+
+def generate(source, outdir, protoc_path, protozero_c_plugin_path, guard_strip_prefix, guard_add_prefix, path_strip_prefix, path_add_prefix):
+  options = {
+      'guard_strip_prefix': guard_strip_prefix,
+      'guard_add_prefix': guard_add_prefix,
+      'path_strip_prefix': path_strip_prefix,
+      'path_add_prefix': path_add_prefix,
+      'invoker': SCRIPT_PATH,
+  }
+  serialized_options = ','.join(
+      ['{}={}'.format(name, value) for name, value in options.items()])
+  subprocess.check_call([
+      protoc_path,
+      '--proto_path=.',
+      '--plugin=protoc-gen-plugin={}'.format(protozero_c_plugin_path),
+      '--plugin_out={}:{}'.format(serialized_options, outdir),
+      source,
+  ],
+                        cwd=ROOT_DIR)
+
+
+# Given filename, the path of a header generated by the ProtoZero C plugin,
+# returns the path where the header should go in the public include directory.
+# Example
+#
+# include_path_for("protos/perfetto/trace/trace.pzc.h") ==
+# "include/perfetto/public/protos/trace/trace.pzc.h"
+def include_path_for(filename):
+  return os.path.join('include', 'perfetto', 'public', 'protos',
+                      *pathlib.Path(transform_extension(filename)).parts[2:])
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--check-only', action='store_true')
+  parser.add_argument('OUT')
+  args = parser.parse_args()
+  out = args.OUT
+
+  call('ninja', '-C', out, 'protoc', 'protozero_c_plugin')
+
+  try:
+    with tempfile.TemporaryDirectory() as tmpdirname:
+      for sources in SOURCE_FILES:
+        for source in sources['files']:
+          generate(source, tmpdirname, protoc_path(out), protozero_c_plugin_path(out),
+                   guard_strip_prefix=sources['guard_strip_prefix'],
+                   guard_add_prefix=sources['guard_add_prefix'],
+                   path_strip_prefix=sources['path_strip_prefix'],
+                   path_add_prefix=sources['path_add_prefix'],
+                   )
+
+          tmpfilename = os.path.join(tmpdirname, transform_extension(source))
+          clang_format(tmpfilename)
+          if source.startswith(sources['path_strip_prefix']):
+            targetfilename = source[len(sources['path_strip_prefix']):]
+          else:
+            targetfilename = source
+
+          targetfilename = sources['path_add_prefix'] + targetfilename
+
+          if 'include_prefix' in sources:
+            targetfilename = os.path.join(sources['include_prefix'], targetfilename)
+          targetfilename = transform_extension(targetfilename)
+
+          if args.check_only:
+            if not filecmp.cmp(tmpfilename, targetfilename):
+              raise AssertionError('Target {} does not match', targetfilename)
+          else:
+            os.makedirs(os.path.dirname(targetfilename), exist_ok=True)
+            shutil.copyfile(tmpfilename, targetfilename)
+
+  except AssertionError as e:
+    if not str(e):
+      raise
+    print('Error: {}'.format(e))
+    return 1
+
+
+if __name__ == '__main__':
+  exit(main())
diff --git a/ui/src/base/object_utils.ts b/ui/src/base/object_utils.ts
index d8cdd38..ec662a8 100644
--- a/ui/src/base/object_utils.ts
+++ b/ui/src/base/object_utils.ts
@@ -49,3 +49,7 @@
   }
   return true;
 }
+
+export function isString(s: unknown): s is string {
+  return typeof s === 'string' || s instanceof String;
+}
diff --git a/ui/src/common/canvas_utils.ts b/ui/src/common/canvas_utils.ts
index 71b3b76..2d09617 100644
--- a/ui/src/common/canvas_utils.ts
+++ b/ui/src/common/canvas_utils.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from '../base/object_utils';
 import {globals} from '../frontend/globals';
 
 export function cropText(str: string, charWidth: number, rectWidth: number) {
@@ -89,12 +90,20 @@
   ctx.lineTo(x + width, y + 4 * triangleSize);
   ctx.lineTo(x, y + height);
 
-  const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
-  gradient.addColorStop(0.66, ctx.fillStyle as string);
-  gradient.addColorStop(1, '#FFFFFF');
-  ctx.fillStyle = gradient;
+  const fillStyle = ctx.fillStyle;
+  if (isString(fillStyle)) {
+    const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
+    gradient.addColorStop(0.66, fillStyle);
+    gradient.addColorStop(1, '#FFFFFF');
+    ctx.fillStyle = gradient;
+  } else {
+    throw new Error(
+        `drawIncompleteSlice() expects fillStyle to be a simple color not ${
+            fillStyle}`);
+  }
 
   ctx.fill();
+  ctx.fillStyle = fillStyle;
 }
 
 export function drawTrackHoverTooltip(