@qlub-foss/argus
v1.7.0
Published
Web performance metric collector
Readme
Argus — Web performance metric collector
Argus, named after the many-eyed giant in Greek mythology, watches your client-side app and emits structured performance payloads you can forward to analytics or logging.
- Web Vitals — CLS, INP, LCP, FCP, and TTFB via
web-vitals - API timing —
PerformanceObserveron resource entries, filtered by URL regex and optional duration bounds - User timing —
performance.mark/measureflows with optional duration bounds, using helpers that pair with named trackers
Installation
The package is published under the npm organization qlub-foss (scope @qlub-foss). Install with your preferred package manager:
npm install @qlub-foss/argusyarn add @qlub-foss/arguspnpm add @qlub-foss/argusbun add @qlub-foss/argusPublished on the public npm registry. No custom .npmrc is required for installation.
Configuration
Define an ArgusConfig (partial configs are merged with defaults):
metadataUpdateMode— controls howsetMetadata()handles empty incoming values (undefined,null,""):override(default): applies incoming values as-is (undefinedremoves a key).keepLastValid: ignores empty incoming values and keeps the previous non-empty value.
samplingRate— Global default (0–1) when a subsection does not override it.webVitals—enabled,samplingRate, optionalwhitelistedFieldson the emitted payload, optionalattribution(defaultfalse) to lazily load theweb-vitalsattribution build. Enabling it dynamically importsweb-vitals/attributionin addition to the base build that's always loaded, so it's a deliberate size tradeoff, not a swap — only pay it if you use it. When enabled, the metric-specific attribution diagnostic fields (e.g.target,timeToFirstByte,resourceLoadDelayfor LCP — see the web-vitals attribution docs) are flattened onto the top-level payload rather than nested under anattributionkey, so each one is individually selectable viawhitelistedFieldsjust like any other field. Several attribution fields carry rawPerformanceEntry-like objects (navigationEntry,lcpEntry,lcpResourceEntry,largestShiftEntry,largestShiftSource,fcpEntry,processedEventEntries,longAnimationFrameEntries,longestScript) — omit these fromwhitelistedFieldsto skip exporting the heavy raw entries. IfwhitelistedFieldsisn't set, everything is exported, including these raw entries.apiTiming—enabled,samplingRate,trackers(regex, optionallabel,lowerBound,upperBound, per-trackersamplingRate), optionalwhitelistedFields.userTiming—enabled(default off in defaults),samplingRate,trackers(idmatching your mark/measure names, optional bounds andsamplingRate), optionalwhitelistedFields.
Each reported object is normalized with agent: "argus", timing fields, your init metadata, and an event / argusMetricType derived from the collector (web-vital, api-timing, user-timing).
Example config
import type { ArgusConfig } from "@qlub-foss/argus";
export const argusConfig: ArgusConfig = {
metadataUpdateMode: "override",
samplingRate: 1,
webVitals: {
enabled: true,
samplingRate: 1,
attribution: true
},
apiTiming: {
enabled: true,
samplingRate: 1,
trackers: [
{
regex: /\/gods/,
label: "fetch_greek_gods",
lowerBound: 200,
upperBound: 800,
samplingRate: 0.7
},
{
regex: /\/philosophers/,
label: "fetch_philosophers",
lowerBound: 100,
upperBound: 900,
samplingRate: 0.3
}
]
},
userTiming: {
enabled: true,
samplingRate: 1,
trackers: [{ id: "checkout-flow", lowerBound: 0, upperBound: 30_000 }]
}
};Usage
- Provide a report callback (send to your backend, analytics, or logger).
- Call
Argus.getInstance(onReport, config)once — the class is a singleton; the first call’s config is kept for the lifetime of the page. - Call
init(metadata?)with optional key/value metadata merged into each payload. - On teardown (e.g. React
useEffectcleanup), callshutdown()to disconnect observers.
User timing trackers expect you to bracket work with the exported helpers (or equivalent marks/measures). The id in config must match the prefix used in markUserTimingStart / markUserTimingEnd:
import { useEffect } from "react";
import { Argus, markUserTimingEnd, markUserTimingStart } from "@qlub-foss/argus";
import { argusConfig } from "./argusConfig";
const handleMetricReport = (metric: Record<string, any>) => {
// forward metric
};
export function App() {
useEffect(() => {
const argus = Argus.getInstance(handleMetricReport, argusConfig);
void argus.init({
appVersion: "1.0.0"
});
return () => {
argus.shutdown();
};
}, []);
const onCheckout = () => {
markUserTimingStart("checkout-flow");
try {
// ...
} finally {
markUserTimingEnd("checkout-flow");
}
};
// ...
}The same pattern works in other frameworks: instantiate once, init after mount, shutdown on unmount. The library targets browsers with PerformanceObserver and the Web Vitals APIs.
Development
- Install pnpm (version pinned in
package.jsonaspackageManager). - Install dependencies:
pnpm install - Build:
pnpm build - Test:
pnpm test
Hooks and quality tooling: Lefthook, ESLint, Prettier, and Commitlint (conventional commits). See Conventional Commits for message format.
Releases
CI runs tests and build, then semantic-release on pushes to main, alpha, beta, and gamma (see .github/workflows/semantic-release.yml). Releases are published to npm and GitHub Releases; version bumps and changelog updates follow Conventional Commits.
