This specification describes an API that allows web applications to control a sampling profiler for measuring client JavaScript execution times.


Complex web applications currently have limited visibility into where JS execution time is spent on clients. Without the ability to efficiently collect stack samples, applications are forced to instrument their code with profiling hooks that are imprecise and can significantly slow down execution. By providing an API to manipulate a sampling profiler, applications can gather rich execution data for aggregation and analysis with minimal overhead.


        const profiler = await performance.profile({ sampleInterval: 10 });
        for (let i = 0; i < 1000000; i++) {
        const trace = await profiler.stop();


A sample is a descriptor of the instantaneous state of execution at a given point in time. Each sample is associated with a stack.

A stack is a list of frames that MUST be ordered sequentially from outermost to innermost frame.

A frame is an element in the context of a stack containing information about the current execution state.

Profiling Sessions

A profiling session is an abstract producer of samples. Each session has:

  1. A state, which is one of {started, paused, stopped}.
  2. A sample interval, defined as the periodicity at which the session obtains samples.

    The UA is NOT REQUIRED to take samples at this rate. However, it is RECOMMENDED that sampling is prioritized to take samples at this rate to produce higher quality traces.

  3. An agent to profile.
  4. A time origin that samples' timestamps are measured relative to.
  5. A ProfilerTrace storing captured samples, with a finite sample buffer size limit.

Multiple profiling sessions on the same page SHOULD be supported.


In the started state, the UA SHOULD make a best-effort to capture samples by executing the take a sample algorithm each time the sample interval has elapsed. In the paused and stopped states, the UA SHOULD NOT capture samples.

Profiling sessions MUST begin in the started state.

The UA MAY move a session from started to paused, and from paused to started.

The user agent is RECOMMENDED to pause the sampling of a profiling session if the browsing context is not in the foreground.

The UA MAY move a session to stopped from any state.

A stopped session MUST NOT move to the started or paused states.

Processing Model

To take a sample given a profiling session, perform the following steps:

  1. If the length of ProfilerTrace.samples is greater than or equal to the sample buffer size limit associated with the profiling session, fire a new event of type samplebufferfull to the associated Profiler, move the state to stopped, and return.
  2. Let sample be a new ProfilerSample.
  3. Set the ProfilerSample.timestamp property of sample to the current high resolution time relative to the profiling session's time origin.
  4. Let stack be the execution context stack associated with the profiling session's agent.
  5. Set the ProfilerSample.stackId property of sample to the result of the get a stack ID algorithm on stack.
  6. Add sample to the ProfilerTrace.samples associated with the session's ProfilerTrace.

To get a stack ID given an execution context stack bound to stack, perform the following steps:

  1. If stack is empty, return undefined.
  2. Let head be the top element of stack, and tail be the remainder of stack after removing its top element.
  3. Let parentId be the result of calling get a stack ID recursively on tail.
  4. Let frameId be the result of calling get a frame ID on head.
  5. If frameId is undefined, return parentId.
  6. Let profilerStack be a new ProfilerStack with ProfilerStack.frameId equal to frameId, and ProfilerStack.parentId equal to parentId.
  7. Return the result of running get an element ID on profilerStack and ProfilerTrace.stacks.

To get a frame ID given an execution context bound to context, perform the following steps:

  1. If the realm associated with context does not match the realm that created the profiling session, return undefined.
  2. If the response containing the ScriptOrModule associated with context has a type other than CORS-same-origin, return undefined.
  3. Let frame be a new ProfilerFrame.
  4. Let instance be equal to the function instance associated with context, or null.
  5. Set of frame to the result of running the following algorithm:
    1. If instance is not null, return the function instance name associated with it.
    2. Otherwise, return null.
  6. Let resourceString be equal to the URL of the ScriptOrModule associated with context.

    We likely ought to make this association more rigorous, particularly for the cases of data URLs and eval.

  7. Set ProfilerFrame.resourceId to the result of running get an element ID on resourceString and ProfilerTrace.resources.
  8. Set ProfilerFrame.line of frame to the 1-based index of the line at which instance is defined in its containing script.
  9. Set ProfilerFrame.column of frame to the 1-based index of the column at which instance is defined in its containing script.
  10. Return the result of running get an element ID on frame and ProfilerTrace.frames.

To get an element ID for an item in a list, run the following steps:

  1. If there exists an element in list component-wise equal to item, return its index.
  2. Otherwise, append item to the end of list and return its index.

The Profiler Interface

      interface Profiler : EventTarget {
        readonly attribute DOMHighResTimeStamp sampleInterval;
        readonly attribute boolean stopped;

        Promise<ProfilerTrace> stop();

Each Profiler MUST be associated with exactly one profiling session.

The sampleInterval attribute MUST reflect the sample interval of the associated profiling session expressed as a DOMHighResTimeStamp.

The stopped attribute MUST be true if and only if the profiling session has state stopped.

stop() method

Stops the profiler and returns a trace. This method MUST run these steps:

  1. If the associated profiling session's state is stopped, return the ProfilerTrace captured by the session.
  2. Set the profiling session's state to stopped.
  3. Return a ProfilerTrace from the profiling session.

Any samples taken after stop() is invoked SHOULD NOT be included by the profiling session.

The ProfilerTrace Dictionary

      typedef DOMString ProfilerResource;

      dictionary ProfilerTrace {
        required sequence<ProfilerResource> resources;
        required sequence<ProfilerFrame> frames;
        required sequence<ProfilerStack> stacks;
        required sequence<ProfilerSample> samples;

The resources attribute MUST contain exactly all unique URLs captured during the profiling session.

The frames attribute MUST contain all frames recorded during the profiling session.

The stacks attribute MUST contain all stacks recorded during the profiling session.

The samples attribute MUST return all samples recorded during the profiling session, ordered from earliest to latest recorded.

Inspired by the V8 trace event format and Gecko profile format, this representation is designed to be easily and efficiently serializable.

The ProfilerSample Dictionary

        dictionary ProfilerSample {
          required DOMHighResTimeStamp timestamp;
          unsigned long long stackId;

The timestamp attribute MUST be the current high resolution time relative to the profiling session's time origin when the sample was recorded.

If the sample captured a stack, the stackId attribute MUST reference a valid index into the parent ProfilerTrace.stacks, where ProfilerTrace.stacks[stackId] is the leaf stack associated with this sample. Otherwise, it MUST be undefined.

The ProfilerStack Dictionary

        dictionary ProfilerStack {
          unsigned long long parentId;
          required unsigned long long frameId;

The parentId attribute MUST be either null if the associated frame is at the bottom of the stack, OR a valid index into the parent ProfilerTrace.stacks, where ProfilerTrace.stacks[parentId] is a parent substack containing outer frames.

The frameId attribute MUST reference a valid index into the parent ProfilerTrace.frames, where ProfilerTrace.frames[frameId] is the frame associated with this stack's top.

The ProfilerFrame Dictionary

        dictionary ProfilerFrame {
          DOMString name;
          required unsigned long long resourceId;
          required unsigned long long line;
          required unsigned long long column;

Let ec be the execution context that the frame models.

Let instance be the function instance associated with ec, or null.

A more rigorous definition needs to be applied to the line and column attributes, particularly to define them for inline, eval, and toplevel scripts.

The ProfilerInitOptions dictionary

      dictionary ProfilerInitOptions {
        required DOMHighResTimeStamp sampleInterval;
        required unsigned long maxBufferSize;

ProfilerInitOptions MUST support the following fields:

Extensions to the Performance Interface

      partial interface Performance {
        Promise<Profiler> profile(ProfilerInitOptions options);

profile() method

Creates a new Profiler associated with a newly started profiling session. It MUST run these steps:

  1. Assert: The value of cross-origin isolated for the agent cluster of the current realm's agent is true.
  2. If options' {{ProfilerInitOptions/sampleInterval}} is less than 0, reject the promise with a RangeError.
  3. Get the policy value for "js-profiling" in the Document. If the result is false, reject the promise with a "NotAllowedError" DOMException.
  4. Create a new profiling session where:
    1. The associated sample interval is set to either ProfilerInitOptions.sampleInterval OR the next lowest interval supported by the UA.
    2. The associated time origin is equal to the time origin of the current global object.
    3. The associated ProfilerTrace is created with size options' {{ProfilerInitOptions/maxBufferSize}}.
  5. Return a Promise that yields a Profiler instance once the profiling session has started.

Document Policy

This spec defines a configuration point in Document Policy with name js-profiling. Its type is boolean with default value false.

Document policy is leveraged to give UAs the ability to avoid storing required metadata for profiling when the document does not explicitly request it. While this metadata could conceivably be generated in response to a profiler being started, we store this bit on the document in order to signal to the engine as early as possible (as profiling early in page load is a common use case). This overhead may be non-trivial depending on the implementation, and therefore we default to false.

Privacy and Security

The primary concerns with introducing a sampling profiling for JavaScript are leakage of execution information across cross-origin execution contexts, leakage of cross-origin scripts, and potential timing attacks.

Implementors must take care to ensure that stack frames from cross-origin execution contexts are not leaked, even when invoked synchronously from the frame that initiates profiling. As script execution information from other contexts could be used to infer the state of these contexts (e.g. whether or not the user is logged in), the spec deems this inpermissible.

Including stack frames from functions defined in a cross-origin resource must be performed with caution. The contents of opaque cross-origin scripts should remain inaccessible to UAs, as the resource has not consented to inspection (even with CORP). The spec limits this by requiring all functions included in a trace to be defined in a same-origin resource, or served via CORS.

Lastly, timing attacks remain a concern for any API introducing a new source of high-resolution time information. The spec aims to mitigate this by requiring pages to be cross-origin isolated, providing UAs with a mechanism to process-isolate pages that perform profiling. See [[?HR-Time]]'s discussion on clock resolution.