diff --git a/packages/core/src/telemetry/index.ts b/packages/core/src/telemetry/index.ts index c379a835be..c86aaf6b46 100644 --- a/packages/core/src/telemetry/index.ts +++ b/packages/core/src/telemetry/index.ts @@ -126,6 +126,7 @@ export { recordBaselineComparison, isPerformanceMonitoringActive, recordFlickerFrame, + recordSlowRender, // Performance monitoring types PerformanceMetricType, MemoryMetricType, diff --git a/packages/core/src/telemetry/metrics.test.ts b/packages/core/src/telemetry/metrics.test.ts index dafedfd23a..f0a9e226d8 100644 --- a/packages/core/src/telemetry/metrics.test.ts +++ b/packages/core/src/telemetry/metrics.test.ts @@ -95,6 +95,7 @@ describe('Telemetry Metrics', () => { let recordExitFailModule: typeof import('./metrics.js').recordExitFail; let recordAgentRunMetricsModule: typeof import('./metrics.js').recordAgentRunMetrics; let recordLinesChangedModule: typeof import('./metrics.js').recordLinesChanged; + let recordSlowRenderModule: typeof import('./metrics.js').recordSlowRender; beforeEach(async () => { vi.resetModules(); @@ -138,6 +139,7 @@ describe('Telemetry Metrics', () => { recordExitFailModule = metricsJsModule.recordExitFail; recordAgentRunMetricsModule = metricsJsModule.recordAgentRunMetrics; recordLinesChangedModule = metricsJsModule.recordLinesChanged; + recordSlowRenderModule = metricsJsModule.recordSlowRender; const otelApiModule = await import('@opentelemetry/api'); @@ -196,6 +198,26 @@ describe('Telemetry Metrics', () => { }); }); + describe('recordSlowRender', () => { + it('does not record metrics if not initialized', () => { + const config = makeFakeConfig({}); + recordSlowRenderModule(config, 123); + expect(mockHistogramRecordFn).not.toHaveBeenCalled(); + }); + + it('records a slow render event when initialized', () => { + const config = makeFakeConfig({}); + initializeMetricsModule(config); + recordSlowRenderModule(config, 123); + + expect(mockHistogramRecordFn).toHaveBeenCalledWith(123, { + 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', + }); + }); + }); + describe('initializeMetrics', () => { const mockConfig = { getSessionId: () => 'test-session-id', diff --git a/packages/core/src/telemetry/metrics.ts b/packages/core/src/telemetry/metrics.ts index 01fb5b7f76..e28413524a 100644 --- a/packages/core/src/telemetry/metrics.ts +++ b/packages/core/src/telemetry/metrics.ts @@ -57,6 +57,7 @@ const REGRESSION_PERCENTAGE_CHANGE = 'gemini_cli.performance.regression.percentage_change'; const BASELINE_COMPARISON = 'gemini_cli.performance.baseline.comparison'; const FLICKER_FRAME_COUNT = 'gemini_cli.ui.flicker.count'; +const SLOW_RENDER_LATENCY = 'gemini_cli.ui.slow_render.latency'; const EXIT_FAIL_COUNT = 'gemini_cli.exit.fail.count'; const baseMetricDefinition = { @@ -227,6 +228,13 @@ const HISTOGRAM_DEFINITIONS = { agent_name: string; }, }, + [SLOW_RENDER_LATENCY]: { + description: 'Counts UI frames that take too long to render.', + unit: 'ms', + valueType: ValueType.INT, + assign: (h: Histogram) => (slowRenderHistogram = h), + attributes: {} as Record, + }, [AGENT_TURNS]: { description: 'Number of turns taken by agents.', unit: 'turns', @@ -472,6 +480,7 @@ let agentDurationHistogram: Histogram | undefined; let agentTurnsHistogram: Histogram | undefined; let flickerFrameCounter: Counter | undefined; let exitFailCounter: Counter | undefined; +let slowRenderHistogram: Histogram | undefined; // OpenTelemetry GenAI Semantic Convention Metrics let genAiClientTokenUsageHistogram: Histogram | undefined; @@ -660,6 +669,16 @@ export function recordExitFail(config: Config): void { exitFailCounter.add(1, baseMetricDefinition.getCommonAttributes(config)); } +/** + * Records a metric for when a UI frame is slow in rendering + */ +export function recordSlowRender(config: Config, renderLatency: number): void { + if (!slowRenderHistogram || !isMetricsInitialized) return; + slowRenderHistogram.record(renderLatency, { + ...baseMetricDefinition.getCommonAttributes(config), + }); +} + /** * Records a metric for when an invalid chunk is received from a stream. */