Merge "Use span join for jit cpu time"
diff --git a/debian/control b/debian/control
index 5467a93..28fcef2 100644
--- a/debian/control
+++ b/debian/control
@@ -3,14 +3,14 @@
 Priority: optional
 Maintainer: Sami Kyostila <skyostil@google.com>
 Build-Depends: debhelper (>= 10),
-  ca-certificates,
-  curl,
   generate-ninja,
   git,
   libprotoc-dev,
   ninja-build,
   protobuf-compiler,
-  python3
+  python3,
+  zlib1g-dev,
+  zlib1g
 Standards-Version: 3.9.8
 Homepage: https://perfetto.dev
 Vcs-Git: https://android.googlesource.com/platform/external/perfetto/
@@ -18,7 +18,7 @@
 
 Package: perfetto
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}
+Depends: ${shlibs:Depends}, ${misc:Depends}, zlib1g
 Description: Performance instrumentation and logging framework
  Perfetto is a performance instrumentation and logging framework for POSIX
  systems.
diff --git a/debian/postinst b/debian/postinst
index c32e828..7d7e266 100755
--- a/debian/postinst
+++ b/debian/postinst
@@ -1,5 +1,5 @@
 #!/bin/sh
 set -e
-adduser --quiet --system --no-create-home --group traced
+adduser --home /nonexistent --quiet --system --no-create-home --group traced
 addgroup --quiet --system traced-consumer
 usermod -a -G traced-consumer traced
diff --git a/debian/rules b/debian/rules
index e6f1de4..a5d6199 100755
--- a/debian/rules
+++ b/debian/rules
@@ -3,11 +3,13 @@
 	dh $@
 
 override_dh_auto_configure:
-	tools/install-build-deps --no-toolchain
 	gn gen out/release --args="is_debug=false use_custom_libcxx=false\
 	  is_hermetic_clang=false is_system_compiler=true is_clang=false\
 	  skip_buildtools_check=true enable_perfetto_integration_tests=false\
-	  enable_perfetto_unittests=false perfetto_use_system_protobuf=true"
+	  enable_perfetto_unittests=false perfetto_use_system_protobuf=true\
+	  perfetto_use_system_zlib=true perfetto_enable_git_rev_version_header=false\
+	  extra_cflags=\"${CFLAGS}\" extra_cxxflags=\"${CXXFLAGS}\"\
+	  extra_ldflags=\"${CXXFLAGS}\" cc=\"${CC}\" cxx=\"${CXX}\""
 
 override_dh_auto_build:
 	ninja -C out/release perfetto traced traced_probes
diff --git a/docs/toc.md b/docs/toc.md
index bd3bbeb..4b1c6a1 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -38,6 +38,7 @@
 
 * [Trace visualization](#)
   * [Perfetto UI](visualization/perfetto-ui.md)
+  * [Visualising large traces](visualization/large-traces.md)
 
 * [Core concepts](#)
   * [Trace configuration](concepts/config.md)
diff --git a/docs/visualization/large-traces.md b/docs/visualization/large-traces.md
new file mode 100644
index 0000000..f4e40d5
--- /dev/null
+++ b/docs/visualization/large-traces.md
@@ -0,0 +1,27 @@
+# Visualising large traces
+
+Browsers often limit the amount of memory a site can use.
+This can cause problems when visualising large traces.
+
+## How to visualise large traces using the Perfetto UI
+
+Perfetto UI has support for a mode where the processing of the trace
+is offloaded to a 'server' instance of `trace_processor` running natively on your local machine.
+This server process can take full advantage of the RAM of your machine as well as running at full native (rather than WASM) performance.
+
+```
+curl -LO https://get.perfetto.dev/trace_processor
+chmod +x ./trace_processor
+trace_processor --httpd /path/to/trace.pftrace
+# Navigate to http://ui.perfetto.dev, it will prompt to use the HTTP+RPC interface
+```
+
+## How big is too big?
+
+The exact memory limit can vary by browser, architecture, and OS however 2gb is typical.
+This limit is a limit on the total memory used at runtime, not on the binary size of the trace.
+The `trace_processor` (and hence the UI) representation of a trace at runtime is normally larger than the binary size of that trace.
+This is because the representation is optimized for query performance rather than size.
+The exact inflation factor varies depending on the trace format but can be 2-4x for uncompressed proto traces.
+
+
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 6793fce..a297a84 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -363,10 +363,16 @@
   }
 }
 
