Merge "perfetto-ui: Make sleeping slices less visually distracting"
diff --git a/Android.bp b/Android.bp
index 5230e56..d3b7f3f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -114,6 +114,7 @@
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
@@ -184,6 +185,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
   ],
   defaults: [
@@ -313,6 +315,7 @@
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
@@ -396,6 +399,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
   ],
   defaults: [
@@ -499,6 +503,7 @@
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
@@ -562,6 +567,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
   ],
   export_generated_headers: [
@@ -604,6 +610,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
   ],
   defaults: [
@@ -673,6 +680,7 @@
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
@@ -736,6 +744,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
   ],
@@ -8154,6 +8163,7 @@
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
@@ -8227,6 +8237,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
   ],
   defaults: [
@@ -8315,6 +8326,7 @@
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
@@ -8373,6 +8385,7 @@
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
   ],
diff --git a/BUILD b/BUILD
index 9696008..e32422d 100644
--- a/BUILD
+++ b/BUILD
@@ -24,6 +24,7 @@
     "perfetto_cc_protocpp_library",
     "perfetto_cc_protozero_library",
     "perfetto_java_proto_library",
+    "perfetto_java_lite_proto_library",
     "perfetto_proto_library",
     "perfetto_py_binary",
     "perfetto_gensignature_internal_only",
@@ -154,6 +155,7 @@
         ":protos_perfetto_trace_ps_zero",
         ":protos_perfetto_trace_sys_stats_zero",
         ":protos_perfetto_trace_system_info_zero",
+        ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
     ],
 )
@@ -250,6 +252,7 @@
         ":protos_perfetto_trace_ps_zero",
         ":protos_perfetto_trace_sys_stats_zero",
         ":protos_perfetto_trace_system_info_zero",
+        ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
     ],
     linkstatic = True,
@@ -2591,6 +2594,14 @@
     ],
 )
 
+# GN target: //protos/perfetto/trace/track_event:cpp
+perfetto_cc_protocpp_library(
+    name = "protos_perfetto_trace_track_event_cpp",
+    deps = [
+        ":protos_perfetto_trace_track_event_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/trace/track_event:lite
 perfetto_cc_proto_library(
     name = "protos_perfetto_trace_track_event_lite",
@@ -2753,6 +2764,7 @@
         ":protos_perfetto_trace_ps_zero",
         ":protos_perfetto_trace_sys_stats_zero",
         ":protos_perfetto_trace_system_info_zero",
+        ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
     ],
     linkstatic = True,
@@ -2832,6 +2844,7 @@
         ":protos_perfetto_trace_ps_zero",
         ":protos_perfetto_trace_sys_stats_zero",
         ":protos_perfetto_trace_system_info_zero",
+        ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
         ":src_perfetto_cmd_protos",
     ] + PERFETTO_CONFIG.deps.zlib,
@@ -3220,6 +3233,13 @@
     ],
 )
 
+perfetto_java_lite_proto_library(
+    name = "protos_perfetto_config_merged_config_java_lite",
+    deps = [
+        ":protos_perfetto_config_merged_config_protos",
+    ],
+)
+
 perfetto_gensignature_internal_only(
     name = "trace_processor_sig",
     srcs = [
diff --git a/BUILD.extras b/BUILD.extras
index f9a85d3..6365f5c 100644
--- a/BUILD.extras
+++ b/BUILD.extras
@@ -50,6 +50,13 @@
     ],
 )
 
