Merge "Add interning support to ProtoToArgsParser."
diff --git a/include/perfetto/ext/base/scoped_file.h b/include/perfetto/ext/base/scoped_file.h
index 978843e..5a99de6 100644
--- a/include/perfetto/ext/base/scoped_file.h
+++ b/include/perfetto/ext/base/scoped_file.h
@@ -53,8 +53,10 @@
           class Checker = internal::DefaultValidityChecker<T, InvalidValue>>
 class PERFETTO_EXPORT ScopedResource {
  public:
-  explicit ScopedResource(T t = InvalidValue) : t_(t) {}
+  using ValidityChecker = Checker;
   static constexpr T kInvalid = InvalidValue;
+
+  explicit ScopedResource(T t = InvalidValue) : t_(t) {}
   ScopedResource(ScopedResource&& other) noexcept {
     t_ = other.t_;
     other.t_ = InvalidValue;
diff --git a/include/perfetto/tracing/console_interceptor.h b/include/perfetto/tracing/console_interceptor.h
index 75fffc7..98c8879 100644
--- a/include/perfetto/tracing/console_interceptor.h
+++ b/include/perfetto/tracing/console_interceptor.h
@@ -57,7 +57,8 @@
 
 struct ConsoleColor;
 
-class ConsoleInterceptor : public Interceptor<ConsoleInterceptor> {
+class PERFETTO_EXPORT ConsoleInterceptor
+    : public Interceptor<ConsoleInterceptor> {
  public:
   ~ConsoleInterceptor() override;
 
diff --git a/include/perfetto/tracing/data_source.h b/include/perfetto/tracing/data_source.h
index cd35ef0..c4c6681 100644
--- a/include/perfetto/tracing/data_source.h
+++ b/include/perfetto/tracing/data_source.h
@@ -319,6 +319,11 @@
     if (PERFETTO_UNLIKELY(!tls_state_))
       tls_state_ = GetOrCreateDataSourceTLS(&static_state_);
 
+    // Avoid re-entering the trace point recursively.
+    if (PERFETTO_UNLIKELY(tls_state_->root_tls->is_in_trace_point))
+      return;
+    internal::ScopedReentrancyAnnotator scoped_annotator(*tls_state_->root_tls);
+
     // TracingTLS::generation is a global monotonic counter that is incremented
     // every time a tracing session is stopped. We use that as a signal to force
     // a slow-path garbage collection of all the trace writers for the current
@@ -373,12 +378,6 @@
       // handshaking to make this extremely unrealistic.
 
       auto& tls_inst = tls_state_->per_instance[i];
-
-      // Avoid re-entering the trace point recursively.
-      if (PERFETTO_UNLIKELY(tls_inst.is_in_trace_point))
-        continue;
-      ScopedReentrancyAnnotator scoped_annotator(tls_inst);
-
       if (PERFETTO_UNLIKELY(!tls_inst.trace_writer)) {
         // Here we need an acquire barrier, which matches the release-store made
         // by TracingMuxerImpl::SetupDataSource(), to ensure that the backend_id
@@ -456,18 +455,6 @@
     }
   };
 
-  struct ScopedReentrancyAnnotator {
-    ScopedReentrancyAnnotator(
-        internal::DataSourceInstanceThreadLocalState& tls_inst)
-        : tls_inst_(tls_inst) {
-      tls_inst_.is_in_trace_point = true;
-    }
-    ~ScopedReentrancyAnnotator() { tls_inst_.is_in_trace_point = false; }
-
-   private:
-    internal::DataSourceInstanceThreadLocalState& tls_inst_;
-  };
-
   // Create the user provided incremental state in the given thread-local
   // storage. Note: The second parameter here is used to specialize the case
   // where there is no incremental state type.
diff --git a/include/perfetto/tracing/internal/data_source_internal.h b/include/perfetto/tracing/internal/data_source_internal.h
index 6f19db0..cb052c6 100644
--- a/include/perfetto/tracing/internal/data_source_internal.h
+++ b/include/perfetto/tracing/internal/data_source_internal.h
@@ -156,7 +156,6 @@
     data_source_instance_id = 0;
     incremental_state_generation = 0;
     is_intercepted = false;
-    is_in_trace_point = false;
   }
 
   std::unique_ptr<TraceWriterBase> trace_writer;
@@ -167,7 +166,6 @@
   BufferId buffer_id;
   uint64_t data_source_instance_id;
   bool is_intercepted;
-  bool is_in_trace_point;
 };
 
 // Per-DataSource-type thread-local state.
diff --git a/include/perfetto/tracing/internal/tracing_tls.h b/include/perfetto/tracing/internal/tracing_tls.h
index 67f0637..68515a4 100644
--- a/include/perfetto/tracing/internal/tracing_tls.h
+++ b/include/perfetto/tracing/internal/tracing_tls.h
@@ -74,6 +74,11 @@
   // thread-local TraceWriter(s) is issued.
   uint32_t generation = 0;
 
+  // This flag is true while this thread is inside a trace point for any data
+  // source or in other delicate parts of the tracing machinery during which we
+  // should not try to trace. Used to prevent unexpected re-entrancy.
+  bool is_in_trace_point = false;
+
   // By default all data source instances have independent thread-local state
   // (see above).
   std::array<DataSourceThreadLocalState, kMaxDataSources> data_sources_tls{};
@@ -84,6 +89,17 @@
   DataSourceThreadLocalState track_event_tls{};
 };
 
+struct ScopedReentrancyAnnotator {
+  ScopedReentrancyAnnotator(TracingTLS& root_tls) : root_tls_(root_tls) {
+    PERFETTO_DCHECK(!root_tls_.is_in_trace_point);
+    root_tls_.is_in_trace_point = true;
+  }
+  ~ScopedReentrancyAnnotator() { root_tls_.is_in_trace_point = false; }
+
+ private:
+  TracingTLS& root_tls_;
+};
+
 }  // namespace internal
 }  // namespace perfetto
 
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index 8c4f20f..d293039 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -187,6 +187,8 @@
 #endif
   }
 
+  static int GetSessionCount();
+
   // Represents the default track for the calling thread.
   static const Track kDefaultTrack;
 
@@ -199,6 +201,8 @@
   static protos::pbzero::DebugAnnotation* AddDebugAnnotation(
       perfetto::EventContext*,
       const char* name);
+
+  static std::atomic<int> session_count_;
 };
 
 }  // namespace internal
diff --git a/include/perfetto/tracing/internal/track_event_interned_fields.h b/include/perfetto/tracing/internal/track_event_interned_fields.h
index cccbf0c..81bfd98 100644
--- a/include/perfetto/tracing/internal/track_event_interned_fields.h
+++ b/include/perfetto/tracing/internal/track_event_interned_fields.h
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/export.h"
 #include "perfetto/tracing/track_event_interned_data_index.h"
 
 #ifndef INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_INTERNED_FIELDS_H_
@@ -26,7 +27,7 @@
 // to share the interning buffers with Perfetto internals (e.g.
 // perfetto::TracedValue implementation).
 
-struct InternedEventCategory
+struct PERFETTO_EXPORT InternedEventCategory
     : public TrackEventInternedDataIndex<
           InternedEventCategory,
           perfetto::protos::pbzero::InternedData::kEventCategoriesFieldNumber,
@@ -37,14 +38,10 @@
   static void Add(protos::pbzero::InternedData* interned_data,
                   size_t iid,
                   const char* value,
-                  size_t length) {
-    auto category = interned_data->add_event_categories();
-    category->set_iid(iid);
-    category->set_name(value, length);
-  }
+                  size_t length);
 };
 
-struct InternedEventName
+struct PERFETTO_EXPORT InternedEventName
     : public TrackEventInternedDataIndex<
           InternedEventName,
           perfetto::protos::pbzero::InternedData::kEventNamesFieldNumber,
@@ -54,14 +51,10 @@
 
   static void Add(protos::pbzero::InternedData* interned_data,
                   size_t iid,
-                  const char* value) {
-    auto name = interned_data->add_event_names();
-    name->set_iid(iid);
-    name->set_name(value);
-  }
+                  const char* value);
 };
 
-struct InternedDebugAnnotationName
+struct PERFETTO_EXPORT InternedDebugAnnotationName
     : public TrackEventInternedDataIndex<
           InternedDebugAnnotationName,
           perfetto::protos::pbzero::InternedData::
