Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create conditional-tracing-explainer.md #112

Open
wants to merge 4 commits into
base: gh-pages
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions conditional-tracing-explainer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Explainer: conditional tracing

# Overview
Recently, there were two discussions that seem separate but might end up being related to the same use case.

## More granular information in Long Animation Frames & event timing
Context: w3c/long-animation-frames#3

A script entry point is often not granular enough, e.g. it can be a React callback that hides all the underlying slow functions,
and also creates faux attribution: the wrapper function is "blamed" for slowness when it just passes the execution to an underlying function.
A similar issue exists for event timing, where the provided timing attributes don't always capture the whole story, and don't always coincide with a more attributable long animation frame.

## Annotated user timing in devtools
Context: w3c/user-timing#111.

Chromium devtools already provides a way to annotate user timing entries by adding details to the user timing `detail` attribute.
Apart from the desire to make those web-exposed annotations somewhat interoperable/standard (kind of like the `console` APIs), a question was raised about the efficiency of user timing entries for the purpose of tracing at high granularity.
Creating user timing entries (mark/measure) has several issues:
- Those functions receive a dictionary with each call, causing a GC load
- User timing entries are buffered in an infinite array by default.
- User timing entries have one global namespace, making it difficult for separate libraries to use them without stepping on each other's toes.

# Proposal: conditional tracing using a `PerformanceTrack`

This proposal is to have a new type of object, that represents a "track" - a namespace of sorts for low-overhead user measurements.
A track is like a mini performance timeline, with the following characteristics:
- Entries are not buffered in the regular performance timeline
- Relevant entry types (`long-animation-frame`, `event`) can buffer and report track entries that are reported during the lifetime of their corresponding entries
- Apart from the entry name, all the other annotations are per-track rather than per-entry, to avoid overhead.
- All devtools-specific annotations are done using the `console` object.

## Shape:

```js
const track = new PerformanceTrack("framework");

// This buffers new entries for not-yet-created LoAF/event observers.
track.startConditionalBuffering({entryType: "long-animation-frame"});

// At some point, add a mark. This mark does not appear in the performance timeline
track.mark(name);

// At some other point, add another mark
track.mark(name);

// Combines mark->mark as a single entry with duration. This measure does not appear in the performance timeline.
track.measure(name);

// Adds devtools-specific information about a given mark/measure
console.describe(trace.mark(name), details);

// Returns a function that calls inner_function and measures it duration.
// Addeded to LoAF scripts as an entry point
track.bind(inner_function, name, [additional_args]);

const observer = new PerformanceObserver(entries => {
for (const loaf : entries.getEntriesByType("long-animation-frame") {
// This will have all the marks & measures from the attached tracks
for (const trackEntry of loaf.trackEntries) {
// report or use the track entry for attribution
}

// This will have now also include functions captured with track.bind
loaf.scripts
}
});
observer.observe({entryType: "long-animation-frame"});

// This adds "tracks" to the LoAF entries observed by `observer`.
observer.attachTrack(track);

// Gives devtools additional information about this track
console.describeTrack(track, { color: "blue" });
```

## Some notes:
We use an object rather than something like `performance.trace` for the following reasons:
- It allows adding detailed annotations without having to create a dictionary during performance critical execution.
- It creates a natural namespace: both the reporter and the observer have to access the same object, rather than rely on (leaky) strings
- From an ergonomic perspective, adding a global `performance.*` function might be confusing as the result doesn't end up in the performance timeline directly.
- Using the `bind` function doesn't guarantee attribution, because it doesn't include microtasks (unlike script entry points). This needs to be handled with care when using it for "blame"
- It might look confusing to mix web-facing and `console` APIs in the same function, however it's intentional as devtools is an "augmentation" here.


Loading