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,