From a8e531e537cdc65ac84b327b3315caf8851e7172 Mon Sep 17 00:00:00 2001 From: Joone Hur Date: Mon, 18 Nov 2024 01:19:24 -0800 Subject: [PATCH] Collect worker thread's id, name, type and url --- LongTasks/explainer.md | 354 +++++++++++++++++++---------------------- 1 file changed, 160 insertions(+), 194 deletions(-) diff --git a/LongTasks/explainer.md b/LongTasks/explainer.md index 694502f3..4346062d 100644 --- a/LongTasks/explainer.md +++ b/LongTasks/explainer.md @@ -9,7 +9,7 @@ The Long Tasks API allows web developers to monitor long-running tasks on the ma # Goals -- Enable sites to identify and diagnose worker-side tasks that block progress, including the worker types. +- Enable sites to identify and diagnose worker-side tasks that block progress, including detailed information about the worker type, source URL, and worker ID. - Improve consistency in telemetry logging across documents and workers by providing a unified monitoring mechanism for main thread and worker threads. # Non-goals @@ -18,237 +18,215 @@ The Long Tasks API allows web developers to monitor long-running tasks on the ma # Problems -When developers use a WebWorker, they expect to know when the task is done quickly. But when there's a delay, it can be hard to find the root cause. The delay might happen because one task is blocking others from running, a task is waiting for a network request, or other reasons. -To address this, Web developers try to use the Long Tasks or LoAF API to identify long-running tasks that affect responsiveness and UI updates. - -This example code simulates long tasks occurring on both the main thread and a worker thread. - -## Main script (main.js) +When developers use a Web Worker, they expect to know when the task is done quickly. But when there's a delay, it can be hard to find the root cause. The delay might happen because one task is blocking others from running, a task is waiting for a network request, or other reasons. +To address this, developers try to use the Long Tasks or LoAF API to identify long-running tasks that affect responsiveness and UI updates. Unfortunately, these APIs are not designed to function within Web Workers. + +[This example code](https://joone.github.io/web/explainers/postMessage_metric/) demonstrates how long tasks on a worker thread can block subsequent tasks from running in the task queue. + +## Main (index.html and main.js) + +```html + + + + + Delayed Post Messages in Web Workers Due to Task Overload + + +

Delayed Post Messages in Web Workers Due to Task Overload

+ +