+perfetto_java_lite_proto_library(
+    name = "protos_perfetto_config_merged_config_java_lite",
+    deps = [
+        ":protos_perfetto_config_merged_config_protos",
+    ],
+)
+
 perfetto_gensignature_internal_only(
     name = "trace_processor_sig",
     srcs = [
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 498f34a..65ff4b0 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -64,6 +64,10 @@
     if not _rule_override("java_proto_library", **kwargs):
         native.java_proto_library(**kwargs)
 
+def perfetto_java_lite_proto_library(**kwargs):
+    if not _rule_override("java_lite_proto_library", **kwargs):
+        native.java_lite_proto_library(**kwargs)
+
 # +----------------------------------------------------------------------------+
 # | Misc rules.                                                                |
 # +----------------------------------------------------------------------------+
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index d441c06..68a0ead 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -77,9 +77,12 @@
         cc_binary = None,
         cc_library = None,
         cc_proto_library = None,
+
         # Supporting java rules pulls in the JDK and generally is not something
         # we need for most embedders.
         java_proto_library = _noop_override,
+        java_lite_proto_library = _noop_override,
+
         proto_library = None,
         py_binary = None,
 
diff --git a/docs/app-instrumentation.md b/docs/app-instrumentation.md
index 697ee49..922f366 100644
--- a/docs/app-instrumentation.md
+++ b/docs/app-instrumentation.md
@@ -499,10 +499,18 @@
 Tracks can also optionally be annotated with metadata:
 
 ```C++
+auto desc = track.Serialize();
+desc.set_name("MyTrack");
+perfetto::TrackEvent::SetTrackDescriptor(track, desc);
+```
+
+Threads and processes can also be named in a similar way, e.g.:
+
+```C++
+auto desc = perfetto::ProcessTrack::Current().Serialize();
+desc.mutable_process()->set_process_name("MyProcess");
 perfetto::TrackEvent::SetTrackDescriptor(
-    track, [](perfetto::protos::pbzero::TrackDescriptor* desc) {
-  desc->set_name("MyTrack");
-});
+    perfetto::ProcessTrack::Current(), desc);
 ```
 
 The metadata remains valid between tracing sessions. To free up data for a
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 2fd0810..401833d 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -18,6 +18,7 @@
     "../../../protos/perfetto/config/track_event:cpp",
     "../../../protos/perfetto/trace:zero",
     "../../../protos/perfetto/trace/interned_data:zero",
+    "../../../protos/perfetto/trace/track_event:cpp",
     "../../../protos/perfetto/trace/track_event:zero",
     "../base",
     "../protozero",
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 1bdffec..f737054 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -406,18 +406,31 @@
   }
 
   // Record metadata about different types of timeline tracks. See Track.
+  static void SetTrackDescriptor(const Track& track,
+                                 const protos::gen::TrackDescriptor& desc) {
+    PERFETTO_DCHECK(track.uuid == desc.uuid());
+    TrackRegistry::Get()->UpdateTrack(track, desc.SerializeAsString());
+    Base::template Trace([&](typename Base::TraceContext ctx) {
+      TrackEventInternal::WriteTrackDescriptor(
+          track, ctx.tls_inst_->trace_writer.get());
+    });
+  }
+
+  // DEPRECATED. Only kept for backwards compatibility.
   static void SetTrackDescriptor(
       const Track& track,
       std::function<void(protos::pbzero::TrackDescriptor*)> callback) {
     SetTrackDescriptorImpl(track, std::move(callback));
   }
 
+  // DEPRECATED. Only kept for backwards compatibility.
   static void SetProcessDescriptor(
       std::function<void(protos::pbzero::TrackDescriptor*)> callback,
       const ProcessTrack& track = ProcessTrack::Current()) {
     SetTrackDescriptorImpl(std::move(track), std::move(callback));
   }
 
+  // DEPRECATED. Only kept for backwards compatibility.
   static void SetThreadDescriptor(
       std::function<void(protos::pbzero::TrackDescriptor*)> callback,
       const ThreadTrack& track = ThreadTrack::Current()) {
@@ -529,8 +542,7 @@
   static void SetTrackDescriptorImpl(
       const TrackType& track,
       std::function<void(protos::pbzero::TrackDescriptor*)> callback) {
-    TrackRegistry::Get()->UpdateTrack(
-        track, [&](protos::pbzero::TrackDescriptor* desc) { callback(desc); });
+    TrackRegistry::Get()->UpdateTrack(track, std::move(callback));
     Base::template Trace([&](typename Base::TraceContext ctx) {
       TrackEventInternal::WriteTrackDescriptor(
           track, ctx.tls_inst_->trace_writer.get());
diff --git a/include/perfetto/tracing/track.h b/include/perfetto/tracing/track.h
index f297b08..0a4697b 100644
--- a/include/perfetto/tracing/track.h
+++ b/include/perfetto/tracing/track.h
@@ -23,6 +23,7 @@
 #include "perfetto/protozero/message_handle.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.gen.h"
 #include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 
 #include <stdint.h>
@@ -56,10 +57,16 @@
 //
 // Tracks can also be annotated with metadata:
 //
+//   auto desc = track.Serialize();
+//   desc.set_name("MyTrack");
+//   perfetto::TrackEvent::SetTrackDescriptor(track, desc);
+//
+// Threads and processes can also be named in a similar way, e.g.:
+//
+//   auto desc = perfetto::ProcessTrack::Current().Serialize();
+//   desc.mutable_process()->set_process_name("MyProcess");
 //   perfetto::TrackEvent::SetTrackDescriptor(
-//       track, [](perfetto::protos::pbzero::TrackDescriptor* desc) {
-//         desc->set_name("MyTrack");
-//       });
+//       perfetto::ProcessTrack::Current(), desc);
 //
 // The metadata remains valid between tracing sessions. To free up data for a
 // track, call EraseTrackDescriptor:
@@ -83,6 +90,7 @@
 
   explicit operator bool() const { return uuid; }
   void Serialize(protos::pbzero::TrackDescriptor*) const;
+  protos::gen::TrackDescriptor Serialize() const;
 
   // Construct a global track with identifier |id|.
   //
@@ -114,6 +122,7 @@
   static ProcessTrack Current() { return ProcessTrack(); }
 
   void Serialize(protos::pbzero::TrackDescriptor*) const;
+  protos::gen::TrackDescriptor Serialize() const;
 
  private:
   ProcessTrack() : Track(MakeProcessTrack()), pid(base::GetProcessId()) {}
@@ -133,6 +142,7 @@
   }
 
   void Serialize(protos::pbzero::TrackDescriptor*) const;
+  protos::gen::TrackDescriptor Serialize() const;
 
  private:
   explicit ThreadTrack(base::PlatformThreadId tid_)
@@ -177,6 +187,9 @@
     });
   }
 
+  // This variant lets the user supply a serialized track descriptor directly.
+  void UpdateTrack(Track, const std::string& serialized_desc);
+
   // If |track| exists in the registry, write out the serialized track
   // descriptor for it into |packet|. Otherwise just the ephemeral track object
   // is serialized without any additional metadata.
diff --git a/src/trace_processor/metrics/android/android_ion.sql b/src/trace_processor/metrics/android/android_ion.sql
index 4372f50..98a6fc9 100644
--- a/src/trace_processor/metrics/android/android_ion.sql
+++ b/src/trace_processor/metrics/android/android_ion.sql
@@ -55,13 +55,18 @@
 FROM ion_raw_allocs
 GROUP BY 1;
 
+-- We need to group by ts here as we can have two ion events from
+-- different processes occurring at the same timestamp. We take the
+-- max as this will take both allocations into account at that
+-- timestamp.
 CREATE VIEW IF NOT EXISTS android_ion_annotations AS
 SELECT
   'counter' AS track_type,
   printf('ION allocations (heap: %s)', heap_name) AS track_name,
   ts,
-  value
-FROM ion_raw_allocs;
+  MAX(value) AS value
+FROM ion_raw_allocs
+GROUP BY 1, 2, 3;
 
 CREATE VIEW IF NOT EXISTS android_ion_output AS
 SELECT AndroidIonMetric(
diff --git a/src/trace_processor/metrics/android/hsc_startups.sql b/src/trace_processor/metrics/android/hsc_startups.sql
index 7df0663..15e62b8 100644
--- a/src/trace_processor/metrics/android/hsc_startups.sql
+++ b/src/trace_processor/metrics/android/hsc_startups.sql
@@ -19,12 +19,14 @@
 SELECT
     slices.ts AS ts,
     slices.ts + slices.dur AS ts_end,
-    thread.name AS name,
-    ROW_NUMBER() OVER(PARTITION BY thread.name ORDER BY ts ASC) as frame_number
+    launches.package AS name,
+    launches.id AS launch_id,
+    ROW_NUMBER() OVER(PARTITION BY launches.id ORDER BY slices.ts ASC) as frame_number
 FROM slices
 INNER JOIN thread_track on slices.track_id = thread_track.id
 INNER JOIN thread USING(utid)
-WHERE slices.name="Choreographer#doFrame";
+INNER JOIN launches on launches.package LIKE '%' || thread.name || '%'
+WHERE slices.name="Choreographer#doFrame" and slices.ts > launches.ts;
 
 CREATE VIEW functions AS
 SELECT
@@ -46,7 +48,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%roid.calcul%";
+WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%roid.calcul%" AND frame_times.launch_id = launches.id;
 
 -- Calendar
 INSERT INTO hsc_based_startup_times
@@ -56,7 +58,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts_end > (SELECT ts + dur FROM functions WHERE function_name LIKE "animator:growScale" AND process_name LIKE "%id.calendar" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.calendar%"
+WHERE frame_times.ts_end > (SELECT ts + dur FROM functions WHERE function_name LIKE "animator:growScale" AND process_name LIKE "%id.calendar" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.calendar%" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Camera
@@ -67,7 +69,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%id.GoogleCamera%";LECT ts + dur FROM functions WHERE function_name="ShutterButtonEnabled" AND process_name LIKE "%id.GoogleCamera%" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%id.GoogleCamera%"
+WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="ShutterButtonEnabled" AND process_name LIKE "%id.GoogleCamera%" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%id.GoogleCamera%" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Chrome
@@ -78,7 +80,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%chrome%";
+WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%chrome%" AND frame_times.launch_id = launches.id;
 
 -- Clock
 INSERT INTO hsc_based_startup_times
@@ -88,7 +90,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%id.deskclock" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.deskclock"
+WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%id.deskclock" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.deskclock" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Contacts
@@ -99,7 +101,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:elevation" AND process_name LIKE "%id.contacts" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.contacts"
+WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:elevation" AND process_name LIKE "%id.contacts" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.contacts" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Dialer
@@ -110,7 +112,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%id.dialer";
+WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%id.dialer" AND frame_times.launch_id = launches.id;
 
 -- Gmail
 INSERT INTO hsc_based_startup_times
@@ -120,19 +122,17 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:elevation" AND process_name LIKE "%android.gm" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%android.gm"
+WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:elevation" AND process_name LIKE "%android.gm" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%android.gm" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Instagram
 INSERT INTO hsc_based_startup_times
 SELECT
-    launches.package as package,
-    launches.id as id,
-    frame_times.ts_end - launches.ts as ts_total
-FROM frame_times
-INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts_end > (SELECT start_ts FROM process WHERE name LIKE "%mqtt%" ORDER BY start_ts LIMIT 1) AND frame_times.name LIKE "%stagram.android%"
-ORDER BY ts_total LIMIT 1;
+    package as package,
+    id as id,
+    (SELECT ts + dur FROM slices WHERE slices.name LIKE "Start proc%mqtt" ORDER BY ts LIMIT 1) - launches.ts as ts_total
+FROM launches
+WHERE launches.package="com.instagram.android";
 
 -- Maps
 INSERT INTO hsc_based_startup_times
@@ -142,7 +142,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%maps%";
+WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%maps%" AND frame_times.launch_id = launches.id;
 
 -- Messages
 INSERT INTO hsc_based_startup_times
@@ -152,7 +152,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts_end > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%apps.messaging%" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%apps.messaging%"
+WHERE frame_times.ts_end > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%apps.messaging%" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%apps.messaging%" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Netflix
@@ -163,7 +163,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts < (SELECT ts FROM functions WHERE function_name LIKE "animator%" AND process_name LIKE "%lix.mediaclient" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%lix.mediaclient%"
+WHERE frame_times.ts < (SELECT ts FROM functions WHERE function_name LIKE "animator%" AND process_name LIKE "%lix.mediaclient" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%lix.mediaclient%" AND frame_times.launch_id = launches.id
 ORDER BY ts_total DESC LIMIT 1;
 
 -- Photos
@@ -174,7 +174,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%apps.photos%";
+WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%apps.photos%" AND frame_times.launch_id = launches.id;
 
 -- Settings
 INSERT INTO hsc_based_startup_times
@@ -184,7 +184,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=4 AND frame_times.name LIKE "%settings%";
+WHERE frame_times.frame_number=4 AND frame_times.name LIKE "%settings%" AND frame_times.launch_id = launches.id;
 
 -- Snapchat
 INSERT INTO hsc_based_startup_times
@@ -194,7 +194,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%napchat.android";
+WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%napchat.android" AND frame_times.launch_id = launches.id;
 
 -- Twitter
 INSERT INTO hsc_based_startup_times
@@ -204,7 +204,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%tter.android" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%tter.android"
+WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%tter.android" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%tter.android" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Youtube
@@ -215,4 +215,4 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%id.youtube";
+WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%id.youtube" AND frame_times.launch_id = launches.id;
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 6eaa39c..1d48dd5 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -1140,6 +1140,49 @@
   EXPECT_NE(0, thread_descs[1].thread().tid());
 }
 
+TEST_F(PerfettoApiTest, CustomTrackDescriptor) {
+  // Setup the trace config.
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  auto track = perfetto::ProcessTrack::Current();
+  auto desc = track.Serialize();
+  desc.mutable_process()->set_process_name("testing.exe");
+  desc.mutable_chrome_process()->set_process_priority(123);
+  perfetto::TrackEvent::SetTrackDescriptor(track, std::move(desc));
+  perfetto::TrackEvent::Flush();
+
+  tracing_session->get()->StopBlocking();
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  constexpr uint32_t kMainThreadSequence = 2;
+  bool found_desc = false;
+  for (const auto& packet : trace.packet()) {
+    if (packet.trusted_packet_sequence_id() != kMainThreadSequence)
+      continue;
+    if (packet.has_track_descriptor()) {
+      auto td = packet.track_descriptor();
+      EXPECT_TRUE(td.has_process());
+      EXPECT_NE(0, td.process().pid());
+      EXPECT_TRUE(td.has_chrome_process());
+      EXPECT_EQ("testing.exe", td.process().process_name());
+      EXPECT_EQ(123, td.chrome_process().process_priority());
+      found_desc = true;
+    }
+  }
+  EXPECT_TRUE(found_desc);
+}
+
 TEST_F(PerfettoApiTest, TrackEventCustomTrack) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"bar"});
diff --git a/src/tracing/track.cc b/src/tracing/track.cc
index b5260e4..56525f7 100644
--- a/src/tracing/track.cc
+++ b/src/tracing/track.cc
@@ -18,7 +18,9 @@
 
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/tracing/internal/track_event_data_source.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.gen.h"
 #include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.gen.h"
 #include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
 
 namespace perfetto {
@@ -26,25 +28,44 @@
 // static
 uint64_t Track::process_uuid;
 
-void Track::Serialize(protos::pbzero::TrackDescriptor* desc) const {
-  desc->set_uuid(uuid);
+protos::gen::TrackDescriptor Track::Serialize() const {
+  protos::gen::TrackDescriptor desc;
+  desc.set_uuid(uuid);
   if (parent_uuid)
-    desc->set_parent_uuid(parent_uuid);
+    desc.set_parent_uuid(parent_uuid);
+  return desc;
+}
+
+void Track::Serialize(protos::pbzero::TrackDescriptor* desc) const {
+  auto bytes = Serialize().SerializeAsString();
+  desc->AppendRawProtoBytes(bytes.data(), bytes.size());
+}
+
+protos::gen::TrackDescriptor ProcessTrack::Serialize() const {
+  auto desc = Track::Serialize();
+  auto pd = desc.mutable_process();
+  pd->set_pid(static_cast<int32_t>(pid));
+  // TODO(skyostil): Record command line.
+  return desc;
 }
 
 void ProcessTrack::Serialize(protos::pbzero::TrackDescriptor* desc) const {
-  Track::Serialize(desc);
-  auto pd = desc->set_process();
-  pd->set_pid(static_cast<int32_t>(pid));
-  // TODO(skyostil): Record command line.
+  auto bytes = Serialize().SerializeAsString();
+  desc->AppendRawProtoBytes(bytes.data(), bytes.size());
 }
 
-void ThreadTrack::Serialize(protos::pbzero::TrackDescriptor* desc) const {
-  Track::Serialize(desc);
-  auto td = desc->set_thread();
+protos::gen::TrackDescriptor ThreadTrack::Serialize() const {
+  auto desc = Track::Serialize();
+  auto td = desc.mutable_thread();
   td->set_pid(static_cast<int32_t>(pid));
   td->set_tid(static_cast<int32_t>(tid));
   // TODO(skyostil): Record thread name.
+  return desc;
+}
+
+void ThreadTrack::Serialize(protos::pbzero::TrackDescriptor* desc) const {
+  auto bytes = Serialize().SerializeAsString();
+  desc->AppendRawProtoBytes(bytes.data(), bytes.size());
 }
 
 namespace internal {
@@ -65,6 +86,12 @@
   Track::process_uuid = static_cast<uint64_t>(base::Uuidv4().lsb());
 }
 
+void TrackRegistry::UpdateTrack(Track track,
+                                const std::string& serialized_desc) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  tracks_[track.uuid] = std::move(serialized_desc);
+}
+
 void TrackRegistry::UpdateTrackImpl(
     Track track,
     std::function<void(protos::pbzero::TrackDescriptor*)> fill_function) {
@@ -74,10 +101,7 @@
       kInitialSliceSize, kMaximumSliceSize);
   fill_function(new_descriptor.get());
   auto serialized_desc = new_descriptor.SerializeAsString();
-  {
-    std::lock_guard<std::mutex> lock(mutex_);
-    tracks_[track.uuid] = std::move(serialized_desc);
-  }
+  UpdateTrack(track, serialized_desc);
 }
 
 void TrackRegistry::EraseTrack(Track track) {
diff --git a/test/configs/BUILD.gn b/test/configs/BUILD.gn
index 0dc0d35..fcef973 100644
--- a/test/configs/BUILD.gn
+++ b/test/configs/BUILD.gn
@@ -37,7 +37,7 @@
     "ftrace_largebuffer.cfg",
     "heapprofd.cfg",
     "long_trace.cfg",
-    "processes.cfg",
+    "scheduling.cfg",
     "summary.cfg",
     "sys_stats.cfg",
   ]
diff --git a/test/configs/processes.cfg b/test/configs/processes.cfg
deleted file mode 100644
index f8c24b8..0000000
--- a/test/configs/processes.cfg
+++ /dev/null
@@ -1,57 +0,0 @@
-buffers {
-  size_kb: 100024
-  fill_policy: RING_BUFFER
-}
-
-data_sources {
-  config {
-    name: "com.google.perfetto.ftrace"
-    target_buffer: 0
-    ftrace_config {
-      buffer_size_kb: 40 # 4 (page size) * 10
-      drain_period_ms: 200
-      ftrace_events: "sched_process_exec"
-      ftrace_events: "sched_process_exit"
-      ftrace_events: "sched_process_fork"
-      ftrace_events: "sched_process_free"
-      ftrace_events: "sched_process_hang"
-      ftrace_events: "sched_process_wait"
-      ftrace_events: "sched_wakeup_new"
-      ftrace_events: "sched_wakeup"
-      ftrace_events: "sched_waking"
-      ftrace_events: "smbus_read"
-      ftrace_events: "smbus_reply"
-      ftrace_events: "smbus_result"
-      ftrace_events: "smbus_write"
-      ftrace_events: "softirq_entry"
-      ftrace_events: "softirq_exit"
-      ftrace_events: "softirq_raise"
-      ftrace_events: "suspend_resume"
-      ftrace_events: "sync_pt"
-      ftrace_events: "sync_timeline"
-      ftrace_events: "sync_wait"
-      ftrace_events: "task_newtask"
-      ftrace_events: "task_rename"
-      ftrace_events: "tracing_mark_write"
-      ftrace_events: "workqueue_activate_work"
-      ftrace_events: "workqueue_execute_end"
-      ftrace_events: "workqueue_execute_start"
-      ftrace_events: "workqueue_queue_work"
-    }
-  }
-}
-
-data_sources {
-  config {
-    name: "com.google.perfetto.process_stats"
-    target_buffer: 0
-  }
-}
-
-producers {
-  producer_name: "com.google.perfetto.traced_probes"
-  shm_size_kb: 4096
-  page_size_kb: 4
-}
-
-duration_ms: 10000
diff --git a/test/configs/scheduling.cfg b/test/configs/scheduling.cfg
new file mode 100644
index 0000000..347a183
--- /dev/null
+++ b/test/configs/scheduling.cfg
@@ -0,0 +1,41 @@
+# One buffer allocated within the central tracing binary for the entire trace,
+# shared by the two data sources below.
+buffers {
+  size_kb: 20480
+  fill_policy: DISCARD
+}
+
+# Ftrace data from the kernel, mainly the process scheduling events.
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      ftrace_events: "sched_switch"
+      ftrace_events: "sched_waking"
+      ftrace_events: "sched_wakeup_new"
+
+      ftrace_events: "task_newtask"
+      ftrace_events: "task_rename"
+
+      ftrace_events: "sched_process_exec"
+      ftrace_events: "sched_process_exit"
+      ftrace_events: "sched_process_fork"
+      ftrace_events: "sched_process_free"
+      ftrace_events: "sched_process_hang"
+      ftrace_events: "sched_process_wait"
+    }
+  }
+}
+
+# Resolve process commandlines and parent/child relationships, to better
+# interpret the ftrace events, which are in terms of pids.
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+  }
+}
+
+# 10s trace, but can be stopped prematurely.
+duration_ms: 10000
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 00568b2..ce82c1e 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -390,6 +390,7 @@
     "perfetto_cc_protocpp_library",
     "perfetto_cc_protozero_library",
     "perfetto_java_proto_library",
