Fix time-in-state annotations.

* Correct duration logic by using a global clock not per-{thread,
  freq} LAG().
* Preserve duration all the way through rather than recalculating it and
  thus losing the first interval for globals. Remove an intermediate
  table this way.
* Add a test. Add support for process stats packets to the Python trace
  generator. Unify add_packet() and add_process_tree_packet() while we
  are there.

Bug: 157216280
Change-Id: Ibff7bdfb6b53b3344d7cbfa5cdd518bf9831f915
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 59fc0e7..8e8e150 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -6302,10 +6302,11 @@
   message Thread {
     optional int32 tid = 1;
 
-    // Pairs of frequency (represented as an index to CpuInfo frequencies) and
-    // time at that frequency (represented as a number of ticks, see SystemInfo
-    // for the HZ (ticks / second) value to convert this to time). Frequencies
-    // with zero ticks are never uploaded. Read from /proc/tid/time_in_state.
+    // Pairs of frequency (represented as a (1-based) index to CpuInfo
+    // frequencies) and time at that frequency (represented as a number of
+    // ticks, see SystemInfo for the HZ (ticks / second) value to convert this
+    // to time). Frequencies with zero ticks are never uploaded. Read from
+    // /proc/tid/time_in_state.
     repeated uint32 cpu_freq_indices = 2;
     repeated uint64 cpu_freq_ticks = 3;
     // Whether all frequencies with non-zero ticks are present in
diff --git a/protos/perfetto/trace/ps/process_stats.proto b/protos/perfetto/trace/ps/process_stats.proto
index d4bf386..9c1b1aa 100644
--- a/protos/perfetto/trace/ps/process_stats.proto
+++ b/protos/perfetto/trace/ps/process_stats.proto
@@ -30,10 +30,11 @@
   message Thread {
     optional int32 tid = 1;
 
-    // Pairs of frequency (represented as an index to CpuInfo frequencies) and
-    // time at that frequency (represented as a number of ticks, see SystemInfo
-    // for the HZ (ticks / second) value to convert this to time). Frequencies
-    // with zero ticks are never uploaded. Read from /proc/tid/time_in_state.
+    // Pairs of frequency (represented as a (1-based) index to CpuInfo
+    // frequencies) and time at that frequency (represented as a number of
+    // ticks, see SystemInfo for the HZ (ticks / second) value to convert this
+    // to time). Frequencies with zero ticks are never uploaded. Read from
+    // /proc/tid/time_in_state.
     repeated uint32 cpu_freq_indices = 2;
     repeated uint64 cpu_freq_ticks = 3;
     // Whether all frequencies with non-zero ticks are present in
diff --git a/src/trace_processor/metrics/android/android_thread_time_in_state.sql b/src/trace_processor/metrics/android/android_thread_time_in_state.sql
index c3375ce..a4e2f75 100644
--- a/src/trace_processor/metrics/android/android_thread_time_in_state.sql
+++ b/src/trace_processor/metrics/android/android_thread_time_in_state.sql
@@ -127,10 +127,20 @@
   )
 );
 
+-- Ensure we always get the previous clock tick for duration in
+-- android_thread_time_in_state_annotations_raw.
+CREATE VIEW android_thread_time_in_state_annotations_clock AS
+SELECT
+  ts,
+  LAG(ts) OVER (ORDER BY ts) AS lag_ts
+FROM (
+  SELECT DISTINCT ts from android_thread_time_in_state_base
+);
+
 CREATE VIEW android_thread_time_in_state_annotations_raw AS
 SELECT
   ts,
-  ts - LAG(ts) OVER (PARTITION BY core_type, utid ORDER BY ts) AS dur,
+  ts - lag_ts AS dur,
   upid,
   core_type,
   utid,
@@ -139,18 +149,9 @@
   runtime_ms_counter - LAG(runtime_ms_counter)
       OVER (PARTITION BY core_type, utid, freq ORDER BY ts) AS runtime_ms
 FROM android_thread_time_in_state_base
+    JOIN android_thread_time_in_state_annotations_clock USING(ts)
 WHERE thread_name IS NOT NULL;
 
