RFC-0030: Bundling Analysis and Visualization Extensions in Trace Files #6230
Replies: 4 comments 4 replies
-
Beta Was this translation helpful? Give feedback.
-
|
This sounds great from the AndroidX side. @ChrisCraik thoughts? |
Beta Was this translation helpful? Give feedback.
-
|
Okay I like the overall idea and the fact that is composable with the existing server extension mechanism. I have two sets of comments: 1 The security aspectsIn a way this is inventing Win95 autorun.inf in traces. This sort of stuff should trigger dialogs where the user needs to acknoledge that the trace wants to autoload some stuff and there needs to be explicit consent:
Can I make a proposal? Can we say that if a directory name in the zip starts with "." (e.g. ".sql/") that directory is ignored for the sake of "trying to load it as a trace" and instead becomes a "support directory that other things can refer to (like this one)". The alternative is to define a Or we can also just decide that only the immediate files in the zip archive get treated as a trace to load, and anything else in any directory, is not auto loaded, and can only be used as metadata / support files. Instead I would NOT do something like "every file gets loaded, unless is references in a files section in the metadta" as it feels more brittle and harder to reason about. |
Beta Was this translation helpful? Give feedback.
-
Totally agree. I do call this out in the doc.
Ah OK this is different to what I had. I was thinking it would be persistent. Do we really want the complexity of "only for this session extension server"? Seems awfully complex.
Well I think these will change often, not sure I would do on this.
Sure. But I would downscope it a bit to only the directory .perfettometa or similar.
In agreement here. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
📄 RFC Doc: 0030-trace-bundled-extensions.md
Bundling Analysis and Visualization Extensions in Trace Files
Authors: @LalitMaganti
Status: Draft
PR: N/A
Problem
Trace producers (AndroidX Benchmark, game engines, CI systems, internal tooling)
generate traces whose interpretation requires domain-specific logic:
debug tracks, open query tabs) for the workflow the trace was recorded for.
TrackEventextension fields.Today that logic must be delivered out-of-band: documentation pages with
copy-pasted queries, extension servers every consumer must configure manually,
or fragile deep-link URLs. Two long-standing feature requests capture this:
macros/startup commands in trace files. The settings-based automation
machinery (macros, startup commands, the command allowlist) now exists, but
there is no way for the trace itself to supply them.
API to bundle "v2 metrics" (PerfettoSQL) into traces, so that the same
queries work in both
trace_processor_shelland the Perfetto UI without aside channel for distributing the SQL.
Two additional constraints shape the solution:
contents from visualization. Baking viewer behavior into the trace data
stream makes it unstrippable and forces backwards compatibility at the wrong
layer forever. Whatever carries this information must be a sidecar that
tooling can add, inspect, and remove without touching trace data.
settings only help users who open the trace in the UI. SQL distributed that
way is invisible to
trace_processor_shelland batch pipelines, and protodescriptors must be configured before the trace is parsed. The mechanism
must be consumed by trace processor itself, not just the UI.
Decision
Pending.
Design
Overview
We extend the
perfetto_metadataJSON sidecar (introduced inRFC-0016) with a new
extensionssection. Thesidecar is the first member of a zip/tar archive, is content-sniffed via its
{"perfetto_metadata"prefix, and is parsed by trace processor before anyother archive member. The schema version stays at
1: the existing readerignores unknown keys, so old trace processor builds load new traces unchanged,
simply without extensions.
The
extensionssection comes in two mutually exclusive variants, expressedas a discriminated union:
inline: the content (macros, SQL modules, proto descriptors, startupcommands) travels with the trace, either embedded in the JSON or as separate
archive members.
server: the trace references anextension server
which provides the content; the UI prompts the user to install it via the
existing add-server flow.
Trace processor consumes the parts it understands (SQL modules, proto
descriptors) directly — this is what makes bundled metrics work in
trace_processor_shell— and exposes the whole section to the UI via themetadatatable, so the UI never needs to parse archives itself and thefeature works identically over Wasm and HTTP-RPC.
Schema
Top level:
inlinevsserver{ "perfetto_metadata": { "version": 1, "extensions": { "type": "inline", "namespace": "androidx.benchmark", "macros": [], "sql_modules": [], "proto_descriptors": [], "startup_commands": [] } } }{ "perfetto_metadata": { "version": 1, "extensions": { "type": "server", "server": { "type": "https", "url": "https://extensions.example.com", "enabled_modules": ["benchmarks"] }, "startup_commands": [] } } }type(required):"inline"or"server". A trace either carries itscontent or points at a server; never both. This keeps the loading story
simple (no dedup/merge semantics between a trace's own content and a
server's) and keeps provenance unambiguous.
namespace(required forinline): the generating tool's namespace inreverse-domain-style dotted notation. Every inline macro
idand everyinline SQL module name must start with
namespace + ".". This mirrors thenamespace enforcement extension servers already have. The namespace is also
the name of the PerfettoSQL package registered in trace processor. Reserved
prefixes (
perfetto,dev.perfetto, and the names of stdlib packages) arerejected.
server(required forserver): mirrors the UI's extension serverconfiguration schema, minus fields that make no sense in a trace
(
auth,origin,enabled):{"type": "https", "url": ..., "enabled_modules": [...]}{"type": "github", "repo": ..., "ref": ..., "path": ..., "enabled_modules": [...]}Authentication always starts as "none"; if the server needs credentials the
user supplies them in the install dialog, exactly as with shared server
links today.
startup_commandsis valid in both variants. Startup commands are aper-trace concept and deliberately not an extension server feature (see
Alternatives); a
server-variant trace still needs them to, say, run amacro that the referenced server provides.
Per-entry:
inlinevsfileEvery content list (
macros,sql_modules,proto_descriptors,startup_commands) holds tagged entries that either embed their payload orreference a separate archive member. Mixing is allowed within a list:
fileentry payloads by list:sql_modulesproto_descriptorsFileDescriptorSet(no base64)macrosstartup_commandsMacro and command shapes are exactly the UI's existing settings shapes:
a macro is
{id, name, run: [{id, args}]}and a command invocation is{id, args: string[]}, so content is copy-pasteable between trace sidecars,settings, and extension server responses.
Worked example
An AndroidX Benchmark output archive:
{ "perfetto_metadata": { "version": 1, "extensions": { "type": "inline", "namespace": "androidx.benchmark", "sql_modules": [ { "type": "file", "name": "androidx.benchmark.frames", "path": "extensions/frames.sql" } ], "proto_descriptors": [ {"type": "file", "path": "extensions/track_event_ext.pb"} ], "macros": [ {"type": "file", "path": "extensions/macros.json"} ], "startup_commands": [ { "type": "inline", "id": "androidx.benchmark.FocusFrameTimeline", "args": [] }, { "type": "inline", "id": "dev.perfetto.RunQueryAndShowTab", "args": [ "SELECT * FROM androidx_benchmark_frame_metrics" ] } ] } } }After loading this archive:
trace_processor_shell benchmark-trace.zipcan immediately runINCLUDE PERFETTO MODULE androidx.benchmark.frames;— the bundled metricswork in batch pipelines with zero extra configuration ([FR] traceprocessor API to bundle v2 metrics into traces #6228).
TrackEventextension fields decode into theargstable becausethe descriptors registered before the proto trace was parsed.
the trace's startup commands, and (on accept) pins/expands the frame tracks
and opens the metrics query tab (FR: allow including UI macros/startup commands in trace files #1342).
Trace processor design
Parsing and validation
The
perfetto_metadatareader(
src/trace_processor/plugins/perfetto_metadata/) parsesextensionsintoTraceMetadataState. Validation enforced at parse time:typevalues,servertogether with inline content lists, missing
namespace, etc.);Validation failures fail the load. Unlike trace data, the sidecar is an
authored artifact; a malformed one is an authoring bug that should be loud.
Claiming
file-referenced archive membersToday, an archive member of unknown type is a hard error: the forwarding
parser rejects it with "Unknown trace type". A bundled
.sqlfile or rawdescriptor would fail the whole load. The archive readers (zip, tar) therefore
claim members referenced by
fileentries: because the metadata membersorts first (archive entries are parsed in priority order with
perfetto_metadataat the front), the set of claimed paths is known beforeany other member is processed. Claimed members are routed into the extensions
state instead of being type-sniffed and forwarded as traces.
Errors:
fileentry referencing a member not present in the archive;filesarray;fileentries in a non-archive context (e.g. a standalone metadata filepassed alongside individual trace files) — inline entries still work there.
SQL module registration (#6228)
At end-of-file processing, trace processor registers the trace's SQL modules
as a PerfettoSQL package named after
namespace, through the same path as theexisting
TraceProcessor::RegisterSqlPackage()API. That path already rejectsname collisions with the stdlib and previously registered packages; for
trace-bundled packages the error is converted into "skip + stat" (a new
statsentry) rather than failing the load, and overriding is never allowed —trace content can never shadow the stdlib or user-registered packages.
Modules are registered, not executed: SQL only runs when something issues
INCLUDE PERFETTO MODULE. Trace processor never executes anything from thesidecar of its own accord; the complete list of effects in trace processor is
(a) lazy SQL package registration and (b) descriptor pool merges. Macros,
startup commands, and server references are inert outside the UI.
Proto descriptor registration
Descriptors register into the trace processor descriptor pool via the same
mechanism as the in-stream
extension_descriptorpacket(
AddFromFileDescriptorSetwith message merging). Because the metadata memberis parsed before all other members, the descriptors are live before any proto
trace tokenizes, so extended
TrackEventfields decode intoargsin traceprocessor itself.
Unlike extension-server descriptors — which the UI must deliver to trace
processor before parsing starts — trace-bundled descriptors need no delivery
step at all: they register wherever and however the trace is parsed,
including
trace_processor_shellinvocations that involve no UI.Exposing extensions to the UI
Trace processor stores the resolved extensions section —
fileentriesmaterialized into their inline equivalents, except descriptor payloads which
are replaced by
{"size": N}stubs since trace processor already consumedthem — as a row in the
metadatatable under the keytrace_extensions,using the existing dynamic-metadata mechanism.
The UI reads it with plain SQL after load, the same way it already reads
timezone_off_minsand friends:This needs no new RPC surface, works identically in Wasm and HTTP-RPC mode,
and the JSON passthrough means UI-facing schema growth (new keys) requires no
trace processor changes.
UI design
Loading
After the trace loads, the UI queries
trace_extensionsand parses it with azod schema reusing the existing macro / command-invocation / extension-server
schemas. A malformed row is logged and ignored (trace processor already
validated the authored file; this is defense in depth only).
Macros (passive — no prompt)
Inline macros register as trace-scoped commands (auto-disposed when the
trace closes), unlike extension-server macros which are app-lifetime. If a
macro's id collides with an existing command — a core command, a settings
macro, or an installed server's macro — the trace's macro is skipped with a
console warning: trace content never shadows existing definitions.
Registering a macro has no effect until something invokes it, which is why no
consent is needed at this stage.
Server references
The
servervariant reuses the existing share-link install flow(
addServer=<base64>) verbatim:enabled → nothing to do, no prompt; its content is already loaded.
enabled_modulesmissing → the edit-servermodal opens with the module sets merged for review.
reference (auth empty).
The dialog is the consent: nothing is fetched or persisted until the user
confirms, and the resulting entry is an ordinary user-added server in
settings. Declining simply means the server's macros/SQL are unavailable;
dependent startup commands will fail visibly (below).
Startup commands (gated — prompt)
Startup commands auto-execute, so they are the one part of the sidecar behind
a consent gate. A new global tri-state setting controls trace-sourced startup
commands:
ask(default): a per-trace dialog lists the commands (id+args) withRun / Skip, plus shortcuts to flip the global setting to
alwaysornever.always: run without prompting.never: ignore, with a notification that the trace carried commands.Independent of the setting, trace-sourced commands are always run with
allowlist enforcement (the existing startup-command allowlist). The user
setting that can disable allowlist enforcement applies only to URL- and
settings-sourced commands. One nuance inherited from the existing
implementation, stated here explicitly: any registered macro is a valid
startup-command entry point, so a trace-bundled macro can be invoked — but
each nested command inside the macro is individually allowlist-checked when
prompts are disabled. A trace cannot smuggle a non-allowlisted command inside
a macro.
Ordering
On trace load:
passive content is in place first, so commands can reference it.
sources are more user-specific and should win: producer defaults, then the
sharer's intent encoded in the URL, then the user's own preferences. All
three run inside the existing prompts-disabled block, and blocked/failed
commands from all sources are reported in the existing issues dialog.
Failure modes and edge cases
through the existing stats-based error UI.
filemember / member claimed twice / claimed member also infiles[]→ load error (authoring bug).handling (toast, cached fallback); dependent startup commands fail into the
issues dialog.
trace_processor_shell/ batch → macros, startup commands, and serverrefs are inert; only SQL modules and descriptors take effect.
extensionsignored entirely (unknown key underversion 1); trace loads as before.
machinery are unaffected; the gate sits in the same code path as the
existing URL/settings sources.
Documentation
This feature touches several existing doc surfaces and finally forces a
reference page for the sidecar itself (currently undocumented):
docs/reference/perfetto-metadata.md: full format reference for theperfetto_metadatasidecar (version,trace_time_clock,files,extensions).docs/getting-started/bundling-analysis.md: walkthrough for traceproducers — building the zip, writing the sidecar, authoring macros/SQL —
linked from the "Converting arbitrary data" guide's next steps.
(
docs/reference/synthetic-track-event.mdalready serves as the advancedsynthetic-trace reference, so no restructuring of
converting.mdisneeded.)
docs/instrumentation/extensions.md: bundledproto_descriptorsas a third descriptor-delivery path (alongside in-stream
extension_descriptorpackets and extension servers).docs/visualization/ui-automation.md: trace-sourced startupcommands, the trust setting, and source ordering.
docs/visualization/extension-servers.md: referencing a serverfrom a trace and the install flow.
docs/visualization/extending-the-ui.md: add trace-bundledextensions to the overview of extension points.
Alternatives considered
A proto packet inside the trace stream (UiState-style)
Define a packet (or extend
UiState) carrying macros/commands/SQL in thetrace data itself.
Pro:
.perfetto-tracefiles without a zip envelope.Con:
mode. Unstrippable without rewriting the trace, invisible to archive
tooling, and locks UI concepts into the stable trace proto schema.
UI reads the sidecar itself
Have the UI unzip the archive and parse
perfetto_metadatabefore handingbytes to trace processor.
Pro:
Con:
([FR] traceprocessor API to bundle v2 metrics into traces #6228), so the parsing would exist on both sides.
Inline extension-server manifest
Model the trace as an extension server: embed a manifest (name, namespace,
features, modules) where features carry
type: "inline"payloads, reusing theserver manifest format wholesale.
This was seriously considered — it maximizes schema reuse and namespace rules
come from the manifest for free — but rejected:
subsets of a long-lived server. A trace is a flat, one-shot payload; the
indirection is pure overhead (we had already reduced it to "only a
defaultmodule allowed" before abandoning it).
trace content is scoped to one trace.
would mean an installed server can auto-run commands on every future
trace — a standing-consent trap. They must stay a per-trace concept.
A separate sidecar file format
A new
perfetto_extensions.jsonnext toperfetto_metadata.json.Con:
versioning story, for no benefit.
perfetto_metadataalready owns"configure how this archive is interpreted" and its ignore-unknown-keys
behavior under version 1 provides backwards compatibility for free.
Open questions
so a trace cannot silently start pulling different content over time?
whether the UI should toast on it.
trace_extensionsexpose full descriptor payloads to the UI insteadof size stubs (e.g. for a future "inspect bundled extensions" page)?
trace_processor_shellgrow a flag to ignore or dump the extensionssection (e.g.
--extensions=ignore|dump)?💬 Discussion Guidelines:
Beta Was this translation helpful? Give feedback.
All reactions