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;