Path: blob/main/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts
5266 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 { onUnexpectedError } from '../../../../../base/common/errors.js';6import { Disposable } from '../../../../../base/common/lifecycle.js';7import { IObservable, runOnChange } from '../../../../../base/common/observable.js';8import { AnnotatedStringEdit } from '../../../../../editor/common/core/edits/stringEdit.js';9import { EditDeltaInfo, EditSuggestionId, ITextModelEditSourceMetadata } from '../../../../../editor/common/textModelEditSource.js';10import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';11import { EditSourceData, IDocumentWithAnnotatedEdits, createDocWithJustReason } from '../helpers/documentWithAnnotatedEdits.js';12import { IAiEditTelemetryService } from './aiEditTelemetry/aiEditTelemetryService.js';13import type { ScmRepoAdapter } from './scmAdapter.js';14import { forwardToChannelIf, isCopilotLikeExtension } from '../../../../../platform/dataChannel/browser/forwardingTelemetryService.js';15import { ProviderId } from '../../../../../editor/common/languages.js';16import { ArcTelemetryReporter } from './arcTelemetryReporter.js';17import { IRandomService } from '../randomService.js';1819export class EditTelemetryReportInlineEditArcSender extends Disposable {20constructor(21docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditSourceData>,22scmRepoBridge: IObservable<ScmRepoAdapter | undefined>,23@IInstantiationService private readonly _instantiationService: IInstantiationService24) {25super();2627this._register(runOnChange(docWithAnnotatedEdits.value, (_val, _prev, changes) => {28const edit = AnnotatedStringEdit.compose(changes.map(c => c.edit));2930if (!edit.replacements.some(r => r.data.editSource.metadata.source === 'inlineCompletionAccept')) {31return;32}33if (!edit.replacements.every(r => r.data.editSource.metadata.source === 'inlineCompletionAccept')) {34onUnexpectedError(new Error('ArcTelemetrySender: Not all edits are inline completion accept edits!'));35return;36}37if (edit.replacements[0].data.editSource.metadata.source !== 'inlineCompletionAccept') {38return;39}40const data = edit.replacements[0].data.editSource.metadata;4142const docWithJustReason = createDocWithJustReason(docWithAnnotatedEdits, this._store);43const reporter = this._store.add(this._instantiationService.createInstance(ArcTelemetryReporter, [0, 30, 120, 300, 600, 900].map(s => s * 1000), _prev, docWithJustReason, scmRepoBridge, edit, res => {44res.telemetryService.publicLog2<{45extensionId: string;46extensionVersion: string;47opportunityId: string;48languageId: string;49correlationId: string | undefined;50didBranchChange: number;51timeDelayMs: number;5253originalCharCount: number;54originalLineCount: number;55originalDeletedLineCount: number;56arc: number;57currentLineCount: number;58currentDeletedLineCount: number;59}, {60owner: 'hediet';61comment: 'Reports for each accepted inline suggestion (= inline completions + next edit suggestions) the accumulated retained character count after a certain time delay. This event is sent 0s, 30s, 120s, 300s, 600s and 900s after acceptance. @sentToGitHub';6263extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id which provided this inline suggestion.' };64extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension.' };65opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline suggestion.' };66languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language id of the document.' };67correlationId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The correlation id of the inline suggestion.' };6869didBranchChange: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Indicates if the branch changed in the meantime. If the branch changed (value is 1); this event should probably be ignored.' };70timeDelayMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The time delay between the user accepting the edit and measuring the survival rate.' };7172originalCharCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original character count before any edits.' };73originalLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original line count before any edits.' };74originalDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original deleted line count before any edits.' };75arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and retained character count.' };76currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' };77currentDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current deleted line count after edits.' };78}>('editTelemetry.reportInlineEditArc', {79extensionId: data.$extensionId ?? '',80extensionVersion: data.$extensionVersion ?? '',81opportunityId: data.$$requestUuid ?? 'unknown',82languageId: data.$$languageId,83correlationId: data.$$correlationId,84didBranchChange: res.didBranchChange ? 1 : 0,85timeDelayMs: res.timeDelayMs,8687originalCharCount: res.originalCharCount,88originalLineCount: res.originalLineCount,89originalDeletedLineCount: res.originalDeletedLineCount,90arc: res.arc,91currentLineCount: res.currentLineCount,92currentDeletedLineCount: res.currentDeletedLineCount,9394...forwardToChannelIf(isCopilotLikeExtension(data.$extensionId)),95});96}, () => {97this._store.delete(reporter);98}));99}));100}101}102103export class CreateSuggestionIdForChatOrInlineChatCaller extends Disposable {104constructor(105docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditSourceData>,106@IAiEditTelemetryService private readonly _aiEditTelemetryService: IAiEditTelemetryService,107) {108super();109110this._register(runOnChange(docWithAnnotatedEdits.value, (_val, _prev, changes) => {111const edit = AnnotatedStringEdit.compose(changes.map(c => c.edit));112113const supportedSource = new Set(['Chat.applyEdits', 'inlineChat.applyEdits'] as ITextModelEditSourceMetadata['source'][]);114115if (!edit.replacements.some(r => supportedSource.has(r.data.editSource.metadata.source))) {116return;117}118if (!edit.replacements.every(r => supportedSource.has(r.data.editSource.metadata.source))) {119onUnexpectedError(new Error(`ArcTelemetrySender: Not all edits are ${edit.replacements[0].data.editSource.metadata.source}!`));120return;121}122let applyCodeBlockSuggestionId: EditSuggestionId | undefined = undefined;123const data = edit.replacements[0].data.editSource;124let feature: 'inlineChat' | 'sideBarChat';125if (data.metadata.source === 'Chat.applyEdits') {126feature = 'sideBarChat';127if (data.metadata.$$mode === 'applyCodeBlock') {128applyCodeBlockSuggestionId = data.metadata.$$codeBlockSuggestionId;129}130} else {131feature = 'inlineChat';132}133134const providerId = new ProviderId(data.props.$extensionId, data.props.$extensionVersion, data.props.$providerId);135136// TODO@hediet tie this suggestion id to hunks, so acceptance can be correlated.137this._aiEditTelemetryService.createSuggestionId({138applyCodeBlockSuggestionId,139languageId: data.props.$$languageId,140presentation: 'highlightedEdit',141feature,142source: providerId,143modelId: data.props.$modelId,144// eslint-disable-next-line local/code-no-any-casts145modeId: data.props.$$mode as any,146editDeltaInfo: EditDeltaInfo.fromEdit(edit, _prev),147});148}));149}150}151152export class EditTelemetryReportEditArcForChatOrInlineChatSender extends Disposable {153constructor(154docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditSourceData>,155scmRepoBridge: IObservable<ScmRepoAdapter | undefined>,156@IInstantiationService private readonly _instantiationService: IInstantiationService,157@IRandomService private readonly _randomService: IRandomService,158) {159super();160161this._register(runOnChange(docWithAnnotatedEdits.value, (_val, _prev, changes) => {162const edit = AnnotatedStringEdit.compose(changes.map(c => c.edit));163164const supportedSource = new Set(['Chat.applyEdits', 'inlineChat.applyEdits'] as ITextModelEditSourceMetadata['source'][]);165166if (!edit.replacements.some(r => supportedSource.has(r.data.editSource.metadata.source))) {167return;168}169if (!edit.replacements.every(r => supportedSource.has(r.data.editSource.metadata.source))) {170onUnexpectedError(new Error(`ArcTelemetrySender: Not all edits are ${edit.replacements[0].data.editSource.metadata.source}!`));171return;172}173const data = edit.replacements[0].data.editSource;174175const uniqueEditId = this._randomService.generateUuid();176177const docWithJustReason = createDocWithJustReason(docWithAnnotatedEdits, this._store);178const reporter = this._store.add(this._instantiationService.createInstance(ArcTelemetryReporter, [0, 60, 300].map(s => s * 1000), _prev, docWithJustReason, scmRepoBridge, edit, res => {179res.telemetryService.publicLog2<{180sourceKeyCleaned: string;181extensionId: string | undefined;182extensionVersion: string | undefined;183opportunityId: string | undefined;184editSessionId: string | undefined;185requestId: string | undefined;186modelId: string | undefined;187languageId: string | undefined;188mode: string | undefined;189uniqueEditId: string | undefined;190191didBranchChange: number;192timeDelayMs: number;193194originalCharCount: number;195originalLineCount: number;196originalDeletedLineCount: number;197arc: number;198currentLineCount: number;199currentDeletedLineCount: number;200}, {201owner: 'hediet';202comment: 'Reports the accepted and retained character count for an inline completion/edit. @sentToGitHub';203204sourceKeyCleaned: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The key of the edit source.' };205extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id (copilot or copilot-chat); which provided this inline completion.' };206extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension.' };207opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline completion or NES.' };208editSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The session id.' };209requestId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The request id.' };210modelId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The model id.' };211languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language id of the document.' };212mode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The mode chat was in.' };213uniqueEditId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The unique identifier for the edit.' };214215didBranchChange: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Indicates if the branch changed in the meantime. If the branch changed (value is 1); this event should probably be ignored.' };216timeDelayMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The time delay between the user accepting the edit and measuring the survival rate.' };217218originalCharCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original character count before any edits.' };219originalLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original line count before any edits.' };220originalDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original deleted line count before any edits.' };221arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and restrained character count.' };222currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' };223currentDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current deleted line count after edits.' };224}>('editTelemetry.reportEditArc', {225sourceKeyCleaned: data.toKey(Number.MAX_SAFE_INTEGER, {226$extensionId: false,227$extensionVersion: false,228$$requestUuid: false,229$$sessionId: false,230$$requestId: false,231$$languageId: false,232$modelId: false,233}),234extensionId: data.props.$extensionId,235extensionVersion: data.props.$extensionVersion,236opportunityId: data.props.$$requestUuid,237editSessionId: data.props.$$sessionId,238requestId: data.props.$$requestId,239modelId: data.props.$modelId,240languageId: data.props.$$languageId,241mode: data.props.$$mode,242uniqueEditId,243244didBranchChange: res.didBranchChange ? 1 : 0,245timeDelayMs: res.timeDelayMs,246247originalCharCount: res.originalCharCount,248originalLineCount: res.originalLineCount,249originalDeletedLineCount: res.originalDeletedLineCount,250arc: res.arc,251currentLineCount: res.currentLineCount,252currentDeletedLineCount: res.currentDeletedLineCount,253254...forwardToChannelIf(isCopilotLikeExtension(data.props.$extensionId)),255});256}, () => {257this._store.delete(reporter);258}));259}));260}261}262263264