Lab
Prototype
type Condition = { metric: string; above: number };
type Labels = Record<string, string>;
// The team's on-call channel. The team owns it and renames it when the rota
// moves; rules point at it. The checker renames it in some cases.
type Channel = { name: string };
// How the checker prints labels: checker plumbing, you don't need to edit this.
const showLabels = (labels: Labels) =>
Object.keys(labels).sort().map((key) => `${key}=${labels[key]}`).join(',');
// Pages the channel while every condition's metric is above its threshold.
class AlertRule {
id: string;
template: string;
conditions: Condition[];
labels: Labels;
channel: Channel;
firing: boolean;
constructor(id: string, template: string, conditions: Condition[], labels: Labels, channel: Channel) {
this.id = id;
this.template = template;
this.conditions = conditions;
this.labels = labels;
this.channel = channel;
this.firing = false;
}
tune(index: number, above: number): void {
this.conditions[index]!.above = above;
}
label(key: string, value: string): void {
this.labels[key] = value;
}
fire(): void {
this.firing = true;
}
// Duplicate, for now: a fresh rule from the template this one started as.
duplicate(newId: string): AlertRule {
return fromTemplate(this.template, newId, this.channel);
}
// How the checker prints a rule: you don't need to edit this.
describe(): string {
const conditions = this.conditions.map((c) => `${c.metric}>${c.above}`).join(',');
const state = this.firing ? 'firing' : 'ok';
return `${this.id}: ${conditions} [${showLabels(this.labels)}] → ${this.channel.name} ${state}`;
}
}
// The templates new rules start from.
const templates: Record<string, { conditions: Condition[]; labels: Labels }> = {
latency: { conditions: [{ metric: 'p99_ms', above: 800 }], labels: { severity: 'page' } },
errors: {
conditions: [{ metric: 'error_rate', above: 0.05 }, { metric: 'requests', above: 100 }],
labels: { severity: 'ticket' },
},
};
// A new rule from a template.
function fromTemplate(template: string, id: string, channel: Channel): AlertRule {
const { conditions, labels } = templates[template]!;
const own = conditions.map((condition) => ({ ...condition }));
return new AlertRule(id, template, own, { ...labels }, channel);
}