Initial trace-based startup metric in AOSP

- Partitions the trace into spans based on the initial intent
- Correlates these spans with an actual launching event
- Breaks down main thread by task state

Bug: b/132775271
Change-Id: I14765c5591f787f269d47b5fe217e458de2eb28e
diff --git a/Android.bp b/Android.bp
index 05add93..0e4b6eb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,6 +20,9 @@
     "src/trace_processor/metrics/android/android_mem.sql",
     "src/trace_processor/metrics/android/android_mem_lmk.sql",
     "src/trace_processor/metrics/android/android_mem_proc_counters.sql",
+    "src/trace_processor/metrics/android/android_startup.sql",
+    "src/trace_processor/metrics/android/android_startup_launches.sql",
+    "src/trace_processor/metrics/android/android_task_state.sql",
   ],
   cmd: "$(location tools/gen_merged_sql_metrics.py) --cpp_out=$(out) $(in)",
   out: [
@@ -1063,6 +1066,7 @@
   name: "perfetto_protos_perfetto_metrics_android_zero_gen",
   srcs: [
     "protos/perfetto/metrics/android/mem_metric.proto",
+    "protos/perfetto/metrics/android/startup_metric.proto",
   ],
   tools: [
     "aprotoc",
@@ -1071,6 +1075,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
   out: [
     "external/perfetto/protos/perfetto/metrics/android/mem_metric.pbzero.cc",
+    "external/perfetto/protos/perfetto/metrics/android/startup_metric.pbzero.cc",
   ],
 }
 
@@ -1079,6 +1084,7 @@
   name: "perfetto_protos_perfetto_metrics_android_zero_gen_headers",
   srcs: [
     "protos/perfetto/metrics/android/mem_metric.proto",
+    "protos/perfetto/metrics/android/startup_metric.proto",
   ],
   tools: [
     "aprotoc",
@@ -1087,6 +1093,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
   out: [
     "external/perfetto/protos/perfetto/metrics/android/mem_metric.pbzero.h",
+    "external/perfetto/protos/perfetto/metrics/android/startup_metric.pbzero.h",
   ],
   export_include_dirs: [
     "protos",
diff --git a/BUILD b/BUILD
index dc108cd..71d1bad 100644
--- a/BUILD
+++ b/BUILD
@@ -27,6 +27,9 @@
         "src/trace_processor/metrics/android/android_mem.sql",
         "src/trace_processor/metrics/android/android_mem_lmk.sql",
         "src/trace_processor/metrics/android/android_mem_proc_counters.sql",
+        "src/trace_processor/metrics/android/android_startup.sql",
+        "src/trace_processor/metrics/android/android_startup_launches.sql",
+        "src/trace_processor/metrics/android/android_task_state.sql",
     ],
     outs = [
         "src/trace_processor/metrics/sql_metrics.h",
diff --git a/protos/BUILD b/protos/BUILD
index bf721d9..473c8e4 100644
--- a/protos/BUILD
+++ b/protos/BUILD
@@ -177,6 +177,7 @@
     name = "metrics_android_zero",
     srcs = [
         "perfetto/metrics/android/mem_metric.proto",
+        "perfetto/metrics/android/startup_metric.proto",
     ],
 )
 
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index e47c896..f2bf316 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -16,7 +16,10 @@
 import("../../../../gn/proto_library.gni")
 import("../../../../gn/protozero_library.gni")
 
-common_sources = [ "mem_metric.proto" ]
+common_sources = [
+  "mem_metric.proto",
+  "startup_metric.proto",
+]
 
 proto_library("lite") {
   generate_python = false
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
new file mode 100644
index 0000000..b0a7fff
--- /dev/null
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package perfetto.protos;
+
+// A simplified view of the task state durations for a thread
+// and a span of time.
+message TaskStateBreakdown {
+  optional int64 running_dur_ns = 1;
+  optional int64 runnable_dur_ns = 2;
+  optional int64 uninterruptible_sleep_dur_ns = 3;
+  optional int64 interruptible_sleep_dur_ns = 4;
+}
+
+// Android app startup metrics.
+message AndroidStartupMetric {
+  // Timing information spanning the intent received by the
+  // activity manager to the first frame drawn.
+  // All times and durations in nanoseconds (ns).
+  message ToFirstFrame {
+    optional int64 dur_ns = 1;
+    optional TaskStateBreakdown main_thread_by_task_state = 2;
+  }
+
+  message Startup {
+    // Random id uniquely identifying an app startup in this trace.
+    optional uint32 startup_id = 1;
+
+    // Name of the package launched
+    optional string package_name = 2;
+
+    // Name of the process launched
+    optional string process_name = 3;
+
+    // Did we start a new process
+    optional bool started_new_process = 4;
+
+    optional ToFirstFrame to_first_frame = 5;
+  }
+
+  repeated Startup startup = 1;
+}
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 234c899..839da04 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -20,9 +20,13 @@
 package perfetto.protos;
 
 import "perfetto/metrics/android/mem_metric.proto";
+import "perfetto/metrics/android/startup_metric.proto";
 
 // Root message for all Perfetto-based metrics.
 message TraceMetrics {
   // Memory metrics on Android (owned by the Android Telemetry team).
   optional AndroidMemoryMetric android_mem = 1;
+
+  // Startup metrics on Android (owned by the Android Telemetry team).
+  optional AndroidStartupMetric android_startup = 2;
 }
diff --git a/src/trace_processor/metrics/BUILD.gn b/src/trace_processor/metrics/BUILD.gn
index 7bea83a..88ae7fb 100644
--- a/src/trace_processor/metrics/BUILD.gn
+++ b/src/trace_processor/metrics/BUILD.gn
@@ -18,6 +18,9 @@
   "android/android_mem.sql",
   "android/android_mem_lmk.sql",
   "android/android_mem_proc_counters.sql",
+  "android/android_startup_launches.sql",
+  "android/android_task_state.sql",
+  "android/android_startup.sql",
 ]
 
 config("gen_config") {
diff --git a/src/trace_processor/metrics/android/android_startup.sql b/src/trace_processor/metrics/android/android_startup.sql
new file mode 100644
index 0000000..6e2413e
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_startup.sql
@@ -0,0 +1,98 @@
+--
+-- Copyright 2019 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 the base tables and views containing the launch spans.
+SELECT RUN_METRIC('android_startup_launches.sql');
+SELECT RUN_METRIC('android_task_state.sql');
+
+-- Slices for forked processes. Never present in hot starts.
+-- Prefer this over process start_ts, since the process might have
+-- been preforked.
+CREATE VIEW zygote_fork_slices AS
+SELECT slices.ts, slices.dur, STR_SPLIT(slices.name, ": ", 1) AS process_name
+FROM slices WHERE name LIKE 'Start proc: %';
+
+CREATE TABLE zygote_forks_by_id AS
+SELECT
+  launches.id,
+  zygote_fork_slices.ts,
+  zygote_fork_slices.dur
+FROM zygote_fork_slices
+JOIN launches
+ON (launches.ts < zygote_fork_slices.ts
+    AND zygote_fork_slices.ts + zygote_fork_slices.dur < launches.ts_end
+    AND zygote_fork_slices.process_name = launches.package
+);
+
+CREATE VIEW launch_main_thread AS
+SELECT
+  launches.ts AS ts,
+  launches.dur AS dur,
+  launches.id AS launch_id,
+  thread.utid AS utid
+FROM launches
+JOIN process USING(upid)
+JOIN thread ON (process.upid = thread.upid AND process.pid = thread.tid)
+ORDER BY ts;
+
+CREATE VIRTUAL TABLE main_thread_state
+USING SPAN_JOIN(
+  launch_main_thread PARTITIONED utid,
+  task_state PARTITIONED utid);
+
+CREATE VIEW launch_by_thread_state AS
+SELECT launch_id, state, SUM(dur) AS dur
+FROM main_thread_state
+GROUP BY 1, 2;
+
+CREATE VIEW startup_view AS
+SELECT
+  AndroidStartupMetric_Startup(
+    'startup_id', launches.id,
+    'package_name', launches.package,
+    'process_name', process.name,
+    'started_new_process', EXISTS(SELECT TRUE FROM zygote_forks_by_id WHERE id = launches.id),
+    'to_first_frame', AndroidStartupMetric_ToFirstFrame(
+      'dur_ns', launches.dur,
+      'main_thread_by_task_state', TaskStateBreakdown(
+        'running_dur_ns', IFNULL(
+            (
+            SELECT dur FROM launch_by_thread_state
+            WHERE launch_id = launches.id AND state = 'running'
+            ), 0),
+        'runnable_dur_ns', IFNULL(
+            (
+            SELECT dur FROM launch_by_thread_state
+            WHERE launch_id = launches.id AND state = 'runnable'
+            ), 0),
+        'uninterruptible_sleep_dur_ns', IFNULL(
+            (
+            SELECT dur FROM launch_by_thread_state
+            WHERE launch_id = launches.id AND state = 'uninterruptible'
+            ), 0),
+        'interruptible_sleep_dur_ns', IFNULL(
+            (
+            SELECT dur FROM launch_by_thread_state
+            WHERE launch_id = launches.id AND state = 'interruptible'
+            ), 0)
+      )
+    )
+  )
+FROM launches
+LEFT JOIN process USING(upid);
+
+CREATE VIEW android_startup_output AS
+SELECT AndroidStartupMetric('startup', 'startup_view');
diff --git a/src/trace_processor/metrics/android/android_startup_launches.sql b/src/trace_processor/metrics/android/android_startup_launches.sql
new file mode 100644
index 0000000..9bda400
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_startup_launches.sql
@@ -0,0 +1,117 @@
+--
+-- Copyright 2019 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.
+--
+
+-- Helper to optimize the query for launching events
+-- TODO(b/132771327): remove when fixed
+CREATE TABLE launching_events_helper AS
+SELECT
+  arg_set_id,
+  STR_SPLIT(STR_SPLIT(args.string_value, "|", 2), ": ", 1) package_name,
+  STR_SPLIT(args.string_value, "|", 0) type
+FROM args
+WHERE string_value LIKE '%|launching: %';
+
+-- TODO: Replace with proper async slices once available
+-- The start of the launching event corresponds to the end of the AM handling
+-- the startActivity intent, whereas the end corresponds to the first frame drawn.
+-- Only successful app launches have a launching event.
+CREATE TABLE launching_events AS
+SELECT
+  ts,
+  package_name,
+  type
+FROM raw
+JOIN launching_events_helper USING(arg_set_id)
+JOIN thread USING(utid)
+JOIN process USING(upid)
+WHERE raw.name = 'print' AND process.name = 'system_server';
+
+-- Marks the beginning of the trace and is equivalent to when the statsd launch
+-- logging begins.
+CREATE VIEW activity_intent_received AS
+SELECT ts FROM slices
+WHERE name = 'MetricsLogger:launchObserverNotifyIntentStarted';
+
+-- We partition the trace into spans based on posted activity intents.
+-- We will refine these progressively in the next steps to only encompass
+-- activity starts.
+CREATE TABLE activity_intent_recv_spans(id INT, ts BIG INT, dur BIG INT);
+
+INSERT INTO activity_intent_recv_spans
+SELECT
+  ROW_NUMBER()
+    OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS id,
+  ts,
+  LEAD(ts, 1, (SELECT end_ts FROM trace_bounds)) OVER(ORDER BY ts) - ts AS dur
+FROM activity_intent_received
+ORDER BY ts;
+
+-- Filter activity_intent_recv_spans, keeping only the ones that triggered
+-- a launch.
+CREATE VIEW launches_started AS
+SELECT * FROM activity_intent_recv_spans
+WHERE 1 = (
+  SELECT COUNT(1)
+  FROM launching_events
+  WHERE TRUE
+    AND type = 'S'
+    AND activity_intent_recv_spans.ts < ts
+    AND ts < activity_intent_recv_spans.ts + activity_intent_recv_spans.dur);
+
+-- All activity launches in the trace, keyed by ID.
+CREATE TABLE package_launches_succeeded(
+  ts BIG INT,
+  ts_end BIG INT,
+  dur BIG INT,
+  id INT,
+  package STRING);
+
+INSERT INTO package_launches_succeeded
+SELECT
+  started.ts AS ts,
+  finished.ts AS ts_end,
+  finished.ts - started.ts AS dur,
+  started.id AS id,
+  finished.package_name AS package
+FROM launches_started AS started
+JOIN (SELECT * FROM launching_events WHERE type = 'F') AS finished
+ON started.ts < finished.ts AND finished.ts <= started.ts + started.dur;
+
+-- Base launches table. A launch is uniquely identified by its id.
+CREATE TABLE launches(
+  ts BIG INT,
+  ts_end BIG INT,
+  dur BIG INT,
+  id INT,
+  package STRING,
+  upid BIG INT);
+
+-- We make the (not always correct) simplification that process == package
+INSERT INTO launches
+SELECT
+  ts,
+  ts_end,
+  dur,
+  id,
+  package,
+  (
+    SELECT upid
+    FROM process
+    WHERE name = pls.package
+    AND (start_ts IS NULL OR start_ts < pls.ts_end)
+    ORDER BY start_ts DESC
+    LIMIT 1) AS upid
+FROM package_launches_succeeded AS pls;
diff --git a/src/trace_processor/metrics/android/android_task_state.sql b/src/trace_processor/metrics/android/android_task_state.sql
new file mode 100644
index 0000000..e296390
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_task_state.sql
@@ -0,0 +1,37 @@
+--
+-- Copyright 2019 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.
+--
+
+-- Spans for each thread not in a running state.
+CREATE TABLE unsched_state (ts BIG INT, dur BIG INT, utid BIG INT, state STRING);
+
+INSERT INTO unsched_state
+SELECT
+  ts_end AS ts,
+  LEAD(ts, 1, null) OVER(PARTITION BY utid ORDER BY ts) - ts_end AS dur,
+  utid,
+  CASE
+    WHEN INSTR(end_state, 'R') > 0 THEN 'runnable'
+    WHEN INSTR(end_state, 'D') > 0 THEN 'uninterruptible'
+    WHEN INSTR(end_state, 'S') > 0 THEN 'interruptible'
+  END AS state
+FROM sched;
+
+-- Create a single view for the task states.
+CREATE VIEW task_state AS
+SELECT utid, state, ts, dur FROM unsched_state
+UNION
+SELECT utid, 'running', ts, dur FROM sched
+ORDER BY ts;
diff --git a/src/trace_processor/metrics/metrics.descriptor.h b/src/trace_processor/metrics/metrics.descriptor.h
index 4e70b86..33fd8aa 100644
--- a/src/trace_processor/metrics/metrics.descriptor.h
+++ b/src/trace_processor/metrics/metrics.descriptor.h
@@ -12,14 +12,14 @@
 // SHA1(tools/gen_binary_descriptors)
 // 750d7d8f95621b45d4b6430d6f8808087a8702e6
 // SHA1(protos/perfetto/metrics/metrics.proto)
-// 3fc743f705a514100577d023096291939c010c6d
+// d4bda49b11630f3175699774d467819548947998
 
 // This is the proto Metrics encoded as a ProtoFileDescriptor to allow
 // for reflection without libprotobuf full/non-lite protos.
 
 namespace perfetto {
 
-constexpr std::array<uint8_t, 2179> kMetricsDescriptor{
+constexpr std::array<uint8_t, 3089> kMetricsDescriptor{
     {0x0a, 0xc6, 0x0f, 0x0a, 0x29, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
      0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
      0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x65,
@@ -186,22 +186,98 @@
      0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a,
      0x06, 0x67, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28,
      0x01, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x42, 0x02, 0x48,
-     0x03, 0x0a, 0xb7, 0x01, 0x0a, 0x1e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x29, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x22, 0x55, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x12, 0x45, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x24, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x03, 0x0a, 0x8b, 0x06, 0x0a, 0x2d, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
+     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x74,
+     0x75, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xe0, 0x01, 0x0a,
+     0x12, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x72,
+     0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x72,
+     0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e,
+     0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x75, 0x6e,
+     0x6e, 0x69, 0x6e, 0x67, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x26, 0x0a,
+     0x0f, 0x72, 0x75, 0x6e, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x75,
+     0x72, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d,
+     0x72, 0x75, 0x6e, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x4e,
+     0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72,
+     0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x6c, 0x65,
+     0x65, 0x70, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x03, 0x20,
+     0x01, 0x28, 0x03, 0x52, 0x19, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72,
+     0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x53, 0x6c, 0x65, 0x65,
+     0x70, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x69, 0x6e,
+     0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x5f,
+     0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73,
+     0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x69, 0x6e, 0x74, 0x65,
+     0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x53, 0x6c, 0x65,
+     0x65, 0x70, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x22, 0xe1, 0x03, 0x0a, 0x14,
+     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74,
+     0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x47, 0x0a, 0x07,
+     0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, 0x03, 0x28,
+     0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
      0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x42, 0x02, 0x48, 0x03}};
+     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
+     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
+     0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x1a, 0x84, 0x01,
+     0x0a, 0x0c, 0x54, 0x6f, 0x46, 0x69, 0x72, 0x73, 0x74, 0x46, 0x72, 0x61,
+     0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73,
+     0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x75, 0x72, 0x4e,
+     0x73, 0x12, 0x5d, 0x0a, 0x19, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74, 0x68,
+     0x72, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x74, 0x61, 0x73, 0x6b,
+     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77,
+     0x6e, 0x52, 0x15, 0x6d, 0x61, 0x69, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x61,
+     0x64, 0x42, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65,
+     0x1a, 0xf8, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
+     0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f,
+     0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74,
+     0x61, 0x72, 0x74, 0x75, 0x70, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70,
+     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+     0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61,
+     0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72,
+     0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
+     0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
+     0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x74, 0x61,
+     0x72, 0x74, 0x65, 0x64, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x72, 0x6f,
+     0x63, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11,
+     0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x4e, 0x65, 0x77, 0x50, 0x72,
+     0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x58, 0x0a, 0x0e, 0x74, 0x6f, 0x5f,
+     0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18,
+     0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74,
+     0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54, 0x6f, 0x46,
+     0x69, 0x72, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x0c, 0x74,
+     0x6f, 0x46, 0x69, 0x72, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x42,
+     0x02, 0x48, 0x03, 0x0a, 0xb7, 0x02, 0x0a, 0x1e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x29, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65,
+     0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x1a, 0x2d, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
+     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
+     0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d,
+     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x45, 0x0a, 0x0b, 0x61, 0x6e,
+     0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x18, 0x01, 0x20,
+     0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
+     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d,
+     0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+     0x69, 0x64, 0x4d, 0x65, 0x6d, 0x12, 0x4e, 0x0a, 0x0f, 0x61, 0x6e, 0x64,
+     0x72, 0x6f, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
+     0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72,
+     0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0e, 0x61,
+     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
+     0x70, 0x42, 0x02, 0x48, 0x03}};
 
 }  // namespace perfetto
 
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index a0892b4..2731638 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -124,6 +124,8 @@
                     nestable_slices_.start_ns().end(), &start_ns, &end_ns);
   MaybeUpdateMinMax(android_log_.timestamps().begin(),
                     android_log_.timestamps().end(), &start_ns, &end_ns);
+  MaybeUpdateMinMax(raw_events_.timestamps().begin(),
+                    raw_events_.timestamps().end(), &start_ns, &end_ns);
 
   if (start_ns == std::numeric_limits<int64_t>::max()) {
     return std::make_pair(0, 0);
diff --git a/test/metrics/android_startup.out b/test/metrics/android_startup.out
new file mode 100644
index 0000000..7b0036c
--- /dev/null
+++ b/test/metrics/android_startup.out
@@ -0,0 +1,17 @@
+android_startup {
+  startup {
+    startup_id: 2
+    package_name: "com.google.android.calendar"
+    process_name: "com.google.android.calendar"
+    started_new_process: false
+    to_first_frame {
+      dur_ns: 108
+      main_thread_by_task_state {
+        running_dur_ns: 51
+        runnable_dur_ns: 49
+        uninterruptible_sleep_dur_ns: 0
+        interruptible_sleep_dur_ns: 0
+      }
+    }
+  }
+}
diff --git a/test/metrics/android_startup.py b/test/metrics/android_startup.py
new file mode 100644
index 0000000..4023721
--- /dev/null
+++ b/test/metrics/android_startup.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python
+# Copyright (C) 2018 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
+
+trace = synth_common.create_trace()
+trace.add_process_tree_packet()
+trace.add_process(1, 0, 'init')
+trace.add_process(2, 1, 'system_server')
+trace.add_process(3, 1, 'com.google.android.calendar')
+
+trace.add_ftrace_packet(cpu=0)
+# Intent without any corresponding end state, will be ignored
+trace.add_atrace_begin(ts=100, tid=2, pid=2,
+    buf='MetricsLogger:launchObserverNotifyIntentStarted')
+trace.add_atrace_end(ts=101, tid=2, pid=2)
+
+# Valid start intent
+trace.add_atrace_begin(ts=102, tid=2, pid=2,
+    buf='MetricsLogger:launchObserverNotifyIntentStarted')
+trace.add_atrace_end(ts=103, tid=2, pid=2)
+
+trace.add_atrace_async_begin(ts=110, tid=2, pid=2,
+    buf='launching: com.google.android.calendar')
+
+trace.add_sched(ts=110, prev_pid=0, next_pid=3)
+trace.add_sched(ts=160, prev_pid=3, next_pid=0, prev_state='R')
+trace.add_sched(ts=209, prev_pid=0, next_pid=3)
+trace.add_sched(ts=211, prev_pid=3, next_pid=0, prev_state='R')
+
+trace.add_atrace_async_end(ts=210, tid=2, pid=2,
+    buf='launching: com.google.android.calendar')
+
+print(trace.trace.SerializeToString())
diff --git a/test/metrics/index b/test/metrics/index
index ec274ca..e94cc4f 100644
--- a/test/metrics/index
+++ b/test/metrics/index
@@ -1,2 +1,3 @@
 ../data/lmk_userspace.pb android_mem android_mem_lmk.out
 ../data/memory_counters.pb android_mem android_mem_counters.out
+android_startup.py android_startup android_startup.out
diff --git a/test/synth_common.py b/test/synth_common.py
index c454b69..7994000 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -25,7 +25,7 @@
   def __init__(self, trace):
     self.trace = trace
     self.proc_map = {}
-    self.proc_map[0] = "idle_thread"
+    self.proc_map[0] = 'idle_thread'
 
   def add_system_info(self, arch=None):
     self.packet = self.trace.packet.add()
@@ -54,13 +54,19 @@
     oom_score.oom_score_adj = oom_score_adj
     oom_score.pid = pid
 
-  def add_sched(self, ts, prev_pid, next_pid, prev_comm=None, next_comm=None):
+  def add_sched(self, ts, prev_pid, next_pid, prev_comm=None, next_comm=None,
+      prev_state=None):
     ftrace = self.__add_ftrace_event(ts, 0)
     ss = ftrace.sched_switch
     ss.prev_comm = prev_comm or self.proc_map[prev_pid]
     ss.prev_pid = prev_pid
     ss.next_pid = next_pid
     ss.next_comm = next_comm or self.proc_map[next_pid]
+    if prev_state:
+      if prev_state == 'R':
+        ss.prev_state = 0
+      else:
+        raise Exception('Invalid prev state {}'.format(prev_state))
 
   def add_cpufreq(self, ts, freq, cpu):
     ftrace = self.__add_ftrace_event(ts, 0)
@@ -91,6 +97,23 @@
     newtask.comm = new_comm
     newtask.clone_flags = flags
 
+  def add_print(self, ts, tid, buf):
+    ftrace = self.__add_ftrace_event(ts, tid)
+    print_event = getattr(ftrace, 'print')
+    print_event.buf = buf
+
+  def add_atrace_begin(self, ts, tid, pid, buf):
+    self.add_print(ts, tid, 'B|{}|{}'.format(pid, buf))
+
+  def add_atrace_end(self, ts, tid, pid):
+    self.add_print(ts, tid, 'E|{}'.format(pid))
+
+  def add_atrace_async_begin(self, ts, tid, pid, buf):
+    self.add_print(ts, tid, 'S|{}|{}|0'.format(pid, buf))
+
+  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:
@@ -134,5 +157,5 @@
       desc_by_path[desc.full_name] = desc
 
   trace = message_factory.MessageFactory().GetPrototype(
-      desc_by_path["perfetto.protos.Trace"])()
+      desc_by_path['perfetto.protos.Trace'])()
   return Trace(trace)