Merge "tp: rewrite DbSqliteTable -> DbSqliteModule and remove SqliteTable" into main
diff --git a/Android.bp b/Android.bp
index 3b4d4d2..3265c66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12364,6 +12364,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
@@ -12399,6 +12400,8 @@
         "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
         "src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql",
         "src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql",
         "src/trace_processor/perfetto_sql/stdlib/cpu/size.sql",
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql",
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql",
@@ -12437,6 +12440,10 @@
         "src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql",
         "src/trace_processor/perfetto_sql/stdlib/time/conversion.sql",
         "src/trace_processor/perfetto_sql/stdlib/v8/jit.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
     ],
     cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
     out: [
diff --git a/BUILD b/BUILD
index c20715b..032036c 100644
--- a/BUILD
+++ b/BUILD
@@ -2414,6 +2414,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
@@ -2468,6 +2469,8 @@
     name = "src_trace_processor_perfetto_sql_stdlib_cpu_cpu",
     srcs = [
         "src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql",
         "src/trace_processor/perfetto_sql/stdlib/cpu/size.sql",
     ],
 )
@@ -2600,6 +2603,17 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/wattson:wattson
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib:stdlib
 perfetto_cc_amalgamated_sql(
     name = "src_trace_processor_perfetto_sql_stdlib_stdlib",
@@ -2624,6 +2638,7 @@
         ":src_trace_processor_perfetto_sql_stdlib_stack_trace_stack_trace",
         ":src_trace_processor_perfetto_sql_stdlib_time_time",
         ":src_trace_processor_perfetto_sql_stdlib_v8_v8",
+        ":src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
     ],
     outs = [
         "src/trace_processor/perfetto_sql/stdlib/stdlib.h",
diff --git a/CHANGELOG b/CHANGELOG
index abdf0f9..788d61f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,16 @@
   Trace Processor:
     *
   UI:
+    *
+  SDK:
+    *
+
+
+v44.0 - 2024-04-10:
+  Trace Processor:
+    * New modules added to standard library: `frames.timeline`,
+      `frame.per_frame_metrics`, `sched.time_in_state`.
+  UI:
     * Per-cpu scheduling tracks now distinguish real-time priority threads with
       a hatched pattern and name prefix. Based on priority during switch-in.
     * Added ftrace event cropping for traces recorded by perfetto v44+.
@@ -11,6 +21,19 @@
       per-cpu data streams. This should significantly improve the presentation
       of RING_BUFFER traces, removing artifacts such as never-ending slices
       starting at the beginning of the trace.
+    * Significantly improved trace load times.
+    * Improved counter track view modes, including log scale, expanded view, and
+      the ability for plugin authors to link scales of several counter tracks
+      together.
+    * Add dominated size and objects to Java heap graph view.
+      Added hotkey Q to open and close bottom drawer.
+    * Fixed bug where timeline header and tracks could become horizontally
+      misaligned when using browser zoom.
+    * Fixed crash when hot-reloading Sass during development.
+    * Fixed bug where crashed debug tracks could not be closed.
+    * Fixed missing flame graph details for area selections.
+    * Consistent reporting of durations for incomplete slices.
+    * Switch to using prettier for formatting TS & Sass instead of ESLint.
   SDK:
     * "track_event" categories are disabled by default in the C API, if they
       don't match anything in the data source config. This behavior differs from
diff --git a/gn/write_buildflag_header.py b/gn/write_buildflag_header.py
index 30751a5..3f11a2f 100644
--- a/gn/write_buildflag_header.py
+++ b/gn/write_buildflag_header.py
@@ -82,7 +82,7 @@
   lines.append('#endif  // %s' % guard)
   lines.append('')
 
-  with open(args.out, 'w') as out:
+  with open(args.out, 'w', newline='\n') as out:
     out.write(COPYRIGHT_HEADER)
     out.write('\n'.join(lines))
 
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 640647b..2163b80 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -19,7 +19,6 @@
 #include <optional>
 
 #include "src/trace_processor/importers/common/args_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index bc832bf..b648907 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -1012,11 +1012,11 @@
         break;
       }
       case FtraceEvent::kFuncgraphEntryFieldNumber: {
-        ParseFuncgraphEntry(ts, pid, fld_bytes, seq_state);
+        ParseFuncgraphEntry(ts, cpu, pid, fld_bytes, seq_state);
         break;
       }
       case FtraceEvent::kFuncgraphExitFieldNumber: {
-        ParseFuncgraphExit(ts, pid, fld_bytes, seq_state);
+        ParseFuncgraphExit(ts, cpu, pid, fld_bytes, seq_state);
         break;
       }
       case FtraceEvent::kV4l2QbufFieldNumber:
