Merge "Update CHANGELOG for v22.1"
diff --git a/Android.bp b/Android.bp
index 372249e..c7f2447 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8116,6 +8116,8 @@
         "src/trace_processor/metrics/sql/android/span_view_stats.sql",
         "src/trace_processor/metrics/sql/android/startup/hsc.sql",
         "src/trace_processor/metrics/sql/android/startup/launches.sql",
+        "src/trace_processor/metrics/sql/android/startup/launches_maxsdk28.sql",
+        "src/trace_processor/metrics/sql/android/startup/launches_minsdk29.sql",
         "src/trace_processor/metrics/sql/android/thread_counter_span_view.sql",
         "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql",
         "src/trace_processor/metrics/sql/chrome/actual_power_by_category.sql",
diff --git a/BUILD b/BUILD
index 94521a4..ccc51d8 100644
--- a/BUILD
+++ b/BUILD
@@ -1088,6 +1088,8 @@
         "src/trace_processor/metrics/sql/android/span_view_stats.sql",
         "src/trace_processor/metrics/sql/android/startup/hsc.sql",
         "src/trace_processor/metrics/sql/android/startup/launches.sql",
+        "src/trace_processor/metrics/sql/android/startup/launches_maxsdk28.sql",
+        "src/trace_processor/metrics/sql/android/startup/launches_minsdk29.sql",
         "src/trace_processor/metrics/sql/android/thread_counter_span_view.sql",
         "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql",
         "src/trace_processor/metrics/sql/chrome/actual_power_by_category.sql",
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index 7ccbad4..8f998dd 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -237,15 +237,14 @@
     if (glob_path.length() + 1 > MAX_PATH)
       return base::ErrStatus("Directory path %s is too long", dir_path.c_str());
     WIN32_FIND_DATAA ffd;
