Merge "Revert^2 "Add input method editor (IME) protos"" into main
diff --git a/Android.bp b/Android.bp
index 63d060b..73b4ae5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5256,6 +5256,7 @@
         "protos/perfetto/metrics/android/ion_metric.proto",
         "protos/perfetto/metrics/android/irq_runtime_metric.proto",
         "protos/perfetto/metrics/android/jank_cuj_metric.proto",
+        "protos/perfetto/metrics/android/java_heap_class_stats.proto",
         "protos/perfetto/metrics/android/java_heap_histogram.proto",
         "protos/perfetto/metrics/android/java_heap_stats.proto",
         "protos/perfetto/metrics/android/lmk_metric.proto",
@@ -5346,6 +5347,7 @@
         "protos/perfetto/metrics/android/ion_metric.proto",
         "protos/perfetto/metrics/android/irq_runtime_metric.proto",
         "protos/perfetto/metrics/android/jank_cuj_metric.proto",
+        "protos/perfetto/metrics/android/java_heap_class_stats.proto",
         "protos/perfetto/metrics/android/java_heap_histogram.proto",
         "protos/perfetto/metrics/android/java_heap_stats.proto",
         "protos/perfetto/metrics/android/lmk_metric.proto",
@@ -5420,6 +5422,7 @@
         "protos/perfetto/metrics/android/ion_metric.proto",
         "protos/perfetto/metrics/android/irq_runtime_metric.proto",
         "protos/perfetto/metrics/android/jank_cuj_metric.proto",
+        "protos/perfetto/metrics/android/java_heap_class_stats.proto",
         "protos/perfetto/metrics/android/java_heap_histogram.proto",
         "protos/perfetto/metrics/android/java_heap_stats.proto",
         "protos/perfetto/metrics/android/lmk_metric.proto",
@@ -12624,6 +12627,7 @@
         "src/trace_processor/metrics/sql/android/jank/relevant_slices.sql",
         "src/trace_processor/metrics/sql/android/jank/relevant_threads.sql",
         "src/trace_processor/metrics/sql/android/jank/slices.sql",
+        "src/trace_processor/metrics/sql/android/java_heap_class_stats.sql",
         "src/trace_processor/metrics/sql/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/sql/android/java_heap_stats.sql",
         "src/trace_processor/metrics/sql/android/mem_stats_priority_breakdown.sql",
diff --git a/BUILD b/BUILD
index 6953e48..2f53ea5 100644
--- a/BUILD
+++ b/BUILD
@@ -2041,6 +2041,7 @@
         "src/trace_processor/metrics/sql/android/jank/relevant_slices.sql",
         "src/trace_processor/metrics/sql/android/jank/relevant_threads.sql",
         "src/trace_processor/metrics/sql/android/jank/slices.sql",
+        "src/trace_processor/metrics/sql/android/java_heap_class_stats.sql",
         "src/trace_processor/metrics/sql/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/sql/android/java_heap_stats.sql",
         "src/trace_processor/metrics/sql/android/mem_stats_priority_breakdown.sql",
@@ -4480,6 +4481,7 @@
         "protos/perfetto/metrics/android/ion_metric.proto",
         "protos/perfetto/metrics/android/irq_runtime_metric.proto",
         "protos/perfetto/metrics/android/jank_cuj_metric.proto",
+        "protos/perfetto/metrics/android/java_heap_class_stats.proto",
         "protos/perfetto/metrics/android/java_heap_histogram.proto",
         "protos/perfetto/metrics/android/java_heap_stats.proto",
         "protos/perfetto/metrics/android/lmk_metric.proto",
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 5430c22..2bb8c0e 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -52,6 +52,7 @@
     "ion_metric.proto",
     "irq_runtime_metric.proto",
     "jank_cuj_metric.proto",
+    "java_heap_class_stats.proto",
     "java_heap_histogram.proto",
     "java_heap_stats.proto",
     "lmk_metric.proto",
