127 lines
5.2 KiB
JavaScript
127 lines
5.2 KiB
JavaScript
import { impactMagnitude } from './impact-magnitude.mjs';
|
|
|
|
const SLOW_ROUTE_P95_THRESHOLD_MS = 500;
|
|
const CACHE_HIT_THRESHOLD_PCT = 50;
|
|
const COLD_START_THRESHOLD_PCT = 40;
|
|
const RELIABILITY_TARGET_PCT = 0.1;
|
|
|
|
export function computeImpactLabel(rec, signals = {}) {
|
|
const il = rec?.impactLabel ?? {};
|
|
if (il.performance) return il.performance;
|
|
if (il.costPhrase) return il.costPhrase;
|
|
|
|
if (typeof rec?.estimatedSavingsUsd === 'number' && rec.impactTier) {
|
|
const magnitude = impactMagnitude({
|
|
currentCost: rec.estimatedSavingsUsd,
|
|
impactTier: rec.impactTier,
|
|
});
|
|
return magnitude.phrase;
|
|
}
|
|
|
|
return synthesizeImpactFromSignal(rec, signals);
|
|
}
|
|
|
|
export function synthesizeImpactFromSignal(rec, signals = {}) {
|
|
const tier = rec?.impactTier;
|
|
const sig = [
|
|
rec?.o11ySignal,
|
|
rec?.evidence?.o11ySignal,
|
|
rec?.why,
|
|
rec?.what,
|
|
].filter((v) => typeof v === 'string' && v.trim()).join('\n') || null;
|
|
if (!sig || !tier) return null;
|
|
const m = String(sig);
|
|
const inv = parseSigNumber(m, /inv=([\d,]+)/);
|
|
const p95 = parseSigNumber(m, /p95=([\d,]+)ms/);
|
|
const cachePct = parseSigNumber(m, /cache=([\d.]+)%/);
|
|
const coldPct = parseSigNumber(m, /cold=([\d.]+)%/);
|
|
const buildSharePct = parseSigNumber(m, /build_minutes_share=([\d.]+)%/i) ??
|
|
parseSigNumber(m, /build(?: CPU)? minutes share:?\s*([\d.]+)%/i);
|
|
const errors = parseSigNumber(m, /errs=([\d,]+)/);
|
|
const errorRatePct = parseSigNumber(m, /rate=([\d.]+)%/);
|
|
const writes = parseSigNumber(m, /writes=([\d,]+)/);
|
|
const reads = parseSigNumber(m, /reads=([\d,]+)/);
|
|
const cwvIssues = [
|
|
cwvIssue('LCP', parseSigNumber(m, /LCP=([\d.]+)ms/i), 2500, 'ms'),
|
|
cwvIssue('INP', parseSigNumber(m, /INP=([\d.]+)ms/i), 200, 'ms'),
|
|
cwvIssue('CLS', parseSigNumber(m, /CLS=([\d.]+)/i), 0.1, ''),
|
|
].filter(Boolean);
|
|
|
|
if (cwvIssues.length > 0) {
|
|
return `${tier} impact — bring ${joinEnglish(cwvIssues.map(formatCwvIssue))}.`;
|
|
}
|
|
if (errors != null && errorRatePct != null) {
|
|
const reductionPct = Math.ceil(Math.max(0, (1 - (RELIABILITY_TARGET_PCT / errorRatePct)) * 100));
|
|
return `${tier} impact — cut 5xx rate by ~${reductionPct}% to get below ${RELIABILITY_TARGET_PCT}% (current ${errorRatePct}%, ${formatInteger(errors)} errors in this window).`;
|
|
}
|
|
if (errors != null) {
|
|
return `${tier} impact — resolve ${formatInteger(errors)} billed 5xx errors in this window.`;
|
|
}
|
|
if (cachePct != null && inv != null) {
|
|
if (cachePct < CACHE_HIT_THRESHOLD_PCT) {
|
|
return `${tier} impact — current cache hit rate is ${cachePct}% across ${formatInteger(inv)} requests in this window; the gate fires below ${CACHE_HIT_THRESHOLD_PCT}%.`;
|
|
}
|
|
if (p95 == null) {
|
|
return `${tier} impact — current cache hit rate is ${cachePct}% across ${formatInteger(inv)} requests in this window; this recommendation targets the remaining uncached traffic.`;
|
|
}
|
|
}
|
|
if (writes != null && reads != null) {
|
|
const ratio = reads > 0 ? writes / reads : null;
|
|
return ratio == null
|
|
? `${tier} impact — ${formatInteger(writes)} ISR write units with no recorded read units in this window.`
|
|
: `${tier} impact — ${formatInteger(writes)} ISR write units vs ${formatInteger(reads)} read units in this window (${round2(ratio)} writes per read).`;
|
|
}
|
|
if (p95 != null && inv != null) {
|
|
const multiple = p95 / SLOW_ROUTE_P95_THRESHOLD_MS;
|
|
return `${tier} impact — current 95th percentile duration is ${formatInteger(p95)}ms across ${formatInteger(inv)} function invocations in this window (${round1(multiple)}x the ${formatInteger(SLOW_ROUTE_P95_THRESHOLD_MS)}ms slow-route threshold).`;
|
|
}
|
|
if (coldPct != null) {
|
|
return `${tier} impact — current cold-start share is ${coldPct}%; the gate fires above ${COLD_START_THRESHOLD_PCT}%.`;
|
|
}
|
|
if (rec?.candidateRef?.startsWith('build_minutes_fanout:') || rec?.kind === 'build_minutes_fanout') {
|
|
return buildSharePct != null
|
|
? `${tier} impact — Build CPU Minutes account for ${buildSharePct}% of observed billed cost in this window.`
|
|
: `${tier} impact — Build CPU Minutes exceeded the gate threshold in this window.`;
|
|
}
|
|
return `${tier} impact — see follow-up metrics for magnitude.`;
|
|
}
|
|
|
|
function cwvIssue(metric, value, threshold, unit) {
|
|
if (value == null || value <= threshold) return null;
|
|
return { metric, value, threshold, unit };
|
|
}
|
|
|
|
function formatCwvIssue(i) {
|
|
const current = i.metric === 'CLS' ? round2(i.value) : Math.round(i.value);
|
|
if (i.unit === 'ms') {
|
|
return `${i.metric} below ${formatInteger(i.threshold)}ms (current ${formatInteger(current)}ms)`;
|
|
}
|
|
return `${i.metric} below ${i.threshold}${i.unit} (current ${current}${i.unit})`;
|
|
}
|
|
|
|
function joinEnglish(parts) {
|
|
if (parts.length <= 1) return parts[0] ?? '';
|
|
if (parts.length === 2) return `${parts[0]} and ${parts[1]}`;
|
|
return `${parts.slice(0, -1).join(', ')}, and ${parts.at(-1)}`;
|
|
}
|
|
|
|
function parseSigNumber(s, re) {
|
|
const m = re.exec(s);
|
|
if (!m) return null;
|
|
const n = Number(String(m[1]).replace(/,/g, ''));
|
|
return Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
function round1(n) {
|
|
return Math.round(n * 10) / 10;
|
|
}
|
|
|
|
function round2(n) {
|
|
return Math.round(n * 100) / 100;
|
|
}
|
|
|
|
function formatInteger(n) {
|
|
if (!Number.isFinite(n)) return String(n);
|
|
return Math.round(n).toLocaleString('en-US');
|
|
}
|