+ + + +``` +When the user clicks the Start button, the runWorker function dispatches five tasks every 60ms with randomly generated input numbers ranging from 30 to 37. If each task is completed within 60ms, there should be no delay while running the tasks. However, if an input number exceeds 36, it may prevent subsequent tasks from being processed in the task queue. ```js -function longTaskMain() { - console.log('Main task completed: fibonacci value:' + fibonacci(40)); -} - -function longTaskWorker() { - const worker = new Worker('worker.js'); - worker.postMessage('startWorkerTask'); +var worker; - worker.onmessage = (event) => { - console.log('Worker task completed:', event.data); - }; +function getRandomNumber(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; } -const observer = new PerformanceObserver((list) => { - const entries = list.getEntries(); - console.log(entries); -}); +function runWorker() { + const worker = new Worker("worker.js", { name: "Fibonacci Worker" }); -observer.observe({ entryTypes: ['longtask', 'long-animation-frame']}); + worker.onmessage = function updateFromWorker(event) { + // Measure the duration from when the task was sent to when the result is received + const endMark = `end-task-${event.data.no}`; + performance.mark(endMark); -longTaskMain(); -longTaskWorker(); + const startMark = `start-task-${event.data.no}`; + const measureName = `duration-task-${event.data.no}`; + performance.measure(measureName, startMark, endMark); -``` + const duration = performance.getEntriesByName(measureName)[0].duration; + // Compute the time spent waiting in the queue + const queueWaitTime = duration - event.data.duration; -## Web Worker script(worker.js) + document.getElementById("result").innerHTML += + `- Result: Task ${event.data.no} returned ${event.data.res.toLocaleString()} \ + in ${duration.toFixed(2)} ms. (message queue wait time:${queueWaitTime.toFixed(2)} ms, \ + task duration: ${event.data.duration.toFixed(2)} ms)
`; -```js -self.onmessage = (event) => { - if (event.data === 'startWorkerTask') { - postMessage('fibonacci value:' + fibonacci(40)); + // Clear the marks and measurements to avoid memory leaks + performance.clearMarks(startMark); + performance.clearMarks(endMark); + performance.clearMeasures(measureName); + }; + + let i = 0; + const interval = 60; // Interval in milliseconds + + // Function to send tasks to the worker at the specified interval + function sendTask() { + if (i < 5) { + const startMark = `start-task-${i}`; + performance.mark(startMark); + const random = getRandomNumber(30, 37); + document.getElementById("result").innerHTML += + `Sending task ${i} to the Fibonacci worker with input ${random}
`; + worker.postMessage({ no: i, input: random }); // Sending data to the worker + i++; + } else { + clearInterval(taskInterval); // Stop sending tasks after 10 messages + } } -}; -``` -However, neither API monitors long-running tasks in Web Workers, which can also impact user experience, particularly when tasks involve fetching or processing data for UI updates. Therefore, this example only shows performance entries monitored from the main thread: + // Start sending tasks every 60ms + const taskInterval = setInterval(sendTask, interval); +} -```json -[ - { - "name": "long-animation-frame", - "entryType": "long-animation-frame", - "startTime": 18.899999976158142, - "duration": 1217, - "renderStart": 1235.1000000238419, - "styleAndLayoutStart": 1235.1000000238419, - "firstUIEventTimestamp": 0, - "blockingDuration": 1151, - "scripts": [ - { - "name": "script", - "entryType": "script", - "startTime": 35.19999998807907, - "duration": 1199, - "invoker": " https://localhost/explainers/long_tasks_ex/", - "invokerType": "classic-script", - "windowAttribution": "self", - "executionStart": 35.5, - "forcedStyleAndLayoutDuration": 0, - "pauseDuration": 0, - "sourceURL": " https://localhost/explainers/long_tasks_ex/", - "sourceFunctionName": "", - "sourceCharPosition": 0 - } - ] - }, - { - "name": "self", - "entryType": "longtask", - "startTime": 34.89999997615814, - "duration": 1199, - "attribution": [ - { - "name": "unknown", - "entryType": "taskattribution", - "startTime": 0, - "duration": 0, - "containerType": "window", - "containerSrc": "", - "containerId": "", - "containerName": "" - } - ] +function stopWorker() { + if (worker) { + worker.terminate(); + worker = undefined; } -] +} ``` -In addition, the name "the Long Tasks API" might cause misunderstandings among web developers, as it suggests the API could work within web workers (see [this Chromium issue](https://issues.chromium.org/issues/41399667)). - -# Proposal +## Web Worker script(worker.js) -We propose extending the Long Tasks API to support Web Workers. To adapt the API for Web Workers, the `PerformanceLongTaskTiming` interface needs to be updated to account for long tasks in worker threads. The primary change would be within the TaskAttribution interface, where certain properties should reflect the worker context instead of the main thread. +The Web Worker simulates long tasks using the Fibonacci function. It also measures the duration of each task using the mark and measure methods of the Performance interface and returns the duration back to the main thread. -```json -{ - "name": "unknown", - "entryType": "longtask", - "startTime": 1448.1000000238419, - "duration": 297, - "attribution": [ - { - "name": "unknown", - "entryType": "taskattribution", - "startTime": 0, - "duration": 0, - "containerType": "worker", - "containerSrc": " https://localhost/explainers/long_tasks_ex/worker.js", - "containerId": "", - "containerName": "" - } - ] +```js +function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); } -``` -## TaskAttribution Changes +onmessage = function runLongTaskOnWorker(e) { + // Start measuring time + const startMark = `start-task-${e.data.no}`; + const endMark = `end-task-${e.data.no}`; + const measureName = `duration-task-${e.data.no}`; + + performance.mark(startMark); -The table below highlights the differences in the properties of the `TaskAttributionTiming` interface for long tasks on Web Workers compared to the main thread: + // Perform the Fibonacci calculation + let fibo_num = fibonacci(e.data.input); -|**Property**|**Value(Main Thread)**|**Value(Web Worker)**| -|---|---|---| -|TaskAttributionTiming.duration|Always returns 0|Always returns 0| -|TaskAttributionTiming.entryType|always returns taskattribution|Always returns taskattribution| -|TaskAttributionTiming.name|always returns unknown|Always returns unknown| -|TaskAttributionTiming.startTime|always returns 0|Always returns 0| -|TaskAttributionTiming.containerType|Returns the type of frame container, one of iframe, embed, or object. Defaults to window if no container is identified.|Always return "worker"| -|TaskAttributionTiming.containerSrc|Returns the container's src attribute.|Returns the worker script's URL| -|TaskAttributionTiming.containerId|Returns the container's id attribute.|Always returns an empty string| -|TaskAttributionTiming.containerName|Returns the container's name attribute.|Always returns an empty string| + // End measuring time + performance.mark(endMark); + performance.measure(measureName, startMark, endMark); + + // Get the measurement + const duration = performance.getEntriesByName(measureName)[0].duration; -For Web Workers, the containerType should return "worker" to clearly indicate the context. Additionally, containerSrc can return the URL of the worker script, helping developers differentiate between multiple workers. + // Send the result and duration back to the main thread + postMessage({no: e.data.no, res: fibo_num , duration: duration}); -## Monitoring multiple web workers + // Clear the marks and measurements to avoid memory leaks + performance.clearMarks(startMark); + performance.clearMarks(endMark); + performance.clearMeasures(measureName); +}; +``` -The main thread can create multiple workers. Instead of setting up a `PerformanceObserver` for each worker, the main thread can gather performance entries from all workers and its own long tasks. Therefore, it is only necessary to set up the `PerformanceObserver` for the longtask type on the main thread to collect these entries: +The results can vary due to randomness, but an example output might look like the following. In this case, task 4 is delayed because task 3 took 109.60ms to complete. Consequently, task 4 experienced a total duration of 73.90ms, including time spent waiting(48.40ms) in the message queue. + +```bash +* Sending task 0 to the Fibonacci worker with input 32 + * Result: Task 0 returned 2,178,309 in 29.40 ms. (message queue wait time:1.80 ms, task duration: 27.60 ms) +* Sending task 1 to the Fibonacci worker with input 30 + * Result: Task 1 returned 832,040 in 11.30 ms. (message queue wait time:0.70 ms, task duration: 10.60 ms) +* Sending task 2 to the Fibonacci worker with input 31 + * Result: Task 2 returned 1,346,269 in 17.60 ms. (message queue wait time:1.30 ms, task duration: 16.30 ms) +* Sending task 3 to the Fibonacci worker with input 35 + * Sending task 4 to the Fibonacci worker with input 32 +* Result: Task 3 returned 9,227,465 in 111.10 ms. (message queue wait time:1.50 ms, task duration: 109.60 ms) + * Result: Task 4 returned 2,178,309 in 73.90 ms. (message queue wait time:48.40 ms, task duration: 25.50 ms) +``` +However, identifying whether task 3 qualifies as a "long task" is challenging without relying on the mark and measure APIs provided by the Performance interface. -## Main script (main.js) +In practice, we have observed long tasks lasting several minutes due to unexpected network delays or IndexedDB overhead. -```js -function longTaskMain() { - fibonacci(40); -} +# Proposal -function longTaskWorker(url) { - const worker = new Worker(url); - worker.postMessage('startWorkerTask'); +We propose extending the Long Tasks API to support Web Workers, enabling developers to use the API within Web Workers to identify long tasks blocking the worker thread. To adapt the API for Web Workers, the `PerformanceLongTaskTiming` interface needs to be updated to account for long tasks in worker threads. - worker.onmessage = (event) => { - console.log('Worker task completed:', event.data); - }; -} +The primary change would be within the TaskAttribution interface, where certain properties should reflect the worker context instead of the main thread. -longTaskMain(); -longTaskWorker("worker1.js"); -longTaskWorker("worker2.js"); -``` +## TaskAttribution Changes + +The table below highlights the differences in the properties of the `TaskAttributionTiming` interface for long tasks on Web Workers compared to the main thread: -If you run the `PerformanceObserver` like this in the JavaScript console, you will get three performance entries as shown below: +|**Property**|**Value(Main Thread)**|**Value(Web Worker)**| +|---|---|---| +|TaskAttributionTiming.duration|Always returns `0`|Always returns `0`| +|TaskAttributionTiming.entryType|always returns `"taskattribution"`|Always returns `"taskattribution"`| +|TaskAttributionTiming.name|always returns `"unknown"`| Return the task name given in postMessage API| +|TaskAttributionTiming.startTime|always returns `0`|Always returns `0`| +|TaskAttributionTiming.containerType|Returns the type of frame container(`iframe`, `embed`, or `object`). Defaults to `"window"` if no container is identified.| Returns the type of Web Worker (`"dedicated-worker"`, `"shared-worker"`, or `"service-worker"`). +|TaskAttributionTiming.containerSrc|Returns the container's `src` attribute.|Returns the worker script's URL| +|TaskAttributionTiming.containerId|Returns the container's `id` attribute.|Returns the worker's ID| +|TaskAttributionTiming.containerName|Returns the container's `name` attribute.|Returns the `name` property of the worker| + +For workers: +- The `containerType` should return the type of Web Worker to clearly indicate the context. +- The `containerSrc` provides the URL of the worker script, helping developers differentiate between multiple workers. +- If a name is specified for the Web Worker, the containerName property should return the assigned name. + +Example: +```js +const myWorker = new Worker("worker.js", { name: "Fibonacci" }); +``` +- When a name is given to postMessage in the following example, the name property of the TaskAttribution provide the task name. +Example: ```js -const observer = new PerformanceObserver((list) => { - console.log(list.getEntries()); -}); + worker.postMessage({ no: i, inputs: random }, { name: `input_${random}`}); +``` +Additionally, the worker ID (`containerId`) should return a unique identifier for the Web Worker, starting from a value of 1. -observer.observe({ type: 'longtask', buffered: true }); +If this proposal were implemented in the Long Tasks API, the performance entry for task 3 in the above results might look like this. +```json [ - { - "name": "self", - "entryType": "longtask", - "startTime": 183.60000002384186, - "duration": 1279, - "attribution": [ - { - "name": "unknown", - "entryType": "taskattribution", - "startTime": 0, - "duration": 0, - "containerType": "window", - "containerSrc": "", - "containerId": "", - "containerName": "" - } - ] - }, { "name": "unknown", "entryType": "longtask", - "startTime": 1508.5, - "duration": 229, + "startTime": 217.4000000357628, + "duration": 110, "attribution": [ { - "name": "unknown", + "name": "input_35", "entryType": "taskattribution", "startTime": 0, "duration": 0, - "containerType": "worker", - "containerSrc": " https://localhost/explainers/long_tasks_ex/worker1.js ", - "containerId": "", - "containerName": "" - } - ] - }, - { - "name": "unknown", - "entryType": "longtask", - "startTime": 1534.199999988079, - "duration": 303, - "attribution": [ - { - "name": "unknown", - "entryType": "taskattribution", - "startTime": 0, - "duration": 0, - "containerType": "worker", - "containerSrc": " https://localhost/explainers/long_tasks_ex/worker2.js ", - "containerId": "", - "containerName": "" + "containerType": "dedicated-worker", + "containerSrc": "http://localhost:3000/explainers/postMessage_metric/worker.js", + "containerId": "24", + "containerName": "Fibonacci" } ] } ] ``` +This enhancement would enable developers to directly identify long tasks within Web Workers and attribute them to specific contexts, such as the worker type or source file, thereby improving monitoring and debugging capabilities. + # Alternatives considered ## DevTools Tracing @@ -261,14 +239,6 @@ Developers can implement a polyfill that intercepts every worker message, wraps # Discussion -## SharedWorker Contexts - -The `PerformanceLongTaskTiming.name` property might indicate multiple contexts for SharedWorkers. Further discussion is needed on how to handle shared execution environments and effectively track long tasks across these different contexts. - -## Different Types of Workers - -There are several types of worker threads, including Web Workers, Service Workers, and Worklets. To differentiate long tasks across these contexts, we can propose introducing a new property (e.g., workerType) to specify the type of worker from which the long task originates. - ## Minimum Duration of Long Tasks in Web Workers According to the specification, 50ms is the minimum duration for long tasks. We should consider whether the 50ms is appropriate for the minimum duration of long tasks in Web Workers as well, as their background operations may require a different standard. Furthermore, the minimum duration of long tasks should be configurable for both main thread and workers to accommodate various situations. @@ -277,10 +247,6 @@ According to the specification, 50ms is the minimum duration for long tasks. We Since the LoAF API provides details about the invoker's source location, the Long Tasks API can extend similar support for Web Workers by exposing the function name and character position of long-running tasks. -## Who observes Web Workers? - -This proposal allows the main thread to monitor long-running tasks on worker threads and collect performance entries directly from them. Alternatively, web developers could set up a `PerformanceObserver` for each worker and manually consolidate the entries. - # Chromium Issue - [Support Long Tasks API in workers [41399667] - Chromium](https://issues.chromium.org/issues/41399667)