Merge changes Ic1c3e077,I69b8ec0d into main

* changes:
  ui: Use plugin deps to add tracks to process groups
  ui: Don't render tracks while the trace is loading
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 6b02ff8..79bca11 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -464,40 +464,6 @@
   return track_id;
 }
 
-TrackId TrackTracker::LegacyInternCpuIdleStateTrack(uint32_t cpu,
-                                                    StringId state) {
-  auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
-  DimensionsBuilder dims_builder = CreateDimensionsBuilder();
-  dims_builder.AppendDimension(
-      context_->storage->InternString("cpu_idle_state"),
-      Variadic::String(state));
-  dims_builder.AppendUcpu(ucpu);
-  Dimensions dims_id = std::move(dims_builder).Build();
-
-  tracks::TrackClassification classification = tracks::cpu_idle_state;
-
-  auto* it = tracks_.Find({classification, dims_id});
-  if (it) {
-    return *it;
-  }
-
-  std::string name =
-      "cpuidle." + context_->storage->GetString(state).ToStdString();
-
-  tables::CpuCounterTrackTable::Row row(
-      context_->storage->InternString(name.c_str()));
-  row.ucpu = ucpu;
-  row.machine_id = context_->machine_id();
-  row.classification =
-      context_->storage->InternString(tracks::ToString(classification));
-  row.dimension_arg_set_id = dims_id.arg_set_id;
-
-  TrackId track_id =
-      context_->storage->mutable_cpu_counter_track_table()->Insert(row).id;
-  tracks_[{classification, dims_id}] = track_id;
-  return track_id;
-}
-
 TrackId TrackTracker::InternGpuCounterTrack(
     tracks::TrackClassification classification,
     uint32_t gpu_id,
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index d4d14f0..3736272 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -247,8 +247,6 @@
                                              bool trace_id_is_process_scoped,
                                              StringId source_scope);
 