@@ -72,11 +65,7 @@
 
   static void Add(protos::pbzero::InternedData* interned_data,
                   size_t iid,
-                  const char* value) {
-    auto name = interned_data->add_debug_annotation_names();
-    name->set_iid(iid);
-    name->set_name(value);
-  }
+                  const char* value);
 };
 
 }  // namespace internal
diff --git a/include/perfetto/tracing/traced_value.h b/include/perfetto/tracing/traced_value.h
index ee36fb5..b1a14d3 100644
--- a/include/perfetto/tracing/traced_value.h
+++ b/include/perfetto/tracing/traced_value.h
@@ -23,13 +23,19 @@
 #include "perfetto/tracing/internal/checked_scope.h"
 #include "perfetto/tracing/string_helpers.h"
 #include "perfetto/tracing/traced_value_forward.h"
-#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
 
+#include <memory>
 #include <type_traits>
 #include <utility>
 
 namespace perfetto {
 
+namespace protos {
+namespace pbzero {
+class DebugAnnotation;
+}
+}  // namespace protos
+
 class DebugAnnotation;
 
 // These classes provide a JSON-inspired way to write structed data into traces.
diff --git a/include/perfetto/tracing/track_event_legacy.h b/include/perfetto/tracing/track_event_legacy.h
index 175226d..cd86510 100644
--- a/include/perfetto/tracing/track_event_legacy.h
+++ b/include/perfetto/tracing/track_event_legacy.h
@@ -1163,10 +1163,18 @@
   } while (0)
 
 // Macro to efficiently determine, through polling, if a new trace has begun.
-// TODO(skyostil): Implement.
-#define TRACE_EVENT_IS_NEW_TRACE(ret) \
-  do {                                \
-    *ret = false;                     \
+#define TRACE_EVENT_IS_NEW_TRACE(ret)                                \
+  do {                                                               \
+    static int PERFETTO_UID(prev) = -1;                              \
+    int PERFETTO_UID(curr) =                                         \
+        ::perfetto::internal::TrackEventInternal::GetSessionCount(); \
+    if (::PERFETTO_TRACK_EVENT_NAMESPACE::TrackEvent::IsEnabled() && \
+        (PERFETTO_UID(prev) != PERFETTO_UID(curr))) {                \
+      *(ret) = true;                                                 \
+      PERFETTO_UID(prev) = PERFETTO_UID(curr);                       \
+    } else {                                                         \
+      *(ret) = false;                                                \
+    }                                                                \
   } while (0)
 
 // ----------------------------------------------------------------------------
diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto
index b49b338..22b18e3 100644
--- a/protos/perfetto/config/data_source_config.proto
+++ b/protos/perfetto/config/data_source_config.proto
@@ -41,7 +41,11 @@
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
-    SESSION_INITIATOR_STATSD = 1;
+    // This trace was initiated from a trusted system app has DUMP and
+    // USAGE_STATS permission. This system app is expected to not expose the
+    // trace to the user of the device.
+    // This is determined by checking the UID initiating the trace.
+    SESSION_INITIATOR_TRUSTED_SYSTEM = 1;
   };
   // Data source unique name, e.g., "linux.ftrace". This must match
   // the name passed by the data source when it registers (see
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index f7c2ffa..529adc7 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -1331,7 +1331,11 @@
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
-    SESSION_INITIATOR_STATSD = 1;
+    // This trace was initiated from a trusted system app has DUMP and
+    // USAGE_STATS permission. This system app is expected to not expose the
+    // trace to the user of the device.
+    // This is determined by checking the UID initiating the trace.
+    SESSION_INITIATOR_TRUSTED_SYSTEM = 1;
   };
   // Data source unique name, e.g., "linux.ftrace". This must match
   // the name passed by the data source when it registers (see
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 9ea57c3..b5c30d1 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -1331,7 +1331,11 @@
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
-    SESSION_INITIATOR_STATSD = 1;
+    // This trace was initiated from a trusted system app has DUMP and
+    // USAGE_STATS permission. This system app is expected to not expose the
+    // trace to the user of the device.
+    // This is determined by checking the UID initiating the trace.
+    SESSION_INITIATOR_TRUSTED_SYSTEM = 1;
   };
   // Data source unique name, e.g., "linux.ftrace". This must match
   // the name passed by the data source when it registers (see
diff --git a/src/base/periodic_task.cc b/src/base/periodic_task.cc
index deeed9a..fcbc9de 100644
--- a/src/base/periodic_task.cc
+++ b/src/base/periodic_task.cc
@@ -118,6 +118,9 @@
     return;  // Destroyed or Reset() in the meanwhile.
   PERFETTO_DCHECK_THREAD(thiz->thread_checker_);
   if (thiz->timer_fd_) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    PERFETTO_FATAL("timerfd for periodic tasks unsupported on Windows");
+#else
     // If we are using a timerfd there is no need to repeatedly call
     // PostDelayedTask(). The kernel will wakeup the timer fd periodically. We
     // just need to read() it.
@@ -130,6 +133,7 @@
       PERFETTO_PLOG("read(timerfd) failed, falling back on PostDelayedTask");
       thiz->ResetTimerFd();
     }
+#endif
   }
   // The repetition of the if() is to deal with the ResetTimerFd() case above.
   if (!thiz->timer_fd_) {
diff --git a/src/base/periodic_task_unittest.cc b/src/base/periodic_task_unittest.cc
index 11bafda..4919720 100644
--- a/src/base/periodic_task_unittest.cc
+++ b/src/base/periodic_task_unittest.cc
@@ -90,7 +90,8 @@
       dup2(*dev_null, pt.timer_fd_for_testing());
     }
 #else
-    EXPECT_EQ(pt.timer_fd_for_testing(), base::ScopedPlatformHandle::kInvalid);
+    EXPECT_FALSE(base::ScopedPlatformHandle::ValidityChecker::IsValid(
+        pt.timer_fd_for_testing()));
 #endif
     if (num_callbacks == 6)
       quit_closure();
diff --git a/src/profiling/common/producer_support.cc b/src/profiling/common/producer_support.cc
index 2ed2852..2347edd 100644
--- a/src/profiling/common/producer_support.cc
+++ b/src/profiling/common/producer_support.cc
@@ -100,7 +100,7 @@
     switch (ds_config.session_initiator()) {
       case DataSourceConfig::SESSION_INITIATOR_UNSPECIFIED:
         return pkg.profileable_from_shell || pkg.debuggable;
-      case DataSourceConfig::SESSION_INITIATOR_STATSD:
+      case DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM:
         return pkg.profileable || pkg.debuggable;
     }
   }
