├── .nojekyll ├── .pr-preview.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── PRIVACY_AND_SECURITY.md ├── README.md ├── doc ├── state-extensions-slides.pdf ├── tpac-2018-slides.pdf └── tpac-2021-slides.pdf ├── index.html ├── markers.md ├── perf.html.png └── w3c.json /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/js-self-profiling/f7ba71f587976a9240cde8c486943e1d34c44140/.nojekyll -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.html", 3 | "type": "respec" 4 | } 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the Web Platform Incubator Community Group, governed by the [W3C Community License 4 | Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To contribute, you must join 5 | the CG. 6 | 7 | If you are not the sole contributor to a contribution (pull request), please identify all 8 | contributors in the pull request's body or in subsequent comments. 9 | 10 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 11 | 12 | ``` 13 | +@github_username 14 | ``` 15 | 16 | If you added a contributor by mistake, you can remove them in a comment with: 17 | 18 | ``` 19 | -@github_username 20 | ``` 21 | 22 | If you are making a pull request on behalf of someone else but you had no part in designing the 23 | feature, you can remove yourself with the above syntax. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors under the 2 | [W3C Software and Document 3 | License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). Contributions to 4 | Specifications are made under the [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 5 | -------------------------------------------------------------------------------- /PRIVACY_AND_SECURITY.md: -------------------------------------------------------------------------------- 1 | # Answers to [Security and Privacy Questionnaire](https://www.w3.org/TR/security-privacy-questionnaire/) 2 | 3 | ### 3.1 Does this specification deal with personally-identifiable information? 4 | 5 | No. 6 | 7 | 8 | ### 3.2 Does this specification deal with high-value data? 9 | 10 | No. 11 | 12 | 13 | ### 3.3 Does this specification introduce new state for an origin that persists across browsing sessions? 14 | 15 | No. 16 | 17 | 18 | ### 3.4 Does this specification expose persistent, cross-origin state to the web? 19 | 20 | No. 21 | 22 | 23 | ### 3.5 Does this specification expose any other data to an origin that it doesn’t currently have access to? 24 | 25 | No. The main concern here would be with cross-origin scripts, whose contents 26 | would not be visible to script ordinarily unless, they passed a CORS check. The 27 | spec therefore mandates such resources to pass a CORS check in order to be 28 | included in profiles. 29 | 30 | 31 | ### 3.6 Does this specification enable new script execution/loading mechanisms? 32 | 33 | No. 34 | 35 | 36 | ### 3.7 Does this specification allow an origin access to a user’s location? 37 | 38 | No. 39 | 40 | 41 | ### 3.8 Does this specification allow an origin access to sensors on a user’s device? 42 | 43 | No. 44 | 45 | 46 | ### 3.9 Does this specification allow an origin access to aspects of a user’s local computing environment? 47 | 48 | Not explicitly. However, the UA is free to select which sampling intervals it 49 | supports- conceivably, a UA could choose a sampling interval "optimal" for 50 | a device, such as the system clock interrupt interval. 51 | 52 | 53 | ### 3.10 Does this specification allow an origin access to other devices? 54 | 55 | No. 56 | 57 | 58 | ### 3.11 Does this specification allow an origin some measure of control over a user agent’s native UI? 59 | 60 | No. 61 | 62 | 63 | ### 3.12 Does this specification expose temporary identifiers to the web? 64 | 65 | No. 66 | 67 | 68 | ### 3.13 Does this specification distinguish between behavior in first-party and third-party contexts? 69 | 70 | No. 71 | 72 | 73 | ### 3.14 How should this specification work in the context of a user agent’s "incognito" mode? 74 | 75 | Semantics should be unchanged. 76 | 77 | 78 | ### 3.15 Does this specification persist data to a user’s local device? 79 | 80 | No. 81 | 82 | 83 | ### 3.16 Does this specification have a "Security Considerations" and "Privacy Considerations" section? 84 | 85 | Yes. 86 | 87 | 88 | ### 3.17 Does this specification allow downgrading default security characteristics? 89 | 90 | No. 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Self-Profiling API Proposal 2 | 3 | - [Specification](https://wicg.github.io/js-self-profiling) 4 | - [Web Platform Tests](https://wpt.fyi/results/js-self-profiling?label=experimental&label=master&aligned) 5 | - Slides and minutes from TPAC [2018](https://docs.google.com/document/d/1bYMLTkjcyOZR5Jt3vrulzMSoS32zOFtwyH33f6hW_C8/edit#heading=h.se3632b9q5z), [2019](https://docs.google.com/document/d/1-xMvUHAjqhQdegNqupxlqsLbfPHWq5MJ0iySg9Z1KBs/edit#heading=h.uq5au3okuh8b), [2020](https://docs.google.com/document/d/1inejuvPONXPOLKTCcUzOBhPh6QOckMcltnR-E3xyZVQ/edit#heading=h.9uskdhbxdwkg) 6 | - GitHub issues + [WICG discourse thread](https://discourse.wicg.io/t/proposal-an-api-to-allow-webpage-javascript-to-profile-its-own-performance/2818) 7 | 8 | ## Motivation 9 | 10 | Currently it is difficult for web developers to understand how their applications perform in the wide variety of conditions encountered on real user devices. A programmable JS profiling API is needed to collect JS profiles from real end-user environments. 11 | 12 | A native self-profiling API for JS code would also allow web developers to efficiently find hotspots in their JS code during page loads and user interactions, to assign CPU budgets to individual JS-implemented features on the page, to find unnecessary work being done on the client, and to find low-priority JS code executing in the background and wasting device power. 13 | 14 | Currently JS self-profiling can be accomplished by instrumenting individual JS functions with timing code but this is cumbersome, bloats JS size, changes the code (potentially altering performance characteristics), adds overhead from timing calls, and risks missing out on hotspots in unexpected corners. 15 | 16 | ### Facebook's Profiler Polyfill 17 | 18 | In an attempt to polyfill the missing self-profiling functionality, Facebook built and deployed its own in-page JS profiler implemented with JavaScript and SharedArrayBuffers. This JS profiler was implemented using a worker thread that signaled to the main thread when it needed to record its current stack. The worker thread would toggle a “capture stack now” flag in a SharedArrayBuffer every few milliseconds, and the value of this flag was read by instrumentation code that was inserted at transpilation time into the beginning of interesting JS functions running on the main thread. If the instrumentation code saw that the flag was set, it would capture the current JS stack (using the Error object) and add it to the running profile. 19 | 20 | This JS profiler was enabled for only a small percentage of Facebook users, and only instrumented functions of 10 statements or more in order to limit performance impact and to limit the quantity of profiling data collected. Nevertheless, we found it extremely valuable for understanding Facebook.com's performance in the field and for finding optimization opportunities. 21 | 22 | This polyfill implementation had some downsides: 23 | 24 | * The size overhead of the added instrumentation required limiting the fraction of functions instrumented (resulting in incomplete coverage) 25 | * The sampling “interrupt” was not instant so stacks were collected from the **_next_** instrumented function which added a lot of noise to the dataset and made it difficult to reason about 26 | * The performance overhead limited the sampling frequency and fraction of pageloads profiled 27 | * Our design depended on SharedArrayBuffers, which have been disabled on the Web for the time-being, so the profiler is disabled for now 28 | 29 | A browser-implemented profiling API would avoid these downsides. 30 | 31 | ### Wider Industry Interest 32 | 33 | It is cumbersome to manage instrumentation for performance measurement, regardless of whether it is inserted by build-time tooling (like Facebook's polyfill above) or inserted by hand in functions of interest. This API eliminates the need to maintain performance instrumentation and therefore allows smaller sites to deploy in-page JS profiling. 34 | 35 | We also expect that third-party analytics providers will offer libraries or infrastructure to record, ingest, aggregate and automatically analyze the collected profiles for optimization opportunities, thus further lowering the barrier to entry. 36 | 37 | Finally, several other Web properties with large codebases have expressed interest to us for using this API to better monitor their webapps performance in the field. 38 | 39 | ## API Overview 40 | 41 | Before developers can make use of the profiler, they'll first have to signal to the UA that they wish to profile by exposing the `Document-Policy: js-profiling` header. 42 | 43 | > This header ensures that any UA-specific profiling overhead is incurred only on loads that may profile. 44 | 45 | Developers will then be able to spin up a new `profiler` via `new Profiler(options)`, where `options` contains the following required fields: 46 | 47 | - `sampleInterval`: Target sample rate (in ms per sample) 48 | - The UA may choose a different sample rate than the one that the user requested (which must be the next lowest valid sampling interval). 49 | - The true sample rate of the profiler may be accessible via `profiler.sampleInterval`. 50 | - `maxBufferSize`: Maximum sample capacity (in samples) 51 | - If the sample buffer capacity is reached, the `samplebufferfull` event is sent to the `profiler` object. This stops profiling immediately. 52 | - The trace may still be collected via `profiler.stop()` when this occurs. 53 | 54 | Creating a new profiler starts profiling immediately. Once the developer wishes to stop profiling, calling `profiler.stop()` returns a promise containing a trace object that can be sent to a server for aggregation. 55 | 56 | > This trace is encoded in a trie format similar to the GeckoProfiler and Chrome tracing formats -- see the appendix for an overview of how stacks are represented. 57 | 58 | An example of how you might want to profile a pageload for server-side analysis is below: 59 | 60 | ```javascript 61 | const profiler = new Profiler({ 62 | sampleInterval: 10, // Target sampling every 10ms 63 | maxBufferSize: 10 * 100, // Cap at ~10s worth of samples 64 | }); 65 | 66 | async function collectAndSendTrace() { 67 | if (profiler.stopped) return; 68 | 69 | const trace = await profiler.stop(); 70 | const traceJson = JSON.stringify({ 71 | timing: performance.timing, 72 | trace, 73 | }); 74 | 75 | // Send the trace JSON to a server via Fetch/XHR 76 | sendTrace(traceJson); 77 | } 78 | 79 | profiler.addEventListener('samplebufferfull', collectAndSendTrace); 80 | window.addEventListener('load', collectAndSendTrace); 81 | 82 | // Rest of the page's JS initialization logic 83 | ``` 84 | 85 | ## Markers Extensions 86 | 87 | See [markers](markers.md) for detailed description of the proposal. 88 | 89 | ## Privacy and Security 90 | 91 | See the [Privacy and Security](https://wicg.github.io/js-self-profiling/#privacy-security) section of the spec. 92 | 93 | ## Appendix: Profile Format 94 | 95 | A trie-like approach is chosen for representing traces obtained from the profiler. There are examples of trie-based approaches in browsers today: 96 | 97 | * [Chrome's Trace Event format, specifically the stackFrames field](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.yr703knxre9f) 98 | * [Firefox's Gecko Profiler format, specifically the stackTable field](https://github.com/devtools-html/perf.html/blob/master/docs-developer/gecko-profile-format.md#source-data-format) 99 | 100 | The API aims to provide deduplication of script resource URLs, stack frames, and stack sequences (through the aforementioned trie approach) to reduce memory pressure and trace size when sent over the network. 101 | 102 | The specification's processing model provides detail on how these traces are constructed. An example (encoded in JSON) can be found below: 103 | 104 | ```javascript 105 | { 106 | "resources" : [ 107 | "https://static.xx.fbcdn.net/rsrc.php/v3/yW/r/ZgaPtFDHPeq.js", 108 | "https://static.xx.fbcdn.net/rsrc.php/v3iMKu4/yW/l/en_US-i/gSq3sO3PcU1.js" 109 | ], 110 | "stacks" : [ 111 | { 112 | "frameId" : 0 113 | }, 114 | { 115 | "frameId" : 1, 116 | "parentId" : 0 117 | }, 118 | { 119 | "frameId" : 2, 120 | "parentId" : 1 121 | } 122 | ], 123 | "samples" : [ 124 | { 125 | "timestamp" : 1551.73499998637, 126 | "stackId": 2 127 | }, 128 | { 129 | "timestamp" : 1576.83999999426, 130 | "stackId": 1 131 | }, 132 | { 133 | "timestamp" : 1601.90499993041 134 | } 135 | ], 136 | "frames" : [ 137 | { 138 | "name" : "b", 139 | "resourceId" : 0, 140 | "line" : 23, 141 | "column" : 169 142 | }, 143 | { 144 | "name" : "l", 145 | "resourceId": 1, 146 | "line" : 313, 147 | "column" : 468 148 | }, 149 | { 150 | "name" : "a", 151 | "resourceId": 1, 152 | "line" : 313, 153 | "column" : 1325 154 | } 155 | ] 156 | } 157 | ``` 158 | 159 | The API may also be combined with other APIs such as [Compression Streams](https://wicg.github.io/compression/) in order to further reduce trace size. 160 | 161 | ### Visualization 162 | 163 | Mozilla's perf.html visualization tool for Firefox profiles or Chrome's trace-viewer (chrome://tracing) UI could be trivially adapted to visualize the data produced by this profiling API. 164 | 165 | ### perf.html 166 | 167 | As an illustration, a screenshot below from Mozilla's perf.html project shows the JS stack aggregation and timeline. It is able to show gaps where JavaScript was not executing, areas where there were long running events (red), and an aggregate view of the samples in the selected time range such as the 15 contiguous samples in function 'user.ts' highlighted in the screenshot below. 168 | 169 | ![Mozilla's perf.html UI for visualizing Gecko profiles](perf.html.png) 170 | 171 | -------------------------------------------------------------------------------- /doc/state-extensions-slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/js-self-profiling/f7ba71f587976a9240cde8c486943e1d34c44140/doc/state-extensions-slides.pdf -------------------------------------------------------------------------------- /doc/tpac-2018-slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/js-self-profiling/f7ba71f587976a9240cde8c486943e1d34c44140/doc/tpac-2018-slides.pdf -------------------------------------------------------------------------------- /doc/tpac-2021-slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/js-self-profiling/f7ba71f587976a9240cde8c486943e1d34c44140/doc/tpac-2021-slides.pdf -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS Self-Profiling API 6 | 7 | 30 | 31 | 32 |
33 |

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

