dashcam 1.4.5-beta.0 → 1.4.9-beta

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,184 @@
1
+ # Linux Performance Tracking Fixes
2
+
3
+ ## Problem
4
+ Performance data from dashcam-cli-minimal was not working on Ubuntu Linux.
5
+
6
+ ## Root Causes Identified
7
+
8
+ ### 1. **ps command compatibility**
9
+ - Some Linux distributions (especially minimal/container images) use BusyBox `ps` which doesn't support the `--sort` option
10
+ - The code was failing when trying to use `ps --sort=-pcpu`
11
+
12
+ ### 2. **Missing error handling**
13
+ - Insufficient logging made it hard to diagnose where the performance tracking was failing
14
+ - No fallback mechanisms when certain system calls failed
15
+
16
+ ### 3. **Network metrics parsing**
17
+ - The macOS netstat parsing had an off-by-one error in the parts length check
18
+ - Could cause network metrics to fail silently
19
+
20
+ ### 4. **Import optimization**
21
+ - The Linux network code was using `await import('fs')` dynamically when `fs` was already imported at the top
22
+
23
+ ## Fixes Applied
24
+
25
+ ### topProcesses.js
26
+ 1. **Added fallback for ps --sort**:
27
+ ```javascript
28
+ try {
29
+ // Try with --sort first
30
+ const result = await execFileAsync('ps', ['-eo', 'pid,pcpu,pmem,comm', '--sort=-pcpu'], ...);
31
+ } catch (sortError) {
32
+ // Fallback to unsorted and sort in JavaScript
33
+ const result = await execFileAsync('ps', ['-eo', 'pid,pcpu,pmem,comm'], ...);
34
+ }
35
+ ```
36
+
37
+ 2. **Improved parsing with filtering**:
38
+ - Now filters out invalid entries (pid <= 0)
39
+ - Handles missing/undefined CPU and memory values
40
+ - Always sorts by CPU in JavaScript as a backup
41
+
42
+ 3. **Added timeouts**:
43
+ - All process listings now have 5-10 second timeouts to prevent hanging
44
+
45
+ ### performanceTracker.js
46
+ 1. **Fixed network metrics**:
47
+ - Corrected macOS netstat parsing (parts.length >= 10 instead of >= 7)
48
+ - Removed redundant `await import('fs')` on Linux
49
+ - Already using `fs` from the top-level import
50
+
51
+ 2. **Enhanced error handling**:
52
+ - All metric collection methods now have comprehensive try/catch blocks
53
+ - Graceful degradation - if one metric fails, others continue
54
+ - Better logging with platform and error context
55
+
56
+ 3. **Improved logging**:
57
+ - Added debug logs for successful operations
58
+ - More detailed error messages including platform info
59
+ - Stack traces for debugging
60
+
61
+ ## Testing
62
+
63
+ ### Quick Test (10 seconds)
64
+ ```bash
65
+ cd dashcam-cli-minimal
66
+ node test-perf-linux.js
67
+ ```
68
+
69
+ This will:
70
+ - Test `pidusage` library
71
+ - Test `getTopProcesses` function
72
+ - Test network metrics reading
73
+ - Test system metrics
74
+ - Run full performance tracker for 10 seconds
75
+ - Show detailed results and identify any failures
76
+
77
+ ### Full Recording Test
78
+ ```bash
79
+ # Start a recording with performance tracking
80
+ dashcam start
81
+
82
+ # Do some work for 10-15 seconds
83
+ # ...
84
+
85
+ # Stop recording
86
+ dashcam stop
87
+
88
+ # Check the output for performance data
89
+ ```
90
+
91
+ ### Expected Output
92
+ The test should show:
93
+ ```
94
+ ✓ pidusage: PASS
95
+ ✓ topProcesses: PASS
96
+ ✓ networkMetrics: PASS
97
+ ✓ systemMetrics: PASS
98
+ ✓ performanceTracker: PASS
99
+
100
+ Overall: ✓ ALL TESTS PASSED
101
+ ```
102
+
103
+ ## Platform-Specific Notes
104
+
105
+ ### Ubuntu/Debian
106
+ - Should work on all versions
107
+ - Uses `/proc/net/dev` for network stats
108
+ - Falls back if `ps --sort` not available
109
+
110
+ ### Alpine Linux / Docker
111
+ - BusyBox `ps` detected automatically
112
+ - Sorts processes in JavaScript instead
113
+ - May have limited network stats in containers
114
+
115
+ ### CentOS/RHEL
116
+ - Full GNU ps support
117
+ - Should use `--sort` option
118
+ - Full network stats available
119
+
120
+ ## Troubleshooting
121
+
122
+ ### If performance data is still empty:
123
+
124
+ 1. **Check pidusage works**:
125
+ ```bash
126
+ node -e "import('pidusage').then(m => m.default(process.pid).then(console.log))"
127
+ ```
128
+
129
+ 2. **Check ps command**:
130
+ ```bash
131
+ ps -eo pid,pcpu,pmem,comm --sort=-pcpu | head -5
132
+ # If this fails, try:
133
+ ps -eo pid,pcpu,pmem,comm | head -5
134
+ ```
135
+
136
+ 3. **Check /proc/net/dev exists**:
137
+ ```bash
138
+ cat /proc/net/dev
139
+ ```
140
+
141
+ 4. **Run with verbose logging**:
142
+ ```bash
143
+ dashcam start --verbose
144
+ # ... do work ...
145
+ dashcam stop --verbose
146
+ ```
147
+
148
+ 5. **Check the performance file directly**:
149
+ ```bash
150
+ # Look for performance.jsonl in the output directory
151
+ cat ~/.dashcam/recordings/*/performance.jsonl | head -1 | jq
152
+ ```
153
+
154
+ ## What Gets Tracked
155
+
156
+ Even with the fixes, the following is tracked every 5 seconds:
157
+
158
+ - ✅ Process CPU usage (dashcam process)
159
+ - ✅ Process memory usage (dashcam process)
160
+ - ✅ System-wide memory usage
161
+ - ✅ System CPU core count
162
+ - ✅ Top 10 processes by CPU
163
+ - ✅ Network I/O (where available)
164
+
165
+ ## API Upload
166
+
167
+ The performance data is uploaded to the API with the recording:
168
+
169
+ ```javascript
170
+ {
171
+ performance: {
172
+ samples: [...], // Array of samples taken during recording
173
+ summary: { // Aggregated statistics
174
+ avgProcessCPU: 12.3,
175
+ maxProcessCPU: 18.7,
176
+ avgProcessMemoryMB: 128.0,
177
+ maxProcessMemoryMB: 192.0,
178
+ // ...
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ This data is then displayed in the web UI under the "Performance" tab.
@@ -1,7 +1,7 @@
1
1
  import os from 'os';
2
2
  import { logger } from './logger.js';
3
3
  import pidusage from 'pidusage';
4
- import { getTopProcesses } from './topProcesses.js';
4
+ import { getTopProcesses, cleanupPowerShell } from './topProcesses.js';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
 
@@ -40,7 +40,7 @@ class PerformanceTracker {
40
40
 
41
41
  for (const line of lines) {
42
42
  const parts = line.trim().split(/\s+/);
43
- if (parts.length >= 7 && parts[0] !== 'Name') {
43
+ if (parts.length >= 10 && parts[0] !== 'Name') {
44
44
  const ibytes = parseInt(parts[6]) || 0;
45
45
  const obytes = parseInt(parts[9]) || 0;
46
46
  totalBytesReceived += ibytes;
@@ -52,7 +52,6 @@ class PerformanceTracker {
52
52
  }
53
53
  } else if (process.platform === 'linux') {
54
54
  // Linux - read from /proc/net/dev
55
- const fs = await import('fs');
56
55
  try {
57
56
  const netDev = fs.readFileSync('/proc/net/dev', 'utf8');
58
57
  const lines = netDev.split('\n');
@@ -157,6 +156,13 @@ class PerformanceTracker {
157
156
  async getProcessMetrics() {
158
157
  try {
159
158
  const stats = await pidusage(this.pid);
159
+
160
+ logger.debug('Process metrics collected', {
161
+ cpu: stats.cpu?.toFixed(2),
162
+ memoryMB: (stats.memory / (1024 * 1024)).toFixed(1),
163
+ pid: stats.pid
164
+ });
165
+
160
166
  return {
161
167
  process: {
162
168
  cpu: stats.cpu, // CPU usage percentage
@@ -169,7 +175,11 @@ class PerformanceTracker {
169
175
  }
170
176
  };
171
177
  } catch (error) {
172
- logger.warn('Failed to get process metrics', { error: error.message });
178
+ logger.warn('Failed to get process metrics', {
179
+ error: error.message,
180
+ pid: this.pid,
181
+ platform: process.platform
182
+ });
173
183
  return {
174
184
  process: {
175
185
  cpu: 0,
@@ -189,12 +199,21 @@ class PerformanceTracker {
189
199
  const topProcs = await getTopProcesses(10);
190
200
 
191
201
  if (!topProcs || topProcs.length === 0) {
202
+ logger.debug('No top processes returned', {
203
+ platform: process.platform
204
+ });
192
205
  return {
193
206
  topProcesses: [],
194
207
  totalProcesses: 0
195
208
  };
196
209
  }
197
210
 
211
+ logger.debug('Top processes fetched', {
212
+ count: topProcs.length,
213
+ platform: process.platform,
214
+ firstProcess: topProcs[0]?.name
215
+ });
216
+
198
217
  // Get detailed stats using pidusage for each process
199
218
  const detailedStats = [];
200
219
  for (const proc of topProcs) {
@@ -213,6 +232,7 @@ class PerformanceTracker {
213
232
  // Process might have exited, use basic data from ps/PowerShell
214
233
  logger.debug('Failed to get detailed stats for process, using basic data', {
215
234
  pid: proc.pid,
235
+ name: proc.name,
216
236
  error: error.message
217
237
  });
218
238
  detailedStats.push({
@@ -233,7 +253,11 @@ class PerformanceTracker {
233
253
  totalProcesses: topProcs.length
234
254
  };
235
255
  } catch (error) {
236
- logger.warn('Failed to get top processes', { error: error.message });
256
+ logger.warn('Failed to get top processes', {
257
+ error: error.message,
258
+ platform: process.platform,
259
+ stack: error.stack
260
+ });
237
261
  return {
238
262
  topProcesses: [],
239
263
  totalProcesses: 0
@@ -478,6 +502,11 @@ class PerformanceTracker {
478
502
  }
479
503
  }
480
504
  this.performanceFile = null;
505
+
506
+ // Cleanup PowerShell instance on Windows
507
+ if (process.platform === 'win32') {
508
+ cleanupPowerShell();
509
+ }
481
510
  }
482
511
  }
483
512
 
@@ -1,25 +1,186 @@
1
- import { execFile } from 'child_process';
1
+ import { execFile, spawn } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import os from 'os';
4
4
  import { logger } from './logger.js';
5
5
 
6
6
  const execFileAsync = promisify(execFile);
7
7
 
8
+ // Persistent PowerShell instance for Windows
9
+ let persistentPowerShell = null;
10
+ let psCommandQueue = [];
11
+ let psProcessing = false;
12
+
13
+ /**
14
+ * Initialize a persistent PowerShell instance for Windows
15
+ */
16
+ function initPersistentPowerShell() {
17
+ if (persistentPowerShell) {
18
+ return persistentPowerShell;
19
+ }
20
+
21
+ logger.debug('Initializing persistent PowerShell instance');
22
+
23
+ const ps = spawn('powershell.exe', [
24
+ '-NoLogo',
25
+ '-NoProfile',
26
+ '-NonInteractive',
27
+ '-WindowStyle', 'Hidden',
28
+ '-Command', '-'
29
+ ], {
30
+ windowsHide: true,
31
+ stdio: ['pipe', 'pipe', 'pipe']
32
+ });
33
+
34
+ let outputBuffer = '';
35
+ const DELIMITER = '---END-OF-COMMAND---';
36
+
37
+ ps.stdout.on('data', (data) => {
38
+ outputBuffer += data.toString();
39
+
40
+ // Check if we have a complete response
41
+ const delimiterIndex = outputBuffer.indexOf(DELIMITER);
42
+ if (delimiterIndex !== -1) {
43
+ const output = outputBuffer.substring(0, delimiterIndex);
44
+ outputBuffer = outputBuffer.substring(delimiterIndex + DELIMITER.length);
45
+
46
+ // Resolve the pending command
47
+ if (psCommandQueue.length > 0) {
48
+ const { resolve } = psCommandQueue.shift();
49
+ resolve(output);
50
+ psProcessing = false;
51
+ processNextCommand();
52
+ }
53
+ }
54
+ });
55
+
56
+ ps.stderr.on('data', (data) => {
57
+ logger.debug('PowerShell stderr:', data.toString());
58
+ });
59
+
60
+ ps.on('close', (code) => {
61
+ logger.debug('PowerShell process closed', { code });
62
+ persistentPowerShell = null;
63
+ // Reject all pending commands
64
+ while (psCommandQueue.length > 0) {
65
+ const { reject } = psCommandQueue.shift();
66
+ reject(new Error('PowerShell process closed'));
67
+ }
68
+ });
69
+
70
+ ps.on('error', (error) => {
71
+ logger.warn('PowerShell process error', { error: error.message });
72
+ persistentPowerShell = null;
73
+ });
74
+
75
+ persistentPowerShell = ps;
76
+ persistentPowerShell.delimiter = DELIMITER;
77
+
78
+ return ps;
79
+ }
80
+
81
+ /**
82
+ * Process the next command in the queue
83
+ */
84
+ function processNextCommand() {
85
+ if (psProcessing || psCommandQueue.length === 0) {
86
+ return;
87
+ }
88
+
89
+ psProcessing = true;
90
+ const { command } = psCommandQueue[0];
91
+
92
+ try {
93
+ persistentPowerShell.stdin.write(command + '\n');
94
+ persistentPowerShell.stdin.write(`Write-Host '${persistentPowerShell.delimiter}'\n`);
95
+ } catch (error) {
96
+ logger.warn('Failed to write to PowerShell stdin', { error: error.message });
97
+ const { reject } = psCommandQueue.shift();
98
+ reject(error);
99
+ psProcessing = false;
100
+ processNextCommand();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Execute a command in the persistent PowerShell instance
106
+ */
107
+ function execPowerShellCommand(command, timeout = 10000) {
108
+ return new Promise((resolve, reject) => {
109
+ const ps = initPersistentPowerShell();
110
+
111
+ const timeoutId = setTimeout(() => {
112
+ reject(new Error('PowerShell command timeout'));
113
+ }, timeout);
114
+
115
+ psCommandQueue.push({
116
+ command,
117
+ resolve: (output) => {
118
+ clearTimeout(timeoutId);
119
+ resolve(output);
120
+ },
121
+ reject: (error) => {
122
+ clearTimeout(timeoutId);
123
+ reject(error);
124
+ }
125
+ });
126
+
127
+ processNextCommand();
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Cleanup the persistent PowerShell instance
133
+ */
134
+ export function cleanupPowerShell() {
135
+ if (persistentPowerShell) {
136
+ logger.debug('Cleaning up persistent PowerShell instance');
137
+ try {
138
+ persistentPowerShell.stdin.end();
139
+ persistentPowerShell.kill();
140
+ } catch (error) {
141
+ logger.debug('Error cleaning up PowerShell', { error: error.message });
142
+ }
143
+ persistentPowerShell = null;
144
+ }
145
+ }
146
+
8
147
  /**
9
148
  * Parse ps output format: " PID %CPU %MEM COMMAND"
10
149
  */
11
150
  function parsePsOutput(stdout, limit) {
151
+ logger.debug('Parsing ps output', {
152
+ outputLength: stdout.length,
153
+ firstLine: stdout.split('\n')[0],
154
+ lineCount: stdout.split('\n').length
155
+ });
156
+
12
157
  const lines = stdout.trim().split('\n');
158
+
159
+ if (lines.length === 0) {
160
+ logger.warn('ps output is empty');
161
+ return [];
162
+ }
163
+
13
164
  // Skip header line
14
- return lines.slice(1, 1 + limit).map(line => {
165
+ const processes = lines.slice(1).map(line => {
15
166
  const parts = line.trim().split(/\s+/, 4);
16
167
  return {
17
168
  pid: Number(parts[0]),
18
- cpu: Number(parts[1]),
19
- mem: Number(parts[2]),
169
+ cpu: Number(parts[1]) || 0,
170
+ mem: Number(parts[2]) || 0,
20
171
  name: parts[3] || ''
21
172
  };
173
+ }).filter(proc => proc.pid > 0); // Filter out invalid entries
174
+
175
+ logger.debug('Parsed processes', {
176
+ count: processes.length,
177
+ sample: processes.slice(0, 3)
22
178
  });
179
+
180
+ // Sort by CPU descending (in case ps doesn't support --sort)
181
+ processes.sort((a, b) => b.cpu - a.cpu);
182
+
183
+ return processes.slice(0, limit);
23
184
  }
24
185
 
25
186
  /**
@@ -32,24 +193,67 @@ async function getTopProcessesUnix(limit = 10) {
32
193
  if (os.platform() === 'darwin') {
33
194
  // macOS uses BSD ps - different syntax, no --sort option
34
195
  // Use -r flag to sort by CPU usage
196
+ console.log('[topProcesses] Running macOS ps command');
35
197
  const result = await execFileAsync('ps', [
36
198
  '-Arco',
37
199
  'pid,pcpu,pmem,comm'
38
- ], { encoding: 'utf8' });
200
+ ], { encoding: 'utf8', timeout: 5000 });
39
201
  stdout = result.stdout;
202
+ console.log('[topProcesses] macOS ps succeeded, output length:', stdout.length);
40
203
  } else {
41
- // Linux uses GNU ps - supports --sort
42
- const result = await execFileAsync('ps', [
43
- '-eo',
44
- 'pid,pcpu,pmem,comm',
45
- '--sort=-pcpu'
46
- ], { encoding: 'utf8' });
47
- stdout = result.stdout;
204
+ // Linux - try different variations in order of preference
205
+ const psVariations = [
206
+ // Standard GNU ps with --sort
207
+ { args: ['-eo', 'pid,pcpu,pmem,comm', '--sort=-pcpu'], name: 'GNU ps with --sort' },
208
+ // GNU ps without --sort (we'll sort in JS)
209
+ { args: ['-eo', 'pid,pcpu,pmem,comm'], name: 'GNU ps without --sort' },
210
+ // BusyBox ps (minimal options)
211
+ { args: ['-o', 'pid,pcpu,pmem,comm'], name: 'BusyBox ps' },
212
+ // Most basic ps command
213
+ { args: ['aux'], name: 'basic ps aux' }
214
+ ];
215
+
216
+ let psSuccess = false;
217
+ for (const variation of psVariations) {
218
+ try {
219
+ console.log(`[topProcesses] Trying: ${variation.name}`);
220
+ const result = await execFileAsync('ps', variation.args, {
221
+ encoding: 'utf8',
222
+ timeout: 5000
223
+ });
224
+ stdout = result.stdout;
225
+ console.log(`[topProcesses] ${variation.name} succeeded, output length:`, stdout.length);
226
+ logger.debug(`ps command succeeded: ${variation.name}`, { outputLength: stdout.length });
227
+ psSuccess = true;
228
+ break;
229
+ } catch (err) {
230
+ console.log(`[topProcesses] ${variation.name} failed:`, err.message);
231
+ // Try next variation
232
+ }
233
+ }
234
+
235
+ if (!psSuccess) {
236
+ throw new Error('All ps command variations failed');
237
+ }
48
238
  }
49
239
 
50
- return parsePsOutput(stdout, limit);
240
+ console.log('[topProcesses] Parsing ps output...');
241
+ const processes = parsePsOutput(stdout, limit);
242
+ console.log('[topProcesses] Parsed', processes.length, 'processes');
243
+ logger.debug('Parsed processes from ps output', {
244
+ processCount: processes.length,
245
+ firstProcess: processes[0]
246
+ });
247
+
248
+ return processes;
51
249
  } catch (error) {
52
- logger.warn('Failed to get top processes on Unix', { error: error.message });
250
+ console.error('[topProcesses] FATAL ERROR getting top processes:', error.message);
251
+ console.error('[topProcesses] Error stack:', error.stack);
252
+ logger.warn('Failed to get top processes on Unix', {
253
+ error: error.message,
254
+ stack: error.stack,
255
+ platform: os.platform()
256
+ });
53
257
  return [];
54
258
  }
55
259
  }
@@ -86,22 +290,10 @@ function parsePsWinJson(stdout, limit) {
86
290
  */
87
291
  async function getTopProcessesWindows(limit = 10) {
88
292
  try {
89
- // Use PowerShell to get process info and convert to JSON
90
- // NOTE: PowerShell startup is slower but more reliable than WMI
91
- const psCmd = [
92
- 'Get-Process | ',
93
- 'Select-Object Id,CPU,WS,ProcessName | ',
94
- 'ConvertTo-Json'
95
- ].join('');
96
-
97
- const { stdout } = await execFileAsync('powershell.exe', [
98
- '-NoLogo',
99
- '-NoProfile',
100
- '-NonInteractive',
101
- '-Command',
102
- psCmd
103
- ], { encoding: 'utf8' });
104
-
293
+ // Use persistent PowerShell instance
294
+ const psCmd = "Get-Process | Select-Object Id,CPU,WS,ProcessName | ConvertTo-Json";
295
+
296
+ const stdout = await execPowerShellCommand(psCmd, 10000);
105
297
  return parsePsWinJson(stdout, limit);
106
298
  } catch (error) {
107
299
  logger.warn('Failed to get top processes on Windows', { error: error.message });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.4.5-beta.0",
3
+ "version": "1.4.9-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Linux-specific performance tracking test
5
+ * Tests each component individually to identify issues
6
+ */
7
+
8
+ import { performanceTracker } from './lib/performanceTracker.js';
9
+ import { getTopProcesses } from './lib/topProcesses.js';
10
+ import { logger, setVerbose } from './lib/logger.js';
11
+ import pidusage from 'pidusage';
12
+ import os from 'os';
13
+ import fs from 'fs';
14
+
15
+ // Enable verbose logging
16
+ setVerbose(true);
17
+
18
+ async function testPidUsage() {
19
+ logger.info('=== Testing pidusage ===');
20
+ try {
21
+ const stats = await pidusage(process.pid);
22
+ logger.info('pidusage SUCCESS', {
23
+ cpu: stats.cpu?.toFixed(2),
24
+ memory: (stats.memory / (1024 * 1024)).toFixed(1) + ' MB',
25
+ pid: stats.pid,
26
+ ppid: stats.ppid
27
+ });
28
+ return true;
29
+ } catch (error) {
30
+ logger.error('pidusage FAILED', { error: error.message, stack: error.stack });
31
+ return false;
32
+ }
33
+ }
34
+
35
+ async function testTopProcesses() {
36
+ logger.info('=== Testing getTopProcesses ===');
37
+ try {
38
+ const topProcs = await getTopProcesses(5);
39
+ logger.info('getTopProcesses SUCCESS', {
40
+ count: topProcs.length,
41
+ processes: topProcs.map(p => `${p.name} (PID: ${p.pid}, CPU: ${p.cpu}%)`)
42
+ });
43
+ return topProcs.length > 0;
44
+ } catch (error) {
45
+ logger.error('getTopProcesses FAILED', { error: error.message, stack: error.stack });
46
+ return false;
47
+ }
48
+ }
49
+
50
+ async function testNetworkMetrics() {
51
+ logger.info('=== Testing Network Metrics ===');
52
+ try {
53
+ if (process.platform === 'linux') {
54
+ // Test /proc/net/dev reading
55
+ if (fs.existsSync('/proc/net/dev')) {
56
+ const content = fs.readFileSync('/proc/net/dev', 'utf8');
57
+ const lines = content.split('\n');
58
+ let totalRx = 0;
59
+ let totalTx = 0;
60
+
61
+ logger.info('/proc/net/dev file found');
62
+ logger.debug('First few lines:', lines.slice(0, 5));
63
+
64
+ for (const line of lines) {
65
+ if (line.includes(':')) {
66
+ const parts = line.split(':')[1].trim().split(/\s+/);
67
+ if (parts.length >= 9) {
68
+ const rx = parseInt(parts[0]) || 0;
69
+ const tx = parseInt(parts[8]) || 0;
70
+ totalRx += rx;
71
+ totalTx += tx;
72
+ }
73
+ }
74
+ }
75
+
76
+ logger.info('Network stats parsed', {
77
+ totalRx: (totalRx / (1024 * 1024)).toFixed(2) + ' MB',
78
+ totalTx: (totalTx / (1024 * 1024)).toFixed(2) + ' MB'
79
+ });
80
+ return true;
81
+ } else {
82
+ logger.warn('/proc/net/dev not found - running in container?');
83
+ return false;
84
+ }
85
+ } else {
86
+ logger.info('Skipping network test (not Linux)');
87
+ return true;
88
+ }
89
+ } catch (error) {
90
+ logger.error('Network metrics FAILED', { error: error.message, stack: error.stack });
91
+ return false;
92
+ }
93
+ }
94
+
95
+ async function testSystemMetrics() {
96
+ logger.info('=== Testing System Metrics ===');
97
+ try {
98
+ const totalMem = os.totalmem();
99
+ const freeMem = os.freemem();
100
+ const usedMem = totalMem - freeMem;
101
+ const cpus = os.cpus();
102
+
103
+ logger.info('System metrics SUCCESS', {
104
+ totalMemory: (totalMem / (1024 * 1024 * 1024)).toFixed(2) + ' GB',
105
+ freeMemory: (freeMem / (1024 * 1024 * 1024)).toFixed(2) + ' GB',
106
+ usedMemory: (usedMem / (1024 * 1024 * 1024)).toFixed(2) + ' GB',
107
+ memoryUsagePercent: ((usedMem / totalMem) * 100).toFixed(1) + '%',
108
+ cpuCount: cpus.length,
109
+ platform: os.platform(),
110
+ arch: os.arch()
111
+ });
112
+ return true;
113
+ } catch (error) {
114
+ logger.error('System metrics FAILED', { error: error.message });
115
+ return false;
116
+ }
117
+ }
118
+
119
+ async function testPerformanceTracker() {
120
+ logger.info('=== Testing PerformanceTracker ===');
121
+ try {
122
+ logger.info('Starting performance tracker for 10 seconds...');
123
+
124
+ // Use temp directory for performance file
125
+ const tmpDir = os.tmpdir();
126
+ performanceTracker.start(tmpDir);
127
+
128
+ // Wait 10 seconds
129
+ await new Promise(resolve => setTimeout(resolve, 10000));
130
+
131
+ // Stop tracking
132
+ const result = performanceTracker.stop();
133
+
134
+ logger.info('PerformanceTracker stopped', {
135
+ sampleCount: result.samples?.length || 0,
136
+ hasSummary: !!result.summary
137
+ });
138
+
139
+ if (result.summary) {
140
+ logger.info('Performance Summary', {
141
+ duration: (result.summary.durationMs / 1000).toFixed(1) + 's',
142
+ avgCPU: result.summary.avgProcessCPU?.toFixed(1) + '%',
143
+ maxCPU: result.summary.maxProcessCPU?.toFixed(1) + '%',
144
+ avgMemory: result.summary.avgProcessMemoryMB?.toFixed(1) + ' MB',
145
+ maxMemory: result.summary.maxProcessMemoryMB?.toFixed(1) + ' MB'
146
+ });
147
+ }
148
+
149
+ if (result.samples && result.samples.length > 0) {
150
+ const lastSample = result.samples[result.samples.length - 1];
151
+ logger.info('Last sample data', {
152
+ hasSystem: !!lastSample.system,
153
+ hasProcess: !!lastSample.process,
154
+ hasNetwork: !!lastSample.network,
155
+ hasTopProcesses: !!lastSample.topProcesses,
156
+ topProcessesCount: lastSample.topProcesses?.length || 0
157
+ });
158
+
159
+ if (lastSample.topProcesses && lastSample.topProcesses.length > 0) {
160
+ logger.info('Top 3 processes from last sample:');
161
+ lastSample.topProcesses.slice(0, 3).forEach((proc, i) => {
162
+ logger.info(` ${i + 1}. ${proc.name} - CPU: ${proc.cpu?.toFixed(1)}%, Mem: ${(proc.memory / (1024 * 1024)).toFixed(1)} MB`);
163
+ });
164
+ }
165
+ }
166
+
167
+ // Clean up
168
+ performanceTracker.cleanup();
169
+
170
+ return result.samples && result.samples.length > 0;
171
+ } catch (error) {
172
+ logger.error('PerformanceTracker FAILED', { error: error.message, stack: error.stack });
173
+ return false;
174
+ }
175
+ }
176
+
177
+ async function runAllTests() {
178
+ logger.info('Starting Linux Performance Tracking Tests');
179
+ logger.info('Platform:', os.platform());
180
+ logger.info('Architecture:', os.arch());
181
+ logger.info('Node version:', process.version);
182
+ logger.info('');
183
+
184
+ const results = {
185
+ pidusage: await testPidUsage(),
186
+ topProcesses: await testTopProcesses(),
187
+ networkMetrics: await testNetworkMetrics(),
188
+ systemMetrics: await testSystemMetrics(),
189
+ performanceTracker: await testPerformanceTracker()
190
+ };
191
+
192
+ logger.info('');
193
+ logger.info('=== Test Results Summary ===');
194
+ Object.entries(results).forEach(([test, passed]) => {
195
+ logger.info(`${test}: ${passed ? '✓ PASS' : '✗ FAIL'}`);
196
+ });
197
+
198
+ const allPassed = Object.values(results).every(r => r);
199
+ logger.info('');
200
+ logger.info(`Overall: ${allPassed ? '✓ ALL TESTS PASSED' : '✗ SOME TESTS FAILED'}`);
201
+
202
+ process.exit(allPassed ? 0 : 1);
203
+ }
204
+
205
+ runAllTests().catch(error => {
206
+ logger.error('Test suite failed', { error: error.message, stack: error.stack });
207
+ process.exit(1);
208
+ });