diff --git a/protos/perfetto/metrics/android/java_heap_class_stats.proto b/protos/perfetto/metrics/android/java_heap_class_stats.proto
new file mode 100644
index 0000000..ecde153
--- /dev/null
+++ b/protos/perfetto/metrics/android/java_heap_class_stats.proto
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+syntax = "proto2";
+package perfetto.protos;
+
+import "protos/perfetto/metrics/android/process_metadata.proto";
+
+message JavaHeapClassStats {
+  // Next id: 11
+  message TypeCount {
+    optional string type_name = 1;
+    optional int64 obj_count = 2;
+    optional int64 size_bytes = 3;
+    optional int64 native_size_bytes = 4;
+    optional int64 reachable_obj_count = 5;
+    optional int64 reachable_size_bytes = 6;
+    optional int64 reachable_native_size_bytes = 7;
+    optional int64 dominated_obj_count = 8;
+    optional int64 dominated_size_bytes = 9;
+    optional int64 dominated_native_size_bytes = 10;
+  }
+
+  message Sample {
+    optional int64 ts = 1;
+    repeated TypeCount type_count = 2;
+  }
+
+  // Stats per process. One sample per dump (with continuous dump you can
+  // have more samples differentiated by ts).
+  message InstanceStats {
+    optional uint32 upid = 1;
+    optional perfetto.protos.AndroidProcessMetadata process = 2;
+    repeated Sample samples = 3;
+  }
+
+  repeated InstanceStats instance_stats = 1;
+}
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index c3025c3..e5a9553 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -48,6 +48,7 @@
 import "protos/perfetto/metrics/android/irq_runtime_metric.proto";
 import "protos/perfetto/metrics/android/jank_cuj_metric.proto";
 import "protos/perfetto/metrics/android/java_heap_histogram.proto";
+import "protos/perfetto/metrics/android/java_heap_class_stats.proto";
 import "protos/perfetto/metrics/android/java_heap_stats.proto";
 import "protos/perfetto/metrics/android/lmk_metric.proto";
 import "protos/perfetto/metrics/android/lmk_reason_metric.proto";
@@ -118,7 +119,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 66
+// Next id: 68
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -170,6 +171,9 @@
   // If the trace contains a heap graph, output histogram.
   optional JavaHeapHistogram java_heap_histogram = 21;
 
+  // If the trace contains a heap graph, output stats per heap class.
+  optional JavaHeapClassStats java_heap_class_stats = 67;
+
   // Metrics used to find potential culprits of low-memory kills.
   optional AndroidLmkReasonMetric android_lmk_reason = 18;
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 86f7d59..a4da778 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -1441,6 +1441,41 @@
 
 // End of protos/perfetto/metrics/android/jank_cuj_metric.proto
 
+// Begin of protos/perfetto/metrics/android/java_heap_class_stats.proto
+
+message JavaHeapClassStats {
+  // Next id: 11
+  message TypeCount {
+    optional string type_name = 1;
+    optional int64 obj_count = 2;
+    optional int64 size_bytes = 3;
+    optional int64 native_size_bytes = 4;
+    optional int64 reachable_obj_count = 5;
+    optional int64 reachable_size_bytes = 6;
+    optional int64 reachable_native_size_bytes = 7;
+    optional int64 dominated_obj_count = 8;
+    optional int64 dominated_size_bytes = 9;
+    optional int64 dominated_native_size_bytes = 10;
+  }
+
+  message Sample {
+    optional int64 ts = 1;
+    repeated TypeCount type_count = 2;
+  }
+
+  // Stats per process. One sample per dump (with continuous dump you can
+  // have more samples differentiated by ts).
+  message InstanceStats {
+    optional uint32 upid = 1;
+    optional perfetto.protos.AndroidProcessMetadata process = 2;
+    repeated Sample samples = 3;
+  }
+
+  repeated InstanceStats instance_stats = 1;
+}
+
+// End of protos/perfetto/metrics/android/java_heap_class_stats.proto
+
 // Begin of protos/perfetto/metrics/android/java_heap_histogram.proto
 
 message JavaHeapHistogram {
@@ -2608,7 +2643,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 66
+// Next id: 68
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -2660,6 +2695,9 @@
   // If the trace contains a heap graph, output histogram.
   optional JavaHeapHistogram java_heap_histogram = 21;
 
+  // If the trace contains a heap graph, output stats per heap class.
+  optional JavaHeapClassStats java_heap_class_stats = 67;
+
   // Metrics used to find potential culprits of low-memory kills.
   optional AndroidLmkReasonMetric android_lmk_reason = 18;
 
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index c75f932..277a251 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -622,5 +622,7 @@
     FastrpcDmaMapFtraceEvent fastrpc_dma_map = 501;
     GoogleIccEventFtraceEvent google_icc_event = 502;
     GoogleIrmEventFtraceEvent google_irm_event = 503;
+    DevicePmCallbackStartFtraceEvent device_pm_callback_start = 504;
+    DevicePmCallbackEndFtraceEvent device_pm_callback_end = 505;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/power.proto b/protos/perfetto/trace/ftrace/power.proto
index 4460ba6..230095b 100644
--- a/protos/perfetto/trace/ftrace/power.proto
+++ b/protos/perfetto/trace/ftrace/power.proto
@@ -57,3 +57,15 @@
   optional uint64 end_time_ns = 4;
   optional uint64 total_active_duration_ns = 5;
 }
