dashcam 1.4.3-beta → 1.4.5-beta.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,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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.4.3-beta",
3
+ "version": "1.4.5-beta.0",
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);
package/test_workflow.sh CHANGED
@@ -115,8 +115,15 @@ sleep 2
115
115
  echo ""
116
116
  echo "6. Stopping recording and uploading..."
117
117
  # Check if recording is still active
118
+ SHARE_LINK=""
118
119
  if ./bin/dashcam.js status | grep -q "Recording in progress"; then
119
- ./bin/dashcam.js stop
120
+ # Capture the output to extract the share link
121
+ STOP_OUTPUT=$(./bin/dashcam.js stop 2>&1)
122
+ echo "$STOP_OUTPUT"
123
+
124
+ # Extract the share link from the output
125
+ SHARE_LINK=$(echo "$STOP_OUTPUT" | grep -oE "https?://[^\s]+" | head -1)
126
+
120
127
  echo "✅ Recording stopped and uploaded"
121
128
  else
122
129
  echo "⚠️ Recording already completed (this is expected with background mode)"
@@ -134,6 +141,14 @@ echo ""
134
141
  echo "📊 Final Status:"
135
142
  ./bin/dashcam.js status
136
143
 
144
+ # Open the recording in browser if we got a share link
145
+ if [ -n "$SHARE_LINK" ]; then
146
+ echo ""
147
+ echo "🌐 Opening recording in browser..."
148
+ echo "Link: $SHARE_LINK"
149
+ open "$SHARE_LINK"
150
+ fi
151
+
137
152
  echo ""
138
153
  echo "╔════════════════════════════════════════════════════════════════╗"
139
154
  echo "║ SYNC VERIFICATION GUIDE ║"