mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
110 lines
2.7 KiB
TypeScript
110 lines
2.7 KiB
TypeScript
import { getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
|
|
|
export type RequestStat = {
|
|
method: string,
|
|
path: string,
|
|
count: number,
|
|
totalTimeMs: number,
|
|
minTimeMs: number,
|
|
maxTimeMs: number,
|
|
lastCalledAt: number,
|
|
};
|
|
|
|
// In-memory storage for request stats (only used in development)
|
|
// Use globalThis to persist across hot reloads
|
|
const requestStatsMap: Map<string, RequestStat> = (globalThis as any).__devRequestStatsMap ??= new Map<string, RequestStat>();
|
|
|
|
function getKey(method: string, path: string): string {
|
|
return `${method}:${path}`;
|
|
}
|
|
|
|
/**
|
|
* Record stats for a completed request.
|
|
* Only records in development mode.
|
|
*/
|
|
export function recordRequestStats(method: string, path: string, durationMs: number): void {
|
|
if (getNodeEnvironment() !== "development") {
|
|
return;
|
|
}
|
|
|
|
const key = getKey(method, path);
|
|
|
|
const existing = requestStatsMap.get(key);
|
|
if (existing) {
|
|
existing.count++;
|
|
existing.totalTimeMs += durationMs;
|
|
existing.minTimeMs = Math.min(existing.minTimeMs, durationMs);
|
|
existing.maxTimeMs = Math.max(existing.maxTimeMs, durationMs);
|
|
existing.lastCalledAt = Date.now();
|
|
} else {
|
|
requestStatsMap.set(key, {
|
|
method,
|
|
path,
|
|
count: 1,
|
|
totalTimeMs: durationMs,
|
|
minTimeMs: durationMs,
|
|
maxTimeMs: durationMs,
|
|
lastCalledAt: Date.now(),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all request stats
|
|
*/
|
|
export function getAllRequestStats(): RequestStat[] {
|
|
return Array.from(requestStatsMap.values());
|
|
}
|
|
|
|
/**
|
|
* Get the most common requests by count
|
|
*/
|
|
export function getMostCommonRequests(limit: number = 20): RequestStat[] {
|
|
return getAllRequestStats()
|
|
.sort((a, b) => b.count - a.count)
|
|
.slice(0, limit);
|
|
}
|
|
|
|
/**
|
|
* Get requests sorted by total time spent (most time first)
|
|
*/
|
|
export function getMostTimeConsumingRequests(limit: number = 20): RequestStat[] {
|
|
return getAllRequestStats()
|
|
.sort((a, b) => b.totalTimeMs - a.totalTimeMs)
|
|
.slice(0, limit);
|
|
}
|
|
|
|
/**
|
|
* Get requests sorted by average time (slowest first)
|
|
*/
|
|
export function getSlowestRequests(limit: number = 20): RequestStat[] {
|
|
return getAllRequestStats()
|
|
.sort((a, b) => (b.totalTimeMs / b.count) - (a.totalTimeMs / a.count))
|
|
.slice(0, limit);
|
|
}
|
|
|
|
/**
|
|
* Get aggregate stats
|
|
*/
|
|
export function getAggregateStats() {
|
|
const stats = getAllRequestStats();
|
|
const totalRequests = stats.reduce((sum, s) => sum + s.count, 0);
|
|
const totalTimeMs = stats.reduce((sum, s) => sum + s.totalTimeMs, 0);
|
|
const uniqueEndpoints = stats.length;
|
|
|
|
return {
|
|
totalRequests,
|
|
totalTimeMs,
|
|
uniqueEndpoints,
|
|
averageTimeMs: totalRequests > 0 ? totalTimeMs / totalRequests : 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clear all stats
|
|
*/
|
|
export function clearRequestStats(): void {
|
|
requestStatsMap.clear();
|
|
}
|
|
|