36 |
37 |
38 |
39 |
40 |

Introduction

41 |

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

50 |
51 |

Examples

52 |

The following example demonstrates how a user may profile an expensive operation, gathering JS execution samples every 10ms. The trace can be sent to a server for analysis to debug outliers and JS execution characteristics in aggregate.

53 |
 54 |         const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
 55 |         const start = performance.now();
 56 |         for (let i = 0; i < 1000000; i++) {
 57 |              doWork();
 58 |         }
 59 |         const duration = performance.now() - start;
 60 |         const trace = await profiler.stop();
 61 |         const traceJson = JSON.stringify({
 62 |           duration,
 63 |           trace,
 64 |         });
 65 |         sendTrace(traceJson);
 66 |         
67 |

Another common real-world scenario is profiling JS across a pageload. This example profiles the onload event, sending performance timing data along with the trace.

68 |
 69 |         const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
 70 | 
 71 |         window.addEventListener('load', async () => {
 72 |           const trace = await profiler.stop();
 73 |           const traceJson = JSON.stringify({
 74 |             timing: performance.timing,
 75 |             trace,
 76 |           });
 77 |           sendTrace(traceJson);
 78 |         });
 79 | 
 80 |         // Rest of the page's JS initialization logic
 81 |         
82 |
83 |
84 |
85 |
86 |
87 |