+config("system_zlib_config") {
+  libs = [ "z" ]
+}
+
 # Zlib is used both by trace_processor and by perfetto_cmd.
 if (enable_perfetto_zlib) {
   group("zlib") {
-    if (perfetto_root_path == "//") {
+    if (perfetto_use_system_zlib) {
+      public_configs = [ "//gn:system_zlib_config" ]
+    } else if (perfetto_root_path == "//") {
       public_configs = [ "//buildtools:zlib_config" ]
       public_deps = [ "//buildtools:zlib" ]
     } else {
diff --git a/gn/gen_perfetto_version_header.gni b/gn/gen_perfetto_version_header.gni
index 29e02e9..17c879f 100644
--- a/gn/gen_perfetto_version_header.gni
+++ b/gn/gen_perfetto_version_header.gni
@@ -28,7 +28,8 @@
     inputs = [ changelog ]
     outputs = []
     args = []
-    if (perfetto_build_standalone && !is_perfetto_build_generator) {
+    if (perfetto_build_standalone && !is_perfetto_build_generator &&
+        perfetto_enable_git_rev_version_header) {
       inputs += [ "${perfetto_root_path}.git/HEAD" ]
     }
 
@@ -48,5 +49,8 @@
       ]
       outputs += [ invoker.ts_out ]
     }
+    if (!perfetto_enable_git_rev_version_header) {
+      args += [ "--no_git" ]
+    }
   }
 }
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 13831a0..c1ff723 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -222,6 +222,8 @@
 }
 
 declare_args() {
+  perfetto_enable_git_rev_version_header = enable_perfetto_version_gen
+
   # The traced_probes daemon is very Linux-specific, as it depends on ftrace and
   # various /proc interfaces. There is no point making its code platform-neutral
   # as it won't do anything useful on Windows.
@@ -281,6 +283,8 @@
   # Used by CrOS system builds. Uses the system version of protobuf
   # from /usr/include instead of the hermetic one.
   perfetto_use_system_protobuf = false
+
+  perfetto_use_system_zlib = false
 }
 
 if (is_win) {
diff --git a/include/perfetto/ext/base/utils.h b/include/perfetto/ext/base/utils.h
index 411ccee..af39b37 100644
--- a/include/perfetto/ext/base/utils.h
+++ b/include/perfetto/ext/base/utils.h
@@ -47,7 +47,7 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 using uid_t = unsigned int;
-#if !PERFETTO_BUILDFLAG(COMPILER_GCC)
+#if !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
 using pid_t = unsigned int;
 #endif
 #if defined(_WIN64)
@@ -119,6 +119,11 @@
 // geteuid() on POSIX OSes, returns 0 on Windows (See comment in utils.cc).
 uid_t GetCurrentUserId();
 
+// Forks the process.
+// Parent: prints the PID of the child and exit(0).
+// Child: redirects stdio onto /dev/null and chdirs into .
+void Daemonize();
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/tracing/event_context.h b/include/perfetto/tracing/event_context.h
index 9e9523f..21cb788 100644
--- a/include/perfetto/tracing/event_context.h
+++ b/include/perfetto/tracing/event_context.h
@@ -19,6 +19,7 @@
 
 #include "perfetto/protozero/message_handle.h"
 #include "perfetto/tracing/internal/track_event_internal.h"
+#include "perfetto/tracing/traced_proto.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
@@ -64,6 +65,16 @@
     return static_cast<EventType*>(event_);
   }
 
+  // Convert a raw pointer to protozero message to TracedProto which captures
+  // the reference to this EventContext.
+  template <typename MessageType>
+  TracedProto<MessageType> Wrap(MessageType* message) {
+    static_assert(std::is_base_of<protozero::Message, MessageType>::value,
+                  "TracedProto can be used only with protozero messages");
+
+    return TracedProto<MessageType>(message, *this);
+  }
+
  private:
   template <typename, size_t, typename, typename>
   friend class TrackEventInternedDataIndex;
diff --git a/include/perfetto/tracing/internal/write_track_event_args.h b/include/perfetto/tracing/internal/write_track_event_args.h
index 9a68ab5..2f84878 100644
--- a/include/perfetto/tracing/internal/write_track_event_args.h
+++ b/include/perfetto/tracing/internal/write_track_event_args.h
@@ -110,9 +110,8 @@
       "Only fields of TrackEvent (and TrackEvent's extensions) can "
       "be passed to TRACE_EVENT");
   WriteIntoTracedProto(
-      TracedProto<typename FieldMetadataType::message_type>::FromProto(
-          event_ctx.event<typename FieldMetadataType::message_type>(),
-          event_ctx),
+      event_ctx.Wrap(
+          event_ctx.event<typename FieldMetadataType::message_type>()),
       field_name, std::forward<ArgValue>(arg_value));
   WriteTrackEventArgs(std::move(event_ctx), std::forward<Args>(args)...);
 }
diff --git a/include/perfetto/tracing/traced_proto.h b/include/perfetto/tracing/traced_proto.h
index 88ee99e..44e4fb2 100644
--- a/include/perfetto/tracing/traced_proto.h
+++ b/include/perfetto/tracing/traced_proto.h
@@ -20,11 +20,11 @@
 #include "perfetto/base/template_util.h"
 #include "perfetto/protozero/field_writer.h"
 #include "perfetto/protozero/proto_utils.h"
-#include "perfetto/tracing/event_context.h"
 
 namespace perfetto {
+class EventContext;
 
-// A wrapper around a protozero message to allow C++ classes to specify how it
+// A Wrapper around a protozero message to allow C++ classes to specify how it
 // should be serialised into the trace:
 //
 // class Foo {
@@ -49,13 +49,6 @@
 template <typename MessageType>
 class TracedProto {
  public:
-  static_assert(std::is_base_of<protozero::Message, MessageType>::value,
-                "TracedProto can be used only with protozero messages");
-
-  static TracedProto FromProto(MessageType* message, EventContext& context) {
-    return TracedProto(message, context);
-  }
-
   TracedProto(const TracedProto&) = delete;
   TracedProto& operator=(const TracedProto&) = delete;
   TracedProto& operator=(TracedProto&&) = delete;
@@ -69,6 +62,8 @@
   EventContext& context() const { return context_; }
 
  private:
+  friend class EventContext;
+
   TracedProto(MessageType* message, EventContext& context)
       : message_(message), context_(context) {}
 
@@ -137,11 +132,10 @@
   Write(TracedProto<Proto> context, ValueType&& value) {
     // TODO(altimin): support TraceFormatTraits here.
     value.WriteIntoTrace(
-        TracedProto<typename FieldMetadata::cpp_field_type>::FromProto(
-            context->template BeginNestedMessage<
-                typename FieldMetadata::cpp_field_type>(
-                FieldMetadata::kFieldId),
-            context.context()));
+        context.context().Wrap(context.message()
+                                   ->template BeginNestedMessage<
+                                       typename FieldMetadata::cpp_field_type>(
+                                       FieldMetadata::kFieldId)));
   }
 
   // Nested repeated non-packed field.
@@ -153,12 +147,11 @@
   Write(TracedProto<Proto> context, ValueType&& value) {
     // TODO(altimin): support TraceFormatTraits here.
     for (auto&& item : value) {
-      item.WriteIntoTrace(
-          TracedProto<typename FieldMetadata::cpp_field_type>::FromProto(
-              context->template BeginNestedMessage<
+      item.WriteIntoTrace(context.context().Wrap(
+          context.message()
+              ->template BeginNestedMessage<
                   typename FieldMetadata::cpp_field_type>(
-                  FieldMetadata::kFieldId),
-              context.context()));
+                  FieldMetadata::kFieldId)));
     }
   }
 };
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 706b574..035113e 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -1190,6 +1190,12 @@
     STAT_FORK_COUNT = 4;
   }
   repeated StatCounters stat_counters = 6;
