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

@mostajs/mjs-metrics

v0.3.1

Published

Métriques & test de charge façon Apache JMeter pour l'écosystème @mostajs/* : scénarios HTTP, montée en charge, percentiles (p50/p90/p99) via t-digest zéro-dépendance, débit, taux d'erreur, seuils. Bâti sur le client HTTP de @mostajs/mjs-unit/web.

Readme

@mostajs/mjs-metrics

Auteur : Dr Hamid MADANI [email protected] · Licence : AGPL-3.0-or-later Statut : ✅ 0.2.0 — implémenté, testé (17 tests verts via mjs-unit), zéro dépendance runtime.

Métriques et test de charge façon Apache JMeter pour l'écosystème @mostajs/*. On décrit des scénarios (suites de requêtes HTTP), on les exécute sous montée en charge (utilisateurs virtuels concurrents, paliers, durée), et on obtient un rapport de performance : débit (req/s), latences p50/p90/p99 (t-digest interne), taux d'erreur, min/max/moy, seuils de réussite, et une preuve HTML autonome.

Bâti sur le client HTTP zéro-dépendance de @mostajs/mjs-unit/web (createHttpTester / startServer) : aucune dépendance de transport ajoutée.

Installation

npm i -D @mostajs/mjs-metrics @mostajs/mjs-unit

@mostajs/mjs-unit est un peer (fournit le client HTTP). En dev d'un module @mostajs/*, les deux sont déjà dans le workspace.

Démarrage rapide

import { scenario, run } from "@mostajs/mjs-metrics";

const home = scenario("home")
  .step("GET /", (http) => http.get("/"))
  .check((res) => res.status === 200);

const report = await run(home, { target: "http://localhost:3000", vus: 25, duration: "15s" });

report.print();                       // résumé console
if (!report.passed) process.exit(1);  // CI : échec si un seuil casse

FAQ — « je veux mesurer des valeurs quelconques dans mon application »