+message DevicePmCallbackStartFtraceEvent {
+  optional string device = 1;
+  optional string driver = 2;
+  optional string parent = 3;
+  optional string pm_ops = 4;
+  optional int32 event = 5;
+}
+message DevicePmCallbackEndFtraceEvent {
+  optional string device = 1;
+  optional string driver = 2;
+  optional int32 error = 3;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 20fc8de..6d7c098 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -9527,6 +9527,18 @@
   optional uint64 end_time_ns = 4;
   optional uint64 total_active_duration_ns = 5;
 }
+message DevicePmCallbackStartFtraceEvent {
+  optional string device = 1;
+  optional string driver = 2;
+  optional string parent = 3;
+  optional string pm_ops = 4;
+  optional int32 event = 5;
+}
+message DevicePmCallbackEndFtraceEvent {
+  optional string device = 1;
+  optional string driver = 2;
+  optional int32 error = 3;
+}
 
 // End of protos/perfetto/trace/ftrace/power.proto
 
@@ -10797,6 +10809,8 @@
     FastrpcDmaMapFtraceEvent fastrpc_dma_map = 501;
     GoogleIccEventFtraceEvent google_icc_event = 502;
     GoogleIrmEventFtraceEvent google_irm_event = 503;
+    DevicePmCallbackStartFtraceEvent device_pm_callback_start = 504;
+    DevicePmCallbackEndFtraceEvent device_pm_callback_end = 505;
   }
 }
 
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index c337e46..59b0f2a 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index 0bc44e0..737e76d 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -498,3 +498,5 @@
 fastrpc/fastrpc_dma_map
 google_icc_trace/google_icc_event
 google_irm_trace/google_irm_event
+power/device_pm_callback_start
+power/device_pm_callback_end
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index aafa9b4..f3fe1ff 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<FtraceMessageDescriptor, 504> descriptors{{
+std::array<FtraceMessageDescriptor, 506> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5565,6 +5565,28 @@
             {"timestamp", ProtoSchemaType::kUint64},
         },
     },
