Usage — total data dir, Corestore, memory rings, warm HyperDB sample
Hot memory — 15m → 24h (or custom points) Warm memory — downsampled ring + samples-per-bucket Disk history — auto-rotate by age (1 day → 1 year, forever, or custom days) Soft disk budget + max warm points Auto prune on a configurable hour interval Dry-run prune / Prune now / Save to agent (admin role)
This commit is contained in:
+8
-1
@@ -1,5 +1,5 @@
|
||||
import test from 'brittle'
|
||||
import { formatMib, formatKilobitsPerSec } from '../shared/format.js'
|
||||
import { formatMib, formatKilobitsPerSec, formatBytes } from '../shared/format.js'
|
||||
|
||||
test('formatMib scales through binary units', async (t) => {
|
||||
t.is(formatMib(null), '—')
|
||||
@@ -21,3 +21,10 @@ test('formatKilobitsPerSec scales through SI bit rates', async (t) => {
|
||||
t.is(formatKilobitsPerSec(2_500_000), '2.5 Gb/s')
|
||||
t.is(formatKilobitsPerSec(3_000_000_000), '3 Tb/s')
|
||||
})
|
||||
|
||||
test('formatBytes scales binary units', async (t) => {
|
||||
t.is(formatBytes(null), '—')
|
||||
t.is(formatBytes(0), '0 B')
|
||||
t.is(formatBytes(512), '512 B')
|
||||
t.is(formatBytes(2048), '2 KiB')
|
||||
})
|
||||
|
||||
@@ -41,6 +41,10 @@ test('method roles cover monitoring surface', (t) => {
|
||||
'listChildPeers',
|
||||
'getWeights',
|
||||
'queryLogs',
|
||||
'getStorageInfo',
|
||||
'getRetentionConfig',
|
||||
'setRetentionConfig',
|
||||
'pruneHistory',
|
||||
]) {
|
||||
t.ok(MethodRoles[m], m)
|
||||
t.is(Methods[m], m)
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import test from 'brittle'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import {
|
||||
defaultRetentionConfig,
|
||||
normalizeRetentionConfig,
|
||||
retentionCutoffMs,
|
||||
RETENTION_PRESETS,
|
||||
matchPointsPreset,
|
||||
HOT_PRESETS,
|
||||
} from '../shared/retention.js'
|
||||
import { formatBytes } from '../shared/format.js'
|
||||
import { MetricStore } from '../server/services/store.js'
|
||||
import { PearDataModel } from '../server/db/model.js'
|
||||
import Corestore from 'corestore'
|
||||
import { validateMethodArgs } from '../shared/schema.js'
|
||||
import { MethodRoles, Methods, Roles } from '../shared/protocol.js'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const TMP = path.join(__dirname, '..', 'tmp-retention-test')
|
||||
|
||||
test('normalizeRetentionConfig applies presets', (t) => {
|
||||
const cfg = normalizeRetentionConfig({
|
||||
warmRetentionPreset: '1m',
|
||||
tier0Points: 900,
|
||||
})
|
||||
t.is(cfg.warmRetentionMs, RETENTION_PRESETS['1m'].ms)
|
||||
t.is(cfg.tier0Points, 900)
|
||||
t.is(matchPointsPreset(900, HOT_PRESETS), '15m')
|
||||
})
|
||||
|
||||
test('retentionCutoffMs respects forever / disabled', (t) => {
|
||||
const now = 1_700_000_000_000
|
||||
t.is(retentionCutoffMs({ warmRetentionEnabled: false, warmRetentionMs: 1000 }, now), null)
|
||||
t.is(retentionCutoffMs({ warmRetentionEnabled: true, warmRetentionMs: 0 }, now), null)
|
||||
t.is(
|
||||
retentionCutoffMs({ warmRetentionEnabled: true, warmRetentionMs: 1000 }, now),
|
||||
now - 1000
|
||||
)
|
||||
})
|
||||
|
||||
test('formatBytes scales', (t) => {
|
||||
t.is(formatBytes(0), '0 B')
|
||||
t.is(formatBytes(1024), '1 KiB')
|
||||
t.is(formatBytes(1536 * 1024 * 1024), '1.5 GiB')
|
||||
})
|
||||
|
||||
test('MetricStore setRetention trims rings', (t) => {
|
||||
const store = new MetricStore()
|
||||
const ts = Date.now()
|
||||
for (let i = 0; i < 100; i++) {
|
||||
store.ingest([
|
||||
{
|
||||
chart: 'system.cpu',
|
||||
context: 'system.cpu',
|
||||
ts: ts + i * 1000,
|
||||
values: { user: 1, idle: 99 },
|
||||
},
|
||||
])
|
||||
}
|
||||
t.ok(store.series.get('system.cpu').points.length >= 100)
|
||||
store.setRetention({ tier0Max: 10, tier1Max: 5, tier1Every: 60 })
|
||||
t.is(store.tier0Max, 10)
|
||||
t.is(store.series.get('system.cpu').points.length, 10)
|
||||
const mem = store.memoryStats()
|
||||
t.is(mem.tier0Max, 10)
|
||||
t.ok(mem.tier0Points <= 10)
|
||||
})
|
||||
|
||||
test('protocol exposes data manager methods', (t) => {
|
||||
for (const m of ['getStorageInfo', 'getRetentionConfig', 'setRetentionConfig', 'pruneHistory']) {
|
||||
t.ok(MethodRoles[m], m)
|
||||
t.is(Methods[m], m)
|
||||
}
|
||||
t.is(MethodRoles.setRetentionConfig, Roles.admin)
|
||||
t.is(MethodRoles.pruneHistory, Roles.admin)
|
||||
t.ok(validateMethodArgs('getStorageInfo', {}).ok)
|
||||
t.ok(validateMethodArgs('setRetentionConfig', { tier0Points: 900 }).ok)
|
||||
t.ok(validateMethodArgs('pruneHistory', { dryRun: true }).ok)
|
||||
})
|
||||
|
||||
test('hyperdb deleteMetricPointsBefore removes old rows', async (t) => {
|
||||
fs.rmSync(TMP, { recursive: true, force: true })
|
||||
const store = new Corestore(path.join(TMP, 'corestore'))
|
||||
await store.ready()
|
||||
const core = store.get({ name: 'peardata-meta' })
|
||||
const model = new PearDataModel(core, { autoUpdate: true })
|
||||
await model.ready()
|
||||
try {
|
||||
const now = Date.now()
|
||||
await model.putMetricPoints([
|
||||
{
|
||||
chart: 'system.cpu',
|
||||
context: 'system.cpu',
|
||||
ts: now - 10_000,
|
||||
values: { user: 1 },
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
chart: 'system.cpu',
|
||||
context: 'system.cpu',
|
||||
ts: now - 1000,
|
||||
values: { user: 2 },
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
chart: 'system.cpu',
|
||||
context: 'system.cpu',
|
||||
ts: now,
|
||||
values: { user: 3 },
|
||||
tier: 1,
|
||||
},
|
||||
])
|
||||
const dry = await model.deleteMetricPointsBefore(now - 2000, {
|
||||
charts: ['system.cpu'],
|
||||
dryRun: true,
|
||||
})
|
||||
t.is(dry.deleted, 1)
|
||||
const del = await model.deleteMetricPointsBefore(now - 2000, {
|
||||
charts: ['system.cpu'],
|
||||
dryRun: false,
|
||||
})
|
||||
t.is(del.deleted, 1)
|
||||
const rows = await model.queryMetricPoints({
|
||||
chart: 'system.cpu',
|
||||
afterMs: now - 20_000,
|
||||
beforeMs: now + 1000,
|
||||
})
|
||||
t.is(rows.length, 2)
|
||||
t.is(rows[0].values.user, 2)
|
||||
} finally {
|
||||
await model.close().catch(() => {})
|
||||
await store.close().catch(() => {})
|
||||
fs.rmSync(TMP, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('defaultRetentionConfig has sane defaults', (t) => {
|
||||
const d = defaultRetentionConfig()
|
||||
t.is(d.warmRetentionPreset, '1w')
|
||||
t.ok(d.autoPruneEnabled)
|
||||
t.ok(d.tier0Points > 0)
|
||||
})
|
||||
Reference in New Issue
Block a user