|
|
@@ -0,0 +1,59 @@
|
|
|
+import path from "path"
|
|
|
+import { writeHeapSnapshot } from "node:v8"
|
|
|
+import { Flag } from "@/flag/flag"
|
|
|
+import { Global } from "@/global"
|
|
|
+import { Log } from "@/util/log"
|
|
|
+
|
|
|
+const log = Log.create({ service: "heap" })
|
|
|
+const MINUTE = 60_000
|
|
|
+const LIMIT = 2 * 1024 * 1024 * 1024
|
|
|
+
|
|
|
+export namespace Heap {
|
|
|
+ let timer: Timer | undefined
|
|
|
+ let lock = false
|
|
|
+ let armed = true
|
|
|
+
|
|
|
+ export function start() {
|
|
|
+ if (!Flag.OPENCODE_AUTO_HEAP_SNAPSHOT) return
|
|
|
+ if (timer) return
|
|
|
+
|
|
|
+ const run = async () => {
|
|
|
+ if (lock) return
|
|
|
+
|
|
|
+ const stat = process.memoryUsage()
|
|
|
+ if (stat.rss <= LIMIT) {
|
|
|
+ armed = true
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!armed) return
|
|
|
+
|
|
|
+ lock = true
|
|
|
+ armed = false
|
|
|
+ const file = path.join(
|
|
|
+ Global.Path.log,
|
|
|
+ `heap-${process.pid}-${new Date().toISOString().replace(/[:.]/g, "")}.heapsnapshot`,
|
|
|
+ )
|
|
|
+ log.warn("heap usage exceeded limit", {
|
|
|
+ rss: stat.rss,
|
|
|
+ heap: stat.heapUsed,
|
|
|
+ file,
|
|
|
+ })
|
|
|
+
|
|
|
+ await Promise.resolve()
|
|
|
+ .then(() => writeHeapSnapshot(file))
|
|
|
+ .catch((err) => {
|
|
|
+ log.error("failed to write heap snapshot", {
|
|
|
+ error: err instanceof Error ? err.message : String(err),
|
|
|
+ file,
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ lock = false
|
|
|
+ }
|
|
|
+
|
|
|
+ timer = setInterval(() => {
|
|
|
+ void run()
|
|
|
+ }, MINUTE)
|
|
|
+ timer.unref?.()
|
|
|
+ }
|
|
|
+}
|