Browser Fingerprint Timing: performance.now, CPU, and Memory Signals
How performance.now, hardwareConcurrency, and deviceMemory combine into a hardware-shaped fingerprint, why VPNs and private windows do not remove it, and how to verify timing.
BotBrowser Team
Prefer the maintained product doc?
This article has a matching page in the docs center. Use the docs for the canonical setup flow, current flags, and long-term reference.
Three signals that describe hardware shape
Timing APIs were built to help developers measure their own code, yet they also let a website observe something about the machine that runs it. Three values come up most often in privacy reviews: the clock behind performance.now(), the logical core count in hardwareConcurrency, and the memory class in deviceMemory. Each one describes the hardware shape of a device rather than a person, and a reviewer who understands all three can reason about what a page is able to learn without any permission prompt.
performance.now() returns a high-resolution timestamp measured from the start of the page's time origin. Browsers deliberately coarsen that clock, and the MDN reference explains that finer resolution is offered only to cross-origin isolated pages while some privacy-focused browser modes round it further. Even a coarse clock still lets a script time a repeated piece of work, and the duration of that work depends on CPU speed, cache behavior, scheduling, and how busy the machine is at that moment.
The coarsening has a history. High-resolution clocks were shown to be usable for Spectre-style side-channel attacks, so browser vendors reduced timer precision, added jitter, and tied better precision to cross-origin isolation. That mitigation was designed to protect memory contents, not to remove hardware shape from the picture. A coarse timer that is read many times still carries information about how long a task takes on a particular machine.
hardwareConcurrency reports the number of logical processors available to the browser. Real devices cluster around a handful of counts such as 4, 8, 12, and 16, so an unusual count stands out more than a common one. Some browsers cap or round the number, which makes the value a hint about the machine rather than a precise inventory, but it stays stable across sessions on the same device.
deviceMemory returns an approximate amount of RAM in gigabytes, rounded to a power of two such as 0.25, 0.5, 1, 2, 4, or 8. The rounding and the upper cap make it a coarse signal, and Chromium-based browsers expose it only in secure contexts. A coarse signal still narrows the field: a machine that reports 8 GB and 8 cores belongs to a different group than one that reports 4 GB and 4 cores.
None of these values needs a permission prompt, none produces a visible indicator, and none is cleared when you delete cookies. That is why they appear in fingerprinting research even though each number looks harmless on its own. The sections below explain how the three combine, which common privacy measures leave them untouched, and how a reviewer can configure and verify consistent reported timing with BotBrowser.
Why the combination is more distinctive
Taken alone, each of these signals carries little information. Many devices share the same core count, and the memory class has only a few possible values. The combination is different, because a site can read all three in one pass and add a short timing sample on top. A device that reports eight cores, an 8 GB memory class, and a particular execution speed falls into a much smaller group than a device described by any one of those facts.
The combination is also compared with the rest of the browser. Reviewers regularly see timing and hardware values read next to the graphics renderer, the screen size, the platform string, and the font list, and a page can check whether they describe the same machine. A profile that claims a modest laptop but returns the timing of a fast workstation, or the reverse, is more distinctive than a profile that is simply ordinary.
Consider a review of a test profile that describes a mid-range laptop with eight logical cores and an 8 GB memory class. On a page you control, the core count and memory class read as expected. A timing sample then shows the loop finishing much faster than a laptop of that class would manage, because the real host is a high-end server. Each hardware value agrees with the profile, yet the timing disagrees with them. In practice, the documented 0.80 to 0.99 scale range is narrow, so it can soften a modest skew but cannot turn one class of hardware into another, and only a reviewer who reads the surfaces together will notice the gap.
Stability matters as much as the values themselves. A reported core count that changes between page loads, or timing that behaves one way in one tab and another way in the next, is more distinctive than a stable but ordinary value. This is the main reason a repeatable seed is useful for review: it removes one source of variation, so any remaining difference between runs points to something else.
That is why this topic is about consistency rather than concealment. The goal of a careful privacy review is not to make every number as small as possible. It is to make sure that the hardware shape reported through one surface agrees with the shape reported through the others, and that the agreement holds on every page load, in every worker, and over time.
Three questions help structure a review:
- Do
hardwareConcurrencyanddeviceMemorydescribe the same class of machine as the loaded profile? - Do repeated timing samples on one page look like that same class of machine, and do they stay similar from one run to the next?
- Do Navigation Timing and Resource Timing entries look coherent with the profile instead of reflecting the real host connection?
What VPNs, private windows, extensions, and noise leave in place
A VPN changes the network path and the IP address that a server sees. It does not change how fast your processor runs a loop, how many logical cores the browser reports, or which memory class the browser exposes. All three values are read locally, so two different machines behind the same VPN endpoint still report different hardware shapes.
Private browsing windows discard cookies, history, and site data when you close them. They do not change the core count, the memory class, or the speed of the underlying hardware, so the timing and hardware values in a private window match the values in a normal window on the same machine.
Reinstalling the browser, switching to another browser on the same computer, or clearing all site data leaves the same processor, the same core count, and the same memory in place. Different browsers can report different values because of their own rounding rules and caps, but one browser on one machine is stable, which is exactly why these values work as a long-lived signal.
Extensions that override hardware properties usually inject a script that redefines a property on the page. That can leave visible traces, such as property descriptors that differ from the browser's own, or a fresh frame that reads the real value before the extension runs. It can also create contradictions: a page that reports four cores while a worker pool clearly runs more parallel tasks is easier to notice than an unmodified value.
Random noise added to timing values has a related problem. A timestamp sequence with artificial jitter does not resemble the variation of real hardware, it can break legitimate performance monitoring on the pages you visit, and noise that changes on every call is itself a visible pattern. Stable and plausible reported values are usually easier to defend than random ones.
Some browsers ship their own protections, such as reduced timer precision or capped core counts. These reduce the information a page can read, but they apply to everyone who uses the same mode, so the reported values become common rather than unique. They also do not make the values match any particular hardware class, so consistency with a chosen profile still has to be checked separately.
This does not make those tools pointless. A VPN protects the network layer, private windows protect local history, and extensions can block many trackers before they load. They address different surfaces. Timing and hardware shape is a surface they were never designed to cover, so a review has to treat it separately and check it directly.
Configuring reported timing with a BotBrowser profile
Start from a profile, because the profile defines the hardware values that the timing flags are meant to stay consistent with. A launch with only a profile gives you a baseline to compare against before you add any timing flag. Then add the flags one at a time and record what changes. Save the console output of each step in a plain text file beside the launch command.
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-time-scale=0.92 \
--bot-time-seed=42
--bot-time-scale compresses reported high-resolution timing intervals. The documented range is 0.80 to 0.99, and a value closer to 0.80 compresses intervals more than a value closer to 0.99. The documentation describes the effect as emulating lower system load and reducing timing skew signals. It is an ENT Tier2 flag.
--bot-time-seed accepts an integer from 1 to 4294967295, and 0 disables it. Each seed produces a stable timing profile that spreads deterministic diversity across browser operations and navigation timing entries, so the same seed with the same profile gives the same behavior on the next run. It is also an ENT Tier2 flag.
Use a different seed for each instance when you want instances to differ, and keep one seed fixed when you want a test to be repeatable. The performance documentation lists identical timing across instances as the symptom of reusing one seed. The scale and the seed address different aspects of timing, and the documentation says they can be used together.
--bot-performance-timing is a separate flag for browser-visible timing entries such as the connection stages in Navigation Timing and Resource Timing. It accepts basic and advanced, needs ENT Tier3, and works only when the active profile package enables Performance Timing Protection. It must be set before the first page or worker starts and is resolved per browser context. Omit it, or pass false, to leave the policy disabled.
Work in a fixed order. Run the profile-only baseline and save the verification output. Add the time scale and compare. Add the seed and compare again. Add --bot-performance-timing last, and only when your tier and profile support it, because it affects a different set of surfaces from the other two. Changing everything at once makes it impossible to tell which flag produced which difference. Check the CLI flags reference for your tier before planning a test, so that a result that looks wrong is not simply a flag your tier does not cover.
Verifying that timing stays consistent
Run verification only on pages and test sessions you are authorized to use. Open a blank page that you control, launch with the profile and flags above, and compare what you read against what the profile describes. Write down the profile you launched with before you read any values, so the comparison is not made from memory. The snippet below can be pasted into the developer console of that page.
const { hardwareConcurrency, deviceMemory } = navigator;
const samples = [];
for (let run = 0; run < 5; run += 1) {
const start = performance.now();
for (let i = 0; i < 100000; i += 1) Math.sqrt(i);
samples.push(performance.now() - start);
}
console.log({ hardwareConcurrency, deviceMemory, samples });
The snippet reads the two hardware values and collects five short timing samples. Treat the output as a consistency check rather than a benchmark: the absolute numbers depend on the host, and you are looking for agreement between the reported values and the profile.
What to check:
hardwareConcurrencymatches the core count that the profile describes.deviceMemorymatches the memory class that the profile describes.- The five samples are close to each other, and a second launch with the same profile, scale, and seed produces a consistent pattern.
- Changing only the seed changes the pattern, which confirms that the seed is applied.
- Reading the same values inside a dedicated worker and an iframe gives the same hardware values as the main page.
Timing samples will never be identical, and they should not be. Real hardware varies slightly from run to run because of scheduling and cache state. A set of samples that is perfectly flat, or one that swings wildly, deserves more suspicion than a set with small natural variation.
Hardware and timing values should read the same wherever a script runs. A site can create dedicated workers and frames as easily as it reads the main page, so a mismatch between them is easy to find. Test those contexts as well, and treat any difference as a finding to record rather than an anomaly to explain away.
If a result does not match, change one setting at a time and repeat the run, which is the approach the performance documentation recommends for troubleshooting. Common causes are a missing or wrong seed, a profile that does not include the feature, or an ENT tier that does not cover the flag.
Public fingerprint test pages can provide a second opinion, but they do not publish guaranteed pass criteria, and a clean-looking result on one page is not a promise about any other site. Treat each page as one data point, and compare several runs before drawing a conclusion. Keep the exact launch command with the results so that another reviewer can repeat the run later.
Where BotBrowser helps and where it stops
BotBrowser documents --bot-time-scale and --bot-time-seed (ENT Tier2) to compress the high-resolution timing intervals reported to page content and to give each instance a stable, seed-reproducible timing profile next to the hardware values the profile defines, so you can repeat consistent timing across test sessions. BotBrowser cannot change how fast the host actually runs JavaScript, replace the physical hardware, or guarantee that every timing surface or third-party detector accepts the result; the flags only change values reported to page content, and they depend on ENT tier and profile support, with --bot-performance-timing additionally needing ENT Tier3 and a profile that enables it.
In practice the useful outcome is a reproducible baseline. You can record the profile, the scale, and the seed, rerun the same checks on another day, and see whether anything about hardware shape or timing drifted. That is a narrower claim than hiding a device, and it is the one the flags are documented to support.
These values describe a device and not a person. Timing and hardware shape is one input among many, and no single value decides how a site treats a visitor. This page makes no claim about how accurately any tracker identifies a device; the aim is to help you check that what your browser reports is internally consistent and honestly documented.
A review record is most useful when it lists:
- The profile file and the BotBrowser version used.
- The exact flags, including the scale and the seed.
- The ENT tier available for the run.
- The hardware values and timing samples that were read.
- The date and the page used for the check.
Related reading covers the neighboring surfaces. The browser fingerprinting overview explains how signals combine, hardware concurrency and worker planning looks at the core count in more detail, and frame rate control covers the display timing flag.
Public sources
Related Articles
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.