+    "perfetto_java_lite_proto_library",
     "perfetto_proto_library",
     "perfetto_py_binary",
     "perfetto_gensignature_internal_only",
diff --git a/tools/tmux b/tools/tmux
index f8e889f..0eb3d6f 100755
--- a/tools/tmux
+++ b/tools/tmux
@@ -13,9 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 set -e
-CUR_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
+SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
 
-export PATH="$PATH:$CUR_DIR"
+export PATH="$PATH:$SCRIPT_DIR"
 
 if [ "$TMPDIR" == "" ]; then
   TMPDIR=/tmp
@@ -194,8 +194,13 @@
   DIR=$(mktemp -p $TMPDIR -d perfetto.XXXXXX)
 fi
 
-"$CUR_DIR/ninja" -C "$OUT" \
-  traced traced_probes perfetto trace_to_text test/configs
+# (re)build the binaries
+BUILD_TARGETS=(traced traced_probes perfetto test/configs)
+if [[ SKIP_CONVERTERS -eq 0 ]]; then
+  BUILD_TARGETS+=(trace_to_text)
+fi
+
+"$SCRIPT_DIR/ninja" -C "$OUT" ${BUILD_TARGETS[*]}
 
 push "$OUT/traced"
 push "$OUT/traced_probes"
@@ -215,6 +220,7 @@
 
 CONFIG_DEVICE_PATH="$CONFIG"
 CMD_OPTS=""
