Metric Correlations
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import test from 'brittle'
|
||||
import {
|
||||
volumeScore,
|
||||
ks2Score,
|
||||
ksStatistic,
|
||||
ksPValue,
|
||||
resolveWindows,
|
||||
spreadEvenly,
|
||||
normalizeWeightsMethod,
|
||||
computeWeights,
|
||||
} from '../server/services/weights.js'
|
||||
import { getStore } from '../server/services/store.js'
|
||||
|
||||
test('normalizeWeightsMethod aliases', (t) => {
|
||||
t.is(normalizeWeightsMethod('KS2'), 'ks2')
|
||||
t.is(normalizeWeightsMethod('vol'), 'volume')
|
||||
t.is(normalizeWeightsMethod('anomaly'), 'anomaly-rate')
|
||||
t.is(normalizeWeightsMethod('cv'), 'value')
|
||||
t.is(normalizeWeightsMethod(''), '')
|
||||
})
|
||||
|
||||
test('resolveWindows defaults baseline to 4x highlight', (t) => {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const w = resolveWindows({ after: now - 60, before: now }, 'volume')
|
||||
t.absent(w.error)
|
||||
t.is(w.highlightBefore - w.highlightAfter, 60)
|
||||
t.is(w.baselineBefore, w.highlightAfter)
|
||||
t.is(w.baselineBefore - w.baselineAfter, 60 * 4)
|
||||
t.ok(w.shifts >= 2)
|
||||
})
|
||||
|
||||
test('resolveWindows rejects short highlight', (t) => {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const w = resolveWindows({ after: now - 10, before: now }, 'ks2')
|
||||
t.ok(w.error)
|
||||
})
|
||||
|
||||
test('volumeScore is low for identical windows', (t) => {
|
||||
const series = Array.from({ length: 40 }, () => 10)
|
||||
t.ok(volumeScore(series, series) < 0.01)
|
||||
})
|
||||
|
||||
test('volumeScore rises when highlight spikes', (t) => {
|
||||
const baseline = Array.from({ length: 40 }, () => 10)
|
||||
const highlight = Array.from({ length: 40 }, (_, i) => (i > 20 ? 40 : 10))
|
||||
t.ok(volumeScore(baseline, highlight) > volumeScore(baseline, baseline))
|
||||
})
|
||||
|
||||
test('ksStatistic is near zero for same distribution', (t) => {
|
||||
const a = Array.from({ length: 50 }, (_, i) => i)
|
||||
const b = Array.from({ length: 50 }, (_, i) => i)
|
||||
t.ok(ksStatistic(a, b) < 0.05)
|
||||
})
|
||||
|
||||
test('ks2Score higher for shifted diffs', (t) => {
|
||||
const steady = Array.from({ length: 40 }, (_, i) => i * 0.1)
|
||||
const spiked = Array.from({ length: 40 }, (_, i) => (i > 25 ? i * 2 : i * 0.1))
|
||||
const same = ks2Score(steady, steady, 2)
|
||||
const diff = ks2Score(steady, spiked, 2)
|
||||
t.ok(diff >= same)
|
||||
})
|
||||
|
||||
test('ksPValue bounds', (t) => {
|
||||
t.ok(ksPValue(50, 0.01) > 0.5)
|
||||
t.ok(ksPValue(50, 0.5) < 0.1)
|
||||
})
|
||||
|
||||
test('spreadEvenly ranks higher raw as higher weight', (t) => {
|
||||
const rows = [
|
||||
{ id: 'a', weight: 0.1 },
|
||||
{ id: 'b', weight: 5 },
|
||||
{ id: 'c', weight: 2 },
|
||||
]
|
||||
spreadEvenly(rows)
|
||||
const byId = Object.fromEntries(rows.map((r) => [r.id, r.weight]))
|
||||
t.ok(byId.b > byId.c)
|
||||
t.ok(byId.c > byId.a)
|
||||
t.ok(byId.b <= 1 && byId.a > 0)
|
||||
})
|
||||
|
||||
test('computeWeights without window returns alerts method', async (t) => {
|
||||
const res = await computeWeights({ limit: 10 })
|
||||
t.is(res.method, 'alerts')
|
||||
t.ok(Array.isArray(res.results))
|
||||
})
|
||||
|
||||
test('computeWeights volume scores store spike', async (t) => {
|
||||
const store = getStore()
|
||||
const now = Date.now()
|
||||
const chart = 'system.cpu'
|
||||
// Inject synthetic series: steady then spike in last 60s
|
||||
const batch = []
|
||||
for (let i = 300; i >= 0; i--) {
|
||||
const ts = now - i * 1000
|
||||
const spike = i < 45
|
||||
batch.push({
|
||||
chart,
|
||||
context: 'system.cpu',
|
||||
ts,
|
||||
values: {
|
||||
user: spike ? 80 : 5,
|
||||
system: 2,
|
||||
idle: spike ? 15 : 90,
|
||||
iowait: 0,
|
||||
steal: 0,
|
||||
softirq: 0,
|
||||
irq: 0,
|
||||
guest: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
store.ingest(batch)
|
||||
|
||||
const before = Math.floor(now / 1000)
|
||||
const after = before - 40
|
||||
const res = await computeWeights({
|
||||
method: 'volume',
|
||||
after,
|
||||
before,
|
||||
points: 120,
|
||||
limit: 50,
|
||||
timeout: 10_000,
|
||||
})
|
||||
t.absent(res.error)
|
||||
t.is(res.method, 'volume')
|
||||
t.ok(res.view?.baseline)
|
||||
t.ok(Array.isArray(res.results))
|
||||
const hit = res.results.find((r) => r.id === chart)
|
||||
t.ok(hit, 'system.cpu should appear in results')
|
||||
t.ok(hit.weight > 0)
|
||||
})
|
||||
Reference in New Issue
Block a user