Merge "regen stale metrics.descriptor"
diff --git a/src/perfetto_cmd/config.cc b/src/perfetto_cmd/config.cc
index 1313dcf..4169eea 100644
--- a/src/perfetto_cmd/config.cc
+++ b/src/perfetto_cmd/config.cc
@@ -118,10 +118,13 @@
   std::vector<std::string> ftrace_events;
   std::vector<std::string> atrace_categories;
   std::vector<std::string> atrace_apps = options.atrace_apps;
+  bool has_hyp_category = false;
 
   for (const auto& category : options.categories) {
     if (base::Contains(category, '/')) {
       ftrace_events.push_back(category);
+    } else if (category == "hyp") {
+      has_hyp_category = true;
     } else {
       atrace_categories.push_back(category);
     }
@@ -153,17 +156,33 @@
   if (max_file_size_kb)
     config->set_write_into_file(true);
   config->add_buffers()->set_size_kb(static_cast<unsigned int>(buffer_size_kb));
-  auto* ds_config = config->add_data_sources()->mutable_config();
-  ds_config->set_name("linux.ftrace");
-  protos::gen::FtraceConfig ftrace_cfg;
-  for (const auto& evt : ftrace_events)
-    ftrace_cfg.add_ftrace_events(evt);
-  for (const auto& cat : atrace_categories)
-    ftrace_cfg.add_atrace_categories(cat);
-  for (const auto& app : atrace_apps)
-    ftrace_cfg.add_atrace_apps(app);
-  ftrace_cfg.set_symbolize_ksyms(true);
-  ds_config->set_ftrace_config_raw(ftrace_cfg.SerializeAsString());
+
+  if (!ftrace_events.empty() || !atrace_categories.empty() ||
+      !atrace_apps.empty()) {
+    auto* ds_config = config->add_data_sources()->mutable_config();
+    ds_config->set_name("linux.ftrace");
+    protos::gen::FtraceConfig ftrace_cfg;
+    for (const auto& evt : ftrace_events)
+      ftrace_cfg.add_ftrace_events(evt);
+    for (const auto& cat : atrace_categories)
+      ftrace_cfg.add_atrace_categories(cat);
+    for (const auto& app : atrace_apps)
+      ftrace_cfg.add_atrace_apps(app);
+    ftrace_cfg.set_symbolize_ksyms(true);
+    ds_config->set_ftrace_config_raw(ftrace_cfg.SerializeAsString());
+  }
+
+  // pKVM hypervisor events are coming from a separate special instance called
+  // "hyp", we need a separate config for it.
+  if (has_hyp_category) {
+    auto* ds_config = config->add_data_sources()->mutable_config();
+    ds_config->set_name("linux.ftrace");
+    protos::gen::FtraceConfig ftrace_cfg;
+    ftrace_cfg.set_instance_name("hyp");
+    // Collect all known hypervisor traces.
+    ftrace_cfg.add_ftrace_events("hyp/*");
+    ds_config->set_ftrace_config_raw(ftrace_cfg.SerializeAsString());
+  }
 
   auto* ps_config = config->add_data_sources()->mutable_config();
   ps_config->set_name("linux.process_stats");
diff --git a/src/perfetto_cmd/config_unittest.cc b/src/perfetto_cmd/config_unittest.cc
index 5435c8a..f7a6c2c 100644
--- a/src/perfetto_cmd/config_unittest.cc
+++ b/src/perfetto_cmd/config_unittest.cc
@@ -152,5 +152,49 @@
   EXPECT_THAT(ftrace.atrace_apps(), Contains("com.android.chrome"));
 }
 