diff --git a/src/profiling/common/producer_support_unittest.cc b/src/profiling/common/producer_support_unittest.cc
index c1131c9..33fdf28 100644
--- a/src/profiling/common/producer_support_unittest.cc
+++ b/src/profiling/common/producer_support_unittest.cc
@@ -106,7 +106,8 @@
 
 TEST(CanProfileAndroidTest, UserProfileableMatchingInstallerStatsd) {
   DataSourceConfig ds_config;
-  ds_config.set_session_initiator(DataSourceConfig::SESSION_INITIATOR_STATSD);
+  ds_config.set_session_initiator(
+      DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM);
   auto tmp = base::TempFile::Create();
   constexpr char content[] =
       "invalid.example 10001 0 /data/user/0/invalid.example "
@@ -131,7 +132,8 @@
 
 TEST(CanProfileAndroidTest, UserProfileableNonMatchingInstallerStatsd) {
   DataSourceConfig ds_config;
-  ds_config.set_session_initiator(DataSourceConfig::SESSION_INITIATOR_STATSD);
+  ds_config.set_session_initiator(
+      DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM);
   auto tmp = base::TempFile::Create();
   constexpr char content[] =
       "invalid.example 10001 0 /data/user/0/invalid.example "
diff --git a/src/profiling/common/unwind_support.cc b/src/profiling/common/unwind_support.cc
index 205ede9..5302015 100644
--- a/src/profiling/common/unwind_support.cc
+++ b/src/profiling/common/unwind_support.cc
@@ -67,6 +67,7 @@
   if (!base::ReadFileDescriptor(*fd_, &content))
     return false;
 
+  unwindstack::SharedString name("");
   unwindstack::MapInfo* prev_map = nullptr;
   unwindstack::MapInfo* prev_real_map = nullptr;
   return android::procinfo::ReadMapFileContent(
@@ -77,9 +78,13 @@
             strncmp(mapinfo.name.c_str() + 5, "ashmem/", 7) != 0) {
           flags |= unwindstack::MAPS_FLAGS_DEVICE_MAP;
         }
+        // Share the string if it matches for consecutive maps.
+        if (name != mapinfo.name) {
+          name = unwindstack::SharedString(mapinfo.name);
+        }
         maps_.emplace_back(new unwindstack::MapInfo(
             prev_map, prev_real_map, mapinfo.start, mapinfo.end, mapinfo.pgoff,
-            flags, mapinfo.name));
+            flags, name));
         prev_map = maps_.back().get();
         if (!prev_map->IsBlank()) {
           prev_real_map = prev_map;
diff --git a/src/profiling/deobfuscator.cc b/src/profiling/deobfuscator.cc
index 2a7b9c0..ba58dfa 100644
--- a/src/profiling/deobfuscator.cc
+++ b/src/profiling/deobfuscator.cc
@@ -16,6 +16,7 @@
 
 #include "src/profiling/deobfuscator.h"
 
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_splitter.h"
@@ -157,37 +158,36 @@
 
 // See https://www.guardsquare.com/en/products/proguard/manual/retrace for the
 // file format we are parsing.
-bool ProguardParser::AddLine(std::string line) {
-  if (line.length() == 0)
-    return true;
+base::Status ProguardParser::AddLine(std::string line) {
+  if (line.length() == 0 || line[0] == '#')
+    return base::Status();
   bool is_member = line[0] == ' ';
   if (is_member && !current_class_) {
-    PERFETTO_ELOG("Failed to parse proguard map. Saw member before class.");
-    return false;
+    return base::Status(
+        "Failed to parse proguard map. Saw member before class.");
   }
   if (!is_member) {
     auto opt_cls = ParseClass(std::move(line));
     if (!opt_cls)
-      return false;
+      return base::Status("Class not found.");
     auto p = mapping_.emplace(std::move(opt_cls->obfuscated_name),
                               std::move(opt_cls->deobfuscated_name));
     if (!p.second) {
-      PERFETTO_ELOG("Duplicate class.");
-      return false;
+      return base::Status("Duplicate class.");
     }
     current_class_ = &p.first->second;
   } else {
     auto opt_member = ParseMember(std::move(line));
     if (!opt_member)
-      return false;
+      return base::Status("Failed to parse member.");
     switch (opt_member->type) {
       case (ProguardMemberType::kField): {
         if (!current_class_->AddField(opt_member->obfuscated_name,
                                       opt_member->deobfuscated_name)) {
-          PERFETTO_ELOG("Member redefinition: %s.%s. Proguard map invalid",
-                        current_class_->deobfuscated_name().c_str(),
-                        opt_member->deobfuscated_name.c_str());
-          return false;
+          return base::Status(std::string("Member redefinition: ") +
+                              current_class_->deobfuscated_name().c_str() +
+                              "." + opt_member->deobfuscated_name.c_str() +
+                              " Proguard map invalid");
         }
         break;
       }
@@ -198,13 +198,19 @@
       }
     }
   }
-  return true;
+  return base::Status();
 }
 
 bool ProguardParser::AddLines(std::string contents) {
+  size_t lineno = 1;
   for (base::StringSplitter lines(std::move(contents), '\n'); lines.Next();) {
-    if (!AddLine(lines.cur_token()))
+    auto status = AddLine(lines.cur_token());
+    if (!status.ok()) {
+      PERFETTO_ELOG("Failed to parse proguard map (line %zu): %s", lineno,
+                    status.c_message());
       return false;
+    }
+    lineno++;
   }
   return true;
 }
diff --git a/src/profiling/deobfuscator.h b/src/profiling/deobfuscator.h
index 061b5da..b96f292 100644
--- a/src/profiling/deobfuscator.h
+++ b/src/profiling/deobfuscator.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <utility>
 #include <vector>
+#include "perfetto/base/status.h"
 
 namespace perfetto {
 namespace profiling {
@@ -59,7 +60,7 @@
   bool AddField(std::string obfuscated_name, std::string deobfuscated_name) {
     auto p = deobfuscated_fields_.emplace(std::move(obfuscated_name),
                                           deobfuscated_name);
-    return p.second && p.first->second == deobfuscated_name;
+    return p.second || p.first->second == deobfuscated_name;
   }
 
   void AddMethod(std::string obfuscated_name, std::string deobfuscated_name) {
@@ -90,7 +91,7 @@
  public:
   // A return value of false means this line failed to parse. This leaves the
   // parser in an undefined state and it should no longer be used.
-  bool AddLine(std::string line);
+  base::Status AddLine(std::string line);
   bool AddLines(std::string contents);
 
   std::map<std::string, ObfuscatedClass> ConsumeMapping() {
diff --git a/src/profiling/deobfuscator_unittest.cc b/src/profiling/deobfuscator_unittest.cc
index 66001dd..02a0e8d 100644
--- a/src/profiling/deobfuscator_unittest.cc
+++ b/src/profiling/deobfuscator_unittest.cc
@@ -37,8 +37,10 @@
 
 TEST(ProguardParserTest, ReadClass) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
   ASSERT_THAT(p.ConsumeMapping(),
               ElementsAre(std::pair<std::string, ObfuscatedClass>(
                   "android.arch.a.a.a",
@@ -47,22 +49,28 @@
 
 TEST(ProguardParserTest, MissingColon) {
   ProguardParser p;
-  ASSERT_FALSE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a"));
+  ASSERT_FALSE(
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a")
+          .ok());
 }
 
 TEST(ProguardParserTest, UnexpectedMember) {
   ProguardParser p;
   ASSERT_FALSE(
-      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b")
+          .ok());
 }
 
 TEST(ProguardParserTest, Member) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
   ASSERT_TRUE(
-      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b")
+          .ok());
   std::map<std::string, std::string> deobfuscated_fields{{"b", "mDelegate"}};
   ASSERT_THAT(
       p.ConsumeMapping(),
@@ -74,9 +82,11 @@
 
 TEST(ProguardParserTest, Method) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  ASSERT_TRUE(
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b").ok());
   auto mapping = p.ConsumeMapping();
   ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
   EXPECT_THAT(
@@ -87,11 +97,13 @@
 
 TEST(ProguardParserTest, AmbiguousMethodSameCls) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
   ASSERT_TRUE(
-      p.AddLine("    15:15:boolean somethingDifferent(int):116:116 -> b"));
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b").ok());
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean somethingDifferent(int):116:116 -> b").ok());
   auto mapping = p.ConsumeMapping();
   ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
   EXPECT_THAT(
@@ -102,11 +114,14 @@
 
 TEST(ProguardParserTest, AmbiguousMethodDifferentCls) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
   ASSERT_TRUE(
-      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b"));
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b").ok());
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b")
+          .ok());
   auto mapping = p.ConsumeMapping();
   ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
   EXPECT_THAT(mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
@@ -118,12 +133,15 @@
 
 TEST(ProguardParserTest, AmbiguousMethodSameAndDifferentCls) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean what(String):116:116 -> b"));
   ASSERT_TRUE(
-      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b"));
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b").ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean what(String):116:116 -> b").ok());
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b")
+          .ok());
   auto mapping = p.ConsumeMapping();
   ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
   EXPECT_THAT(mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
@@ -135,13 +153,17 @@
 
 TEST(ProguardParserTest, AmbiguousMethodSameAndDifferentCls2) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean what(String):116:116 -> b"));
   ASSERT_TRUE(
-      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean Foo.third(int,int):116:116 -> b"));
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b").ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean what(String):116:116 -> b").ok());
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b")
+          .ok());
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.third(int,int):116:116 -> b").ok());
   auto mapping = p.ConsumeMapping();
   ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
   EXPECT_THAT(mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
@@ -153,28 +175,53 @@
 
 TEST(ProguardParserTest, DuplicateClass) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_FALSE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor2 -> android.arch.a.a.a:"));
+  ASSERT_TRUE(
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_FALSE(p.AddLine("android.arch.core.executor.ArchTaskExecutor2 -> "
+                         "android.arch.a.a.a:")
+                   .ok());
 }
 
 TEST(ProguardParserTest, DuplicateField) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
   ASSERT_TRUE(
-      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b")
+          .ok());
   ASSERT_FALSE(
-      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate2 -> b"));
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate2 -> b")
+          .ok());
 }
 
 TEST(ProguardParserTest, DuplicateMethod) {
   ProguardParser p;
-  ASSERT_TRUE(p.AddLine(
-      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
-  ASSERT_TRUE(p.AddLine("    15:15:boolean doSomething(boolean):116:116 -> b"));
+  ASSERT_TRUE(
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b").ok());
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean doSomething(boolean):116:116 -> b").ok());
+}
+
+TEST(ProguardParserTest, DuplicateFieldSame) {
+  ProguardParser p;
+  ASSERT_TRUE(
+      p.AddLine(
+           "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:")
+          .ok());
+  ASSERT_TRUE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b")
+          .ok());
+  ASSERT_TRUE(
+      p.AddLine(
+           "    1:1:android.arch.core.executor.TaskExecutor mDelegate -> b")
+          .ok());
 }
 
 }  // namespace
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index 1e434ee..16da1fa 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -375,7 +375,7 @@
 void HeapprofdProducer::SetupDataSource(DataSourceInstanceID id,
                                         const DataSourceConfig& ds_config) {
   if (ds_config.session_initiator() ==
-      DataSourceConfig::SESSION_INITIATOR_STATSD) {
+      DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM) {
     PERFETTO_LOG("Setting up datasource: statsd initiator.");
   } else {
     PERFETTO_LOG("Setting up datasource: non-statsd initiator.");
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index f069019..2531150 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -155,8 +155,10 @@
       size_t name_index = 2 + tgid_length + 1;
       out->name = base::StringView(
           s + name_index, len - name_index - (s[len - 1] == '\n' ? 1 : 0));
-      if (out->name.empty())
-        return SystraceParseResult::kFailure;
+      if (out->name.empty()) {
+        static const char kEmptySliceName[] = "[empty slice name]";
+        out->name = base::StringView(kEmptySliceName);
+      }
       return SystraceParseResult::kSuccess;
     }
     case 'E': {
diff --git a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
index 5c65514..b0e9de1 100644
--- a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
@@ -41,7 +41,9 @@
   ASSERT_EQ(ParseSystraceTracePoint("C", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("S", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("F", &result), Result::kFailure);
-  ASSERT_EQ(ParseSystraceTracePoint("B|42|\n", &result), Result::kFailure);
+
+  ASSERT_EQ(ParseSystraceTracePoint("B|42|\n", &result), Result::kSuccess);
+  EXPECT_EQ(result, SystraceTracePoint::B(42, "[empty slice name]"));
 
   ASSERT_EQ(ParseSystraceTracePoint("B|1|foo", &result), Result::kSuccess);
   EXPECT_EQ(result, SystraceTracePoint::B(1, "foo"));
diff --git a/src/trace_processor/metrics/android/android_startup.sql b/src/trace_processor/metrics/android/android_startup.sql
index 6ea79ee..0c6d429 100644
--- a/src/trace_processor/metrics/android/android_startup.sql
+++ b/src/trace_processor/metrics/android/android_startup.sql
@@ -229,10 +229,41 @@
 CREATE VIRTUAL TABLE gc_slices_by_state
 USING SPAN_JOIN(gc_slices PARTITIONED utid, thread_state_extended PARTITIONED utid);
 
+DROP TABLE IF EXISTS gc_slices_by_state_materialized;
+CREATE TABLE gc_slices_by_state_materialized AS
+SELECT launch_id, SUM(dur) as sum_dur
+FROM gc_slices_by_state
+WHERE state = 'Running'
+GROUP BY launch_id;
+
 DROP TABLE IF EXISTS launch_threads_cpu;
 CREATE VIRTUAL TABLE launch_threads_cpu
 USING SPAN_JOIN(launch_threads PARTITIONED utid, thread_state_extended PARTITIONED utid);
 
+DROP TABLE IF EXISTS launch_threads_cpu_materialized;
+CREATE TABLE launch_threads_cpu_materialized AS
+SELECT launch_id, SUM(dur) as sum_dur
+FROM launch_threads_cpu
+WHERE thread_name = 'Jit thread pool' AND state = 'Running'
+GROUP BY launch_id;
+
+DROP TABLE IF EXISTS activity_names_materialized;
+CREATE TABLE activity_names_materialized AS
+SELECT launch_id, slice_name, slice_ts
+FROM main_process_slice_unaggregated
+WHERE (slice_name LIKE 'performResume:%' OR slice_name LIKE 'performCreate:%');
+
+DROP TABLE IF EXISTS jit_compiled_methods_materialized;
+CREATE TABLE jit_compiled_methods_materialized AS
+SELECT
+  launch_id,
+  COUNT(1) as count
+FROM main_process_slice_unaggregated
+WHERE
+  slice_name LIKE 'JIT compiling%'
+  AND thread_name = 'Jit thread pool'
+GROUP BY launch_id;
+
 DROP VIEW IF EXISTS startup_view;
 CREATE VIEW startup_view AS
 SELECT
@@ -240,20 +271,18 @@
     'startup_id', launches.id,
     'package_name', launches.package,
     'process_name', (
-      SELECT name FROM process
-      WHERE upid IN (
-        SELECT upid FROM launch_processes p
-        WHERE p.launch_id = launches.id
-        LIMIT 1
-      )
+      SELECT p.name
+      FROM launch_processes lp
+      JOIN process p USING (upid)
+      WHERE lp.launch_id = launches.id
+      LIMIT 1
     ),
     'process', (
-      SELECT metadata FROM process_metadata
-      WHERE upid IN (
-        SELECT upid FROM launch_processes p
-        WHERE p.launch_id = launches.id
-        LIMIT 1
-      )
+      SELECT m.metadata
+      FROM process_metadata m
+      JOIN launch_processes p USING (upid)
+      WHERE p.launch_id = launches.id
+      LIMIT 1
     ),
     'activities', (
       SELECT RepeatedField(AndroidStartupMetric_Activity(
@@ -261,9 +290,8 @@
         'method', (SELECT STR_SPLIT(s.slice_name, ':', 0)),
         'ts_method_start', s.slice_ts
       ))
-      FROM main_process_slice_unaggregated s
+      FROM activity_names_materialized s
       WHERE s.launch_id = launches.id
-      AND (slice_name LIKE 'performResume:%' OR slice_name LIKE 'performCreate:%')
     ),
     'zygote_new_process', EXISTS(SELECT TRUE FROM zygote_forks_by_id WHERE id = launches.id),
     'activity_hosting_process_count', (
@@ -422,21 +450,18 @@
         WHERE s.launch_id = launches.id AND name = 'VerifyClass'
       ),
       'jit_compiled_methods', (
-        SELECT SUM(1)
-        FROM main_process_slice_unaggregated
-        WHERE slice_name LIKE 'JIT compiling%'
-          AND thread_name = 'Jit thread pool'
+        SELECT count
+        FROM jit_compiled_methods_materialized s
+        WHERE s.launch_id = launches.id
       ),
       'time_jit_thread_pool_on_cpu', (
         SELECT
-        NULL_IF_EMPTY(AndroidStartupMetric_Slice(
-          'dur_ns', SUM(dur),
-          'dur_ms', SUM(dur) / 1e6))
-        FROM launch_threads_cpu
-        WHERE
-          launch_id = launches.id
-          AND thread_name = 'Jit thread pool'
-          AND state = 'Running'
+          NULL_IF_EMPTY(AndroidStartupMetric_Slice(
+            'dur_ns', sum_dur,
+            'dur_ms', sum_dur / 1e6
+          ))
+        FROM launch_threads_cpu_materialized
+        WHERE launch_id = launches.id
       ),
       'time_gc_total', (
         SELECT slice_proto
@@ -446,11 +471,11 @@
       'time_gc_on_cpu', (
         SELECT
           NULL_IF_EMPTY(AndroidStartupMetric_Slice(
-            'dur_ns', SUM(dur),
-            'dur_ms', SUM(dur) / 1e6
+            'dur_ns', sum_dur,
+            'dur_ms', sum_dur / 1e6
           ))
-        FROM gc_slices_by_state
-        WHERE launch_id = launches.id AND state = 'Running'
+        FROM gc_slices_by_state_materialized
+        WHERE launch_id = launches.id
       )
     ),
     'hsc', (
diff --git a/src/tracing/console_interceptor.cc b/src/tracing/console_interceptor.cc
index c3b0e1c..99ce956 100644
--- a/src/tracing/console_interceptor.cc
+++ b/src/tracing/console_interceptor.cc
@@ -317,7 +317,7 @@
     va_start(args, format);
     written = vsnprintf(&tls.message_buffer[tls.buffer_pos],
                         static_cast<size_t>(remaining), format, args);
-    PERFETTO_DCHECK(written > 0);
+    PERFETTO_DCHECK(written >= 0);
     va_end(args);
   }
 
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 77f83d0..ea17dd4 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -441,7 +441,8 @@
 std::unique_ptr<TracingService::ConsumerEndpoint>
 TracingServiceImpl::ConnectConsumer(Consumer* consumer, uid_t uid) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
-  PERFETTO_DLOG("Consumer %p connected", reinterpret_cast<void*>(consumer));
+  PERFETTO_DLOG("Consumer %p connected from UID %" PRIu64,
+                reinterpret_cast<void*>(consumer), static_cast<uint64_t>(uid));
   std::unique_ptr<ConsumerEndpointImpl> endpoint(
       new ConsumerEndpointImpl(this, task_runner_, consumer, uid));
   auto it_and_inserted = consumers_.emplace(endpoint.get());
@@ -2401,8 +2402,21 @@
   ds_config.set_stop_timeout_ms(tracing_session->data_source_stop_timeout_ms());
   ds_config.set_enable_extra_guardrails(
       tracing_session->config.enable_extra_guardrails());
-  if (tracing_session->consumer_uid == 1066 /* AID_STATSD */) {
-    ds_config.set_session_initiator(DataSourceConfig::SESSION_INITIATOR_STATSD);
+  if (tracing_session->consumer_uid == 1066 /* AID_STATSD */ &&
+      tracing_session->config.statsd_metadata().triggering_config_uid() !=
+          2000 /* AID_SHELL */
+      && tracing_session->config.statsd_metadata().triggering_config_uid() !=
+             0 /* AID_ROOT */) {
+    // StatsD can be triggered either by shell, root or an app that has DUMP and
+    // USAGE_STATS permission. When triggered by shell or root, we do not want
+    // to consider the trace a trusted system trace, as it was initiated by the
+    // user. Otherwise, it has to come from an app with DUMP and
+    // PACKAGE_USAGE_STATS, which has to be preinstalled and trusted by the
+    // system.
+    // Check for shell / root: https://bit.ly/3b7oZNi
+    // Check for DUMP or PACKAGE_USAGE_STATS: https://bit.ly/3ep0NrR
+    ds_config.set_session_initiator(
+        DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM);
   } else {
     // Unset in case the consumer set it.
     // We need to be able to trust this field.
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 280a990..1fb5ae8 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -58,6 +58,58 @@
 
 namespace {
 
+// A task runner which prevents calls to DataSource::Trace() while an operation
+// is in progress. Used to guard against unexpected re-entrancy where the
+// user-provided task runner implementation tries to enter a trace point under
+// the hood.
+class NonReentrantTaskRunner : public base::TaskRunner {
+ public:
+  NonReentrantTaskRunner(TracingMuxer* muxer,
+                         std::unique_ptr<base::TaskRunner> task_runner)
+      : muxer_(muxer), task_runner_(std::move(task_runner)) {}
+
+  // base::TaskRunner implementation.
+  void PostTask(std::function<void()> task) override {
+    CallWithGuard([&] { task_runner_->PostTask(std::move(task)); });
+  }
+
+  void PostDelayedTask(std::function<void()> task, uint32_t delay_ms) override {
+    CallWithGuard(
+        [&] { task_runner_->PostDelayedTask(std::move(task), delay_ms); });
+  }
+
+  void AddFileDescriptorWatch(base::PlatformHandle fd,
+                              std::function<void()> callback) override {
+    CallWithGuard(
+        [&] { task_runner_->AddFileDescriptorWatch(fd, std::move(callback)); });
+  }
+
+  void RemoveFileDescriptorWatch(base::PlatformHandle fd) override {
+    CallWithGuard([&] { task_runner_->RemoveFileDescriptorWatch(fd); });
+  }
+
+  bool RunsTasksOnCurrentThread() const override {
+    bool result;
+    CallWithGuard([&] { result = task_runner_->RunsTasksOnCurrentThread(); });
+    return result;
+  }
+
+ private:
+  template <typename T>
+  void CallWithGuard(T lambda) const {
+    auto* root_tls = muxer_->GetOrCreateTracingTLS();
+    if (PERFETTO_UNLIKELY(root_tls->is_in_trace_point)) {
+      lambda();
+      return;
+    }
+    ScopedReentrancyAnnotator scoped_annotator(*root_tls);
+    lambda();
+  }
+
+  TracingMuxer* const muxer_;
+  std::unique_ptr<base::TaskRunner> task_runner_;
+};
+
 class StopArgsImpl : public DataSourceBase::StopArgs {
  public:
   std::function<void()> HandleStopAsynchronously() const override {
@@ -642,9 +694,11 @@
     : TracingMuxer(args.platform ? args.platform
                                  : Platform::GetDefaultPlatform()) {
   PERFETTO_DETACH_FROM_THREAD(thread_checker_);
+  instance_ = this;
 
   // Create the thread where muxer, producers and service will live.
-  task_runner_ = platform_->CreateTaskRunner({});
+  task_runner_.reset(
+      new NonReentrantTaskRunner(this, platform_->CreateTaskRunner({})));
 
   // Run the initializer on that thread.
   task_runner_->PostTask([this, args] { Initialize(args); });
@@ -1561,7 +1615,7 @@
 void TracingMuxerImpl::InitializeInstance(const TracingInitArgs& args) {
   if (instance_ != TracingMuxerFake::Get())
     PERFETTO_FATAL("Tracing already initialized");
-  instance_ = new TracingMuxerImpl(args);
+  new TracingMuxerImpl(args);
 }
 
 TracingMuxer::~TracingMuxer() = default;
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index cffab69..736c312 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -95,6 +95,9 @@
 const Track TrackEventInternal::kDefaultTrack{};
 
 // static
+std::atomic<int> TrackEventInternal::session_count_{};
+
+// static
 bool TrackEventInternal::Initialize(
     const TrackEventCategoryRegistry& registry,
     bool (*register_data_source)(const DataSourceDescriptor&)) {
@@ -172,6 +175,7 @@
 
 // static
 void TrackEventInternal::OnStart(const DataSourceBase::StartArgs& args) {
+  session_count_.fetch_add(1);
   ForEachObserver([&](TrackEventSessionObserver*& o) {
     if (o)
       o->OnStart(args);
@@ -219,6 +223,14 @@
         }
         break;
       }
+      // No match? Must be a dynamic category.
+      DynamicCategory dyn_category(std::string(member_name, name_size));
+      Category ref_category{Category::FromDynamicCategory(dyn_category)};
+      if (IsCategoryEnabled(registry, config, ref_category)) {
+        result = true;
+        // Break ForEachGroupMember() loop.
+        return false;
+      }
       // No match found => keep iterating.
       return true;
     });
@@ -291,6 +303,11 @@
 }
 
 // static
+int TrackEventInternal::GetSessionCount() {
+  return session_count_.load();
+}
+
+// static
 void TrackEventInternal::ResetIncrementalState(TraceWriterBase* trace_writer,
                                                uint64_t timestamp) {
   auto default_track = ThreadTrack::Current();
diff --git a/src/tracing/internal/track_event_interned_fields.cc b/src/tracing/internal/track_event_interned_fields.cc
index a9c5f0e..804aeb4 100644
--- a/src/tracing/internal/track_event_interned_fields.cc
+++ b/src/tracing/internal/track_event_interned_fields.cc
@@ -21,9 +21,38 @@
 
 InternedEventCategory::~InternedEventCategory() = default;
 
+// static
+void InternedEventCategory::Add(protos::pbzero::InternedData* interned_data,
+                                size_t iid,
+                                const char* value,
+                                size_t length) {
+  auto category = interned_data->add_event_categories();
+  category->set_iid(iid);
+  category->set_name(value, length);
+}
+
 InternedEventName::~InternedEventName() = default;
 
+// static
+void InternedEventName::Add(protos::pbzero::InternedData* interned_data,
+                            size_t iid,
+                            const char* value) {
+  auto name = interned_data->add_event_names();
+  name->set_iid(iid);
+  name->set_name(value);
+}
+
 InternedDebugAnnotationName::~InternedDebugAnnotationName() = default;
 
+// static
+void InternedDebugAnnotationName::Add(
+    protos::pbzero::InternedData* interned_data,
+    size_t iid,
+    const char* value) {
+  auto name = interned_data->add_debug_annotation_names();
+  name->set_iid(iid);
+  name->set_name(value);
+}
+
 }  // namespace internal
 }  // namespace perfetto
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 84c0d7c..061e2fe 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -2457,7 +2457,8 @@
         slices,
         ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
                     "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
-                    "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent",
+                    "B:red,green,blue,yellow.MultiNone", "B:test.TagEvent",
+                    "B:$dynamic,$foo.DynamicGroupFooEvent",
                     "B:$dynamic,$bar.DynamicGroupBarEvent"));
   }
 
@@ -2472,6 +2473,16 @@
                                     "B:$dynamic,$foo.DynamicGroupFooEvent"));
   }
 
+  // Enable exactly one dynamic category.
+  {
+    perfetto::protos::gen::TrackEventConfig te_cfg;
+    te_cfg.add_disabled_categories("*");
+    te_cfg.add_enabled_categories("dynamic");
+    auto slices = check_config(te_cfg);
+    EXPECT_THAT(slices, ElementsAre("B:$dynamic,$foo.DynamicGroupFooEvent",
+                                    "B:$dynamic,$bar.DynamicGroupBarEvent"));
+  }
+
   // Enable two categories.
   {
     perfetto::protos::gen::TrackEventConfig te_cfg;
@@ -2497,7 +2508,8 @@
         slices,
         ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
                     "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
-                    "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent",
+                    "B:red,green,blue,yellow.MultiNone", "B:test.TagEvent",
+                    "B:$dynamic,$foo.DynamicGroupFooEvent",
                     "B:$dynamic,$bar.DynamicGroupBarEvent"));
   }
 
@@ -2539,14 +2551,15 @@
     te_cfg.add_enabled_tags("slow");
     te_cfg.add_enabled_tags("debug");
     auto slices = check_config(te_cfg);
-    EXPECT_THAT(slices,
-                ElementsAre("B:foo.FooEvent", "B:bar.BarEvent",
-                            "B:foo,bar.MultiFooBar", "B:baz,bar,quux.MultiBar",
-                            "B:red,green,blue,foo.MultiFoo", "B:cat.SlowEvent",
-                            "B:cat.verbose.DebugEvent", "B:test.TagEvent",
-                            "B:disabled-by-default-cat.SlowDisabledEvent",
-                            "B:$dynamic,$foo.DynamicGroupFooEvent",
-                            "B:$dynamic,$bar.DynamicGroupBarEvent"));
+    EXPECT_THAT(
+        slices,
+        ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+                    "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+                    "B:red,green,blue,yellow.MultiNone", "B:cat.SlowEvent",
+                    "B:cat.verbose.DebugEvent", "B:test.TagEvent",
+                    "B:disabled-by-default-cat.SlowDisabledEvent",
+                    "B:$dynamic,$foo.DynamicGroupFooEvent",
+                    "B:$dynamic,$bar.DynamicGroupBarEvent"));
   }
 }
 
@@ -3246,10 +3259,19 @@
 }
 
 TEST_P(PerfettoApiTest, LegacyTraceEvents) {
+  auto is_new_session = [] {
+    bool result;
+    TRACE_EVENT_IS_NEW_TRACE(&result);
+    return result;
+  };
+
   // Create a new trace session.
+  EXPECT_FALSE(is_new_session());
   auto* tracing_session =
       NewTraceWithCategories({"cat", TRACE_DISABLED_BY_DEFAULT("cat")});
   tracing_session->get()->StartBlocking();
+  EXPECT_TRUE(is_new_session());
+  EXPECT_FALSE(is_new_session());
 
   // Basic events.
   TRACE_EVENT_INSTANT0("cat", "LegacyEvent", TRACE_EVENT_SCOPE_GLOBAL);
diff --git a/src/tracing/track_event_state_tracker.cc b/src/tracing/track_event_state_tracker.cc
index 4385f62..7cb1b09 100644
--- a/src/tracing/track_event_state_tracker.cc
+++ b/src/tracing/track_event_state_tracker.cc
@@ -143,8 +143,10 @@
   parsed_event.name_hash = name_hash;
   delegate.OnTrackEvent(*track, parsed_event);
 
-  if (track_event.type() == protos::pbzero::TrackEvent::TYPE_SLICE_END)
+  if (track_event.type() == protos::pbzero::TrackEvent::TYPE_SLICE_END &&
+      !track->stack.empty()) {
     track->stack.pop_back();
+  }
 }
 
 // static
diff --git a/src/tracing/virtual_destructors.cc b/src/tracing/virtual_destructors.cc
index d548c8e..d473551 100644
--- a/src/tracing/virtual_destructors.cc
+++ b/src/tracing/virtual_destructors.cc
@@ -26,7 +26,10 @@
 namespace perfetto {
 namespace internal {
 
-TracingTLS::~TracingTLS() = default;
+TracingTLS::~TracingTLS() {
+  // Avoid entering trace points while the thread is being torn down.
+  is_in_trace_point = true;
+}
 
 }  // namespace internal
 
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 013b9a8..e982bc9 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -108,13 +108,15 @@
 function rank(ts: TrackState): number[] {
   const hpRank = rankIndex(ts.kind, highPriorityTrackOrder);
   const lpRank = rankIndex(ts.kind, lowPriorityTrackOrder);
-  return [hpRank, ts.trackKindPriority.valueOf(), lpRank];
+  // TODO(hjd): Create sortBy object on TrackState to avoid this cast.
+  const tid = (ts.config as {tid?: number}).tid || 0;
+  return [hpRank, ts.trackKindPriority.valueOf(), lpRank, tid];
 }
 
 function rankIndex<T>(element: T, array: T[]): number {
   const index = array.indexOf(element);
-  if (index === -1) return 0;
-  return array.length - index;
+  if (index === -1) return array.length;
+  return index;
 }
 
 export const StateActions = {
@@ -278,7 +280,7 @@
         const aRank = rank(state.tracks[a]);
         const bRank = rank(state.tracks[b]);
         for (let i = 0; i < aRank.length; i++) {
-          if (aRank[i] !== bRank[i]) return bRank[i] - aRank[i];
+          if (aRank[i] !== bRank[i]) return aRank[i] - bRank[i];
         }
 
         const aName = state.tracks[a].name.toLocaleLowerCase();
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 87bf309..8cf6acf 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -19,6 +19,7 @@
 import {
   PROCESS_SCHEDULING_TRACK_KIND
 } from '../tracks/process_scheduling/common';
+import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
 
 import {StateActions} from './actions';
 import {
@@ -34,7 +35,8 @@
   kind?: string,
   trackGroup?: string,
   trackKindPriority?: TrackKindPriority,
-  name?: string
+  name?: string,
+  tid?: string
 }): State {
   return produce(state, draft => {
     StateActions.addTrack(draft, {
@@ -46,7 +48,7 @@
           TrackKindPriority.ORDINARY :
           args.trackKindPriority,
       trackGroup: args.trackGroup || SCROLLING_TRACK_GROUP,
-      config: {}
+      config: {tid: args.tid || '0'}
     });
   });
 }
@@ -392,3 +394,35 @@
   expect(after.trackGroups['g'].tracks)
       .toEqual(['a', 'b', 'b', 'c', 'd', 'e', 'f', 'g']);
 });
+
+test('sortTracksByTidThenName', () => {
+  let state = createEmptyState();
+  state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'a'});
+  state = fakeTrack(state, {
+    id: 'a',
+    kind: SLICE_TRACK_KIND,
+    trackGroup: 'g',
+    name: 'aaa',
+    tid: '1'
+  });
+  state = fakeTrack(state, {
+    id: 'b',
+    kind: SLICE_TRACK_KIND,
+    trackGroup: 'g',
+    name: 'bbb',
+    tid: '2'
+  });
+  state = fakeTrack(state, {
+    id: 'c',
+    kind: THREAD_STATE_TRACK_KIND,
+    trackGroup: 'g',
+    name: 'ccc',
+    tid: '1'
+  });
+
+  const after = produce(state, draft => {
+    StateActions.sortThreadTracks(draft, {});
+  });
+
+  expect(after.trackGroups['g'].tracks).toEqual(['a', 'a', 'c', 'b']);
+});
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index c270010..3d387d4 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -18,7 +18,7 @@
   RawQueryArgs,
   RawQueryResult
 } from './protos';
-import {slowlyCountRows} from './query_iterator';
+import {iter, NUM_NULL, slowlyCountRows, STR} from './query_iterator';
 import {TimeSpan} from './time';
 
 export interface LoadingTracker {
@@ -177,4 +177,25 @@
     const res = (await this.queryOneRow(query));
     return new TimeSpan(res[0] / 1e9, res[1] / 1e9);
   }
+
+  async getTracingMetadataTimeBounds(): Promise<TimeSpan> {
+    const query = await this.query(`select name, int_value from metadata
+         where name = 'tracing_started_ns' or name = 'tracing_disabled_ns'
+         or name = 'all_data_source_started_ns'`);
+    let startBound = -Infinity;
+    let endBound = Infinity;
+    const it = iter({'name': STR, 'int_value': NUM_NULL}, query);
+    for (; it.valid(); it.next()) {
+      const columnName = it.row.name;
+      const timestamp = it.row.int_value;
+      if (timestamp === null) continue;
+      if (columnName === 'tracing_disabled_ns') {
+        endBound = Math.min(endBound, timestamp / 1e9);
+      } else {
+        startBound = Math.max(startBound, timestamp / 1e9);
+      }
+    }
+
+    return new TimeSpan(startBound, endBound);
+  }
 }
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index a194042..71c3f2f 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -60,10 +60,10 @@
 export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM';
 
 export enum TrackKindPriority {
-  'MAIN_THREAD' = 3,
-  'RENDER_THREAD' = 2,
-  'GPU_COMPLETION' = 1,
-  'ORDINARY' = 0
+  'MAIN_THREAD' = 0,
+  'RENDER_THREAD' = 1,
+  'GPU_COMPLETION' = 2,
+  'ORDINARY' = 3
 }
 
 export type HeapProfileFlamegraphViewingOption =
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 2ef2012..65558b2 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -298,11 +298,26 @@
       Actions.navigate({route: '/viewer'}),
     ];
 
+    let visibleStartSec = startSec;
+    let visibleEndSec = endSec;
+    const mdTime = await this.engine.getTracingMetadataTimeBounds();
+    // make sure the bounds hold
+    if (Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S) <
+        Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) {
+      visibleStartSec =
+          Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S);
+      visibleEndSec = Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S);
+    }
+
     // We don't know the resolution at this point. However this will be
     // replaced in 50ms so a guess is fine.