-    // We do not use a ScopedResource for the HANDLE from FindFirstFile because
-    // the invalid value INVALID_HANDLE_VALUE is not a constexpr under some
-    // compile configurations, and thus cannot be used as a template argument.
-    HANDLE hFind = FindFirstFileA(glob_path.c_str(), &ffd);
-    if (hFind == INVALID_HANDLE_VALUE) {
+
+    base::ScopedResource<HANDLE, FindClose, nullptr, false,
+                         base::PlatformHandleChecker>
+        hFind(FindFirstFileA(glob_path.c_str(), &ffd));
+    if (!hFind) {
       // For empty directories, there should be at least one entry '.'.
       // If FindFirstFileA returns INVALID_HANDLE_VALUE, this means directory
       // couldn't be accessed.
-      FindClose(hFind);
       return base::ErrStatus("Failed to open directory %s", cur_dir.c_str());
     }
     do {
@@ -254,13 +253,12 @@
       if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
         std::string subdir_path = cur_dir + ffd.cFileName + '/';
         dir_queue.push_back(subdir_path);
-      } else if (ffd.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) {
+      } else {
         const std::string full_path = cur_dir + ffd.cFileName;
         PERFETTO_CHECK(full_path.length() > root_dir_path.length());
         output.push_back(full_path.substr(root_dir_path.length()));
       }
-    } while (FindNextFileA(hFind, &ffd));
-    FindClose(hFind);
+    } while (FindNextFileA(*hFind, &ffd));
 #else
     ScopedDir dir = ScopedDir(opendir(cur_dir.c_str()));
     if (!dir) {
diff --git a/src/base/http/http_server.cc b/src/base/http/http_server.cc
index 17a1652..0b098e1 100644
--- a/src/base/http/http_server.cc
+++ b/src/base/http/http_server.cc
@@ -29,7 +29,7 @@
 namespace base {
 
 namespace {
-constexpr size_t kMaxPayloadSize = 32 * 1024 * 1024;
+constexpr size_t kMaxPayloadSize = 64 * 1024 * 1024;
 constexpr size_t kMaxRequestSize = kMaxPayloadSize + 4096;
 
 enum WebsocketOpcode : uint8_t {
@@ -464,11 +464,12 @@
   append("HTTP/1.1 ");
   append(http_code);
   append("\r\n");
-  for (const char* hdr : headers) {
-    if (strlen(hdr) == 0)
+  for (const char* hdr_cstr : headers) {
+    StringView hdr = (hdr_cstr);
+    if (hdr.empty())
       continue;
-    has_connection_header |= strncasecmp(hdr, "connection:", 11) == 0;
-    append(hdr);
+    has_connection_header |= hdr.substr(0, 11).CaseInsensitiveEq("connection:");
+    append(hdr_cstr);
     append("\r\n");
   }
   content_len_actual_ = 0;
diff --git a/src/base/http/http_server_unittest.cc b/src/base/http/http_server_unittest.cc
index 9726ab6..be65198 100644
--- a/src/base/http/http_server_unittest.cc
+++ b/src/base/http/http_server_unittest.cc
@@ -66,16 +66,17 @@
     auto checkpoint = task_runner_->CreateCheckpoint(checkpoint_name);
     std::string rxbuf;
     sock.SetBlocking(false);
-    task_runner_->AddFileDescriptorWatch(sock.fd(), [&] {
+    task_runner_->AddFileDescriptorWatch(sock.watch_handle(), [&] {
       char buf[1024]{};
       auto rsize = PERFETTO_EINTR(sock.Receive(buf, sizeof(buf)));
-      ASSERT_GE(rsize, 0);
+      if (rsize < 0)
+        return;
       rxbuf.append(buf, static_cast<size_t>(rsize));
       if (rsize == 0 || (min_bytes && rxbuf.length() >= min_bytes))
         checkpoint();
     });
     task_runner_->RunUntilCheckpoint(checkpoint_name);
-    task_runner_->RemoveFileDescriptorWatch(sock.fd());
+    task_runner_->RemoveFileDescriptorWatch(sock.watch_handle());
     return rxbuf;
   }
 
@@ -289,7 +290,9 @@
   srv_.AddAllowedOrigin("notallowed.com");
 
   HttpCli cli(&task_runner_);
-  EXPECT_CALL(handler_, OnHttpConnectionClosed(_));
+  auto close_checkpoint = task_runner_.CreateCheckpoint("close");
+  EXPECT_CALL(handler_, OnHttpConnectionClosed(_))
+      .WillOnce(InvokeWithoutArgs(close_checkpoint));
   EXPECT_CALL(handler_, OnHttpRequest(_))
       .WillOnce(Invoke([&](const HttpRequest& req) {
         EXPECT_EQ(req.origin.ToStdString(), "http://notallowed.com");
@@ -314,6 +317,7 @@
 
   EXPECT_EQ(cli.Recv(expected_resp.size()), expected_resp);
   cli.sock.Shutdown();
+  task_runner_.RunUntilCheckpoint("close");
 }
 
 }  // namespace
diff --git a/src/base/http/sha1.cc b/src/base/http/sha1.cc
index da3f753..f3e1e5d 100644
--- a/src/base/http/sha1.cc
+++ b/src/base/http/sha1.cc
@@ -30,7 +30,7 @@
 #if defined(__GNUC__)
   return __builtin_bswap32(x);
 #elif defined(_MSC_VER)
-  return _byteswap_ulong(val);
+  return _byteswap_ulong(x);
 #else
   return (((x & 0xff000000u) >> 24) | ((x & 0x00ff0000u) >> 8) |
           ((x & 0x0000ff00u) << 8) | ((x & 0x000000ffu) << 24));
diff --git a/src/base/unix_socket_unittest.cc b/src/base/unix_socket_unittest.cc
index c838663..45c09d5 100644
--- a/src/base/unix_socket_unittest.cc
+++ b/src/base/unix_socket_unittest.cc
@@ -332,53 +332,6 @@
   tx_thread.join();
 }
 
-// Regression test for b/193234818. SO_SNDTIMEO is unreliable on most systems.
-// It doesn't guarantee that the whole send() call blocks for at most X, as the
-// kernel rearms the timeout if the send buffers frees up and allows a partial
-// send. This test reproduces the issue 100% on Mac. Unfortunately on Linux the
-// repro seem to happen only when a suspend happens in the middle.
-TEST_F(UnixSocketTest, BlockingSendTimeout) {
-  TestTaskRunner ttr;
-  UnixSocketRaw send_sock;
-  UnixSocketRaw recv_sock;
-  std::tie(send_sock, recv_sock) =
-      UnixSocketRaw::CreatePairPosix(kTestSocket.family(), SockType::kStream);
-
-  auto blocking_send_done = ttr.CreateCheckpoint("blocking_send_done");
-
-  std::thread tx_thread([&] {
-    // Fill the tx buffer in non-blocking mode.
-    send_sock.SetBlocking(false);
-    char buf[1024 * 16]{};
-    while (send_sock.Send(buf, sizeof(buf)) > 0) {
-    }
-
-    // Then do a blocking send. It should return a partial value within the tx
-    // timeout.
-    send_sock.SetBlocking(true);
-    send_sock.SetTxTimeout(10);
-    ASSERT_LT(send_sock.Send(buf, sizeof(buf)),
-              static_cast<ssize_t>(sizeof(buf)));
-    ttr.PostTask(blocking_send_done);
-  });
-
-  // This task needs to be slow enough so that doesn't unblock the send, but
-  // fast enough so that within a blocking cycle, the send re-attempts and
-  // re-arms the timeout.
-  PeriodicTask read_slowly_task(&ttr);
-  PeriodicTask::Args args;
-  args.period_ms = 1;  // Read 1 byte every ms (1 KiB/s).
-  args.task = [&] {
-    char rxbuf[1]{};
-    recv_sock.Receive(rxbuf, sizeof(rxbuf));
-  };
-  read_slowly_task.Start(args);
-
-  ttr.RunUntilCheckpoint("blocking_send_done");
-  read_slowly_task.Reset();
-  tx_thread.join();
-}
-
 // Regression test for b/76155349 . If the receiver end disconnects while the
 // sender is in the middle of a large send(), the socket should gracefully give
 // up (i.e. Shutdown()) but not crash.
@@ -939,6 +892,53 @@
   ASSERT_EQ(hdr.msg_iov, nullptr);
   ASSERT_EQ(memcmp(&send_buf[0], &recv_buf[0], send_buf.size()), 0);
 }
+
+// Regression test for b/193234818. SO_SNDTIMEO is unreliable on most systems.
+// It doesn't guarantee that the whole send() call blocks for at most X, as the
+// kernel rearms the timeout if the send buffers frees up and allows a partial
+// send. This test reproduces the issue 100% on Mac. Unfortunately on Linux the
+// repro seem to happen only when a suspend happens in the middle.
+TEST_F(UnixSocketTest, BlockingSendTimeout) {
+  TestTaskRunner ttr;
+  UnixSocketRaw send_sock;
+  UnixSocketRaw recv_sock;
+  std::tie(send_sock, recv_sock) =
+      UnixSocketRaw::CreatePairPosix(kTestSocket.family(), SockType::kStream);
+
+  auto blocking_send_done = ttr.CreateCheckpoint("blocking_send_done");
+
+  std::thread tx_thread([&] {
+    // Fill the tx buffer in non-blocking mode.
+    send_sock.SetBlocking(false);
+    char buf[1024 * 16]{};
+    while (send_sock.Send(buf, sizeof(buf)) > 0) {
+    }
+
+    // Then do a blocking send. It should return a partial value within the tx
+    // timeout.
+    send_sock.SetBlocking(true);
+    send_sock.SetTxTimeout(10);
+    ASSERT_LT(send_sock.Send(buf, sizeof(buf)),
+              static_cast<ssize_t>(sizeof(buf)));
+    ttr.PostTask(blocking_send_done);
+  });
+
+  // This task needs to be slow enough so that doesn't unblock the send, but
+  // fast enough so that within a blocking cycle, the send re-attempts and
+  // re-arms the timeout.
+  PeriodicTask read_slowly_task(&ttr);
+  PeriodicTask::Args args;
+  args.period_ms = 1;  // Read 1 byte every ms (1 KiB/s).
+  args.task = [&] {
+    char rxbuf[1]{};
+    recv_sock.Receive(rxbuf, sizeof(rxbuf));
+  };
+  read_slowly_task.Start(args);
+
+  ttr.RunUntilCheckpoint("blocking_send_done");
+  read_slowly_task.Reset();
+  tx_thread.join();
+}
 #endif  // !OS_WIN
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 390d82f..403b300 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -223,8 +223,14 @@
 
 void FtraceParser::ParseFtraceStats(ConstBytes blob) {
   protos::pbzero::FtraceStats::Decoder evt(blob.data, blob.size);
-  size_t phase =
-      evt.phase() == protos::pbzero::FtraceStats_Phase_END_OF_TRACE ? 1 : 0;
+  bool is_start =
+      evt.phase() == protos::pbzero::FtraceStats_Phase_START_OF_TRACE;
+  bool is_end = evt.phase() == protos::pbzero::FtraceStats_Phase_END_OF_TRACE;
+  if (!is_start && !is_end) {
+    PERFETTO_ELOG("Ignoring unknown ftrace stats phase %d", evt.phase());
+    return;
+  }
+  size_t phase = is_end ? 1 : 0;
 
   // This code relies on the fact that each ftrace_cpu_XXX_end event is
   // just after the corresponding ftrace_cpu_XXX_begin event.
@@ -238,15 +244,81 @@
   for (auto it = evt.cpu_stats(); it; ++it) {
     protos::pbzero::FtraceCpuStats::Decoder cpu_stats(*it);
     int cpu = static_cast<int>(cpu_stats.cpu());
+
+    int64_t entries = static_cast<int64_t>(cpu_stats.entries());
+    int64_t overrun = static_cast<int64_t>(cpu_stats.overrun());
+    int64_t commit_overrun = static_cast<int64_t>(cpu_stats.commit_overrun());
+    int64_t bytes_read = static_cast<int64_t>(cpu_stats.bytes_read());
+    int64_t dropped_events = static_cast<int64_t>(cpu_stats.dropped_events());
+    int64_t read_events = static_cast<int64_t>(cpu_stats.read_events());
+    int64_t now_ts = static_cast<int64_t>(cpu_stats.now_ts() * 1e9);
+
     storage->SetIndexedStats(stats::ftrace_cpu_entries_begin + phase, cpu,
-                             static_cast<int64_t>(cpu_stats.entries()));
+                             entries);
     storage->SetIndexedStats(stats::ftrace_cpu_overrun_begin + phase, cpu,
-                             static_cast<int64_t>(cpu_stats.overrun()));
+                             overrun);
     storage->SetIndexedStats(stats::ftrace_cpu_commit_overrun_begin + phase,
-                             cpu,
-                             static_cast<int64_t>(cpu_stats.commit_overrun()));
+                             cpu, commit_overrun);
     storage->SetIndexedStats(stats::ftrace_cpu_bytes_read_begin + phase, cpu,
-                             static_cast<int64_t>(cpu_stats.bytes_read()));
+                             bytes_read);
+    storage->SetIndexedStats(stats::ftrace_cpu_dropped_events_begin + phase,
+                             cpu, dropped_events);
+    storage->SetIndexedStats(stats::ftrace_cpu_read_events_begin + phase, cpu,
+                             read_events);
+    storage->SetIndexedStats(stats::ftrace_cpu_now_ts_begin + phase, cpu,
+                             now_ts);
+
+    if (is_end) {
+      auto opt_entries_begin =
+          storage->GetIndexedStats(stats::ftrace_cpu_entries_begin, cpu);
+      if (opt_entries_begin) {
+        int64_t delta_entries = entries - opt_entries_begin.value();
+        storage->SetIndexedStats(stats::ftrace_cpu_entries_delta, cpu,
+                                 delta_entries);
+      }
+
+      auto opt_overrun_begin =
+          storage->GetIndexedStats(stats::ftrace_cpu_overrun_begin, cpu);
+      if (opt_overrun_begin) {
+        int64_t delta_overrun = overrun - opt_overrun_begin.value();
+        storage->SetIndexedStats(stats::ftrace_cpu_overrun_delta, cpu,
+                                 delta_overrun);
+      }
+
+      auto opt_commit_overrun_begin =
+          storage->GetIndexedStats(stats::ftrace_cpu_commit_overrun_begin, cpu);
+      if (opt_commit_overrun_begin) {
+        int64_t delta_commit_overrun =
+            commit_overrun - opt_commit_overrun_begin.value();
+        storage->SetIndexedStats(stats::ftrace_cpu_commit_overrun_delta, cpu,
+                                 delta_commit_overrun);
+      }
+
+      auto opt_bytes_read_begin =
+          storage->GetIndexedStats(stats::ftrace_cpu_bytes_read_begin, cpu);
+      if (opt_bytes_read_begin) {
+        int64_t delta_bytes_read = bytes_read - opt_bytes_read_begin.value();
+        storage->SetIndexedStats(stats::ftrace_cpu_bytes_read_delta, cpu,
+                                 delta_bytes_read);
+      }
+
+      auto opt_dropped_events_begin =
+          storage->GetIndexedStats(stats::ftrace_cpu_dropped_events_begin, cpu);
+      if (opt_dropped_events_begin) {
+        int64_t delta_dropped_events =
+            dropped_events - opt_dropped_events_begin.value();
+        storage->SetIndexedStats(stats::ftrace_cpu_dropped_events_delta, cpu,
+                                 delta_dropped_events);
+      }
+
+      auto opt_read_events_begin =
+          storage->GetIndexedStats(stats::ftrace_cpu_read_events_begin, cpu);
+      if (opt_read_events_begin) {
+        int64_t delta_read_events = read_events - opt_read_events_begin.value();
+        storage->SetIndexedStats(stats::ftrace_cpu_read_events_delta, cpu,
+                                 delta_read_events);
+      }
+    }
 
     // oldest_event_ts can often be set to very high values, possibly because
     // of wrapping. Ensure that we are not overflowing to avoid ubsan
@@ -266,14 +338,6 @@
       storage->SetIndexedStats(stats::ftrace_cpu_oldest_event_ts_begin + phase,
                                cpu, static_cast<int64_t>(oldest_event_ts));
     }
-
-    storage->SetIndexedStats(stats::ftrace_cpu_now_ts_begin + phase, cpu,
-                             static_cast<int64_t>(cpu_stats.now_ts() * 1e9));
-    storage->SetIndexedStats(stats::ftrace_cpu_dropped_events_begin + phase,
-                             cpu,
-                             static_cast<int64_t>(cpu_stats.dropped_events()));
-    storage->SetIndexedStats(stats::ftrace_cpu_read_events_begin + phase, cpu,
-                             static_cast<int64_t>(cpu_stats.read_events()));
   }
 }
 
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 6926cff..6c2536e 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -620,8 +620,9 @@
   auto metric_it = std::find_if(
       ctx->metrics->begin(), ctx->metrics->end(),
       [path](const SqlMetricFile& metric) { return metric.path == path; });