Definitions

88 |

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

93 |

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

97 |

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

101 |
102 |
103 |

Profiling Sessions

104 |

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

107 |
    108 |
  1. A state, which is one of {started, paused, stopped}.
  2. 109 |
  3. 110 | A sample interval, defined as the periodicity at which the session obtains samples. 111 |

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

    116 |
  4. 117 |
  5. An agent to profile.
  6. 118 |
  7. A realm to profile.
  8. 119 |
  9. A time origin that samples' timestamps are measured relative to.
  10. 120 |
  11. A sample buffer size limit.
  12. 121 |
  13. A ProfilerTrace storing captured samples.
  14. 122 |
123 |

124 | Multiple profiling sessions on the same page SHOULD be supported. 125 |

126 |
127 |

States

128 |

129 | In the started state, the UA SHOULD make a best-effort to 130 | capture samples by executing the take a sample algorithm [= in 131 | parallel =] each time the sample interval has elapsed. 132 | In the paused and stopped states, the UA SHOULD NOT capture samples. 133 |

134 |

135 | Profiling sessions MUST begin in the started state. 136 |

137 |

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

140 |

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

143 |

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

146 |
147 |
148 |
149 |

Processing Model

150 |

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

153 |
    154 |
  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. 155 |
  3. Let sample be a new ProfilerSample.
  4. 156 |
  5. Set the ProfilerSample.timestamp property of sample to the current high resolution time relative to the profiling session's time origin.
  6. 157 |
  7. Let stack be the execution context stack associated with the profiling session's agent.
  8. 158 |
  9. Set the ProfilerSample.stackId property of sample to the result of the get a stack ID algorithm on stack.
  10. 159 |
  11. Add sample to the ProfilerTrace.samples associated with the session's ProfilerTrace.
  12. 160 |