-CREATE VIEW android_thread_time_in_state_annotations_global_raw AS
-SELECT
-  ts,
-  core_type,
-  SUM(runtime_ms * freq) AS ms_freq
-FROM android_thread_time_in_state_annotations_raw
-WHERE thread_name IS NOT NULL
-  AND runtime_ms IS NOT NULL
-GROUP BY ts, core_type;
-
 CREATE VIEW android_thread_time_in_state_annotations_thread AS
 SELECT
   'counter' AS track_type,
@@ -169,10 +170,12 @@
   'counter' AS track_type,
   'Total ' || core_type || ' core cycles / sec' as track_name,
   ts,
-  ts - LAG(ts) OVER (PARTITION BY core_type ORDER BY ts) AS dur,
+  dur,
   0 AS upid,
-  ms_freq
-FROM android_thread_time_in_state_annotations_global_raw;
+  SUM(runtime_ms * freq) AS ms_freq
+FROM android_thread_time_in_state_annotations_raw
+WHERE runtime_ms IS NOT NULL
+GROUP BY ts, track_name;
 
 CREATE VIEW android_thread_time_in_state_annotations AS
 SELECT track_type, track_name, ts, dur, upid, ms_freq * 1000000 / dur AS value
diff --git a/test/metrics/android_ion.py b/test/metrics/android_ion.py
index 6bff44f..29e63fa 100644
--- a/test/metrics/android_ion.py
+++ b/test/metrics/android_ion.py
@@ -19,7 +19,7 @@
 import synth_common
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'com.google.android.calendar')
diff --git a/test/metrics/android_lmk.py b/test/metrics/android_lmk.py
index a6cc12e..fab3acb 100644
--- a/test/metrics/android_lmk.py
+++ b/test/metrics/android_lmk.py
@@ -19,7 +19,7 @@
 import synth_common
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'com.google.android.calendar')
diff --git a/test/metrics/android_lmk_reason.py b/test/metrics/android_lmk_reason.py
index 4756cf1..4fde7f3 100644
--- a/test/metrics/android_lmk_reason.py
+++ b/test/metrics/android_lmk_reason.py
@@ -23,7 +23,7 @@
 
 trace = synth_common.create_trace()
 
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'lmk_victim:no_data:ignored')
diff --git a/test/metrics/android_mem_by_priority.py b/test/metrics/android_mem_by_priority.py
index 0d9227b..6680e90 100644
--- a/test/metrics/android_mem_by_priority.py
+++ b/test/metrics/android_mem_by_priority.py
@@ -23,7 +23,7 @@
 swap_member = 2
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'com.google.android.calendar')
diff --git a/test/metrics/android_startup.py b/test/metrics/android_startup.py
index 8a1ad9c..a6a94bb 100644
--- a/test/metrics/android_startup.py
+++ b/test/metrics/android_startup.py
@@ -19,7 +19,7 @@
 import synth_common
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'com.google.android.calendar', 10001)
diff --git a/test/metrics/android_startup_breakdown.py b/test/metrics/android_startup_breakdown.py
index fe30239..8325284 100644
--- a/test/metrics/android_startup_breakdown.py
+++ b/test/metrics/android_startup_breakdown.py
@@ -19,7 +19,7 @@
 import synth_common
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'com.google.android.calendar')
diff --git a/test/metrics/android_startup_cpu.py b/test/metrics/android_startup_cpu.py
index 35c51da..75aa7b6 100644
--- a/test/metrics/android_startup_cpu.py
+++ b/test/metrics/android_startup_cpu.py
@@ -33,7 +33,7 @@
 trace.add_cpufreq(ts=15 * 1000000, freq=8000000, cpu=6)
 
 # Add 3 processes. This also adds one main thread per process.
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(pid=1, ppid=0, cmdline="Process1")
 trace.add_process(pid=2, ppid=0, cmdline="Process2")
 trace.add_process(pid=3, ppid=0, cmdline="Process3")
diff --git a/test/metrics/android_startup_process_track.py b/test/metrics/android_startup_process_track.py
index 4d927b1..a65406f 100644
--- a/test/metrics/android_startup_process_track.py
+++ b/test/metrics/android_startup_process_track.py
@@ -49,7 +49,7 @@
 # Verify that each startup is only associated with a single process
 # (i.e. process exit is taken into account).
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 add_startup(trace, ts=100, pid=3)
diff --git a/test/synth_common.py b/test/synth_common.py
index 45cf760..52f033f 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -39,8 +39,10 @@
     self.packet = self.trace.packet.add()
     self.packet.ftrace_events.cpu = cpu
 
-  def add_packet(self):
+  def add_packet(self, ts=None):
     self.packet = self.trace.packet.add()
+    if ts is not None:
+      self.packet.timestamp = ts
     return self.packet
 
   def __add_ftrace_event(self, ts, tid):
@@ -176,11 +178,6 @@
   def add_atrace_async_end(self, ts, tid, pid, buf):
     self.add_print(ts, tid, 'F|{}|{}|0'.format(pid, buf))
 
-  def add_process_tree_packet(self, ts=None):
-    self.packet = self.trace.packet.add()
-    if ts is not None:
-      self.packet.timestamp = ts
-
   def add_process(self, pid, ppid, cmdline, uid=None):
     process = self.packet.process_tree.processes.add()
     process.pid = pid
@@ -378,6 +375,20 @@
       buffer_event.type = event_type
     buffer_event.duration_ns = duration
 
+  def add_cpu(self, freqs):
+    cpu = self.packet.cpu_info.cpus.add()
+    for freq in freqs:
+      cpu.frequencies.append(freq)
+
+  def add_process_stats(self, pid, freqs):
+    process = self.packet.process_stats.processes.add()
+    process.pid = pid
+    thread = process.threads.add()
+    thread.tid = pid * 10
+    for index in freqs:
+      thread.cpu_freq_indices.append(index)
+      thread.cpu_freq_ticks.append(freqs[index])
+
 
 def create_trace():
   parser = argparse.ArgumentParser()
diff --git a/test/trace_processor/index b/test/trace_processor/index
index 7fef4c4..bea869f 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -213,6 +213,7 @@
 
 # Thread time_in_state
 thread_time_in_state.textproto thread_time_in_state.sql thread_time_in_state.out
+thread_time_in_state_annotations.py thread_time_in_state_annotations.sql thread_time_in_state_annotations.out
 
 # Initial display state
 initial_display_state.textproto initial_display_state.sql initial_display_state.out
diff --git a/test/trace_processor/kernel_lmk.py b/test/trace_processor/kernel_lmk.py
index 98681f5..e653950 100644
--- a/test/trace_processor/kernel_lmk.py
+++ b/test/trace_processor/kernel_lmk.py
@@ -18,7 +18,7 @@
 import synth_common
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(pid=1, ppid=0, cmdline="init")
 trace.add_process(pid=2, ppid=1, cmdline="two_thread_process")
 trace.add_process(pid=4, ppid=1, cmdline="single_thread_process")
diff --git a/test/trace_processor/print_systrace_unsigned.py b/test/trace_processor/print_systrace_unsigned.py
index e3e5d56..f058b0e 100644
--- a/test/trace_processor/print_systrace_unsigned.py
+++ b/test/trace_processor/print_systrace_unsigned.py
@@ -32,7 +32,7 @@
 
 # Without special-casing, we print everything as unsigned decimal.
 
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(pid=10, ppid=1, cmdline="perfetto")
 
 trace.add_ftrace_packet(cpu=0)
diff --git a/test/trace_processor/process_parent_pid_tracking_1.py b/test/trace_processor/process_parent_pid_tracking_1.py
index 08db19c..6da9547 100644
--- a/test/trace_processor/process_parent_pid_tracking_1.py
+++ b/test/trace_processor/process_parent_pid_tracking_1.py
@@ -25,7 +25,7 @@
 trace = synth_common.create_trace()
 
 # Create a multi-threaded process which will be forked below.
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "main_thread")
 trace.add_thread(11, 10, "worker_thread")
 