+# Shorthand for using serialized test configs.
 if [[ "$CONFIG" == *.protobuf ]]; then
   CONFIG_DEVICE_PATH="$CONFIG"
   CONFIG_PATH=$OUT/$CONFIG
@@ -225,10 +231,10 @@
   push "$CONFIG_PATH"
 elif [[ "$CONFIG" != ":test" ]]; then
   CONFIG_DEVICE_PATH="$(basename "$CONFIG")"
-  CONFIG_PATH=test/configs/$CONFIG
-  # Check if this is a valid absolute path
+  CONFIG_PATH=$CONFIG
+  # If path isn't valid, assume it's a name of a test config.
   if [[ ! -f $CONFIG_PATH ]]; then
-    CONFIG_PATH=$CONFIG
+    CONFIG_PATH=test/configs/$CONFIG
     if [[ ! -f $CONFIG_PATH ]]; then
       echo >&2 "Config \"$CONFIG\" not known."
       exit 1
@@ -360,7 +366,7 @@
 
 reset_tracing
 
-TRACE=$HOME/Downloads/trace
+TRACE=$TMPDIR/trace
 echo -e "\n\x1b[32mPulling trace into $TRACE.protobuf\x1b[0m"
 pull trace "$TRACE.protobuf"
 
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 4c5a1cd..c17c086 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -132,6 +132,7 @@
     if (!this._cpus) {
       const result =
           await this.query('select distinct(cpu) from sched order by cpu;');
+      if (result.numRecords === 0) return [];
       this._cpus = result.columns[0].longValues!.map(n => +n);
     }
     return this._cpus;
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 703b190..ee8ad85 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -189,6 +189,7 @@
     procThreadAssociationFtrace = true;
     ftraceEvents.add('mm_event/mm_event_record');
     ftraceEvents.add('kmem/rss_stat');