161 |

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

    164 |
  1. If stack is empty, return undefined.
  2. 165 |
  3. Let head be the top element of stack, and tail be the remainder of stack after removing its top element.
  4. 166 |
  5. Let parentId be the result of calling get a stack ID recursively on tail.
  6. 167 |
  7. Let frameId be the result of calling get a frame ID on head.
  8. 168 |
  9. If frameId is undefined, return parentId.
  10. 169 |
  11. Let profilerStack be a new ProfilerStack with ProfilerStack.frameId equal to frameId, and ProfilerStack.parentId equal to parentId. 170 |
  12. Return the result of running get an element ID on profilerStack and ProfilerTrace.stacks.
  13. 171 |
172 |

173 |

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

    176 |
  1. If the [= realm =] associated with context does not match the realm associated with the profiling session, return undefined.
  2. 177 |
  3. Let instance be equal to the function instance associated with context.
  4. 178 |
  5. Let scriptOrModule be equal to the ScriptOrModule associated with context.
  6. 179 |
  7. 180 | Let |attributedScriptOrModule : ScriptOrModule| be equal to the result of running the following algorithm: 181 |
      182 |
    1. If |scriptOrModule| is non-null, return |scriptOrModule|.
    2. 183 |
    3. If |instance| is a built-in function object, return the ScriptOrModule containing the function that invoked |instance|. 184 |

      185 | The purpose of the above logic is to ensure that built-in functions invoked by inaccessible scripts are not exposed in traces, by using the ScriptOrModule that invoked them for attribution. 186 |

      187 |

      188 | "[...] the ScriptOrModule containing the function that invoked |instance|" should be defined more rigorously. We could leverage the top-most execution context on the stack that defines a ScriptOrModule to provide this, but it's not ideal -- there may (theoretically) be other mechanisms for a builtin to be enqueued on the execution context stack, in which case the attribution would be invalid. 189 |

      190 |
    4. 191 |
    5. Otherwise, return null.
    6. 192 |
    193 |
  8. 194 |
  9. If |attributedScriptOrModule| is null, return undefined.
  10. 195 |
  11. Let |attributedScript : Script| be the [= script =] obtained from |attributedScriptOrModule|.[[\HostDefined]].
  12. 196 |
  13. 197 | If |attributedScript| is a [= classic script =] and its muted errors boolean is equal to true, return undefined. 198 |

    199 | This check ensures that we avoid including stack frames from cross-origin scripts served in a CORS-cross-origin response. We may want to consider renaming muted errors to better reflect this use case. 200 |

    201 |

  14. 202 |
  15. Let frame be a new ProfilerFrame.
  16. 203 |
  17. Set ProfilerFrame.name of frame to the function instance name associated with |instance|.
  18. 204 |
  19. 205 | If |scriptOrModule| is non-null: 206 |
      207 |
    1. Let script be the script obtained from scriptOrModule.[[\HostDefined]].
    2. 208 |
    3. Let resourceString be equal to the base URL of script.
    4. 209 |
    5. Set ProfilerFrame.resourceId to the result of running get an element ID on resourceString and ProfilerTrace.resources.
    6. 210 |
    7. Set ProfilerFrame.line of frame to the 1-based index of the line at which instance is defined in |script|.
    8. 211 |
    9. Set ProfilerFrame.column of frame to the 1-based index of the column at which instance is defined in |script|.
    10. 212 |
    213 |
  20. Return the result of running get an element ID on frame and ProfilerTrace.frames.
  21. 214 |
215 |

216 |

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

    219 |
  1. If there exists an element in list component-wise equal to item, return its index.
  2. 220 |
  3. Otherwise, append item to the end of list and return its index.
  4. 221 |
222 |

223 |
224 | 225 |
226 |

The Profiler Interface

227 |
228 |       [Exposed=Window]
229 |       interface Profiler : EventTarget {
230 |         readonly attribute DOMHighResTimeStamp sampleInterval;
231 |         readonly attribute boolean stopped;
232 | 
233 |         constructor(ProfilerInitOptions options);
234 |         Promise<ProfilerTrace> stop();
235 |       };
236 |       
237 |

Each Profiler MUST be associated with exactly one profiling session.

238 |

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

239 |

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

240 |

241 | {{Profiler}} is only exposed on {{Window}} until consensus is reached on [[Permissions-Policy]] and {{Worker}} integration. 242 |

243 |
244 |