+TEST_F(CreateConfigFromOptionsTest, OnlyHypervisorTraces) {
+  options.buffer_size = "100mb";
+  options.time = "10s";
+  options.categories.push_back("hyp");
+  ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+  EXPECT_EQ(config.duration_ms(), 10 * 1000u);
+  EXPECT_EQ(config.buffers()[0].size_kb(), 100 * 1024u);
+  EXPECT_EQ(config.data_sources()[0].config().name(), "linux.ftrace");
+  protos::gen::FtraceConfig ftrace;
+  ASSERT_TRUE(ftrace.ParseFromString(
+      config.data_sources()[0].config().ftrace_config_raw()));
+  EXPECT_THAT(ftrace.ftrace_events(), Contains("hyp/*"));
+  ASSERT_EQ(ftrace.instance_name(), "hyp");
+}
+
+TEST_F(CreateConfigFromOptionsTest, BothHypervisorAndHostFtraceTraces) {
+  options.buffer_size = "100mb";
+  options.time = "10s";
+  options.categories.push_back("hyp");
+  options.categories.push_back("sw");
+  options.categories.push_back("sched/sched_switch");
+  ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+  EXPECT_EQ(config.duration_ms(), 10 * 1000u);
+  EXPECT_EQ(config.buffers()[0].size_kb(), 100 * 1024u);
+  EXPECT_EQ(config.buffers()[0].size_kb(), 100 * 1024u);
+  EXPECT_EQ(config.data_sources()[0].config().name(), "linux.ftrace");
+  {
+    protos::gen::FtraceConfig ftrace;
+    ASSERT_TRUE(ftrace.ParseFromString(
+        config.data_sources()[0].config().ftrace_config_raw()));
+    EXPECT_THAT(ftrace.ftrace_events(), Contains("sched/sched_switch"));
+    EXPECT_THAT(ftrace.atrace_categories(), Contains("sw"));
+    EXPECT_TRUE(ftrace.symbolize_ksyms());
+  }
+  EXPECT_EQ(config.data_sources()[1].config().name(), "linux.ftrace");
+  {
+    protos::gen::FtraceConfig ftrace;
+    ASSERT_TRUE(ftrace.ParseFromString(
+        config.data_sources()[1].config().ftrace_config_raw()));
+    EXPECT_THAT(ftrace.ftrace_events(), Contains("hyp/*"));
+    ASSERT_EQ(ftrace.instance_name(), "hyp");
+  }
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/tools/protoprofile/main.cc b/src/tools/protoprofile/main.cc
index 4ab52f8..129ee44 100644
--- a/src/tools/protoprofile/main.cc
+++ b/src/tools/protoprofile/main.cc
@@ -34,7 +34,9 @@
 
 SELECT
   EXPERIMENTAL_PROFILE(
-    EXPERIMENTAL_PROTO_PATH_TO_STACK(path_id), 'size', 'bytes', size)
+    EXPERIMENTAL_PROTO_PATH_TO_STACK(path_id),
+    'size', 'bytes', size,
+    'proto', 'count', count)
 FROM EXPERIMENTAL_PROTO_CONTENT;
 
 )";
diff --git a/src/trace_processor/importers/proto/content_analyzer.cc b/src/trace_processor/importers/proto/content_analyzer.cc
index a6e97bc..a225f09 100644
--- a/src/trace_processor/importers/proto/content_analyzer.cc
+++ b/src/trace_processor/importers/proto/content_analyzer.cc
@@ -50,10 +50,12 @@
   for (auto sample = computer_.GetNext(); sample.has_value();
        sample = computer_.GetNext()) {
     auto* value = map.Find(computer_.GetPath());
-    if (value)
-      *value += *sample;
-    else
-      map.Insert(computer_.GetPath(), *sample);
+    if (value) {
+      value->size += *sample;
+      ++value->count;
+    } else {
+      map.Insert(computer_.GetPath(), Sample{*sample, 1});
+    }
   }
 }
 
@@ -123,8 +125,9 @@
       content_row.path =
           context_->storage->InternString(base::StringView(path_string));
       content_row.path_id = *previous_path_id;
-      content_row.total_size = static_cast<int64_t>(sample.value());
-      content_row.size = static_cast<int64_t>(sample.value());
+      content_row.total_size = static_cast<int64_t>(sample.value().size);
+      content_row.size = static_cast<int64_t>(sample.value().size);
+      content_row.count = static_cast<int64_t>(sample.value().count);
       context_->storage->mutable_experimental_proto_content_table()->Insert(
           content_row);
     }
diff --git a/src/trace_processor/importers/proto/content_analyzer.h b/src/trace_processor/importers/proto/content_analyzer.h
index 27ccded..84fd674 100644
--- a/src/trace_processor/importers/proto/content_analyzer.h
+++ b/src/trace_processor/importers/proto/content_analyzer.h
@@ -33,9 +33,13 @@
 // Interface for a module that processes track event information.
 class ProtoContentAnalyzer : public PacketAnalyzer {
  public:
+  struct Sample {
+    size_t size;
+    size_t count;
+  };
   using PathToSamplesMap =
       base::FlatHashMap<util::SizeProfileComputer::FieldPath,
-                        size_t,
+                        Sample,
                         util::SizeProfileComputer::FieldPathHasher>;
   using SampleAnnotation = PacketAnalyzer::SampleAnnotation;
 
diff --git a/src/trace_processor/tables/trace_proto_tables.h b/src/trace_processor/tables/trace_proto_tables.h
index f49b3b3..04579ea 100644
--- a/src/trace_processor/tables/trace_proto_tables.h
+++ b/src/trace_processor/tables/trace_proto_tables.h
@@ -40,7 +40,8 @@
   C(StringPool::Id, path)                                                 \
   C(ExperimentalProtoPathTable::Id, path_id)                              \
   C(int64_t, total_size)                                                  \
-  C(int64_t, size)
+  C(int64_t, size)                                                        \
+  C(int64_t, count)
 
 PERFETTO_TP_TABLE(PERFETTO_TP_EXPERIMENTAL_PROTO_CONTENT_TABLE_DEF);