+    {
+        "device_pm_callback_start",
+        5,
+        {
+            {},
+            {"device", ProtoSchemaType::kString},
+            {"driver", ProtoSchemaType::kString},
+            {"parent", ProtoSchemaType::kString},
+            {"pm_ops", ProtoSchemaType::kString},
+            {"event", ProtoSchemaType::kInt32},
+        },
+    },
+    {
+        "device_pm_callback_end",
+        3,
+        {
+            {},
+            {"device", ProtoSchemaType::kString},
+            {"driver", ProtoSchemaType::kString},
+            {"error", ProtoSchemaType::kInt32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index 7de8cf6..9eb8b04 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -92,6 +92,7 @@
     "jank/relevant_slices.sql",
     "jank/relevant_threads.sql",
     "jank/slices.sql",
+    "java_heap_class_stats.sql",
     "java_heap_histogram.sql",
     "java_heap_stats.sql",
     "mem_stats_priority_breakdown.sql",
diff --git a/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql b/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql
new file mode 100644
index 0000000..531b440
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql
@@ -0,0 +1,109 @@
+--
+-- Copyright 2024 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
+--
+--     https://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.
+--
+
+SELECT RUN_METRIC('android/process_metadata.sql');
+
+INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+INCLUDE PERFETTO MODULE graphs.partition;
+
+DROP TABLE IF EXISTS _heap_graph_dominator_tree_for_partition;
+CREATE PERFETTO TABLE _heap_graph_dominator_tree_for_partition AS
+SELECT
+  tree.id,
+  tree.idom_id as parent_id,
+  obj.type_id as group_key
+FROM memory_heap_graph_dominator_tree tree
+JOIN heap_graph_object obj USING(id)
+UNION ALL
+-- provide a single root required by tree partition if heap graph exists.
+SELECT
+  memory_heap_graph_super_root_fn() AS id,
+  NULL AS parent_id,
+  (SELECT MAX(id) + 1 FROM heap_graph_class) AS group_key
+WHERE memory_heap_graph_super_root_fn() IS NOT NULL;
+
+DROP TABLE IF EXISTS _heap_object_marked_for_dominated_stats;
+CREATE PERFETTO TABLE _heap_object_marked_for_dominated_stats AS
+SELECT
+  id,
+  IIF(parent_id IS NULL, 1, 0) as marked
+FROM tree_structural_partition_by_group!(_heap_graph_dominator_tree_for_partition)
+ORDER BY id;
+
+DROP TABLE IF EXISTS _heap_class_stats;
+CREATE PERFETTO TABLE _heap_class_stats AS
+SELECT
+  obj.upid,
+  obj.graph_sample_ts,
+  obj.type_id,
+  COUNT(1) AS obj_count,
+  SUM(self_size) AS size_bytes,
+  SUM(native_size) AS native_size_bytes,
+  SUM(IIF(obj.reachable, 1, 0)) AS reachable_obj_count,
+  SUM(IIF(obj.reachable, self_size, 0)) AS reachable_size_bytes,
+  SUM(IIF(obj.reachable, native_size, 0)) AS reachable_native_size_bytes,
+  SUM(IIF(marked, dominated_obj_count, 0)) AS dominated_obj_count,
+  SUM(IIF(marked, dominated_size_bytes, 0)) AS dominated_size_bytes,
+  SUM(IIF(marked, dominated_native_size_bytes, 0)) AS dominated_native_size_bytes
+FROM heap_graph_object obj
+-- Left joins to preserve unreachable objects.
+LEFT JOIN _heap_object_marked_for_dominated_stats USING(id)
+LEFT JOIN memory_heap_graph_dominator_tree USING(id)
+GROUP BY 1, 2, 3
+ORDER BY 1, 2, 3;
+
+DROP VIEW IF EXISTS java_heap_class_stats_output;
+CREATE PERFETTO VIEW java_heap_class_stats_output AS
+WITH
+-- Group by to build the repeated field by upid, ts
+heap_class_stats_count_protos AS (
+  SELECT
+    upid,
+    graph_sample_ts,
+    RepeatedField(JavaHeapClassStats_TypeCount(
+      'type_name', IFNULL(c.deobfuscated_name, c.name),
+      'obj_count', obj_count,
+      'size_bytes', size_bytes,
+      'native_size_bytes', native_size_bytes,
+      'reachable_obj_count', reachable_obj_count,
+      'reachable_size_bytes', reachable_size_bytes,
+      'reachable_native_size_bytes', reachable_native_size_bytes,
+      'dominated_obj_count', dominated_obj_count,
+      'dominated_size_bytes', dominated_size_bytes,
+      'dominated_native_size_bytes', dominated_native_size_bytes
+    )) AS count_protos
+  FROM _heap_class_stats s
+  JOIN heap_graph_class c ON s.type_id = c.id
+  GROUP BY 1, 2
+),
+-- Group by to build the repeated field by upid
+heap_class_stats_sample_protos AS (
+  SELECT
+    upid,
+    RepeatedField(JavaHeapClassStats_Sample(
+      'ts', graph_sample_ts,
+      'type_count', count_protos
+    )) AS sample_protos
+  FROM heap_class_stats_count_protos
+  GROUP BY 1
+)
+SELECT JavaHeapClassStats(
+  'instance_stats', RepeatedField(JavaHeapClassStats_InstanceStats(
+    'upid', upid,
+    'process', process_metadata.metadata,
+    'samples', sample_protos
+  )))
+FROM heap_class_stats_sample_protos JOIN process_metadata USING (upid);
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 23fc16a..37a1b1a 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7869,6 +7869,44 @@
        kUnsetFtraceId,
        488,
        kUnsetSize},
+      {"device_pm_callback_start",
+       "power",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "device", 1, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "driver", 2, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "parent", 3, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "pm_ops", 4, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "event", 5, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       504,
+       kUnsetSize},
+      {"device_pm_callback_end",
+       "power",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "device", 1, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "driver", 2, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "error", 3, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       505,
+       kUnsetSize},
       {"console",
        "printk",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/power/device_pm_callback_end/format b/src/traced/probes/ftrace/test/data/synthetic/events/power/device_pm_callback_end/format
new file mode 100644
index 0000000..8742e41
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/power/device_pm_callback_end/format
@@ -0,0 +1,13 @@
+name: device_pm_callback_end
+ID: 185
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:__data_loc char[] device;	offset:8;	size:4;	signed:0;
+	field:__data_loc char[] driver;	offset:12;	size:4;	signed:0;
+	field:int error;	offset:16;	size:4;	signed:1;
+
+print fmt: "%s %s, err=%d", __get_str(driver), __get_str(device), REC->error
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/power/device_pm_callback_start/format b/src/traced/probes/ftrace/test/data/synthetic/events/power/device_pm_callback_start/format
new file mode 100644
index 0000000..3d65009
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/power/device_pm_callback_start/format
@@ -0,0 +1,15 @@
+name: device_pm_callback_start
+ID: 184
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:__data_loc char[] device;	offset:8;	size:4;	signed:0;
+	field:__data_loc char[] driver;	offset:12;	size:4;	signed:0;
+	field:__data_loc char[] parent;	offset:16;	size:4;	signed:0;
+	field:__data_loc char[] pm_ops;	offset:20;	size:4;	signed:0;
+	field:int event;	offset:24;	size:4;	signed:1;
+
+print fmt: "%s %s, parent: %s, %s[%s]", __get_str(driver), __get_str(device), __get_str(parent), __get_str(pm_ops), __print_symbolic(REC->event, { 0x0002, "suspend" }, { 0x0010, "resume" }, { 0x0001, "freeze" }, { 0x0008, "quiesce" }, { 0x0004, "hibernate" }, { 0x0020, "thaw" }, { 0x0040, "restore" }, { 0x0080, "recover" })
diff --git a/src/tracing/core/id_allocator.h b/src/tracing/core/id_allocator.h
index 90df970..97c9981 100644
--- a/src/tracing/core/id_allocator.h
+++ b/src/tracing/core/id_allocator.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 
+#include <cstddef>
 #include <type_traits>
 #include <vector>
 
diff --git a/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out b/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out
new file mode 100644
index 0000000..2a2a361
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out
@@ -0,0 +1,73 @@
+java_heap_class_stats {
+  instance_stats {
+    upid: 2
+    process {
+      name: "system_server"
+      uid: 1000
+      pid: 2
+    }
+    samples {
+      ts: 10
+      type_count {
+        type_name: "FactoryProducerDelegateImplActor"
+        obj_count: 1
+        size_bytes: 64
+        native_size_bytes: 0
+        reachable_obj_count: 1
+        reachable_size_bytes: 64
+        reachable_native_size_bytes: 0
+        dominated_obj_count: 2
+        dominated_size_bytes: 96
+        dominated_native_size_bytes: 0
+      }
+      type_count {
+        type_name: "Foo"
+        obj_count: 2
+        size_bytes: 160
+        native_size_bytes: 0
+        reachable_obj_count: 1
+        reachable_size_bytes: 32
+        reachable_native_size_bytes: 0
+        dominated_obj_count: 1
+        dominated_size_bytes: 32
+        dominated_native_size_bytes: 0
+      }
+      type_count {
+        type_name: "DeobfuscatedA"
+        obj_count: 1
+        size_bytes: 1024
+        native_size_bytes: 0
+        reachable_obj_count: 0
+        reachable_size_bytes: 0
+        reachable_native_size_bytes: 0
+        dominated_obj_count: 0
+        dominated_size_bytes: 0
+        dominated_native_size_bytes: 0
+      }
+      type_count {
+        type_name: "DeobfuscatedA[]"
+        obj_count: 1
+        size_bytes: 256
+        native_size_bytes: 0
+        reachable_obj_count: 1
+        reachable_size_bytes: 256
+        reachable_native_size_bytes: 0
+        dominated_obj_count: 1
+        dominated_size_bytes: 256
+        dominated_native_size_bytes: 0
+      }
+      type_count {
+        type_name: "java.lang.Class<DeobfuscatedA[]>"
+        obj_count: 1
+        size_bytes: 256
+        native_size_bytes: 0
+        reachable_obj_count: 0
+        reachable_size_bytes: 0
+        reachable_native_size_bytes: 0
+        dominated_obj_count: 0
+        dominated_size_bytes: 0
+        dominated_native_size_bytes: 0
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/diff_tests/metrics/profiling/tests.py b/test/trace_processor/diff_tests/metrics/profiling/tests.py
index 9a4ff07..94dea75 100644
--- a/test/trace_processor/diff_tests/metrics/profiling/tests.py
+++ b/test/trace_processor/diff_tests/metrics/profiling/tests.py
@@ -109,3 +109,18 @@
         trace=Path('heap_graph.textproto'),
         query=Metric('java_heap_histogram'),
         out=Path('java_heap_histogram.out'))
+
+  def test_java_heap_class_stats(self):
+    return DiffTestBlueprint(
+        trace=Path('heap_graph.textproto'),
+        query=Metric('java_heap_class_stats'),
+        out=Path('java_heap_class_stats.out'))
+
+  def test_java_heap_class_stats_no_heap_graph(self):
+    return DiffTestBlueprint(
+        trace=Path('heap_profile_no_symbols.textproto'),
+        query=Metric('java_heap_class_stats'),
+        out=TextProto(r"""
+        java_heap_class_stats {
+        }
+        """))
diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py
index 2eba2b0..0ef3dc6 100644
--- a/test/trace_processor/diff_tests/syntax/table_tests.py
+++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -150,3 +150,30 @@
         "col_type"
         "id"
         """))
+
+  def test_distinct_trivial(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        query="""
+        WITH trivial_count AS (
+          SELECT DISTINCT name AS c FROM slice
+        ),
+        few_results AS (
+          SELECT DISTINCT depth AS c FROM slice
+        ),
+        simple_nullable AS (
+          SELECT DISTINCT parent_id AS c FROM slice
+        ),
+        selector AS (
+          SELECT DISTINCT cpu AS c FROM ftrace_event
+        )
+        SELECT
+          (SELECT COUNT(*) FROM trivial_count) AS name,
+          (SELECT COUNT(*) FROM few_results) AS depth,
+          (SELECT COUNT(*) FROM simple_nullable) AS parent_id,
+          (SELECT COUNT(*) FROM selector) AS cpu_from_ftrace;
+        """,
+        out=Csv("""
+        "name","depth","parent_id","cpu_from_ftrace"
+        3073,8,4529,8
+        """))
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index ff66708..55a2cd9 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -453,11 +453,16 @@
   font-size: 11px;
   font-family: var(--monospace-font);
 
-  .colour {
-    display: inline-block;
-    height: 10px;
-    width: 10px;
-    margin-right: 4px;
+  .pf-ftrace-namebox {
+    display: flex;
+    align-items: center;
+
+    .pf-ftrace-colorbox {
+      display: inline-block;
+      height: 10px;
+      width: 10px;
+      margin-right: 4px;
+    }
   }
 }
 
diff --git a/ui/src/assets/widgets/virtual_table.scss b/ui/src/assets/widgets/virtual_table.scss
index acd22d5..9057db8 100644
--- a/ui/src/assets/widgets/virtual_table.scss
+++ b/ui/src/assets/widgets/virtual_table.scss
@@ -67,6 +67,8 @@
 
         .pf-vtable-puck {
           .pf-vtable-row {
+            display: flex;
+            align-items: center;
             white-space: nowrap;
             padding-inline: 4px;
 
diff --git a/ui/src/base/hotkeys.ts b/ui/src/base/hotkeys.ts
index ea278d1..bb43cec 100644
--- a/ui/src/base/hotkeys.ts
+++ b/ui/src/base/hotkeys.ts
@@ -104,6 +104,27 @@
 type AllowInEditable = '!' | '';
 export type Hotkey = `${AllowInEditable}${Modifier}${Key}`;
 
+// The following list of keys cannot be pressed wither with or without the
+// presence of the Shift modifier on most keyboard layouts. Thus we should
+// ignore shift in these cases.
+const shiftExceptions = [
+  '0',
+  '1',
+  '2',
+  '3',
+  '4',
+  '5',
+  '6',
+  '7',
+  '8',
+  '9',
+  '/',
+  '?',
+  '!',
+  '[',
+  ']',
+];
+
 // Represents a deconstructed hotkey.
 export interface HotkeyParts {
   // The name of the primary key of this hotkey.
@@ -191,7 +212,8 @@
 
   // For certain keys we relax the shift requirement, as they usually cannot be
   // pressed without the shift key on English keyboards.
-  const shiftOk = key.match(/[\?\!]/) || shiftKey === wantShift;
+  const shiftOk =
+    shiftExceptions.includes(key as string) || shiftKey === wantShift;
 
   return (
     metaKey === wantMeta &&
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index dc65d19..3dea6fd 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -466,7 +466,7 @@
       id: 'perfetto.Search',
       name: 'Search',
       callback: () => this.enterSearchMode(true),
-      defaultHotkey: '!Mod+S',
+      defaultHotkey: '/',
     },
     {
       id: 'perfetto.ShowHelp',
diff --git a/ui/src/frontend/help_modal.ts b/ui/src/frontend/help_modal.ts
index 23f1c32..d08f6e7 100644
--- a/ui/src/frontend/help_modal.ts
+++ b/ui/src/frontend/help_modal.ts
@@ -247,11 +247,7 @@
           m('td', keycap(ctrlOrCmd), ' + ', keycap('o')),
           m('td', 'Run query'),
         ),
-        m(
-          'tr',
-          m('td', keycap(ctrlOrCmd), ' + ', keycap('s')),
-          m('td', 'Search'),
-        ),
+        m('tr', m('td', keycap('/')), m('td', 'Search')),
         m('tr', m('td', keycap('q')), m('td', 'Toggle tab drawer')),
         ...sidebarInstructions,
         m('tr', m('td', keycap('?')), m('td', 'Show help')),
diff --git a/ui/src/tracks/ftrace/ftrace_explorer.ts b/ui/src/tracks/ftrace/ftrace_explorer.ts
index 5e7eb32..d8cad78 100644
--- a/ui/src/tracks/ftrace/ftrace_explorer.ts
+++ b/ui/src/tracks/ftrace/ftrace_explorer.ts
@@ -188,7 +188,11 @@
         cells: [
           id,
           timestamp,
-          m('', m('span.colour', {style: {background: color}}), name),
+          m(
+            '.pf-ftrace-namebox',
+            m('.pf-ftrace-colorbox', {style: {background: color}}),
+            name,
+          ),
           cpu,
           process,
           args,