-  TrackId LegacyInternCpuIdleStateTrack(uint32_t cpu, StringId state);
-
   TrackId LegacyCreateGpuCounterTrack(StringId name,
                                       uint32_t gpu_id,
                                       StringId description = StringId::Null(),
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 08212a8..574653a 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -166,6 +166,7 @@
       gpufreq_id(context->storage->InternString("gpufreq")),
       gpufreq_unit_id(context->storage->InternString("MHz")),
       cpu_stat_counter_name_id_(context->storage->InternString("counter_name")),
+      cpu_idle_state_id_(context_->storage->InternString("cpu_idle_state")),
       arm_cpu_implementer(
           context->storage->InternString("arm_cpu_implementer")),
       arm_cpu_architecture(
@@ -526,12 +527,20 @@
 void SystemProbesParser::ParseCpuIdleStats(int64_t ts, ConstBytes blob) {
   protos::pbzero::SysStats::CpuIdleState::Decoder cpuidle_state(blob);
   uint32_t cpu_id = cpuidle_state.cpu_id();
+  auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu_id);
   for (auto cpuidle_field = cpuidle_state.cpuidle_state_entry(); cpuidle_field;
        ++cpuidle_field) {
     protos::pbzero::SysStats::CpuIdleStateEntry::Decoder idle(*cpuidle_field);
 
-    TrackId track = context_->track_tracker->LegacyInternCpuIdleStateTrack(
-        cpu_id, context_->storage->InternString(idle.state()));
+    TrackTracker::DimensionsBuilder dims_builder =
+        context_->track_tracker->CreateDimensionsBuilder();
+    dims_builder.AppendDimension(
+        cpu_idle_state_id_,
+        Variadic::String(context_->storage->InternString(idle.state())));
+    dims_builder.AppendUcpu(ucpu);
+    TrackId track = context_->track_tracker->InternTrack(
+        tracks::cpu_idle_state, std::move(dims_builder).Build());
+
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(idle.duration_us()), track);
   }
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 87b5734..1db4b65 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -67,6 +67,8 @@
 
   const StringId cpu_stat_counter_name_id_;
 
+  const StringId cpu_idle_state_id_;
+
   // Arm CPU identifier string IDs
   const StringId arm_cpu_implementer;
   const StringId arm_cpu_architecture;
diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql
index 8bff4b3..5a4298e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql
@@ -21,6 +21,8 @@
 CREATE PERFETTO TABLE cpu_idle_time_in_state_counters(
   -- Timestamp.
   ts LONG,
+  -- The machine this residency is calculated for.
+  machine_id LONG,
   -- State name.
   state_name STRING,
   -- Percentage of time all CPUS spent in this state.
@@ -30,31 +32,51 @@
   -- Time all CPUS spent in any state, in microseconds.
   time_slice INT
 ) AS
-WITH residency_deltas AS (
+WITH cpu_counts_per_machine AS (
+  SELECT machine_id, count(1) AS cpu_count
+  FROM cpu
+  GROUP BY machine_id
+),
+idle_states AS (
   SELECT
-  ts,
-  c.name as state_name,
-  value - (LAG(value) OVER (PARTITION BY c.name, cct.cpu ORDER BY ts)) as delta
-  FROM counters c
-  JOIN cpu_counter_track cct on c.track_id=cct.id
-  WHERE c.name GLOB 'cpuidle.*'
+    c.ts,
+    c.value,
+    EXTRACT_ARG(t.dimension_arg_set_id, 'cpu_idle_state') as state,
+    EXTRACT_ARG(t.dimension_arg_set_id, 'ucpu') as ucpu
+  FROM counter c
+  JOIN track t on c.track_id = t.id
+  WHERE t.classification = 'cpu_idle_state'
+),
+residency_deltas AS (
+  SELECT
+    ts,
+    state,
+    ucpu,
+    value - (LAG(value) OVER (PARTITION BY state, ucpu ORDER BY ts)) as delta
+  FROM idle_states
 ),
 total_residency_calc AS (
-SELECT
-  ts,
-  state_name,
-  sum(delta) as total_residency,
-  -- Perfetto timestamp is in nanoseconds whereas sysfs cpuidle time
-  -- is in microseconds.
-  (
-    (SELECT count(distinct cpu) from cpu_counter_track) *
-    (time_to_us(ts - LAG(ts,1) over (partition by state_name order by ts)))
-  )  as time_slice
+  SELECT
+    ts,
+    cpu.machine_id,
+    state AS state_name,
+    SUM(delta) as total_residency,
+    -- Perfetto timestamp is in nanoseconds whereas sysfs cpuidle time
+    -- is in microseconds.
+    (
+      cpu_counts_per_machine.cpu_count *
+      (time_to_us(ts - LAG(ts, 1) over (PARTITION BY state ORDER BY ts)))
+    )  as time_slice
   FROM residency_deltas
-GROUP BY ts, state_name
+  JOIN cpu USING (ucpu)
+  -- The use of `IS` instead of `=` is intentional because machine_id can be
+  -- null and we still want this join to work in that case.
+  JOIN cpu_counts_per_machine ON cpu.machine_id IS cpu_counts_per_machine.machine_id
+  GROUP BY ts, cpu.machine_id, state
 )
 SELECT
   ts,
+  machine_id,
   state_name,
   MIN(100, (total_residency / time_slice) * 100) as idle_percentage,
   total_residency,
@@ -65,7 +87,8 @@
 -- Calculate c0 state by subtracting all other states from total time.
 SELECT
   ts,
-  'cpuidle.C0' as state_name,
+  machine_id,
+  'C0' as state_name,
   (MAX(0,time_slice - SUM(total_residency)) / time_slice) * 100 AS idle_percentage,
   time_slice - SUM(total_residency),
   time_slice
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
index dbaf4ac..9435c09 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
@@ -55,6 +55,22 @@
   base.freq_7 = lut7.freq_khz AND
   base.idle_7 = lut7.idle;
 
+-- Get nominal devfreq_dsu counter, OR use a dummy one for Pixel 9 VM traces
+-- The VM doesn't have a DSU, so the placeholder value of FMin is put in. The
+-- DSU frequency is a prerequisite for power estimation on Pixel 9.
+CREATE PERFETTO TABLE _dsu_frequency AS
+SELECT * from linux_devfreq_dsu_counter
+UNION ALL
+SELECT
+ 0 as id,
+ trace_start() as ts,
+ trace_end() - trace_start() as dur,
+ 610000 as dsu_freq
+-- Only add this for traces from a VM on Pixel 9 where DSU values aren't present
+WHERE (SELECT str_value FROM metadata WHERE name = 'android_guest_soc_model')
+  IN (SELECT device FROM _use_devfreq)
+  AND (SELECT COUNT(*) FROM linux_devfreq_dsu_counter) = 0;
+
 CREATE PERFETTO TABLE _w_dsu_dependence AS
 SELECT
   c.ts, c.dur,
@@ -80,10 +96,10 @@
 FROM _interval_intersect!(
   (
     _ii_subquery!(_cpu_curves),
-    _ii_subquery!(linux_devfreq_dsu_counter)
+    _ii_subquery!(_dsu_frequency)
   ),
   ()
 ) ii
 JOIN _cpu_curves AS c ON c._auto_id = id_0
-JOIN linux_devfreq_dsu_counter AS d on d._auto_id = id_1;
+JOIN _dsu_frequency AS d on d._auto_id = id_1;
 
diff --git a/test/data/wattson_tk4_vm.pb.sha256 b/test/data/wattson_tk4_vm.pb.sha256
new file mode 100644
index 0000000..52b09dd
--- /dev/null
+++ b/test/data/wattson_tk4_vm.pb.sha256
@@ -0,0 +1 @@
+12beb14bc06e28ecbdfc618d423aeea2badd449aeb9ccc65a457aa6c05ebf2be
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py b/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py
index 56e2959..ab23e0b 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py
@@ -50,15 +50,19 @@
         }
         """),
         query="""
-        SELECT ts, cct.name, value, cct.cpu
+        SELECT
+          ts,
+          EXTRACT_ARG(t.dimension_arg_set_id, 'cpu_idle_state') as state,
+          value,
+          EXTRACT_ARG(t.dimension_arg_set_id, 'ucpu') as cpu
         FROM counter c
-        JOIN cpu_counter_track cct on c.track_id = cct.id
+        JOIN track t on c.track_id = t.id
         ORDER BY ts;
         """,
         out=Csv("""
-        "ts","name","value","cpu"
-        71625871363623,"cpuidle.C8",486626084.000000,0
-        71626000387166,"cpuidle.C8",486636254.000000,0
+        "ts","state","value","cpu"
+        71625871363623,"C8",486626084.000000,0
+        71626000387166,"C8",486636254.000000,0
         """))
 
   def test_thermal_zones(self):
diff --git a/test/trace_processor/diff_tests/stdlib/linux/cpu.py b/test/trace_processor/diff_tests/stdlib/linux/cpu.py
index 1c38b5b..1783b17 100644
--- a/test/trace_processor/diff_tests/stdlib/linux/cpu.py
+++ b/test/trace_processor/diff_tests/stdlib/linux/cpu.py
@@ -566,9 +566,9 @@
          SELECT * FROM cpu_idle_time_in_state_counters;
          """,
         out=Csv("""
-         "ts","state_name","idle_percentage","total_residency","time_slice"
-          200001000000,"cpuidle.C8",10.000000,100.000000,1000
-          200002000000,"cpuidle.C8",10.000000,100.000000,1000
-          200001000000,"cpuidle.C0",90.000000,900.000000,1000
-          200002000000,"cpuidle.C0",90.000000,900.000000,1000
+          "ts","machine_id","state_name","idle_percentage","total_residency","time_slice"
+          200001000000,"[NULL]","C8",10.000000,100.000000,1000
+          200002000000,"[NULL]","C8",10.000000,100.000000,1000
+          200001000000,"[NULL]","C0",90.000000,900.000000,1000
+          200002000000,"[NULL]","C0",90.000000,900.000000,1000
          """))
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
index b5c9cae..5b20333 100644
--- a/test/trace_processor/diff_tests/stdlib/wattson/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -452,3 +452,30 @@
             452415394221,69579176303,13654,13361,11651,9609,1
             564873995228,135118729231,45223,37594,22798,20132,1
             """))
+
+  # Tests traces from VM that have incomplete CPU tracks
+  def test_wattson_missing_cpus_on_guest(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_tk4_vm.pb'),
+        query=("""
+            INCLUDE PERFETTO MODULE wattson.curves.estimates;
+               SELECT
+                 ts, dur, cpu0_mw, cpu1_mw, cpu2_mw, cpu3_mw, cpu4_mw, cpu5_mw,
+                 cpu6_mw
+               FROM _system_state_mw
+               WHERE ts > 25150000000
+               LIMIT 10
+            """),
+        out=Csv("""
+            "ts","dur","cpu0_mw","cpu1_mw","cpu2_mw","cpu3_mw","cpu4_mw","cpu5_mw","cpu6_mw"
+            25150029000,1080,0.000000,0.000000,0.000000,0.000000,70.050000,83.260000,0.000000
+            25150030640,42920,0.000000,0.000000,0.000000,0.000000,70.050000,70.050000,0.000000
+            25150073560,99800,0.000000,0.000000,0.000000,0.000000,70.050000,0.000000,0.000000
+            25150173360,28240,176.280000,0.000000,0.000000,0.000000,70.050000,0.000000,0.000000
+            25150201600,6480,176.280000,0.000000,0.000000,176.280000,70.050000,0.000000,0.000000
+            25150208080,29840,176.280000,0.000000,0.000000,176.280000,70.050000,70.050000,0.000000
+            25150237920,129800,0.000000,0.000000,0.000000,176.280000,70.050000,70.050000,0.000000
+            25150367720,37480,0.000000,0.000000,0.000000,176.280000,70.050000,0.000000,0.000000
+            25150405200,15120,0.000000,176.280000,0.000000,176.280000,70.050000,0.000000,0.000000
+            25150420320,15920,0.000000,176.280000,0.000000,0.000000,70.050000,0.000000,0.000000
+            """))
diff --git a/ui/src/assets/explore_page.scss b/ui/src/assets/explore_page.scss
new file mode 100644
index 0000000..c31b288
--- /dev/null
+++ b/ui/src/assets/explore_page.scss
@@ -0,0 +1,16 @@
+// 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.
+.explore-page {
+  overflow: auto;
+}
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 3e04c42..cb4387d 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -30,6 +30,7 @@
 @import "viz_page";
 @import "widgets_page";
 @import "plugins_page";
+@import "explore_page";
 
 // Widgets - keep these sorted (they should NOT have any inter-dependencies)
 @import "widgets/anchor";
diff --git a/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts b/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts
index 3e4aaa1..e180585 100644
--- a/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts
+++ b/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts
@@ -65,11 +65,13 @@
       this.addCounterTrack(
         ctx,
         it.state_name,
-        `select
+        `
+          select
             ts,
             idle_percentage as value
-        from cpu_idle_time_in_state_counters
-        where state_name='${it.state_name}'`,
+          from cpu_idle_time_in_state_counters
+          where state_name = '${it.state_name}'
+        `,
         group,
         {unit: 'percent'},
       );