@yunzai-ng/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +51 -0
- package/dist/adapter/accounts.d.ts +174 -0
- package/dist/adapter/accounts.d.ts.map +1 -0
- package/dist/adapter/accounts.js +653 -0
- package/dist/adapter/accounts.js.map +1 -0
- package/dist/adapter/bots.d.ts +142 -0
- package/dist/adapter/bots.d.ts.map +1 -0
- package/dist/adapter/bots.js +454 -0
- package/dist/adapter/bots.js.map +1 -0
- package/dist/adapter/host.d.ts +114 -0
- package/dist/adapter/host.d.ts.map +1 -0
- package/dist/adapter/host.js +90 -0
- package/dist/adapter/host.js.map +1 -0
- package/dist/adapter/login.d.ts +174 -0
- package/dist/adapter/login.d.ts.map +1 -0
- package/dist/adapter/login.js +345 -0
- package/dist/adapter/login.js.map +1 -0
- package/dist/adapter/registry.d.ts +102 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/registry.js +150 -0
- package/dist/adapter/registry.js.map +1 -0
- package/dist/config/core-config.d.ts +151 -0
- package/dist/config/core-config.d.ts.map +1 -0
- package/dist/config/core-config.js +338 -0
- package/dist/config/core-config.js.map +1 -0
- package/dist/config/schema.d.ts +449 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +871 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +184 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +428 -0
- package/dist/config/store.js.map +1 -0
- package/dist/config/yaml.d.ts +23 -0
- package/dist/config/yaml.d.ts.map +1 -0
- package/dist/config/yaml.js +108 -0
- package/dist/config/yaml.js.map +1 -0
- package/dist/http/client.d.ts +82 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +658 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/app.d.ts +244 -0
- package/dist/kernel/app.d.ts.map +1 -0
- package/dist/kernel/app.js +654 -0
- package/dist/kernel/app.js.map +1 -0
- package/dist/kernel/kv-sink.d.ts +34 -0
- package/dist/kernel/kv-sink.d.ts.map +1 -0
- package/dist/kernel/kv-sink.js +25 -0
- package/dist/kernel/kv-sink.js.map +1 -0
- package/dist/kernel/policy.d.ts +77 -0
- package/dist/kernel/policy.d.ts.map +1 -0
- package/dist/kernel/policy.js +99 -0
- package/dist/kernel/policy.js.map +1 -0
- package/dist/kernel/runtime.d.ts +117 -0
- package/dist/kernel/runtime.d.ts.map +1 -0
- package/dist/kernel/runtime.js +197 -0
- package/dist/kernel/runtime.js.map +1 -0
- package/dist/kernel/sql-sink.d.ts +36 -0
- package/dist/kernel/sql-sink.d.ts.map +1 -0
- package/dist/kernel/sql-sink.js +114 -0
- package/dist/kernel/sql-sink.js.map +1 -0
- package/dist/logger/format.d.ts +67 -0
- package/dist/logger/format.d.ts.map +1 -0
- package/dist/logger/format.js +218 -0
- package/dist/logger/format.js.map +1 -0
- package/dist/logger/index.d.ts +97 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +363 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/rotate.d.ts +47 -0
- package/dist/logger/rotate.d.ts.map +1 -0
- package/dist/logger/rotate.js +242 -0
- package/dist/logger/rotate.js.map +1 -0
- package/dist/message/segment.d.ts +255 -0
- package/dist/message/segment.d.ts.map +1 -0
- package/dist/message/segment.js +510 -0
- package/dist/message/segment.js.map +1 -0
- package/dist/message/split.d.ts +34 -0
- package/dist/message/split.d.ts.map +1 -0
- package/dist/message/split.js +79 -0
- package/dist/message/split.js.map +1 -0
- package/dist/message/target.d.ts +29 -0
- package/dist/message/target.d.ts.map +1 -0
- package/dist/message/target.js +34 -0
- package/dist/message/target.js.map +1 -0
- package/dist/pipeline/cooldown.d.ts +84 -0
- package/dist/pipeline/cooldown.d.ts.map +1 -0
- package/dist/pipeline/cooldown.js +94 -0
- package/dist/pipeline/cooldown.js.map +1 -0
- package/dist/pipeline/dispatch.d.ts +98 -0
- package/dist/pipeline/dispatch.d.ts.map +1 -0
- package/dist/pipeline/dispatch.js +323 -0
- package/dist/pipeline/dispatch.js.map +1 -0
- package/dist/pipeline/event.d.ts +130 -0
- package/dist/pipeline/event.d.ts.map +1 -0
- package/dist/pipeline/event.js +538 -0
- package/dist/pipeline/event.js.map +1 -0
- package/dist/pipeline/middleware.d.ts +63 -0
- package/dist/pipeline/middleware.d.ts.map +1 -0
- package/dist/pipeline/middleware.js +174 -0
- package/dist/pipeline/middleware.js.map +1 -0
- package/dist/pipeline/prompt.d.ts +80 -0
- package/dist/pipeline/prompt.d.ts.map +1 -0
- package/dist/pipeline/prompt.js +155 -0
- package/dist/pipeline/prompt.js.map +1 -0
- package/dist/pipeline/router.d.ts +98 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +489 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/platform/detect.d.ts +24 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +178 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/paths.d.ts +38 -0
- package/dist/platform/paths.d.ts.map +1 -0
- package/dist/platform/paths.js +129 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/system.d.ts +71 -0
- package/dist/platform/system.d.ts.map +1 -0
- package/dist/platform/system.js +242 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/plugin/context.d.ts +92 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +573 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +92 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +82 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/discover.d.ts +106 -0
- package/dist/plugin/discover.d.ts.map +1 -0
- package/dist/plugin/discover.js +254 -0
- package/dist/plugin/discover.js.map +1 -0
- package/dist/plugin/events.d.ts +109 -0
- package/dist/plugin/events.d.ts.map +1 -0
- package/dist/plugin/events.js +209 -0
- package/dist/plugin/events.js.map +1 -0
- package/dist/plugin/hooks.d.ts +266 -0
- package/dist/plugin/hooks.d.ts.map +1 -0
- package/dist/plugin/hooks.js +84 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.d.ts +160 -0
- package/dist/plugin/host.d.ts.map +1 -0
- package/dist/plugin/host.js +546 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/market.d.ts +264 -0
- package/dist/plugin/market.d.ts.map +1 -0
- package/dist/plugin/market.js +700 -0
- package/dist/plugin/market.js.map +1 -0
- package/dist/plugin/services.d.ts +116 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +213 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/tar.d.ts +65 -0
- package/dist/plugin/tar.d.ts.map +1 -0
- package/dist/plugin/tar.js +277 -0
- package/dist/plugin/tar.js.map +1 -0
- package/dist/render/registry.d.ts +127 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +306 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/scheduler/index.d.ts +67 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +279 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/server/api.d.ts +179 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +602 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/auth.d.ts +83 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +178 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/browse.d.ts +60 -0
- package/dist/server/browse.d.ts.map +1 -0
- package/dist/server/browse.js +187 -0
- package/dist/server/browse.js.map +1 -0
- package/dist/server/files.d.ts +27 -0
- package/dist/server/files.d.ts.map +1 -0
- package/dist/server/files.js +76 -0
- package/dist/server/files.js.map +1 -0
- package/dist/server/index.d.ts +189 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1123 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/table.d.ts +105 -0
- package/dist/server/table.d.ts.map +1 -0
- package/dist/server/table.js +289 -0
- package/dist/server/table.js.map +1 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +120 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +73 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +174 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/kv.d.ts +126 -0
- package/dist/store/kv.d.ts.map +1 -0
- package/dist/store/kv.js +232 -0
- package/dist/store/kv.js.map +1 -0
- package/dist/store/level.d.ts +103 -0
- package/dist/store/level.d.ts.map +1 -0
- package/dist/store/level.js +119 -0
- package/dist/store/level.js.map +1 -0
- package/dist/store/memory.d.ts +61 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +94 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/sql.d.ts +142 -0
- package/dist/store/sql.d.ts.map +1 -0
- package/dist/store/sql.js +246 -0
- package/dist/store/sql.js.map +1 -0
- package/dist/testing/fake.d.ts +109 -0
- package/dist/testing/fake.d.ts.map +1 -0
- package/dist/testing/fake.js +196 -0
- package/dist/testing/fake.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-adapter.d.ts +205 -0
- package/dist/testing/mock-adapter.d.ts.map +1 -0
- package/dist/testing/mock-adapter.js +309 -0
- package/dist/testing/mock-adapter.js.map +1 -0
- package/dist/util/deep.d.ts +108 -0
- package/dist/util/deep.d.ts.map +1 -0
- package/dist/util/deep.js +275 -0
- package/dist/util/deep.js.map +1 -0
- package/dist/util/defer.d.ts +107 -0
- package/dist/util/defer.d.ts.map +1 -0
- package/dist/util/defer.js +163 -0
- package/dist/util/defer.js.map +1 -0
- package/dist/util/dispose.d.ts +88 -0
- package/dist/util/dispose.d.ts.map +1 -0
- package/dist/util/dispose.js +143 -0
- package/dist/util/dispose.js.map +1 -0
- package/dist/util/duration.d.ts +28 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +77 -0
- package/dist/util/duration.js.map +1 -0
- package/dist/util/fs.d.ts +110 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +269 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/id.d.ts +49 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +87 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lru.d.ts +74 -0
- package/dist/util/lru.d.ts.map +1 -0
- package/dist/util/lru.js +156 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/util/queue.d.ts +64 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +147 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/text.d.ts +86 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +182 -0
- package/dist/util/text.js.map +1 -0
- package/package.json +44 -0
- package/src/adapter/accounts.ts +758 -0
- package/src/adapter/bots.ts +582 -0
- package/src/adapter/host.ts +224 -0
- package/src/adapter/login.ts +481 -0
- package/src/adapter/registry.ts +202 -0
- package/src/config/core-config.test.ts +60 -0
- package/src/config/core-config.ts +401 -0
- package/src/config/schema.test.ts +179 -0
- package/src/config/schema.ts +1036 -0
- package/src/config/store.test.ts +245 -0
- package/src/config/store.ts +506 -0
- package/src/config/yaml.ts +119 -0
- package/src/http/client.test.ts +568 -0
- package/src/http/client.ts +777 -0
- package/src/index.ts +106 -0
- package/src/kernel/app.test.ts +267 -0
- package/src/kernel/app.ts +843 -0
- package/src/kernel/kv-sink.ts +58 -0
- package/src/kernel/policy.ts +120 -0
- package/src/kernel/runtime.test.ts +542 -0
- package/src/kernel/runtime.ts +307 -0
- package/src/kernel/sql-sink.ts +154 -0
- package/src/logger/format.ts +259 -0
- package/src/logger/index.ts +427 -0
- package/src/logger/rotate.ts +265 -0
- package/src/message/segment.test.ts +335 -0
- package/src/message/segment.ts +569 -0
- package/src/message/split.ts +97 -0
- package/src/message/target.ts +48 -0
- package/src/pipeline/cooldown.ts +136 -0
- package/src/pipeline/dispatch.ts +419 -0
- package/src/pipeline/event.ts +735 -0
- package/src/pipeline/middleware.test.ts +426 -0
- package/src/pipeline/middleware.ts +199 -0
- package/src/pipeline/prompt.ts +212 -0
- package/src/pipeline/router.test.ts +432 -0
- package/src/pipeline/router.ts +575 -0
- package/src/platform/detect.ts +178 -0
- package/src/platform/paths.ts +147 -0
- package/src/platform/system.test.ts +102 -0
- package/src/platform/system.ts +289 -0
- package/src/plugin/context.test.ts +459 -0
- package/src/plugin/context.ts +757 -0
- package/src/plugin/define.test.ts +120 -0
- package/src/plugin/define.ts +142 -0
- package/src/plugin/discover.test.ts +253 -0
- package/src/plugin/discover.ts +353 -0
- package/src/plugin/events.test.ts +162 -0
- package/src/plugin/events.ts +266 -0
- package/src/plugin/hooks.ts +373 -0
- package/src/plugin/host.test.ts +696 -0
- package/src/plugin/host.ts +738 -0
- package/src/plugin/market.test.ts +781 -0
- package/src/plugin/market.ts +870 -0
- package/src/plugin/services.test.ts +135 -0
- package/src/plugin/services.ts +261 -0
- package/src/plugin/tar.test.ts +158 -0
- package/src/plugin/tar.ts +309 -0
- package/src/render/registry.test.ts +127 -0
- package/src/render/registry.ts +439 -0
- package/src/scheduler/index.ts +357 -0
- package/src/server/api.test.ts +802 -0
- package/src/server/api.ts +869 -0
- package/src/server/auth.ts +201 -0
- package/src/server/browse.test.ts +193 -0
- package/src/server/browse.ts +240 -0
- package/src/server/files.ts +97 -0
- package/src/server/index.test.ts +566 -0
- package/src/server/index.ts +1386 -0
- package/src/server/table.ts +351 -0
- package/src/store/index.ts +156 -0
- package/src/store/json.ts +198 -0
- package/src/store/kv.test.ts +307 -0
- package/src/store/kv.ts +256 -0
- package/src/store/level.ts +167 -0
- package/src/store/memory.ts +109 -0
- package/src/store/sql.test.ts +204 -0
- package/src/store/sql.ts +324 -0
- package/src/testing/fake.ts +297 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/mock-adapter.ts +588 -0
- package/src/util/deep.ts +272 -0
- package/src/util/defer.ts +211 -0
- package/src/util/dispose.ts +169 -0
- package/src/util/duration.ts +82 -0
- package/src/util/fs.ts +279 -0
- package/src/util/id.ts +94 -0
- package/src/util/lru.ts +185 -0
- package/src/util/queue.ts +167 -0
- package/src/util/text.ts +189 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:探测运行环境(操作系统族、Termux、容器、内存档位、资源占用)
|
|
3
|
+
* 依赖方向:仅依赖 node 内置模块与类型包
|
|
4
|
+
* 生命周期:`detectPlatform()` 结果在进程内不变,缓存一份
|
|
5
|
+
* 注意事项:Termux 上的 Node 通常把 `process.platform` 报成 `"linux"`,
|
|
6
|
+
* 单看它会把安卓当普通 Linux,进而按桌面策略去找 Chromium、
|
|
7
|
+
* 按大内存开并发 —— 这是安卓端最常见的 OOM 原因。
|
|
8
|
+
* 所以这里做多重取证:环境变量 PREFIX / TERMUX_VERSION、
|
|
9
|
+
* `/data/data/com.termux` 目录、`/system/build.prop`。
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
12
|
+
import { arch, cpus, platform, totalmem } from "node:os"
|
|
13
|
+
import type { PlatformInfo, OsFamily, ResourceUsage } from "@yunzai-ng/types"
|
|
14
|
+
|
|
15
|
+
/** 低内存判定阈值:2GB */
|
|
16
|
+
const LOW_MEMORY_BYTES = 2 * 1024 * 1024 * 1024
|
|
17
|
+
|
|
18
|
+
/** 探测结果缓存 */
|
|
19
|
+
let cached: PlatformInfo | undefined
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 是否运行在 Termux(安卓)里
|
|
23
|
+
* @returns 是否为 Termux 环境
|
|
24
|
+
*/
|
|
25
|
+
function detectTermux(): boolean {
|
|
26
|
+
if (process.env.TERMUX_VERSION) return true
|
|
27
|
+
const prefix = process.env.PREFIX ?? ""
|
|
28
|
+
if (prefix.includes("com.termux")) return true
|
|
29
|
+
if (existsSync("/data/data/com.termux/files/usr")) return true
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 是否运行在安卓上(含非 Termux 的其他安卓运行环境)
|
|
35
|
+
* @returns 是否为安卓
|
|
36
|
+
*/
|
|
37
|
+
function detectAndroid(): boolean {
|
|
38
|
+
if (platform() === "android") return true
|
|
39
|
+
if (detectTermux()) return true
|
|
40
|
+
// 非 Termux 的安卓容器:靠系统属性文件判断
|
|
41
|
+
return existsSync("/system/build.prop") && existsSync("/system/bin/app_process")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 是否运行在容器里
|
|
46
|
+
*
|
|
47
|
+
* 容器内可用内存往往远小于 `os.totalmem()` 报告的宿主机内存,
|
|
48
|
+
* 日志和并发策略需要知道这件事。
|
|
49
|
+
* @returns 是否为容器环境
|
|
50
|
+
*/
|
|
51
|
+
function detectContainer(): boolean {
|
|
52
|
+
if (existsSync("/.dockerenv")) return true
|
|
53
|
+
if (process.env.KUBERNETES_SERVICE_HOST) return true
|
|
54
|
+
try {
|
|
55
|
+
const cgroup = readFileSync("/proc/1/cgroup", "utf8")
|
|
56
|
+
return /docker|kubepods|containerd|lxc/.test(cgroup)
|
|
57
|
+
} catch {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 判定操作系统族
|
|
64
|
+
* @returns 操作系统族
|
|
65
|
+
*/
|
|
66
|
+
function detectOs(): OsFamily {
|
|
67
|
+
if (detectAndroid()) return "android"
|
|
68
|
+
switch (platform()) {
|
|
69
|
+
case "win32":
|
|
70
|
+
return "windows"
|
|
71
|
+
case "darwin":
|
|
72
|
+
return "macos"
|
|
73
|
+
case "linux":
|
|
74
|
+
return "linux"
|
|
75
|
+
default:
|
|
76
|
+
return "unknown"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 读取 cgroup v2/v1 的内存上限
|
|
82
|
+
*
|
|
83
|
+
* 容器里 `os.totalmem()` 给的是宿主机内存,会让"低内存档位"判断失效。
|
|
84
|
+
* @returns 字节数;读不到时 undefined
|
|
85
|
+
*/
|
|
86
|
+
function readCgroupMemoryLimit(): number | undefined {
|
|
87
|
+
const candidates = ["/sys/fs/cgroup/memory.max", "/sys/fs/cgroup/memory/memory.limit_in_bytes"]
|
|
88
|
+
for (const file of candidates) {
|
|
89
|
+
try {
|
|
90
|
+
const text = readFileSync(file, "utf8").trim()
|
|
91
|
+
if (text === "max") continue
|
|
92
|
+
const value = Number(text)
|
|
93
|
+
// cgroup v1 未设限时是一个接近 2^63 的巨大数,直接忽略
|
|
94
|
+
if (Number.isFinite(value) && value > 0 && value < Number.MAX_SAFE_INTEGER) return value
|
|
95
|
+
} catch {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 探测运行环境
|
|
104
|
+
* @returns 环境信息(进程内缓存,多次调用返回同一对象)
|
|
105
|
+
*/
|
|
106
|
+
export function detectPlatform(): PlatformInfo {
|
|
107
|
+
if (cached) return cached
|
|
108
|
+
|
|
109
|
+
const isContainer = detectContainer()
|
|
110
|
+
const hostMemory = totalmem()
|
|
111
|
+
const limited = readCgroupMemoryLimit()
|
|
112
|
+
const totalMemory = limited !== undefined ? Math.min(hostMemory, limited) : hostMemory
|
|
113
|
+
|
|
114
|
+
cached = Object.freeze({
|
|
115
|
+
os: detectOs(),
|
|
116
|
+
arch: arch(),
|
|
117
|
+
isTermux: detectTermux(),
|
|
118
|
+
isContainer,
|
|
119
|
+
nodeVersion: process.versions.node,
|
|
120
|
+
cpus: Math.max(1, cpus().length),
|
|
121
|
+
totalMemory,
|
|
122
|
+
lowMemory: totalMemory > 0 && totalMemory < LOW_MEMORY_BYTES
|
|
123
|
+
})
|
|
124
|
+
return cached
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 上一次 CPU 采样点 */
|
|
128
|
+
let lastCpuSample: { time: bigint; usage: NodeJS.CpuUsage } | undefined
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 采样进程资源占用
|
|
132
|
+
*
|
|
133
|
+
* `cpu` 是**两次调用之间**的平均占用率,因此第一次调用必然返回 0。
|
|
134
|
+
* WebUI 的资源曲线按固定间隔轮询即可。
|
|
135
|
+
* @returns 资源占用快照
|
|
136
|
+
*/
|
|
137
|
+
export function sampleUsage(): ResourceUsage {
|
|
138
|
+
const mem = process.memoryUsage()
|
|
139
|
+
const now = process.hrtime.bigint()
|
|
140
|
+
const usage = process.cpuUsage()
|
|
141
|
+
|
|
142
|
+
let cpu = 0
|
|
143
|
+
if (lastCpuSample) {
|
|
144
|
+
const elapsedUs = Number(now - lastCpuSample.time) / 1000
|
|
145
|
+
if (elapsedUs > 0) {
|
|
146
|
+
const usedUs = usage.user - lastCpuSample.usage.user + (usage.system - lastCpuSample.usage.system)
|
|
147
|
+
// 除以核心数,让多核机器上的数值仍落在 0~1
|
|
148
|
+
cpu = Math.min(1, Math.max(0, usedUs / elapsedUs / detectPlatform().cpus))
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
lastCpuSample = { time: now, usage }
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
rss: mem.rss,
|
|
155
|
+
heapUsed: mem.heapUsed,
|
|
156
|
+
heapTotal: mem.heapTotal,
|
|
157
|
+
external: mem.external + (mem.arrayBuffers ?? 0),
|
|
158
|
+
uptime: Math.round(process.uptime()),
|
|
159
|
+
cpu: Number(cpu.toFixed(4))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 按内存档位给出建议的并发上限
|
|
165
|
+
*
|
|
166
|
+
* 渲染器、图片下载这类"每个任务几十 MB"的操作用它定并发,
|
|
167
|
+
* 而不是各自拍一个魔数。
|
|
168
|
+
* @param kind 任务类型:`render` 每任务约 80MB,`io` 约 2MB
|
|
169
|
+
* @returns 建议并发数,至少 1
|
|
170
|
+
*/
|
|
171
|
+
export function suggestConcurrency(kind: "render" | "io"): number {
|
|
172
|
+
const info = detectPlatform()
|
|
173
|
+
if (kind === "io") return info.lowMemory ? 4 : Math.min(16, info.cpus * 2)
|
|
174
|
+
// 渲染:低内存设备强制串行,避免 Chromium 多标签把内存打满
|
|
175
|
+
if (info.lowMemory) return 1
|
|
176
|
+
const byMemory = Math.floor(info.totalMemory / (1024 * 1024 * 1024))
|
|
177
|
+
return Math.max(1, Math.min(4, Math.min(byMemory - 1, info.cpus)))
|
|
178
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:确定并创建运行时目录布局
|
|
3
|
+
* 依赖方向:依赖 util/fs、platform/detect
|
|
4
|
+
* 生命周期:启动时解析一次,之后只读
|
|
5
|
+
* 注意事项:全程不使用 `process.cwd()` —— 以 Windows 服务、开机自启或 pm2 启动时
|
|
6
|
+
* 工作目录并非项目目录。启动时把八个目录一次性定死成绝对路径,
|
|
7
|
+
* 其余模块只准通过 `app.paths` 取。
|
|
8
|
+
*
|
|
9
|
+
* 优先级:显式参数 > `YZNG_HOME` 环境变量 > 便携模式标记 > 系统默认位置。
|
|
10
|
+
* 便携模式(安装目录下有 `.portable` 文件)让 ZIP 解压即用、
|
|
11
|
+
* 换机拷走整个文件夹就能迁移 —— Windows 用户最常见的诉求。
|
|
12
|
+
*/
|
|
13
|
+
import { homedir } from "node:os"
|
|
14
|
+
import { dirname, isAbsolute, join, resolve } from "node:path"
|
|
15
|
+
import { fileURLToPath } from "node:url"
|
|
16
|
+
import { existsSync } from "node:fs"
|
|
17
|
+
import type { RuntimePaths } from "@yunzai-ng/types"
|
|
18
|
+
import { ensureDir } from "../util/fs.js"
|
|
19
|
+
import { detectPlatform } from "./detect.js"
|
|
20
|
+
|
|
21
|
+
/** 便携模式标记文件名 */
|
|
22
|
+
const PORTABLE_MARKER = ".portable"
|
|
23
|
+
|
|
24
|
+
/** 应用目录名(系统默认位置下使用) */
|
|
25
|
+
const APP_DIR_NAME = "YunzaiNG"
|
|
26
|
+
|
|
27
|
+
/** 类 Unix 系统下的隐藏目录名 */
|
|
28
|
+
const APP_DIR_NAME_UNIX = ".yunzai-ng"
|
|
29
|
+
|
|
30
|
+
/** 解析目录布局的参数 */
|
|
31
|
+
export interface ResolvePathsOptions {
|
|
32
|
+
/** 显式指定数据根目录;相对路径按 `process.cwd()` 解析 */
|
|
33
|
+
home?: string
|
|
34
|
+
/** 内核包所在目录;缺省由 `import.meta.url` 推导 */
|
|
35
|
+
runtime?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 推导内核自身所在目录
|
|
40
|
+
*
|
|
41
|
+
* 编译后本文件位于 `<pkg>/dist/platform/paths.js`,上溯两级即包根。
|
|
42
|
+
* @returns 内核包根目录的绝对路径
|
|
43
|
+
*/
|
|
44
|
+
function detectRuntimeDir(): string {
|
|
45
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
46
|
+
return resolve(here, "..", "..")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 寻找便携模式标记
|
|
51
|
+
*
|
|
52
|
+
* 依次检查工作目录、内核包上两级(`node_modules/@yunzai-ng/core` → 安装根)。
|
|
53
|
+
* @param runtimeDir 内核包目录
|
|
54
|
+
* @returns 命中的便携根目录;未命中时 undefined
|
|
55
|
+
*/
|
|
56
|
+
function findPortableRoot(runtimeDir: string): string | undefined {
|
|
57
|
+
const candidates = [process.cwd(), resolve(runtimeDir, "..", "..", ".."), resolve(runtimeDir, "..", "..")]
|
|
58
|
+
for (const dir of candidates) {
|
|
59
|
+
if (existsSync(join(dir, PORTABLE_MARKER))) return dir
|
|
60
|
+
}
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 系统默认的数据根目录
|
|
66
|
+
* @returns 绝对路径
|
|
67
|
+
*/
|
|
68
|
+
function defaultHome(): string {
|
|
69
|
+
const info = detectPlatform()
|
|
70
|
+
const home = homedir()
|
|
71
|
+
|
|
72
|
+
if (info.os === "windows") {
|
|
73
|
+
const base = process.env.LOCALAPPDATA ?? process.env.APPDATA ?? join(home, "AppData", "Local")
|
|
74
|
+
return join(base, APP_DIR_NAME)
|
|
75
|
+
}
|
|
76
|
+
if (info.os === "macos") {
|
|
77
|
+
return join(home, "Library", "Application Support", APP_DIR_NAME)
|
|
78
|
+
}
|
|
79
|
+
if (info.os === "android") {
|
|
80
|
+
// Termux 的 $HOME 已经在应用私有目录里,再套 XDG 只会加深路径
|
|
81
|
+
return join(home, APP_DIR_NAME_UNIX)
|
|
82
|
+
}
|
|
83
|
+
const xdg = process.env.XDG_DATA_HOME
|
|
84
|
+
if (xdg && isAbsolute(xdg)) return join(xdg, "yunzai-ng")
|
|
85
|
+
return join(home, APP_DIR_NAME_UNIX)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 解析目录布局
|
|
90
|
+
*
|
|
91
|
+
* 只做路径计算,不创建目录 —— 便于测试与 `yzng doctor` 之类"只想看看
|
|
92
|
+
* 路径解析成什么"的场景。创建请调用 `ensurePaths`。
|
|
93
|
+
* @param opts 解析参数
|
|
94
|
+
* @returns 目录布局(全为绝对路径)
|
|
95
|
+
*/
|
|
96
|
+
export function resolvePaths(opts: ResolvePathsOptions = {}): RuntimePaths {
|
|
97
|
+
const runtime = opts.runtime ? resolve(opts.runtime) : detectRuntimeDir()
|
|
98
|
+
|
|
99
|
+
const explicit = opts.home ?? process.env.YZNG_HOME
|
|
100
|
+
const home = explicit ? resolve(process.cwd(), explicit) : (findPortableRoot(runtime) ?? defaultHome())
|
|
101
|
+
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
home,
|
|
104
|
+
config: join(home, "config"),
|
|
105
|
+
data: join(home, "data"),
|
|
106
|
+
logs: join(home, "logs"),
|
|
107
|
+
temp: join(home, "temp"),
|
|
108
|
+
plugins: join(home, "plugins"),
|
|
109
|
+
cache: join(home, "cache"),
|
|
110
|
+
runtime
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 创建目录布局里的所有目录
|
|
116
|
+
*
|
|
117
|
+
* `runtime` 不创建(它是安装目录,理应已存在且可能只读)。
|
|
118
|
+
* @param paths 目录布局
|
|
119
|
+
* @returns 传入的布局本身,便于链式使用
|
|
120
|
+
* @throws 目录无法创建时抛出(磁盘只读、权限不足),此时应终止启动
|
|
121
|
+
*/
|
|
122
|
+
export async function ensurePaths(paths: RuntimePaths): Promise<RuntimePaths> {
|
|
123
|
+
await Promise.all([
|
|
124
|
+
ensureDir(paths.home),
|
|
125
|
+
ensureDir(paths.config),
|
|
126
|
+
ensureDir(paths.data),
|
|
127
|
+
ensureDir(paths.logs),
|
|
128
|
+
ensureDir(paths.temp),
|
|
129
|
+
ensureDir(paths.plugins),
|
|
130
|
+
ensureDir(paths.cache)
|
|
131
|
+
])
|
|
132
|
+
return paths
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 在临时目录下生成一个带前缀的路径
|
|
137
|
+
*
|
|
138
|
+
* 用完请自行删除;`temp` 目录也会在启动时按保留期清理。
|
|
139
|
+
* @param paths 目录布局
|
|
140
|
+
* @param prefix 文件名前缀
|
|
141
|
+
* @param ext 扩展名(含点),如 `".png"`
|
|
142
|
+
* @param unique 唯一后缀,通常传 `randomId()`
|
|
143
|
+
* @returns 临时文件绝对路径
|
|
144
|
+
*/
|
|
145
|
+
export function tempFile(paths: RuntimePaths, prefix: string, ext: string, unique: string): string {
|
|
146
|
+
return join(paths.temp, `${prefix}-${unique}${ext}`)
|
|
147
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:`platform/system.ts` 的用例 —— 磁盘量测、nvidia-smi 解析与缓存
|
|
3
|
+
* 依赖方向:测试文件,依赖被测模块
|
|
4
|
+
* 生命周期:每个用例前清一次缓存
|
|
5
|
+
* 注意事项:**磁盘一节跑在真实文件系统上,不打桩。** 要验的恰是「`fs.statfs` 在这台
|
|
6
|
+
* 机器上给出什么」—— 把它打成桩就只是在验证桩自己,而 Windows 盘根与
|
|
7
|
+
* Linux 挂载点的行为差异全在真实文件系统一侧。因此断言只挑**与平台无关的
|
|
8
|
+
* 性质**(总量为正、可用不超过总量、已用与可用相加等于总量),不断言具体数值。
|
|
9
|
+
*
|
|
10
|
+
* GPU 一节反过来全部是纯函数解析:`nvidia-smi` 在 CI 与多数开发机上不存在,
|
|
11
|
+
* 用例若依赖它的存在就会在没有 N 卡的机器上红。故把「跑命令」与「解析输出」
|
|
12
|
+
* 分成两个函数,用例只验后者。
|
|
13
|
+
*/
|
|
14
|
+
import { beforeEach, describe, expect, it } from "vitest"
|
|
15
|
+
import { parseNvidiaSmi, probeDisks, resetSystemCache, sampleSystem } from "./system.js"
|
|
16
|
+
|
|
17
|
+
describe("系统探测", () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
resetSystemCache()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe("磁盘", () => {
|
|
23
|
+
it("至少探到一个分区,且每一项的数字自洽", async () => {
|
|
24
|
+
const disks = await probeDisks()
|
|
25
|
+
expect(disks.length).toBeGreaterThan(0)
|
|
26
|
+
for (const disk of disks) {
|
|
27
|
+
expect(disk.mount).not.toBe("")
|
|
28
|
+
expect(disk.total).toBeGreaterThan(0)
|
|
29
|
+
expect(disk.free).toBeGreaterThanOrEqual(0)
|
|
30
|
+
expect(disk.free).toBeLessThanOrEqual(disk.total)
|
|
31
|
+
// used 由 total - free 算出,故这条等式必然成立;验它是为了防止日后有人
|
|
32
|
+
// 改成各自独立采样 —— 那时两个数就会来自不同时刻,相加不再等于总量
|
|
33
|
+
expect(disk.used + disk.free).toBe(disk.total)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("挂载点不重复 —— bind mount 与同一盘的多次探测都不该各记一条", async () => {
|
|
38
|
+
const disks = await probeDisks()
|
|
39
|
+
expect(new Set(disks.map(d => d.mount)).size).toBe(disks.length)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe("nvidia-smi 输出解析", () => {
|
|
44
|
+
it("解析常规的一行", () => {
|
|
45
|
+
const gpus = parseNvidiaSmi("NVIDIA GeForce RTX 4090, 37, 2048, 24564\n")
|
|
46
|
+
expect(gpus).toEqual([
|
|
47
|
+
{ name: "NVIDIA GeForce RTX 4090", load: 0.37, memoryUsed: 2048 * 1024 * 1024, memoryTotal: 24564 * 1024 * 1024 }
|
|
48
|
+
])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("解析多块显卡", () => {
|
|
52
|
+
const gpus = parseNvidiaSmi("Tesla T4, 0, 12, 15360\nTesla T4, 99, 15000, 15360\n")
|
|
53
|
+
expect(gpus).toHaveLength(2)
|
|
54
|
+
expect(gpus[0]?.load).toBe(0)
|
|
55
|
+
expect(gpus[1]?.load).toBe(0.99)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("**`[N/A]` 的字段不出现,而不是记 0** —— 0% 会被读成「空闲」,而真相是「测不到」", () => {
|
|
59
|
+
const gpus = parseNvidiaSmi("NVIDIA A100, [N/A], 512, 40960\n")
|
|
60
|
+
expect(gpus[0]?.name).toBe("NVIDIA A100")
|
|
61
|
+
expect(gpus[0]).not.toHaveProperty("load")
|
|
62
|
+
expect(gpus[0]?.memoryUsed).toBe(512 * 1024 * 1024)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("占用率钳在 0~1 内 —— 驱动偶有报出 101 的记录", () => {
|
|
66
|
+
expect(parseNvidiaSmi("X, 101, 1, 2")[0]?.load).toBe(1)
|
|
67
|
+
expect(parseNvidiaSmi("X, -5, 1, 2")[0]?.load).toBe(0)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it("空输出与空行给出空数组,不抛错", () => {
|
|
71
|
+
expect(parseNvidiaSmi("")).toEqual([])
|
|
72
|
+
expect(parseNvidiaSmi("\n\n")).toEqual([])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("缺列的行只丢掉缺的那几项,型号仍在", () => {
|
|
76
|
+
const gpus = parseNvidiaSmi("NVIDIA T1000\n")
|
|
77
|
+
expect(gpus).toEqual([{ name: "NVIDIA T1000" }])
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe("快照", () => {
|
|
82
|
+
it("**测不到显卡时不出现 `gpus` 字段** —— 空数组会被读成「确实有 0 块」", async () => {
|
|
83
|
+
const snapshot = await sampleSystem(true)
|
|
84
|
+
// 本机有无 N 卡未知,故两种情形都接受,但「有这个字段且为空数组」不接受
|
|
85
|
+
if ("gpus" in snapshot) expect(snapshot.gpus?.length).toBeGreaterThan(0)
|
|
86
|
+
else expect(snapshot.gpus).toBeUndefined()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("缓存生效:连续两次取到同一个对象", async () => {
|
|
90
|
+
const first = await sampleSystem()
|
|
91
|
+
const second = await sampleSystem()
|
|
92
|
+
expect(second).toBe(first)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("force 绕过缓存,取到的是另一次采样", async () => {
|
|
96
|
+
const first = await sampleSystem()
|
|
97
|
+
const forced = await sampleSystem(true)
|
|
98
|
+
expect(forced).not.toBe(first)
|
|
99
|
+
expect(forced.disks.length).toBe(first.disks.length)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
})
|