diff --git a/test/trace_processor/process_parent_pid_tracking_2.py b/test/trace_processor/process_parent_pid_tracking_2.py
index 1685d59..fb31a99 100644
--- a/test/trace_processor/process_parent_pid_tracking_2.py
+++ b/test/trace_processor/process_parent_pid_tracking_2.py
@@ -30,7 +30,7 @@
 trace.add_newtask(ts=15, tid=11, new_tid=20, new_comm='child', flags=0)
 
 # Create a multi-threaded process which will be forked below.
-trace.add_process_tree_packet(ts=25)
+trace.add_packet(ts=25)
 trace.add_process(10, 0, "main_thread")
 trace.add_thread(11, 10, "worker_thread")
 
diff --git a/test/trace_processor/process_tracking_exec.py b/test/trace_processor/process_tracking_exec.py
index 672ce79..1b075fe 100644
--- a/test/trace_processor/process_tracking_exec.py
+++ b/test/trace_processor/process_tracking_exec.py
@@ -24,7 +24,7 @@
 trace = synth_common.create_trace()
 
 # Create a parent process which  will be forked below.
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "parent")
 
 # Fork off the new process and then kill it 5ns later.
@@ -33,7 +33,7 @@
 trace.add_sched(ts=16, prev_pid=10, next_pid=11, next_comm='child')
 
 # Create a parent process which  will be forked below.
-trace.add_process_tree_packet(ts=20)
+trace.add_packet(ts=20)
 trace.add_process(11, 0, "child_process")
 
 trace.add_ftrace_packet(0)
@@ -41,7 +41,7 @@
     ts=25, tid=11, old_comm='child', new_comm='true_name', oom_score_adj=1000)
 
 # Create a parent process which  will be forked below.
-trace.add_process_tree_packet(ts=30)
+trace.add_packet(ts=30)
 trace.add_process(11, 10, "true_process_name")
 
 print(trace.trace.SerializeToString())
diff --git a/test/trace_processor/process_tracking_short_lived_1.py b/test/trace_processor/process_tracking_short_lived_1.py
index a147a53..7811898 100644
--- a/test/trace_processor/process_tracking_short_lived_1.py
+++ b/test/trace_processor/process_tracking_short_lived_1.py
@@ -25,7 +25,7 @@
 trace = synth_common.create_trace()
 
 # Create a parent process which  will be forked below.
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "parent")
 
 # Fork off the new process and then kill it 5ns later.
diff --git a/test/trace_processor/process_tracking_short_lived_2.py b/test/trace_processor/process_tracking_short_lived_2.py
index 957b8c4..3eab4b6 100644
--- a/test/trace_processor/process_tracking_short_lived_2.py
+++ b/test/trace_processor/process_tracking_short_lived_2.py
@@ -26,7 +26,7 @@
 trace = synth_common.create_trace()
 
 # Create a parent process which  will be forked below.
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "parent")
 
 # Fork off the new process and then kill it 5ns later.
diff --git a/test/trace_processor/reused_thread_print.py b/test/trace_processor/reused_thread_print.py
index 6c7815f..576eada 100644
--- a/test/trace_processor/reused_thread_print.py
+++ b/test/trace_processor/reused_thread_print.py
@@ -26,7 +26,7 @@
 trace = synth_common.create_trace()
 
 # Create a parent process which  will be forked below.
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "parent")
 trace.add_process(11, 0, "short_lived")
 
diff --git a/test/trace_processor/rss_stat_after_free.py b/test/trace_processor/rss_stat_after_free.py
index d7ac469..d408955 100644
--- a/test/trace_processor/rss_stat_after_free.py
+++ b/test/trace_processor/rss_stat_after_free.py
@@ -24,7 +24,7 @@
 
 trace = synth_common.create_trace()
 
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 1, "parent_process")
 trace.add_process(11, 1, "other_process")
 
diff --git a/test/trace_processor/rss_stat_legacy.py b/test/trace_processor/rss_stat_legacy.py
index 2fb682f..c9d6c17 100644
--- a/test/trace_processor/rss_stat_legacy.py
+++ b/test/trace_processor/rss_stat_legacy.py
@@ -24,7 +24,7 @@
 
 trace = synth_common.create_trace()
 
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "process")
 
 trace.add_ftrace_packet(0)
