npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mark1russell7/lag

v0.2.0

Published

Main-thread lag monitors for browser apps, on the devices of real users: blocked time, hangs, long animation frames, INP and the Core Web Vitals of each page view, as OpenTelemetry metrics, events and spans.

Readme

@mark1russell7/lag

npm CI license

@mark1russell7/lag measures the lag of the main thread in browser apps, on the devices of real users. It sends the results as OpenTelemetry metrics, events and spans.

The website has the documentation, a live playground, the thesis and the test results: https://mark1russell7.github.io/lag/.

What the monitors measure

  • Blocked time. DriftLag measures how much of each 100 ms the main thread was busy.
  • Hangs. A Web Worker sends heartbeats. When the main thread does not acknowledge them, the worker reports the hang itself.
  • Long animation frames. The monitor finds the script that blocked a frame (Chromium only).
  • Interactions. Event Timing gives the input delay, the processing time and the presentation delay of each interaction.
  • Core Web Vitals. The monitors give INP, CLS, LCP, FCP and TTFB for each page view, also after a restore from the back/forward cache.
  • The device and the clocks. Other monitors measure the frames, the idle time, the memory, the compute pressure, the garbage collections, the timer throttling and the clock jumps.

setupAllMonitors() starts each monitor that the browser can support. The monitor overview lists each monitor with its browser support and its cost.

Why this library

  • Calibrated probes. A timer step takes longer than its delay, also on an idle page. DriftLag subtracts the idle step duration of its browser and operating system.
  • Valid samples only. The monitors discard each sample that a hidden page, a frozen page or a system suspend makes incorrect.
  • No lost hangs. When a page closes during a hang, the next page of the origin reports the hang from the hang journal of the worker. Another open page of the origin can also report it.
  • Low cardinality. All metrics are counters or histograms with a small, fixed set of attribute values. The details go into the events.
  • Full teardown. stop() releases each timer, listener and observer.

Install

npm install @mark1russell7/lag

The package is an ES module with TypeScript types. Its only dependency is page-lifecycle-tracker.

The worker monitors need a bundler that supports module workers (new Worker(new URL("./file.js", import.meta.url), { type: "module" })), for example Vite, webpack 5, or Rollup with a worker plugin. The bundler makes a separate file for the worker.

Vite

In the Vite dev server, exclude the worker export from the dependency optimization. Without this setting, the dev server cannot find the file of the worker. vite build needs no setting.

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
    optimizeDeps : {
        exclude : ["@mark1russell7/lag/worker"],
    },
});

Quick start

This example uses the OpenTelemetry SDK for JavaScript. Each OpenTelemetry Meter works.

npm install @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http @opentelemetry/resources
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { createBrowserDeps, setupAllMonitors } from "@mark1russell7/lag";
import { createLagWorker } from "@mark1russell7/lag/worker";

const provider = new MeterProvider({
    resource : resourceFromAttributes({ "service.name" : "shop", "service.instance.id" : crypto.randomUUID() }),
    readers : [new PeriodicExportingMetricReader({ exporter : new OTLPMetricExporter({ url : "https://collector.example.com/v1/metrics" }) })],
});

const worker = createLagWorker();
const monitors = setupAllMonitors(createBrowserDeps(window, { meter : provider.getMeter("lag"), logger : console, worker }));

// When the page becomes hidden: record the final values of the page view, then send them
document.addEventListener("visibilitychange", (event) => {
    if (document.visibilityState !== "hidden") return;
    monitors.flush(event);
    void provider.forceFlush();
});

The code does these steps:

  1. It makes a MeterProvider with a new service.instance.id for each page load. Thus each browser writes its own series.
  2. createLagWorker() starts the Web Worker of the worker-lag monitor.
  3. createBrowserDeps(window, options) finds the browser APIs. setupAllMonitors() starts the monitors.
  4. When the page becomes hidden, monitors.flush(event) records the final values of the page view before the last export.

To stop the monitors, use monitors.stop(), then worker.terminate(). The worker is yours: the monitors do not stop it.

For the events, the spans and the hang reports of the worker, refer to the quick start on the website. The OpenTelemetry setup gives the SDK settings for the metrics of many browsers, for example exponential histograms.

Exports

| Export | What it contains | |---|---| | @mark1russell7/lag | The monitors, setupAllMonitors(), createBrowserDeps(), the catalogs of the metrics, events and spans, and the OpenTelemetry adapters. The module reads no browser global when it loads. | | @mark1russell7/lag/worker | createLagWorker() and the worker module that it starts. | | @mark1russell7/lag/async-context | An optional add-on: the function, the API and the operation of each long callback, in all browsers. It uses the callback hook of async-browser-context 0.2.1 or later, an optional peer dependency. | | @mark1russell7/lag/event-line.js | formatEventLine(), which makes the body line of each event record. |

The API reference lists each export.

Documentation

License

MIT