updates
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import test from 'brittle'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { getStore } from '../server/services/store.js'
|
||||
import {
|
||||
toPrometheusText,
|
||||
writeSnapshotFile,
|
||||
buildSnapshot,
|
||||
} from '../server/services/export.js'
|
||||
|
||||
test('toPrometheusText includes peardata_info and dimensions', (t) => {
|
||||
const text = toPrometheusText({
|
||||
'system.cpu': {
|
||||
ts: Date.now(),
|
||||
values: { user: 10, idle: 90 },
|
||||
},
|
||||
})
|
||||
t.ok(text.includes('peardata_info'))
|
||||
t.ok(text.includes('peardata_system_cpu'))
|
||||
t.ok(text.includes('dimension="idle"'))
|
||||
t.ok(text.includes(' 90'))
|
||||
})
|
||||
|
||||
test('writeSnapshotFile respects PEARDATA_EXPORT_DIR', (t) => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'peardata-export-'))
|
||||
const prev = process.env.PEARDATA_EXPORT_DIR
|
||||
process.env.PEARDATA_EXPORT_DIR = dir
|
||||
const snap = { ts: 1234567890, latest: {}, success: true }
|
||||
const written = writeSnapshotFile(snap)
|
||||
t.ok(written.path)
|
||||
t.ok(fs.existsSync(written.path))
|
||||
const parsed = JSON.parse(fs.readFileSync(written.path, 'utf8'))
|
||||
t.is(parsed.ts, 1234567890)
|
||||
fs.rmSync(dir, { recursive: true, force: true })
|
||||
if (prev == null) delete process.env.PEARDATA_EXPORT_DIR
|
||||
else process.env.PEARDATA_EXPORT_DIR = prev
|
||||
})
|
||||
|
||||
test('buildSnapshot returns latest from store', (t) => {
|
||||
// ensure singleton has something
|
||||
const store = getStore()
|
||||
store.ingest([
|
||||
{
|
||||
chart: 'system.ram',
|
||||
context: 'system.ram',
|
||||
ts: Date.now(),
|
||||
values: { used: 100, free: 200, cached: 50, buffers: 10 },
|
||||
},
|
||||
])
|
||||
const snap = buildSnapshot()
|
||||
t.ok(snap.success)
|
||||
t.ok(snap.latest['system.ram'])
|
||||
t.ok(snap.health)
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import test from 'brittle'
|
||||
import { parseNginxStubStatus, isNginxEnabled } from '../server/services/collectors/nginx.js'
|
||||
|
||||
const SAMPLE = `Active connections: 291
|
||||
server accepts handled requests
|
||||
16630948 16630948 31070465
|
||||
Reading: 6 Writing: 179 Waiting: 106
|
||||
`
|
||||
|
||||
test('parseNginxStubStatus', (t) => {
|
||||
const p = parseNginxStubStatus(SAMPLE)
|
||||
t.ok(p)
|
||||
t.is(p.active, 291)
|
||||
t.is(p.accepts, 16630948)
|
||||
t.is(p.handled, 16630948)
|
||||
t.is(p.requests, 31070465)
|
||||
t.is(p.reading, 6)
|
||||
t.is(p.writing, 179)
|
||||
t.is(p.waiting, 106)
|
||||
})
|
||||
|
||||
test('parseNginxStubStatus rejects junk', (t) => {
|
||||
t.is(parseNginxStubStatus('hello'), null)
|
||||
})
|
||||
|
||||
test('isNginxEnabled', (t) => {
|
||||
const prev = process.env.PEARDATA_NGINX
|
||||
delete process.env.PEARDATA_NGINX
|
||||
t.absent(isNginxEnabled())
|
||||
process.env.PEARDATA_NGINX = '1'
|
||||
t.ok(isNginxEnabled())
|
||||
if (prev == null) delete process.env.PEARDATA_NGINX
|
||||
else process.env.PEARDATA_NGINX = prev
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import test from 'brittle'
|
||||
import {
|
||||
extractChildStats,
|
||||
parseParentPeers,
|
||||
isParentEnabled,
|
||||
} from '../server/services/collectors/parent.js'
|
||||
|
||||
test('isParentEnabled', (t) => {
|
||||
const prev = process.env.PEARDATA_PARENT
|
||||
delete process.env.PEARDATA_PARENT
|
||||
t.absent(isParentEnabled())
|
||||
process.env.PEARDATA_PARENT = '1'
|
||||
t.ok(isParentEnabled())
|
||||
if (prev == null) delete process.env.PEARDATA_PARENT
|
||||
else process.env.PEARDATA_PARENT = prev
|
||||
})
|
||||
|
||||
test('parseParentPeers filters hex keys', (t) => {
|
||||
const prev = process.env.PEARDATA_PARENT_PEERS
|
||||
const prevFile = process.env.PEARDATA_PARENT_PEERS_FILE
|
||||
delete process.env.PEARDATA_PARENT_PEERS_FILE
|
||||
process.env.PEARDATA_PARENT_PEERS = `aa${'b'.repeat(62)}, not-a-key, ${'c'.repeat(64)}`
|
||||
const list = parseParentPeers()
|
||||
t.is(list.length, 2)
|
||||
t.ok(list[0].startsWith('aa'))
|
||||
if (prev == null) delete process.env.PEARDATA_PARENT_PEERS
|
||||
else process.env.PEARDATA_PARENT_PEERS = prev
|
||||
if (prevFile == null) delete process.env.PEARDATA_PARENT_PEERS_FILE
|
||||
else process.env.PEARDATA_PARENT_PEERS_FILE = prevFile
|
||||
})
|
||||
|
||||
test('extractChildStats from getAllMetrics body', (t) => {
|
||||
const stats = extractChildStats({
|
||||
contentType: 'application/json',
|
||||
body: {
|
||||
charts: {
|
||||
'system.cpu': { dimensions: { idle: 72, user: 20, system: 8 } },
|
||||
'system.ram': { dimensions: { used: 2048, free: 1024 } },
|
||||
},
|
||||
},
|
||||
})
|
||||
t.is(stats.cpu, 28)
|
||||
t.is(stats.ram, 2048)
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import test from 'brittle'
|
||||
import {
|
||||
parsePostgresStats,
|
||||
isPostgresEnabled,
|
||||
} from '../server/services/collectors/postgres.js'
|
||||
|
||||
test('parsePostgresStats', (t) => {
|
||||
const s = parsePostgresStats(`
|
||||
# comment
|
||||
connections=18
|
||||
xact_commit: 1000
|
||||
xact_rollback=2
|
||||
tuples_returned=50000
|
||||
`)
|
||||
t.is(s.connections, 18)
|
||||
t.is(s.xact_commit, 1000)
|
||||
t.is(s.xact_rollback, 2)
|
||||
t.is(s.tuples_returned, 50000)
|
||||
})
|
||||
|
||||
test('isPostgresEnabled', (t) => {
|
||||
const prev = process.env.PEARDATA_POSTGRES
|
||||
delete process.env.PEARDATA_POSTGRES
|
||||
t.absent(isPostgresEnabled())
|
||||
process.env.PEARDATA_POSTGRES = '1'
|
||||
t.ok(isPostgresEnabled())
|
||||
if (prev == null) delete process.env.PEARDATA_POSTGRES
|
||||
else process.env.PEARDATA_POSTGRES = prev
|
||||
})
|
||||
@@ -37,6 +37,8 @@ test('method roles cover monitoring surface', (t) => {
|
||||
'getDbInfo',
|
||||
'linkPeer',
|
||||
'unlinkPeer',
|
||||
'getFleetHealth',
|
||||
'listChildPeers',
|
||||
]) {
|
||||
t.ok(MethodRoles[m], m)
|
||||
t.is(Methods[m], m)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import test from 'brittle'
|
||||
import {
|
||||
parseRedisInfo,
|
||||
parseRedisAddr,
|
||||
isRedisEnabled,
|
||||
} from '../server/services/collectors/redis.js'
|
||||
|
||||
const SAMPLE = `# Server
|
||||
redis_version:7.2.0
|
||||
# Memory
|
||||
used_memory:1048576
|
||||
used_memory_peak:2097152
|
||||
used_memory_rss:3145728
|
||||
# Clients
|
||||
connected_clients:12
|
||||
blocked_clients:1
|
||||
# Stats
|
||||
instantaneous_ops_per_sec:42
|
||||
keyspace_hits:90
|
||||
keyspace_misses:10
|
||||
`
|
||||
|
||||
test('parseRedisInfo', (t) => {
|
||||
const info = parseRedisInfo(SAMPLE)
|
||||
t.is(info.redis_version, '7.2.0')
|
||||
t.is(info.used_memory, '1048576')
|
||||
t.is(info.connected_clients, '12')
|
||||
t.is(info.instantaneous_ops_per_sec, '42')
|
||||
})
|
||||
|
||||
test('parseRedisAddr', (t) => {
|
||||
t.is(parseRedisAddr('10.0.0.2:6380').port, 6380)
|
||||
t.is(parseRedisAddr('redis://localhost:6379/0').host, 'localhost')
|
||||
t.is(parseRedisAddr('redis://localhost:6379/0').port, 6379)
|
||||
})
|
||||
|
||||
test('isRedisEnabled', (t) => {
|
||||
const prev = process.env.PEARDATA_REDIS
|
||||
delete process.env.PEARDATA_REDIS
|
||||
t.absent(isRedisEnabled())
|
||||
process.env.PEARDATA_REDIS = '1'
|
||||
t.ok(isRedisEnabled())
|
||||
if (prev == null) delete process.env.PEARDATA_REDIS
|
||||
else process.env.PEARDATA_REDIS = prev
|
||||
})
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Linked peer warm pull: replicate Corestore A → B, open remote bee by dbKeyHex,
|
||||
* query metric points without re-scraping.
|
||||
*/
|
||||
import test from 'brittle'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import Corestore from 'corestore'
|
||||
import { PearDataModel } from '../server/db/model.js'
|
||||
import { MetricStore } from '../server/services/store.js'
|
||||
import { openDb, closeDb } from '../server/db/index.js'
|
||||
import { openRemoteDb, closeRemoteDbs, queryRemoteMetricPoints } from '../server/db/remote.js'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const TMP = path.join(__dirname, '..', 'tmp-swarm-pull-test')
|
||||
|
||||
function replicatePair(a, b) {
|
||||
const s1 = a.replicate(true)
|
||||
const s2 = b.replicate(false)
|
||||
s1.pipe(s2).pipe(s1)
|
||||
return () => {
|
||||
try {
|
||||
s1.destroy()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
s2.destroy()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('linked peer pulls warm points over corestore replicate', async (t) => {
|
||||
fs.rmSync(TMP, { recursive: true, force: true })
|
||||
const dirA = path.join(TMP, 'a')
|
||||
const dirB = path.join(TMP, 'b-local')
|
||||
|
||||
const storeA = new Corestore(path.join(dirA, 'corestore'))
|
||||
await storeA.ready()
|
||||
const coreA = storeA.get({ name: 'peardata-meta' })
|
||||
const modelA = new PearDataModel(coreA, { autoUpdate: true })
|
||||
await modelA.ready()
|
||||
|
||||
const now = Date.now()
|
||||
await modelA.putMetricPoints([
|
||||
{
|
||||
chart: 'system.cpu',
|
||||
context: 'system.cpu',
|
||||
ts: now - 90_000,
|
||||
values: { user: 15, system: 5, nice: 0, iowait: 0, irq: 0, softirq: 0, idle: 80 },
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
chart: 'system.cpu',
|
||||
context: 'system.cpu',
|
||||
ts: now - 30_000,
|
||||
values: { user: 25, system: 5, nice: 0, iowait: 0, irq: 0, softirq: 0, idle: 70 },
|
||||
tier: 1,
|
||||
},
|
||||
])
|
||||
|
||||
const dbKeyHex = modelA.publicKeyHex
|
||||
|
||||
// B: local agent HyperDB + open A's core by key, then replicate
|
||||
process.env.PEARDATA_DATA_DIR = dirB
|
||||
delete process.env.PEARDATA_HYPERDB
|
||||
await closeDb().catch(() => {})
|
||||
await closeRemoteDbs().catch(() => {})
|
||||
const localB = await openDb()
|
||||
t.ok(localB)
|
||||
|
||||
const remote = await openRemoteDb(dbKeyHex)
|
||||
t.ok(remote)
|
||||
|
||||
const storeB = (await import('../server/db/index.js')).getCorestore()
|
||||
const stop = replicatePair(storeA, storeB)
|
||||
|
||||
// wait for remote core to download blocks
|
||||
const deadline = Date.now() + 10_000
|
||||
let rows = []
|
||||
while (Date.now() < deadline) {
|
||||
rows = await queryRemoteMetricPoints({
|
||||
chart: 'system.cpu',
|
||||
afterMs: now - 180_000,
|
||||
beforeMs: now + 1000,
|
||||
tier: 1,
|
||||
limit: 100,
|
||||
})
|
||||
if (rows.length >= 2) break
|
||||
await new Promise((r) => setTimeout(r, 100))
|
||||
}
|
||||
|
||||
t.ok(rows.length >= 2, `expected warm rows, got ${rows.length}`)
|
||||
|
||||
// MetricStore query should surface hyperdb-remote when memory empty
|
||||
const mem = new MetricStore()
|
||||
const q = await mem.query({
|
||||
chart: 'system.cpu',
|
||||
after: Math.floor((now - 180_000) / 1000),
|
||||
before: Math.floor(now / 1000),
|
||||
points: 60,
|
||||
tier: 1,
|
||||
})
|
||||
t.ok(q.source === 'hyperdb-remote' || q.source === 'hyperdb-warm')
|
||||
t.ok(q.data.length >= 2)
|
||||
|
||||
stop()
|
||||
await closeRemoteDbs()
|
||||
await closeDb()
|
||||
await modelA.close().catch(() => {})
|
||||
await storeA.close().catch(() => {})
|
||||
fs.rmSync(TMP, { recursive: true, force: true })
|
||||
delete process.env.PEARDATA_DATA_DIR
|
||||
})
|
||||
Reference in New Issue
Block a user