dashcam 1.4.2-beta → 1.4.5-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.
package/lib/recorder.js CHANGED
@@ -4,6 +4,7 @@ import { createGif, createSnapshot } from './ffmpeg.js';
4
4
  import { applicationTracker } from './applicationTracker.js';
5
5
  import { logsTrackerManager, trimLogs } from './logs/index.js';
6
6
  import { getFfmpegPath, getFfprobePath } from './binaries.js';
7
+ import { performanceTracker } from './performanceTracker.js';
7
8
  import path from 'path';
8
9
  import os from 'os';
9
10
  import fs from 'fs';
@@ -328,14 +329,14 @@ export async function startRecording({
328
329
  const outputArgs = [
329
330
  // Convert pixel format first to handle transparency issues on Windows
330
331
  '-pix_fmt', 'yuv420p', // Force YUV420P format (no alpha channel)
331
- '-c:v', 'libvpx', // Use VP9 codec for better quality and compression
332
+ '-c:v', 'libvpx', // Use VP8 codec (not VP9) for lower CPU usage
332
333
  '-quality', 'realtime', // Use realtime quality preset for faster encoding
333
334
  '-cpu-used', '8', // Maximum speed (0-8, higher = faster but lower quality)
334
335
  '-deadline', 'realtime',// Realtime encoding mode for lowest latency
335
- '-b:v', '2M', // Target bitrate
336
+ '-b:v', '1M', // Reduced bitrate to 1M (from 2M) to lower CPU usage
336
337
  '-r', fps.toString(), // Ensure output framerate matches input
337
- '-g', fps.toString(), // Keyframe interval = 1 second (every fps frames) - ensures frequent keyframes
338
- '-force_key_frames', `expr:gte(t,n_forced*1)`, // Force keyframe every 1 second
338
+ '-g', (fps * 2).toString(), // Keyframe interval = 2 seconds (reduced frequency)
339
+ '-threads', '2', // Limit thread count to reduce CPU usage
339
340
  '-auto-alt-ref', '0', // Disable auto alternate reference frames (fixes transparency encoding error)
340
341
  // WebM options for more frequent disk writes and proper stream handling
341
342
  '-f', 'webm', // Force WebM container format
@@ -343,7 +344,7 @@ export async function startRecording({
343
344
  '-fflags', '+genpts', // Generate presentation timestamps
344
345
  '-avoid_negative_ts', 'make_zero', // Avoid negative timestamps
345
346
  '-vsync', '1', // Ensure every frame is encoded (CFR - constant frame rate)
346
- '-max_muxing_queue_size', '9999' // Large queue to prevent frame drops
347
+ '-max_muxing_queue_size', '1024' // Reduced queue size to lower memory usage
347
348
  ];
348
349
 
349
350
  if (includeAudio) {
@@ -414,19 +415,12 @@ export async function startRecording({
414
415
  reject: false,
415
416
  all: true, // Capture both stdout and stderr
416
417
  stdin: 'pipe', // Enable stdin for sending 'q' to stop recording
417
- detached: false,
418
+ detached: false, // Keep attached so it dies with parent
418
419
  windowsHide: true // Hide the console window on Windows
419
420
  });
420
421
 
421
- // Unref the child process so it doesn't keep the parent alive
422
- if (currentRecording.pid) {
423
- try {
424
- currentRecording.unref();
425
- } catch (e) {
426
- // Ignore errors if unref is not available
427
- logger.debug('Could not unref ffmpeg process');
428
- }
429
- }
422
+ // Don't unref - we want FFmpeg to be killed when parent dies
423
+ // Removing unref() ensures orphaned processes don't hang around
430
424
 
431
425
  logger.info('FFmpeg process spawned', {
432
426
  pid: currentRecording.pid,
@@ -450,6 +444,11 @@ export async function startRecording({
450
444
  logger.debug('Starting application tracking...');
451
445
  applicationTracker.start();
452
446
 
447
+ // Start performance tracking - pass output directory for file storage
448
+ logger.debug('Starting performance tracking...');
449
+ const outputDir = path.dirname(outputPath);
450
+ performanceTracker.start(outputDir);
451
+
453
452
  // Start log tracking for this recording (don't await to avoid blocking)
454
453
  const recorderId = path.basename(outputPath).replace('.webm', '');
455
454
  logger.debug('Starting log tracking...', { recorderId });
@@ -724,7 +723,8 @@ export async function stopRecording() {
724
723
  clientStartDate: recordingStartTime,
725
724
  apps: applicationTracker.stop().apps,
726
725
  icons: applicationTracker.stop().icons,
727
- logs: await logsTrackerManager.stop({ recorderId: path.basename(outputPath).replace('.webm', ''), screenId: '1' })
726
+ logs: await logsTrackerManager.stop({ recorderId: path.basename(outputPath).replace('.webm', ''), screenId: '1' }),
727
+ performance: performanceTracker.stop()
728
728
  };
729
729
 
730
730
  currentRecording = null;
@@ -799,6 +799,10 @@ export async function stopRecording() {
799
799
  logger.debug('Stopping application tracking...');
800
800
  const appTrackingResults = applicationTracker.stop();
801
801
 
802
+ // Stop performance tracking and get results
803
+ logger.debug('Stopping performance tracking...');
804
+ const performanceResults = performanceTracker.stop();
805
+
802
806
  // Stop log tracking and get results
803
807
  const recorderId = path.basename(outputPath).replace('.webm', '');
804
808
  logger.debug('Stopping log tracking...', { recorderId });
@@ -816,6 +820,11 @@ export async function stopRecording() {
816
820
  logResults: {
817
821
  trackers: logTrackingResults.length,
818
822
  totalEvents: logTrackingResults.reduce((sum, result) => sum + result.count, 0)
823
+ },
824
+ performanceResults: {
825
+ sampleCount: performanceResults.summary?.sampleCount || 0,
826
+ avgProcessCPU: performanceResults.summary?.avgProcessCPU?.toFixed(1) || 0,
827
+ avgProcessMemoryMB: performanceResults.summary?.avgProcessMemoryMB?.toFixed(1) || 0
819
828
  }
820
829
  });
821
830
 
@@ -847,13 +856,19 @@ export async function stopRecording() {
847
856
  clientStartDate: recordingStartTime, // Include the recording start timestamp
848
857
  apps: appTrackingResults.apps, // Include tracked applications
849
858
  icons: appTrackingResults.icons, // Include application icons metadata
850
- logs: logTrackingResults // Include log tracking results
859
+ logs: logTrackingResults, // Include log tracking results
860
+ performance: performanceResults // Include performance tracking results
851
861
  };
852
862
 
853
- logger.info('Recording stopped with clientStartDate', {
863
+ logger.info('Recording stopped with performance data', {
854
864
  clientStartDate: recordingStartTime,
855
865
  clientStartDateReadable: new Date(recordingStartTime).toISOString(),
856
- duration: result.duration
866
+ duration: result.duration,
867
+ performanceSamples: performanceResults.summary?.sampleCount || 0,
868
+ avgCPU: performanceResults.summary?.avgProcessCPU?.toFixed(1) || 0,
869
+ maxCPU: performanceResults.summary?.maxProcessCPU?.toFixed(1) || 0,
870
+ avgMemoryMB: performanceResults.summary?.avgProcessMemoryMB?.toFixed(1) || 0,
871
+ maxMemoryMB: performanceResults.summary?.maxProcessMemoryMB?.toFixed(1) || 0
857
872
  });
858
873
 
859
874
  currentRecording = null;
@@ -888,6 +903,12 @@ export async function stopRecording() {
888
903
 
889
904
  // Stop application tracking on error
890
905
  applicationTracker.stop();
906
+
907
+ // Stop performance tracking on error
908
+ if (performanceTracker.isTracking()) {
909
+ performanceTracker.stop();
910
+ }
911
+
891
912
  throw error;
892
913
  }
893
914
  }
@@ -0,0 +1,128 @@
1
+ import { execFile } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import os from 'os';
4
+ import { logger } from './logger.js';
5
+
6
+ const execFileAsync = promisify(execFile);
7
+
8
+ /**
9
+ * Parse ps output format: " PID %CPU %MEM COMMAND"
10
+ */
11
+ function parsePsOutput(stdout, limit) {
12
+ const lines = stdout.trim().split('\n');
13
+ // Skip header line
14
+ return lines.slice(1, 1 + limit).map(line => {
15
+ const parts = line.trim().split(/\s+/, 4);
16
+ return {
17
+ pid: Number(parts[0]),
18
+ cpu: Number(parts[1]),
19
+ mem: Number(parts[2]),
20
+ name: parts[3] || ''
21
+ };
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Get top processes on Unix-like systems (Linux, macOS)
27
+ */
28
+ async function getTopProcessesUnix(limit = 10) {
29
+ try {
30
+ let stdout;
31
+
32
+ if (os.platform() === 'darwin') {
33
+ // macOS uses BSD ps - different syntax, no --sort option
34
+ // Use -r flag to sort by CPU usage
35
+ const result = await execFileAsync('ps', [
36
+ '-Arco',
37
+ 'pid,pcpu,pmem,comm'
38
+ ], { encoding: 'utf8' });
39
+ stdout = result.stdout;
40
+ } 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;
48
+ }
49
+
50
+ return parsePsOutput(stdout, limit);
51
+ } catch (error) {
52
+ logger.warn('Failed to get top processes on Unix', { error: error.message });
53
+ return [];
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Parse PowerShell JSON output for Windows processes
59
+ */
60
+ function parsePsWinJson(stdout, limit) {
61
+ let arr;
62
+ try {
63
+ arr = JSON.parse(stdout);
64
+ } catch (e) {
65
+ logger.warn('Failed to parse PowerShell JSON output', { error: e.message });
66
+ return [];
67
+ }
68
+
69
+ if (!Array.isArray(arr)) {
70
+ arr = [arr];
71
+ }
72
+
73
+ // Some fields may be undefined if CPU hasn't updated yet
74
+ arr.sort((a, b) => (b.CPU || 0) - (a.CPU || 0));
75
+
76
+ return arr.slice(0, limit).map(p => ({
77
+ pid: p.Id,
78
+ cpu: p.CPU || 0, // total CPU seconds
79
+ memBytes: p.WS || 0, // working set in bytes
80
+ name: p.ProcessName || ''
81
+ }));
82
+ }
83
+
84
+ /**
85
+ * Get top processes on Windows using PowerShell
86
+ */
87
+ async function getTopProcessesWindows(limit = 10) {
88
+ 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
+
105
+ return parsePsWinJson(stdout, limit);
106
+ } catch (error) {
107
+ logger.warn('Failed to get top processes on Windows', { error: error.message });
108
+ return [];
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Get top processes by CPU usage (cross-platform)
114
+ * @param {number} limit - Number of top processes to return (default: 10)
115
+ * @returns {Promise<Array>} Array of process objects with pid, cpu, mem/memBytes, and name
116
+ */
117
+ export async function getTopProcesses(limit = 10) {
118
+ const platform = os.platform();
119
+
120
+ logger.debug('Getting top processes', { platform, limit });
121
+
122
+ if (platform === 'win32') {
123
+ return getTopProcessesWindows(limit);
124
+ }
125
+
126
+ // Linux, macOS, and other Unix-like systems
127
+ return getTopProcessesUnix(limit);
128
+ }
package/lib/uploader.js CHANGED
@@ -6,6 +6,7 @@ import path from 'path';
6
6
  import { auth } from './auth.js';
7
7
  import got from 'got';
8
8
  import { getSystemInfo } from './systemInfo.js';
9
+ import { API_ENDPOINT } from './config.js';
9
10
 
10
11
  class Uploader {
11
12
  constructor() {
@@ -250,7 +251,8 @@ export async function upload(filePath, metadata = {}) {
250
251
  apps: metadata.apps && metadata.apps.length > 0 ? metadata.apps : ['Screen Recording'], // Use tracked apps or fallback
251
252
  title: metadata.title || defaultTitle,
252
253
  system: systemInfo, // Include system information
253
- clientStartDate: metadata.clientStartDate || Date.now() // Use actual recording start time
254
+ clientStartDate: metadata.clientStartDate || Date.now(), // Use actual recording start time
255
+ performance: metadata.performance // Include performance tracking data
254
256
  };
255
257
 
256
258
  // Add project if we have one
@@ -262,18 +264,28 @@ export async function upload(filePath, metadata = {}) {
262
264
  replayConfig.description = metadata.description;
263
265
  }
264
266
 
265
- logger.verbose('Creating replay with config', replayConfig);
267
+ logger.verbose('Creating replay with config', {
268
+ ...replayConfig,
269
+ performanceSamples: replayConfig.performance?.samples?.length || 0,
270
+ performanceSummary: replayConfig.performance?.summary ? 'included' : 'none'
271
+ });
266
272
 
267
273
  console.log('Creating replay on server...');
268
- logger.info('Creating replay', replayConfig);
274
+ logger.info('Creating replay', {
275
+ title: replayConfig.title,
276
+ duration: replayConfig.duration,
277
+ apps: replayConfig.apps,
278
+ hasPerformanceData: !!replayConfig.performance,
279
+ performanceSamples: replayConfig.performance?.samples?.length || 0
280
+ });
269
281
 
270
282
  // Create the replay first
271
283
  const token = await auth.getToken();
272
284
 
273
285
  let newReplay;
274
286
  try {
275
- logger.debug('Sending replay creation request...');
276
- newReplay = await got.post('https://testdriver-api.onrender.com/api/v1/replay', {
287
+ logger.debug('Sending replay creation request...', { apiEndpoint: API_ENDPOINT });
288
+ newReplay = await got.post(`${API_ENDPOINT}/api/v1/replay`, {
277
289
  headers: {
278
290
  Authorization: `Bearer ${token}`
279
291
  },
@@ -459,7 +471,7 @@ export async function upload(filePath, metadata = {}) {
459
471
  console.log('Publishing replay...');
460
472
 
461
473
  try {
462
- await got.post('https://testdriver-api.onrender.com/api/v1/replay/publish', {
474
+ await got.post(`${API_ENDPOINT}/api/v1/replay/publish`, {
463
475
  headers: {
464
476
  Authorization: `Bearer ${token}`
465
477
  },
@@ -62,14 +62,23 @@ export const jsonl = {
62
62
  let fd = fs.openSync(file, 'w');
63
63
  fs.closeSync(fd);
64
64
  } catch (error) {
65
- throttledLog('info', `jsonl.js failed to initialize file ${error}`);
65
+ throttledLog('info', `jsonl.js failed to initialize file ${error.message}`, {
66
+ directory,
67
+ fileName,
68
+ error: error.message
69
+ });
70
+ throw error;
66
71
  }
67
72
  }
68
73
  try {
69
74
  let data = arrayOfJsonObjects.map((x) => JSON.stringify(x)).join('\n');
70
75
  fs.writeFileSync(file, data);
71
76
  } catch (error) {
72
- throttledLog('info', `jsonl.js failed to write to file ${error}`);
77
+ throttledLog('info', `jsonl.js failed to write to file ${error.message}`, {
78
+ file,
79
+ error: error.message
80
+ });
81
+ throw error;
73
82
  }
74
83
 
75
84
  return file;
@@ -51,7 +51,7 @@ class WSServer {
51
51
  return;
52
52
  }
53
53
 
54
- logger.debug('WebSocketServer: Starting server, trying ports...', { ports: this.ports });
54
+ logger.info('WebSocketServer: Starting server, trying ports...', { ports: this.ports });
55
55
  for (const port of this.ports) {
56
56
  const ws = await new Promise((resolve) => {
57
57
  logger.debug('WebSocketServer: Trying port ' + port);
@@ -104,7 +104,11 @@ class WSServer {
104
104
  });
105
105
 
106
106
  this.#socket.on('connection', (client) => {
107
- logger.info('WebSocketServer: New client connection established');
107
+ logger.info('WebSocketServer: New client connection established', {
108
+ clientAddress: client._socket?.remoteAddress,
109
+ clientPort: client._socket?.remotePort,
110
+ totalClients: this.#socket.clients.size
111
+ });
108
112
 
109
113
  let state = states.NEW;
110
114
  const failValidation = () => {
@@ -124,17 +128,22 @@ class WSServer {
124
128
 
125
129
  client.on('message', (data, isBinary) => {
126
130
  let message = isBinary ? data : data.toString();
127
- logger.debug('WebSocketServer: Received message from client', {
131
+ logger.info('WebSocketServer: Received message from client', {
128
132
  isBinary,
129
133
  messageLength: message.length,
134
+ messagePreview: message.substring(0, 100),
130
135
  state
131
136
  });
132
137
 
133
138
  try {
134
139
  message = JSON.parse(message);
135
- logger.debug('WebSocketServer: Parsed message', { type: message.type, hasPayload: !!message.payload });
140
+ logger.info('WebSocketServer: Parsed message', {
141
+ type: message.type,
142
+ hasPayload: !!message.payload,
143
+ payloadKeys: message.payload ? Object.keys(message.payload) : []
144
+ });
136
145
  } catch (err) {
137
- logger.debug('WebSocketServer: Message is not JSON, treating as raw string');
146
+ logger.info('WebSocketServer: Message is not JSON, treating as raw string', { rawMessage: message });
138
147
  }
139
148
 
140
149
  if (state === states.SENT_HEADERS) {
@@ -152,14 +161,14 @@ class WSServer {
152
161
  this.emit('message', message, client);
153
162
  });
154
163
 
155
- logger.debug('WebSocketServer: Sending connection header to client');
164
+ logger.info('WebSocketServer: Sending connection header to client');
156
165
  client.send('dashcam_desktop_socket_connected', (err) => {
157
166
  if (err) {
158
167
  logger.error('WebSocketServer: Failed to send connection header', { error: err.message });
159
168
  client.close();
160
169
  clearTimeout(timeout);
161
170
  } else {
162
- logger.debug('WebSocketServer: Connection header sent, waiting for confirmation');
171
+ logger.info('WebSocketServer: Connection header sent, waiting for confirmation');
163
172
  state = states.SENT_HEADERS;
164
173
  }
165
174
  });
@@ -176,9 +185,10 @@ class WSServer {
176
185
  throw new Error('Server not currently running');
177
186
  }
178
187
 
179
- logger.debug('WebSocketServer: Broadcasting message to all clients', {
188
+ logger.info('WebSocketServer: Broadcasting message to all clients', {
180
189
  clientCount: this.#socket.clients.size,
181
- messageType: message.type || 'raw'
190
+ messageType: message.type || 'raw',
191
+ messagePayload: message.payload ? JSON.stringify(message.payload).substring(0, 100) : 'none'
182
192
  });
183
193
 
184
194
  this.#socket.clients.forEach((client) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.4.2-beta",
3
+ "version": "1.4.5-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {
@@ -47,6 +47,7 @@
47
47
  "mask-sensitive-data": "^0.11.5",
48
48
  "node-fetch": "^2.7.0",
49
49
  "open": "^9.1.0",
50
+ "pidusage": "^3.0.2",
50
51
  "systeminformation": "^5.18.15",
51
52
  "tail": "^2.2.6",
52
53
  "winston": "^3.11.0",
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PerformanceTracker } from './lib/performanceTracker.js';
4
+ import { logger, setVerbose } from './lib/logger.js';
5
+
6
+ setVerbose(true);
7
+
8
+ async function testPerformanceTracking() {
9
+ console.log('Testing performance tracking with top processes...\n');
10
+
11
+ const tracker = new PerformanceTracker();
12
+
13
+ // Start tracking
14
+ tracker.start();
15
+ console.log('Started tracking...');
16
+
17
+ // Wait for 6 seconds to get at least one sample
18
+ console.log('Waiting 6 seconds for sample...');
19
+ await new Promise(resolve => setTimeout(resolve, 6000));
20
+
21
+ // Stop tracking
22
+ const result = tracker.stop();
23
+
24
+ console.log('\n=== Performance Tracking Results ===');
25
+ console.log(`Total samples: ${result.samples.length}`);
26
+
27
+ if (result.samples.length > 0) {
28
+ const firstSample = result.samples[0];
29
+ console.log('\nFirst sample keys:', Object.keys(firstSample));
30
+ console.log('Has topProcesses:', !!firstSample.topProcesses);
31
+ console.log('topProcesses count:', firstSample.topProcesses?.length || 0);
32
+
33
+ if (firstSample.topProcesses && firstSample.topProcesses.length > 0) {
34
+ console.log('\nTop 3 processes:');
35
+ firstSample.topProcesses.slice(0, 3).forEach((proc, i) => {
36
+ console.log(`${i + 1}. ${proc.name} (PID: ${proc.pid})`);
37
+ console.log(` CPU: ${proc.cpu.toFixed(1)}%`);
38
+ console.log(` Memory: ${(proc.memory / (1024 * 1024)).toFixed(1)} MB`);
39
+ });
40
+ }
41
+ }
42
+
43
+ if (result.summary) {
44
+ console.log('\nSummary:');
45
+ console.log(` Duration: ${(result.summary.durationMs / 1000).toFixed(1)}s`);
46
+ console.log(` Sample count: ${result.summary.sampleCount}`);
47
+ console.log(` Avg CPU: ${result.summary.avgProcessCPU.toFixed(1)}%`);
48
+ console.log(` Max CPU: ${result.summary.maxProcessCPU.toFixed(1)}%`);
49
+ }
50
+ }
51
+
52
+ testPerformanceTracking().catch(console.error);
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test script to verify performance tracking during recording
5
+ */
6
+
7
+ import { startRecording, stopRecording } from './lib/recorder.js';
8
+ import { logger, setVerbose } from './lib/logger.js';
9
+ import fs from 'fs';
10
+
11
+ // Enable verbose logging
12
+ setVerbose(true);
13
+
14
+ async function testPerformanceTracking() {
15
+ try {
16
+ logger.info('Starting performance tracking test...');
17
+
18
+ // Start recording
19
+ logger.info('Starting recording...');
20
+ const startResult = await startRecording({
21
+ fps: 10,
22
+ includeAudio: false
23
+ });
24
+
25
+ logger.info('Recording started', {
26
+ outputPath: startResult.outputPath,
27
+ startTime: startResult.startTime
28
+ });
29
+
30
+ // Record for 10 seconds
31
+ logger.info('Recording for 10 seconds...');
32
+ await new Promise(resolve => setTimeout(resolve, 10000));
33
+
34
+ // Stop recording
35
+ logger.info('Stopping recording...');
36
+ const result = await stopRecording();
37
+
38
+ logger.info('Recording stopped successfully');
39
+
40
+ // Display performance results
41
+ if (result.performance) {
42
+ logger.info('=== Performance Tracking Results ===');
43
+
44
+ if (result.performance.summary) {
45
+ const summary = result.performance.summary;
46
+ logger.info('Summary Statistics:');
47
+ logger.info(` Duration: ${(summary.durationMs / 1000).toFixed(1)}s`);
48
+ logger.info(` Sample Count: ${summary.sampleCount}`);
49
+ logger.info(` Monitor Interval: ${summary.monitorInterval}ms`);
50
+ logger.info('');
51
+ logger.info('Process Metrics:');
52
+ logger.info(` Average CPU: ${summary.avgProcessCPU.toFixed(1)}%`);
53
+ logger.info(` Max CPU: ${summary.maxProcessCPU.toFixed(1)}%`);
54
+ logger.info(` Average Memory: ${summary.avgProcessMemoryMB.toFixed(1)} MB`);
55
+ logger.info(` Max Memory: ${summary.maxProcessMemoryMB.toFixed(1)} MB`);
56
+ logger.info('');
57
+ logger.info('System Metrics:');
58
+ logger.info(` Average System Memory Usage: ${summary.avgSystemMemoryUsagePercent.toFixed(1)}%`);
59
+ logger.info(` Max System Memory Usage: ${summary.maxSystemMemoryUsagePercent.toFixed(1)}%`);
60
+ logger.info(` Total System Memory: ${summary.totalSystemMemoryGB.toFixed(2)} GB`);
61
+ }
62
+
63
+ if (result.performance.samples && result.performance.samples.length > 0) {
64
+ logger.info('');
65
+ logger.info(`Collected ${result.performance.samples.length} performance samples`);
66
+
67
+ // Show top processes from the last sample
68
+ const lastSample = result.performance.samples[result.performance.samples.length - 1];
69
+ if (lastSample.topProcesses && lastSample.topProcesses.length > 0) {
70
+ logger.info('');
71
+ logger.info('Top 10 Processes (from last sample):');
72
+ lastSample.topProcesses.forEach((proc, index) => {
73
+ logger.info(` ${index + 1}. ${proc.name} (PID: ${proc.pid})`);
74
+ logger.info(` CPU: ${proc.cpu.toFixed(1)}%, Memory: ${(proc.memory / (1024 * 1024)).toFixed(1)} MB`);
75
+ });
76
+ logger.info(`Total processes on system: ${lastSample.totalProcesses}`);
77
+ }
78
+ }
79
+ } else {
80
+ logger.warn('No performance data in result');
81
+ }
82
+
83
+ // Display other results
84
+ logger.info('');
85
+ logger.info('=== Recording Results ===');
86
+ logger.info(`Output: ${result.outputPath}`);
87
+ logger.info(`Duration: ${result.duration}ms`);
88
+ logger.info(`File Size: ${(result.fileSize / 1024 / 1024).toFixed(2)} MB`);
89
+ logger.info(`Apps Tracked: ${result.apps?.length || 0}`);
90
+ logger.info(`Log Events: ${result.logs?.reduce((sum, log) => sum + log.count, 0) || 0}`);
91
+
92
+ // Verify file exists
93
+ if (fs.existsSync(result.outputPath)) {
94
+ logger.info('Recording file created successfully');
95
+ } else {
96
+ logger.error('Recording file not found!');
97
+ }
98
+
99
+ logger.info('Test completed successfully!');
100
+
101
+ } catch (error) {
102
+ logger.error('Test failed:', error);
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ // Run the test
108
+ testPerformanceTracking();
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { getTopProcesses } from './lib/topProcesses.js';
4
+ import { logger, setVerbose } from './lib/logger.js';
5
+
6
+ setVerbose(true);
7
+
8
+ async function test() {
9
+ console.log('Testing getTopProcesses...\n');
10
+
11
+ const processes = await getTopProcesses(10);
12
+
13
+ console.log(`Found ${processes.length} processes:\n`);
14
+
15
+ processes.forEach((proc, index) => {
16
+ console.log(`${index + 1}. ${proc.name} (PID: ${proc.pid})`);
17
+ console.log(` CPU: ${proc.cpu}%`);
18
+ console.log(` Memory: ${proc.mem || proc.memBytes}${proc.memBytes ? ' bytes' : '%'}`);
19
+ console.log();
20
+ });
21
+ }
22
+
23
+ test().catch(console.error);