UNPKG

@memprofile/agent-node

Version:

Lightweight Node.js heap-snapshot agent for MemProfiler AI

153 lines (147 loc) 4.46 kB
#!/usr/bin/env node var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/config.ts import os from "os"; var cfg; var init_config = __esm({ "src/config.ts"() { "use strict"; cfg = { apiKey: process.env.MEMPROF_API_KEY || "", endpoint: process.env.MEMPROF_ENDPOINT || "https://api.memprofiler.io/v1/snapshots", intervalSec: +(process.env.MEMPROF_INTERVAL || 3600), thresholdPct: +(process.env.MEMPROF_THRESHOLD || 10), host: process.env.MEMPROF_HOSTNAME || os.hostname(), snapshotDir: process.env.MEMPROF_TMP || ".snapshots" }; if (!cfg.apiKey) throw new Error("MEMPROF_API_KEY missing"); } }); // src/snapshot.ts import inspector from "inspector"; import fs from "fs/promises"; function takeSnapshot(file) { return __async(this, null, function* () { const session = new inspector.Session(); session.connect(); const chunks = []; yield new Promise((res, rej) => { session.post("HeapProfiler.enable", () => { session.post("HeapProfiler.takeHeapSnapshot", {}, (err) => { if (err) rej(err); }); }); session.on("HeapProfiler.addHeapSnapshotChunk", (m) => chunks.push(m.params.chunk)); session.on("HeapProfiler.reportHeapSnapshotProgress", (m) => { if (m.params.finished) { session.disconnect(); res(); } }); }); yield fs.writeFile(file, chunks.join("")); }); } var init_snapshot = __esm({ "src/snapshot.ts"() { "use strict"; } }); // src/compress.ts import { gzip } from "zlib"; import { promisify } from "util"; function gzFile(input) { return __async(this, null, function* () { return gz(input, { level: 9 }); }); } var gz; var init_compress = __esm({ "src/compress.ts"() { "use strict"; gz = promisify(gzip); } }); // src/upload.ts import fetch from "node-fetch"; import { FormData } from "formdata-node"; function upload(buf) { return __async(this, null, function* () { const fd = new FormData(); fd.set("host", cfg.host); fd.set("language", "node"); fd.append("snapshot", buf, "snap.gz"); const res = yield fetch(cfg.endpoint, { method: "POST", headers: { "x-api-key": cfg.apiKey }, body: fd }); if (!res.ok) { const text = yield res.text(); throw new Error(`Upload failed (${res.status}): ${text.slice(0, 200)}`); } }); } var init_upload = __esm({ "src/upload.ts"() { "use strict"; init_config(); } }); // src/agent.ts import fs2 from "fs/promises"; import path from "path"; var require_agent = __commonJS({ "src/agent.ts"(exports) { init_config(); init_snapshot(); init_compress(); init_upload(); (() => __async(null, null, function* () { yield fs2.mkdir(cfg.snapshotDir, { recursive: true }); function cycle() { return __async(this, null, function* () { const stamp = Date.now(); const raw = path.join(cfg.snapshotDir, `${stamp}.heapsnapshot`); yield takeSnapshot(raw); const buf = yield fs2.readFile(raw); const gzBuf = yield gzFile(buf); yield upload(gzBuf); const files = (yield fs2.readdir(cfg.snapshotDir)).filter((f) => f.endsWith(".heapsnapshot")); if (files.length > 3) for (const f of files.slice(0, files.length - 3)) yield fs2.rm(path.join(cfg.snapshotDir, f)); console.log(`[memprof] snapshot ${stamp} uploaded (${(buf.length / 1e6).toFixed(1)} MB)`); }); } setInterval(cycle, cfg.intervalSec * 1e3); cycle(); }))(); } }); export default require_agent();