+
+  // Polls /sys/devfreq/*/curfreq every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // This option can be used to record unchanging values.
+  // Updates from frequency changes can come from ftrace/set_clock_rate.
+  optional uint32 devfreq_period_ms = 7;
 }
 
 // End of protos/perfetto/config/sys_stats/sys_stats_config.proto
diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto
index 4e037fc..0986924 100644
--- a/protos/perfetto/config/sys_stats/sys_stats_config.proto
+++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -57,4 +57,10 @@
     STAT_FORK_COUNT = 4;
   }
   repeated StatCounters stat_counters = 6;
+
+  // Polls /sys/devfreq/*/curfreq every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // This option can be used to record unchanging values.
+  // Updates from frequency changes can come from ftrace/set_clock_rate.
+  optional uint32 devfreq_period_ms = 7;
 }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index bf33910..1fb9ca1 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -1190,6 +1190,12 @@
     STAT_FORK_COUNT = 4;
   }
   repeated StatCounters stat_counters = 6;
+
+  // Polls /sys/devfreq/*/curfreq every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // This option can be used to record unchanging values.
+  // Updates from frequency changes can come from ftrace/set_clock_rate.
+  optional uint32 devfreq_period_ms = 7;
 }
 
 // End of protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -8213,6 +8219,15 @@
   // the top-level packet timestamp is the time at which
   // we begin collection.
   optional uint64 collection_end_timestamp = 9;
+
+  // Frequencies for /sys/class/devfreq/ entries in kHz.
+  message DevfreqValue {
+    optional string key = 1;
+    optional uint64 value = 2;
+  };
+
+  // One entry per device.
+  repeated DevfreqValue devfreq = 10;
 }
 
 // End of protos/perfetto/trace/sys_stats/sys_stats.proto
diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto
index 0161a6e..30edac0 100644
--- a/protos/perfetto/trace/sys_stats/sys_stats.proto
+++ b/protos/perfetto/trace/sys_stats/sys_stats.proto
@@ -94,4 +94,13 @@
   // the top-level packet timestamp is the time at which
   // we begin collection.
   optional uint64 collection_end_timestamp = 9;
+
+  // Frequencies for /sys/class/devfreq/ entries in kHz.
+  message DevfreqValue {
+    optional string key = 1;
+    optional uint64 value = 2;
+  };
+
+  // One entry per device.
+  repeated DevfreqValue devfreq = 10;
 }
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 5a8939e..813b174 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -50,7 +50,7 @@
   optional uint64 source_location_iid = 4;
 }
 
-message ChromeTaskGraphRunner {
+message ChromeRasterTask {
   optional int64 source_frame_number = 1;
 }
 
@@ -125,9 +125,16 @@
   optional bool has_speculative_render_frame_host = 3;
 }
 
+message ChromeHashedPerformanceMark {
+  optional uint32 site_hash = 1;
+  optional string site = 2;
+  optional uint32 mark_hash = 3;
+  optional string mark = 4;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1011
+  // Next ID: 1012
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -143,7 +150,7 @@
     optional ChromeTaskPostedToDisabledQueue
         chrome_task_posted_to_disabled_queue = 1005;
 
-    optional ChromeTaskGraphRunner chrome_task_graph_runner = 1006;
+    optional ChromeRasterTask chrome_raster_task = 1006;
 
     optional ChromeMessagePumpForUI chrome_message_pump_for_ui = 1007;
 
@@ -153,5 +160,7 @@
         should_swap_browsing_instances_result = 1009;
 
     optional FrameTreeNodeInfo frame_tree_node_info = 1010;
+
+    optional ChromeHashedPerformanceMark chrome_hashed_performance_mark = 1011;
   }
 }
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 0e15bf6..d5bd5a0 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/utils.h"
 
 #include "perfetto/base/build_config.h"
@@ -22,7 +23,7 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
     PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
-#include <unistd.h>  // For getpagesize() and geteuid().
+#include <unistd.h>  // For getpagesize() and geteuid() & fork()
 #endif
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
@@ -108,5 +109,37 @@
 #endif
 }
 
