/timenstein

A simpler way to gather user timings.

Primary LanguageJavaScript

Timenstein

Timenstein's Monster

ES5 (.js) version

Uncompressed size. gzip size. Brotli size.

ES6 (.mjs) version

Uncompressed size. gzip size. Brotli size.

Timenstein is a very small framework that simplifies working with the User Timing API. The User Timing API is very useful for profiling JavaScript performance in applications, but working with it can be (IMO) unwieldy at times. I made Timenstein to simplify working with it as much as possible.

How does User Timing work?

The User Timing API consists of two pieces of functionality: marks and measures. Let's recap what these are.

Marks are used to mark specific spots in the performance timeline in a page's life cycle. Marks are the simplest aspect of User Timing. They can be used to develop custom metrics specific to your application, such as the time it takes for a user to click on a call to action, for example:

document.getElementById("cta-button").addEventListener("click", () => {
  // Define mark name
  const markName = "time-to-cta-click";

  // Mark when the image finishes loading
  performance.mark(markName);

  // Get the entry associated with the mark:
  const markEntry = performance.getEntriesByName(markName)[0];
});

Measures are used to measure the time between marks on the performance timeline. This is useful when you want to profile the performance of some part of your application's code, such as an asynchronous operation like a fetch request from an API:

document.getElementById("get-data").addEventListener("click", () => {
  // Define mark names
  const measureName = "get-data";
  const startMarkName = `${measureName}-start`;
  const endMarkName = `${measureName}-end`;

  // Mark the start point
  performance.mark(startMarkName);

  fetch("https://bigolemoviedatabase.dev/api/movies/fargo").then(response => response.json()).then(data => {
    // We have the response, now mark the end point
    performance.mark(endMarkName);

    // Now we can measure
    performance.measure(measureName, startMarkName, )
  });
});

As you can see in both examples, we have to maintain the name of each mark and then retrieve it from the performance entry buffer ourselves either via getEntriesByName as shown in the above example, or through a PerformanceObserver. The latter is the recommended approach, but can be problematic depending on your application architecture.

Great, so how does Timenstein simplify this?

Like User Timing, Timenstein makes use of marks and measures, but it stores this information in its own buffer and simplifies measurements by relying on handles. In Timenstein, a handle is a unique string that it uses to organize and manage marks and measures, reducing the amount of work you would otherwise do if you worked with User Timing directly.

import Timenstein from "timenstein";

const userPerf = new Timenstein();

// Define the handle we'll use to make marks and measures
const handle = "get-data";

// Mark the start point using our handle
userPerf.mark(handle);

fetch("https://bigolemoviedatabase.dev/api/movies/fargo").then(response => response.json()).then(data => {
  // Mark the end point using the same handle
  userPerf.mark(handle);

  // Measure how long it took to get the data by using the same handle, only
  // this time we call the `measure` method
  userPerf.measure(handle);
});