ScopedPerformance

deno doc codecov

A Performance API (User timing) wrapper to avoid mark/measure name collisions.

Problem

When working with the User Timing Performance API on a Deno server with async handlers. You may encounter interferences, as the performance instance and entries are shared for all the concurrently executing handlers on the same isolate.

The following code poses a problem because the same names might be used for diferent requests if they overlap in time:

Deno.serve(async (_req: Request => {
  performance.mark('start');

  // some async work here ...

  const measureEntry = performance.measure('total', 'start');

  return new Response(...);
});

Using clearMarks()/clearMeasures() does not help; It is even worse as it may remove the entries of another request.

Solution

We can avoid the problem by making a new ScopedPerformance instance for each request, which will automatically take care of prefixing the mark/measure names with a unique prefix:

import { ScopedPerformance } from './mod.ts';

Deno.serve(async (_req: Request => {
  const scoped = new ScopedPerformance();
  scoped.mark('start');

  // some async work here ...

  const measureEntry = scoped.measure('total', 'start');

  return new Response(...);
});

Executing getEntries() in the ScopedPerformance instance will only return the scoped entries:

import { ScopedPerformance } from './mod.ts';

// Let's make a mark in the global original `performance` instance
performance.mark('global start');

// Then proced to make an scoped instance and some scoped marks
const scoped = new ScopedPerformance();
scoped.mark('start');
scoped.measure('total', 'start');

// When retrieving the entrios of the scoped instance, we just get those...
console.log(scoped.getEntries());

/*
[
  PerformanceMark {
  name: "f5a8be11-610b-4745-afdd-98fa8ae0e6d0::start",
  entryType: "mark",
  startTime: 24,
  duration: 0,
  detail: null
},
  PerformanceMeasure {
  name: "f5a8be11-610b-4745-afdd-98fa8ae0e6d0::total",
  entryType: "measure",
  startTime: 24,
  duration: 0,
  detail: null
}
]
*/

// The global `performance` instace obviusly contains all the marks ...
console.log(performance.getEntries());

/*
[
  PerformanceMark {
  name: "global start",
  entryType: "mark",
  startTime: 24,
  duration: 0,
  detail: null
},
  PerformanceMark {
  name: "f5a8be11-610b-4745-afdd-98fa8ae0e6d0::start",
  entryType: "mark",
  startTime: 24,
  duration: 0,
  detail: null
},
  PerformanceMeasure {
  name: "f5a8be11-610b-4745-afdd-98fa8ae0e6d0::total",
  entryType: "measure",
  startTime: 24,
  duration: 0,
  detail: null
}
]
*/