autotel
Version:
Write Once, Observe Anywhere
87 lines • 3.74 kB
TypeScript
//#region src/analysis.d.ts
/**
* Core Analysis Loop
*
* Debugging from first principles: take the events you care about, take a
* comparable normal population, and ask which recorded field separates them.
* The loop is mechanical, so run it in code instead of clicking through a
* dashboard.
*
* `compareCohorts()` scores every field/value pair by how much more often it
* appears in the outlier group than in the baseline. The top result names the
* cohort behind a regression, which is the hypothesis you then verify against
* individual traces.
*
* The input is any array of flat records: wide events from
* `getRequestLogger()`, `TestSpan.attributes` from `autotel/testing`, or rows
* returned by a backend query.
*
* @example Find the cohort behind a latency regression
* ```typescript
* import { compareCohorts } from 'autotel/analysis'
*
* const slow = events.filter((event) => event['duration_ms'] >= 800)
* const normal = events.filter((event) => event['duration_ms'] < 800)
*
* const [top] = compareCohorts({ outlier: slow, baseline: normal })
* // { field: 'payment.provider', value: 'bank-beta', difference: 0.94, ... }
* ```
*/
/** A flat telemetry record. Span attributes and wide events both satisfy it. */
type AnalysisEvent = Record<string, unknown>;
/** How strongly one field/value pair separates the outlier group from the baseline. */
interface CohortDifference {
/** Attribute name, such as `payment.provider`. */
field: string;
/** The value, rendered as a string for display and grouping. */
value: string;
/** Share of outlier events carrying this value, 0-1. */
outlierFraction: number;
/** Share of baseline events carrying this value, 0-1. */
baselineFraction: number;
/** `outlierFraction - baselineFraction`. Positive means over-represented in the outliers. */
difference: number;
outlierCount: number;
baselineCount: number;
}
interface CompareCohortsOptions {
/** The events you are investigating: the errors, the slow requests, the failed checkouts. */
outlier: readonly AnalysisEvent[];
/** A comparable normal population from the same time range. */
baseline: readonly AnalysisEvent[];
/** Restrict the scan to these fields. Defaults to every field seen in either group. */
fields?: readonly string[];
/** Fields to skip, such as identifiers you already know are unique. */
ignoreFields?: readonly string[];
/**
* Skip a field once it holds more distinct values than this across both
* groups. Default 50.
*/
maxValuesPerField?: number;
/**
* Skip a field whose distinct values outnumber this share of the combined
* events. A request id or a raw duration takes a near-unique value per
* event, so its values never repeat and it cannot describe a cohort. The
* ratio catches those fields in small populations, where an absolute cap
* does not. Bucket numeric fields at instrumentation time if you want them
* ranked. Default 0.5.
*/
maxUniqueRatio?: number;
/** Drop pairs whose absolute difference falls below this. Default 0.1. */
minDifference?: number;
/** Maximum results to return, strongest first. Default 20. */
limit?: number;
}
/**
* Rank the field/value pairs that separate an outlier group from a baseline.
*
* Returns an empty array when either group is empty, because a fraction over
* zero events carries no information.
*
* @param options - The two populations and the scan limits
* @returns Differences sorted by absolute strength, strongest first
*/
declare function compareCohorts(options: CompareCohortsOptions): CohortDifference[];
//#endregion
export { AnalysisEvent, CohortDifference, CompareCohortsOptions, compareCohorts };
//# sourceMappingURL=analysis.d.ts.map