ES5 (.js) version
ES6 (.mjs) version
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.
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.
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);
});