Files
MikrotikManager/backend/src/services/poller.ts
T
DenozordecandCursor c6c859a495
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-test (push) Successful in 2m2s
Docker images / frontend-image (push) Successful in 3m23s
Docker images / updater-image (push) Successful in 44s
Docker images / backend-image (push) Successful in 2m47s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s
perf(db): сжать схему PostgreSQL 18 и повторно загрузить SQLite
При первом рестарте wipe всех таблиц и импорт из SQLite в компактную схему. Retention через DROP PARTITION, lz4 и AIO worker.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 10:54:36 +07:00

117 lines
4.0 KiB
TypeScript

import { eq } from "drizzle-orm"
import { db, dbQuery, pool } from "../db/index.js"
import { dropExpiredPartitions, ensurePartitionsAround } from "../db/partitions.js"
import { servers, serverSnapshots } from "../db/schema.js"
import type { SnapshotInsert } from "../db/schema.js"
import type { SnapshotRead } from "../types/server.js"
import { MikrotikClient } from "./mikrotik.js"
import { parseRosCpuLoadPercent, parseRosDataSizeBytes } from "./ros-metric-parse.js"
import { invalidateFlowCatalogCache } from "./traffic-flow-topology.js"
// ── pollServer ─────────────────────────────────────────────────────────────────
/**
* Connect to a RouterOS device, collect system info, persist a snapshot,
* and sync the server's display name from system/identity.
*/
export async function pollServer(serverId: number): Promise<SnapshotRead> {
const server = (await db
.select()
.from(servers)
.where(eq(servers.id, serverId))
.limit(1))[0]
if (!server) {
throw new Error(`Server with id=${serverId} not found`)
}
const now = new Date().toISOString()
const client = MikrotikClient.fromServer(server)
const t0 = performance.now()
const partialSnap: Partial<SnapshotInsert> = {
serverId,
polledAt: now,
status: "offline",
latencyMs: null,
}
try {
// Fire all requests in parallel for speed
const [identity, resource, ifaces, addresses] = await Promise.all([
client.getIdentity(),
client.getResource(),
client.getInterfaces(),
client.getIpAddresses(),
])
const latencyMs = performance.now() - t0
const cpuLoad = parseRosCpuLoadPercent(resource["cpu-load"])
const freeMem = parseRosDataSizeBytes(resource["free-memory"])
const totalMem = parseRosDataSizeBytes(resource["total-memory"])
Object.assign(partialSnap, {
status: "online",
latencyMs,
identityName: identity.name,
rosVersion: resource["version"],
boardName: resource["board-name"],
uptime: resource["uptime"],
cpuLoad,
freeMemory: freeMem,
totalMemory: totalMem,
rawInterfaces: ifaces,
rawIpAddresses: addresses,
} satisfies Partial<SnapshotInsert>)
if ((identity.name || "") !== (server.name || "")) {
await db.update(servers)
.set({ name: identity.name, updatedAt: now })
.where(eq(servers.id, serverId))
invalidateFlowCatalogCache()
}
} catch (err) {
// Log but don't throw — we still persist the offline snapshot
console.warn(`[poller] server id=${serverId} unreachable:`, (err as Error).message)
}
const [inserted] = await db
.insert(serverSnapshots)
.values(partialSnap as SnapshotInsert)
.returning()
if (inserted) {
await dbQuery(
`UPDATE server_snapshots
SET raw_interfaces = NULL, raw_ip_addresses = NULL
WHERE server_id = $1 AND polled_at < $2
AND (raw_interfaces IS NOT NULL OR raw_ip_addresses IS NOT NULL)`,
[serverId, inserted.polledAt],
)
}
void dropExpiredPartitions(pool).then(() => ensurePartitionsAround(pool))
return toSnapshotRead(inserted!)
}
// ── helper ─────────────────────────────────────────────────────────────────────
export function toSnapshotRead(s: typeof serverSnapshots.$inferSelect): SnapshotRead {
return {
id: s.id,
serverId: s.serverId,
polledAt: s.polledAt,
status: s.status,
latencyMs: s.latencyMs ?? null,
rosVersion: s.rosVersion ?? null,
boardName: s.boardName ?? null,
uptime: s.uptime ?? null,
cpuLoad: s.cpuLoad ?? null,
freeMemory: s.freeMemory ?? null,
totalMemory: s.totalMemory ?? null,
identityName: s.identityName ?? null,
}
}