mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-13 11:30:39 -07:00
2bc481a3b5
Decouples generic OpenTelemetry attributes from metric-specific attributes to resolve a Cloud Monitoring 'Internal error encountered' caused by a cardinality explosion. High-cardinality values like session.id, installation.id, user.email, and experiments.ids are now excluded from time series metric labels, but they are still preserved on traces and logs. Also refactors getCommonAttributes to use getCommonMetricAttributes to reduce repetition, and adds robust unit tests covering the truncation of the experiments.ids array and fallback cases.
1788 lines
55 KiB
TypeScript
1788 lines
55 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
diag,
|
|
metrics,
|
|
ValueType,
|
|
type Attributes,
|
|
type Meter,
|
|
type Counter,
|
|
type Histogram,
|
|
} from '@opentelemetry/api';
|
|
import { SERVICE_NAME } from './constants.js';
|
|
import type { Config } from '../config/config.js';
|
|
import type {
|
|
ModelRoutingEvent,
|
|
ModelSlashCommandEvent,
|
|
AgentFinishEvent,
|
|
RecoveryAttemptEvent,
|
|
KeychainAvailabilityEvent,
|
|
TokenStorageInitializationEvent,
|
|
} from './types.js';
|
|
import { AuthType } from '../core/contentGenerator.js';
|
|
import { getCommonMetricAttributes } from './telemetryAttributes.js';
|
|
import { sanitizeHookName } from './sanitize.js';
|
|
|
|
const EVENT_CHAT_COMPRESSION = 'gemini_cli.chat_compression';
|
|
const TOOL_CALL_COUNT = 'gemini_cli.tool.call.count';
|
|
const TOOL_CALL_LATENCY = 'gemini_cli.tool.call.latency';
|
|
const API_REQUEST_COUNT = 'gemini_cli.api.request.count';
|
|
const API_REQUEST_LATENCY = 'gemini_cli.api.request.latency';
|
|
const TOKEN_USAGE = 'gemini_cli.token.usage';
|
|
const SESSION_COUNT = 'gemini_cli.session.count';
|
|
const FILE_OPERATION_COUNT = 'gemini_cli.file.operation.count';
|
|
const LINES_CHANGED = 'gemini_cli.lines.changed';
|
|
const INVALID_CHUNK_COUNT = 'gemini_cli.chat.invalid_chunk.count';
|
|
const CONTENT_RETRY_COUNT = 'gemini_cli.chat.content_retry.count';
|
|
const CONTENT_RETRY_FAILURE_COUNT =
|
|
'gemini_cli.chat.content_retry_failure.count';
|
|
const NETWORK_RETRY_COUNT = 'gemini_cli.network_retry.count';
|
|
const MODEL_ROUTING_LATENCY = 'gemini_cli.model_routing.latency';
|
|
const MODEL_ROUTING_FAILURE_COUNT = 'gemini_cli.model_routing.failure.count';
|
|
const MODEL_SLASH_COMMAND_CALL_COUNT =
|
|
'gemini_cli.slash_command.model.call_count';
|
|
const EVENT_HOOK_CALL_COUNT = 'gemini_cli.hook_call.count';
|
|
const EVENT_HOOK_CALL_LATENCY = 'gemini_cli.hook_call.latency';
|
|
const KEYCHAIN_AVAILABILITY_COUNT = 'gemini_cli.keychain.availability.count';
|
|
const TOKEN_STORAGE_TYPE_COUNT = 'gemini_cli.token_storage.type.count';
|
|
const OVERAGE_OPTION_COUNT = 'gemini_cli.overage_option.count';
|
|
const CREDIT_PURCHASE_COUNT = 'gemini_cli.credit_purchase.count';
|
|
const EVENT_ONBOARDING_START = 'gemini_cli.onboarding.start';
|
|
const EVENT_ONBOARDING_SUCCESS = 'gemini_cli.onboarding.success';
|
|
const EVENT_ONBOARDING_DURATION_MS = 'gemini_cli.onboarding.duration';
|
|
|
|
// Agent Metrics
|
|
const AGENT_RUN_COUNT = 'gemini_cli.agent.run.count';
|
|
const AGENT_DURATION_MS = 'gemini_cli.agent.duration';
|
|
const AGENT_TURNS = 'gemini_cli.agent.turns';
|
|
const AGENT_RECOVERY_ATTEMPT_COUNT = 'gemini_cli.agent.recovery_attempt.count';
|
|
const AGENT_RECOVERY_ATTEMPT_DURATION =
|
|
'gemini_cli.agent.recovery_attempt.duration';
|
|
|
|
// Browser Agent Metrics
|
|
const BROWSER_AGENT_CONNECTION_DURATION =
|
|
'gemini_cli.browser_agent.connection.duration';
|
|
const BROWSER_AGENT_CONNECTION_FAILURE_COUNT =
|
|
'gemini_cli.browser_agent.connection.failure.count';
|
|
const BROWSER_AGENT_TOOLS_DISCOVERED =
|
|
'gemini_cli.browser_agent.tools.discovered';
|
|
const BROWSER_AGENT_TOOLS_MISSING_SEMANTIC =
|
|
'gemini_cli.browser_agent.tools.missing_semantic';
|
|
const BROWSER_AGENT_VISION_STATUS = 'gemini_cli.browser_agent.vision.status';
|
|
const BROWSER_AGENT_TASK_OUTCOME = 'gemini_cli.browser_agent.task.outcome';
|
|
const BROWSER_AGENT_TASK_DURATION = 'gemini_cli.browser_agent.task.duration';
|
|
const BROWSER_AGENT_CLEANUP_DURATION =
|
|
'gemini_cli.browser_agent.cleanup.duration';
|
|
const BROWSER_AGENT_CLEANUP_FAILURE_COUNT =
|
|
'gemini_cli.browser_agent.cleanup.failure.count';
|
|
|
|
// OpenTelemetry GenAI Semantic Convention Metrics
|
|
const GEN_AI_CLIENT_TOKEN_USAGE = 'gen_ai.client.token.usage';
|
|
const GEN_AI_CLIENT_OPERATION_DURATION = 'gen_ai.client.operation.duration';
|
|
|
|
// Performance Monitoring Metrics
|
|
const STARTUP_TIME = 'gemini_cli.startup.duration';
|
|
const MEMORY_USAGE = 'gemini_cli.memory.usage';
|
|
const CPU_USAGE = 'gemini_cli.cpu.usage';
|
|
const EVENT_LOOP_DELAY = 'gemini_cli.event_loop.delay';
|
|
const TOOL_QUEUE_DEPTH = 'gemini_cli.tool.queue.depth';
|
|
const TOOL_EXECUTION_BREAKDOWN = 'gemini_cli.tool.execution.breakdown';
|
|
const TOKEN_EFFICIENCY = 'gemini_cli.token.efficiency';
|
|
const API_REQUEST_BREAKDOWN = 'gemini_cli.api.request.breakdown';
|
|
const PERFORMANCE_SCORE = 'gemini_cli.performance.score';
|
|
const REGRESSION_DETECTION = 'gemini_cli.performance.regression';
|
|
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 PLAN_EXECUTION_COUNT = 'gemini_cli.plan.execution.count';
|
|
|
|
const baseMetricDefinition = {
|
|
getCommonAttributes: getCommonMetricAttributes,
|
|
};
|
|
|
|
const COUNTER_DEFINITIONS = {
|
|
[TOOL_CALL_COUNT]: {
|
|
description: 'Counts tool calls, tagged by function name and success.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (toolCallCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
function_name: string;
|
|
success: boolean;
|
|
decision?: 'accept' | 'reject' | 'modify' | 'auto_accept';
|
|
tool_type?: 'native' | 'mcp';
|
|
},
|
|
},
|
|
[API_REQUEST_COUNT]: {
|
|
description: 'Counts API requests, tagged by model and status.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (apiRequestCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
model: string;
|
|
status_code?: number | string;
|
|
error_type?: string;
|
|
},
|
|
},
|
|
[TOKEN_USAGE]: {
|
|
description: 'Counts the total number of tokens used.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (tokenUsageCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
model: string;
|
|
type: 'input' | 'output' | 'thought' | 'cache' | 'tool';
|
|
},
|
|
},
|
|
[SESSION_COUNT]: {
|
|
description: 'Count of CLI sessions started.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (sessionCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[FILE_OPERATION_COUNT]: {
|
|
description: 'Counts file operations (create, read, update).',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (fileOperationCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
operation: FileOperation;
|
|
lines?: number;
|
|
mimetype?: string;
|
|
extension?: string;
|
|
programming_language?: string;
|
|
},
|
|
},
|
|
[LINES_CHANGED]: {
|
|
description: 'Number of lines changed (from file diffs).',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (linesChangedCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
function_name?: string;
|
|
type: 'added' | 'removed';
|
|
},
|
|
},
|
|
[INVALID_CHUNK_COUNT]: {
|
|
description: 'Counts invalid chunks received from a stream.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (invalidChunkCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[CONTENT_RETRY_COUNT]: {
|
|
description: 'Counts retries due to content errors (e.g., empty stream).',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (contentRetryCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[CONTENT_RETRY_FAILURE_COUNT]: {
|
|
description: 'Counts occurrences of all content retries failing.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (contentRetryFailureCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[NETWORK_RETRY_COUNT]: {
|
|
description: 'Counts network retries.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (networkRetryCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
model: string;
|
|
attempt: number;
|
|
},
|
|
},
|
|
[MODEL_ROUTING_FAILURE_COUNT]: {
|
|
description: 'Counts model routing failures.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (modelRoutingFailureCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
'routing.decision_source': string;
|
|
'routing.error_message': string;
|
|
},
|
|
},
|
|
[MODEL_SLASH_COMMAND_CALL_COUNT]: {
|
|
description: 'Counts model slash command calls.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (modelSlashCommandCallCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
'slash_command.model.model_name': string;
|
|
},
|
|
},
|
|
[EVENT_CHAT_COMPRESSION]: {
|
|
description: 'Counts chat compression events.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (chatCompressionCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
tokens_before: number;
|
|
tokens_after: number;
|
|
},
|
|
},
|
|
[AGENT_RUN_COUNT]: {
|
|
description: 'Counts agent runs, tagged by name and termination reason.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (agentRunCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
agent_name: string;
|
|
terminate_reason: string;
|
|
},
|
|
},
|
|
[AGENT_RECOVERY_ATTEMPT_COUNT]: {
|
|
description: 'Counts agent recovery attempts.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (agentRecoveryAttemptCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
agent_name: string;
|
|
reason: string;
|
|
success: boolean;
|
|
},
|
|
},
|
|
[FLICKER_FRAME_COUNT]: {
|
|
description:
|
|
'Counts UI frames that flicker (render taller than the terminal).',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (flickerFrameCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[EXIT_FAIL_COUNT]: {
|
|
description: 'Counts CLI exit failures.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (exitFailCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[PLAN_EXECUTION_COUNT]: {
|
|
description: 'Counts plan executions (switching from Plan Mode).',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (planExecutionCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
approval_mode: string;
|
|
},
|
|
},
|
|
[EVENT_HOOK_CALL_COUNT]: {
|
|
description: 'Counts hook calls, tagged by hook event name and success.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (hookCallCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
hook_event_name: string;
|
|
hook_name: string;
|
|
success: boolean;
|
|
},
|
|
},
|
|
[KEYCHAIN_AVAILABILITY_COUNT]: {
|
|
description: 'Counts keychain availability checks.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (keychainAvailabilityCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
available: boolean;
|
|
},
|
|
},
|
|
[TOKEN_STORAGE_TYPE_COUNT]: {
|
|
description: 'Counts token storage type initializations.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (tokenStorageTypeCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
type: string;
|
|
forced: boolean;
|
|
},
|
|
},
|
|
[OVERAGE_OPTION_COUNT]: {
|
|
description: 'Counts overage option selections.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (overageOptionCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
selected_option: string;
|
|
model: string;
|
|
},
|
|
},
|
|
[CREDIT_PURCHASE_COUNT]: {
|
|
description: 'Counts credit purchase link clicks.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (creditPurchaseCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
source: string;
|
|
model: string;
|
|
},
|
|
},
|
|
[BROWSER_AGENT_CONNECTION_FAILURE_COUNT]: {
|
|
description: 'Counts browser agent MCP connection failures.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (browserAgentConnectionFailureCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
headless: boolean;
|
|
error_type:
|
|
| 'profile_locked'
|
|
| 'timeout'
|
|
| 'connection_refused'
|
|
| 'unknown';
|
|
},
|
|
},
|
|
[BROWSER_AGENT_TOOLS_MISSING_SEMANTIC]: {
|
|
description: 'Counts missing required semantic tools discovered from MCP.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (browserAgentToolsMissingSemanticCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as { tool_name: string },
|
|
},
|
|
[BROWSER_AGENT_VISION_STATUS]: {
|
|
description: 'Counts browser agent invocations by vision status.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (browserAgentVisionStatusCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
enabled: boolean;
|
|
disabled_reason?:
|
|
| 'no_visual_model'
|
|
| 'missing_visual_tools'
|
|
| 'blocked_auth_type';
|
|
},
|
|
},
|
|
[BROWSER_AGENT_TASK_OUTCOME]: {
|
|
description: 'Counts browser agent task outcomes.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (browserAgentTaskOutcomeCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
success: boolean;
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
vision_enabled: boolean;
|
|
headless: boolean;
|
|
},
|
|
},
|
|
[BROWSER_AGENT_CLEANUP_FAILURE_COUNT]: {
|
|
description: 'Counts browser agent cleanup failures.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (browserAgentCleanupFailureCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
},
|
|
},
|
|
[EVENT_ONBOARDING_START]: {
|
|
description: 'Counts onboarding started',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (onboardingStartCounter = c),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[EVENT_ONBOARDING_SUCCESS]: {
|
|
description: 'Counts onboarding succeeded',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (onboardingSuccessCounter = c),
|
|
attributes: {} as {
|
|
user_tier?: string;
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
const HISTOGRAM_DEFINITIONS = {
|
|
[TOOL_CALL_LATENCY]: {
|
|
description: 'Latency of tool calls in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (toolCallLatencyHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
function_name: string;
|
|
},
|
|
},
|
|
[API_REQUEST_LATENCY]: {
|
|
description: 'Latency of API requests in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (apiRequestLatencyHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
model: string;
|
|
},
|
|
},
|
|
[MODEL_ROUTING_LATENCY]: {
|
|
description: 'Latency of model routing decisions in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (modelRoutingLatencyHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
'routing.decision_model': string;
|
|
'routing.decision_source': string;
|
|
},
|
|
},
|
|
[AGENT_DURATION_MS]: {
|
|
description: 'Duration of agent runs in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (agentDurationHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
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<string, never>,
|
|
},
|
|
[AGENT_TURNS]: {
|
|
description: 'Number of turns taken by agents.',
|
|
unit: 'turns',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (agentTurnsHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
agent_name: string;
|
|
},
|
|
},
|
|
[AGENT_RECOVERY_ATTEMPT_DURATION]: {
|
|
description: 'Duration of agent recovery attempts in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (agentRecoveryAttemptDurationHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
agent_name: string;
|
|
},
|
|
},
|
|
[GEN_AI_CLIENT_TOKEN_USAGE]: {
|
|
description: 'Number of input and output tokens used.',
|
|
unit: 'token',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (genAiClientTokenUsageHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
'gen_ai.operation.name': string;
|
|
'gen_ai.provider.name': string;
|
|
'gen_ai.token.type': 'input' | 'output';
|
|
'gen_ai.request.model'?: string;
|
|
'gen_ai.response.model'?: string;
|
|
'server.address'?: string;
|
|
'server.port'?: number;
|
|
},
|
|
},
|
|
[GEN_AI_CLIENT_OPERATION_DURATION]: {
|
|
description: 'GenAI operation duration.',
|
|
unit: 's',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (genAiClientOperationDurationHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
'gen_ai.operation.name': string;
|
|
'gen_ai.provider.name': string;
|
|
'gen_ai.request.model'?: string;
|
|
'gen_ai.response.model'?: string;
|
|
'server.address'?: string;
|
|
'server.port'?: number;
|
|
'error.type'?: string;
|
|
},
|
|
},
|
|
[EVENT_HOOK_CALL_LATENCY]: {
|
|
description: 'Latency of hook calls in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Histogram) => (hookCallLatencyHistogram = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
hook_event_name: string;
|
|
hook_name: string;
|
|
success: boolean;
|
|
},
|
|
},
|
|
[BROWSER_AGENT_CONNECTION_DURATION]: {
|
|
description:
|
|
'Duration of browser agent MCP connection setup in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (browserAgentConnectionDurationHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
headless: boolean;
|
|
success: boolean;
|
|
},
|
|
},
|
|
[BROWSER_AGENT_TOOLS_DISCOVERED]: {
|
|
description: 'Count of tools discovered from chrome-devtools-mcp.',
|
|
unit: 'tools',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (browserAgentToolsDiscoveredHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
},
|
|
},
|
|
[BROWSER_AGENT_TASK_DURATION]: {
|
|
description:
|
|
'Full invocation duration of browser agent (connect + run + cleanup) in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (browserAgentTaskDurationHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
success: boolean;
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
},
|
|
},
|
|
[BROWSER_AGENT_CLEANUP_DURATION]: {
|
|
description: 'Duration of browser agent cleanup in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (browserAgentCleanupDurationHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
},
|
|
},
|
|
[EVENT_ONBOARDING_DURATION_MS]: {
|
|
description: 'Duration of onboarding in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (onboardingDurationHistogram = h),
|
|
attributes: {} as {
|
|
user_tier?: string;
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
const PERFORMANCE_COUNTER_DEFINITIONS = {
|
|
[REGRESSION_DETECTION]: {
|
|
description: 'Performance regression detection events.',
|
|
valueType: ValueType.INT,
|
|
assign: (c: Counter) => (regressionDetectionCounter = c),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
metric: string;
|
|
severity: 'low' | 'medium' | 'high';
|
|
current_value: number;
|
|
baseline_value: number;
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
const PERFORMANCE_HISTOGRAM_DEFINITIONS = {
|
|
[STARTUP_TIME]: {
|
|
description:
|
|
'CLI startup time in milliseconds, broken down by initialization phase.',
|
|
unit: 'ms',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (startupTimeHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
phase: string;
|
|
details?: Record<string, string | number | boolean>;
|
|
},
|
|
},
|
|
[MEMORY_USAGE]: {
|
|
description: 'Memory usage in bytes.',
|
|
unit: 'bytes',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (memoryUsageGauge = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
memory_type: MemoryMetricType;
|
|
component?: string;
|
|
},
|
|
},
|
|
[CPU_USAGE]: {
|
|
description: 'CPU usage percentage.',
|
|
unit: 'percent',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (cpuUsageGauge = h),
|
|
attributes: {} as {
|
|
component?: string;
|
|
},
|
|
},
|
|
[EVENT_LOOP_DELAY]: {
|
|
description: 'Event loop delay in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (eventLoopDelayHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
percentile: string;
|
|
component?: string;
|
|
},
|
|
},
|
|
[TOOL_QUEUE_DEPTH]: {
|
|
description: 'Number of tools in execution queue.',
|
|
unit: 'count',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (toolQueueDepthGauge = h),
|
|
attributes: {} as Record<string, never>,
|
|
},
|
|
[TOOL_EXECUTION_BREAKDOWN]: {
|
|
description: 'Tool execution time breakdown by phase in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (toolExecutionBreakdownHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
function_name: string;
|
|
phase: ToolExecutionPhase;
|
|
},
|
|
},
|
|
[TOKEN_EFFICIENCY]: {
|
|
description:
|
|
'Token efficiency metrics (tokens per operation, cache hit rate, etc.).',
|
|
unit: 'ratio',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (tokenEfficiencyHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
model: string;
|
|
metric: string;
|
|
context?: string;
|
|
},
|
|
},
|
|
[API_REQUEST_BREAKDOWN]: {
|
|
description: 'API request time breakdown by phase in milliseconds.',
|
|
unit: 'ms',
|
|
valueType: ValueType.INT,
|
|
assign: (h: Histogram) => (apiRequestBreakdownHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
model: string;
|
|
phase: ApiRequestPhase;
|
|
},
|
|
},
|
|
[PERFORMANCE_SCORE]: {
|
|
description: 'Composite performance score (0-100).',
|
|
unit: 'score',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (performanceScoreGauge = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
category: string;
|
|
baseline?: number;
|
|
},
|
|
},
|
|
[REGRESSION_PERCENTAGE_CHANGE]: {
|
|
description:
|
|
'Percentage change compared to baseline for detected regressions.',
|
|
unit: 'percent',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (regressionPercentageChangeHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
metric: string;
|
|
severity: 'low' | 'medium' | 'high';
|
|
current_value: number;
|
|
baseline_value: number;
|
|
},
|
|
},
|
|
[BASELINE_COMPARISON]: {
|
|
description:
|
|
'Performance comparison to established baseline (percentage change).',
|
|
unit: 'percent',
|
|
valueType: ValueType.DOUBLE,
|
|
assign: (h: Histogram) => (baselineComparisonHistogram = h),
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
attributes: {} as {
|
|
metric: string;
|
|
category: string;
|
|
current_value: number;
|
|
baseline_value: number;
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
type AllMetricDefs = typeof COUNTER_DEFINITIONS &
|
|
typeof HISTOGRAM_DEFINITIONS &
|
|
typeof PERFORMANCE_COUNTER_DEFINITIONS &
|
|
typeof PERFORMANCE_HISTOGRAM_DEFINITIONS;
|
|
|
|
export type MetricDefinitions = {
|
|
[K in keyof AllMetricDefs]: {
|
|
attributes: AllMetricDefs[K]['attributes'];
|
|
};
|
|
};
|
|
|
|
export enum FileOperation {
|
|
CREATE = 'create',
|
|
READ = 'read',
|
|
UPDATE = 'update',
|
|
}
|
|
|
|
export enum PerformanceMetricType {
|
|
STARTUP = 'startup',
|
|
MEMORY = 'memory',
|
|
CPU = 'cpu',
|
|
TOOL_EXECUTION = 'tool_execution',
|
|
API_REQUEST = 'api_request',
|
|
TOKEN_EFFICIENCY = 'token_efficiency',
|
|
}
|
|
|
|
export enum MemoryMetricType {
|
|
HEAP_USED = 'heap_used',
|
|
HEAP_TOTAL = 'heap_total',
|
|
EXTERNAL = 'external',
|
|
RSS = 'rss',
|
|
}
|
|
|
|
export enum ToolExecutionPhase {
|
|
VALIDATION = 'validation',
|
|
PREPARATION = 'preparation',
|
|
EXECUTION = 'execution',
|
|
RESULT_PROCESSING = 'result_processing',
|
|
}
|
|
|
|
export enum ApiRequestPhase {
|
|
REQUEST_PREPARATION = 'request_preparation',
|
|
NETWORK_LATENCY = 'network_latency',
|
|
RESPONSE_PROCESSING = 'response_processing',
|
|
TOKEN_PROCESSING = 'token_processing',
|
|
}
|
|
|
|
export enum GenAiOperationName {
|
|
GENERATE_CONTENT = 'generate_content',
|
|
}
|
|
|
|
export enum GenAiProviderName {
|
|
GCP_GEN_AI = 'gcp.gen_ai',
|
|
GCP_VERTEX_AI = 'gcp.vertex_ai',
|
|
}
|
|
|
|
export enum GenAiTokenType {
|
|
INPUT = 'input',
|
|
OUTPUT = 'output',
|
|
}
|
|
|
|
let cliMeter: Meter | undefined;
|
|
let toolCallCounter: Counter | undefined;
|
|
let toolCallLatencyHistogram: Histogram | undefined;
|
|
let apiRequestCounter: Counter | undefined;
|
|
let apiRequestLatencyHistogram: Histogram | undefined;
|
|
let tokenUsageCounter: Counter | undefined;
|
|
let sessionCounter: Counter | undefined;
|
|
let fileOperationCounter: Counter | undefined;
|
|
let linesChangedCounter: Counter | undefined;
|
|
let chatCompressionCounter: Counter | undefined;
|
|
let invalidChunkCounter: Counter | undefined;
|
|
let contentRetryCounter: Counter | undefined;
|
|
let contentRetryFailureCounter: Counter | undefined;
|
|
let networkRetryCounter: Counter | undefined;
|
|
let modelRoutingLatencyHistogram: Histogram | undefined;
|
|
let modelRoutingFailureCounter: Counter | undefined;
|
|
let modelSlashCommandCallCounter: Counter | undefined;
|
|
let agentRunCounter: Counter | undefined;
|
|
let agentDurationHistogram: Histogram | undefined;
|
|
let agentTurnsHistogram: Histogram | undefined;
|
|
let agentRecoveryAttemptCounter: Counter | undefined;
|
|
let agentRecoveryAttemptDurationHistogram: Histogram | undefined;
|
|
let flickerFrameCounter: Counter | undefined;
|
|
let exitFailCounter: Counter | undefined;
|
|
let planExecutionCounter: Counter | undefined;
|
|
let slowRenderHistogram: Histogram | undefined;
|
|
let hookCallCounter: Counter | undefined;
|
|
let hookCallLatencyHistogram: Histogram | undefined;
|
|
let keychainAvailabilityCounter: Counter | undefined;
|
|
let tokenStorageTypeCounter: Counter | undefined;
|
|
let overageOptionCounter: Counter | undefined;
|
|
let creditPurchaseCounter: Counter | undefined;
|
|
let onboardingStartCounter: Counter | undefined;
|
|
let onboardingSuccessCounter: Counter | undefined;
|
|
let onboardingDurationHistogram: Histogram | undefined;
|
|
|
|
let browserAgentConnectionDurationHistogram: Histogram | undefined;
|
|
let browserAgentConnectionFailureCounter: Counter | undefined;
|
|
let browserAgentToolsDiscoveredHistogram: Histogram | undefined;
|
|
let browserAgentToolsMissingSemanticCounter: Counter | undefined;
|
|
let browserAgentVisionStatusCounter: Counter | undefined;
|
|
let browserAgentTaskOutcomeCounter: Counter | undefined;
|
|
let browserAgentTaskDurationHistogram: Histogram | undefined;
|
|
let browserAgentCleanupDurationHistogram: Histogram | undefined;
|
|
let browserAgentCleanupFailureCounter: Counter | undefined;
|
|
|
|
// OpenTelemetry GenAI Semantic Convention Metrics
|
|
let genAiClientTokenUsageHistogram: Histogram | undefined;
|
|
let genAiClientOperationDurationHistogram: Histogram | undefined;
|
|
|
|
// Performance Monitoring Metrics
|
|
let startupTimeHistogram: Histogram | undefined;
|
|
let memoryUsageGauge: Histogram | undefined; // Using Histogram until ObservableGauge is available
|
|
let cpuUsageGauge: Histogram | undefined;
|
|
let eventLoopDelayHistogram: Histogram | undefined;
|
|
let toolQueueDepthGauge: Histogram | undefined;
|
|
let toolExecutionBreakdownHistogram: Histogram | undefined;
|
|
let tokenEfficiencyHistogram: Histogram | undefined;
|
|
let apiRequestBreakdownHistogram: Histogram | undefined;
|
|
let performanceScoreGauge: Histogram | undefined;
|
|
let regressionDetectionCounter: Counter | undefined;
|
|
let regressionPercentageChangeHistogram: Histogram | undefined;
|
|
let baselineComparisonHistogram: Histogram | undefined;
|
|
let isMetricsInitialized = false;
|
|
let isPerformanceMonitoringEnabled = false;
|
|
|
|
function getMeter(): Meter | undefined {
|
|
if (!cliMeter) {
|
|
cliMeter = metrics.getMeter(SERVICE_NAME);
|
|
}
|
|
return cliMeter;
|
|
}
|
|
|
|
export function initializeMetrics(config: Config): void {
|
|
if (isMetricsInitialized) return;
|
|
|
|
const meter = getMeter();
|
|
if (!meter) return;
|
|
|
|
// Initialize core metrics
|
|
Object.entries(COUNTER_DEFINITIONS).forEach(
|
|
([name, { description, valueType, assign }]) => {
|
|
assign(meter.createCounter(name, { description, valueType }));
|
|
},
|
|
);
|
|
|
|
Object.entries(HISTOGRAM_DEFINITIONS).forEach(
|
|
([name, { description, unit, valueType, assign }]) => {
|
|
assign(meter.createHistogram(name, { description, unit, valueType }));
|
|
},
|
|
);
|
|
|
|
// Increment session counter after all metrics are initialized
|
|
sessionCounter?.add(1, baseMetricDefinition.getCommonAttributes(config));
|
|
|
|
// Initialize performance monitoring metrics if enabled
|
|
initializePerformanceMonitoring(config);
|
|
|
|
isMetricsInitialized = true;
|
|
}
|
|
|
|
export function recordChatCompressionMetrics(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof EVENT_CHAT_COMPRESSION]['attributes'],
|
|
) {
|
|
if (!chatCompressionCounter || !isMetricsInitialized) return;
|
|
chatCompressionCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
export function recordToolCallMetrics(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: MetricDefinitions[typeof TOOL_CALL_COUNT]['attributes'],
|
|
): void {
|
|
if (!toolCallCounter || !toolCallLatencyHistogram || !isMetricsInitialized)
|
|
return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
toolCallCounter.add(1, metricAttributes);
|
|
toolCallLatencyHistogram.record(durationMs, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
function_name: attributes.function_name,
|
|
});
|
|
}
|
|
|
|
export function recordCustomTokenUsageMetrics(
|
|
config: Config,
|
|
tokenCount: number,
|
|
attributes: MetricDefinitions[typeof TOKEN_USAGE]['attributes'],
|
|
): void {
|
|
if (!tokenUsageCounter || !isMetricsInitialized) return;
|
|
tokenUsageCounter.add(tokenCount, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
export function recordCustomApiResponseMetrics(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: MetricDefinitions[typeof API_REQUEST_COUNT]['attributes'],
|
|
): void {
|
|
if (
|
|
!apiRequestCounter ||
|
|
!apiRequestLatencyHistogram ||
|
|
!isMetricsInitialized
|
|
)
|
|
return;
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
model: attributes.model,
|
|
status_code: attributes.status_code ?? 'ok',
|
|
};
|
|
apiRequestCounter.add(1, metricAttributes);
|
|
apiRequestLatencyHistogram.record(durationMs, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
model: attributes.model,
|
|
});
|
|
}
|
|
|
|
export function recordApiErrorMetrics(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: MetricDefinitions[typeof API_REQUEST_COUNT]['attributes'],
|
|
): void {
|
|
if (
|
|
!apiRequestCounter ||
|
|
!apiRequestLatencyHistogram ||
|
|
!isMetricsInitialized
|
|
)
|
|
return;
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
model: attributes.model,
|
|
status_code: attributes.status_code ?? 'error',
|
|
error_type: attributes.error_type ?? 'unknown',
|
|
};
|
|
apiRequestCounter.add(1, metricAttributes);
|
|
apiRequestLatencyHistogram.record(durationMs, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
model: attributes.model,
|
|
});
|
|
}
|
|
|
|
export function recordFileOperationMetric(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof FILE_OPERATION_COUNT]['attributes'],
|
|
): void {
|
|
if (!fileOperationCounter || !isMetricsInitialized) return;
|
|
fileOperationCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
export function recordLinesChanged(
|
|
config: Config,
|
|
lines: number,
|
|
changeType: 'added' | 'removed',
|
|
attributes?: { function_name?: string },
|
|
): void {
|
|
if (!linesChangedCounter || !isMetricsInitialized) return;
|
|
if (!Number.isFinite(lines) || lines <= 0) return;
|
|
linesChangedCounter.add(lines, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
type: changeType,
|
|
...(attributes ?? {}),
|
|
});
|
|
}
|
|
|
|
// --- New Metric Recording Functions ---
|
|
|
|
/**
|
|
* Records a metric for when the Google auth process starts.
|
|
*/
|
|
export function recordOnboardingStart(config: Config): void {
|
|
if (!onboardingStartCounter || !isMetricsInitialized) return;
|
|
onboardingStartCounter.add(
|
|
1,
|
|
baseMetricDefinition.getCommonAttributes(config),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Records a metric for when the Google auth process ends successfully.
|
|
*/
|
|
export function recordOnboardingSuccess(
|
|
config: Config,
|
|
userTier?: string,
|
|
durationMs?: number,
|
|
): void {
|
|
if (!isMetricsInitialized) return;
|
|
|
|
const attributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...(userTier && { user_tier: userTier }),
|
|
};
|
|
|
|
if (onboardingSuccessCounter) {
|
|
onboardingSuccessCounter.add(1, attributes);
|
|
}
|
|
|
|
if (durationMs !== undefined && onboardingDurationHistogram) {
|
|
onboardingDurationHistogram.record(durationMs, attributes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Records a metric for when a UI frame flickers.
|
|
*/
|
|
export function recordFlickerFrame(config: Config): void {
|
|
if (!flickerFrameCounter || !isMetricsInitialized) return;
|
|
flickerFrameCounter.add(1, baseMetricDefinition.getCommonAttributes(config));
|
|
}
|
|
|
|
/**
|
|
* Records a metric for when user failed to exit
|
|
*/
|
|
export function recordExitFail(config: Config): void {
|
|
if (!exitFailCounter || !isMetricsInitialized) return;
|
|
exitFailCounter.add(1, baseMetricDefinition.getCommonAttributes(config));
|
|
}
|
|
|
|
/**
|
|
* Records a metric for when a plan is executed.
|
|
*/
|
|
export function recordPlanExecution(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof PLAN_EXECUTION_COUNT]['attributes'],
|
|
): void {
|
|
if (!planExecutionCounter || !isMetricsInitialized) return;
|
|
planExecutionCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
export function recordInvalidChunk(config: Config): void {
|
|
if (!invalidChunkCounter || !isMetricsInitialized) return;
|
|
invalidChunkCounter.add(1, baseMetricDefinition.getCommonAttributes(config));
|
|
}
|
|
|
|
export function recordRetryAttemptMetrics(
|
|
config: Config,
|
|
attributes: {
|
|
model: string;
|
|
attempt: number;
|
|
},
|
|
): void {
|
|
if (!networkRetryCounter || !isMetricsInitialized) return;
|
|
networkRetryCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Records a metric for when a retry is triggered due to a content error.
|
|
*/
|
|
export function recordContentRetry(config: Config): void {
|
|
if (!contentRetryCounter || !isMetricsInitialized) return;
|
|
contentRetryCounter.add(1, baseMetricDefinition.getCommonAttributes(config));
|
|
}
|
|
|
|
/**
|
|
* Records a metric for when all content error retries have failed for a request.
|
|
*/
|
|
export function recordContentRetryFailure(config: Config): void {
|
|
if (!contentRetryFailureCounter || !isMetricsInitialized) return;
|
|
contentRetryFailureCounter.add(
|
|
1,
|
|
baseMetricDefinition.getCommonAttributes(config),
|
|
);
|
|
}
|
|
|
|
export function recordModelSlashCommand(
|
|
config: Config,
|
|
event: ModelSlashCommandEvent,
|
|
): void {
|
|
if (!modelSlashCommandCallCounter || !isMetricsInitialized) return;
|
|
modelSlashCommandCallCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
'slash_command.model.model_name': event.model_name,
|
|
});
|
|
}
|
|
|
|
export function recordModelRoutingMetrics(
|
|
config: Config,
|
|
event: ModelRoutingEvent,
|
|
): void {
|
|
if (
|
|
!modelRoutingLatencyHistogram ||
|
|
!modelRoutingFailureCounter ||
|
|
!isMetricsInitialized
|
|
)
|
|
return;
|
|
|
|
const attributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
'routing.decision_model': event.decision_model,
|
|
'routing.decision_source': event.decision_source,
|
|
'routing.failed': event.failed,
|
|
'routing.approval_mode': event.approval_mode,
|
|
};
|
|
|
|
if (event.reasoning) {
|
|
attributes['routing.reasoning'] = event.reasoning;
|
|
}
|
|
if (event.enable_numerical_routing !== undefined) {
|
|
attributes['routing.enable_numerical_routing'] =
|
|
event.enable_numerical_routing;
|
|
}
|
|
if (event.classifier_threshold) {
|
|
attributes['routing.classifier_threshold'] = event.classifier_threshold;
|
|
}
|
|
|
|
modelRoutingLatencyHistogram.record(event.routing_latency_ms, attributes);
|
|
|
|
if (event.failed) {
|
|
modelRoutingFailureCounter.add(1, {
|
|
...attributes,
|
|
'routing.error_message': event.error_message,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function recordAgentRunMetrics(
|
|
config: Config,
|
|
event: AgentFinishEvent,
|
|
): void {
|
|
if (
|
|
!agentRunCounter ||
|
|
!agentDurationHistogram ||
|
|
!agentTurnsHistogram ||
|
|
!isMetricsInitialized
|
|
)
|
|
return;
|
|
|
|
const commonAttributes = baseMetricDefinition.getCommonAttributes(config);
|
|
|
|
agentRunCounter.add(1, {
|
|
...commonAttributes,
|
|
agent_name: event.agent_name,
|
|
terminate_reason: event.terminate_reason,
|
|
});
|
|
|
|
agentDurationHistogram.record(event.duration_ms, {
|
|
...commonAttributes,
|
|
agent_name: event.agent_name,
|
|
});
|
|
|
|
agentTurnsHistogram.record(event.turn_count, {
|
|
...commonAttributes,
|
|
agent_name: event.agent_name,
|
|
});
|
|
}
|
|
|
|
export function recordRecoveryAttemptMetrics(
|
|
config: Config,
|
|
event: RecoveryAttemptEvent,
|
|
): void {
|
|
if (
|
|
!agentRecoveryAttemptCounter ||
|
|
!agentRecoveryAttemptDurationHistogram ||
|
|
!isMetricsInitialized
|
|
)
|
|
return;
|
|
|
|
const commonAttributes = baseMetricDefinition.getCommonAttributes(config);
|
|
|
|
agentRecoveryAttemptCounter.add(1, {
|
|
...commonAttributes,
|
|
agent_name: event.agent_name,
|
|
reason: event.reason,
|
|
success: event.success,
|
|
});
|
|
|
|
agentRecoveryAttemptDurationHistogram.record(event.duration_ms, {
|
|
...commonAttributes,
|
|
agent_name: event.agent_name,
|
|
});
|
|
}
|
|
|
|
// OpenTelemetry GenAI Semantic Convention Recording Functions
|
|
|
|
export function recordGenAiClientTokenUsage(
|
|
config: Config,
|
|
tokenCount: number,
|
|
attributes: MetricDefinitions[typeof GEN_AI_CLIENT_TOKEN_USAGE]['attributes'],
|
|
): void {
|
|
if (!genAiClientTokenUsageHistogram || !isMetricsInitialized) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
genAiClientTokenUsageHistogram.record(tokenCount, metricAttributes);
|
|
}
|
|
|
|
export function recordGenAiClientOperationDuration(
|
|
config: Config,
|
|
durationSeconds: number,
|
|
attributes: MetricDefinitions[typeof GEN_AI_CLIENT_OPERATION_DURATION]['attributes'],
|
|
): void {
|
|
if (!genAiClientOperationDurationHistogram || !isMetricsInitialized) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
genAiClientOperationDurationHistogram.record(
|
|
durationSeconds,
|
|
metricAttributes,
|
|
);
|
|
}
|
|
|
|
export function getConventionAttributes(event: {
|
|
model: string;
|
|
auth_type?: string;
|
|
}): {
|
|
'gen_ai.operation.name': GenAiOperationName;
|
|
'gen_ai.provider.name': GenAiProviderName;
|
|
'gen_ai.request.model': string;
|
|
'gen_ai.response.model': string;
|
|
} {
|
|
const operationName = getGenAiOperationName();
|
|
const provider = getGenAiProvider(event.auth_type);
|
|
|
|
return {
|
|
'gen_ai.operation.name': operationName,
|
|
'gen_ai.provider.name': provider,
|
|
'gen_ai.request.model': event.model,
|
|
'gen_ai.response.model': event.model,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Maps authentication type to GenAI provider name following OpenTelemetry conventions
|
|
*/
|
|
function getGenAiProvider(authType?: string): GenAiProviderName {
|
|
switch (authType) {
|
|
case AuthType.USE_VERTEX_AI:
|
|
case AuthType.COMPUTE_ADC:
|
|
case AuthType.LOGIN_WITH_GOOGLE:
|
|
return GenAiProviderName.GCP_VERTEX_AI;
|
|
case AuthType.USE_GEMINI:
|
|
default:
|
|
return GenAiProviderName.GCP_GEN_AI;
|
|
}
|
|
}
|
|
|
|
function getGenAiOperationName(): GenAiOperationName {
|
|
return GenAiOperationName.GENERATE_CONTENT;
|
|
}
|
|
|
|
// Performance Monitoring Functions
|
|
|
|
function initializePerformanceMonitoring(config: Config): void {
|
|
const meter = getMeter();
|
|
if (!meter) return;
|
|
|
|
// Check if performance monitoring is enabled in config
|
|
// For now, enable performance monitoring when telemetry is enabled
|
|
// TODO: Add specific performance monitoring settings to config
|
|
isPerformanceMonitoringEnabled = config.getTelemetryEnabled();
|
|
|
|
if (!isPerformanceMonitoringEnabled) return;
|
|
|
|
Object.entries(PERFORMANCE_COUNTER_DEFINITIONS).forEach(
|
|
([name, { description, valueType, assign }]) => {
|
|
assign(meter.createCounter(name, { description, valueType }));
|
|
},
|
|
);
|
|
|
|
Object.entries(PERFORMANCE_HISTOGRAM_DEFINITIONS).forEach(
|
|
([name, { description, unit, valueType, assign }]) => {
|
|
assign(meter.createHistogram(name, { description, unit, valueType }));
|
|
},
|
|
);
|
|
}
|
|
|
|
export function recordStartupPerformance(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: MetricDefinitions[typeof STARTUP_TIME]['attributes'],
|
|
): void {
|
|
if (!startupTimeHistogram || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
phase: attributes.phase,
|
|
...attributes.details,
|
|
};
|
|
|
|
startupTimeHistogram.record(durationMs, metricAttributes);
|
|
}
|
|
|
|
export function recordMemoryUsage(
|
|
config: Config,
|
|
bytes: number,
|
|
attributes: MetricDefinitions[typeof MEMORY_USAGE]['attributes'],
|
|
): void {
|
|
if (!memoryUsageGauge || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
memoryUsageGauge.record(bytes, metricAttributes);
|
|
}
|
|
|
|
export function recordCpuUsage(
|
|
config: Config,
|
|
percentage: number,
|
|
attributes: MetricDefinitions[typeof CPU_USAGE]['attributes'],
|
|
): void {
|
|
if (!cpuUsageGauge || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
cpuUsageGauge.record(percentage, metricAttributes);
|
|
}
|
|
|
|
export function recordEventLoopDelay(
|
|
config: Config,
|
|
delayMs: number,
|
|
attributes: MetricDefinitions[typeof EVENT_LOOP_DELAY]['attributes'],
|
|
): void {
|
|
if (!eventLoopDelayHistogram || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
eventLoopDelayHistogram.record(delayMs, metricAttributes);
|
|
}
|
|
|
|
export function recordToolQueueDepth(config: Config, queueDepth: number): void {
|
|
if (!toolQueueDepthGauge || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const attributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
};
|
|
|
|
toolQueueDepthGauge.record(queueDepth, attributes);
|
|
}
|
|
|
|
export function recordToolExecutionBreakdown(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: MetricDefinitions[typeof TOOL_EXECUTION_BREAKDOWN]['attributes'],
|
|
): void {
|
|
if (!toolExecutionBreakdownHistogram || !isPerformanceMonitoringEnabled)
|
|
return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
toolExecutionBreakdownHistogram.record(durationMs, metricAttributes);
|
|
}
|
|
|
|
export function recordTokenEfficiency(
|
|
config: Config,
|
|
value: number,
|
|
attributes: MetricDefinitions[typeof TOKEN_EFFICIENCY]['attributes'],
|
|
): void {
|
|
if (!tokenEfficiencyHistogram || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
tokenEfficiencyHistogram.record(value, metricAttributes);
|
|
}
|
|
|
|
export function recordApiRequestBreakdown(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: MetricDefinitions[typeof API_REQUEST_BREAKDOWN]['attributes'],
|
|
): void {
|
|
if (!apiRequestBreakdownHistogram || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
apiRequestBreakdownHistogram.record(durationMs, metricAttributes);
|
|
}
|
|
|
|
export function recordPerformanceScore(
|
|
config: Config,
|
|
score: number,
|
|
attributes: MetricDefinitions[typeof PERFORMANCE_SCORE]['attributes'],
|
|
): void {
|
|
if (!performanceScoreGauge || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
performanceScoreGauge.record(score, metricAttributes);
|
|
}
|
|
|
|
export function recordPerformanceRegression(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof REGRESSION_DETECTION]['attributes'],
|
|
): void {
|
|
if (!regressionDetectionCounter || !isPerformanceMonitoringEnabled) return;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
regressionDetectionCounter.add(1, metricAttributes);
|
|
|
|
if (attributes.baseline_value !== 0 && regressionPercentageChangeHistogram) {
|
|
const percentageChange =
|
|
((attributes.current_value - attributes.baseline_value) /
|
|
attributes.baseline_value) *
|
|
100;
|
|
regressionPercentageChangeHistogram.record(
|
|
percentageChange,
|
|
metricAttributes,
|
|
);
|
|
}
|
|
}
|
|
|
|
export function recordBaselineComparison(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof BASELINE_COMPARISON]['attributes'],
|
|
): void {
|
|
if (!baselineComparisonHistogram || !isPerformanceMonitoringEnabled) return;
|
|
|
|
if (attributes.baseline_value === 0) {
|
|
diag.warn('Baseline value is zero, skipping comparison.');
|
|
return;
|
|
}
|
|
const percentageChange =
|
|
((attributes.current_value - attributes.baseline_value) /
|
|
attributes.baseline_value) *
|
|
100;
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
};
|
|
|
|
baselineComparisonHistogram.record(percentageChange, metricAttributes);
|
|
}
|
|
|
|
// Utility function to check if performance monitoring is enabled
|
|
export function isPerformanceMonitoringActive(): boolean {
|
|
return isPerformanceMonitoringEnabled && isMetricsInitialized;
|
|
}
|
|
|
|
/**
|
|
* Token usage recording that emits both custom and convention metrics.
|
|
*/
|
|
export function recordTokenUsageMetrics(
|
|
config: Config,
|
|
tokenCount: number,
|
|
attributes: {
|
|
model: string;
|
|
type: 'input' | 'output' | 'thought' | 'cache' | 'tool';
|
|
genAiAttributes?: {
|
|
'gen_ai.operation.name': string;
|
|
'gen_ai.provider.name': string;
|
|
'gen_ai.request.model'?: string;
|
|
'gen_ai.response.model'?: string;
|
|
'server.address'?: string;
|
|
'server.port'?: number;
|
|
};
|
|
},
|
|
): void {
|
|
recordCustomTokenUsageMetrics(config, tokenCount, {
|
|
model: attributes.model,
|
|
type: attributes.type,
|
|
});
|
|
|
|
if (
|
|
(attributes.type === 'input' || attributes.type === 'output') &&
|
|
attributes.genAiAttributes
|
|
) {
|
|
recordGenAiClientTokenUsage(config, tokenCount, {
|
|
...attributes.genAiAttributes,
|
|
'gen_ai.token.type': attributes.type,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Operation latency recording that emits both custom and convention metrics.
|
|
*/
|
|
export function recordApiResponseMetrics(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: {
|
|
model: string;
|
|
status_code?: number | string;
|
|
genAiAttributes?: {
|
|
'gen_ai.operation.name': string;
|
|
'gen_ai.provider.name': string;
|
|
'gen_ai.request.model'?: string;
|
|
'gen_ai.response.model'?: string;
|
|
'server.address'?: string;
|
|
'server.port'?: number;
|
|
'error.type'?: string;
|
|
};
|
|
},
|
|
): void {
|
|
recordCustomApiResponseMetrics(config, durationMs, {
|
|
model: attributes.model,
|
|
status_code: attributes.status_code,
|
|
});
|
|
|
|
if (attributes.genAiAttributes) {
|
|
const durationSeconds = durationMs / 1000;
|
|
recordGenAiClientOperationDuration(config, durationSeconds, {
|
|
...attributes.genAiAttributes,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function recordHookCallMetrics(
|
|
config: Config,
|
|
hookEventName: string,
|
|
hookName: string,
|
|
durationMs: number,
|
|
success: boolean,
|
|
): void {
|
|
if (!hookCallCounter || !hookCallLatencyHistogram || !isMetricsInitialized)
|
|
return;
|
|
|
|
// Always sanitize hook names in metrics (metrics are aggregated and exposed)
|
|
const sanitizedHookName = sanitizeHookName(hookName);
|
|
|
|
const metricAttributes: Attributes = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
hook_event_name: hookEventName,
|
|
hook_name: sanitizedHookName,
|
|
success,
|
|
};
|
|
|
|
hookCallCounter.add(1, metricAttributes);
|
|
hookCallLatencyHistogram.record(durationMs, metricAttributes);
|
|
}
|
|
|
|
/**
|
|
* Records a metric for keychain availability.
|
|
*/
|
|
export function recordKeychainAvailability(
|
|
config: Config,
|
|
event: KeychainAvailabilityEvent,
|
|
): void {
|
|
if (!keychainAvailabilityCounter || !isMetricsInitialized) return;
|
|
keychainAvailabilityCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
available: event.available,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Records a metric for token storage type initialization.
|
|
*/
|
|
export function recordTokenStorageInitialization(
|
|
config: Config,
|
|
event: TokenStorageInitializationEvent,
|
|
): void {
|
|
if (!tokenStorageTypeCounter || !isMetricsInitialized) return;
|
|
tokenStorageTypeCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
type: event.type,
|
|
forced: event.forced,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Records a metric for an overage option selection.
|
|
*/
|
|
export function recordOverageOptionSelected(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof OVERAGE_OPTION_COUNT]['attributes'],
|
|
): void {
|
|
if (!overageOptionCounter || !isMetricsInitialized) return;
|
|
overageOptionCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Records a metric for a credit purchase link click.
|
|
*/
|
|
export function recordCreditPurchaseClick(
|
|
config: Config,
|
|
attributes: MetricDefinitions[typeof CREDIT_PURCHASE_COUNT]['attributes'],
|
|
): void {
|
|
if (!creditPurchaseCounter || !isMetricsInitialized) return;
|
|
creditPurchaseCounter.add(1, {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
...attributes,
|
|
});
|
|
}
|
|
|
|
export function recordBrowserAgentConnection(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
headless: boolean;
|
|
success: boolean;
|
|
error_type?:
|
|
| 'profile_locked'
|
|
| 'timeout'
|
|
| 'connection_refused'
|
|
| 'unknown';
|
|
tool_count?: number;
|
|
},
|
|
): void {
|
|
if (!isMetricsInitialized) return;
|
|
if (!browserAgentConnectionDurationHistogram) return;
|
|
|
|
const commonAttribs = baseMetricDefinition.getCommonAttributes(config);
|
|
browserAgentConnectionDurationHistogram.record(durationMs, {
|
|
...commonAttribs,
|
|
session_mode: attributes.session_mode,
|
|
headless: attributes.headless,
|
|
success: attributes.success,
|
|
tool_count: attributes.tool_count,
|
|
});
|
|
|
|
if (!attributes.success && browserAgentConnectionFailureCounter) {
|
|
browserAgentConnectionFailureCounter.add(1, {
|
|
...commonAttribs,
|
|
session_mode: attributes.session_mode,
|
|
headless: attributes.headless,
|
|
error_type: attributes.error_type ?? 'unknown',
|
|
});
|
|
}
|
|
}
|
|
|
|
export function recordBrowserAgentToolDiscovery(
|
|
config: Config,
|
|
toolCount: number,
|
|
missingSemanticTools: string[],
|
|
sessionMode: 'persistent' | 'isolated' | 'existing',
|
|
): void {
|
|
if (!isMetricsInitialized) return;
|
|
|
|
const commonAttribs = baseMetricDefinition.getCommonAttributes(config);
|
|
if (browserAgentToolsDiscoveredHistogram) {
|
|
browserAgentToolsDiscoveredHistogram.record(toolCount, {
|
|
...commonAttribs,
|
|
session_mode: sessionMode,
|
|
});
|
|
}
|
|
|
|
if (browserAgentToolsMissingSemanticCounter) {
|
|
for (const tool of missingSemanticTools) {
|
|
browserAgentToolsMissingSemanticCounter.add(1, {
|
|
...commonAttribs,
|
|
tool_name: tool,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export function recordBrowserAgentVisionStatus(
|
|
config: Config,
|
|
attributes: {
|
|
enabled: boolean;
|
|
disabled_reason?:
|
|
| 'no_visual_model'
|
|
| 'missing_visual_tools'
|
|
| 'blocked_auth_type';
|
|
},
|
|
): void {
|
|
if (!isMetricsInitialized || !browserAgentVisionStatusCounter) return;
|
|
|
|
const metricAttributes: Record<string, string | number | boolean> = {
|
|
...baseMetricDefinition.getCommonAttributes(config),
|
|
enabled: attributes.enabled,
|
|
};
|
|
if (attributes.disabled_reason) {
|
|
metricAttributes['disabled_reason'] = attributes.disabled_reason;
|
|
}
|
|
|
|
browserAgentVisionStatusCounter.add(1, metricAttributes);
|
|
}
|
|
|
|
export function recordBrowserAgentTaskOutcome(
|
|
config: Config,
|
|
attributes: {
|
|
success: boolean;
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
vision_enabled: boolean;
|
|
headless: boolean;
|
|
duration_ms: number;
|
|
},
|
|
): void {
|
|
if (!isMetricsInitialized) return;
|
|
|
|
const commonAttribs = baseMetricDefinition.getCommonAttributes(config);
|
|
|
|
if (browserAgentTaskOutcomeCounter) {
|
|
browserAgentTaskOutcomeCounter.add(1, {
|
|
...commonAttribs,
|
|
success: attributes.success,
|
|
session_mode: attributes.session_mode,
|
|
vision_enabled: attributes.vision_enabled,
|
|
headless: attributes.headless,
|
|
});
|
|
}
|
|
|
|
if (browserAgentTaskDurationHistogram) {
|
|
browserAgentTaskDurationHistogram.record(attributes.duration_ms, {
|
|
...commonAttribs,
|
|
success: attributes.success,
|
|
session_mode: attributes.session_mode,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function recordBrowserAgentCleanup(
|
|
config: Config,
|
|
durationMs: number,
|
|
attributes: {
|
|
session_mode: 'persistent' | 'isolated' | 'existing';
|
|
success: boolean;
|
|
},
|
|
): void {
|
|
if (!isMetricsInitialized) return;
|
|
|
|
const commonAttribs = baseMetricDefinition.getCommonAttributes(config);
|
|
|
|
if (browserAgentCleanupDurationHistogram) {
|
|
browserAgentCleanupDurationHistogram.record(durationMs, {
|
|
...commonAttribs,
|
|
session_mode: attributes.session_mode,
|
|
});
|
|
}
|
|
|
|
if (!attributes.success && browserAgentCleanupFailureCounter) {
|
|
browserAgentCleanupFailureCounter.add(1, {
|
|
...commonAttribs,
|
|
session_mode: attributes.session_mode,
|
|
});
|
|
}
|
|
}
|