+void Daemonize() {
+   #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+       PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+       PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+      pid_t pid;
+      switch (pid = fork()) {
+        case -1:
+          PERFETTO_FATAL("fork");
+        case 0: {
+          PERFETTO_CHECK(setsid() != -1);
+          base::ignore_result(chdir("/"));
+          base::ScopedFile null = base::OpenFile("/dev/null", O_RDONLY);
+          PERFETTO_CHECK(null);
+          PERFETTO_CHECK(dup2(*null, STDIN_FILENO) != -1);
+          PERFETTO_CHECK(dup2(*null, STDOUT_FILENO) != -1);
+          PERFETTO_CHECK(dup2(*null, STDERR_FILENO) != -1);
+          // Do not accidentally close stdin/stdout/stderr.
+          if (*null <= 2)
+            null.release();
+          break;
+        }
+        default:
+          printf("%d\n", pid);
+          exit(0);
+      }
+  #else
+    // Avoid -Wunreachable warnings.
+    if (reinterpret_cast<intptr_t>(&Daemonize) != 16)
+      PERFETTO_FATAL("--background is only supported on Linux/Android/Mac");
+  #endif  // OS_WIN
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index c76e064..4643399 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -654,31 +654,7 @@
   }
 
   if (background) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-    PERFETTO_FATAL("--background is not supported on Windows");
-#else
-    pid_t pid;
-    switch (pid = fork()) {
-      case -1:
-        PERFETTO_FATAL("fork");
-      case 0: {
-        PERFETTO_CHECK(setsid() != -1);
-        base::ignore_result(chdir("/"));
-        base::ScopedFile null = base::OpenFile("/dev/null", O_RDONLY);
-        PERFETTO_CHECK(null);
-        PERFETTO_CHECK(dup2(*null, STDIN_FILENO) != -1);
-        PERFETTO_CHECK(dup2(*null, STDOUT_FILENO) != -1);
-        PERFETTO_CHECK(dup2(*null, STDERR_FILENO) != -1);
-        // Do not accidentally close stdin/stdout/stderr.
-        if (*null <= 2)
-          null.release();
-        break;
-      }
-      default:
-        printf("%d\n", pid);
-        exit(0);
-    }
-#endif  // OS_WIN
+    base::Daemonize();
   }
 
   // If we are just activating triggers then we don't need to rate limit,
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 2319a13..214c0fa 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -119,6 +119,20 @@
         ts, static_cast<double>(mi.value()) * 1024., track);
   }
 
+  for (auto it = sys_stats.devfreq(); it; ++it) {
+    protos::pbzero::SysStats::DevfreqValue::Decoder vm(*it);
+    auto key = static_cast<base::StringView>(vm.key());
+    // Append " Frequency" to align names with FtraceParser::ParseClockSetRate
+    base::StringView devfreq_subtitle("Frequency");
+    char counter_name[255];
+    snprintf(counter_name, sizeof(counter_name), "%.*s %.*s", int(key.size()),
+             key.data(), int(devfreq_subtitle.size()), devfreq_subtitle.data());
+    StringId name = context_->storage->InternString(counter_name);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+    context_->event_tracker->PushCounter(ts, static_cast<double>(vm.value()),
+                                         track);
+  }
+
   for (auto it = sys_stats.vmstat(); it; ++it) {
     protos::pbzero::SysStats::VmstatValue::Decoder vm(*it);
     auto key = static_cast<size_t>(vm.key());
diff --git a/src/trace_processor/metrics/trace_metadata.sql b/src/trace_processor/metrics/trace_metadata.sql
index 770b7cb..838116b 100644
--- a/src/trace_processor/metrics/trace_metadata.sql
+++ b/src/trace_processor/metrics/trace_metadata.sql
@@ -17,7 +17,7 @@
 DROP VIEW IF EXISTS trace_metadata_output;
 CREATE VIEW trace_metadata_output AS
 SELECT TraceMetadata(
-  'trace_duration_ns', (SELECT end_ts - start_ts FROM trace_bounds),
+  'trace_duration_ns', CAST((SELECT end_ts - start_ts FROM trace_bounds) AS INT),
   'trace_uuid', (SELECT str_value FROM metadata WHERE name = 'trace_uuid'),
   'android_build_fingerprint', (
     SELECT str_value FROM metadata WHERE name = 'android_build_fingerprint'
diff --git a/src/traced/probes/probes.cc b/src/traced/probes/probes.cc
index 60d1cc7..b02b6d5 100644
--- a/src/traced/probes/probes.cc
+++ b/src/traced/probes/probes.cc
@@ -22,6 +22,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/version.h"
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
@@ -36,9 +37,13 @@
   enum LongOption {
     OPT_CLEANUP_AFTER_CRASH = 1000,
     OPT_VERSION,
+    OPT_BACKGROUND,
   };
 
+  bool background = false;
+
   static const option long_options[] = {
+      {"background", no_argument, nullptr, OPT_BACKGROUND},
       {"cleanup-after-crash", no_argument, nullptr, OPT_CLEANUP_AFTER_CRASH},
       {"version", no_argument, nullptr, OPT_VERSION},
       {nullptr, 0, nullptr, 0}};
@@ -48,6 +53,9 @@
     if (option == -1)
       break;
     switch (option) {
+      case OPT_BACKGROUND:
+        background = true;
+        break;
       case OPT_CLEANUP_AFTER_CRASH:
         HardResetFtraceState();
         return 0;
@@ -55,11 +63,15 @@
         printf("%s\n", base::GetVersionString());
         return 0;
       default:
-        PERFETTO_ELOG("Usage: %s [--cleanup-after-crash|--version]", argv[0]);
+        PERFETTO_ELOG("Usage: %s [--background|--cleanup-after-crash|--version]", argv[0]);
         return 1;
     }
   }
 
+  if (background) {
+    base::Daemonize();
+  }
+
   base::Watchdog* watchdog = base::Watchdog::GetInstance();
   // The memory watchdog will be updated soon after connect, once the shmem
   // buffer size is known, in ProbesProducer::OnTracingSetup().
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index f900db8..3d75ff2 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -138,19 +138,21 @@
     stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
   }
 
-  std::array<uint32_t, 3> periods_ms{};
-  std::array<uint32_t, 3> ticks{};
+  std::array<uint32_t, 4> periods_ms{};
+  std::array<uint32_t, 4> ticks{};
   static_assert(periods_ms.size() == ticks.size(), "must have same size");
 
   periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
   periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
   periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
+  periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms");
 
   tick_period_ms_ = 0;
   for (uint32_t ms : periods_ms) {
     if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
       tick_period_ms_ = ms;
   }
+
   if (tick_period_ms_ == 0)
     return;  // No polling configured.
 
@@ -165,6 +167,7 @@
   meminfo_ticks_ = ticks[0];
   vmstat_ticks_ = ticks[1];
   stat_ticks_ = ticks[2];
+  devfreq_ticks_ = ticks[3];
 }
 
 void SysStatsDataSource::Start() {
@@ -205,12 +208,61 @@
   if (stat_ticks_ && tick_ % stat_ticks_ == 0)
     ReadStat(sys_stats);
 
+  if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0)
+    ReadDevfreq(sys_stats);
+
   sys_stats->set_collection_end_timestamp(
       static_cast<uint64_t>(base::GetBootTimeNs().count()));
 
   tick_++;
 }
 
