Copyright © 2023 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
The Compute Pressure API provides a way for websites to react to changes in the CPU pressure of the target device, such that websites can trade off resources for an improved user experience.
This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
This document was published by the Devices and Sensors Working Group as a Working Draft using the Recommendation track.
Publication as a Working Draft does not imply endorsement by W3C and its Members.
This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
This document is governed by the 2 November 2021 W3C Process Document.
This section is non-normative.
Modern applications often need to balance the trade offs and advantages of fully utilizing the system's computing resources, in order to provide a modern and delightful user experience.
As an example, many applications can render video effects with varying degrees of sophistication. These applications aim to provide the best user experience, while avoiding driving the user's device into a high pressure regime.
Utilization of processing units close to and often reaching 100% can lead to a bad user experience, as different tasks are fighting for the processing time. This can lead to slowless, which is especially noticeable with input delay. Further, a prolonged utilization close 100% can cause the processing units to heat up due to prolonged boosting, which can lead to throttling, resulting in an even worse user experience.
As a result of thermal limits, many smartphones, tablets and laptops can become uncomfortably hot to the touch. The fans in laptops and desktops can become so loud that they disrupt conversations or the users’ ability to focus.
In many cases, a device under high pressure appears to be unresponsive, as the operating system may fail to schedule the threads advancing the task that the user is waiting for. See also Use Cases.
This section is non-normative.
Feature detection is an established web development best practice. Resources on the topic are plentiful on- and offline and the purpose of this section is not to discuss it further, but rather to put it in the context of detecting hardware-dependent features.
Consider the below feature detection examples:
This specification defines the following concepts:
Computing devices consist of a multitude of different processing units such as the Central Processing Unit (CPU), the Graphics Processing Unit (GPU) and many specialized processing units. The latter are becoming popular such as ones designed to accelerate specific tasks like machine learning or computer vision.
The specification currently defines the supported source types as the Central Processing Unit, also know as the CPU, but intents to support other source types such as GPU (Graphical Processing Unit) in future levels of this specification.
The sampling rate for a platform collector is defined as a rate at which the user agent obtains telemetry readings from the underlying platform, and it might differ from the pressure observers' requested sampling rates.
The reporting rate for a pressure observer is the rate at which it runs the data delivery steps.
The sampling rate differs from the requested sampling rate when the requested sampling rate exceeds upper or lower sampling rate bounds supported or accepted by the underlying platform and user agent†.
†It is recommended that the user agent limits the reporting rate as outlined in 10.7.1.1 Rate-limiting change notifications.
In case the user didn't request a sampling rate, the sampling rate is implementation-defined.
The platform collector refers to a platform interface, with which the user agent interacts to obtain the telemetry readings required by this specification.
A platform collector can be defined by the underlying platform (e.g. in a native telemetry framework) or by the user agent, if it has a direct access to hardware counters.
A platform collector can support telemetry for different source types of computing
devices defined by PressureSource
, or there can be multiple platform collectors.
From the implementation perspective platform collector can be treated as a software proxy for the corresponding hardware counters. It is possible to have multiple platform collector simultaneously interacting with the same underlying hardware if the underlying platform supports it.
In simple cases, a platform collector represents individual hardware counters, but if the provided counter readings are a product of data fusion performed in software, the platform collector represents the results of the data fusion process. This may happen in user space or in kernel space.
As collecting telemetry data often means polling hardware counters, it is not a free operation and thus, it should not happen if there are no one observing the data. See 10.5 Life-cycle and garbage collection for more information.
A platform collector samples data at a specific rate. A user agent may modify this rate (if possible) for privacy reasons, or ignore and fuse certain readings.
It is RECOMMENDED that a user agent show some form of unobtrusive notification that informs the user when a pressure observer is active, as well as provides the user with the means to block the ongoing operation, or simply dismiss the notification.
The Compute Pressure API defines a policy-controlled feature
identified by the token "compute-pressure".
Its default allowlist is ["self"]
.
Each global object has:
PressureObserver
object).
A constructed PressureObserver
object has the following internal slots:
PressureUpdateCallback
set on creation.
PressureSource
string and promise holds a Promise
object.
PressureRecord
objects, which is initially empty.
PressureSource
,
representing the source type to which the last record belongs.
The ordered map's value is a PressureRecord
.
The user agent additionally has a max queued records integer, which is set to an implementation-defined value, greater than 0.
Pressure states represents the minimal set of useful states that allows websites to react to changes in compute and system pressure with minimal degration in quality or service, or user experience.
WebIDLenum PressureState
{ "nominal
", "fair
", "serious
", "critical
" };
The PressureState
enum represents the pressure state with the following states:
Contributing factors represents the factors contributing to the system performance and current pressure state.
In case the pressure state is nominal or fair, the PressureRecord
internal slot [[Factors]]
will always be empty.
WebIDLenum PressureFactor
{ "thermal
", "power-supply
" };
The PressureFactor
enum represents the contributing factors:
The change in contributing factors is substantial steps are as follows:
PressureUpdateCallback
callbackWebIDLcallback PressureUpdateCallback
= undefined (
sequence<PressureRecord
> changes,
PressureObserver
observer
);
This callback will be invoked when the pressure state changes.
PressureObserver
object
The PressureObserver
can be used to observe changes in the pressure states.
WebIDLenum PressureSource
{ "cpu
" };
[Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
interface PressureObserver
{
constructor
(PressureUpdateCallback
callback, optional PressureObserverOptions
options = {});
Promise<undefined> observe
(PressureSource
source);
undefined unobserve
(PressureSource
source);
undefined disconnect
();
sequence<PressureRecord
> takeRecords
();
[SameObject] static readonly attribute FrozenArray<PressureSource
> supportedSources
;
};
The PressureObserver
interface represents a PressureObserver
.
The new
PressureObserver
(callback, options)
constructor steps are:
[[Callback]]
to callback.
RangeError
.
[[SampleRate]]
to options["sampleRate"].
The observe
(source)
method steps are:
NotAllowedError
.
[[PendingObservePromises]]
.
[[PendingObservePromises]]
.
NotSupportedError
and abort these steps.
The unobserve
(source)
method steps are:
NotSupportedError
".
[[QueuedRecords]]
all
records associated with source.
[[LastRecordMap]]
[source].
[[PendingObservePromises]]
,
if source is equal to promiseSource, reject pendingPromise with an AbortError
.
The disconnect
()
method steps are:
[[QueuedRecords]]
.
[[LastRecordMap]]
.
[[PendingObservePromises]]
,
reject pendingPromise with an AbortError
.
The takeRecords
()
method steps are:
[[QueuedRecords]]
.
[[QueuedRecords]]
.
The supportedSources
attribute is informing on the supported source type by the platform collector.
The supportedSources
getter steps are:
WebIDL[Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
interface PressureRecord
{
readonly attribute PressureSource
source
;
readonly attribute PressureState
state
;
readonly attribute FrozenArray<PressureFactor
> factors
;
readonly attribute DOMHighResTimeStamp time
;
[Default] object toJSON
();
};
A constructed PressureRecord
object has the following internal slots:
PressureSource
, which represents the current source type.
PressureState
, which represents the current pressure state.
PressureFactor
values,
which represents the current contributing factors.
DOMHighResTimeStamp
,
which corresponds to the
time the data was obtained from the system, relative to the time origin of the global object associated with
the PressureObserver
instance that generated the notification.
The source
getter steps are to return its [[Source]]
internal slot.
The state
getter steps are to return its [[State]]
internal slot.
The factors
getter steps are to return its [[Factors]]
internal slot.
The time
getter steps are to return its [[Time]]
internal slot.
When PressureRecord
.toJSON
is called, run Web IDL Standard's default toJSON steps.
WebIDLdictionary PressureObserverOptions
{
double sampleRate
= 1.0;
};
The sampleRate
member represents the requested sampling
rate expressed in Hz, ie. it represents the number of samples requested to be obtained
from the hardware per second. The reporting rate will never exceed the requested sampling rate.
Each global object has a strong reference to registered observers in their registered observer list (one per source).
This section outlines the steps the user agent must take when implementing the specification.
Window
object:
WorkerGlobalScope
object:
[[LastRecordMap]]
[source] does not exist, return true.
[[LastRecordMap]]
[source].
[[SampleRate]]
.
[[Time]]
.
[[LastRecordMap]]
[source] does not exist, return true.
[[LastRecordMap]]
[source].
[[State]]
is not equal to state and change in contributing factors is substantial
returns true, return true.
[[Factors]]
and factors are not supersets of each other, return true.
Data delivery from a platform collector can be activate and deactivated in an implementation-defined manner per source type and global object.
The data delivery steps that are run when an implementation-defined data sample of source type source is obtained from global object relevantGlobal's platform collector, are as follows:
To queue a record given the arguments observer, source, state, factors and timestamp, run these steps:
PressureRecord
object with its
[[Source]]
set to source,
[[State]]
set to state,
[[Factors]]
set to factors
and [[Time]]
set to timestamp.
[[QueuedRecords]]
is greater than
max queued records, then remove the first item.
[[QueuedRecords]]
.
[[LastRecordMap]]
[source] to record.
The PressureObserver task source is a task source used for scheduling tasks to 10.6.5 Notify Pressure Observers.
To queue a pressure observer task given relevantGlobal as input, run these steps:
To notify pressure observers given relevantGlobal as input, run these steps:
[[QueuedRecords]]
.
[[QueuedRecords]]
.
[[Callback]]
with records and observer. If this throws an exception, catch it, and report the exception.
When a Document
document is no longer fully active,
deactivate data delivery of data of all supported source types to document's relevant global object.
When a worker with associated WorkerGlobalScope
relevantGlobal is no longer
an
active needed workers,
deactivate data delivery of data of all supported source types to relevantGlobal.
When a Document
document becomes fully active,
for each non-empty registered observer list associated the source type source,
activate data delivery of source data to document's relevant global object.
When a worker with associated WorkerGlobalScope
relevantGlobal becomes
an
active needed workers,
for each non-empty registered observer list associated the source type source,
activate data delivery of source data to document's relevant global object.
When a worker with associated WorkerGlobalScope
relevantGlobal,
once relevantGlobal's closing flag is set to true,
deactivate data delivery for all supported source types to relevantGlobal.
As one of the unloading document cleanup steps given Document
document,
deactivate data delivery for all supported source types to document's relevant global object.
Please consult the Security and Privacy Self-Assessment based upon the [security-privacy-questionnaire].
Exposing hardware related events related to low -level details such as exact CPU utilization or frequency increases the risk of harming the user's privacy.
To mitigate this risk, no such low level details are exposed.
The subsections below describe the processing model. At a high level, the information exposed is reduced by the following steps:
We propose exposing the pressure state via rate-limited change notifications. This aims to remove the ability to observe the precise time when a value transitions between two states.
More precisely, once the pressure observer is activated, it will be called once with initial values, and then be called when the values change. The subsequent calls will be rate-limited. When the callback is called, the most recent value is reported.
The specification will recommend a rate limit of at most one call per second for the active window, and one call per 10 seconds for all other windows. We will also recommend that the call timings are jittered across origins.
These measures benefit the user's privacy, by reducing the risk of identifying a device across multiple origins. The rate-limiting also benefits the user's security, by making it difficult to use this API for timing attacks. Last, rate-limiting change callbacks places an upper bound on the performance overhead of this API.
Rate limiting can be implemented in the user agent, but it might also be possible to simply change the polling/sampling rate of the underlying hardware counters, if not accessed via a higher level framework.
It is possible to identify users across non-same origin sites if unique or very precise values can be accessed at the same time by sites not sharing origin.
If the same pressure state and timestamp is observed by two origins, that would be a good indication that the origin is used by the same user on the same machine. For this reason, the API limits reporting pressure state changes to one origin at the time.
A common way to do this, is only to report changes to the focused page, but one of the main users of this API are video conferencing sites. These sites want to make sure that the video streams and effects doesn't negatively affect the system and thus the conferencing experience - but there are two common cases where the site will usually not be focused:
This API will only be available in frames served from the same origin as the top-level frame. This requirement is necessary for preserving the privacy benefits of the API's quantizing scheme.
The same-origin requirement above implies that the API is only available in first-party contexts by default.
This section is non-normative.
const samples = [];
function pressureChange(records, observer) {
for (const record of records) {
samples.push(record.state);
// We only want 20 samples.
if (samples.length == 20) {
observer.disconnect();
return;
}
}
}
const observer = new PressureObserver(pressureChange);
observer.observe("cpu");
In the following example we want to lower the number of concurrent video streams when the pressure becomes critical. For the sake of simplicity we only consider this one state.
As lowering the amount of streams might not result in exiting the critical state, or at least not immediately, we use a strategy where we lower one stream at the time every 30 seconds while still in the critical state.
We accomplish this by making sure the callback is called at least once every 30 seconds, or when the state actually changes. When the state changes we reset the interval timer.
let timerId = -1;
function pressureChange(records) {
// Clear timer every time we are called, either by an actual state change,
// or when called by setTimeout (see below).
if (timerId > 0) {
clearTimeout(timerId);
}
// When entering critical state, we want to recheck every 30sec if we are
// still in critical state and if so, further reduce our concurrent streams.
// For this reason we create a timer for 30 seconds that will call us back
// with the last result in there were no change.
const lastRecordArray = [records.at(records.length - 1)];
timerId = setTimeout(pressureChange.bind(this, lastRecordArray), 30_000);
for (const record of records) {
if (record.state == "critical") {
let streamsCount = getStreamsCount();
setStreamsCount(streamsCount--);
}
}
}
const observer = new PressureObserver(pressureChange);
observer.observe("cpu");
In the following example, we want to demonstrate the usage of takeRecords
()
,
by retrieving the remaining records accumulated since the the callback was last
invoked.
It is recommended to do so before disconnect
()
,
otherwise disconnect
()
will clear them and they will be lost forever.
For example, we might want to measure the pressure during a benchmarking workload, and thus want pressure telemetry for the exact duration of the workload. This means disconnecting all observers immediately when the task is completed, and manually requesting any pending pressure telemetry up to this point that might not have been delivered yet as part of the event loop cycle.
function logWorkloadStatistics(records) {
// do something with records.
}
const observer = new PressureObserver(logWorkloadStatistics);
observer.observe("cpu");
// Read pending state change records, otherwise they will be cleared
// when we disconnect.
const records = observer.takeRecords();
logWorkloadStatistics(records);
observer.disconnect();
In the following example, we show how to tell the observer to stop watching a specific
source by invoking unobserve
()
with source.
const observer = new PressureObserver(records => { // do something with records. }));
observer.observe("cpu");
observer.observe("gpu");
// Callback now gets called whenever the pressure state changes for 'cpu' or 'gpu'.
observer.unobserve("gpu");
// Callback now only gets called whenever the pressure state changes for 'cpu'.
In the following example, we show how to tell the observer to stop watching for any
state changes by calling disconnect
()
. Calling
disconnect
()
will stop observing all sources observed
by previous observe
()
calls.
Additionally it will clear all pending records collected since the last callback was invoked.
const observer = new PressureObserver(records => { // do something with records. });
observer.observe("cpu");
observer.observe("gpu");
// some time later...
observer.disconnect();
// records will be an empty array, because of the previous disconnect().
const records = observer.takeRecords();
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key word RECOMMENDED in this document is to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
This specification defines conformance criteria for a single product: a user agent that implements the interfaces that it contains.
This section is non-normative.
Many thanks for valuable feedback and advice from Anssi Kostiainen, Asaf Yaffe, Chen Xing, Evan Shrubsole, François Beaufort, Jan Gora, Jesse Barnes, Joshua Bell, Kamila Hasanbega, Matt Menke, Moh Haghighat, Nicolás Peña Moreno, Opal Voravootivat, Paul Jensen, Peter Djeu, Raphael Kubo da Costa, Reilly Grant, Ulan Degenbaev, Victor Miura, Wei Wang, and Zhenyao Mo
Special thanks to Amanda Zhao, Fidel Tian, Zhiliang Wang and others from the Zoom engineering team for the feedback and hands-on experiments that have helped improve this API in real-world scenarios.
WebIDLenum PressureState
{ "nominal
", "fair
", "serious
", "critical
" };
enum PressureFactor
{ "thermal
", "power-supply
" };
callback PressureUpdateCallback
= undefined (
sequence<PressureRecord
> changes,
PressureObserver
observer
);
enum PressureSource
{ "cpu
" };
[Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
interface PressureObserver
{
constructor
(PressureUpdateCallback
callback, optional PressureObserverOptions
options = {});
Promise<undefined> observe
(PressureSource
source);
undefined unobserve
(PressureSource
source);
undefined disconnect
();
sequence<PressureRecord
> takeRecords
();
[SameObject] static readonly attribute FrozenArray<PressureSource
> supportedSources
;
};
[Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
interface PressureRecord
{
readonly attribute PressureSource
source
;
readonly attribute PressureState
state
;
readonly attribute FrozenArray<PressureFactor
> factors
;
readonly attribute DOMHighResTimeStamp time
;
[Default] object toJSON
();
};
dictionary PressureObserverOptions
{
double sampleRate
= 1.0;
};
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: