perfstack 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/histogram.ts","../src/tracer.ts","../src/profiler.ts","../src/express.ts","../src/mongoose.ts"],"sourcesContent":["export { Profiler } from \"./profiler.js\";\nexport { Histogram, percentile } from \"./histogram.js\";\nexport { Tracer } from \"./tracer.js\";\nexport { expressMiddleware, dashboardMiddleware } from \"./express.js\";\nexport { mongoosePlugin } from \"./mongoose.js\";\nexport type {\n HttpRecord,\n QueryRecord,\n MemorySnapshot,\n PerfReport,\n PerfstackOptions,\n RouteSummary,\n HistogramSummary,\n Span,\n} from \"./types.js\";\n\nimport { Profiler } from \"./profiler.js\";\nimport { dashboardMiddleware, expressMiddleware } from \"./express.js\";\nimport { mongoosePlugin } from \"./mongoose.js\";\n\ntype AnyApp = {\n use: (...args: unknown[]) => unknown;\n get?: (path: string, handler: (...args: unknown[]) => unknown) => unknown;\n};\n\nexport interface InitResult {\n profiler: Profiler;\n middleware: ReturnType<typeof expressMiddleware>;\n dashboard: ReturnType<typeof dashboardMiddleware>;\n mongoosePlugin: ReturnType<typeof mongoosePlugin>;\n}\n\nexport function init(\n app?: AnyApp,\n options: ConstructorParameters<typeof Profiler>[0] & { dashboardPath?: string } = {},\n): InitResult {\n const profiler = new Profiler(options);\n const middleware = expressMiddleware(profiler);\n const dashboard = dashboardMiddleware(profiler);\n const plugin = mongoosePlugin(profiler);\n if (app) {\n app.use(middleware);\n if (typeof app.get === \"function\") {\n app.get(options.dashboardPath ?? \"/__perf\", dashboard as unknown as (...a: unknown[]) => unknown);\n app.get((options.dashboardPath ?? \"/__perf\") + \".json\", dashboard as unknown as (...a: unknown[]) => unknown);\n }\n }\n return { profiler, middleware, dashboard, mongoosePlugin: plugin };\n}\n","import type { HistogramSummary } from \"./types.js\";\n\nexport class Histogram {\n private samples: number[] = [];\n private max: number;\n\n constructor(max = 1000) {\n this.max = max;\n }\n\n record(value: number): void {\n this.samples.push(value);\n if (this.samples.length > this.max) {\n this.samples.shift();\n }\n }\n\n summary(): HistogramSummary {\n const count = this.samples.length;\n if (count === 0) {\n return { count: 0, min: 0, max: 0, avg: 0, p50: 0, p90: 0, p95: 0, p99: 0 };\n }\n const sorted = [...this.samples].sort((a, b) => a - b);\n const sum = sorted.reduce((s, v) => s + v, 0);\n return {\n count,\n min: sorted[0]!,\n max: sorted[sorted.length - 1]!,\n avg: round(sum / count),\n p50: percentile(sorted, 0.5),\n p90: percentile(sorted, 0.9),\n p95: percentile(sorted, 0.95),\n p99: percentile(sorted, 0.99),\n };\n }\n\n reset(): void {\n this.samples = [];\n }\n}\n\nexport function percentile(sortedAscending: number[], p: number): number {\n if (sortedAscending.length === 0) return 0;\n const idx = Math.min(sortedAscending.length - 1, Math.floor(p * sortedAscending.length));\n return round(sortedAscending[idx]!);\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomBytes } from \"node:crypto\";\nimport type { Span } from \"./types.js\";\n\ninterface TraceContext {\n traceId: string;\n spanId: string;\n}\n\nexport class Tracer {\n private storage = new AsyncLocalStorage<TraceContext>();\n private spans: Span[] = [];\n private max: number;\n\n constructor(options: { maxSpans?: number } = {}) {\n this.max = options.maxSpans ?? 500;\n }\n\n start(name: string, kind: Span[\"kind\"] = \"custom\", metadata: Record<string, unknown> = {}): {\n end: (info?: { error?: unknown; status?: Span[\"status\"]; extra?: Record<string, unknown> }) => Span;\n } {\n const startedAt = Date.now();\n const id = `s_${randomBytes(6).toString(\"hex\")}`;\n const parent = this.storage.getStore();\n const traceId = parent?.traceId ?? `t_${randomBytes(8).toString(\"hex\")}`;\n const parentId = parent?.spanId;\n\n return {\n end: (info = {}) => {\n const status: Span[\"status\"] = info.error ? \"error\" : info.status ?? \"ok\";\n const errorMsg = info.error instanceof Error ? info.error.message : info.error ? String(info.error) : undefined;\n const span: Span = {\n id,\n parentId,\n traceId,\n name,\n kind,\n startedAt,\n durationMs: Date.now() - startedAt,\n metadata: { ...metadata, ...(info.extra ?? {}) },\n status,\n error: errorMsg,\n };\n this.push(span);\n return span;\n },\n };\n }\n\n async withSpan<T>(\n name: string,\n fn: () => Promise<T>,\n options: { kind?: Span[\"kind\"]; metadata?: Record<string, unknown> } = {},\n ): Promise<T> {\n const startedAt = Date.now();\n const parent = this.storage.getStore();\n const traceId = parent?.traceId ?? `t_${randomBytes(8).toString(\"hex\")}`;\n const spanId = `s_${randomBytes(6).toString(\"hex\")}`;\n return this.storage.run({ traceId, spanId }, async () => {\n try {\n const result = await fn();\n this.push({\n id: spanId,\n parentId: parent?.spanId,\n traceId,\n name,\n kind: options.kind ?? \"custom\",\n startedAt,\n durationMs: Date.now() - startedAt,\n metadata: options.metadata ?? {},\n status: \"ok\",\n });\n return result;\n } catch (err) {\n this.push({\n id: spanId,\n parentId: parent?.spanId,\n traceId,\n name,\n kind: options.kind ?? \"custom\",\n startedAt,\n durationMs: Date.now() - startedAt,\n metadata: options.metadata ?? {},\n status: \"error\",\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n });\n }\n\n runInContext<T>(traceId: string, fn: () => T): T {\n const spanId = `s_${randomBytes(6).toString(\"hex\")}`;\n return this.storage.run({ traceId, spanId }, fn);\n }\n\n currentTraceId(): string | undefined {\n return this.storage.getStore()?.traceId;\n }\n\n recent(limit = 100): Span[] {\n return this.spans.slice(-limit);\n }\n\n byTrace(traceId: string): Span[] {\n return this.spans.filter((s) => s.traceId === traceId);\n }\n\n reset(): void {\n this.spans = [];\n }\n\n private push(span: Span): void {\n this.spans.push(span);\n if (this.spans.length > this.max) this.spans.shift();\n }\n}\n","import { Histogram } from \"./histogram.js\";\nimport { Tracer } from \"./tracer.js\";\nimport type {\n HttpRecord,\n MemorySnapshot,\n PerfReport,\n PerfstackOptions,\n QueryRecord,\n RouteSummary,\n} from \"./types.js\";\n\ninterface RouteBucket {\n method: string;\n path: string;\n histogram: Histogram;\n errors: number;\n}\n\nexport class Profiler {\n private startedAt: number;\n private httpHistogram = new Histogram();\n private queryHistogram = new Histogram();\n private routes = new Map<string, RouteBucket>();\n private slowestQueries: QueryRecord[] = [];\n private memorySamples: MemorySnapshot[] = [];\n private peakRss = 0;\n private peakHeapUsed = 0;\n private totalRequests = 0;\n private totalErrors = 0;\n private totalQueries = 0;\n private memoryInterval?: ReturnType<typeof setInterval>;\n readonly tracer: Tracer;\n private opts: Required<Pick<\n PerfstackOptions,\n | \"slowRequestThreshold\"\n | \"slowQueryThreshold\"\n | \"memorySampleIntervalMs\"\n | \"memorySamplesMax\"\n | \"routeBucketsMax\"\n | \"enableMemorySampling\"\n >>;\n\n constructor(options: PerfstackOptions = {}) {\n this.startedAt = options.startedAt ?? Date.now();\n this.opts = {\n slowRequestThreshold: options.slowRequestThreshold ?? 500,\n slowQueryThreshold: options.slowQueryThreshold ?? 200,\n memorySampleIntervalMs: options.memorySampleIntervalMs ?? 5_000,\n memorySamplesMax: options.memorySamplesMax ?? 120,\n routeBucketsMax: options.routeBucketsMax ?? 200,\n enableMemorySampling: options.enableMemorySampling ?? false,\n };\n this.tracer = new Tracer();\n if (this.opts.enableMemorySampling) this.startMemorySampling();\n }\n\n recordHttp(record: HttpRecord): void {\n this.totalRequests += 1;\n if (record.statusCode >= 500) this.totalErrors += 1;\n this.httpHistogram.record(record.durationMs);\n const key = `${record.method} ${record.path}`;\n let bucket = this.routes.get(key);\n if (!bucket) {\n if (this.routes.size >= this.opts.routeBucketsMax) {\n const oldest = this.routes.keys().next().value;\n if (oldest !== undefined) this.routes.delete(oldest);\n }\n bucket = { method: record.method, path: record.path, histogram: new Histogram(), errors: 0 };\n this.routes.set(key, bucket);\n }\n bucket.histogram.record(record.durationMs);\n if (record.statusCode >= 500) bucket.errors += 1;\n }\n\n recordQuery(record: QueryRecord): void {\n this.totalQueries += 1;\n this.queryHistogram.record(record.durationMs);\n if (record.durationMs >= this.opts.slowQueryThreshold) {\n this.slowestQueries.push(record);\n this.slowestQueries.sort((a, b) => b.durationMs - a.durationMs);\n if (this.slowestQueries.length > 50) this.slowestQueries.length = 50;\n }\n }\n\n takeMemorySample(): MemorySnapshot {\n const m = process.memoryUsage();\n const snap: MemorySnapshot = {\n takenAt: Date.now(),\n rss: m.rss,\n heapTotal: m.heapTotal,\n heapUsed: m.heapUsed,\n external: m.external,\n arrayBuffers: m.arrayBuffers,\n };\n this.memorySamples.push(snap);\n if (this.memorySamples.length > this.opts.memorySamplesMax) this.memorySamples.shift();\n if (snap.rss > this.peakRss) this.peakRss = snap.rss;\n if (snap.heapUsed > this.peakHeapUsed) this.peakHeapUsed = snap.heapUsed;\n return snap;\n }\n\n startMemorySampling(): void {\n if (this.memoryInterval) return;\n this.takeMemorySample();\n const interval = setInterval(() => this.takeMemorySample(), this.opts.memorySampleIntervalMs);\n (interval as { unref?: () => void }).unref?.();\n this.memoryInterval = interval;\n }\n\n stopMemorySampling(): void {\n if (this.memoryInterval) {\n clearInterval(this.memoryInterval);\n this.memoryInterval = undefined;\n }\n }\n\n report(): PerfReport {\n if (this.memorySamples.length === 0) this.takeMemorySample();\n const routes: RouteSummary[] = Array.from(this.routes.values()).map((bucket) => ({\n method: bucket.method,\n path: bucket.path,\n errorCount: bucket.errors,\n ...bucket.histogram.summary(),\n }));\n const slowestRoutes = [...routes].sort((a, b) => b.p95 - a.p95).slice(0, 10);\n return {\n uptimeMs: Date.now() - this.startedAt,\n startedAt: this.startedAt,\n takenAt: Date.now(),\n totalRequests: this.totalRequests,\n totalErrors: this.totalErrors,\n totalQueries: this.totalQueries,\n http: this.httpHistogram.summary(),\n routes,\n slowestRoutes,\n queries: this.queryHistogram.summary(),\n slowestQueries: [...this.slowestQueries],\n memory: {\n current: this.memorySamples.at(-1) ?? null,\n samples: [...this.memorySamples],\n peakRss: this.peakRss,\n peakHeapUsed: this.peakHeapUsed,\n },\n };\n }\n\n reset(): void {\n this.httpHistogram.reset();\n this.queryHistogram.reset();\n this.routes.clear();\n this.slowestQueries = [];\n this.memorySamples = [];\n this.peakRss = 0;\n this.peakHeapUsed = 0;\n this.totalRequests = 0;\n this.totalErrors = 0;\n this.totalQueries = 0;\n this.tracer.reset();\n }\n\n isSlowRequest(durationMs: number): boolean {\n return durationMs >= this.opts.slowRequestThreshold;\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport type { Profiler } from \"./profiler.js\";\n\ntype ReqLike = {\n method?: string;\n originalUrl?: string;\n url?: string;\n route?: { path?: string };\n headers: Record<string, string | string[] | undefined>;\n};\ntype ResLike = {\n statusCode: number;\n on(event: \"finish\", cb: () => void): void;\n setHeader(name: string, value: string): void;\n};\ntype NextLike = () => void;\n\nexport interface ExpressMiddlewareOptions {\n ignorePaths?: string[];\n traceHeader?: string;\n exposeTraceHeader?: boolean;\n}\n\nexport function expressMiddleware(\n profiler: Profiler,\n options: ExpressMiddlewareOptions = {},\n): (req: ReqLike, res: ResLike, next: NextLike) => void {\n const { ignorePaths = [], traceHeader = \"x-trace-id\", exposeTraceHeader = true } = options;\n return function perfstackMiddleware(req, res, next) {\n const path = req.originalUrl ?? req.url ?? \"/\";\n if (ignorePaths.some((p) => path === p || path.startsWith(`${p}/`))) {\n next();\n return;\n }\n\n const traceHeaderVal = req.headers[traceHeader.toLowerCase()];\n const traceId =\n (typeof traceHeaderVal === \"string\" && traceHeaderVal) ||\n (Array.isArray(traceHeaderVal) && traceHeaderVal[0]) ||\n `req_${randomBytes(8).toString(\"hex\")}`;\n\n if (exposeTraceHeader) res.setHeader(traceHeader, traceId);\n\n const startedAt = Date.now();\n\n profiler.tracer.runInContext(traceId, () => {\n res.on(\"finish\", () => {\n profiler.recordHttp({\n method: req.method ?? \"GET\",\n path: req.route?.path ?? simplifyPath(path),\n statusCode: res.statusCode,\n durationMs: Date.now() - startedAt,\n startedAt,\n traceId,\n });\n });\n next();\n });\n };\n}\n\nexport function dashboardMiddleware(\n profiler: Profiler,\n): (req: ReqLike, res: ResLikeWithSend, next: NextLike) => void {\n return function perfstackDashboard(req, res) {\n const path = req.originalUrl ?? req.url ?? \"/\";\n if (path.endsWith(\".json\")) {\n res.setHeader(\"content-type\", \"application/json\");\n res.statusCode = 200;\n res.end(JSON.stringify(profiler.report(), null, 2));\n return;\n }\n res.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n res.statusCode = 200;\n res.end(renderDashboard(profiler));\n };\n}\n\ntype ResLikeWithSend = ResLike & {\n statusCode: number;\n end(payload?: string): void;\n};\n\nfunction simplifyPath(p: string): string {\n return p\n .split(\"?\")[0]!\n .replace(/\\/[0-9a-f]{24}\\b/gi, \"/:id\")\n .replace(/\\/\\d+\\b/g, \"/:id\");\n}\n\nfunction renderDashboard(profiler: Profiler): string {\n const report = profiler.report();\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\" />\n<title>perfstack</title>\n<style>\nbody{font:14px/1.4 ui-monospace,Menlo,monospace;background:#0b1020;color:#e6e6e6;margin:0;padding:24px}\nh1,h2{font-weight:600;margin:0 0 12px}h2{margin-top:24px}\ntable{border-collapse:collapse;width:100%;margin-top:8px}\nth,td{padding:6px 10px;text-align:left;border-bottom:1px solid #1d2440}\nth{color:#8aa6ff;font-weight:600}\n.bad{color:#ff8a8a}.good{color:#8aff9c}\n.card{background:#121838;border-radius:8px;padding:16px;margin-bottom:16px}\n.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}\n.k{color:#8a93b3}.v{font-size:20px;font-weight:600}\n</style></head>\n<body>\n<h1>perfstack</h1>\n<div class=\"grid\">\n <div class=\"card\"><div class=\"k\">uptime</div><div class=\"v\">${Math.round(report.uptimeMs / 1000)}s</div></div>\n <div class=\"card\"><div class=\"k\">requests</div><div class=\"v\">${report.totalRequests}</div></div>\n <div class=\"card\"><div class=\"k\">errors</div><div class=\"v ${report.totalErrors ? \"bad\" : \"good\"}\">${report.totalErrors}</div></div>\n <div class=\"card\"><div class=\"k\">queries</div><div class=\"v\">${report.totalQueries}</div></div>\n</div>\n\n<h2>HTTP latency (ms)</h2>\n<table><tr><th>count</th><th>min</th><th>avg</th><th>p50</th><th>p90</th><th>p95</th><th>p99</th><th>max</th></tr>\n<tr><td>${report.http.count}</td><td>${report.http.min}</td><td>${report.http.avg}</td><td>${report.http.p50}</td><td>${report.http.p90}</td><td>${report.http.p95}</td><td>${report.http.p99}</td><td>${report.http.max}</td></tr>\n</table>\n\n<h2>Slowest routes (p95)</h2>\n<table><tr><th>route</th><th>count</th><th>p95</th><th>p99</th><th>max</th><th>errors</th></tr>\n${report.slowestRoutes\n .map((r) => `<tr><td>${escapeHtml(r.method)} ${escapeHtml(r.path)}</td><td>${r.count}</td><td>${r.p95}</td><td>${r.p99}</td><td>${r.max}</td><td>${r.errorCount}</td></tr>`)\n .join(\"\\n\")}\n</table>\n\n<h2>Slowest queries</h2>\n<table><tr><th>collection</th><th>op</th><th>duration</th></tr>\n${report.slowestQueries\n .map((q) => `<tr><td>${escapeHtml(q.collection)}</td><td>${escapeHtml(q.op)}</td><td>${q.durationMs}ms</td></tr>`)\n .join(\"\\n\")}\n</table>\n\n<h2>Memory</h2>\n<table><tr><th>rss</th><th>heap used</th><th>heap total</th><th>peak rss</th><th>peak heap</th></tr>\n<tr>\n <td>${fmtBytes(report.memory.current?.rss ?? 0)}</td>\n <td>${fmtBytes(report.memory.current?.heapUsed ?? 0)}</td>\n <td>${fmtBytes(report.memory.current?.heapTotal ?? 0)}</td>\n <td>${fmtBytes(report.memory.peakRss)}</td>\n <td>${fmtBytes(report.memory.peakHeapUsed)}</td>\n</tr>\n</table>\n\n</body></html>`;\n}\n\nfunction fmtBytes(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;\n return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n","import type { Profiler } from \"./profiler.js\";\n\ntype SchemaLike = {\n pre(hook: string, fn: (this: any) => void): unknown;\n post(hook: string, fn: (this: any, result: unknown) => void): unknown;\n};\n\nconst HOOKS = [\n \"find\",\n \"findOne\",\n \"findOneAndUpdate\",\n \"findOneAndDelete\",\n \"count\",\n \"countDocuments\",\n \"updateOne\",\n \"updateMany\",\n \"deleteOne\",\n \"deleteMany\",\n \"distinct\",\n];\n\nexport function mongoosePlugin(profiler: Profiler) {\n return function perfstackMongoose(schema: SchemaLike) {\n for (const hook of HOOKS) {\n schema.pre(hook, function (this: any) {\n this._perfstackStart = Date.now();\n this._perfstackOp = hook;\n this._perfstackCollection = this.model?.collection?.name ?? this.model?.modelName ?? \"unknown\";\n });\n schema.post(hook, function (this: any) {\n const startedAt = this._perfstackStart ?? Date.now();\n profiler.recordQuery({\n collection: this._perfstackCollection ?? \"unknown\",\n op: this._perfstackOp ?? hook,\n durationMs: Date.now() - startedAt,\n startedAt,\n traceId: profiler.tracer.currentTraceId(),\n });\n });\n }\n schema.pre(\"aggregate\", function (this: any) {\n this._perfstackStart = Date.now();\n const model = typeof this.model === \"function\" ? this.model() : undefined;\n this._perfstackCollection = model?.collection?.name ?? model?.modelName ?? \"unknown\";\n });\n schema.post(\"aggregate\", function (this: any) {\n const startedAt = this._perfstackStart ?? Date.now();\n profiler.recordQuery({\n collection: this._perfstackCollection ?? \"unknown\",\n op: \"aggregate\",\n durationMs: Date.now() - startedAt,\n startedAt,\n traceId: profiler.tracer.currentTraceId(),\n });\n });\n };\n}\n"],"mappings":"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","names":["import_node_crypto"]}
@@ -0,0 +1,190 @@
1
+ interface Span {
2
+ id: string;
3
+ parentId?: string;
4
+ traceId: string;
5
+ name: string;
6
+ kind: "http" | "db" | "external" | "custom";
7
+ startedAt: number;
8
+ durationMs: number;
9
+ metadata: Record<string, unknown>;
10
+ status: "ok" | "error";
11
+ error?: string;
12
+ }
13
+ interface HttpRecord {
14
+ method: string;
15
+ path: string;
16
+ statusCode: number;
17
+ durationMs: number;
18
+ startedAt: number;
19
+ traceId: string;
20
+ }
21
+ interface QueryRecord {
22
+ collection: string;
23
+ op: string;
24
+ durationMs: number;
25
+ traceId?: string;
26
+ startedAt: number;
27
+ }
28
+ interface MemorySnapshot {
29
+ takenAt: number;
30
+ rss: number;
31
+ heapTotal: number;
32
+ heapUsed: number;
33
+ external: number;
34
+ arrayBuffers: number;
35
+ }
36
+ interface HistogramSummary {
37
+ count: number;
38
+ min: number;
39
+ max: number;
40
+ avg: number;
41
+ p50: number;
42
+ p90: number;
43
+ p95: number;
44
+ p99: number;
45
+ }
46
+ interface RouteSummary extends HistogramSummary {
47
+ method: string;
48
+ path: string;
49
+ errorCount: number;
50
+ }
51
+ interface PerfReport {
52
+ uptimeMs: number;
53
+ startedAt: number;
54
+ takenAt: number;
55
+ totalRequests: number;
56
+ totalErrors: number;
57
+ totalQueries: number;
58
+ http: HistogramSummary;
59
+ routes: RouteSummary[];
60
+ slowestRoutes: RouteSummary[];
61
+ queries: HistogramSummary;
62
+ slowestQueries: QueryRecord[];
63
+ memory: {
64
+ current: MemorySnapshot | null;
65
+ samples: MemorySnapshot[];
66
+ peakRss: number;
67
+ peakHeapUsed: number;
68
+ };
69
+ }
70
+ interface PerfstackOptions {
71
+ slowRequestThreshold?: number;
72
+ slowQueryThreshold?: number;
73
+ memorySampleIntervalMs?: number;
74
+ memorySamplesMax?: number;
75
+ routeBucketsMax?: number;
76
+ ignorePaths?: string[];
77
+ enableMemorySampling?: boolean;
78
+ startedAt?: number;
79
+ }
80
+
81
+ declare class Tracer {
82
+ private storage;
83
+ private spans;
84
+ private max;
85
+ constructor(options?: {
86
+ maxSpans?: number;
87
+ });
88
+ start(name: string, kind?: Span["kind"], metadata?: Record<string, unknown>): {
89
+ end: (info?: {
90
+ error?: unknown;
91
+ status?: Span["status"];
92
+ extra?: Record<string, unknown>;
93
+ }) => Span;
94
+ };
95
+ withSpan<T>(name: string, fn: () => Promise<T>, options?: {
96
+ kind?: Span["kind"];
97
+ metadata?: Record<string, unknown>;
98
+ }): Promise<T>;
99
+ runInContext<T>(traceId: string, fn: () => T): T;
100
+ currentTraceId(): string | undefined;
101
+ recent(limit?: number): Span[];
102
+ byTrace(traceId: string): Span[];
103
+ reset(): void;
104
+ private push;
105
+ }
106
+
107
+ declare class Profiler {
108
+ private startedAt;
109
+ private httpHistogram;
110
+ private queryHistogram;
111
+ private routes;
112
+ private slowestQueries;
113
+ private memorySamples;
114
+ private peakRss;
115
+ private peakHeapUsed;
116
+ private totalRequests;
117
+ private totalErrors;
118
+ private totalQueries;
119
+ private memoryInterval?;
120
+ readonly tracer: Tracer;
121
+ private opts;
122
+ constructor(options?: PerfstackOptions);
123
+ recordHttp(record: HttpRecord): void;
124
+ recordQuery(record: QueryRecord): void;
125
+ takeMemorySample(): MemorySnapshot;
126
+ startMemorySampling(): void;
127
+ stopMemorySampling(): void;
128
+ report(): PerfReport;
129
+ reset(): void;
130
+ isSlowRequest(durationMs: number): boolean;
131
+ }
132
+
133
+ declare class Histogram {
134
+ private samples;
135
+ private max;
136
+ constructor(max?: number);
137
+ record(value: number): void;
138
+ summary(): HistogramSummary;
139
+ reset(): void;
140
+ }
141
+ declare function percentile(sortedAscending: number[], p: number): number;
142
+
143
+ type ReqLike = {
144
+ method?: string;
145
+ originalUrl?: string;
146
+ url?: string;
147
+ route?: {
148
+ path?: string;
149
+ };
150
+ headers: Record<string, string | string[] | undefined>;
151
+ };
152
+ type ResLike = {
153
+ statusCode: number;
154
+ on(event: "finish", cb: () => void): void;
155
+ setHeader(name: string, value: string): void;
156
+ };
157
+ type NextLike = () => void;
158
+ interface ExpressMiddlewareOptions {
159
+ ignorePaths?: string[];
160
+ traceHeader?: string;
161
+ exposeTraceHeader?: boolean;
162
+ }
163
+ declare function expressMiddleware(profiler: Profiler, options?: ExpressMiddlewareOptions): (req: ReqLike, res: ResLike, next: NextLike) => void;
164
+ declare function dashboardMiddleware(profiler: Profiler): (req: ReqLike, res: ResLikeWithSend, next: NextLike) => void;
165
+ type ResLikeWithSend = ResLike & {
166
+ statusCode: number;
167
+ end(payload?: string): void;
168
+ };
169
+
170
+ type SchemaLike = {
171
+ pre(hook: string, fn: (this: any) => void): unknown;
172
+ post(hook: string, fn: (this: any, result: unknown) => void): unknown;
173
+ };
174
+ declare function mongoosePlugin(profiler: Profiler): (schema: SchemaLike) => void;
175
+
176
+ type AnyApp = {
177
+ use: (...args: unknown[]) => unknown;
178
+ get?: (path: string, handler: (...args: unknown[]) => unknown) => unknown;
179
+ };
180
+ interface InitResult {
181
+ profiler: Profiler;
182
+ middleware: ReturnType<typeof expressMiddleware>;
183
+ dashboard: ReturnType<typeof dashboardMiddleware>;
184
+ mongoosePlugin: ReturnType<typeof mongoosePlugin>;
185
+ }
186
+ declare function init(app?: AnyApp, options?: ConstructorParameters<typeof Profiler>[0] & {
187
+ dashboardPath?: string;
188
+ }): InitResult;
189
+
190
+ export { Histogram, type HistogramSummary, type HttpRecord, type InitResult, type MemorySnapshot, type PerfReport, type PerfstackOptions, Profiler, type QueryRecord, type RouteSummary, type Span, Tracer, dashboardMiddleware, expressMiddleware, init, mongoosePlugin, percentile };
@@ -0,0 +1,190 @@
1
+ interface Span {
2
+ id: string;
3
+ parentId?: string;
4
+ traceId: string;
5
+ name: string;
6
+ kind: "http" | "db" | "external" | "custom";
7
+ startedAt: number;
8
+ durationMs: number;
9
+ metadata: Record<string, unknown>;
10
+ status: "ok" | "error";
11
+ error?: string;
12
+ }
13
+ interface HttpRecord {
14
+ method: string;
15
+ path: string;
16
+ statusCode: number;
17
+ durationMs: number;
18
+ startedAt: number;
19
+ traceId: string;
20
+ }
21
+ interface QueryRecord {
22
+ collection: string;
23
+ op: string;
24
+ durationMs: number;
25
+ traceId?: string;
26
+ startedAt: number;
27
+ }
28
+ interface MemorySnapshot {
29
+ takenAt: number;
30
+ rss: number;
31
+ heapTotal: number;
32
+ heapUsed: number;
33
+ external: number;
34
+ arrayBuffers: number;
35
+ }
36
+ interface HistogramSummary {
37
+ count: number;
38
+ min: number;
39
+ max: number;
40
+ avg: number;
41
+ p50: number;
42
+ p90: number;
43
+ p95: number;
44
+ p99: number;
45
+ }
46
+ interface RouteSummary extends HistogramSummary {
47
+ method: string;
48
+ path: string;
49
+ errorCount: number;
50
+ }
51
+ interface PerfReport {
52
+ uptimeMs: number;
53
+ startedAt: number;
54
+ takenAt: number;
55
+ totalRequests: number;
56
+ totalErrors: number;
57
+ totalQueries: number;
58
+ http: HistogramSummary;
59
+ routes: RouteSummary[];
60
+ slowestRoutes: RouteSummary[];
61
+ queries: HistogramSummary;
62
+ slowestQueries: QueryRecord[];
63
+ memory: {
64
+ current: MemorySnapshot | null;
65
+ samples: MemorySnapshot[];
66
+ peakRss: number;
67
+ peakHeapUsed: number;
68
+ };
69
+ }
70
+ interface PerfstackOptions {
71
+ slowRequestThreshold?: number;
72
+ slowQueryThreshold?: number;
73
+ memorySampleIntervalMs?: number;
74
+ memorySamplesMax?: number;
75
+ routeBucketsMax?: number;
76
+ ignorePaths?: string[];
77
+ enableMemorySampling?: boolean;
78
+ startedAt?: number;
79
+ }
80
+
81
+ declare class Tracer {
82
+ private storage;
83
+ private spans;
84
+ private max;
85
+ constructor(options?: {
86
+ maxSpans?: number;
87
+ });
88
+ start(name: string, kind?: Span["kind"], metadata?: Record<string, unknown>): {
89
+ end: (info?: {
90
+ error?: unknown;
91
+ status?: Span["status"];
92
+ extra?: Record<string, unknown>;
93
+ }) => Span;
94
+ };
95
+ withSpan<T>(name: string, fn: () => Promise<T>, options?: {
96
+ kind?: Span["kind"];
97
+ metadata?: Record<string, unknown>;
98
+ }): Promise<T>;
99
+ runInContext<T>(traceId: string, fn: () => T): T;
100
+ currentTraceId(): string | undefined;
101
+ recent(limit?: number): Span[];
102
+ byTrace(traceId: string): Span[];
103
+ reset(): void;
104
+ private push;
105
+ }
106
+
107
+ declare class Profiler {
108
+ private startedAt;
109
+ private httpHistogram;
110
+ private queryHistogram;
111
+ private routes;
112
+ private slowestQueries;
113
+ private memorySamples;
114
+ private peakRss;
115
+ private peakHeapUsed;
116
+ private totalRequests;
117
+ private totalErrors;
118
+ private totalQueries;
119
+ private memoryInterval?;
120
+ readonly tracer: Tracer;
121
+ private opts;
122
+ constructor(options?: PerfstackOptions);
123
+ recordHttp(record: HttpRecord): void;
124
+ recordQuery(record: QueryRecord): void;
125
+ takeMemorySample(): MemorySnapshot;
126
+ startMemorySampling(): void;
127
+ stopMemorySampling(): void;
128
+ report(): PerfReport;
129
+ reset(): void;
130
+ isSlowRequest(durationMs: number): boolean;
131
+ }
132
+
133
+ declare class Histogram {
134
+ private samples;
135
+ private max;
136
+ constructor(max?: number);
137
+ record(value: number): void;
138
+ summary(): HistogramSummary;
139
+ reset(): void;
140
+ }
141
+ declare function percentile(sortedAscending: number[], p: number): number;
142
+
143
+ type ReqLike = {
144
+ method?: string;
145
+ originalUrl?: string;
146
+ url?: string;
147
+ route?: {
148
+ path?: string;
149
+ };
150
+ headers: Record<string, string | string[] | undefined>;
151
+ };
152
+ type ResLike = {
153
+ statusCode: number;
154
+ on(event: "finish", cb: () => void): void;
155
+ setHeader(name: string, value: string): void;
156
+ };
157
+ type NextLike = () => void;
158
+ interface ExpressMiddlewareOptions {
159
+ ignorePaths?: string[];
160
+ traceHeader?: string;
161
+ exposeTraceHeader?: boolean;
162
+ }
163
+ declare function expressMiddleware(profiler: Profiler, options?: ExpressMiddlewareOptions): (req: ReqLike, res: ResLike, next: NextLike) => void;
164
+ declare function dashboardMiddleware(profiler: Profiler): (req: ReqLike, res: ResLikeWithSend, next: NextLike) => void;
165
+ type ResLikeWithSend = ResLike & {
166
+ statusCode: number;
167
+ end(payload?: string): void;
168
+ };
169
+
170
+ type SchemaLike = {
171
+ pre(hook: string, fn: (this: any) => void): unknown;
172
+ post(hook: string, fn: (this: any, result: unknown) => void): unknown;
173
+ };
174
+ declare function mongoosePlugin(profiler: Profiler): (schema: SchemaLike) => void;
175
+
176
+ type AnyApp = {
177
+ use: (...args: unknown[]) => unknown;
178
+ get?: (path: string, handler: (...args: unknown[]) => unknown) => unknown;
179
+ };
180
+ interface InitResult {
181
+ profiler: Profiler;
182
+ middleware: ReturnType<typeof expressMiddleware>;
183
+ dashboard: ReturnType<typeof dashboardMiddleware>;
184
+ mongoosePlugin: ReturnType<typeof mongoosePlugin>;
185
+ }
186
+ declare function init(app?: AnyApp, options?: ConstructorParameters<typeof Profiler>[0] & {
187
+ dashboardPath?: string;
188
+ }): InitResult;
189
+
190
+ export { Histogram, type HistogramSummary, type HttpRecord, type InitResult, type MemorySnapshot, type PerfReport, type PerfstackOptions, Profiler, type QueryRecord, type RouteSummary, type Span, Tracer, dashboardMiddleware, expressMiddleware, init, mongoosePlugin, percentile };