079project 1.0.0 → 3.0.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.
@@ -0,0 +1,202 @@
1
+ /* 日志子进程:接收主进程发来的 IO 记录与进程注册信息,按周期采样资源并输出文本表格 */
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { exec } = require('child_process');
6
+
7
+ const LOG_DIR = path.join(__dirname, 'logs');
8
+ fs.mkdirSync(LOG_DIR, { recursive: true });
9
+
10
+ let watch = new Map(); // key: port -> { pid, groupId, role, port }
11
+ let prevSample = new Map(); // pid -> { cpuSec, readBytes, writeBytes, ts }
12
+ let lastMetricsWriteHour = '';
13
+ let lastIoWriteHour = '';
14
+ let sampleIntervalMs = 5000;
15
+ let cores = os.cpus().length;
16
+
17
+ // 工具:获取当前小时文件名
18
+ function hourSuffix() {
19
+ const d = new Date();
20
+ const y = d.getFullYear();
21
+ const m = String(d.getMonth() + 1).padStart(2, '0');
22
+ const dd = String(d.getDate()).padStart(2, '0');
23
+ const hh = String(d.getHours()).padStart(2, '0');
24
+ return `${y}${m}${dd}_${hh}`;
25
+ }
26
+ function ioLogPath() { return path.join(LOG_DIR, `io_${hourSuffix()}.log`); }
27
+ function metricsLogPath() { return path.join(LOG_DIR, `metrics_${hourSuffix()}.txt`); }
28
+
29
+ function appendLine(file, line) {
30
+ fs.appendFile(file, line + '\n', () => {});
31
+ }
32
+
33
+ function writeIo(record) {
34
+ const nowHour = hourSuffix();
35
+ if (lastIoWriteHour !== nowHour) lastIoWriteHour = nowHour;
36
+ const file = ioLogPath();
37
+ appendLine(file, JSON.stringify(record));
38
+ }
39
+ function getProcInfoPowershell(pids) {
40
+ return new Promise((resolve) => {
41
+ if (!pids.length) return resolve([]);
42
+ const pidList = pids.join(',');
43
+ const cmd = [
44
+ 'powershell.exe -NoProfile -Command',
45
+ `"Get-Process -Id ${pidList} | Select-Object Id,CPU,WorkingSet64,Name,`,
46
+ 'IOReadBytes,IOWriteBytes | ConvertTo-Json -Compress"'
47
+ ].join(' ');
48
+ exec(cmd, { windowsHide: true, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
49
+ if (err || !stdout) return resolve([]);
50
+ try {
51
+ const data = JSON.parse(stdout);
52
+ resolve(Array.isArray(data) ? data : [data]);
53
+ } catch {
54
+ resolve([]);
55
+ }
56
+ });
57
+ });
58
+ }
59
+
60
+ function getTcpConnCounts() {
61
+ return new Promise((resolve) => {
62
+ // 使用 netstat -ano 统计 Owning PID 的 TCP 连接数量
63
+ exec('netstat -ano', { windowsHide: true, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
64
+ if (err || !stdout) return resolve(new Map());
65
+ const lines = stdout.split(/\r?\n/).filter(Boolean);
66
+ const map = new Map(); // pid -> count
67
+ for (const line of lines) {
68
+ // Proto Local Address Foreign Address State PID
69
+ const parts = line.trim().split(/\s+/);
70
+ if (parts.length < 5) continue;
71
+ const pid = Number(parts[parts.length - 1]);
72
+ if (!Number.isFinite(pid)) continue;
73
+ map.set(pid, (map.get(pid) || 0) + 1);
74
+ }
75
+ resolve(map);
76
+ });
77
+ });
78
+ }
79
+
80
+ function getListeningPortsByPid() {
81
+ return new Promise((resolve) => {
82
+ exec('netstat -ano -p TCP', { windowsHide: true, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
83
+ const map = new Map(); // pid -> Set(ports)
84
+ if (err || !stdout) return resolve(map);
85
+ const lines = stdout.split(/\r?\n/).filter(Boolean);
86
+ for (const line of lines) {
87
+ const parts = line.trim().split(/\s+/);
88
+ if (parts.length < 5) continue;
89
+ const state = parts[3] || parts[4] || '';
90
+ const pid = Number(parts[parts.length - 1]);
91
+ if (!Number.isFinite(pid)) continue;
92
+ const local = parts[1] || '';
93
+ const port = Number(local.split(':').pop());
94
+ if (!Number.isFinite(port)) continue;
95
+ if (!map.has(pid)) map.set(pid, new Set());
96
+ if (state.toUpperCase().includes('LISTEN')) {
97
+ map.get(pid).add(port);
98
+ }
99
+ }
100
+ resolve(map);
101
+ });
102
+ });
103
+ }
104
+
105
+ async function sampleAndWrite() {
106
+ try {
107
+ const ports = Array.from(watch.keys());
108
+ const procs = Array.from(watch.values());
109
+ const pids = Array.from(new Set(procs.map(p => p.pid))).filter(Boolean);
110
+
111
+ const [procInfo, connCounts, listenMap] = await Promise.all([
112
+ getProcInfoPowershell(pids),
113
+ getTcpConnCounts(),
114
+ getListeningPortsByPid()
115
+ ]);
116
+
117
+ const infoByPid = new Map();
118
+ for (const it of procInfo) {
119
+ infoByPid.set(Number(it.Id), {
120
+ cpuSec: Number(it.CPU || 0),
121
+ rss: Number(it.WorkingSet64 || 0),
122
+ ioRead: Number(it.IOReadBytes || 0),
123
+ ioWrite: Number(it.IOWriteBytes || 0),
124
+ name: it.Name || ''
125
+ });
126
+ }
127
+
128
+ const nowTs = Date.now();
129
+ const rows = [];
130
+ let aliveCount = 0;
131
+
132
+ for (const port of ports) {
133
+ const { pid, role, groupId } = watch.get(port);
134
+ const info = infoByPid.get(pid) || { cpuSec: 0, rss: 0, ioRead: 0, ioWrite: 0, name: '' };
135
+ const prev = prevSample.get(pid) || { cpuSec: info.cpuSec, readBytes: info.ioRead, writeBytes: info.ioWrite, ts: nowTs };
136
+ const dt = Math.max(1, (nowTs - prev.ts) / 1000); // sec
137
+ const dCpu = Math.max(0, info.cpuSec - prev.cpuSec); // sec
138
+ const cpuPct = Math.min(100, (dCpu / dt) * (100 / Math.max(1, 1 / cores))); // 近似
139
+ const dRead = Math.max(0, info.ioRead - prev.readBytes);
140
+ const dWrite = Math.max(0, info.ioWrite - prev.writeBytes);
141
+ const conn = connCounts.get(pid) || 0;
142
+ const rssMb = (info.rss / 1024 / 1024).toFixed(1);
143
+ const rps = (dRead / dt) | 0;
144
+ const wps = (dWrite / dt) | 0;
145
+ const listeningPorts = listenMap.get(pid) || new Set();
146
+ const up = listeningPorts.has(port) ? 'Y' : 'N';
147
+ if (up === 'Y') aliveCount++;
148
+
149
+ rows.push({
150
+ port,
151
+ pid,
152
+ role,
153
+ groupId,
154
+ cpu: cpuPct.toFixed(1),
155
+ mem: rssMb,
156
+ conn,
157
+ rps,
158
+ wps,
159
+ up
160
+ });
161
+
162
+ prevSample.set(pid, { cpuSec: info.cpuSec, readBytes: info.ioRead, writeBytes: info.ioWrite, ts: nowTs });
163
+ }
164
+
165
+ // 输出文本表
166
+ const nowHour = hourSuffix();
167
+ if (lastMetricsWriteHour !== nowHour) lastMetricsWriteHour = nowHour;
168
+ const file = metricsLogPath();
169
+ const header = `# ${new Date().toISOString()} AlivePorts=${aliveCount}/${ports.length}\n` +
170
+ 'PORT PID ROLE GID CPU% MEM(MB) CONN DiskR/s DiskW/s UP\n' +
171
+ '----- ------- --------- --- ---- ------- ---- ------- ------- --';
172
+ const lines = rows
173
+ .sort((a, b) => a.port - b.port)
174
+ .map(r =>
175
+ `${String(r.port).padEnd(5)} ${String(r.pid).padEnd(7)} ${String(r.role).padEnd(9)} ${String(r.groupId).padEnd(3)} ${String(r.cpu).padStart(4)} ${String(r.mem).padStart(7)} ${String(r.conn).padStart(4)} ${String(r.rps).padStart(7)} ${String(r.wps).padStart(7)} ${r.up}`
176
+ );
177
+ appendLine(file, [header, ...lines, ''].join('\n'));
178
+ } catch (e) {
179
+ // 忽略单次采样错误
180
+ }
181
+ }
182
+
183
+ process.on('message', (msg) => {
184
+ if (!msg || typeof msg !== 'object') return;
185
+ if (msg.type === 'register' && Array.isArray(msg.processes)) {
186
+ for (const p of msg.processes) {
187
+ if (p && p.port) {
188
+ watch.set(p.port, { pid: p.pid, role: p.role, groupId: p.groupId, port: p.port });
189
+ }
190
+ }
191
+ } else if (msg.type === 'update' && msg.port) {
192
+ const old = watch.get(msg.port) || {};
193
+ watch.set(msg.port, { ...old, ...msg });
194
+ } else if (msg.type === 'io') {
195
+ // { ts, reqId, input, selectedPorts, responses[] }
196
+ writeIo(msg);
197
+ } else if (msg.type === 'config' && msg.sampleIntervalMs) {
198
+ sampleIntervalMs = Math.max(1000, Number(msg.sampleIntervalMs) || sampleIntervalMs);
199
+ }
200
+ });
201
+
202
+ setInterval(sampleAndWrite, sampleIntervalMs);