new Profiler(options)

245 | new Profiler(options) runs the following steps given an object options of type ProfilerInitOptions: 246 |
    247 |
  1. If options' {{ProfilerInitOptions/sampleInterval}} is less than 0, throw a RangeError.
  2. 248 |
  3. Get the policy value for "js-profiling" in the Document. If the result is false, throw a "NotAllowedError" DOMException.
  4. 249 |
  5. Create a new profiling session where:
  6. 250 |
      251 |
    1. The associated sample interval is set to either ProfilerInitOptions.sampleInterval OR the next lowest interval supported by the UA.
    2. 252 |
    3. The associated time origin is equal to the time origin of the current global object.
    4. 253 |
    5. The associated sample buffer size limit is set to {{ProfilerInitOptions/maxBufferSize}}.
    6. 254 |
    7. The associated [= agent =] is set to the surrounding agent.
    8. 255 |
    9. The associated [= realm =] is set to the current realm record.
    10. 256 |
    11. The associated ProfilerTrace is set to «[{{ProfilerTrace/resources}} → «», {{ProfilerTrace/frames}} → «», {{ProfilerTrace/stacks}} → «», {{ProfilerTrace/samples}} → «»]».
    12. 257 |
    258 |
  7. Return a new Profiler associated with the newly created profiling session.
  8. 259 |
260 |

261 |
262 |
263 |

stop() method

264 |

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

267 |
    268 |
  1. If the associated [= profiling session =]'s state is stopped, return [= a promise rejected with =] an "InvalidStateError" DOMException. 269 |
  2. Set the [= profiling session =]'s state to stopped.
  3. 270 |
  4. Let |p:Promise| be [= a new promise =].
  5. 271 |
  6. 272 | Run the following steps [= in parallel =]: 273 |
      274 |
    1. Perform any [= implementation-defined =] work to stop the [= profiling session =].
    2. 275 |
    3. Resolve |p| with the {{ProfilerTrace}} associated with the profiler's [= profiling session =].
    4. 276 |
    277 |
  7. 278 |
  8. Return |p|.
  9. 279 |
280 |

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

283 |
284 |
285 |
286 |

The ProfilerTrace Dictionary

287 |
288 |       typedef DOMString ProfilerResource;
289 | 
290 |       dictionary ProfilerTrace {
291 |         required sequence<ProfilerResource> resources;
292 |         required sequence<ProfilerFrame> frames;
293 |         required sequence<ProfilerStack> stacks;
294 |         required sequence<ProfilerSample> samples;
295 |       };
296 |       
297 |

The resources attribute MUST return the ProfilerResource list set by the take a sample algorithm.

298 | 299 |

The frames attribute MUST return the ProfilerFrame list set by the take a sample algorithm.

300 |

The stacks attribute MUST return the ProfilerStack list set by the take a sample algorithm.

301 |

The samples attribute MUST return the ProfilerSample list set by the take a sample algorithm.

302 |

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

307 |
308 |

The ProfilerSample Dictionary

309 |
310 |         dictionary ProfilerSample {
311 |           required DOMHighResTimeStamp timestamp;
312 |           unsigned long long stackId;
313 |         };
314 |         
315 |

timestamp MUST return the value it was initialized to.

316 |

stackId MUST return the value it was initialized to.

317 |
318 |
319 |

The ProfilerStack Dictionary

320 |
321 |         dictionary ProfilerStack {
322 |           unsigned long long parentId;
323 |           required unsigned long long frameId;
324 |         };
325 |         
326 |

parentId MUST return the value it was initialized to.

327 |

frameId MUST return the value it was iniitalized to.

328 |
329 |
330 |

The ProfilerFrame Dictionary

331 |
332 |         dictionary ProfilerFrame {
333 |           required DOMString name;
334 |           unsigned long long resourceId;
335 |           unsigned long long line;
336 |           unsigned long long column;
337 |         };
338 |         
339 |

name MUST return the value it was initialized to.

340 |

resourceId MUST return the value it was initialized to.

341 |

line MUST return the value it was initialized to.

342 |

column MUST return the value it was initialized to.

343 |
344 |
345 |
346 |

The ProfilerInitOptions dictionary

347 |
348 |       dictionary ProfilerInitOptions {
349 |         required DOMHighResTimeStamp sampleInterval;
350 |         required unsigned long maxBufferSize;
351 |       };
352 |       
353 |

ProfilerInitOptions MUST support the following fields:

354 | 358 |
359 |
360 |

Document Policy

361 |

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

364 |

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

374 |
375 |
376 |

Automation

377 |

378 | For the purposes of user-agent automation and application testing, this document defines the following [[WebDriver]] extension command. 379 |

380 |
381 |

Force Sample

382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 |
HTTP MethodURI Template
POST`/session/{session id}/forcesample`
396 |

397 | The Force Sample extension command forces all [=profiling sessions=] to [=take a sample=] for the purpose of enabling more deterministic testing. 398 |

399 |

400 | The remote end steps are: 401 |

    402 |
  1. Let |sessions:list| be a [=list=] of all [=profiling sessions=] created in the current browsing context.
  2. 403 |
  3. 404 | For each |session:profiling session| of |sessions|: 405 |
      406 |
    1. If the [=state=] of |session| is started, [=take a sample=] with |session|.
    2. 407 |
    408 |
  4. 409 |
  5. Return success with data null.
  6. 410 |
411 |

412 |
413 |
414 |
415 |

Privacy and Security

416 |