@@ -3194,37 +3194,54 @@
 
 void FtraceParser::ParseFuncgraphEntry(
     int64_t timestamp,
+    uint32_t cpu,
     uint32_t pid,
     protozero::ConstBytes blob,
     PacketSequenceStateGeneration* seq_state) {
-  // TODO(rsavitski): remove if/when we stop collapsing all idle (swapper)
-  // threads to a single track, otherwise this breaks slice nesting.
-  if (pid == 0)
-    return;
-
   protos::pbzero::FuncgraphEntryFtraceEvent::Decoder evt(blob.data, blob.size);
   StringId name_id = InternedKernelSymbolOrFallback(evt.func(), seq_state);
 
-  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
-  TrackId track = context_->track_tracker->InternThreadTrack(utid);
+  TrackId track = {};
+  if (pid != 0) {
+    // common case: normal thread
+    UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+    track = context_->track_tracker->InternThreadTrack(utid);
+  } else {
+    // Idle threads (swapper) are implicit, and all share the same thread id 0.
+    // Therefore we cannot use a thread-scoped track because many instances
+    // of swapper might be running concurrently. Fall back onto global tracks
+    // (one per cpu).
+    base::StackString<255> track_name("swapper%" PRIu32 "-funcgraph", cpu);
+    StringId track_name_id =
+        context_->storage->InternString(track_name.string_view());
+    track = context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+  }
+
   context_->slice_tracker->Begin(timestamp, track, kNullStringId, name_id);
 }
 
 void FtraceParser::ParseFuncgraphExit(
     int64_t timestamp,
+    uint32_t cpu,
     uint32_t pid,
     protozero::ConstBytes blob,
     PacketSequenceStateGeneration* seq_state) {
-  // TODO(rsavitski): remove if/when we stop collapsing all idle (swapper)
-  // threads to a single track, otherwise this breaks slice nesting.
-  if (pid == 0)
-    return;
-
   protos::pbzero::FuncgraphExitFtraceEvent::Decoder evt(blob.data, blob.size);
   StringId name_id = InternedKernelSymbolOrFallback(evt.func(), seq_state);
 
-  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
-  TrackId track = context_->track_tracker->InternThreadTrack(utid);
+  TrackId track = {};
+  if (pid != 0) {
+    // common case: normal thread
+    UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+    track = context_->track_tracker->InternThreadTrack(utid);
+  } else {
+    // special case: see |ParseFuncgraphEntry|
+    base::StackString<255> track_name("swapper%" PRIu32 "-funcgraph", cpu);
+    StringId track_name_id =
+        context_->storage->InternString(track_name.string_view());
+    track = context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+  }
+
   context_->slice_tracker->End(timestamp, track, kNullStringId, name_id);
 }
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 402ad73..6c84c6d 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -237,10 +237,12 @@
   void ParseSchedCpuUtilCfs(int64_t timestamp, protozero::ConstBytes);
 
   void ParseFuncgraphEntry(int64_t timestamp,
+                           uint32_t cpu,
                            uint32_t pid,
                            protozero::ConstBytes blob,
                            PacketSequenceStateGeneration* seq_state);
   void ParseFuncgraphExit(int64_t timestamp,
+                          uint32_t cpu,
                           uint32_t pid,
                           protozero::ConstBytes blob,
                           PacketSequenceStateGeneration* seq_state);
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 73bde66..4c492d1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -36,6 +36,7 @@
     "stack_trace",
     "time",
     "v8",
+    "wattson",
   ]
   generated_header = "stdlib.h"
   namespace = "stdlib"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index da1202f..786882e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -27,6 +27,7 @@
     "binder.sql",
     "broadcasts.sql",
     "critical_blocking_calls.sql",
+    "device.sql",
     "dvfs.sql",
     "freezer.sql",
     "garbage_collection.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/device.sql b/src/trace_processor/perfetto_sql/stdlib/android/device.sql
