Path: blob/main/extensions/copilot/src/platform/otel/common/genAiMetrics.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CopilotChatAttr, type EditOutcome, type EditSource, GenAiAttr, StdAttr } from './genAiAttributes';6import type { IOTelService } from './otelService';78/**9* Pre-configured OTel GenAI metric instruments.10* All methods are static to avoid per-call allocations (aligned with gemini-cli pattern).11*/12export class GenAiMetrics {1314// ── GenAI Convention Metrics ──1516static recordOperationDuration(17otel: IOTelService,18durationSec: number,19attrs: {20operationName: string;21providerName: string;22requestModel: string;23responseModel?: string;24serverAddress?: string;25serverPort?: number;26errorType?: string;27},28): void {29otel.recordMetric('gen_ai.client.operation.duration', durationSec, {30[GenAiAttr.OPERATION_NAME]: attrs.operationName,31[GenAiAttr.PROVIDER_NAME]: attrs.providerName,32[GenAiAttr.REQUEST_MODEL]: attrs.requestModel,33...(attrs.responseModel ? { [GenAiAttr.RESPONSE_MODEL]: attrs.responseModel } : {}),34...(attrs.serverAddress ? { [StdAttr.SERVER_ADDRESS]: attrs.serverAddress } : {}),35...(attrs.serverPort ? { [StdAttr.SERVER_PORT]: attrs.serverPort } : {}),36...(attrs.errorType ? { [StdAttr.ERROR_TYPE]: attrs.errorType } : {}),37});38}3940static recordTokenUsage(41otel: IOTelService,42tokenCount: number,43tokenType: 'input' | 'output',44attrs: {45operationName: string;46providerName: string;47requestModel: string;48responseModel?: string;49serverAddress?: string;50},51): void {52otel.recordMetric('gen_ai.client.token.usage', tokenCount, {53[GenAiAttr.OPERATION_NAME]: attrs.operationName,54[GenAiAttr.PROVIDER_NAME]: attrs.providerName,55[GenAiAttr.TOKEN_TYPE]: tokenType,56[GenAiAttr.REQUEST_MODEL]: attrs.requestModel,57...(attrs.responseModel ? { [GenAiAttr.RESPONSE_MODEL]: attrs.responseModel } : {}),58...(attrs.serverAddress ? { [StdAttr.SERVER_ADDRESS]: attrs.serverAddress } : {}),59});60}6162// ── Extension-Specific Metrics ──6364static recordToolCallCount(otel: IOTelService, toolName: string, success: boolean): void {65otel.incrementCounter('copilot_chat.tool.call.count', 1, {66[GenAiAttr.TOOL_NAME]: toolName,67success,68});69}7071static recordToolCallDuration(otel: IOTelService, toolName: string, durationMs: number): void {72otel.recordMetric('copilot_chat.tool.call.duration', durationMs, {73[GenAiAttr.TOOL_NAME]: toolName,74});75}7677static recordAgentDuration(otel: IOTelService, agentName: string, durationSec: number): void {78otel.recordMetric('copilot_chat.agent.invocation.duration', durationSec, {79[GenAiAttr.AGENT_NAME]: agentName,80});81}8283static recordAgentTurnCount(otel: IOTelService, agentName: string, turnCount: number): void {84otel.recordMetric('copilot_chat.agent.turn.count', turnCount, {85[GenAiAttr.AGENT_NAME]: agentName,86});87}8889static recordTimeToFirstToken(otel: IOTelService, model: string, ttftSec: number): void {90otel.recordMetric('copilot_chat.time_to_first_token', ttftSec, {91[GenAiAttr.REQUEST_MODEL]: model,92});93}9495static incrementSessionCount(otel: IOTelService): void {96otel.incrementCounter('copilot_chat.session.count');97}9899// ── Agent Activity & Outcome Metrics ──100101/** Accept/reject counter for inline chat and chat editing edits */102static recordEditAcceptance(otel: IOTelService, source: EditSource, outcome: EditOutcome, languageId?: string): void {103otel.incrementCounter('copilot_chat.edit.acceptance.count', 1, {104[CopilotChatAttr.EDIT_SOURCE]: source,105[CopilotChatAttr.EDIT_OUTCOME]: outcome,106...(languageId ? { [CopilotChatAttr.LANGUAGE_ID]: languageId } : {}),107});108}109110/** File-level chat editing session outcome (accepted/rejected/saved) */111static recordChatEditOutcome(otel: IOTelService, source: EditSource, outcome: EditOutcome, languageId?: string, hasRemainingEdits?: boolean): void {112otel.incrementCounter('copilot_chat.chat_edit.outcome.count', 1, {113[CopilotChatAttr.EDIT_SOURCE]: source,114[CopilotChatAttr.EDIT_OUTCOME]: outcome,115...(languageId ? { [CopilotChatAttr.LANGUAGE_ID]: languageId } : {}),116...(hasRemainingEdits !== undefined ? { [CopilotChatAttr.HAS_REMAINING_EDITS]: hasRemainingEdits } : {}),117});118}119120/** 4-gram text similarity survival score */121static recordEditSurvivalFourGram(otel: IOTelService, source: EditSource, score: number, timeDelayMs: number): void {122otel.recordMetric('copilot_chat.edit.survival.four_gram', score, {123[CopilotChatAttr.EDIT_SOURCE]: source,124[CopilotChatAttr.TIME_DELAY_MS]: timeDelayMs,125});126}127128/** No-revert survival score */129static recordEditSurvivalNoRevert(otel: IOTelService, source: EditSource, score: number, timeDelayMs: number): void {130otel.recordMetric('copilot_chat.edit.survival.no_revert', score, {131[CopilotChatAttr.EDIT_SOURCE]: source,132[CopilotChatAttr.TIME_DELAY_MS]: timeDelayMs,133});134}135136/** Lines of code added/removed by accepted agent edits */137static incrementLinesOfCode(otel: IOTelService, type: 'added' | 'removed', languageId: string | undefined, count: number): void {138otel.incrementCounter('copilot_chat.lines_of_code.count', count, {139'type': type,140...(languageId ? { [CopilotChatAttr.LANGUAGE_ID]: languageId } : {}),141});142}143144// ── User Engagement Metrics ──145146static incrementUserActionCount(otel: IOTelService, action: string): void {147otel.incrementCounter('copilot_chat.user.action.count', 1, {148'action': action,149});150}151152static incrementUserFeedbackCount(otel: IOTelService, rating: string): void {153otel.incrementCounter('copilot_chat.user.feedback.count', 1, {154'rating': rating,155});156}157158// ── Agent Internals Metrics ──159160static incrementAgentEditResponseCount(otel: IOTelService, outcome: string): void {161otel.incrementCounter('copilot_chat.agent.edit_response.count', 1, {162'outcome': outcome,163});164}165166static incrementAgentSummarizationCount(otel: IOTelService, outcome: string): void {167otel.incrementCounter('copilot_chat.agent.summarization.count', 1, {168'outcome': outcome,169});170}171172// ── Background/Cloud Metrics ──173174static incrementPullRequestCount(otel: IOTelService): void {175otel.incrementCounter('copilot_chat.pull_request.count');176}177178static incrementCloudSessionCount(otel: IOTelService, partnerAgent: string): void {179otel.incrementCounter('copilot_chat.cloud.session.count', 1, {180'partner_agent': partnerAgent,181});182}183184static incrementCloudPrReadyCount(otel: IOTelService): void {185otel.incrementCounter('copilot_chat.cloud.pr_ready.count');186}187}188189190