diff --git a/test/trace_processor/rss_stat_mm_id.py b/test/trace_processor/rss_stat_mm_id.py
index d2b5e5a..d7101a5 100644
--- a/test/trace_processor/rss_stat_mm_id.py
+++ b/test/trace_processor/rss_stat_mm_id.py
@@ -23,7 +23,7 @@
 
 trace = synth_common.create_trace()
 
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 0, "process")
 
 trace.add_ftrace_packet(0)
diff --git a/test/trace_processor/rss_stat_mm_id_clone.py b/test/trace_processor/rss_stat_mm_id_clone.py
index 308b6b4..42319c1 100644
--- a/test/trace_processor/rss_stat_mm_id_clone.py
+++ b/test/trace_processor/rss_stat_mm_id_clone.py
@@ -23,7 +23,7 @@
 
 trace = synth_common.create_trace()
 
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 1, "parent_process")
 trace.add_process(3, 2, "kernel_thread")
 
diff --git a/test/trace_processor/rss_stat_mm_id_reuse.py b/test/trace_processor/rss_stat_mm_id_reuse.py
index 58d7b4d..142fe6c 100644
--- a/test/trace_processor/rss_stat_mm_id_reuse.py
+++ b/test/trace_processor/rss_stat_mm_id_reuse.py
@@ -23,7 +23,7 @@
 
 trace = synth_common.create_trace()
 
-trace.add_process_tree_packet(ts=1)
+trace.add_packet(ts=1)
 trace.add_process(10, 1, "parent_process")
 
 trace.add_ftrace_packet(1)
diff --git a/test/trace_processor/synth_1.py b/test/trace_processor/synth_1.py
index dbead5c..88c466a 100644
--- a/test/trace_processor/synth_1.py
+++ b/test/trace_processor/synth_1.py
@@ -19,7 +19,7 @@
 import synth_common
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, "init")
 trace.add_process(2, 1, "two_thread_process")
 trace.add_process(4, 1, "single_thread_process")
diff --git a/test/trace_processor/synth_oom.py b/test/trace_processor/synth_oom.py
index cc29268..434ef6e 100644
--- a/test/trace_processor/synth_oom.py
+++ b/test/trace_processor/synth_oom.py
@@ -21,7 +21,7 @@
 anon_member = 1
 
 trace = synth_common.create_trace()
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(1, 0, "init")
 trace.add_process(2, 1, "process_a")
 trace.add_process(3, 1, "process_b")
diff --git a/test/trace_processor/synth_process_tracking.py b/test/trace_processor/synth_process_tracking.py
index a7a4d2d..e44f196 100644
--- a/test/trace_processor/synth_process_tracking.py
+++ b/test/trace_processor/synth_process_tracking.py
@@ -39,7 +39,7 @@
 # In the synthetic /proc/pic scraper packet, we pretend we missed p1-1. At the
 # SQL level we should be able to tell that p1-t0 and p1-t2 belong to 'process1'
 # but p1-t1 should be left unjoinable.
-trace.add_process_tree_packet(ts=5)
+trace.add_packet(ts=5)
 trace.add_process(10, 0, "process1", 1001)
 trace.add_thread(12, 10, "p1-t2")
 
@@ -57,7 +57,7 @@
 trace.add_sched(ts=14, prev_pid=22, next_pid=0, prev_comm='p2-t2')
 
 # From the process tracker viewpoint we pretend we only scraped tids=20,21.
-trace.add_process_tree_packet(ts=15)
+trace.add_packet(ts=15)
 trace.add_process(20, 0, "process_2", 1002)
 trace.add_thread(21, 20, "p2-t1")
 
@@ -78,7 +78,7 @@
     ts=24, prev_pid=31, next_pid=32, prev_comm='p3-t1', next_comm='p3-t2')
 trace.add_newtask(
     ts=25, tid=32, new_tid=34, new_comm='p3-t4', flags=CLONE_THREAD)
-trace.add_process_tree_packet(ts=26)
+trace.add_packet(ts=26)
 trace.add_process(30, 0, "process_3")
 trace.add_thread(31, 30, "p3-t1")
 
