Commit 65575a0
authored
perf: optimize telemetry initialization and reduce startup overhead (#3620)
* perf: optimize telemetry initialization and reduce startup overhead
This commit significantly improves CLI startup performance by optimizing
how OpenTelemetry and Sentry telemetry is initialized and sent.
Key improvements:
- Remove upfront initializeInstrumentation() call to eliminate 28,000+
require-in-the-middle hook registrations on every command
- Implement lazy initialization of OpenTelemetry during exit handler
- Separate Sentry initialization to only load when errors occur
- Cache auth token to avoid recreating Config/APIClient instances
- Remove empty instrumentation registration that added overhead
- Consolidate platform checks into isTelemetryEnabled() utility
- Fix duplicate setupTelemetry() calls that overwrote timing data
- Batch file existence checks in analytics using Promise.all
- Remove blocking await in beforeExit handler
- Add analytics-telemetry debug scope for troubleshooting
Performance results:
- 17-21% faster startup time (0.770s -> 0.608s)
- Zero require-in-the-middle overhead (28,003 lines -> 0)
- HTTP calls still made successfully to Honeycomb/Sentry
- All telemetry data identical to production build
The telemetry functionality remains fully intact - we've only optimized
when and how the infrastructure is initialized.
* feat: add finally hook to report command errors to Sentry
- Created finally hook that sends command errors to Sentry and Honeycomb
- Filters out 4xx client errors (user errors) to reduce noise
- Only reports 5xx server errors and internal CLI exceptions
- Restored missing sentryClient variable in global_telemetry.ts
* fix: filter out command_not_found errors from Sentry
Command not found errors are user typos, not bugs. Filter them out by:
- Matching the error message pattern
- Checking for exit code 127 (standard "command not found" code)
* perf: spawn telemetry worker process for non-blocking collection
Implemented background worker process for telemetry to eliminate blocking:
- Created telemetry_worker.ts that handles all OpenTelemetry/Sentry initialization
- Spawn detached worker process via stdin for data transfer
- Main CLI exits immediately without waiting for HTTP requests
- Worker inherits stderr for DEBUG=analytics-telemetry visibility
Performance improvement: ~25% faster (0.608s → 0.45s)
Also enhanced debug output:
- Added payload logging for Honeycomb and Sentry
- Shows full telemetry data structure before sending
- Helps verify data and troubleshoot issues
* refactor: move telemetry files to lib/analytics-telemetry
Organized telemetry code into its own directory to avoid confusion
with src/commands/telemetry:
- Moved global_telemetry.ts to lib/analytics-telemetry/global-telemetry.ts
- Moved telemetry_worker.ts to lib/analytics-telemetry/telemetry-worker.ts
- Renamed files to use hyphens instead of underscores
- Updated all import paths across hooks, bin/run.js, and tests
- Fixed process.exit linter rule for telemetry-worker.ts
* refactor: modularize telemetry code and add comprehensive tests
Split monolithic global-telemetry.ts (357 lines) into focused modules:
- telemetry-utils.ts: Shared utilities, types, and helpers
- honeycomb-client.ts: OpenTelemetry/Honeycomb integration
- sentry-client.ts: Sentry error reporting
- global-telemetry.ts: Thin orchestrator (123 lines)
- worker-client.ts: Background worker process management
Refactored bin/run.js from 116 to 34 lines by extracting telemetry
setup and signal handlers into worker-client.ts.
Added comprehensive test coverage (32 tests):
- test/unit/analytics-telemetry/telemetry-utils.unit.test.ts (12 tests)
- test/unit/analytics-telemetry/honeycomb-client.unit.test.ts (6 tests)
- test/unit/analytics-telemetry/sentry-client.unit.test.ts (5 tests)
- test/unit/analytics-telemetry/global-telemetry.unit.test.ts (9 tests)
Benefits:
- Single responsibility per module
- Easier to test in isolation
- Easier to maintain and understand
- Public API unchanged (backward compatible)
* chore: remove old global_telemetry test file
The tests have been replaced with new modular tests in test/unit/analytics-telemetry/
* refactor: replace 'any' types with proper TypeScript types in telemetry
Improvements:
- Extended CLIError interface with all error properties (code, statusCode, http, oclif)
- Added TelemetryData union type for Telemetry | CLIError
- Added TelemetryGlobal interface for global.cliTelemetry
- Added TelemetryOptions interface for hook options
- Used Config type from @oclif/core/interfaces instead of 'any'
- Replaced all '(data as any)' casts with proper CLIError type
- Replaced all '(global as any)' casts with proper global typing
- Added proper declare global block in worker-client.ts
All 32 tests passing with improved type safety.
* revert: restore analytics.ts to main branch version
The changes to analytics.ts (reformatting and Promise.all optimization)
were unintentional and not relevant to this telemetry refactoring PR.
Reverted to keep the PR focused on telemetry-specific improvements.
* fix: add Windows compatibility for telemetry worker process
Add windowsHide: true option to spawn() calls to prevent console windows
from appearing on Windows when telemetry is explicitly enabled.
This ensures a better user experience for Windows users who enable
telemetry with ENABLE_WINDOWS_TELEMETRY=true.
Changes:
- Added windowsHide: true to worker-client.ts spawn options
- Added windowsHide: true to finally hook spawn options
- Prevents console window flash on Windows
- No impact on Unix/macOS behavior
* refactor: remove unnecessary re-exports from global-telemetry
Remove all re-exports from global-telemetry.ts and have consumers import
utilities directly from telemetry-utils.ts where appropriate.
Changes:
- Removed re-exports of getProcessor, initializeInstrumentation
- Removed re-exports of ensureSentryInitialized
- Removed re-exports of computeDuration, isTelemetryEnabled
- Removed re-exports of types (CLIError, Telemetry, TelemetryGlobal)
Updated imports:
- bin/run.js: Import computeDuration from telemetry-utils
- All hooks: Import isTelemetryEnabled from telemetry-utils
- Hooks still import orchestrator functions from global-telemetry
(setupTelemetry, reportCmdNotFound)
Updated tests:
- Removed duplicate computeDuration and isTelemetryEnabled tests from
global-telemetry.unit.test.ts (already tested in telemetry-utils.unit.test.ts)
- Removed unused sinon imports
- Test file now only tests orchestrator functions
Windows compatibility:
- Added windowsHide: true to sentry.ts spawn call to prevent console
windows on Windows
Benefits:
- Clearer API surface - global-telemetry only exports what it owns
- No unnecessary indirection for utility functions
- Makes it obvious which functions are orchestrators vs utilities
- Better test organization - tests colocated with implementations
- Reduces coupling between modules
The public API of global-telemetry now consists only of:
- setupTelemetry()
- reportCmdNotFound()
- sendTelemetry()
* include oclif:perf with oclif:*
* refactor: consolidate duplicate telemetry functions
Move serializeTelemetryData and spawnTelemetryWorker functions from
worker-client.ts and sentry.ts to telemetry-utils.ts to eliminate
duplication. Keep isUserError in sentry.ts since it's only used there
for Sentry-specific error filtering.1 parent bde5a5c commit 65575a0
File tree
19 files changed
+1145
-490
lines changed- bin
- src
- hooks
- command_not_found
- finally
- init
- postrun
- prerun
- lib/analytics-telemetry
- test/unit
- analytics-telemetry
19 files changed
+1145
-490
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | | - | |
| 7 | + | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | 19 | | |
65 | 20 | | |
66 | | - | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
67 | 31 | | |
68 | 32 | | |
69 | 33 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
184 | 184 | | |
185 | 185 | | |
186 | 186 | | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
187 | 190 | | |
188 | 191 | | |
189 | 192 | | |
| |||
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | | - | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
6 | 9 | | |
7 | 10 | | |
8 | 11 | | |
9 | | - | |
10 | | - | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
0 commit comments