+void SysStatsDataSource::ReadDevfreq(protos::pbzero::SysStats* sys_stats) {
+  base::ScopedDir devfreq_dir = OpenDevfreqDir();
+  if (devfreq_dir) {
+    while (struct dirent* dir_ent = readdir(*devfreq_dir)) {
+      // Entries in /sys/class/devfreq are symlinks to /devices/platform
+      if (dir_ent->d_type != DT_LNK)
+        continue;
+      const char* name = dir_ent->d_name;
+      const char* file_content = ReadDevfreqCurFreq(name);
+      auto value = static_cast<uint64_t>(strtoll(file_content, nullptr, 10));
+      auto* devfreq = sys_stats->add_devfreq();
+      devfreq->set_key(name);
+      devfreq->set_value(value);
+    }
+  }
+}
+
+base::ScopedDir SysStatsDataSource::OpenDevfreqDir() {
+  const char* base_dir = "/sys/class/devfreq/";
+  base::ScopedDir devfreq_dir(opendir(base_dir));
+  if (!devfreq_dir && !devfreq_error_logged_) {
+    devfreq_error_logged_ = true;
+    PERFETTO_PLOG("failed to opendir(/sys/class/devfreq)");
+  }
+  return devfreq_dir;
+}
+
+const char* SysStatsDataSource::ReadDevfreqCurFreq(
+    const std::string& deviceName) {
+  const char* devfreq_base_path = "/sys/class/devfreq";
+  const char* freq_file_name = "cur_freq";
+  char cur_freq_path[256];
+  snprintf(cur_freq_path, sizeof(cur_freq_path), "%s/%s/%s", devfreq_base_path,
+           deviceName.c_str(), freq_file_name);
+  base::ScopedFile fd = OpenReadOnly(cur_freq_path);
+  if (!fd && !devfreq_error_logged_) {
+    devfreq_error_logged_ = true;
+    PERFETTO_PLOG("Failed to open %s", cur_freq_path);
+    return "";
+  }
+  size_t rsize = ReadFile(&fd, cur_freq_path);
+  if (!rsize)
+    return "";
+  return static_cast<char*>(read_buf_.Get());
+}
+
 void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
   size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
   if (!rsize)
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.h b/src/traced/probes/sys_stats/sys_stats_data_source.h
index df47176..cf47770 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.h
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.h
@@ -64,6 +64,10 @@
   void set_ns_per_user_hz_for_testing(uint64_t ns) { ns_per_user_hz_ = ns; }
   uint32_t tick_for_testing() const { return tick_; }
 
+  // Virtual for testing
+  virtual base::ScopedDir OpenDevfreqDir();
+  virtual const char* ReadDevfreqCurFreq(const std::string& name);
+
  private:
   struct CStrCmp {
     bool operator()(const char* a, const char* b) const {
@@ -79,6 +83,7 @@
   void ReadMeminfo(protos::pbzero::SysStats* sys_stats);
   void ReadVmstat(protos::pbzero::SysStats* sys_stats);
   void ReadStat(protos::pbzero::SysStats* sys_stats);
+  void ReadDevfreq(protos::pbzero::SysStats* sys_stats);
   size_t ReadFile(base::ScopedFile*, const char* path);
 
   base::TaskRunner* const task_runner_;
@@ -97,6 +102,8 @@
   uint32_t vmstat_ticks_ = 0;
   uint32_t stat_ticks_ = 0;
   uint32_t stat_enabled_fields_ = 0;
+  uint32_t devfreq_ticks_ = 0;
+  bool devfreq_error_logged_ = false;
 
   base::WeakPtrFactory<SysStatsDataSource> weak_factory_;  // Keep last.
 };
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
index 38a7414..b239ffa 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
@@ -16,6 +16,7 @@
 
 #include <unistd.h>
 
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "src/base/test/test_task_runner.h"
 #include "src/traced/probes/sys_stats/sys_stats_data_source.h"
@@ -186,6 +187,26 @@
 procs_blocked 0
 softirq 84611084 10220177 28299167 155083 3035679 6390543 66234 4396819 15604187 0 16443195)";
 