-  if (metric_it == ctx->metrics->end())
-    return base::ErrStatus("RUN_METRIC: Unknown filename provided");
+  if (metric_it == ctx->metrics->end()) {
+    return base::ErrStatus("RUN_METRIC: Unknown filename provided %s", path);
+  }
   const auto& sql = metric_it->sql;
 
   std::unordered_map<std::string, std::string> substitutions;
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index 064630b..7ddbf2a 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -71,6 +71,8 @@
   "android/gpu_counter_span_view.sql",
   "android/thread_counter_span_view.sql",
   "android/unsymbolized_frames.sql",
+  "android/startup/launches_maxsdk28.sql",
+  "android/startup/launches_minsdk29.sql",
   "android/startup/launches.sql",
   "android/startup/hsc.sql",
   "chrome/actual_power_by_category.sql",
diff --git a/src/trace_processor/metrics/sql/android/startup/launches.sql b/src/trace_processor/metrics/sql/android/startup/launches.sql
index c3be64c..492bac5 100644
--- a/src/trace_processor/metrics/sql/android/startup/launches.sql
+++ b/src/trace_processor/metrics/sql/android/startup/launches.sql
@@ -12,29 +12,6 @@
 -- 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.
---
-
--- Marks the beginning of the trace and is equivalent to when the statsd launch
--- logging begins.
-DROP VIEW IF EXISTS activity_intent_received;
-CREATE VIEW activity_intent_received AS
-SELECT ts FROM slice
-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.
-DROP TABLE IF EXISTS activity_intent_recv_spans;
-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;
 
 -- 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.