-    const resolution = (traceTime.end - traceTime.start) / 1000;
-    actions.push(Actions.setVisibleTraceTime(
-        {...traceTimeState, lastUpdate: Date.now() / 1000, resolution}));
+    const resolution = (visibleStartSec - visibleEndSec) / 1000;
+    actions.push(Actions.setVisibleTraceTime({
+      startSec: visibleStartSec,
+      endSec: visibleEndSec,
+      lastUpdate: Date.now() / 1000,
+      resolution
+    }));
 
     globals.dispatchMultiple(actions);
 
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index ff27d17..75ee194 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -16,7 +16,7 @@
 import {Engine} from '../common/engine';
 import {Registry} from '../common/registry';
 import {TraceTime, TrackState} from '../common/state';
-import {toNs} from '../common/time';
+import {fromNs, toNs} from '../common/time';
 import {LIMIT, TrackData} from '../common/track_data';
 
 import {Controller} from './controller';
@@ -252,10 +252,18 @@
         promise
             .then(() => {
               this.isSetup = true;
+              let resolution = visibleState.resolution;
+              // TODO(hjd): We shouldn't have to be so defensive here.
+              if (Math.log2(toNs(resolution)) % 1 !== 0) {
+                // resolution is in pixels per second so 1000 means
+                // 1px = 1ms.
+                resolution =
+                    fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000)))));
+              }
               return this.onBoundsChange(
                   visibleState.startSec - dur,
                   visibleState.endSec + dur,
-                  visibleState.resolution);
+                  resolution);
             })
             .then(data => {
               this.publish(data);
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index e320fd8..35e6ddb 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -48,6 +48,10 @@
 import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
 import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
 
+const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
+const MEM_DMA = 'mem.dma_buffer';
+const MEM_ION = 'mem.ion';
+
 export async function decideTracks(
     engineId: string, engine: Engine): Promise<DeferredAction[]> {
   return (new TrackDecider(engineId, engine)).decideTracks();
@@ -296,6 +300,50 @@
     }
   }
 
+  async groupGlobalIonTracks(): Promise<void> {
+    const ionTracks: AddTrackArgs[] = [];
+    for (const track of this.tracksToAdd) {
+      const isIon = track.name.startsWith(MEM_ION);
+      const isDmaHeapCounter = track.name === MEM_DMA_COUNTER_NAME;
+      const isDmaBuffferSlices = track.name === MEM_DMA;
+      if (isIon || isDmaHeapCounter || isDmaBuffferSlices) {
+        ionTracks.push(track);
+      }
+    }
+
+    if (ionTracks.length === 0) {
+      return;
+    }
+
+    const id = uuidv4();
+    const summaryTrackId = uuidv4();
+    let foundSummary = false;
+
+    for (const track of ionTracks) {
+      if (!foundSummary &&
+          [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name)) {
+        foundSummary = true;
+        track.id = summaryTrackId;
+        track.trackGroup = undefined;
+      } else {
+        track.trackGroup = id;
+      }
+    }
+
+    if (!foundSummary) {
+      return;
+    }
+
+    const addGroup = Actions.addTrackGroup({
+      engineId: this.engineId,
+      summaryTrackId,
+      name: MEM_DMA_COUNTER_NAME,
+      id,
+      collapsed: true,
+    });
+    this.addTrackGroupActions.push(addGroup);
+  }
+
   async addLogsTrack(): Promise<void> {
     const logCount =
         await this.engine.query(`select count(1) from android_logs`);
@@ -410,7 +458,7 @@
         trackGroup: uuid,
         trackKindPriority:
             TrackDecider.inferTrackKindPriority(threadName, tid, pid),
-        config: {utid}
+        config: {utid, tid}
       });
     }
   }