@@ -89,7 +89,7 @@
 trace.add_sched(
     ts=28, prev_pid=32, next_pid=40, prev_comm='p3-t2', next_comm='p4-t0')
 
-trace.add_process_tree_packet(ts=29)
+trace.add_packet(ts=29)
 trace.add_process(40, 0, "process_4")
 
 # And now, this new process starts a new thread that recycles TID=31 (previously
diff --git a/test/trace_processor/syscall.py b/test/trace_processor/syscall.py
index dd823b3..3ca27ef 100644
--- a/test/trace_processor/syscall.py
+++ b/test/trace_processor/syscall.py
@@ -20,7 +20,7 @@
 trace = synth_common.create_trace()
 trace.add_system_info(arch='aarch64')
 
-trace.add_process_tree_packet()
+trace.add_packet()
 trace.add_process(pid=1, ppid=0, cmdline="init")
 trace.add_process(pid=2, ppid=1, cmdline="two_thread_process")
 trace.add_process(pid=4, ppid=1, cmdline="single_thread_process")
diff --git a/test/trace_processor/thread_time_in_state_annotations.out b/test/trace_processor/thread_time_in_state_annotations.out
new file mode 100644
index 0000000..55243be
--- /dev/null
+++ b/test/trace_processor/thread_time_in_state_annotations.out
@@ -0,0 +1,8 @@
+
+
+"track_type","track_name","ts","dur","upid","value"
+"counter","Total unknown core cycles / sec",3000000000,1000000000,0,33000
+"counter","Thread 10 (1) (unknown core)",3000000000,1000000000,1,22000
+"counter","Thread 20 (3) (unknown core)",3000000000,1000000000,2,11000
+"counter","Thread 10 (1) (unknown core)",2000000000,1000000000,1,1100
+"counter","Total unknown core cycles / sec",2000000000,1000000000,0,1100
diff --git a/test/trace_processor/thread_time_in_state_annotations.py b/test/trace_processor/thread_time_in_state_annotations.py
new file mode 100644
index 0000000..e26e22e
--- /dev/null
+++ b/test/trace_processor/thread_time_in_state_annotations.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+# Copyright (C) 2020 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.
+
+from os import sys, path
+
+sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
+import synth_common
+
+# Since we do various time based conversions to build cycles/sec, ensure that
+# the timestamps look a bit realistic so they don't make those results look
+# weird.
+SEC = 1000000000
+
+trace = synth_common.create_trace()
+
+trace.add_system_info(arch='x86_64')
+trace.packet.system_info.hz = 1
+
+trace.add_packet(1)
+trace.add_cpu([100, 200])
+trace.add_cpu([1000, 2000])
+
+trace.add_packet(1 * SEC)
+trace.add_process_stats(pid=1, freqs={1: 1, 2: 1, 3: 1, 4: 1})
+trace.add_process_stats(pid=2, freqs={1: 1, 2: 1, 3: 1, 4: 1})
+
+trace.add_packet(2 * SEC)
+trace.add_process_stats(pid=1, freqs={1: 2, 3: 2})
+# Don't log anything for pid=2 thread, test that the packet at t=3 is based
+# against t=2 anyway.
+
+trace.add_packet(3 * SEC)
+trace.add_process_stats(pid=1, freqs={2: 11, 4: 11})
+trace.add_process_stats(pid=2, freqs={1: 11, 3: 11})
+
+print(trace.trace.SerializeToString())
diff --git a/test/trace_processor/thread_time_in_state_annotations.sql b/test/trace_processor/thread_time_in_state_annotations.sql
new file mode 100644
index 0000000..be86992
--- /dev/null
+++ b/test/trace_processor/thread_time_in_state_annotations.sql
@@ -0,0 +1,21 @@
+--
+-- Copyright 2020 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.
+--
+-- Create so that RUN_METRIC will run without outputting any rows.
+CREATE TABLE TEST_TMP AS
+SELECT RUN_METRIC('android/android_thread_time_in_state.sql');
+DROP TABLE TEST_TMP;
+
+SELECT * FROM android_thread_time_in_state_annotations;