Skip to content

Commit 49e7cf8

Browse files
committed
Adds sourceKeyWithoutExtId to editTelemetry.editSources.details
1 parent bbf6eae commit 49e7cf8

File tree

4 files changed

+57
-29
lines changed

4 files changed

+57
-29
lines changed

src/vs/editor/common/textModelEditReason.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class TextModelEditReason {
3535
* Converts the metadata to a key string.
3636
* Only includes properties/values that have `level` many `$` prefixes or less.
3737
*/
38-
public toKey(level: number, filter: { [TKey in keyof ITextModelEditReasonMetadata]?: boolean } = {}): string {
38+
public toKey(level: number, filter: { [TKey in ITextModelEditReasonMetadataKeys]?: boolean } = {}): string {
3939
const metadata = this.metadata;
4040
const keys = Object.entries(metadata).filter(([key, value]) => {
4141
const filterVal = (filter as Record<string, boolean>)[key];
@@ -133,6 +133,8 @@ function toProperties(version: ProviderId | undefined) {
133133

134134
type Values<T> = T[keyof T];
135135
type ITextModelEditReasonMetadata = Values<{ [TKey in keyof typeof EditReasons]: ReturnType<typeof EditReasons[TKey]>['metadataT'] }>;
136+
type ITextModelEditReasonMetadataKeys = Values<{ [TKey in keyof typeof EditReasons]: keyof ReturnType<typeof EditReasons[TKey]>['metadataT'] }>;
137+
136138

137139
function avoidPathRedaction(str: string | undefined): string | undefined {
138140
if (str === undefined) {

src/vs/workbench/contrib/editTelemetry/browser/documentWithAnnotatedEdits.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IEditorWorkerService } from '../../../../editor/common/services/editorW
1313
import { TextModelEditReason } from '../../../../editor/common/textModelEditReason.js';
1414
import { IObservableDocument } from './observableWorkspace.js';
1515

16-
export interface IDocumentWithAnnotatedEdits<TEditData extends IEditData<TEditData> = EditSourceData> {
16+
export interface IDocumentWithAnnotatedEdits<TEditData extends IEditData<TEditData> = EditKeySourceData> {
1717
readonly value: IObservableWithChange<StringText, { edit: AnnotatedStringEdit<TEditData> }>;
1818
waitForQueue(): Promise<void>;
1919
}
@@ -22,8 +22,8 @@ export interface IDocumentWithAnnotatedEdits<TEditData extends IEditData<TEditDa
2222
* Creates a document that is a delayed copy of the original document,
2323
* but with edits annotated with the source of the edit.
2424
*/
25-
export class DocumentWithAnnotatedEdits extends Disposable implements IDocumentWithAnnotatedEdits<EditReasonData> {
26-
public readonly value: IObservableWithChange<StringText, { edit: AnnotatedStringEdit<EditReasonData> }>;
25+
export class DocumentWithSourceAnnotatedEdits extends Disposable implements IDocumentWithAnnotatedEdits<EditSourceData> {
26+
public readonly value: IObservableWithChange<StringText, { edit: AnnotatedStringEdit<EditSourceData> }>;
2727

2828
constructor(private readonly _originalDoc: IObservableDocument) {
2929
super();
@@ -32,7 +32,7 @@ export class DocumentWithAnnotatedEdits extends Disposable implements IDocumentW
3232

3333
this._register(runOnChange(this._originalDoc.value, (val, _prevVal, edits) => {
3434
const eComposed = AnnotatedStringEdit.compose(edits.map(e => {
35-
const editSourceData = new EditReasonData(e.reason);
35+
const editSourceData = new EditSourceData(e.reason);
3636
return e.mapData(() => editSourceData);
3737
}));
3838

@@ -46,9 +46,9 @@ export class DocumentWithAnnotatedEdits extends Disposable implements IDocumentW
4646
}
4747

4848
/**
49-
* Only joins touching edits if the source and the metadata is the same.
49+
* Only joins touching edits if the source and the metadata is the same (e.g. requestUuids must be equal).
5050
*/
51-
export class EditReasonData implements IEditData<EditReasonData> {
51+
export class EditSourceData implements IEditData<EditSourceData> {
5252
public readonly source;
5353
public readonly key;
5454

@@ -59,31 +59,33 @@ export class EditReasonData implements IEditData<EditReasonData> {
5959
this.source = EditSourceBase.create(this.editReason);
6060
}
6161

62-
join(data: EditReasonData): EditReasonData | undefined {
62+
join(data: EditSourceData): EditSourceData | undefined {
6363
if (this.editReason !== data.editReason) {
6464
return undefined;
6565
}
6666
return this;
6767
}
6868

69-
toEditSourceData(): EditSourceData {
70-
return new EditSourceData(this.key, this.source);
69+
toEditSourceData(): EditKeySourceData {
70+
return new EditKeySourceData(this.key, this.source, this.editReason);
7171
}
7272
}
7373

74-
export class EditSourceData implements IEditData<EditSourceData> {
74+
export class EditKeySourceData implements IEditData<EditKeySourceData> {
7575
constructor(
7676
public readonly key: string,
7777
public readonly source: EditSource,
78+
public readonly representative: TextModelEditReason,
7879
) { }
7980

80-
join(data: EditSourceData): EditSourceData | undefined {
81+
join(data: EditKeySourceData): EditKeySourceData | undefined {
8182
if (this.key !== data.key) {
8283
return undefined;
8384
}
8485
if (this.source !== data.source) {
8586
return undefined;
8687
}
88+
// The representatives could be different! (But equal modulo key)
8789
return this;
8890
}
8991
}
@@ -197,7 +199,7 @@ class UnknownEditSource extends EditSourceBase {
197199
public getColor(): string { return '#ff000033'; }
198200
}
199201

200-
export class CombineStreamedChanges<TEditData extends EditSourceData & IEditData<TEditData>> extends Disposable implements IDocumentWithAnnotatedEdits<TEditData> {
202+
export class CombineStreamedChanges<TEditData extends (EditKeySourceData | EditSourceData) & IEditData<TEditData>> extends Disposable implements IDocumentWithAnnotatedEdits<TEditData> {
201203
private readonly _value: ISettableObservable<StringText, { edit: AnnotatedStringEdit<TEditData> }>;
202204
readonly value: IObservableWithChange<StringText, { edit: AnnotatedStringEdit<TEditData> }>;
203205
private readonly _runStore = this._register(new DisposableStore());
@@ -265,7 +267,7 @@ export class CombineStreamedChanges<TEditData extends EditSourceData & IEditData
265267
}
266268
}
267269

268-
function isChatEdit(next: { value: StringText; change: { edit: AnnotatedStringEdit<EditSourceData> }[] }) {
270+
function isChatEdit(next: { value: StringText; change: { edit: AnnotatedStringEdit<EditKeySourceData | EditSourceData> }[] }) {
269271
return next.change.every(c => c.edit.replacements.every(e => {
270272
if (e.data.source.category === 'ai' && e.data.source.feature === 'chat') {
271273
return true;

src/vs/workbench/contrib/editTelemetry/browser/editSourceTrackingImpl.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import { URI } from '../../../../base/common/uri.js';
1212
import { generateUuid } from '../../../../base/common/uuid.js';
1313
import { AnnotatedStringEdit, BaseStringEdit } from '../../../../editor/common/core/edits/stringEdit.js';
1414
import { StringText } from '../../../../editor/common/core/text/abstractText.js';
15+
import { TextModelEditReason } from '../../../../editor/common/textModelEditReason.js';
1516
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
1617
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
1718
import { ISCMRepository, ISCMService } from '../../scm/common/scm.js';
1819
import { ArcTracker } from './arcTracker.js';
19-
import { CombineStreamedChanges, DocumentWithAnnotatedEdits, EditReasonData, EditSource, EditSourceData, IDocumentWithAnnotatedEdits, MinimizeEditsProcessor } from './documentWithAnnotatedEdits.js';
20+
import { CombineStreamedChanges, DocumentWithSourceAnnotatedEdits, EditKeySourceData, EditSource, EditSourceData, IDocumentWithAnnotatedEdits, MinimizeEditsProcessor } from './documentWithAnnotatedEdits.js';
2021
import { DocumentEditSourceTracker, TrackedEdit } from './editTracker.js';
2122
import { ObservableWorkspace, IObservableDocument } from './observableWorkspace.js';
2223

@@ -89,9 +90,9 @@ class TrackedDocumentInfo extends Disposable {
8990
super();
9091

9192
// Use the listener service and special events from core to annotate where an edit came from (is async)
92-
let processedDoc: IDocumentWithAnnotatedEdits<EditReasonData> = this._store.add(new DocumentWithAnnotatedEdits(_doc));
93+
let processedDoc: IDocumentWithAnnotatedEdits<EditSourceData> = this._store.add(new DocumentWithSourceAnnotatedEdits(_doc));
9394
// Combine streaming edits into one and make edit smaller
94-
processedDoc = this._store.add(this._instantiationService.createInstance((CombineStreamedChanges<EditReasonData>), processedDoc));
95+
processedDoc = this._store.add(this._instantiationService.createInstance((CombineStreamedChanges<EditSourceData>), processedDoc));
9596
// Remove common suffix and prefix from edits
9697
processedDoc = this._store.add(new MinimizeEditsProcessor(processedDoc));
9798

@@ -223,6 +224,10 @@ class TrackedDocumentInfo extends Disposable {
223224
isTrackedByGit: isTrackedByGit ? 1 : 0,
224225
});
225226

227+
const sourceKeyToRepresentative = new Map<string, TextModelEditReason>();
228+
for (const r of ranges) {
229+
sourceKeyToRepresentative.set(r.sourceKey, r.sourceRepresentative);
230+
}
226231

227232
const sums = sumByCategory(ranges, r => r.range.length, r => r.sourceKey);
228233
const entries = Object.entries(sums).filter(([key, value]) => value !== undefined);
@@ -233,9 +238,21 @@ class TrackedDocumentInfo extends Disposable {
233238
if (value === undefined) {
234239
continue;
235240
}
241+
242+
243+
const repr = sourceKeyToRepresentative.get(key);
244+
const cleanedKey = repr?.toKey(1, { $extensionId: false, $extensionVersion: false });
245+
246+
const metadata = repr?.metadata;
247+
const extensionId = metadata && '$extensionId' in metadata ? metadata.$extensionId : undefined;
248+
const extensionVersion = metadata && '$extensionVersion' in metadata ? metadata.$extensionVersion : undefined;
249+
236250
this._telemetryService.publicLog2<{
237251
mode: string;
238-
reasonKey: string;
252+
sourceKey: string;
253+
extensionId: string;
254+
extensionVersion: string;
255+
sourceKeyWithoutExtId: string;
239256
languageId: string;
240257
statsUuid: string;
241258
modifiedCount: number;
@@ -244,16 +261,22 @@ class TrackedDocumentInfo extends Disposable {
244261
owner: 'hediet';
245262
comment: 'Reports distribution of various edit kinds.';
246263

247-
reasonKey: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the edit.' };
264+
sourceKey: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source of the edit.' };
248265
mode: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'longterm or 5minWindow' };
249266
languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language id of the document.' };
250267
statsUuid: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The unique identifier for the telemetry event.' };
268+
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id which provided this inline completion.' };
269+
extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension.' };
270+
sourceKeyWithoutExtId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source of the edit.' };
251271

252272
modifiedCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Fraction of nes modified characters'; isMeasurement: true };
253273
totalModifiedCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Total number of characters'; isMeasurement: true };
254274
}>('editTelemetry.editSources.details', {
255275
mode,
256-
reasonKey: key,
276+
sourceKey: key,
277+
extensionId: extensionId ?? '',
278+
extensionVersion: extensionVersion ?? '',
279+
sourceKeyWithoutExtId: cleanedKey ?? '',
257280
languageId: this._doc.languageId.get(),
258281
statsUuid: statsUuid,
259282
modifiedCount: value,
@@ -312,8 +335,8 @@ function mapObservableDelta<T, TDelta, TDeltaNew>(obs: IObservableWithChange<T,
312335
/**
313336
* Removing the metadata allows touching edits from the same source to merged, even if they were caused by different actions (e.g. two user edits).
314337
*/
315-
function createDocWithJustReason(docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditReasonData>, store: DisposableStore): IDocumentWithAnnotatedEdits<EditSourceData> {
316-
const docWithJustReason: IDocumentWithAnnotatedEdits<EditSourceData> = {
338+
function createDocWithJustReason(docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditSourceData>, store: DisposableStore): IDocumentWithAnnotatedEdits<EditKeySourceData> {
339+
const docWithJustReason: IDocumentWithAnnotatedEdits<EditKeySourceData> = {
317340
value: mapObservableDelta(docWithAnnotatedEdits.value, edit => ({ edit: edit.edit.mapData(d => d.data.toEditSourceData()) }), store),
318341
waitForQueue: () => docWithAnnotatedEdits.waitForQueue(),
319342
};
@@ -322,7 +345,7 @@ function createDocWithJustReason(docWithAnnotatedEdits: IDocumentWithAnnotatedEd
322345

323346
class ArcTelemetrySender extends Disposable {
324347
constructor(
325-
docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditReasonData>,
348+
docWithAnnotatedEdits: IDocumentWithAnnotatedEdits<EditSourceData>,
326349
scmRepoBridge: ScmRepoBridge | undefined,
327350
@IInstantiationService private readonly _instantiationService: IInstantiationService,
328351
) {

src/vs/workbench/contrib/editTelemetry/browser/editTracker.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
88
import { observableSignal, runOnChange, IReader } from '../../../../base/common/observable.js';
99
import { AnnotatedStringEdit } from '../../../../editor/common/core/edits/stringEdit.js';
1010
import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js';
11-
import { IDocumentWithAnnotatedEdits, EditSourceData, EditSource } from './documentWithAnnotatedEdits.js';
11+
import { TextModelEditReason } from '../../../../editor/common/textModelEditReason.js';
12+
import { IDocumentWithAnnotatedEdits, EditKeySourceData, EditSource } from './documentWithAnnotatedEdits.js';
1213

1314
/**
1415
* Tracks a single document.
1516
*/
1617
export class DocumentEditSourceTracker<T = void> extends Disposable {
17-
private _edits: AnnotatedStringEdit<EditSourceData> = AnnotatedStringEdit.empty;
18-
private _pendingExternalEdits: AnnotatedStringEdit<EditSourceData> = AnnotatedStringEdit.empty;
18+
private _edits: AnnotatedStringEdit<EditKeySourceData> = AnnotatedStringEdit.empty;
19+
private _pendingExternalEdits: AnnotatedStringEdit<EditKeySourceData> = AnnotatedStringEdit.empty;
1920

2021
private readonly _update = observableSignal(this);
2122

@@ -55,8 +56,7 @@ export class DocumentEditSourceTracker<T = void> extends Disposable {
5556
const ranges = this._edits.getNewRanges();
5657
return ranges.map((r, idx) => {
5758
const e = this._edits.replacements[idx];
58-
const reason = e.data.source;
59-
const te = new TrackedEdit(e.replaceRange, r, reason, e.data.key);
59+
const te = new TrackedEdit(e.replaceRange, r, e.data.key, e.data.source, e.data.representative);
6060
return te;
6161
});
6262
}
@@ -90,7 +90,8 @@ export class TrackedEdit {
9090
constructor(
9191
public readonly originalRange: OffsetRange,
9292
public readonly range: OffsetRange,
93-
public readonly source: EditSource,
9493
public readonly sourceKey: string,
94+
public readonly source: EditSource,
95+
public readonly sourceRepresentative: TextModelEditReason,
9596
) { }
9697
}

0 commit comments

Comments
 (0)