The following sections detail some of the privacy and security choices of the API, illustrating protection strategies against various types of attacks.

417 |
418 |

Cross-origin script contents

419 |

420 | The API avoids exposing contents of cross-origin scripts by requiring all functions included via the take a sample algorithm to be defined in a script served with CORS-same-origin through the muted errors property. Browser builtins (such as performance.now()) must also only be included when invoked from [= CORS-same-origin =] script. 421 |

422 |

423 | As a result, the API does not expose any new insight into the contents or execution characteristics of cross-origin script, beyond what is already possible through manual instrumentation. UAs are encouraged to verify this holds if they choose to support extremely low sample interval values (e.g. less than one millisecond). 424 |

425 |
426 |
427 |

Cross-origin execution

428 |

429 | Cross-origin execution contexts should not be observable by the API through the realm check in the take a sample algorithm. Cross-origin iframes and other execution contexts that share an agent with a profiler will therefore not have their execution observable through this API. 430 |

431 |
432 |
433 |

Timing attacks

434 |

435 | Timing attacks remain a concern for any API that could introduce a new source of high-resolution timing information. Timestamps gathered in traces should be obtained from the same source as [[?HR-Time]]'s current high resolution time to avoid exposing a new vector for side-channel attacks. 436 |

437 | See [[?HR-Time]]'s discussion on clock resolution. 438 |

