Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
238 lines
7.0 KiB
JavaScript
238 lines
7.0 KiB
JavaScript
"use strict";
|
||
|
||
/**
|
||
* Windows 服务器采集器
|
||
*
|
||
* 采集指标:
|
||
* - cpu_usage CPU 使用率(%)
|
||
* - memory_usage 内存使用率(%)
|
||
* - disk_usage 磁盘使用率(%)
|
||
* - process_count 进程数
|
||
* - uptime 运行时间
|
||
* - service_status 关键服务状态
|
||
*
|
||
* 实现说明:
|
||
* 使用 PowerShell Base64 EncodedCommand 方式执行,与 NetMesh System Manager 一致。
|
||
* 传入 exec 函数执行远程命令(由调度器注入)。
|
||
*/
|
||
|
||
const BaseAdapter = require("./baseAdapter.cjs");
|
||
|
||
class WindowsServerAdapter extends BaseAdapter {
|
||
constructor(connectionInfo, options = {}) {
|
||
super(connectionInfo, options);
|
||
this.adapterName = "windows_server";
|
||
if (options.execFn) {
|
||
this.execute = options.execFn;
|
||
}
|
||
}
|
||
|
||
getSupportedMetrics() {
|
||
return [
|
||
"cpu_usage",
|
||
"memory_usage",
|
||
"disk_usage",
|
||
"process_count",
|
||
"uptime",
|
||
"service_status",
|
||
];
|
||
}
|
||
|
||
async collectMetrics() {
|
||
const metrics = {};
|
||
const raw = {};
|
||
const DEBUG = process.env.INSPECTION_DEBUG === "1";
|
||
|
||
// 1. CPU + 内存 + 运行时间(快速,独立执行)
|
||
try {
|
||
const script = this._buildFastStatsScript();
|
||
const cmd = this._buildPowerShellCmd(script);
|
||
if (DEBUG) console.log("[WinAdapter] 执行命令:", cmd);
|
||
raw.stats = await this.execute(cmd);
|
||
if (DEBUG) console.log("[WinAdapter] 原始输出:", raw.stats);
|
||
const parsed = this._parseStats(raw.stats);
|
||
if (DEBUG) console.log("[WinAdapter] 解析结果:", parsed);
|
||
Object.assign(metrics, parsed);
|
||
} catch (e) {
|
||
raw.stats_error = e.message;
|
||
if (DEBUG) console.error("[WinAdapter] 采集失败:", e.message);
|
||
}
|
||
|
||
// 2. 磁盘使用率(较慢,单独执行,超时放宽到 30s)
|
||
try {
|
||
const diskScript = this._buildDiskScript();
|
||
const diskCmd = this._buildPowerShellCmd(diskScript);
|
||
if (DEBUG) console.log("[WinAdapter] 磁盘命令:", diskCmd);
|
||
raw.disk = await this.execute(diskCmd);
|
||
if (DEBUG) console.log("[WinAdapter] 磁盘原始输出:", raw.disk);
|
||
metrics.disk_usage = this._parseDisk(raw.disk);
|
||
} catch (e) {
|
||
raw.disk_error = e.message;
|
||
if (DEBUG) console.error("[WinAdapter] 磁盘采集失败:", e.message);
|
||
}
|
||
|
||
// 3. 关键服务状态
|
||
try {
|
||
const serviceScript = this._buildServiceScript();
|
||
const cmd = this._buildPowerShellCmd(serviceScript);
|
||
if (DEBUG) console.log("[WinAdapter] 服务命令:", cmd);
|
||
raw.services = await this.execute(cmd);
|
||
if (DEBUG) console.log("[WinAdapter] 服务原始输出:", raw.services);
|
||
metrics.service_status = this._parseServices(raw.services);
|
||
} catch (e) {
|
||
metrics.service_status = [];
|
||
raw.services_error = e.message;
|
||
if (DEBUG) console.error("[WinAdapter] 服务采集失败:", e.message);
|
||
}
|
||
|
||
return metrics;
|
||
}
|
||
|
||
/**
|
||
* 快速统计:CPU + 内存(使用更快的 WMI 查询)
|
||
*/
|
||
_buildFastStatsScript() {
|
||
return `
|
||
try { $cpu = (Get-WmiObject Win32_Processor).LoadPercentage; if (-not $cpu) { $cpu = 0 } } catch { $cpu = 0 }
|
||
try { $os = Get-WmiObject Win32_OperatingSystem; $total = [math]::Round($os.TotalVisibleMemorySize/1MB,0); $free = [math]::Round($os.FreePhysicalMemory/1MB,0); $usage = if ($total -gt 0) { [math]::Round(($total-$free)/$total*100,1) } else { 0 } } catch { $total = 0; $usage = 0; $free = 0 }
|
||
@{ cpu = $cpu; mem_usage = $usage; mem_total = $total; mem_free = $free } | ConvertTo-Json -Compress
|
||
`.trim();
|
||
}
|
||
|
||
/** 磁盘使用率(只查 C: 盘) */
|
||
_buildDiskScript() {
|
||
return `
|
||
try {
|
||
$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'"
|
||
if ($disk -and $disk.Size -gt 0) {
|
||
$s = [math]::Round($disk.Size/1GB,1); $f = [math]::Round($disk.FreeSpace/1GB,1); $u = [math]::Round(($disk.Size-$disk.FreeSpace)/$disk.Size*100,1)
|
||
@{ d='C:'; s=$s; f=$f; u=$u } | ConvertTo-Json -Compress
|
||
} else { 'null' }
|
||
} catch { 'null' }
|
||
`.trim();
|
||
}
|
||
|
||
_parseDisk(stdout) {
|
||
try {
|
||
const json = this._extractJson(stdout);
|
||
if (!json || json === "null" || json === null) return [];
|
||
return [{
|
||
drive: json.d || "C:",
|
||
size_gb: json.s || 0,
|
||
free_gb: json.f || 0,
|
||
usage_percent: json.u || 0,
|
||
}];
|
||
} catch {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构建 PowerShell 命令:UTF-16LE Base64 编码 + -EncodedCommand
|
||
* Node.js Buffer.from(str, "utf16le") 是最可靠的编码方式
|
||
*/
|
||
_buildPowerShellCmd(script) {
|
||
// 用 Node.js 原生 Buffer 进行 UTF-16LE 编码,再转 Base64
|
||
// UTF-16LE 编码(不带 BOM),然后 Base64
|
||
const utf16le = Buffer.from(script, "utf16le");
|
||
const encoded = utf16le.toString("base64");
|
||
return `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
|
||
}
|
||
|
||
_buildServiceScript() {
|
||
return `
|
||
$ErrorActionPreference = 'SilentlyContinue'
|
||
|
||
# 关键服务
|
||
$services = @(
|
||
"wuauserv", # Windows Update
|
||
"Spooler", # Print Spooler
|
||
"TermService", # Remote Desktop
|
||
"DHCP", # DHCP Client
|
||
"DNScache" # DNS Client
|
||
)
|
||
|
||
$results = foreach ($svc in $services) {
|
||
$s = Get-Service -Name $svc -ErrorAction SilentlyContinue
|
||
if ($s) {
|
||
[PSCustomObject]@{
|
||
name = $s.Name
|
||
display_name = $s.DisplayName
|
||
status = $s.Status.ToString()
|
||
start_type = $s.StartType.ToString()
|
||
}
|
||
}
|
||
}
|
||
|
||
$results | ConvertTo-Json -Depth 3
|
||
`.trim();
|
||
}
|
||
|
||
_parseStats(stdout) {
|
||
try {
|
||
const json = this._extractJson(stdout);
|
||
if (!json) return {};
|
||
|
||
const result = {};
|
||
if (json.cpu !== undefined) result.cpu_usage = json.cpu;
|
||
if (json.mem_usage !== undefined) result.memory_usage = json.mem_usage;
|
||
if (json.mem_total !== undefined && json.mem_total > 0) {
|
||
result.memory = {
|
||
total_mb: json.mem_total,
|
||
used_mb: Math.round((json.mem_usage / 100) * json.mem_total),
|
||
};
|
||
}
|
||
|
||
return result;
|
||
} catch (e) {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
_parseServices(stdout) {
|
||
try {
|
||
const json = this._extractJson(stdout);
|
||
if (!json) return [];
|
||
return Array.isArray(json) ? json : [json];
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从 PowerShell 输出中提取 JSON(可能有多余的警告/文本)
|
||
*/
|
||
_extractJson(text) {
|
||
if (!text) return null;
|
||
const start = text.indexOf("{");
|
||
const startArr = text.indexOf("[");
|
||
const realStart = startArr === -1 ? start : (start === -1 ? startArr : Math.min(start, startArr));
|
||
if (realStart === -1) return null;
|
||
|
||
let depth = 0;
|
||
let end = -1;
|
||
const open = text[realStart];
|
||
const close = open === "{" ? "}" : "]";
|
||
|
||
for (let i = realStart; i < text.length; i++) {
|
||
if (text[i] === open) depth++;
|
||
else if (text[i] === close) {
|
||
depth--;
|
||
if (depth === 0) {
|
||
end = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (end === -1) return null;
|
||
try {
|
||
return JSON.parse(text.substring(realStart, end + 1));
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = WindowsServerAdapter;
|