Modern web apps increasingly juggle heavy, real‑time workloads—think video conferencing, collaborative editors, and 3D games—all competing with other apps for the same device resources. The Compute Pressure API is an experimental Web API that helps your app sense when the system is under strain and adapt proactively, preserving a smooth user experience.
What it does
- Observes high-level “pressure” signals from the device (e.g., CPU and overall thermals), not low-level metrics like utilization that can be misleading.
- Reports human-readable states: nominal, fair, serious, and critical. These reflect trends and substantial changes, reducing noisy flip‑flopping.
- Delivers signals proactively as system pressure rises or eases, so you can scale workloads up or down at the right time.
Where to use it
- Real-time media: Dynamically adjust video resolution, frame rate, or number of active feeds to avoid dropped frames and audio hiccups.
- Web games: Balance 3D asset complexity, post‑processing effects, or target frame rate to keep latency low and FPS stable.
- Data-heavy UIs: Render lightweight placeholders during pressure spikes, then hydrate full content when conditions improve.
Key concepts
- Sources: The current spec defines two main sources—"cpu" (average pressure across cores) and "thermals" (global thermal state). Availability varies by browser, OS, and hardware.
- Contexts: Works in Window, Worker, and SharedWorker contexts, and in iframes with an appropriate Permissions-Policy. Not available in Service Workers.
- Policy and security: Controlled by the compute-pressure directive in Permissions-Policy. Default allowlist is 'self', blocking third‑party usage without explicit opt‑in.
- Environment: Secure contexts only (HTTPS). The feature is experimental and not broadly supported; always check compatibility and handle NotSupportedError gracefully.
Why it matters
Traditional performance tuning often reacts after glitches happen—dropping animation frames or throttling tasks when stutters are already visible. The Compute Pressure API encourages proactive adaptation, helping you prevent device overheating, fan noise escalation, or battery drain while keeping your app responsive and pleasant to use.
Getting started tips
- Feature-detect and plan fallbacks for unsupported environments.
- Decide adaptation strategies upfront: what to degrade first (effects, resolution, concurrency) and what to protect (input responsiveness, core UX).
- Use the API’s trend-aware states as triggers for your quality-of-service ladder.
- Monitor outcomes alongside other performance APIs (e.g., PerformanceLongTaskTiming) for post‑hoc analysis.
Caveats
- Experimental and limited availability—don’t rely on it exclusively.
- Source availability differs by platform; verify via knownSources and handle errors.
- The API abstracts away raw telemetry; treat states as guidance, not precise meters.
Compute Pressure API Demo
Compute Pressure API Demo
Experimental API. Requires HTTPS. See MDN:
Compute Pressure API
Environment & Support
-
Secure context (HTTPS):
checking…
-
PressureObserver available:
checking…
-
Supported sources:
checking…
-
Current observation:
stopped
Observer Controls
Latest state: —
time: —
CPU Stress Tool (optional)
Starts a Web Worker that performs CPU-intensive work to increase system pressure.
Use carefully and stop when done.
Change Log
/* Layout helpers */
.full-width {
width: 100%;
}
.grid-gap {
display: grid;
gap: 0.5rem;
}
.mt-05 {
margin-top: 0.5rem;
}
.my-025 {
margin: 0.25rem 0;
}
/* Replaces the inline pre/box styling used for results and previews */
.pre-box {
white-space: pre-wrap;
background: #f6f8fa;
padding: 0.75rem;
border-radius: 6px;
}
/* Optional: small base styles (add/remove as you prefer) */
/*
body {
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
line-height: 1.4;
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
*/
/*
Compute Pressure API Demo
-------------------------
- Observes system resource pressure (e.g., CPU) and reports high-level states.
- Experimental: requires HTTPS and limited browser support.
- Reference: https://developer.mozilla.org/en-US/docs/Web/API/Compute_Pressure_API
This script:
1) Detects API support and secure context.
2) Queries supported sources (commonly ['cpu']) and populates a selector.
3) Starts/stops a PressureObserver and updates UI with the latest PressureRecord.
4) Includes an optional CPU stress tool (Web Worker via Blob URL) to help provoke state changes.
*/
/* --------------- DOM elements --------------- */
const secureStatusEl = document.querySelector('#secureStatus');
const poStatusEl = document.querySelector('#poStatus');
const sourcesEl = document.querySelector('#sources');
const obsStatusEl = document.querySelector('#obsStatus');
const supportNoteEl = document.querySelector('#supportNote');
const sourceSel = document.querySelector('#sourceSel');
const btnStart = document.querySelector('#btnStart');
const btnStop = document.querySelector('#btnStop');
const stateDot = document.querySelector('#stateDot');
const stateText = document.querySelector('#stateText');
const stateMeta = document.querySelector('#stateMeta');
const intensityRange = document.querySelector('#intensity');
const intensityVal = document.querySelector('#intensityVal');
const btnStressStart = document.querySelector('#btnStressStart');
const btnStressStop = document.querySelector('#btnStressStop');
const logEl = document.querySelector('#log');
/* --------------- Utilities --------------- */
/**
* Append a line to the log.
* @param {string} msg
* @param {string} [state] - one of nominal|fair|serious|critical to colorize
*/
const log = (msg, state) => {
const p = document.createElement('div');
p.className = 'line';
if (state) {
const tag = document.createElement('span');
tag.className = `tag ${state}`;
tag.textContent = state.toUpperCase();
p.appendChild(tag);
}
p.append(document.createTextNode(msg));
logEl.appendChild(p);
logEl.scrollTop = logEl.scrollHeight;
};
/** Format DOMHighResTimeStamp to wall clock */
const formatTimeStamp = (ts) => {
// record.time is DOMHighResTimeStamp relative to timeOrigin (if provided by the UA)
const wall = performance.timeOrigin
? new Date(performance.timeOrigin + ts)
: new Date();
return wall.toLocaleTimeString();
};
/** Update the state indicator dot + labels */
const updateStateUI = (state, time) => {
stateDot.classList.remove(
'state-nominal',
'state-fair',
'state-serious',
'state-critical'
);
if (state) {
const safe = ['nominal', 'fair', 'serious', 'critical'].includes(state)
? state
: 'nominal';
stateDot.classList.add(`state-${safe}`);
stateText.textContent = safe;
stateMeta.textContent = time ? `time: ${formatTimeStamp(time)}` : 'time: —';
stateDot.title = `Latest state: ${safe}`;
} else {
stateText.textContent = '—';
stateMeta.textContent = 'time: —';
stateDot.title = 'No data';
}
};
/* --------------- CPU Stress Worker (optional) --------------- */
/*
We create a Web Worker from a Blob URL so this demo remains a single JS file.
The worker busy-waits in short slices, controlled by an "intensity" percentage (0–100).
- At 0% it sleeps (no significant CPU).
- At 100% it busy-waits nearly constantly.
This can help provoke pressure state changes on supported browsers/devices.
*/
let stressWorker = null;
const createStressWorker = () => {
const workerCode = `
let running = false;
let intensity = 50; // 0..100 (% of each 50ms slice spent busy-waiting)
function busyWait(ms) {
const end = performance.now() + ms;
while (performance.now() < end) {
// Do some meaningless math to keep CPU busy
let x = 0;
for (let i = 0; i < 1000; i++) x += Math.sqrt(i) % 3;
}
}
function loop() {
if (!running) return;
const slice = 50; // ms per slice
const busy = (intensity / 100) * slice;
if (busy > 1) busyWait(busy);
// Sleep the remainder of the slice
setTimeout(loop, Math.max(0, slice - busy));
}
self.onmessage = (e) => {
const { cmd, value } = e.data || {};
if (cmd === 'start') {
running = true;
loop();
} else if (cmd === 'stop') {
running = false;
} else if (cmd === 'intensity') {
// clamp 0..100
intensity = Math.max(0, Math.min(100, Number(value) || 0));
}
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
return new Worker(URL.createObjectURL(blob));
};
const startStress = () => {
if (!stressWorker) stressWorker = createStressWorker();
stressWorker.postMessage({ cmd: 'intensity', value: intensityRange.value });
stressWorker.postMessage({ cmd: 'start' });
btnStressStart.disabled = true;
btnStressStop.disabled = false;
log('CPU stress worker: started');
};
const stopStress = () => {
if (stressWorker) stressWorker.postMessage({ cmd: 'stop' });
btnStressStart.disabled = false;
btnStressStop.disabled = true;
log('CPU stress worker: stopped');
};
/* --------------- Pressure Observer --------------- */
let observer = null;
let observingSource = null;
/**
* Initialize a PressureObserver with a callback to handle PressureRecord arrays.
* Note: Options (like sampleRate) may vary by implementation; we omit them for broadest compatibility.
*/
const createObserver = () => {
// The callback receives an array of PressureRecord(s). Each has:
// - record.source (e.g., 'cpu')
// - record.state ('nominal'|'fair'|'serious'|'critical')
// - record.time (DOMHighResTimeStamp)
observer = new PressureObserver((records) => {
for (const rec of records) {
const msg = `[${rec.source}] state=${rec.state} at ${formatTimeStamp(
rec.time
)}`;
log(msg, rec.state);
// Update the UI to reflect the latest record for the currently observed source
if (rec.source === observingSource) {
updateStateUI(rec.state, rec.time);
}
}
});
};
/** Start observing the selected source */
const startObserving = async () => {
if (!('PressureObserver' in window)) return;
if (!observer) createObserver();
const source = sourceSel.value || 'cpu';
try {
// Observe the given source (e.g., 'cpu').
await observer.observe(source);
observingSource = source;
obsStatusEl.textContent = `observing '${source}'`;
btnStart.disabled = true;
btnStop.disabled = false;
log(`Observer: now observing '${source}'`);
} catch (err) {
log(`Observer error: ${err?.message || err}`, 'serious');
}
};
/** Stop observing the current source */
const stopObserving = async () => {
if (!observer || !observingSource) return;
try {
await observer.unobserve(observingSource);
} catch (e) {
// Some implementations may not throw; ignore failures here.
}
log(`Observer: stopped observing '${observingSource}'`);
observingSource = null;
obsStatusEl.textContent = 'stopped';
btnStart.disabled = false;
btnStop.disabled = true;
updateStateUI(null, null);
};
/* --------------- Setup & Feature Detection --------------- */
(init = async () => {
// Secure context check
secureStatusEl.textContent = window.isSecureContext ? 'yes' : 'no';
if (!window.isSecureContext) {
supportNoteEl.textContent = 'This API requires a secure context (HTTPS).';
}
// API feature detection
const supported = 'PressureObserver' in window;
poStatusEl.textContent = supported ? 'yes' : 'no';
if (!supported) {
supportNoteEl.textContent =
'PressureObserver is not available in this browser. Try a recent Chromium-based browser, enable flags, or check MDN for updates.';
}
// Populate supported sources (commonly ['cpu']). Fallback to 'cpu' if not available.
let sources = ['cpu'];
if (supported && typeof PressureObserver.supportedSources === 'function') {
try {
const s = await PressureObserver.supportedSources();
if (Array.isArray(s) && s.length) sources = s;
} catch {
// Ignore errors, keep default
}
}
sourcesEl.textContent = sources.join(', ');
sourceSel.innerHTML = sources
.map((s) => ``)
.join('');
// Hook up UI events
btnStart.addEventListener('click', startObserving);
btnStop.addEventListener('click', stopObserving);
intensityRange.addEventListener('input', () => {
intensityVal.textContent = `${intensityRange.value}%`;
if (stressWorker)
stressWorker.postMessage({
cmd: 'intensity',
value: intensityRange.value
});
});
btnStressStart.addEventListener('click', startStress);
btnStressStop.addEventListener('click', stopStress);
// If supported, prepare the observer
if (supported) {
createObserver();
log('Observer ready. Click "Start observing" to begin.');
} else {
log(
'Compute Pressure API not supported. UI will not receive real updates.',
'fair'
);
}
})();
Bottom line
The Compute Pressure API gives the web a vocabulary for device strain and a way to act before users feel pain. If your app’s quality hinges on consistent performance under variable load, integrating these signals can make your experience more resilient—just be sure to gate it behind capability checks and maintain graceful fallbacks.