@@ -52,64 +29,66 @@
 WHERE s.name GLOB 'launching: *'
 AND (process.name IS NULL OR process.name = 'system_server');
 
--- Filter activity_intent_recv_spans, keeping only the ones that triggered
--- a launch.
-DROP VIEW IF EXISTS launch_partitions;
-CREATE VIEW launch_partitions AS
-SELECT * FROM activity_intent_recv_spans AS spans
-WHERE 1 = (
-  SELECT COUNT(1)
-  FROM launching_events
-  WHERE launching_events.ts BETWEEN spans.ts AND spans.ts + spans.dur);
+SELECT CREATE_FUNCTION(
+  'ANDROID_SDK_LEVEL()',
+  'INT', "
+    SELECT int_value
+    FROM metadata
+    WHERE name = 'android_sdk_version'
+  ");
 
--- Successful activity launch. The end of the 'launching' event is not related
--- to whether it actually succeeded or not.
-DROP VIEW IF EXISTS activity_intent_launch_successful;
-CREATE VIEW activity_intent_launch_successful AS
-SELECT ts FROM slice
-WHERE name = 'MetricsLogger:launchObserverNotifyActivityLaunchFinished';
-
--- All activity launches in the trace, keyed by ID.
-DROP TABLE IF EXISTS launches;
-CREATE TABLE launches(
-  ts BIG INT,
-  ts_end BIG INT,
-  dur BIG INT,
-  id INT,
-  package STRING);
-
--- Use the starting event package name. The finish event package name
--- is not reliable in the case of failed launches.
-INSERT INTO launches
-SELECT
-  lpart.ts AS ts,
-  launching_events.ts_end AS ts_end,
-  launching_events.ts_end - lpart.ts AS dur,
-  lpart.id AS id,
-  package_name AS package
-FROM launch_partitions AS lpart
-JOIN launching_events ON
-  (launching_events.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur) AND
-  (launching_events.ts_end BETWEEN lpart.ts AND lpart.ts + lpart.dur)
-WHERE (
-  SELECT COUNT(1)
-  FROM activity_intent_launch_successful AS successful
-  WHERE successful.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur
-) > 0;
+-- Note: on Q, we didn't have Android fingerprints but we *did*
+-- have ActivityMetricsLogger events so we will use this approach
+-- if we see any such events.
+SELECT CASE
+  WHEN (
+    ANDROID_SDK_LEVEL() >= 29
+    OR (
+      SELECT COUNT(1) FROM slice
+      WHERE name GLOB 'MetricsLogger:*'
+    ) > 0
+  )
+  THEN RUN_METRIC('android/startup/launches_minsdk29.sql')
+  ELSE RUN_METRIC('android/startup/launches_maxsdk28.sql')
+END;
 
 -- Maps a launch to the corresponding set of processes that handled the
 -- activity start. The vast majority of cases should be a single process.
 -- However it is possible that the process dies during the activity launch
 -- and is respawned.
 DROP TABLE IF EXISTS launch_processes;
-CREATE TABLE launch_processes(launch_id INT, upid BIG INT);
+CREATE TABLE launch_processes(launch_id INT, upid BIG INT, launch_type STRING);
 
-INSERT INTO launch_processes
-SELECT launches.id, process.upid
-FROM launches
-  LEFT JOIN package_list ON (launches.package = package_list.package_name)
-  JOIN process ON (launches.package = process.name OR process.uid = package_list.uid)
-  JOIN thread ON (process.upid = thread.upid AND process.pid = thread.tid)
-WHERE (process.start_ts IS NULL OR process.start_ts < launches.ts_end)
-AND (thread.end_ts IS NULL OR thread.end_ts > launches.ts_end)
-ORDER BY process.start_ts DESC;
+SELECT CREATE_FUNCTION(
+  'STARTUP_SLICE_COUNT(start_ts LONG, end_ts LONG, utid INT, name STRING)',
+  'INT',
+  '
+    SELECT COUNT(1)
+    FROM thread_track t
+    JOIN slice s ON s.track_id = t.id
+    WHERE
+      t.utid = $utid AND
+      s.ts >= $start_ts AND
+      s.ts < $end_ts AND
+      s.name = $name
+  '
+);
+
+INSERT INTO launch_processes(launch_id, upid, launch_type)
+SELECT
+  l.id AS launch_id,
+  p.upid,
+  CASE
+    WHEN STARTUP_SLICE_COUNT(l.ts, l.ts_end, t.utid, 'bindApplication') > 0
+      THEN 'cold'
+    WHEN STARTUP_SLICE_COUNT(l.ts, l.ts_end, t.utid, 'activityStart') > 0
+      THEN 'warm'
+    WHEN STARTUP_SLICE_COUNT(l.ts, l.ts_end, t.utid, 'activityResume') > 0
+      THEN 'hot'
+    ELSE NULL
+  END AS launch_type
+FROM launches l
+LEFT JOIN package_list ON (l.package = package_list.package_name)
+JOIN process p ON (l.package = p.name OR p.uid = package_list.uid)
+JOIN thread t ON (p.upid = t.upid AND t.is_main_thread)
+WHERE launch_type IS NOT NULL;
diff --git a/src/trace_processor/metrics/sql/android/startup/launches_maxsdk28.sql b/src/trace_processor/metrics/sql/android/startup/launches_maxsdk28.sql
new file mode 100644
index 0000000..8753da1
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/startup/launches_maxsdk28.sql
@@ -0,0 +1,36 @@
+--
+-- Copyright 2021 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.
+--
+
+-- All activity launches in the trace, keyed by ID.
+DROP TABLE IF EXISTS launches;
+CREATE TABLE launches(
+  id INTEGER PRIMARY KEY,
+  ts BIG INT,
+  ts_end BIG INT,
+  dur BIG INT,
+  package STRING
+);
+
+-- Cold/warm starts emitted launching slices on API level 28-.
+INSERT INTO launches(ts, ts_end, dur, package)
+SELECT
+  launching_events.ts AS ts,
+  launching_events.ts_end AS ts_end,
+  launching_events.ts_end - launching_events.ts AS dur,
+  package_name AS package
+FROM launching_events;
+
+-- TODO(lalitm): add handling of hot starts using frame timings.
diff --git a/src/trace_processor/metrics/sql/android/startup/launches_minsdk29.sql b/src/trace_processor/metrics/sql/android/startup/launches_minsdk29.sql
new file mode 100644
index 0000000..1f4c0f7
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/startup/launches_minsdk29.sql
@@ -0,0 +1,82 @@
+--
+-- Copyright 2021 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.
+--
+
+-- Marks the beginning of the trace and is equivalent to when the statsd launch
+-- logging begins.
+DROP VIEW IF EXISTS activity_intent_received;
+CREATE VIEW activity_intent_received AS
+SELECT ts FROM slice
+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.
+DROP TABLE IF EXISTS activity_intent_recv_spans;
+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.
+DROP VIEW IF EXISTS launch_partitions;
+CREATE VIEW launch_partitions AS
+SELECT * FROM activity_intent_recv_spans AS spans
+WHERE 1 = (
+  SELECT COUNT(1)
+  FROM launching_events
+  WHERE launching_events.ts BETWEEN spans.ts AND spans.ts + spans.dur);
+
+-- Successful activity launch. The end of the 'launching' event is not related
+-- to whether it actually succeeded or not.
+DROP VIEW IF EXISTS activity_intent_launch_successful;
+CREATE VIEW activity_intent_launch_successful AS
+SELECT ts FROM slice
+WHERE name = 'MetricsLogger:launchObserverNotifyActivityLaunchFinished';
+
+-- All activity launches in the trace, keyed by ID.
+DROP TABLE IF EXISTS launches;
+CREATE TABLE launches(
+  ts BIG INT,
+  ts_end BIG INT,
+  dur BIG INT,
+  id INT,
+  package STRING);
+
+-- Use the starting event package name. The finish event package name
+-- is not reliable in the case of failed launches.
+INSERT INTO launches
+SELECT
+  lpart.ts AS ts,
+  launching_events.ts_end AS ts_end,
+  launching_events.ts_end - lpart.ts AS dur,
+  lpart.id AS id,
+  package_name AS package
+FROM launch_partitions AS lpart
+JOIN launching_events ON
+  (launching_events.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur) AND
+  (launching_events.ts_end BETWEEN lpart.ts AND lpart.ts + lpart.dur)
+WHERE (
+  SELECT COUNT(1)
+  FROM activity_intent_launch_successful AS successful
+  WHERE successful.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur
+) > 0;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 7e537ca..b52a4c9 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -35,22 +35,28 @@
   F(ftrace_bundle_tokenizer_errors,     kSingle,  kError,    kAnalysis, ""),   \
   F(ftrace_cpu_bytes_read_begin,        kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_bytes_read_end,          kIndexed, kInfo,     kTrace,    ""),   \
-  F(ftrace_cpu_commit_overrun_begin,    kIndexed, kError,    kTrace,    ""),   \
-  F(ftrace_cpu_commit_overrun_end,      kIndexed, kError,    kTrace,    ""),   \
-  F(ftrace_cpu_dropped_events_begin,    kIndexed, kError,    kTrace,    ""),   \
-  F(ftrace_cpu_dropped_events_end,      kIndexed, kError,    kTrace,    ""),   \
+  F(ftrace_cpu_bytes_read_delta,        kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_commit_overrun_begin,    kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_commit_overrun_end,      kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_commit_overrun_delta,    kIndexed, kError,    kTrace,    ""),   \
+  F(ftrace_cpu_dropped_events_begin,    kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_dropped_events_end,      kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_dropped_events_delta,    kIndexed, kError,    kTrace,    ""),   \
   F(ftrace_cpu_entries_begin,           kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_entries_end,             kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_entries_delta,           kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_now_ts_begin,            kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_now_ts_end,              kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_oldest_event_ts_begin,   kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_oldest_event_ts_end,     kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_overrun_begin,           kIndexed, kInfo,     kTrace,    ""),   \
-  F(ftrace_cpu_overrun_end,             kIndexed, kDataLoss, kTrace,           \
+  F(ftrace_cpu_overrun_end,             kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_overrun_delta,           kIndexed, kDataLoss, kTrace,           \
       "The kernel ftrace buffer cannot keep up with the rate of events "       \
       "produced. Indexed by CPU. This is likely a misconfiguration."),         \
   F(ftrace_cpu_read_events_begin,       kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_read_events_end,         kIndexed, kInfo,     kTrace,    ""),   \
+  F(ftrace_cpu_read_events_delta,       kIndexed, kInfo,     kTrace,    ""),   \
   F(fuchsia_non_numeric_counters,       kSingle,  kError,    kAnalysis, ""),   \
   F(fuchsia_timestamp_overflow,         kSingle,  kError,    kAnalysis, ""),   \
   F(fuchsia_invalid_event,              kSingle,  kError,    kAnalysis, ""),   \
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index f092c74..af98024 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -256,6 +256,17 @@
     stats_[key].indexed_values[index] = value;
   }
 
+  // Example usage: opt_cpu_failure = GetIndexedStats(stats::cpu_failure, 1);
+  base::Optional<int64_t> GetIndexedStats(size_t key, int index) {
+    PERFETTO_DCHECK(key < stats::kNumKeys);
+    PERFETTO_DCHECK(stats::kTypes[key] == stats::kIndexed);
+    auto kv = stats_[key].indexed_values.find(index);
+    if (kv != stats_[key].indexed_values.end()) {
+      return kv->second;
+    }
+    return base::nullopt;
+  }
+
   class ScopedStatsTracer {
    public:
     ScopedStatsTracer(TraceStorage* storage, size_t key)
diff --git a/src/trace_processor/timestamped_trace_piece.h b/src/trace_processor/timestamped_trace_piece.h
index ca61546..1b1a0f6 100644
--- a/src/trace_processor/timestamped_trace_piece.h
+++ b/src/trace_processor/timestamped_trace_piece.h
@@ -77,9 +77,19 @@
   std::array<double, kMaxNumExtraCounters> extra_counter_values = {};
 };
 
+// On Windows std::aligned_storage was broken before VS 2017 15.8 and the
+// compiler (even clang-cl) requires -D_ENABLE_EXTENDED_ALIGNED_STORAGE. Given
+// the alignment here is purely a performance enhancment with no other
+// functional requirement, disable it on Win.
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#define PERFETTO_TTS_ALIGNMENT alignas(64)
+#else
+#define PERFETTO_TTS_ALIGNMENT
+#endif
+
 // A TimestampedTracePiece is (usually a reference to) a piece of a trace that
 // is sorted by TraceSorter.
-struct alignas(64) TimestampedTracePiece {
+struct PERFETTO_TTS_ALIGNMENT TimestampedTracePiece {
   enum class Type {
     kInvalid = 0,
     kFtraceEvent,
diff --git a/src/tracing/test/api_test_support.cc b/src/tracing/test/api_test_support.cc
index 0142d28..a03135d 100644
--- a/src/tracing/test/api_test_support.cc
+++ b/src/tracing/test/api_test_support.cc
@@ -27,6 +27,10 @@
 #include "test/test_helper.h"
 #endif
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#endif
+
 namespace perfetto {
 namespace test {
 
diff --git a/test/trace_processor/parsing/android_sched_and_ps_stats.out b/test/trace_processor/parsing/android_sched_and_ps_stats.out
index 4ff60f7..fcc2279 100644
--- a/test/trace_processor/parsing/android_sched_and_ps_stats.out
+++ b/test/trace_processor/parsing/android_sched_and_ps_stats.out
@@ -15,38 +15,62 @@
 "ftrace_cpu_bytes_read_end",5,"info","trace",1188
 "ftrace_cpu_bytes_read_end",6,"info","trace",1576
 "ftrace_cpu_bytes_read_end",7,"info","trace",3264
-"ftrace_cpu_commit_overrun_begin",0,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",1,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",2,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",3,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",4,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",5,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",6,"error","trace",0
-"ftrace_cpu_commit_overrun_begin",7,"error","trace",0
-"ftrace_cpu_commit_overrun_end",0,"error","trace",0
-"ftrace_cpu_commit_overrun_end",1,"error","trace",0
-"ftrace_cpu_commit_overrun_end",2,"error","trace",0
-"ftrace_cpu_commit_overrun_end",3,"error","trace",0
-"ftrace_cpu_commit_overrun_end",4,"error","trace",0
-"ftrace_cpu_commit_overrun_end",5,"error","trace",0
-"ftrace_cpu_commit_overrun_end",6,"error","trace",0
-"ftrace_cpu_commit_overrun_end",7,"error","trace",0
-"ftrace_cpu_dropped_events_begin",0,"error","trace",0
-"ftrace_cpu_dropped_events_begin",1,"error","trace",0
-"ftrace_cpu_dropped_events_begin",2,"error","trace",0
-"ftrace_cpu_dropped_events_begin",3,"error","trace",0
-"ftrace_cpu_dropped_events_begin",4,"error","trace",0
-"ftrace_cpu_dropped_events_begin",5,"error","trace",0
-"ftrace_cpu_dropped_events_begin",6,"error","trace",0
-"ftrace_cpu_dropped_events_begin",7,"error","trace",0
-"ftrace_cpu_dropped_events_end",0,"error","trace",0
-"ftrace_cpu_dropped_events_end",1,"error","trace",0
-"ftrace_cpu_dropped_events_end",2,"error","trace",0
-"ftrace_cpu_dropped_events_end",3,"error","trace",0
-"ftrace_cpu_dropped_events_end",4,"error","trace",0
-"ftrace_cpu_dropped_events_end",5,"error","trace",0
-"ftrace_cpu_dropped_events_end",6,"error","trace",0
-"ftrace_cpu_dropped_events_end",7,"error","trace",0
+"ftrace_cpu_bytes_read_delta",0,"info","trace",20236
+"ftrace_cpu_bytes_read_delta",1,"info","trace",11380
+"ftrace_cpu_bytes_read_delta",2,"info","trace",12336
+"ftrace_cpu_bytes_read_delta",3,"info","trace",6236
+"ftrace_cpu_bytes_read_delta",4,"info","trace",2676
+"ftrace_cpu_bytes_read_delta",5,"info","trace",1188
+"ftrace_cpu_bytes_read_delta",6,"info","trace",1576
+"ftrace_cpu_bytes_read_delta",7,"info","trace",3264
+"ftrace_cpu_commit_overrun_begin",0,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",1,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",2,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",3,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",4,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",5,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",6,"info","trace",0
+"ftrace_cpu_commit_overrun_begin",7,"info","trace",0
+"ftrace_cpu_commit_overrun_end",0,"info","trace",0
+"ftrace_cpu_commit_overrun_end",1,"info","trace",0
+"ftrace_cpu_commit_overrun_end",2,"info","trace",0
+"ftrace_cpu_commit_overrun_end",3,"info","trace",0
+"ftrace_cpu_commit_overrun_end",4,"info","trace",0
+"ftrace_cpu_commit_overrun_end",5,"info","trace",0
+"ftrace_cpu_commit_overrun_end",6,"info","trace",0
+"ftrace_cpu_commit_overrun_end",7,"info","trace",0
+"ftrace_cpu_commit_overrun_delta",0,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",1,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",2,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",3,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",4,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",5,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",6,"error","trace",0
+"ftrace_cpu_commit_overrun_delta",7,"error","trace",0
+"ftrace_cpu_dropped_events_begin",0,"info","trace",0
+"ftrace_cpu_dropped_events_begin",1,"info","trace",0
+"ftrace_cpu_dropped_events_begin",2,"info","trace",0
+"ftrace_cpu_dropped_events_begin",3,"info","trace",0
+"ftrace_cpu_dropped_events_begin",4,"info","trace",0
+"ftrace_cpu_dropped_events_begin",5,"info","trace",0
+"ftrace_cpu_dropped_events_begin",6,"info","trace",0
+"ftrace_cpu_dropped_events_begin",7,"info","trace",0
+"ftrace_cpu_dropped_events_end",0,"info","trace",0
+"ftrace_cpu_dropped_events_end",1,"info","trace",0
+"ftrace_cpu_dropped_events_end",2,"info","trace",0
+"ftrace_cpu_dropped_events_end",3,"info","trace",0
+"ftrace_cpu_dropped_events_end",4,"info","trace",0
+"ftrace_cpu_dropped_events_end",5,"info","trace",0
+"ftrace_cpu_dropped_events_end",6,"info","trace",0
+"ftrace_cpu_dropped_events_end",7,"info","trace",0
+"ftrace_cpu_dropped_events_delta",0,"error","trace",0
+"ftrace_cpu_dropped_events_delta",1,"error","trace",0
+"ftrace_cpu_dropped_events_delta",2,"error","trace",0
+"ftrace_cpu_dropped_events_delta",3,"error","trace",0
+"ftrace_cpu_dropped_events_delta",4,"error","trace",0
+"ftrace_cpu_dropped_events_delta",5,"error","trace",0
+"ftrace_cpu_dropped_events_delta",6,"error","trace",0
+"ftrace_cpu_dropped_events_delta",7,"error","trace",0
 "ftrace_cpu_entries_begin",0,"info","trace",4
 "ftrace_cpu_entries_begin",1,"info","trace",4
 "ftrace_cpu_entries_begin",2,"info","trace",23
@@ -63,6 +87,14 @@
 "ftrace_cpu_entries_end",5,"info","trace",21
 "ftrace_cpu_entries_end",6,"info","trace",26
 "ftrace_cpu_entries_end",7,"info","trace",54
+"ftrace_cpu_entries_delta",0,"info","trace",337
+"ftrace_cpu_entries_delta",1,"info","trace",191
+"ftrace_cpu_entries_delta",2,"info","trace",202
+"ftrace_cpu_entries_delta",3,"info","trace",107
+"ftrace_cpu_entries_delta",4,"info","trace",45
+"ftrace_cpu_entries_delta",5,"info","trace",21
+"ftrace_cpu_entries_delta",6,"info","trace",26
+"ftrace_cpu_entries_delta",7,"info","trace",54
 "ftrace_cpu_now_ts_begin",0,"info","trace",81473010735000
 "ftrace_cpu_now_ts_begin",1,"info","trace",81473010800000
 "ftrace_cpu_now_ts_begin",2,"info","trace",81473010839000
@@ -103,14 +135,22 @@
 "ftrace_cpu_overrun_begin",5,"info","trace",0
 "ftrace_cpu_overrun_begin",6,"info","trace",0
 "ftrace_cpu_overrun_begin",7,"info","trace",0
-"ftrace_cpu_overrun_end",0,"data_loss","trace",0
-"ftrace_cpu_overrun_end",1,"data_loss","trace",0
-"ftrace_cpu_overrun_end",2,"data_loss","trace",0
-"ftrace_cpu_overrun_end",3,"data_loss","trace",0
-"ftrace_cpu_overrun_end",4,"data_loss","trace",0
-"ftrace_cpu_overrun_end",5,"data_loss","trace",0
-"ftrace_cpu_overrun_end",6,"data_loss","trace",0
-"ftrace_cpu_overrun_end",7,"data_loss","trace",0
+"ftrace_cpu_overrun_end",0,"info","trace",0
+"ftrace_cpu_overrun_end",1,"info","trace",0
+"ftrace_cpu_overrun_end",2,"info","trace",0
+"ftrace_cpu_overrun_end",3,"info","trace",0
+"ftrace_cpu_overrun_end",4,"info","trace",0
+"ftrace_cpu_overrun_end",5,"info","trace",0
+"ftrace_cpu_overrun_end",6,"info","trace",0
+"ftrace_cpu_overrun_end",7,"info","trace",0
+"ftrace_cpu_overrun_delta",0,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",1,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",2,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",3,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",4,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",5,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",6,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",7,"data_loss","trace",0
 "ftrace_cpu_read_events_begin",0,"info","trace",0
 "ftrace_cpu_read_events_begin",1,"info","trace",0
 "ftrace_cpu_read_events_begin",2,"info","trace",0
@@ -127,6 +167,14 @@
 "ftrace_cpu_read_events_end",5,"info","trace",23232
 "ftrace_cpu_read_events_end",6,"info","trace",36733
 "ftrace_cpu_read_events_end",7,"info","trace",39240
+"ftrace_cpu_read_events_delta",0,"info","trace",62303
+"ftrace_cpu_read_events_delta",1,"info","trace",54916
+"ftrace_cpu_read_events_delta",2,"info","trace",55882
+"ftrace_cpu_read_events_delta",3,"info","trace",47953
+"ftrace_cpu_read_events_delta",4,"info","trace",31345
+"ftrace_cpu_read_events_delta",5,"info","trace",23232
+"ftrace_cpu_read_events_delta",6,"info","trace",36733
+"ftrace_cpu_read_events_delta",7,"info","trace",39240
 "traced_buf_buffer_size",0,"info","trace",0
 "traced_buf_bytes_overwritten",0,"info","trace",0
 "traced_buf_bytes_read",0,"info","trace",0
diff --git a/test/trace_processor/startup/android_startup.out b/test/trace_processor/startup/android_startup.out
index a7dd383..5d323e4 100644
--- a/test/trace_processor/startup/android_startup.out
+++ b/test/trace_processor/startup/android_startup.out
@@ -18,12 +18,12 @@
       }
     }
     zygote_new_process: false
-    activity_hosting_process_count: 2
+    activity_hosting_process_count: 1
     to_first_frame {
       dur_ns: 108
       main_thread_by_task_state {
-        running_dur_ns: 41
-        runnable_dur_ns: 129
+        running_dur_ns: 10
+        runnable_dur_ns: 80
         uninterruptible_sleep_dur_ns: 0
         interruptible_sleep_dur_ns: 10
       }
@@ -34,6 +34,10 @@
       }
       mcycles_by_core_type {
       }
+      time_activity_start {
+        dur_ns: 2
+        dur_ms: 2e-06
+      }
       dur_ms: 0.000108
     }
     report_fully_drawn {
diff --git a/test/trace_processor/startup/android_startup.py b/test/trace_processor/startup/android_startup.py
index 494849c..c5f0e37 100644
--- a/test/trace_processor/startup/android_startup.py
+++ b/test/trace_processor/startup/android_startup.py
@@ -22,7 +22,7 @@
 trace.add_process(1, 0, 'init')
 trace.add_process(2, 1, 'system_server')
 trace.add_process(3, 1, 'com.google.android.calendar', 10001)
-trace.add_process(4, 1, 'com.google.android.calendar')
+trace.add_process(4, 3, 'com.google.android.calendar', 10001)
 
 trace.add_package_list(
     ts=1, name='com.google.android.calendar', uid=10001, version_code=123)
@@ -42,6 +42,13 @@
     ts=110, tid=2, pid=2, buf='launching: com.google.android.calendar')
 
 trace.add_sched(ts=110, prev_pid=0, next_pid=3)
+
+# As the process already existed before intent started, this is a
+# warm/hot start (we choose warm). Therefore, emit an activityStart
+# slice.
+trace.add_atrace_begin(ts=115, tid=3, pid=3, buf='activityStart')
+trace.add_atrace_end(ts=117, tid=3, pid=3)
+
 # P1: 10ns running
 trace.add_sched(ts=120, prev_pid=3, next_pid=0, prev_state='S')
 # P1: 10ns sleep
diff --git a/test/trace_processor/startup/android_startup_attribution.out b/test/trace_processor/startup/android_startup_attribution.out
index 87b1a30..3c74145 100644
--- a/test/trace_processor/startup/android_startup_attribution.out
+++ b/test/trace_processor/startup/android_startup_attribution.out
@@ -17,6 +17,10 @@
         dur_ns: 2
         dur_ms: 2e-06
       }
+      time_activity_resume {
+        dur_ns: 5
+        dur_ms: 5e-06
+      }
       dur_ms: 999.9999
       time_dex_open {
         dur_ns: 20
diff --git a/test/trace_processor/startup/android_startup_attribution.py b/test/trace_processor/startup/android_startup_attribution.py
index cf3ab8e..2f8a4be 100644
--- a/test/trace_processor/startup/android_startup_attribution.py
+++ b/test/trace_processor/startup/android_startup_attribution.py
@@ -18,7 +18,7 @@
 import synth_common
 
 APP_PID = 3
-APP_TID = 1
+APP_TID = APP_PID
 SECOND_APP_TID = 3
 JIT_TID = 4
 GC_TID = 5
@@ -65,6 +65,10 @@
     tid=SYSTEM_SERVER_TID,
     buf='launching: com.some.app')
 
+# Emulate a hot start (and therefore that we only see activityResume).
+trace.add_atrace_begin(ts=125, tid=APP_TID, pid=APP_PID, buf='activityResume')
+trace.add_atrace_end(ts=130, tid=APP_TID, pid=APP_PID)
+
 # OpenDex slices within the startup.
 trace.add_atrace_begin(
     ts=150, pid=APP_PID, tid=APP_TID, buf='OpenDexFilesFromOat(something)')
diff --git a/test/trace_processor/startup/android_startup_process_track.out b/test/trace_processor/startup/android_startup_process_track.out
index 357603d..7a8ae0f 100644
--- a/test/trace_processor/startup/android_startup_process_track.out
+++ b/test/trace_processor/startup/android_startup_process_track.out
@@ -8,7 +8,7 @@
     }
     zygote_new_process: false
     to_first_frame {
-      dur_ns: 4
+      dur_ns: 5
       main_thread_by_task_state {
         running_dur_ns: 0
         runnable_dur_ns: 0
@@ -22,11 +22,19 @@
       }
       mcycles_by_core_type {
       }
-      dur_ms: 4e-06
+      time_bind_application {
+        dur_ns: 1
+        dur_ms: 1e-06
+      }
+      dur_ms: 5e-06
+      to_bind_application {
+        dur_ns: 3
+        dur_ms: 3e-06
+      }
     }
     event_timestamps {
       intent_received: 100
-      first_frame: 104
+      first_frame: 105
     }
     activity_hosting_process_count: 1
   }
@@ -39,7 +47,7 @@
     }
     zygote_new_process: false
     to_first_frame {
-      dur_ns: 4
+      dur_ns: 5
       main_thread_by_task_state {
         running_dur_ns: 0
         runnable_dur_ns: 0
@@ -53,11 +61,19 @@
       }
       mcycles_by_core_type {
       }
-      dur_ms: 4e-06
+      time_bind_application {
+        dur_ns: 1
+        dur_ms: 1e-06
+      }
+      dur_ms: 5e-06
+      to_bind_application {
+        dur_ns: 3
+        dur_ms: 3e-06
+      }
     }
     event_timestamps {
       intent_received: 200
-      first_frame: 204
+      first_frame: 205
     }
     activity_hosting_process_count: 1
   }
diff --git a/test/trace_processor/startup/android_startup_process_track.py b/test/trace_processor/startup/android_startup_process_track.py
index a9696ad..07fcf92 100644
--- a/test/trace_processor/startup/android_startup_process_track.py
+++ b/test/trace_processor/startup/android_startup_process_track.py
@@ -34,10 +34,12 @@
       new_tid=pid,
       new_comm='com.google.android.calendar',
       flags=0)
+  trace.add_atrace_begin(ts=ts + 3, tid=pid, pid=pid, buf='bindApplication')
+  trace.add_atrace_end(ts=ts + 4, tid=pid, pid=pid)
   trace.add_atrace_async_end(
-      ts=ts + 4, tid=2, pid=2, buf='launching: com.google.android.calendar')
+      ts=ts + 5, tid=2, pid=2, buf='launching: com.google.android.calendar')
   trace.add_atrace_begin(
-      ts=ts + 5,
+      ts=ts + 6,
       tid=2,
       pid=2,
       buf='MetricsLogger:launchObserverNotifyActivityLaunchFinished')
