Authors: @LalitMaganti Status: Draft PR: N/A
When the trace processor opens a “merged trace” (a zip/tar containing multiple trace files), clock resolution is ad-hoc. Each format parser independently tries to set the global trace clock via SetTraceTimeClock(). If a clock was already set by a previously parsed file, the call is silently rejected and the trace relies on clock snapshots from the first file.
This works in practice for the common case (proto + perf on same device) but the behavior is implicit, undocumented, and produces surprising results when the assumptions don't hold (e.g. two proto traces in the same zip).
A related issue is that “dumb” trace formats (JSON, etc.) that have no clock metadata currently end up implicitly claiming BOOTTIME as their clock, which is incorrect.
A trace file's clock capabilities are not a property of its format but of what clock information it actually contains. There are three tiers:
ClockSnapshot packets mapping between clock domains. Typical of proto traces produced by traced.Importantly, a proto trace is not inherently a full authority. A synthetically generated proto trace can be semi-smart or entirely dumb depending on what packets it contains.
TRACE_SCOPED_CLOCK builtin clockWe introduce a new builtin clock ID: TRACE_SCOPED_CLOCK. This is the clock that dumb trace formats should declare instead of incorrectly claiming a real clock domain like BOOTTIME.
TRACE_SCOPED_CLOCK means: “this file has timestamps but does not know what clock domain they are in.” Trace processor handles disambiguating multiple files that all declare TRACE_SCOPED_CLOCK; individual parsers do not need to coordinate.
The behavior of TRACE_SCOPED_CLOCK depends on context:
TRACE_SCOPED_CLOCK becomes the trace clock. Timestamps pass through as-is.TRACE_SCOPED_CLOCK → <global trace clock>. Timestamps pass through as-is by default. This mapping can be overridden via the metadata JSON (see below).This fixes the existing bug where JSON traces implicitly get BOOTTIME as their clock.
Files in a merged trace are parsed in a deterministic order: traced proto traces first, then synthetic proto traces, then semi-smart traces (perf, etc.), then dumb traces (JSON, etc.). This ordering ensures clock context is likely to be established before files that depend on it are parsed.
Within the same category (e.g. among synthetic proto traces), parse order is not guaranteed. If ordering matters, users should use the metadata JSON to declare an explicit authority.
Clock authority is discovered during parsing, not assumed from file format. A trace becomes a full authority when it produces ClockSnapshot packets, not because it is a proto file.
The first trace parsed (by parse order) becomes the global clock authority regardless of whether it has snapshots. It:
BOOTTIME, or TRACE_SCOPED_CLOCK if it declares nothing).ClockSnapshot packets it produces. If it produces none, the shared pool is empty.The core rule: use a file's own clock snapshots if it has them, otherwise fall back to the shared snapshot pool.
Concretely:
TRACE_SCOPED_CLOCK. Trace processor adds a 1:1 identity mapping to the global trace clock by default.A non-authority trace may start parsing without snapshots (resolving against the shared pool) and later encounter its own ClockSnapshot packets. When this happens, it switches from the shared pool to its own snapshots for all subsequent timestamp resolution.
This transition means timestamps before the first snapshot were resolved differently (via the shared pool) than timestamps after (via the trace's own snapshots). This can produce a visible discontinuity. Trace processor should flag this as an import warning to the user, without failing the import.
A merged trace may include a perfetto_metadata.json file that overrides the default heuristics. This is the layer at which users can customize clock behavior when the defaults don't work.
Schema:
{ "perfetto_metadata": { "trace_clock": { "id": "BOOTTIME", "authority": "trace_a.perfetto-trace" }, "traces": { "inner.tar/trace_c.perf": { "clock_snapshot_source": "trace_b.perfetto-trace" } } } }
Fields:
trace_clock.id (optional): force a specific clock as the global trace clock, instead of using whichever the first trace declares.trace_clock.authority (optional): designate a specific file as the clock authority (i.e. whose snapshots populate the shared pool), instead of the first trace in parse order.traces (optional): per-file overrides, keyed by path relative to the root of the outermost archive. For files inside nested archives (zips inside tars or vice versa), use / as separator (e.g. inner.tar/trace_c.perf).clock_snapshot_source: resolve this file‘s clocks using a specific other file’s snapshots instead of the shared pool.All fields are optional. When no metadata JSON is present, the default heuristics apply.
Rather than restricting the shared pool to the first authority, we could merge snapshots from all proto traces into a single pool. This would give better temporal coverage (e.g. if the first proto trace is short but the second covers a longer window).
Rejected because: (a) it introduces surprising behavior — adding a second proto trace to a zip could silently change how other files' timestamps resolve; (b) a buggy trace could corrupt clock resolution for other files; (c) clock resolution happens during parsing, so pooling requires either a pre-pass over all files to extract snapshots (complex) or accepting that the pool grows mid-parse (order-dependent and confusing).
We could assign every file a TRACE_SCOPED_CLOCK, even proto and perf traces, and rely entirely on the metadata JSON for cross-file alignment.
Rejected because: for the common case (proto + perf on same device), implicit resolution via shared snapshots just works and requiring explicit metadata would be a regression in usability.
perfetto_metadata.json at the root?).TRACE_SCOPED_CLOCK mechanisms are designed to extend naturally to that case.