+    ftraceEvents.add('ion/ion_stat');
     ftraceEvents.add('kmem/ion_heap_grow');
     ftraceEvents.add('kmem/ion_heap_shrink');
   }
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index fb6d17f..5550582 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -143,7 +143,8 @@
 
     const utids = [...rawUtidResult.columns[0].longValues!];
 
-    const maxCpu = Math.max(...await this.engine.getCpus());
+    const cpus = await this.engine.getCpus();
+    const maxCpu = Math.max(...cpus, -1);
 
     const rawResult = await this.query(`
         select
diff --git a/ui/src/tracks/process_scheduling/controller.ts b/ui/src/tracks/process_scheduling/controller.ts
index 3736cca..914e452 100644
--- a/ui/src/tracks/process_scheduling/controller.ts
+++ b/ui/src/tracks/process_scheduling/controller.ts
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {assertTrue} from '../../base/logging';
 import {fromNs, toNs} from '../../common/time';
 import {LIMIT} from '../../common/track_data';
-
 import {
   TrackController,
   trackControllerRegistry
@@ -55,7 +55,10 @@
       await this.query(`create virtual table ${this.tableName('span')}
               using span_join(${this.tableName('process')} PARTITIONED utid,
                               ${this.tableName('window')});`);
-      this.maxCpu = Math.max(...await this.engine.getCpus()) + 1;
+      const cpus = await this.engine.getCpus();
+      // A process scheduling track should only exist in a trace that has cpus.
+      assertTrue(cpus.length > 0);
+      this.maxCpu = Math.max(...cpus) + 1;
       this.setup = true;
     }