@@ -506,12 +554,7 @@
         name,
         trackKindPriority: TrackDecider.inferTrackKindPriority(threadName),
         trackGroup: uuid,
-        config: {
-          name,
-          trackId,
-          startTs,
-          endTs,
-        }
+        config: {name, trackId, startTs, endTs, tid}
       });
     }
   }
@@ -760,10 +803,7 @@
         name,
         trackGroup: uuid,
         trackKindPriority,
-        config: {
-          trackId,
-          maxDepth,
-        }
+        config: {trackId, maxDepth, tid}
       });
     }
   }
@@ -973,7 +1013,7 @@
           kind,
           trackKindPriority: TrackDecider.inferTrackKindPriority(threadName),
           name: `${upid === null ? tid : pid} summary`,
-          config: {pidForColor, upid, utid},
+          config: {pidForColor, upid, utid, tid},
         });
 
         const name = TrackDecider.getTrackName(
@@ -998,11 +1038,12 @@
     await this.addGlobalAsyncTracks();
     await this.addGpuFreqTracks();
     await this.addGlobalCounterTracks();
+    await this.groupGlobalIonTracks();
 
     // Create the per-process track groups. Note that this won't necessarily
     // create a track per process. If a process has been completely idle and has
     // no sched events, no track group will be emitted.
-    // Will populate this.addTrackGroupActions().
+    // Will populate this.addTrackGroupActions
     await this.addProcessTrackGroups();
 
     await this.addProcessHeapProfileTracks();
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 0c7474c..dd1e858 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -399,8 +399,23 @@
     // window span). Therefore, zooming out by six levels is 1.1^6 ~= 2.
     // Similarily, zooming in six levels is 0.9^6 ~= 0.5.
     const pxToSec = this.frontendLocalState.timeScale.deltaPxToDuration(1);
+    // TODO(b/186265930): Remove once fixed:
+    if (!isFinite(pxToSec)) {
+      // Resolution is in pixels per second so 1000 means 1px = 1ms.
+      console.error(`b/186265930: Bad pxToSec suppressed ${pxToSec}`);
+      return fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000)))));
+    }
     const pxToNs = Math.max(toNs(pxToSec), 1);