Question fréquente : comment introduire mjs-metrics pour suivre des flux à l'intérieur d'une app que je développe (pas la charger de l'extérieur) ?

Il y a deux usages distincts :

| Besoin | Outil | |---|---| | Charger depuis l'extérieur (envoyer du trafic, mesurer la latence HTTP) | scenario() + run() | | Instrumenter des valeurs à l'intérieur de l'app (suivre un flux, chronométrer une fonction, compter des événements) | createMetrics() — voir Instrumentation in-app |

scenario().step(http => …) est couplé au HTTP (la step renvoie une TestResponse, c'est piloté par vus/iterations) : ce n'est pas l'outil pour du suivi interne continu. Pour ça, le registre @mostajs/mjs-metrics/runtime te donne compteurs / jauges / histogrammes / chronomètres + snapshot() / toJSON() / toHTML() et un probe /metrics.

Besoin sur mesure ? La primitive TDigest (+ summarize) est exportée et réutilisable seule, si tu veux construire ton propre agrégat :

import { TDigest, summarize } from "@mostajs/mjs-metrics";
import { performance } from "node:perf_hooks";

const traitementCmd = new TDigest();          // un histogramme par flux suivi
let nbCmd = 0, nbErreurs = 0;

async function traiterCommande(cmd) {
  const t0 = performance.now();
  try { await faireLeTravail(cmd); nbCmd++; }
  catch (e) { nbErreurs++; throw e; }
  finally { traitementCmd.add(performance.now() - t0); } // ← la valeur mesurée
}

export const snapshot = () => ({
  commandes: nbCmd,
  erreurs: nbErreurs,
  latence_ms: summarize(traitementCmd), // { count, min, max, mean, p50, p90, p99 }
});

digest.add(valeur) accepte n'importe quelle valeur numérique (durée, taille de payload, profondeur de file, score…), pas seulement du temps.

How-to (recettes)

1. Tester un handler/app sans démarrer de serveur

target accepte un handler Node (req, res) => … (ou une app Express) : un serveur éphémère est démarré pour toute la campagne puis fermé. Idéal en CI / micro-bench.

import { scenario, run } from "@mostajs/mjs-metrics";
import { createApp } from "../src/app.js"; // votre app : (req,res)=>… ou http.Server

const report = await run(
  scenario("ping").step("GET /health", (http) => http.get("/health")).check((r) => r.status === 200),
  { target: createApp(), vus: 50, iterations: 100 }, // iterations = déterministe
);
report.print();

2. Parcours multi-étapes avec vérifications et think time

const parcours = scenario("checkout")
  .step("POST /login", (http) => http.post("/login", { user: "a", pass: "b" }))
  .check((r) => r.status === 200)
  .think(300) // pause "humaine" entre deux étapes
  .step("GET /cart", (http) => http.get("/cart"))
  .check((r) => Array.isArray(r.body.items));

await run(parcours, { target: "http://localhost:3000", vus: 30, duration: "1m" });

Chaque .check() se rattache à la dernière étape ; un check faux compte la requête comme un échec (alimente errorRate).

3. Montée en charge progressive (rampUp)

await run(home, {
  target: "http://localhost:3000",
  vus: 200,
  rampUp: "20s",   // les 200 VUs démarrent étalés sur 20 s
  duration: "2m",
});

4. Seuils → succès/échec (CI)

const report = await run(home, {
  target: "http://localhost:3000",
  vus: 50,
  duration: "30s",
  thresholds: {
    p95: "<300",        // latence p95 sous 300 ms
    p99: "<800",
    errorRate: "<=1%",  // le % divise par 100
    throughput: ">100", // au moins 100 req/s
  },
});
console.log(report.passed ? "OK" : "KO", report.thresholds);

Clés acceptées : p50 p90 p95 p99 min max mean errorRate throughput requests failures. Opérateurs : < <= > >= ==.

5. Preuve HTML autonome

import { writeFileSync } from "node:fs";

const report = await run(home, { target: "http://localhost:3000", vus: 25, duration: "15s" });
writeFileSync("charge.html", report.toHTML({ title: "Charge — page d'accueil" }));

Agréger plusieurs campagnes dans un seul rapport :

import { renderHtmlReport } from "@mostajs/mjs-metrics/report";

const a = await run(scenarioA, optsA);
const b = await run(scenarioB, optsB);
writeFileSync("rapport.html", renderHtmlReport([
  { label: "API publique", report: a },
  { label: "Back-office", report: b },
]));

6. Lire les métriques par programme

const { metrics } = await run(home, { target, vus: 10, iterations: 50 });
console.log(metrics.throughput, metrics.errorRate, metrics.latency.p99);
console.log(metrics.perStep["GET /"].p90); // latences par étape

CLI

# scénario versionné + preuve HTML
mjs-metrics scenarios/login.mjs --vus 50 --duration 30s --rampup 5s --html=login.html

# plusieurs fichiers, sortie JSON agrégée
mjs-metrics scenarios/*.mjs --iterations 100 --json-out=rapport.json

# override de la cible depuis la ligne de commande
mjs-metrics scenarios/api.mjs --target http://staging.local --vus 20

Chaque fichier exporte scenarios (et éventuellement options) :

// scenarios/login.mjs
import { scenario } from "@mostajs/mjs-metrics";
export const scenarios = scenario("login")
  .step("POST /login", (http) => http.post("/login", { user: "a", pass: "b" }))
  .check((r) => r.status === 200);
export const options = { target: "http://localhost:3000", vus: 50, thresholds: { p99: "<500" } };

Les drapeaux CLI priment sur les options. Code de sortie 1 si un seuil casse.


Mini-démo

Le dépôt fournit une démo autonome (serveur simulé à latence variable + preuve HTML) :

node examples/mini-demo.mjs        # → examples/mini-demo-report.html
mjs-metrics examples/cli-scenario.mjs --html=examples/cli-report.html

# instrumentation in-app : dashboard LIVE (recharger = chiffres frais)
bash examples/serve.sh             # http://127.0.0.1:8088/metrics.html  (ou : npm run demo)

Tous les exemples sont décrits dans examples/README.md.

Extrait de sortie :

── mjs-metrics ─────────────────────────────
requêtes   : 400  (échecs 0, 0.00%)
débit      : 420.7 req/s sur 950.85 ms  (10 VUs)
latence ms : min 2.62 · moy 12.39 · p50 12.14 · p90 18.92 · p99 31.82 · max 34.37
  · POST /api/login: p50 15.51 · p90 20.05 · p99 33.44 (200)
  · GET /api/profile: p50 9.06 · p90 13.99 · p99 15.95 (200)
seuils :
  ✓ p99 <200 (réel 31.82)
  ✓ errorRate <=0 (réel 0)
  ✓ throughput >50 (réel 420.68)
→ TOUS LES SEUILS PASSENT

Instrumentation in-app (suivre des flux dans votre application)

Au-delà du load-testing, mjs-metrics expose un registre de métriques pour mesurer des valeurs quelconques à l'intérieur de votre app — sous-export @mostajs/mjs-metrics/runtime, zéro-dépendance (histogrammes/chronos via le même t-digest).

import { createMetrics } from "@mostajs/mjs-metrics/runtime";

// Une fois, au démarrage de l'app.
export const m = createMetrics();
const commandes = m.counter("commandes");        // cumulatif
const file      = m.gauge("file_attente");        // monte/descend
const taille    = m.histogram("taille_panier");   // distribution d'une valeur
const chrono    = m.timer("traitement_ms");       // durées (ms)

Puis, là où vit le flux métier :

async function traiterCommande(cmd) {
  file.inc();
  try {
    await chrono.measure(() => faireLeTravail(cmd)); // chronomètre auto
    taille.observe(cmd.items.length);
    commandes.inc();
  } finally {
    file.dec();
  }
}

Lecture à la demande :

m.snapshot();   // { counters, gauges, histograms:{p50,p90,p99,…}, timers:{…} }
m.toJSON();     // idem (endpoint /metrics)
m.toHTML();     // tableau de bord HTML autonome

Exposer en HTTP (/metrics)

Le probe branche le registre sur une app Express ou un handler Node brut :

import { metricsProbe } from "@mostajs/mjs-metrics/runtime";

// Express
app.use(metricsProbe(m));                 // GET /metrics (JSON) + /metrics.html (dashboard)

// http natif
const probe = metricsProbe(m, { title: "Service commandes" });
http.createServer((req, res) => {
  if (probe(req, res)) return;            // servi par le probe
  // … votre routage …
}).listen(3000);

Voir examples/runtime-inapp.mjs pour une démo complète (compteur/jauge/histo/chrono

  • probe + HTML).

| Métrique | Méthodes | Pour | |---|---|---| | counter(n) | inc(k=1) add(k) reset() value | événements cumulés (requêtes, erreurs…) | | gauge(n) | set(v) inc() dec() value | valeur instantanée (file, connexions…) | | histogram(n) | observe(v) summary() count | distribution d'une valeur (tailles, scores…) | | timer(n) | measure(fn) start()→stop() record(ms) summary() | durées (p50/p90/p99 en ms) |

Charge externescenario()/run(). Suivi interne continu → createMetrics(). Même registre redemandé par son nom = même instance (partage entre modules).

API de référence

| Export | Rôle | |---|---| | scenario(name) | builder .step(label, fn).check(pred).think(ms) | | run(scenarios, opts) | exécute la campagne → Promise<Report> | | Report | { metrics, thresholds, passed, print(), toJSON(), toHTML(opts?) } | | renderHtmlReport(input, opts?) | HTML autonome (1 rapport ou [{label, report}]) — via @mostajs/mjs-metrics/report | | renderJsonReport(input) | JSON (1 rapport ou liste) | | TDigest | estimateur de quantiles streaming, zéro-dépendance | | summarize(digest) · parseDuration(str) | utilitaires |

RunOptions = { target, vus, rampUp?, duration?, iterations?, thresholds? } (iterations rend la campagne déterministe et prime sur duration).

Tests

Suite exécutée par le CLI @mostajs/mjs-unit (zéro-dépendance) :

npm test    # typecheck (tsc --noEmit) → build → node node_modules/.bin/mjs-unit test-scripts/*.test.mjs

17 tests : t-digest, builder de scénario, orchestrateur run(), reporters HTML/JSON.

Limites

Mono-process : la charge réaliste est bornée par l'event-loop Node (très bien pour de l'in-process ou un service local ; pour des campagnes massives distribuées, viser une 0.3.0 multi-process).