new file mode 100644
index 0000000..55e8365
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/device.sql
@@ -0,0 +1,42 @@
+--
+-- 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.
+
+-- Extract name of the device based on metadata from the trace.
+CREATE PERFETTO TABLE android_device_name(
+  -- Device name.
+  name STRING
+)
+AS
+WITH
+  -- Example str_value:
+  -- Android/aosp_raven/raven:VanillaIceCream/UDC/11197703:userdebug/test-keys
+  -- Gets substring after first slash;
+  after_first_slash(str) AS (
+    SELECT SUBSTR(str_value, INSTR(str_value, '/') + 1)
+    FROM metadata
+    WHERE name = 'android_build_fingerprint'
+  ),
+  -- Gets substring after second slash
+  after_second_slash(str) AS (
+    SELECT SUBSTR(str, INSTR(str, '/') + 1)
+    FROM after_first_slash
+  ),
+  -- Gets substring after second slash and before the colon
+  before_colon(str) AS (
+    SELECT SUBSTR(str, 0, INSTR(str, ':'))
+    FROM after_second_slash
+  )
+SELECT str AS name FROM before_colon;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
index e17e6b2..28fc5a0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
@@ -17,6 +17,8 @@
 perfetto_sql_source_set("cpu") {
   sources = [
     "cpus.sql",
+    "freq.sql",
+    "idle.sql",
     "size.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
new file mode 100644
index 0000000..c0226bf
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
@@ -0,0 +1,51 @@
+--
+-- 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.
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- Counter information for each frequency change for each CPU. Finds each time
+-- region where a CPU frequency is constant.
+CREATE PERFETTO TABLE cpu_freq_counters(
+  -- Counter id.
+  id INT,
+  -- Joinable with 'counter_track.id'.
+  track_id INT,
+  -- Starting timestamp of the counter
+  ts LONG,
+  -- Duration in which counter is constant and frequency doesn't change.
+  dur INT,
+  -- Frequency in kHz of the CPU that corresponds to this counter. NULL if not
+  -- found or undefined.
+  freq INT,
+  -- CPU that corresponds to this counter.
+  cpu INT
+) AS
+SELECT
+  count_w_dur.id,
+  count_w_dur.track_id,
+  count_w_dur.ts,
+  count_w_dur.dur,
+  count_w_dur.value as freq,
+  cct.cpu
+FROM
+counter_leading_intervals!((
+  SELECT c.*
+  FROM counter c
+  JOIN cpu_counter_track cct
+  ON cct.id = c.track_id AND cct.name = 'cpufreq'
+)) count_w_dur
+JOIN cpu_counter_track cct
+ON track_id = cct.id;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
new file mode 100644
index 0000000..7f837a2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
@@ -0,0 +1,53 @@
+--
+-- 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.
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- Counter information for each idle state change for each CPU. Finds each time
+-- region where a CPU idle state is constant.
+CREATE PERFETTO TABLE cpu_idle_counters(
+  -- Counter id.
+  id INT,
+  -- Joinable with 'counter_track.id'.
+  track_id INT,
+  -- Starting timestamp of the counter.
+  ts LONG,
+  -- Duration in which the counter is contant and idle state doesn't change.
+  dur INT,
+  -- Idle state of the CPU that corresponds to this counter. An idle state of -1
+  -- is defined to be active state for the CPU, and the larger the integer, the
+  -- deeper the idle state of the CPU. NULL if not found or undefined.
+  idle INT,
+  -- CPU that corresponds to this counter.
+  cpu INT
+)
+AS
+SELECT
+  count_w_dur.id,
+  count_w_dur.track_id,
+  count_w_dur.ts,
+  count_w_dur.dur,
+  IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value) AS idle,
+  cct.cpu
+FROM
+counter_leading_intervals!((
+  SELECT c.*
+  FROM counter c
+  JOIN cpu_counter_track cct
+  ON cct.id = c.track_id and cct.name = 'cpuidle'
+)) count_w_dur
+JOIN cpu_counter_track cct
+ON track_id = cct.id;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
new file mode 100644
index 0000000..4167d8c
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
@@ -0,0 +1,24 @@
+# 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.
+
+import("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("wattson") {
+  sources = [
+    "arm_dsu.sql",
+    "cpu_freq.sql",
+    "cpu_idle.sql",
+    "system_state.sql",
+  ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql
new file mode 100644
index 0000000..4f0572d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql
@@ -0,0 +1,49 @@
+--
+-- 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.
+
+-- Converts event counter from count to rate (num of accesses per ns).
+CREATE PERFETTO FUNCTION _get_rate(event STRING)
+RETURNS TABLE(ts LONG, dur INT, access_rate INT)
+AS
+SELECT
+  ts,
+  lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur,
+  -- Rate of event accesses in a section (i.e. count / dur).
+  value / (lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts) AS access_rate
+FROM counter AS c
+JOIN counter_track AS t
+  ON c.track_id = t.id
+WHERE t.name = $event;
+
+-- The rate of L3 misses for each time slice based on the ARM DSU PMU counter's
+-- bus_access event. Units will be in number of L3 misses per ns. The number of
+-- accesses in a given duration can be calculated by multiplying the appropriate
+-- rate with the time in the window of interest.
+CREATE PERFETTO TABLE _arm_l3_miss_rate
+AS
+SELECT
+  ts, dur, access_rate AS l3_miss_rate
+FROM _get_rate("arm_dsu_0/bus_access/_cpu0");
+
+-- The rate of L3 accesses for each time slice based on the ARM DSU PMU
+-- counter's l3d_cache event. Units will be in number of DDR accesses per ns.
+-- The number of accesses in a given duration can be calculated by multiplying
+-- the appropriate rate with the time in the window of interest.
+CREATE PERFETTO TABLE _arm_l3_hit_rate
+AS
+SELECT
+  ts, dur, access_rate AS l3_hit_rate
+FROM _get_rate("arm_dsu_0/l3d_cache/_cpu0");
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
new file mode 100644
index 0000000..93738f6
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
@@ -0,0 +1,92 @@
+--
+-- 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.
+
+INCLUDE PERFETTO MODULE cpu.freq;
+
+-- Filters for CPU specific frequency slices
+CREATE PERFETTO FUNCTION _per_cpu_freq_slice(cpu_match INT)
+RETURNS TABLE(ts LONG, dur INT, freq INT)
+AS
+SELECT ts, dur, freq
+FROM cpu_freq_counters WHERE cpu = $cpu_match;
+
+-- _freq_slices_cpux has CPUx specific frequency slices.
+CREATE PERFETTO TABLE _freq_slices_cpu0
+AS
+SELECT ts, dur, freq AS freq_0 FROM _per_cpu_freq_slice(0);
+
+CREATE PERFETTO TABLE _freq_slices_cpu1
+AS
+SELECT ts, dur, freq AS freq_1 FROM _per_cpu_freq_slice(1);
+
+CREATE PERFETTO TABLE _freq_slices_cpu2
+AS
+SELECT ts, dur, freq AS freq_2 FROM _per_cpu_freq_slice(2);
+
+CREATE PERFETTO TABLE _freq_slices_cpu3
+AS
+SELECT ts, dur, freq AS freq_3 FROM _per_cpu_freq_slice(3);
+
+CREATE PERFETTO TABLE _freq_slices_cpu4
+AS
+SELECT ts, dur, freq AS freq_4 FROM _per_cpu_freq_slice(4);
+
+CREATE PERFETTO TABLE _freq_slices_cpu5
+AS
+SELECT ts, dur, freq AS freq_5 FROM _per_cpu_freq_slice(5);
+
+CREATE PERFETTO TABLE _freq_slices_cpu6
+AS
+SELECT ts, dur, freq AS freq_6 FROM _per_cpu_freq_slice(6);
+
+CREATE PERFETTO TABLE _freq_slices_cpu7
+AS
+SELECT ts, dur, freq AS freq_7 FROM _per_cpu_freq_slice(7);
+
+-- SPAN_OUTER_JOIN of all CPUs' frequency tables.
+CREATE VIRTUAL TABLE _freq_slices_cpu01
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu0, _freq_slices_cpu1);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu012
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu01, _freq_slices_cpu2);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu0123
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu012, _freq_slices_cpu3);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu01234
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu0123, _freq_slices_cpu4);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu012345
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu01234, _freq_slices_cpu5);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu0123456
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu012345, _freq_slices_cpu6);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu01234567
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu0123456, _freq_slices_cpu7);
+
+-- Table that holds time slices of the trace with the frequency transition
+-- information of every CPU in the system.
+CREATE PERFETTO TABLE _cpu_freq_all
+AS
+SELECT * FROM _freq_slices_cpu01234567;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
new file mode 100644
index 0000000..006c876
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
@@ -0,0 +1,160 @@
+--
+-- 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.
+
+INCLUDE PERFETTO MODULE android.device;
+INCLUDE PERFETTO MODULE cpu.idle;
+
+-- Device specific info for deep idle time offsets
+CREATE PERFETTO TABLE _device_cpu_deep_idle_offsets
+AS
+WITH data(device, cpu, offset_ns) AS (
+  VALUES
+  ("oriole", 6, 200000),
+  ("oriole", 7, 200000),
+  ("raven", 6, 200000),
+  ("raven", 7, 200000),
+  ("eos", 0, 450000),
+  ("eos", 1, 450000),
+  ("eos", 2, 450000),
+  ("eos", 3, 450000)
+)
+select * from data;
+
+-- Get the corresponding deep idle time offset based on device and CPU.
+CREATE PERFETTO FUNCTION _get_deep_idle_offset(cpu INT)
+RETURNS INT
+AS
+SELECT offset_ns
+FROM _device_cpu_deep_idle_offsets as offsets, android_device_name as device
+WHERE
+  offsets.device = device.name AND cpu = $cpu;
+
+-- Adjust duration of active portion to be slightly longer to account for
+-- overhead cost of transitioning out of deep idle. This is done because the
+-- device is active and consumes power for longer than the logs actually report.
+CREATE PERFETTO FUNCTION _adjust_deep_idle(cpu_match INT)
+RETURNS TABLE(ts LONG, dur INT, idle INT) AS
+WITH
+  idle_prev AS (
+    SELECT
+      ts,
+      dur,
+      idle,
+      lag(idle) OVER (PARTITION BY track_id ORDER BY ts) AS idle_prev,
+      cpu
+    FROM cpu_idle_counters
+  ),
+  offset_ns AS (
+    SELECT IFNULL(_get_deep_idle_offset($cpu_match), 0) as offset_ns
+  ),
+  -- Adjusted ts if applicable, which makes the current deep idle state
+  -- slightly shorter.
+  idle_mod AS (
+    SELECT
+      IIF(
+        idle_prev = -1 AND idle = 1,
+        IIF(dur > offset_ns, ts + offset_ns, ts + dur),
+        ts
+      ) as ts,
+      -- ts_next is the starting timestamp of the next slice (e.g. end ts of
+      -- current slice)
+      ts + dur as ts_next,
+      idle
+    FROM idle_prev, offset_ns
+    WHERE cpu = $cpu_match
+  )
+SELECT
+  ts,
+  lead(ts, 1, trace_end()) OVER (ORDER by ts) - ts as dur,
+  idle
+FROM idle_mod
+WHERE ts != ts_next;
+
+-- idle_slices_cpux has CPUx specific idle state slices.
+CREATE PERFETTO TABLE _idle_slices_cpu0
+AS
+SELECT idle as idle_0, ts, dur
+FROM _adjust_deep_idle(0);
+
+CREATE PERFETTO TABLE _idle_slices_cpu1
+AS
+SELECT idle as idle_1, ts, dur
+FROM _adjust_deep_idle(1);
+
+CREATE PERFETTO TABLE _idle_slices_cpu2
+AS
+SELECT idle as idle_2, ts, dur
+FROM _adjust_deep_idle(2);
+
+CREATE PERFETTO TABLE _idle_slices_cpu3
+AS
+SELECT idle as idle_3, ts, dur
+FROM _adjust_deep_idle(3);
+
+CREATE PERFETTO TABLE _idle_slices_cpu4
+AS
+SELECT idle as idle_4, ts, dur
+FROM _adjust_deep_idle(4);
+
+CREATE PERFETTO TABLE _idle_slices_cpu5
+AS
+SELECT idle as idle_5, ts, dur
+FROM _adjust_deep_idle(5);
+
+CREATE PERFETTO TABLE _idle_slices_cpu6
+AS
+SELECT idle as idle_6, ts, dur
+FROM _adjust_deep_idle(6);
+
+CREATE PERFETTO TABLE _idle_slices_cpu7
+AS
+SELECT idle as idle_7, ts, dur
+FROM _adjust_deep_idle(7);
+
+-- SPAN_OUTER_JOIN of all CPUs' idle state tables.
+CREATE VIRTUAL TABLE _idle_slices_cpu01
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu0, _idle_slices_cpu1);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu012
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu01, _idle_slices_cpu2);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu0123
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu012, _idle_slices_cpu3);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu01234
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu0123, _idle_slices_cpu4);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu012345
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu01234, _idle_slices_cpu5);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu0123456
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu012345, _idle_slices_cpu6);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu01234567
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu0123456, _idle_slices_cpu7);
+
+-- Table that holds time slices of the entire trace with the idle state
+-- transition information of every CPU in the system.
+CREATE PERFETTO TABLE _cpu_idle_all
+AS
+SELECT * FROM _idle_slices_cpu01234567;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
new file mode 100644
index 0000000..63eeb7a
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
@@ -0,0 +1,129 @@
+--
+-- 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.
+
+INCLUDE PERFETTO MODULE time.conversion;
+INCLUDE PERFETTO MODULE wattson.arm_dsu;
+INCLUDE PERFETTO MODULE wattson.cpu_freq;
+INCLUDE PERFETTO MODULE wattson.cpu_idle;
+
+-- Combines idle and freq tables of all CPUs to create system state.
+CREATE VIRTUAL TABLE _idle_freq_slice
+USING
+  SPAN_OUTER_JOIN(_cpu_freq_all, _cpu_idle_all);
+
+-- get suspend resume state as logged by ftrace.
+CREATE PERFETTO TABLE _suspend_slice
+AS
+SELECT
+  ts, dur, TRUE AS suspended
+FROM slice
+WHERE name GLOB "timekeeping_freeze(0)";
+
+-- Combine suspend information with CPU idle and frequency system states.
+CREATE VIRTUAL TABLE _idle_freq_suspend_slice
+USING
+  SPAN_OUTER_JOIN(_idle_freq_slice, _suspend_slice);
+
+-- Add extra column indicating that idle and frequency info are present before
+-- SPAN_OUTER_JOIN with the DSU PMU counters.
+CREATE PERFETTO TABLE _idle_freq_filtered
+AS
+SELECT *, TRUE AS has_idle_freq
+FROM _idle_freq_suspend_slice
+WHERE freq_0 GLOB '*[0-9]*';
+
+-- Combine system state so that it has idle, freq, and L3 hit info.
+CREATE VIRTUAL TABLE _idle_freq_l3_hit_slice
+USING
+  SPAN_OUTER_JOIN(_idle_freq_filtered, _arm_l3_hit_rate);
+
+-- Combine system state so that it has idle, freq, L3 hit, and L3 miss info.
+CREATE VIRTUAL TABLE _idle_freq_l3_hit_l3_miss_slice
+USING
+  SPAN_OUTER_JOIN(_idle_freq_l3_hit_slice, _arm_l3_miss_rate);
+
+-- The final system state for the CPU subsystem, which has all the information
+-- needed by Wattson to estimate energy for the CPU subsystem.
+CREATE PERFETTO TABLE wattson_system_states(
+  -- Starting timestamp of the current counter where system state is constant.
+  ts LONG,
+  -- Duration of the current counter where system state is constant.
+  dur INT,
+  -- Number of L3 hits the current system state.
+  l3_hit_count INT,
+  -- Number of L3 misses in the current system state.
+  l3_miss_count INT,
+  -- Frequency of CPU0.
+  freq_0 INT,
+  -- Idle state of CPU0.
+  idle_0 INT,
+  -- Frequency of CPU1.
+  freq_1 INT,
+  -- Idle state of CPU1.
+  idle_1 INT,
+  -- Frequency of CPU2.
+  freq_2 INT,
+  -- Idle state of CPU2.
+  idle_2 INT,
+  -- Frequency of CPU3.
+  freq_3 INT,
+  -- Idle state of CPU3.
+  idle_3 INT,
+  -- Frequency of CPU4.
+  freq_4 INT,
+  -- Idle state of CPU4.
+  idle_4 INT,
+  -- Frequency of CPU5.
+  freq_5 INT,
+  -- Idle state of CPU5.
+  idle_5 INT,
+  -- Frequency of CPU6.
+  freq_6 INT,
+  -- Idle state of CPU6.
+  idle_6 INT,
+  -- Frequency of CPU7.
+  freq_7 INT,
+  -- Idle state of CPU7.
+  idle_7 INT,
+  -- Flag indicating if current system state is suspended.
+  suspended BOOL
+)
+AS
+SELECT
+  ts,
+  dur,
+  cast_int!(round(l3_hit_rate * dur, 0)) as l3_hit_count,
+  cast_int!(round(l3_miss_rate * dur, 0)) as l3_miss_count,
+  freq_0,
+  idle_0,
+  freq_1,
+  idle_1,
+  freq_2,
+  idle_2,
+  freq_3,
+  idle_3,
+  freq_4,
+  idle_4,
+  freq_5,
+  idle_5,
+  freq_6,
+  idle_6,
+  freq_7,
+  idle_7,
+  IFNULL(suspended, FALSE) as suspended
+FROM _idle_freq_l3_hit_l3_miss_slice
+-- Needs to be at least 1us to reduce inconsequential rows.
+WHERE dur > time_from_us(1) and has_idle_freq IS NOT NULL;
+
diff --git a/src/trace_redaction/filter_print_events.cc b/src/trace_redaction/filter_print_events.cc
index de0c952..6c1843b 100644
--- a/src/trace_redaction/filter_print_events.cc
+++ b/src/trace_redaction/filter_print_events.cc
@@ -47,7 +47,8 @@
   protozero::ProtoDecoder event(bytes);
 
   // This is not a print packet. Keep the packet.
