Skip to content

Are v8’s optimizations deterministic? Extracting the sequence of JS functions executed by any given webpage

I am trying to reconstruct the exact sequence of executed Javascript functions (call graph) from log data gathered using the Tracing Profiler, particularly from the category “v8.cpu_profiler”.

Unfortunately, the number of nodes (function definitions) and edges (function calls) I obtain fluctuates across runs even if I interact with the test Web application in exactly the same way.

Currently, I use Puppeteer to extract the trace and interact with the application:

let browser = await puppeteer.launch({
    headless: true,
    userDataDir: userDataFolder,
        `--js-flags=--jitless --no-opt --predictable --no-concurrent-recompilation `] // disable chrome's optimizations

// Get the tab opened by default
let [page] = await browser.pages();

// Make sure to disable the cache
await page.setCacheEnabled(false);

// create cdp session
const cdp = await;

await cdp.send('Tracing.start', {
    traceConfig: {
        recordMode: 'recordContinuously',
        includedCategories: ['disabled-by-default-v8.cpu_profiler'],
        excludedCategories: ['*']
    transferMode: 'ReturnAsStream'

await page.waitForTimeout(1000);

// Go to the page
await Promise.race([
    page.goto(this.url, { waitUntil: ["load", "networkidle2"] }),

// Wait to make sure that the page is fully loaded
await page.waitForTimeout(1000);

// dynamically interact with the webpage
await interact(page);

As you can see, I use the flags --jitless --no-opt --predictable --no-concurrent-recompilation to disable any source of non-determinism I know of, but it is still not enough. Why does this happen? Theoretically, the executed functions are exactly the same and the Tracing Profiler should be very precise.

Is it possible that v8 is still doing some optimizations under the hood and if yes, how can I disable them?

Is there any other way I can extract the exact sequence of executed functions?



V8 has a --trace flag, which gives an exact sequence of executed functions. Warning: it produces lots of output, because there are lots of function calls on typical websites.

The function call sequence is entirely deterministic. Internal optimizations have nothing to do with that, so there’s no reason to turn them off. (Think about it: if optimization changed the order in which functions called each other, that would break applications. Whatever an engine does internally must not change the behavior that the developer of the website intended.)

The “Tracing Profiler” is a sample-based profiler, and as such non-deterministic by design (and there can’t possibly be a flag to “turn that off”). It uses a ticker thread that samples the main thread every millisecond (or similar interval, I’m not sure about the exact value). What exactly the main thread is doing at this point will be different between different runs; it’s only statistically meaningful (e.g. if a function takes X% of the overall time, it’ll be represented on approximately X% of the sampling ticks).

Of course, JavaScript applications can have their own sources of non-determinism:
if (Math.random() < 0.5) { foo(); } else { bar(); } will create different call sequences on different runs. It’s also conceivable that JavaScript reacts to mouse movements or other user interactions, which would be exceedingly difficult to replicate exactly.

User contributions licensed under: CC BY-SA
4 People found this is helpful