+const char kDevfreq1[] = "1000000";
+const char kDevfreq2[] = "20000000";
+
+class TestSysStatsDataSource : public SysStatsDataSource {
+ public:
+  TestSysStatsDataSource(base::TaskRunner* task_runner,
+                         TracingSessionID id,
+                         std::unique_ptr<TraceWriter> writer,
+                         const DataSourceConfig& config,
+                         OpenFunction open_fn)
+      : SysStatsDataSource(task_runner,
+                           id,
+                           std::move(writer),
+                           config,
+                           open_fn) {}
+
+  MOCK_METHOD0(OpenDevfreqDir, base::ScopedDir());
+  MOCK_METHOD1(ReadDevfreqCurFreq, const char*(const std::string& deviceName));
+};
+
 base::ScopedFile MockOpenReadOnly(const char* path) {
   base::TempFile tmp_ = base::TempFile::CreateUnlinked();
   if (!strcmp(path, "/proc/meminfo")) {
@@ -202,13 +223,14 @@
 
 class SysStatsDataSourceTest : public ::testing::Test {
  protected:
-  std::unique_ptr<SysStatsDataSource> GetSysStatsDataSource(
+  std::unique_ptr<TestSysStatsDataSource> GetSysStatsDataSource(
       const DataSourceConfig& cfg) {
     auto writer =
         std::unique_ptr<TraceWriterForTesting>(new TraceWriterForTesting());
     writer_raw_ = writer.get();
-    auto instance = std::unique_ptr<SysStatsDataSource>(new SysStatsDataSource(
-        &task_runner_, 0, std::move(writer), cfg, MockOpenReadOnly));
+    auto instance =
+        std::unique_ptr<TestSysStatsDataSource>(new TestSysStatsDataSource(
+            &task_runner_, 0, std::move(writer), cfg, MockOpenReadOnly));
     instance->set_ns_per_user_hz_for_testing(1000000000ull / 100);  // 100 Hz.
     instance->Start();
     return instance;
@@ -252,6 +274,7 @@
   const auto& sys_stats = packet.sys_stats();
   EXPECT_EQ(sys_stats.vmstat_size(), 0);
   EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+  EXPECT_EQ(sys_stats.devfreq_size(), 0);
 
   using KV = std::pair<int, uint64_t>;
   std::vector<KV> kvs;
@@ -280,6 +303,7 @@
   const auto& sys_stats = packet.sys_stats();
   EXPECT_EQ(sys_stats.vmstat_size(), 0);
   EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+  EXPECT_EQ(sys_stats.devfreq_size(), 0);
   EXPECT_GE(sys_stats.meminfo_size(), 10);
 }
 
@@ -302,6 +326,7 @@
   const auto& sys_stats = packet.sys_stats();
   EXPECT_EQ(sys_stats.meminfo_size(), 0);
   EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+  EXPECT_EQ(sys_stats.devfreq_size(), 0);
 
   using KV = std::pair<int, uint64_t>;
   std::vector<KV> kvs;
@@ -329,9 +354,70 @@
   const auto& sys_stats = packet.sys_stats();
   EXPECT_EQ(sys_stats.meminfo_size(), 0);
   EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+  EXPECT_EQ(sys_stats.devfreq_size(), 0);
   EXPECT_GE(sys_stats.vmstat_size(), 10);
 }
 
+TEST_F(SysStatsDataSourceTest, DevfreqAll) {
+  DataSourceConfig config;
+  protos::gen::SysStatsConfig sys_cfg;
+  sys_cfg.set_devfreq_period_ms(10);
+  config.set_sys_stats_config_raw(sys_cfg.SerializeAsString());
+  auto data_source = GetSysStatsDataSource(config);
+
+  // Create dirs and symlinks, but only read the symlinks.
+  std::vector<std::string> dirs_to_delete;
+  std::vector<std::string> symlinks_to_delete;
+  auto make_devfreq_paths = [&symlinks_to_delete, &dirs_to_delete](
+                                base::TempDir& temp_dir, base::TempDir& sym_dir,
+                                const char* name) {
+    char path[256];
+    sprintf(path, "%s/%s", temp_dir.path().c_str(), name);
+    dirs_to_delete.push_back(path);
+    mkdir(path, 0755);
+    char sym_path[256];
+    sprintf(sym_path, "%s/%s", sym_dir.path().c_str(), name);
+    symlinks_to_delete.push_back(sym_path);
+    symlink(path, sym_path);
+  };
+  auto fake_devfreq = base::TempDir::Create();
+  auto fake_devfreq_symdir = base::TempDir::Create();
+  static const char* const devfreq_names[] = {"10010.devfreq_device_a",
+                                              "10020.devfreq_device_b"};
+  for (auto dev : devfreq_names) {
+    make_devfreq_paths(fake_devfreq, fake_devfreq_symdir, dev);
+  }
+
+  EXPECT_CALL(*data_source, OpenDevfreqDir())
+      .WillRepeatedly(Invoke([&fake_devfreq_symdir] {
+        return base::ScopedDir(opendir(fake_devfreq_symdir.path().c_str()));
+      }));
+  EXPECT_CALL(*data_source, ReadDevfreqCurFreq("10010.devfreq_device_a"))
+      .WillRepeatedly(Return(kDevfreq1));
+  EXPECT_CALL(*data_source, ReadDevfreqCurFreq("10020.devfreq_device_b"))
+      .WillRepeatedly(Return(kDevfreq2));
+
+  WaitTick(data_source.get());
+
+  protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket();
+  ASSERT_TRUE(packet.has_sys_stats());
+  const auto& sys_stats = packet.sys_stats();
+  EXPECT_EQ(sys_stats.meminfo_size(), 0);
+  EXPECT_EQ(sys_stats.cpu_stat_size(), 0);
+
+  using KV = std::pair<std::string, uint64_t>;
+  std::vector<KV> kvs;
+  for (const auto& kv : sys_stats.devfreq())
+    kvs.push_back({kv.key(), kv.value()});
+  EXPECT_THAT(kvs,
+              UnorderedElementsAre(KV{"10010.devfreq_device_a", 1000000},
+                                   KV{"10020.devfreq_device_b", 20000000}));
+  for (const std::string& path : dirs_to_delete)
+    base::Rmdir(path);
+  for (const std::string& path : symlinks_to_delete)
+    remove(path.c_str());
+}
+
 TEST_F(SysStatsDataSourceTest, StatAll) {
   DataSourceConfig config;
   protos::gen::SysStatsConfig sys_cfg;
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index 4881b75..6d9ba8b 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -20,6 +20,7 @@
 #include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/version.h"
 #include "perfetto/ext/base/watchdog.h"
 #include "perfetto/ext/traced/traced.h"
@@ -77,6 +78,7 @@
   PERFETTO_ELOG(R"(
 Usage: %s [option] ...
 Options and arguments
+    --background : Exits immediately and continues running in the background
     --version : print the version number and exit.
     --set-socket-permissions <permissions> : sets group ownership and permission
         mode bits of the producer and consumer sockets.
@@ -98,9 +100,13 @@
   enum LongOption {
     OPT_VERSION = 1000,
     OPT_SET_SOCKET_PERMISSIONS = 1001,
+    OPT_BACKGROUND,
   };
 
+  bool background = false;
+
   static const option long_options[] = {
+      {"background", no_argument, nullptr, OPT_BACKGROUND},
       {"version", no_argument, nullptr, OPT_VERSION},
       {"set-socket-permissions", required_argument, nullptr,
        OPT_SET_SOCKET_PERMISSIONS},
@@ -114,6 +120,9 @@
     if (option == -1)
       break;
     switch (option) {
+      case OPT_BACKGROUND:
+        background = true;
+        break;
       case OPT_VERSION:
         printf("%s\n", base::GetVersionString());
         return 0;
@@ -136,6 +145,10 @@
     }
   }
 
+  if (background) {
+    base::Daemonize();
+  }
+
   base::UnixTaskRunner task_runner;
   std::unique_ptr<ServiceIPCHost> svc;
   svc = ServiceIPCHost::CreateInstance(&task_runner);
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 57e92b3..3658229 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -409,7 +409,7 @@
           return true;
         });
   }
-  if (name) {
+  if (name && type != protos::pbzero::TrackEvent::TYPE_SLICE_END) {
     size_t name_iid = InternedEventName::Get(&ctx, name);
     track_event->set_name_iid(name_iid);
   }
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 8f6dd15..962d389 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -3209,8 +3209,8 @@
       slices,
       ElementsAre(
           "[track=0]I:cat.LegacyEvent", "B:cat.LegacyEvent(arg=(int)123)",
-          "E.LegacyEvent(arg=(string)string,arg2=(double)0.123)",
-          "B:cat.ScopedLegacyEvent", "E",
+          "E(arg=(string)string,arg2=(double)0.123)", "B:cat.ScopedLegacyEvent",
+          "E",
           "B(bind_id=3671771902)(flow_direction=1):disabled-by-default-cat."
           "LegacyFlowEvent",
           "[track=0]I:cat.LegacyInstantEvent",
diff --git a/src/tracing/traced_proto_unittest.cc b/src/tracing/traced_proto_unittest.cc
index 5a95516..e5c84f1 100644
--- a/src/tracing/traced_proto_unittest.cc
+++ b/src/tracing/traced_proto_unittest.cc
@@ -42,9 +42,8 @@
 
 TEST_F(TracedProtoTest, SingleInt) {
   protozero::HeapBuffered<TestPayload> event;
-  WriteIntoTracedProto(
-      TracedProto<TestPayload>::FromProto(event.get(), context()),
-      TestPayload::kSingleInt, 42);
+  WriteIntoTracedProto(context().Wrap(event.get()), TestPayload::kSingleInt,
+                       42);
 
   protos::TestEvent::TestPayload result;
   result.ParseFromString(event.SerializeAsString());
@@ -54,9 +53,8 @@
 
 TEST_F(TracedProtoTest, RepeatedInt) {
   protozero::HeapBuffered<TestPayload> event;
-  WriteIntoTracedProto(
-      TracedProto<TestPayload>::FromProto(event.get(), context()),
-      TestPayload::kRepeatedInts, std::vector<int>{1, 2, 3});
+  WriteIntoTracedProto(context().Wrap(event.get()), TestPayload::kRepeatedInts,
+                       std::vector<int>{1, 2, 3});
 
   protos::TestEvent::TestPayload result;
   result.ParseFromString(event.SerializeAsString());
@@ -65,9 +63,8 @@
 
 TEST_F(TracedProtoTest, SingleString) {
   protozero::HeapBuffered<TestPayload> event;
-  WriteIntoTracedProto(
-      TracedProto<TestPayload>::FromProto(event.get(), context()),
-      TestPayload::kSingleString, "foo");
+  WriteIntoTracedProto(context().Wrap(event.get()), TestPayload::kSingleString,
+                       "foo");
 
   protos::TestEvent::TestPayload result;
   result.ParseFromString(event.SerializeAsString());
@@ -77,9 +74,8 @@
 
 TEST_F(TracedProtoTest, RepeatedString) {
   protozero::HeapBuffered<TestPayload> event;
-  WriteIntoTracedProto(
-      TracedProto<TestPayload>::FromProto(event.get(), context()),
-      TestPayload::kStr, std::vector<std::string>{"foo", "bar"});
+  WriteIntoTracedProto(context().Wrap(event.get()), TestPayload::kStr,
+                       std::vector<std::string>{"foo", "bar"});
 
   protos::TestEvent::TestPayload result;
   result.ParseFromString(event.SerializeAsString());
@@ -98,9 +94,8 @@
 
 TEST_F(TracedProtoTest, SingleNestedMessage) {
   protozero::HeapBuffered<protos::pbzero::TestEvent> event;
-  WriteIntoTracedProto(
-      TracedProto<protos::pbzero::TestEvent>::FromProto(event.get(), context()),
-      protos::pbzero::TestEvent::kPayload, Foo());
+  WriteIntoTracedProto(context().Wrap(event.get()),
+                       protos::pbzero::TestEvent::kPayload, Foo());
 
   protos::TestEvent result;
   result.ParseFromString(event.SerializeAsString());
@@ -109,9 +104,8 @@
 
 TEST_F(TracedProtoTest, RepeatedNestedMessage) {
   protozero::HeapBuffered<TestPayload> event;
-  WriteIntoTracedProto(
-      TracedProto<TestPayload>::FromProto(event.get(), context()),
-      TestPayload::kNested, std::vector<Foo>{Foo(), Foo()});
+  WriteIntoTracedProto(context().Wrap(event.get()), TestPayload::kNested,
+                       std::vector<Foo>{Foo(), Foo()});
 
   protos::TestEvent::TestPayload result;
   result.ParseFromString(event.SerializeAsString());
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 2a5f87f..5c66c42 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -1827,9 +1827,9 @@
       "dev": true
     },
     "devtools-protocol": {
-      "version": "0.0.681549",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.681549.tgz",
-      "integrity": "sha512-YVwTu4T4zzgf88Y8+t9lIDT+qAn2YDcig4zRgqq5+4bFACn8WDzbqAhct5zVUhefRMOwLGPXyLFTim1FV7keVg=="
+      "version": "0.0.847576",
+      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.847576.tgz",
+      "integrity": "sha512-0M8kobnSQE0Jmly7Mhbeq0W/PpZfnuK+WjN2ZRVPbGqYwCHCioAVp84H0TcLimgECcN5H976y5QiXMGBC9JKmg=="
     },
     "diff": {
       "version": "4.0.2",
@@ -2617,6 +2617,11 @@
       "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
       "dev": true
     },
+    "hsluv": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.1.0.tgz",
+      "integrity": "sha512-ERcanKLAszD2XN3Vh5r5Szkrv9q0oSTudmP0rkiKAGM/3NMc9FLmMZBB7TSqTaXJfSDBOreYTfjezCOYbRKqlw=="
+    },
     "html-encoding-sniffer": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts
index cc10366..30a7a2d 100644
--- a/ui/src/common/constants.ts
+++ b/ui/src/common/constants.ts
@@ -13,3 +13,5 @@
 // limitations under the License.
 
 export const TRACE_SUFFIX = '.perfetto-trace';
+
+export const TRACE_MARGIN_TIME_S = 1 / 1e7;
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index c5ee827..37b76ce 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {TRACE_MARGIN_TIME_S} from '../common/constants';
 import {Engine} from '../common/engine';
 import {slowlyCountRows} from '../common/query_iterator';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
@@ -86,7 +87,7 @@
       return;
     }
     this.previousSpan = new TimeSpan(
-        Math.max(newSpan.start - newSpan.duration, 0),
+        Math.max(newSpan.start - newSpan.duration, -TRACE_MARGIN_TIME_S),
         newSpan.end + newSpan.duration);
     this.previousResolution = newResolution;
     this.previousSearch = newSearch;
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index df00635..2ef2012 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -19,6 +19,7 @@
   Actions,
   DeferredAction,
 } from '../common/actions';
+import {TRACE_MARGIN_TIME_S} from '../common/constants';
 import {Engine, QueryError} from '../common/engine';
 import {HttpRpcEngine} from '../common/http_rpc_engine';
 import {slowlyCountRows} from '../common/query_iterator';
@@ -84,8 +85,6 @@
 
 type States = 'init'|'loading_trace'|'ready';
 
-const TRACE_MARGIN_TIME_S = 1 / 1e7;
-
 // TraceController handles handshakes with the frontend for everything that
 // concerns a single trace. It owns the WASM trace processor engine, handles
 // tracks data and SQL queries. There is one TraceController instance for each
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 7c45bb8..c6fe1b7 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -17,6 +17,7 @@
 import {Actions} from '../../common/actions';
 import {cropText, drawIncompleteSlice} from '../../common/canvas_utils';
 import {hslForSlice} from '../../common/colorizer';
+import {TRACE_MARGIN_TIME_S} from '../../common/constants';
 import {TrackState} from '../../common/state';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
@@ -259,7 +260,7 @@
       |undefined {
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
-    const left = Math.max(timeScale.timeToPx(tStart), 0);
+    const left = Math.max(timeScale.timeToPx(tStart), -TRACE_MARGIN_TIME_S);
     const right = Math.min(timeScale.timeToPx(tEnd), pxEnd);
     return {
       left,