-    return fromNs(Math.pow(2, Math.floor(Math.log2(pxToNs))));
+    const resolution = fromNs(Math.pow(2, Math.floor(Math.log2(pxToNs))));
+    const log2 = Math.log2(toNs(resolution));
+    if (log2 % 1 !== 0) {
+      throw new Error(`Resolution should be a power of two.
+        pxToSec: ${pxToSec},
+        pxToNs: ${pxToNs},
+        resolution: ${resolution},
+        log2: ${Math.log2(toNs(resolution))}`);
+    }
+    return resolution;
   }
 
   makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') {
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 017deea..55dc47a 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -94,6 +94,8 @@
 
     for (const note of Object.values(globals.state.notes)) {
       const timestamp = getStartTimestamp(note);
+      // TODO(hjd): We should still render area selection marks in viewport is
+      // *within* the area (e.g. both lhs and rhs are out of bounds).
       if ((note.noteType !== 'AREA' && !timeScale.timeInBounds(timestamp)) ||
           (note.noteType === 'AREA' &&
            !timeScale.timeInBounds(globals.state.areas[note.areaId].endSec) &&
@@ -182,12 +184,12 @@
     ctx.stroke();
 
     // Start line after track shell section, join triangles.
-    const startDraw =
-        Math.max(
-            x,
-            globals.frontendLocalState.timeScale.startPx + TRACK_SHELL_WIDTH) -
-        1;
-    ctx.fillRect(startDraw, topOffset - 1, xEnd - startDraw + 1, 1);
+    const startDraw = Math.max(
+        x, globals.frontendLocalState.timeScale.startPx + TRACK_SHELL_WIDTH);
+    ctx.beginPath();
+    ctx.moveTo(startDraw, topOffset);
+    ctx.lineTo(xEnd, topOffset);
+    ctx.stroke();
   }
 
   private drawFlag(
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index f493630..182a66b 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -56,44 +56,43 @@
 const POLL_INTERVAL_MS = [250, 500, 1000, 2500, 5000, 30000, 60000];
 
 const ATRACE_CATEGORIES = new Map<string, string>();
+ATRACE_CATEGORIES.set('adb', 'ADB');
+ATRACE_CATEGORIES.set('aidl', 'AIDL calls');
+ATRACE_CATEGORIES.set('am', 'Activity Manager');
+ATRACE_CATEGORIES.set('audio', 'Audio');
+ATRACE_CATEGORIES.set('binder_driver', 'Binder Kernel driver');
+ATRACE_CATEGORIES.set('binder_lock', 'Binder global lock trace');
+ATRACE_CATEGORIES.set('bionic', 'Bionic C library');
+ATRACE_CATEGORIES.set('camera', 'Camera');
+ATRACE_CATEGORIES.set('dalvik', 'ART & Dalvik');
+ATRACE_CATEGORIES.set('database', 'Database');
 ATRACE_CATEGORIES.set('gfx', 'Graphics');
+ATRACE_CATEGORIES.set('hal', 'Hardware Modules');
 ATRACE_CATEGORIES.set('input', 'Input');
+ATRACE_CATEGORIES.set('network', 'Network');
+ATRACE_CATEGORIES.set('nnapi', 'Neural Network API');
+ATRACE_CATEGORIES.set('pm', 'Package Manager');
+ATRACE_CATEGORIES.set('power', 'Power Management');
+ATRACE_CATEGORIES.set('res', 'Resource Loading');
+ATRACE_CATEGORIES.set('rro', 'Resource Overlay');
+ATRACE_CATEGORIES.set('rs', 'RenderScript');
+ATRACE_CATEGORIES.set('sm', 'Sync Manager');
+ATRACE_CATEGORIES.set('ss', 'System Server');
+ATRACE_CATEGORIES.set('vibrator', 'Vibrator');
+ATRACE_CATEGORIES.set('video', 'Video');
 ATRACE_CATEGORIES.set('view', 'View System');
 ATRACE_CATEGORIES.set('webview', 'WebView');
 ATRACE_CATEGORIES.set('wm', 'Window Manager');
-ATRACE_CATEGORIES.set('am', 'Activity Manager');
-ATRACE_CATEGORIES.set('sm', 'Sync Manager');
-ATRACE_CATEGORIES.set('audio', 'Audio');
-ATRACE_CATEGORIES.set('video', 'Video');
-ATRACE_CATEGORIES.set('camera', 'Camera');
-ATRACE_CATEGORIES.set('hal', 'Hardware Modules');
-ATRACE_CATEGORIES.set('res', 'Resource Loading');
-ATRACE_CATEGORIES.set('dalvik', 'ART & Dalvik');
-ATRACE_CATEGORIES.set('rs', 'RenderScript');
-ATRACE_CATEGORIES.set('bionic', 'Bionic C library');
-ATRACE_CATEGORIES.set('gfx', 'Graphics');
-ATRACE_CATEGORIES.set('power', 'Power Management');
-ATRACE_CATEGORIES.set('pm', 'Package Manager');
-ATRACE_CATEGORIES.set('ss', 'System Server');
-ATRACE_CATEGORIES.set('database', 'Database');
-ATRACE_CATEGORIES.set('network', 'Network');
-ATRACE_CATEGORIES.set('adb', 'ADB');
-ATRACE_CATEGORIES.set('vibrator', 'Vibrator');
-ATRACE_CATEGORIES.set('aidl', 'AIDL calls');
-ATRACE_CATEGORIES.set('nnapi', 'Neural Network API');
-ATRACE_CATEGORIES.set('rro', 'Resource Overlay');
-ATRACE_CATEGORIES.set('binder_driver', 'Binder Kernel driver');
-ATRACE_CATEGORIES.set('binder_lock', 'Binder global lock trace');
 
 const LOG_BUFFERS = new Map<string, string>();
-LOG_BUFFERS.set('LID_DEFAULT', 'Main');
-LOG_BUFFERS.set('LID_RADIO', 'Radio');
-LOG_BUFFERS.set('LID_EVENTS', 'Binary events');
-LOG_BUFFERS.set('LID_SYSTEM', 'System');
 LOG_BUFFERS.set('LID_CRASH', 'Crash');
-LOG_BUFFERS.set('LID_STATS', 'Stats');
-LOG_BUFFERS.set('LID_SECURITY', 'Security');
+LOG_BUFFERS.set('LID_DEFAULT', 'Main');
+LOG_BUFFERS.set('LID_EVENTS', 'Binary events');
 LOG_BUFFERS.set('LID_KERNEL', 'Kernel');
+LOG_BUFFERS.set('LID_RADIO', 'Radio');
+LOG_BUFFERS.set('LID_SECURITY', 'Security');
+LOG_BUFFERS.set('LID_STATS', 'Stats');
+LOG_BUFFERS.set('LID_SYSTEM', 'System');
 
 const FTRACE_CATEGORIES = new Map<string, string>();
 FTRACE_CATEGORIES.set('binder/*', 'binder');
diff --git a/ui/src/frontend/record_widgets.ts b/ui/src/frontend/record_widgets.ts
index fd919a8..b107080 100644
--- a/ui/src/frontend/record_widgets.ts
+++ b/ui/src/frontend/record_widgets.ts
@@ -251,7 +251,9 @@
     const options: m.Children = [];
     const selItems = attrs.get(globals.state.recordConfig);
     let numSelected = 0;
-    for (const [key, label] of attrs.options) {
+    const entries = [...attrs.options.entries()];
+    entries.sort((a, b) => a[1].localeCompare(b[1]));
+    for (const [key, label] of entries) {
       const opts = {value: key, selected: false};
       if (selItems.includes(key)) {
         opts.selected = true;
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index d46074d..b9f8d0f 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -852,9 +852,12 @@
 }
 
 function createTraceLink(title: string, url: string) {
+  if (url === '') {
+    return m('a.trace-file-name', title);
+  }
   const linkProps = {
     href: url,
-    title: url !== '' ? 'Click to copy the URL' : '',
+    title: 'Click to copy the URL',
     target: '_blank',
     onclick: (e: Event) => {
       e.preventDefault();
diff --git a/ui/src/tracks/async_slices/controller.ts b/ui/src/tracks/async_slices/controller.ts
index 96bd6e4..50a074a 100644
--- a/ui/src/tracks/async_slices/controller.ts
+++ b/ui/src/tracks/async_slices/controller.ts
@@ -38,7 +38,7 @@
 
     if (this.maxDurNs === 0) {
       const maxDurResult = await this.query(`
-        select max(dur)
+        select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
         from experimental_slice_layout
         where filter_track_ids = '${this.config.trackIds.join(',')}'
       `);
@@ -51,7 +51,7 @@
       SELECT
         (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
         ts,
-        max(dur) as dur,
+        max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
         layout_depth,
         name,
         id,