439 |
440 |
441 | 442 | 443 | -------------------------------------------------------------------------------- /markers.md: -------------------------------------------------------------------------------- 1 | # JS Self-Profiling Markers 2 | 3 | ## Introduction 4 | 5 | It is difficult to analyze a trace without visibility into the context around it and the events competing for the UI thread such as rendering or painting. 6 | By adding markers to the captured samples, web developers will be able to correlate slow traces with the browser activity. 7 | 8 | End-users will benefit from faster and more efficient websites by giving the tools to web-developers to understand how their application performs on conditions encountered on real user devices. 9 | 10 | ## Goals 11 | 12 | * Being able to identify the browser activity at the time the sample was captured 13 | * Being able to differentiate scripting, rendering, painting and GC related activities. 14 | 15 | ## Context 16 | 17 | ### Javascript profilers 18 | 19 | Javascript profilers offered by User Agents give detailed insights to the browser activity during the trace, common categories include: scripting, rendering and painting. The level of details and the events themselves can vary per browser, see [Gecko](https://github.com/mozilla/gecko-dev/blob/master/devtools/client/performance/modules/markers.js#L12) and [v8](https://source.chromium.org/chromium/chromium/src/+/main:third_party/devtools-frontend/src/front_end/panels/timeline/TimelineUIUtils.ts;l=1252?q=TimelineUIUtils.ts&ss=chromium). 20 | 21 | In addition to highlight browser activity around task execution, profilers also show events that may pause stack execution like [Garbage Collection](https://source.chromium.org/chromium/chromium/src/+/main:third_party/devtools-frontend/src/front_end/panels/timeline/TimelineUIUtils.ts;l=1339?q=TimelineUIUtils.ts&ss=chromium). This type of browser activity is not detectable with stacks alone and make trace analysis more difficult. 22 | 23 | ### Trace 24 | 25 | Details about the Javascript Self-Profiling API trace format can be found [here](https://wicg.github.io/js-self-profiling/#the-profilertrace-dictionary). 26 | 27 | Generally, a trace is represented as a collection of sample with a **timestamp** and **stack id** fields: 28 | 29 | ``` 30 | { 31 | "stacks" : [ 32 | {"frameId" : 0}, 33 | {"frameId" : 1,"parentId" : 0}, 34 | {"frameId" : 2,"parentId" : 1} 35 | ], 36 | "samples" : [ 37 | {"timestamp" : 1551.73499998637,"stackId": 2}, 38 | {"timestamp" : 1576.83999999426,"stackId": 1}, 39 | {"timestamp" : 1601.90499993041} 40 | ], 41 | } 42 | ``` 43 | 44 | ## Format proposal 45 | 46 | 47 | The proposal is to add a **marker** member to the sample dictionary: 48 | 49 | * `marker`: optional string, if member is missing idle state is assumed 50 | 51 | The marker represents the type of work being executed by the User Agent. Since some work like GC may interrupt stack execution it is necessary to add a marker to a sample with a stack to differentiate script execution from any other type of work. 52 | 53 | The marker can be one of the following strings: 54 | 55 | * `script`: script related activity as specified in the [script processing model](https://html.spec.whatwg.org/#script-processing-model) 56 | * `gc`: garbage collection related activity 57 | For trace analysis developers need to be able to identify frames that were not executing on the main thread due to a garbage collection. 58 | The W3C design principle [recommends](https://w3ctag.github.io/design-principles/#js-gc) not exposing the timing of a garbage collection and APIs depending on GC must set clear expectation about their interaction with the GC, see [WeakRef](https://tc39.es/ecma262/#sec-weak-ref-objects). 59 | Considering the GC timing is exposed after the complete trace is captured with a resolution 10 ms, this marker type should not be considered a weak reference to a GC event. 60 | * `other`: all other activities that do not fit the other classification 61 | 62 | 63 | Rendering stages are not fully specified in the event loop processing model, see [Update the rendering](https://html.spec.whatwg.org/#update-the-rendering). 64 | But we can envision 3 markers corresponding to the stages described in draft document [resize-observer](https://www.w3.org/TR/resize-observer/#html-event-loop): 65 | * `style` 66 | * `layout` 67 | * `paint` 68 | 69 | 70 | ``` 71 | enum ProfilerMarker { "script", "gc", "style", "layout", "paint", "other" }; 72 | 73 | dictionary ProfilerSample { 74 | required DOMHighResTimeStamp timestamp; 75 | unsigned long long stackId; 76 | [CrossOriginIsolated] ProfilerMarker? marker; 77 | }; 78 | ``` 79 | 80 | ## Key scenarios 81 | 82 | ### Browser activity 83 | 84 | In the current implementation a trace captured by a web application developer shows gaps between each stack that cannot be interpreted. 85 | The addition of markers to samples helps developers understand what are the browser operations that made the trace slow outside of javascript and enables them to minimize style or layout impact for example. 86 | 87 | trace with markers: 88 | 89 | ``` 90 | "samples" : [ 91 | { 92 | "timestamp" : 100, 93 | "stackId": 2, 94 | "marker": "script" 95 | }, 96 | { 97 | "timestamp" : 110, 98 | "stackId": 1, 99 | "marker": "script" 100 | }, 101 | { 102 | "timestamp" :120, 103 | } 104 | { 105 | "timestamp" :130, 106 | "marker": "style" 107 | }, 108 | .... 109 | { 110 | "timestamp" :150, 111 | "marker": "layout" 112 | }, 113 | .... 114 | 115 | { 116 | "timestamp" :170, 117 | "marker": "paint", 118 | "stackId": 3 119 | }, 120 | { 121 | "timestamp" : 180, 122 | "stackId": 3, 123 | "marker": "script" 124 | } 125 | ``` 126 | 127 | ### Report layout thrashing. 128 | 129 | Some properties or methods may trigger the browser to recompute the style or layout. 130 | Without a marker tagging a sample for a render operation, there is no easy way to differentiate script execution from a rendering operation. 131 | This is especially useful considering that operations triggering a re-flow are UA dependent and are subject to change. 132 | 133 | trace without markers: 134 | 135 | ``` 136 | "samples" : [ 137 | { 138 | "timestamp" : 100, 139 | "stackId": 2, // operation recomputing the layout 140 | }, 141 | { 142 | "timestamp" : 110, 143 | "stackId": 2, 144 | }, 145 | { 146 | "timestamp" : 120, 147 | "stackId": 1, 148 | }, 149 | { 150 | "timestamp" :130 151 | } 152 | ``` 153 | 154 | trace with markers: 155 | 156 | ``` 157 | "samples" : [ 158 | { 159 | "timestamp" : 100, 160 | "stackId": 2, 161 | "marker": "layout" 162 | }, 163 | { 164 | "timestamp" : 110, 165 | "stackId": 2, 166 | "marker": "layout" 167 | }, 168 | { 169 | "timestamp" : 120, 170 | "stackId": 1, 171 | "marker": "script" 172 | }, 173 | { 174 | "timestamp" :130 175 | } 176 | ``` 177 | 178 | ### Long trace due to GC activity 179 | 180 | A sample was captured indicating 30 ms spent on stack 2. A major GC happened at timestamp 105 that took 20 ms to complete. 181 | With the addition of markers, a web application developer will be able to attribute the longer duration to a GC event. 182 | This can be used to dismiss longer samples due to random GC events or to focus on the memory allocation pattern of his application if this type of traces increases over time. 183 | 184 | Trace without browser markers: 185 | 186 | ``` 187 | "samples" : [ 188 | { 189 | "timestamp" : 100, 190 | "stackId": 3 191 | }, 192 | { 193 | "timestamp" : 110, 194 | "stackId": 2 195 | }, 196 | { 197 | "timestamp" : 120, 198 | "stackId": 2 199 | }, 200 | { 201 | "timestamp" : 130, 202 | "stackId": 2 203 | }, 204 | { 205 | "timestamp" : 140, 206 | "stackId": 1 207 | }, 208 | { 209 | "timestamp" : 150 210 | } 211 | ``` 212 | 213 | Trace with markers: 214 | 215 | ``` 216 | "samples" : [ 217 | { 218 | "timestamp" : 100, 219 | "stackId": 3, 220 | "marker": "script" 221 | }, 222 | { 223 | "timestamp" : 110, 224 | "stackId": 2, 225 | "marker": "script" 226 | }, 227 | { 228 | "timestamp" : 120, 229 | "stackId": 2, 230 | "marker": "gc" 231 | }, 232 | { 233 | "timestamp" : 130, 234 | "stackId": 2, 235 | "marker": "gc" 236 | }, 237 | { 238 | "timestamp" : 140, 239 | "stackId": 1, 240 | "marker": "script" 241 | }, 242 | { 243 | "timestamp" :150 244 | } 245 | ``` 246 | ## Privacy and Security 247 | 248 | Careful consideration must be taken to avoid leaking top level UA work performed on a cross-origin document. UAs must only expose a marker if the responsible document for the work is same-origin with the profiler. 249 | 250 | There is a risk to introduce a new source of side channel information through this API. Specifically, the timings of cross-origin opaque resources owned by the document that do not pass a Timing-Allow-Origin check or the timings of cross-origin documents hosted by the same process. To mitigate this risk, a Sample's marker attribute may only be accessible when the current Realm's settings objects's cross-origin isolated capability boolean is set to true. 251 | 252 | Additional checks may also be required by user agents to implement this feature. 253 | ## Considered alternatives 254 | 255 | Instead of setting a marker at the sample level, it is possible to attach the marker to a stack element instead. 256 | A single JS stack may be duplicated to represent the different markers that it’s been associated with during the sampling. 257 | Browser activity not linked to a script may be represented by a top level entry with a marker field. 258 | 259 | This approach has the disadvantage of making the stack resolution logic more complex and breaks the one to one mapping between the script’s stack and the trace’s stack. 260 | For example the `parentId` attribution is no longer clearly defined. In case of a duplicate stack we have multiple candidates and defaulting to the stack marked `script` is not an option since that stack is not guaranteed to exist. 261 | 262 | Using the same GC example the trace would be presented like this: 263 | 264 | ``` 265 | { 266 | "stacks" : [ 267 | {"marker" : "idle"}, // id: 0 268 | {"marker" : "script"}, 269 | {"marker" : "parse"}, 270 | {"marker" : "gc"}, 271 | {"marker" : "paint"}, 272 | {"marker" : "other"}, 273 | {"frameId" : 1, "marker": "script"}, // id: 6 274 | {"frameId" : 2, "parentId" : 6, "marker": "script"}, // id: 7 275 | {"frameId" : 2, "parentId" : 6, "marker": "gc"}, // id: 8 276 | {"frameId" : 3, "parentId" : 8, "marker": "script"} // id: 9 277 | ], 278 | "samples" : [ 279 | { 280 | "timestamp" : 100, 281 | "stackId": 9, 282 | }, 283 | { 284 | "timestamp" : 110, 285 | "stackId": 7, 286 | }, 287 | { 288 | "timestamp" : 120, 289 | "stackId": 8, 290 | }, 291 | { 292 | "timestamp" : 130, 293 | "stackId": 8, 294 | }, 295 | { 296 | "timestamp" : 140, 297 | "stackId": 6, 298 | }, 299 | { 300 | "timestamp" :150, 301 | "stackId": 0 302 | } 303 | } 304 | ``` 305 | 306 | ## Security and Privacy questionnaire 307 | 308 | `01. What information might this feature expose to Web sites or other parties,` 309 | ` and for what purposes is that exposure necessary?` 310 | 311 | This feature exposes user agent work on the main thread during a trace captured with JS Self Profiling API. This information is limited to high level categories of work that help web developers understand and improve the performance of their application. 312 | 313 | ` 02. Do features in your specification expose the minimum amount of information` 314 | ` necessary to enable their intended uses?` 315 | 316 | Yes. 317 | 318 | ` 03. How do the features in your specification deal with personal information,` 319 | ` personally-identifiable information (PII), or information derived from` 320 | ` them?` 321 | 322 | Does not expose PIIs. 323 | 324 | ` 04. How do the features in your specification deal with sensitive information?` 325 | 326 | Does not expose sensitive information. 327 | 328 | ` 05. Do the features in your specification introduce new state for an origin` 329 | ` that persists across browsing sessions?` 330 | 331 | No. 332 | 333 | ` 06. Do the features in your specification expose information about the` 334 | ` underlying platform to origins?` 335 | 336 | Does not expose data about the underlying platform. 337 | 338 | ` 07. Does this specification allow an origin to send data to the underlying` 339 | ` platform?` 340 | 341 | No. 342 | 343 | ` 08. Do features in this specification enable access to device sensors?` 344 | 345 | No. 346 | 347 | ` 09. What data do the features in this specification expose to an origin? Please` 348 | ` also document what data is identical to data exposed by other features, in the` 349 | ` same or different contexts.` 350 | 351 | There is concern for a class of user agent work that may be shared across origins. Currently only Garbage collection event is concerned by this and may be mitigated by requesting cross-origin isolation. 352 | Other events are only exposed if the active profiler belongs to the same origin. 353 | 354 | ` 10. Do features in this specification enable new script execution/loading` 355 | ` mechanisms?` 356 | 357 | No. 358 | 359 | ` 11. Do features in this specification allow an origin to access other devices?` 360 | 361 | No. 362 | 363 | ` 12. Do features in this specification allow an origin some measure of control over` 364 | ` a user agent's native UI?` 365 | 366 | No. 367 | 368 | ` 13. What temporary identifiers do the features in this specification create or` 369 | ` expose to the web?` 370 | 371 | No temporary identifier created. 372 | 373 | ` 14. How does this specification distinguish between behavior in first-party and` 374 | ` third-party contexts?` 375 | 376 | No distinction. 377 | 378 | ` 15. How do the features in this specification work in the context of a browser’s` 379 | ` Private Browsing or Incognito mode?` 380 | 381 | Semantics should be unchanged. 382 | 383 | ` 16. Does this specification have both "Security Considerations" and "Privacy` 384 | ` Considerations" sections?` 385 | 386 | Yes. 387 | 388 | ` 17. Do features in your specification enable origins to downgrade default` 389 | ` security protections?` 390 | 391 | No. 392 | 393 | ` 18. How does your feature handle non-"fully active" documents?` 394 | 395 | The JS Self Profiling API only capture samples from the Main thread and does not interact with non-fully active documents. 396 | 397 | ` 19. What should this questionnaire have asked?` 398 | -------------------------------------------------------------------------------- /perf.html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/js-self-profiling/f7ba71f587976a9240cde8c486943e1d34c44140/perf.html.png -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [80485] 3 | , "contacts": ["yoavweiss"] 4 | , "repo-type": "cg-report" 5 | } 6 | --------------------------------------------------------------------------------