import { type Entry } from '../entry/entry.js';
export interface SlowEntry {
    id: string;
    type: string;
    durationMs: number;
    label: string;
    batchId: string;
}
export interface ExceptionGroup {
    familyHash: string;
    class: string;
    message: string;
    count: number;
    lastSeen: string;
}
export interface NPlusOneHotspot {
    familyHash: string;
    sql: string;
    /** Worst (max) repetition count of this family within a single request/batch. */
    perRequest: number;
    /** Number of distinct requests/batches where this family tripped the threshold. */
    requests: number;
    /** Sum of repetition counts across those requests. */
    total: number;
    /**
     * Total duration (ms) spent across ALL occurrences of this family in the
     * window — the time "wasted" in the loop. Lets the dashboard weight an N+1 by
     * cost, not just repetition count (a 200×1ms loop vs a 6×80ms loop). Derived
     * from the content-less `durationMs` column, so it needs no hydration.
     */
    totalDurationMs: number;
    /** One batch id (the worst) to deep-link to. */
    sampleBatchId: string;
}
/**
 * A consistently-slow endpoint, aggregated by route family. The `route` IS the
 * normalized `familyHash` (e.g. "GET /api/base/:id/mel"), so it doubles as the
 * label — fully derived from content-less columns, no hydration.
 */
export interface SlowRouteHotspot {
    /** The normalized route family — equals the request entry's `familyHash`. */
    route: string;
    count: number;
    p99: number;
    p50: number;
}
/**
 * A user's share of the load in the window — request count and the total time
 * spent serving them. The `user` is the id from the request's `user:<id>` tag.
 * Modelled on Laravel Pulse's "Usage" card.
 */
export interface UserLoad {
    user: string;
    count: number;
    totalDurationMs: number;
}
export interface PulseSummary {
    windowStart: string;
    windowEnd: string;
    windowMs: number;
    counts: Record<string, number>;
    slowest: SlowEntry[];
    topExceptions: ExceptionGroup[];
    nPlusOne: NPlusOneHotspot[];
    slowRoutes: SlowRouteHotspot[];
    slowOutgoing: SlowRouteHotspot[];
    /** Slowest job families (by p99), the queue analogue of `slowRoutes`. */
    slowJobs: SlowRouteHotspot[];
    /** Top users by total request time in the window. */
    loadByUser: UserLoad[];
}
export interface PulseOptions {
    topN: number;
    nPlusOneThreshold: number;
    /** Minimum request count for a route to qualify as a slow-route hotspot. */
    slowRouteMinCount: number;
    /**
     * Minimum p99 (ms) for a route family to count as a slow-route hotspot. A
     * route only surfaces here when its p99 is **>= slowRouteMs** — a hotspot is a
     * route that is *actually slow*, not merely the slowest of an otherwise-healthy
     * set. Without this gate, "Slow request hotspots" is a pure top-N p99 ranking,
     * so on a quiet host it surfaces e.g. `/health` at 18ms and reads as a false
     * alarm. The default (1000) matches the `slow` request tag threshold
     * (`SLOW_THRESHOLD_MS` in tagging/tagger.ts) and the HttpClientWatcher's
     * `slowMs` default, so "hotspot" means the same thing here as the `slow` tag
     * does everywhere else. Applies to both incoming slow-route and outgoing
     * slow-HTTP hotspots (both are p99 route rankings).
     */
    slowRouteMs: number;
}
/**
 * The exact set of entry ids whose `content` the final pulse output displays.
 * Everything else aggregates over content-less columns, so a caller can run a
 * content-less primary scan and then hydrate only THESE ids:
 *  - `slowest`: the top-N slowest entries (labels come from content).
 *  - `exceptions`: one representative per reported exception family (class/message).
 *  - `nPlusOne`: one representative query entry per reported N+1 family (sql).
 */
export interface PulseHydrationIds {
    slowest: string[];
    exceptions: string[];
    nPlusOne: string[];
}
/** A content lookup for a previously-identified id; returns the hydrated content
 *  or undefined when the entry could not be re-read (e.g. since pruned). */
export type HydrateContent = (id: string) => unknown;
interface SlowCandidate {
    id: string;
    type: string;
    durationMs: number;
    batchId: string;
}
interface ExceptionAccumulator {
    /** A representative entry id to hydrate class/message from. */
    representativeId: string;
    count: number;
    lastSeen: Date;
}
interface ExceptionGroupAggregate extends ExceptionAccumulator {
    familyHash: string;
}
interface NPlusOneAccumulator {
    familyHash: string;
    perRequest: number;
    requests: number;
    total: number;
    totalDurationMs: number;
    sampleBatchId: string;
    /** A representative query entry id to hydrate the sql from. */
    representativeId: string;
}
/**
 * What `summarizePulse` derives from the content-less columns alone, BEFORE any
 * content hydration: counts, the ranked slowest candidates, exception groups
 * (without class/message), and N+1 hotspots (without sql). The pulse service
 * hydrates the ids in {@link hydrationIds} and calls {@link finalizePulse}.
 */
export interface PulseAggregates {
    windowStart: Date;
    windowEnd: Date;
    options: PulseOptions;
    counts: Record<string, number>;
    slowest: SlowCandidate[];
    exceptions: ExceptionGroupAggregate[];
    nPlusOne: NPlusOneAccumulator[];
    /**
     * Slow-route hotspots, already final: the route IS the familyHash and the
     * stats come from content-less columns, so no hydration is required.
     */
    slowRoutes: SlowRouteHotspot[];
    /**
     * Slow outgoing-HTTP hotspots, already final: the `route` IS the http_client
     * familyHash (method + host + normalized path) and the stats come from
     * content-less columns, so no hydration is required.
     */
    slowOutgoing: SlowRouteHotspot[];
    /** Slowest job families, already final (familyHash is the label). */
    slowJobs: SlowRouteHotspot[];
    /** Top users by request time, already final (no hydration needed). */
    loadByUser: UserLoad[];
    hydrationIds: PulseHydrationIds;
}
/**
 * Pass 1: aggregate the windowed entries over their content-less columns only.
 * Reads `type`, `durationMs`, `familyHash`, `batchId`, `createdAt`, `sequence`
 * — never `content`. Produces the ranked/sliced aggregates plus the exact ids
 * whose content the final output needs.
 */
export declare function aggregatePulse(entries: Entry[], windowStart: Date, windowEnd: Date, options: PulseOptions): PulseAggregates;
/**
 * Pass 2: build the final {@link PulseSummary}, reading content for ONLY the few
 * displayed rows via the `hydrate` lookup. `hydrate(id)` returns the entry's
 * content (or undefined if it could not be re-read).
 */
export declare function finalizePulse(aggregates: PulseAggregates, hydrate: HydrateContent): PulseSummary;
/**
 * Summarize stored entries into a health snapshot: per-type counts, slowest
 * entries, top exceptions, and N+1 hotspots aggregated by query family. Pure:
 * callers fetch the windowed entries (createdAt is not re-checked here).
 *
 * When the entries carry their `content` (the in-process / single-pass path),
 * labels/class/message/sql resolve directly from each entry. The two-pass
 * content-less path uses {@link aggregatePulse} + {@link finalizePulse} instead.
 */
export declare function summarizePulse(entries: Entry[], windowStart: Date, windowEnd: Date, options: PulseOptions): PulseSummary;
export {};
//# sourceMappingURL=pulse-summary.d.ts.map