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/PERFORMANCE_TRACKING.md +139 -0
- package/bin/dashcam-background.js +59 -4
- package/bin/dashcam.js +321 -7
- package/lib/auth.js +8 -7
- package/lib/config.js +2 -1
- package/lib/extension-logs/helpers.js +7 -1
- package/lib/logs/index.js +5 -1
- package/lib/performanceTracker.js +487 -0
- package/lib/processManager.js +31 -12
- package/lib/recorder.js +40 -19
- package/lib/topProcesses.js +128 -0
- package/lib/uploader.js +18 -6
- package/lib/utilities/jsonl.js +11 -2
- package/lib/websocket/server.js +19 -9
- package/package.json +2 -1
- package/test-perf-tracker.js +52 -0
- package/test-performance-tracking.js +108 -0
- package/test-top-processes.js +23 -0
- package/test_workflow.sh +22 -5
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
|
|
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', '
|
|
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 =
|
|
338
|
-
'-
|
|
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', '
|
|
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
|
-
//
|
|
422
|
-
|
|
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
|
|
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',
|
|
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',
|
|
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(
|
|
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(
|
|
474
|
+
await got.post(`${API_ENDPOINT}/api/v1/replay/publish`, {
|
|
463
475
|
headers: {
|
|
464
476
|
Authorization: `Bearer ${token}`
|
|
465
477
|
},
|
package/lib/utilities/jsonl.js
CHANGED
|
@@ -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;
|
package/lib/websocket/server.js
CHANGED
|
@@ -51,7 +51,7 @@ class WSServer {
|
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
logger.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|