diff --git a/tools/gen_amalgamated_sql_metrics.py b/tools/gen_amalgamated_sql_metrics.py
index eed5d8e..16f6aac 100755
--- a/tools/gen_amalgamated_sql_metrics.py
+++ b/tools/gen_amalgamated_sql_metrics.py
@@ -85,7 +85,7 @@
     with open(file_name, 'r') as f:
       relpath = os.path.relpath(file_name, root_path)
       sql_outputs[relpath] = "".join(
-          x for x in f.readlines() if not x.startswith('--'))
+          x.lstrip() for x in f.readlines() if not x.lstrip().startswith('--'))
 
   with open(args.cpp_out, 'w+') as output:
     output.write(REPLACEMENT_HEADER)
diff --git a/tools/test_data b/tools/test_data
index cf8ae14..7dd8975 100755
--- a/tools/test_data
+++ b/tools/test_data
@@ -141,7 +141,7 @@
     subprocess.check_call(cmd)
     with open(fs.path + SUFFIX + '.swp', 'w') as f:
       f.write(fs.actual_digest)
-    os.rename(fs.path + SUFFIX + '.swp', fs.path + SUFFIX)
+    os.replace(fs.path + SUFFIX + '.swp', fs.path + SUFFIX)
     return fs
 
   map_concurrently(upload_one_file, files_to_upload)
@@ -194,7 +194,7 @@
     if digest != fs.expected_digest:
       raise Exception('Mismatching digest for %s. expected=%s, actual=%s' %
                       (uri, fs.expected_digest, digest))
-    os.rename(tmp_path, fs.path)
+    os.replace(tmp_path, fs.path)
     return fs
 
   map_concurrently(download_one_file, files_to_download)