-  if (event.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber).valid()) {
+  if (!event.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber)
+           .valid()) {
     return true;
   }
 
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
index 3260c71..c3d1abf 100644
--- a/src/trace_redaction/scrub_ftrace_events.cc
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -94,7 +94,9 @@
 bool ScrubFtraceEvents::KeepEvent(const Context& context,
                                   protozero::ConstBytes bytes) const {
   for (const auto& filter : filters_) {
-    if (!filter->KeepEvent(context, bytes)) {
+    auto keep = filter->KeepEvent(context, bytes);
+
+    if (!keep) {
       return false;
     }
   }
diff --git a/test/data/android_cpu_eos.pb.sha256 b/test/data/android_cpu_eos.pb.sha256
new file mode 100644
index 0000000..15f2572
--- /dev/null
+++ b/test/data/android_cpu_eos.pb.sha256
@@ -0,0 +1 @@
+68377df0bb4fc55a26e2da4285aa441b6d1d3c4e99a192a7bc80876834954c2b
\ No newline at end of file
diff --git a/test/data/wattson_dsu_pmu.pb.sha256 b/test/data/wattson_dsu_pmu.pb.sha256
new file mode 100644
index 0000000..e547cee
--- /dev/null
+++ b/test/data/wattson_dsu_pmu.pb.sha256
@@ -0,0 +1 @@
+848d01bf930bd5b05a9d13c921a38b0101f6f8ec0b69e74ea2a6f408e118b995
\ No newline at end of file
diff --git a/test/data/wattson_eos_suspend.pb.sha256 b/test/data/wattson_eos_suspend.pb.sha256
new file mode 100644
index 0000000..52b8eef
--- /dev/null
+++ b/test/data/wattson_eos_suspend.pb.sha256
@@ -0,0 +1 @@
+dcae89e16d14152cd2cd19bcd7c800981041be74d002b3ab99dc469618d84401
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 1edf16f..1f711b8 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -97,6 +97,7 @@
 from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
+from diff_tests.stdlib.cpu.tests import CpuStdlib
 from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
 from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
 from diff_tests.stdlib.graphs.partition_tests import GraphPartitionTests
@@ -118,6 +119,7 @@
 from diff_tests.stdlib.span_join.tests_smoke import SpanJoinSmoke
 from diff_tests.stdlib.tests import StdlibSmoke
 from diff_tests.stdlib.timestamps.tests import Timestamps
+from diff_tests.stdlib.wattson.tests import WattsonStdlib
 from diff_tests.syntax.filtering_tests import PerfettoFiltering
 from diff_tests.syntax.function_tests import PerfettoFunction
 from diff_tests.syntax.include_tests import PerfettoInclude
@@ -243,6 +245,7 @@
 
   stdlib_tests = [
       *AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
+      *CpuStdlib(index_path, 'stdlib/cpu', 'CpuStdlib').fetch(),
       *DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
       *Frames(index_path, 'stdlib/android', 'Frames').fetch(),
       *GraphSearchTests(index_path, 'stdlib/graphs',
@@ -280,6 +283,7 @@
       *IntervalsIntersect(index_path, 'stdlib/intervals',
                           'StdlibIntervalsIntersect').fetch(),
       *Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
+      *WattsonStdlib(index_path, 'stdlib/wattson', 'WattsonStdlib').fetch(),
   ] + chrome_stdlib_tests
 
   syntax_tests = [
diff --git a/test/trace_processor/diff_tests/stdlib/cpu/tests.py b/test/trace_processor/diff_tests/stdlib/cpu/tests.py
new file mode 100644
index 0000000..0c32ed3
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/cpu/tests.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class CpuStdlib(TestSuite):
+  # Test CPU frequency counter grouping.
+  def test_cpu_eos_counters_freq(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query=("""
+             INCLUDE PERFETTO MODULE cpu.freq;
+             select
+               track_id,
+               freq,
+               cpu,
+               sum(dur) as dur
+             from cpu_freq_counters
+             GROUP BY freq, cpu
+             """),
+        out=Csv("""
+            "track_id","freq","cpu","dur"
+            33,614400,0,4755967239
+            34,614400,1,4755971561
+            35,614400,2,4755968228
+            36,614400,3,4755964320
+            33,864000,0,442371195
+            34,864000,1,442397134
+            35,864000,2,442417916
+            36,864000,3,442434530
+            33,1363200,0,897122398
+            34,1363200,1,897144167
+            35,1363200,2,897180154
+            36,1363200,3,897216772
+            33,1708800,0,2553979530
+            34,1708800,1,2553923073
+            35,1708800,2,2553866772
+            36,1708800,3,2553814688
+            """))
+
+  # Test CPU idle state counter grouping.
+  def test_cpu_eos_counters_idle(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query=("""
+             INCLUDE PERFETTO MODULE cpu.idle;
+             select
+               track_id,
+               idle,
+               cpu,
+               sum(dur) as dur
+             from cpu_idle_counters
+             GROUP BY idle, cpu
+             """),
+        out=Csv("""
+             "track_id","idle","cpu","dur"
+             0,-1,0,2839828332
+             37,-1,1,1977033843
+             32,-1,2,1800498713
+             1,-1,3,1884366297
+             0,0,0,1833971336
+             37,0,1,2285260950
+             32,0,2,1348416182
+             1,0,3,1338508968
+             0,1,0,4013820433
+             37,1,1,4386917600
+             32,1,2,5532102915
+             1,1,3,5462026920
+            """))
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
new file mode 100644
index 0000000..94c9bd7
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class WattsonStdlib(TestSuite):
+  consolidate_tables_template = ('''
+      SELECT
+        sum(dur) as duration,
+        SUM(l3_hit_count) AS l3_hit_count,
+        SUM(l3_miss_count) AS l3_miss_count,
+        freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+        freq_4, idle_4, freq_5, idle_5, freq_6, idle_6, freq_7, idle_7,
+        suspended
+      FROM SYSTEM_STATE_TABLE
+      GROUP BY
+        freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+        freq_4, idle_4, freq_5, idle_5, freq_6, idle_6, freq_7, idle_7,
+        suspended
+      ORDER BY duration desc
+      LIMIT 20;
+      ''')
+
+  # Test fixup of deep idle offset and time marker window.
+  def test_wattson_time_window(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_dsu_pmu.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.system_state;
+
+        CREATE PERFETTO TABLE wattson_time_window
+        AS
+        SELECT 362426061658 AS ts, 5067704349 AS dur;
+
+        -- Final table that is cut off to fit within the requested time window.
+        CREATE VIRTUAL TABLE time_window_intersect
+          USING SPAN_JOIN(wattson_system_states, wattson_time_window);
+        """ + self.consolidate_tables_template.replace("SYSTEM_STATE_TABLE",
+                                                       "time_window_intersect"),
+        out=Csv("""
+            "duration","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended"
+            58982760,2787779,1229222,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,1,500000,0,0
+            50664181,2364802,1133322,574000,0,574000,1,574000,1,574000,0,553000,1,553000,0,500000,0,500000,0,0
+            41743544,2013295,917107,574000,0,574000,0,574000,1,574000,1,553000,0,553000,0,500000,0,500000,1,0
+            33801135,1479851,684489,300000,0,300000,0,300000,1,300000,1,400000,0,400000,0,500000,0,500000,1,0
+            32703489,1428203,690001,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+            29062133,1597555,719900,574000,0,574000,0,574000,1,574000,0,553000,1,553000,0,500000,1,500000,0,0
+            28310872,1211262,566873,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+            26754474,1224826,569901,300000,0,300000,1,300000,0,300000,0,400000,1,400000,0,500000,0,500000,0,0
+            25107621,1059311,473161,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,1,500000,0,0
+            23771603,987803,450930,300000,0,300000,1,300000,1,300000,0,400000,1,400000,0,500000,0,500000,0,0
+            23721891,963220,409196,300000,0,300000,0,300000,1,300000,0,400000,1,400000,0,500000,1,500000,0,0
+            22988523,984240,473025,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,1,0
+            21790436,987511,449348,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,0,500000,1,0
+            21673975,1034856,445803,574000,0,574000,0,574000,1,574000,0,553000,0,553000,0,500000,0,500000,1,0
+            20665650,974100,442861,300000,0,300000,0,300000,1,300000,0,400000,0,400000,1,500000,0,500000,0,0
+            18024891,823424,339250,300000,0,300000,1,300000,0,300000,0,400000,0,400000,0,500000,1,500000,0,0
+            17669272,826030,346995,574000,0,574000,0,574000,0,574000,0,553000,1,553000,0,500000,1,500000,0,0
+            16774291,762738,348469,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,0,500000,1,0
+            16191449,689792,316923,300000,0,300000,0,300000,1,300000,0,400000,1,400000,0,500000,0,500000,0,0
+            15918895,742531,325426,574000,0,574000,1,574000,0,574000,1,553000,0,553000,0,500000,1,500000,0,0
+            """))
+
+  # Test on Raven for checking system states and the DSU PMU counts.
+  def test_wattson_dsu_pmu(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_dsu_pmu.pb'),
+        query=("INCLUDE PERFETTO MODULE wattson.system_state;\n" +
+               self.consolidate_tables_template.replace(
+                   "SYSTEM_STATE_TABLE", "wattson_system_states")),
+        out=Csv("""
+            "duration","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended"
+            1279130692,1318302,419159,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            165854009,118369,42037,300000,-1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            121156343,5846554,2343180,574000,0,574000,1,574000,0,574000,0,553000,0,553000,0,500000,1,500000,1,0
+            72876331,133532,57759,300000,1,300000,1,300000,-1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            71060865,69029,22056,300000,1,300000,-1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            64501262,276098,309757,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,-1,500000,1,0
+            58982760,2787779,1229222,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,1,500000,0,0
+            51127388,50724,18075,300000,1,300000,1,300000,1,300000,-1,400000,1,400000,1,500000,1,500000,1,0
+            50664181,2364802,1133322,574000,0,574000,1,574000,1,574000,0,553000,1,553000,0,500000,0,500000,0,0
+            49948122,2216740,934893,300000,0,300000,1,300000,0,300000,0,400000,0,400000,0,500000,1,500000,1,0
+            41743544,2013295,917107,574000,0,574000,0,574000,1,574000,1,553000,0,553000,0,500000,0,500000,1,0
+            40606558,"[NULL]","[NULL]",1401000,1,1401000,1,1401000,1,1401000,1,400000,1,400000,1,2802000,1,2802000,1,0
+            39887541,14272,1252,300000,0,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            38159789,1428203,690001,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+            33801135,1479851,684489,300000,0,300000,0,300000,1,300000,1,400000,0,400000,0,500000,0,500000,1,0
+            31543574,34702,18036,300000,1,300000,1,300000,1,300000,1,400000,-1,400000,1,500000,1,500000,1,0
+            31470669,163778,200331,1098000,1,1098000,1,1098000,1,1098000,1,400000,1,400000,1,500000,1,500000,-1,0
+            30993650,39579,48376,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,-1,0
+            30144287,1396131,585581,574000,0,574000,1,574000,0,574000,0,553000,0,553000,0,500000,1,500000,0,0
+            30009881,"[NULL]","[NULL]",1328000,1,1328000,1,1328000,1,1328000,1,2253000,1,2253000,1,500000,1,500000,1,0
+            """))
+
+  # Test on eos to check that suspend states are being calculated appropriately.
+  def test_wattson_suspend(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_eos_suspend.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.system_state;
+        SELECT
+          sum(dur) as duration,
+          freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+          suspended
+        FROM wattson_system_states
+        GROUP BY
+          freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+          suspended
+        ORDER BY duration desc
+        LIMIT 20;
+        """,
+        out=Csv("""
+            "duration","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","suspended"
+            16606175990,614400,1,614400,1,614400,1,614400,1,0
+            10648392546,1708800,-1,1708800,-1,1708800,-1,1708800,-1,1
+            6933558399,1708800,-1,1708800,-1,1708800,-1,1708800,-1,0
+            1649400745,614400,0,614400,0,614400,0,614400,0,0
+            1199187488,614400,-1,614400,1,614400,1,614400,1,0
+            945900007,1708800,0,1708800,0,1708800,0,1708800,0,0
+            936351409,1363200,0,1363200,0,1363200,0,1363200,1,0
+            708490325,1708800,0,1708800,0,1708800,0,1708800,1,0
+            706695995,1708800,1,1708800,1,1708800,1,1708800,1,0
+            656873956,1363200,1,1363200,1,1363200,1,1363200,1,0
+            633440914,1363200,0,1363200,0,1363200,0,1363200,0,0
+            627708654,1708800,-1,1708800,0,1708800,0,1708800,0,0
+            620315547,1708800,-1,1708800,1,1708800,1,1708800,1,0
+            578173274,1708800,-1,1708800,0,1708800,0,1708800,-1,0
+            530967964,1708800,-1,1708800,-1,1708800,0,1708800,-1,0
+            516281990,1708800,0,1708800,0,1708800,0,1708800,-1,0
+            473910837,1363200,-1,1363200,0,1363200,0,1363200,1,0
+            461831724,1708800,0,1708800,-1,1708800,0,1708800,0,0
+            402233299,1708800,-1,1708800,-1,1708800,-1,1708800,1,0
+            375051979,864000,1,864000,1,864000,1,864000,1,0
+            """))
+
+  # Test cpu_idle.sql module to make sure final CPU idle system state is proper.
+  def test_wattson_cpu_idle(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.cpu_idle;
+        SELECT ts, dur, idle_0, idle_1, idle_2, idle_3
+        FROM _cpu_idle_all
+        ORDER BY ts DESC
+        LIMIT 20;
+        """,
+        out=Csv("""
+            "ts","dur","idle_0","idle_1","idle_2","idle_3"
+            183589213730,322865,-1,-1,-1,-1
+            183589083105,130625,-1,0,-1,-1
+            183587834095,1249010,-1,-1,-1,-1
+            183586743417,1090678,0,-1,-1,-1
+            183584248209,2495208,0,-1,-1,0
+            183584002220,245989,-1,-1,-1,0
+            183580262740,3739480,0,-1,-1,0
+            183580247324,15416,0,-1,-1,-1
+            183580135813,111511,-1,-1,-1,-1
+            183580002792,133021,-1,-1,-1,0
+            183576199459,3803333,0,-1,-1,0
+            183576002376,197083,-1,-1,-1,0
+            183575781386,220990,0,-1,-1,0
+            183575764355,17031,0,-1,-1,-1
+            183575719824,44531,-1,-1,-1,-1
+            183575706490,13334,-1,-1,-1,0
+            183575630865,75625,-1,-1,-1,-1
+            183575503886,126979,0,-1,-1,-1
+            183573856542,1647344,0,-1,-1,0
+            183573750032,106510,-1,-1,-1,0
+            """))
+
+  # Test cpu_freq.sql module to make sure final CPU frequency states are proper.
+  def test_wattson_cpu_freq(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.cpu_freq;
+        SELECT ts, dur, freq_0, freq_1, freq_2, freq_3
+        FROM _cpu_freq_all
+        ORDER BY ts DESC
+        LIMIT 20;
+        """,
+        out=Csv("""
+            "ts","dur","freq_0","freq_1","freq_2","freq_3"
+            183219975813,369560782,1708800,1708800,1708800,1708800
+            183219971595,4218,1708800,1708800,1708800,1363200
+            183219967324,4271,1708800,1708800,1363200,1363200
+            183219961334,5990,1708800,1363200,1363200,1363200
+            183217603313,2358021,1363200,1363200,1363200,1363200
+            183217599147,4166,1363200,1363200,1363200,614400
+            183217594720,4427,1363200,1363200,614400,614400
+            183217584459,10261,1363200,614400,614400,614400
+            183099692324,117892135,614400,614400,614400,614400
+            183099688001,4323,614400,614400,614400,1708800
+            183099683418,4583,614400,614400,1708800,1708800
+            183099677584,5834,614400,1708800,1708800,1708800
+            183000228105,99449479,1708800,1708800,1708800,1708800
+            183000223938,4167,1708800,1708800,1708800,864000
+            183000219303,4635,1708800,1708800,864000,864000
+            183000213313,5990,1708800,864000,864000,864000
+            182984137428,16075885,864000,864000,864000,864000
+            182984133522,3906,864000,864000,864000,1363200
+            182984129355,4167,864000,864000,1363200,1363200
+            182984123678,5677,864000,1363200,1363200,1363200
+            """))
+
+  # Test that the device name can be extracted from the trace's metadata.
+  def test_wattson_device_name(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query=("""
+            INCLUDE PERFETTO MODULE android.device;
+            select name from android_device_name
+            """),
+        out=Csv("""
+            "name"
+            "eos"
+            """))
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 049cf40..6036809 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -284,6 +284,7 @@
     gn = gn_utils.GnParser(gn_desc)
     project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
     tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
+    tool_name = tool_name.replace('\\', '/')
     res = '''
 # Copyright (C) 2022 The Android Open Source Project
 #
@@ -746,6 +747,7 @@
   gn = gn_utils.GnParser(gn_desc)
   project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
   tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
+  tool_name = tool_name.replace('\\', '/')
   res = '''
 # Copyright (C) 2019 The Android Open Source Project
 #
@@ -915,14 +917,14 @@
 
   contents = generate_build(desc, args.targets or default_targets, extras)
   out_files.append(args.output + '.swp')
-  with open(out_files[-1], 'w') as out_f:
+  with open(out_files[-1], 'w', newline='\n') as out_f:
     out_f.write(contents)
 
   # Generate the python BUILD file.
   python_gen = PythonBuildGenerator()
   python_contents = python_gen.generate(desc)
   out_files.append(args.output_python + '.swp')
-  with open(out_files[-1], 'w') as out_f:
+  with open(out_files[-1], 'w', newline='\n') as out_f:
     out_f.write(python_contents)
 
   # Generate the build flags file.
diff --git a/tools/gn_utils.py b/tools/gn_utils.py
index 65c0ff0..d0417d7 100644
--- a/tools/gn_utils.py
+++ b/tools/gn_utils.py
@@ -123,7 +123,7 @@
     source files in the amalgamated result.
     """
   targets = [t.replace('//', '') for t in targets]
-  with open(os.devnull, 'w') as devnull:
+  with open(os.devnull, 'w', newline='\n') as devnull:
     stdout = devnull if quiet else None
     cmd = _tool_path('ninja', system_buildtools) + targets
     subprocess.check_call(cmd, cwd=os.path.abspath(out), stdout=stdout)
@@ -211,7 +211,7 @@
         res = 1
       os.unlink(tmp_file)
     else:
-      os.rename(tmp_file, target_file)
+      os.replace(tmp_file, target_file)
   return res