Files
SenMeshNet/electron/bridges/inspection/adapters/windowsServerAdapter.cjs
zhaolei 2f9f8da48e
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
Initial commit: NetMesh smart ops platform with 3D topology, inspection reports, RCA and AI ops assistant
2026-10-15 13:53:04 +08:00

238 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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;