mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-21 10:01:35 +01:00
* utils: document binarySearch
* nes-datagen: generate training data from continuous recordings
Continuous enhanced telemetry now ships sliding-window recordings that, unlike per-request alternative-action recordings, carry no requestTime. The datagen pipeline needs a point to split each recording into edit history before/after, so this adds a pluggable pivot strategy (starting with Random, selectable via --pivot-strategy) and a new continuous/ pipeline module that replays a recording at the chosen pivot to produce a processed row.
Along the way this consolidates the pipeline's error and index handling: a shared WithRowIndex<T> replaces the ad-hoc { originalRowIndex, ... } pairs, per-record processing returns Result<IProcessedRow, Error> instead of field-presence unions, and failures surface as original Error objects (no string round-tripping). The telemetry sender's continuous payload is now the documented IContinuousRecording type.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* nes-datagen: label alt-action replay errors by originalRowIndex
Address PR review: the alternative-action path mislabeled diagnostics when
earlier records failed to parse.
- processAllRows: push replay errors with the row's true `originalRowIndex`
instead of its position in the filtered `rows` array (parse failures make
`rows` sparse, so the two diverge).
- loadAndProduceProcessedRows: resolve `languageForRow` via an
`originalRowIndex`-keyed Map rather than positional `rows[i]`, matching how
callers pass `e.originalRowIndex`.
- Clarify the `recordCount` doc: it counts successfully-parsed records (parse
failures are counted separately in `parseErrors`).
- Add a regression spec asserting replay errors carry the row index, not the
array position.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
101 lines
5.2 KiB
TypeScript
101 lines
5.2 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { LogEntry } from '../../../src/platform/workspaceRecorder/common/workspaceLog';
|
|
import { PivotStrategy } from '../../base/simulationOptions';
|
|
import { IContinuousRecord } from './continuousRecord';
|
|
import { CONTINUOUS_SUGGESTION_STATUS, processContinuousRecord, processContinuousRecords } from './processContinuous';
|
|
|
|
const doc = Array.from({ length: 30 }, (_, i) => `// L${String(i).padStart(2, '0')}`).join('\n') + '\n';
|
|
|
|
// Window metadata the sender always ships; irrelevant to pivot/replay logic but
|
|
// required by `IContinuousRecording`, so these tests fill it with fixed dummies.
|
|
const META = { windowStart: 0, windowEnd: 1, sessionId: 'test', sequenceNumber: 0 };
|
|
|
|
const entries: LogEntry[] = [
|
|
{ kind: 'meta', data: { repoRootUri: 'file:///ws' } },
|
|
{ kind: 'documentEncountered', id: 0, time: 1000, relativePath: 'src/x.ts' },
|
|
{ kind: 'setContent', id: 0, time: 1001, content: doc, v: 0 },
|
|
{ kind: 'selectionChanged', id: 0, time: 1002, selection: [[14, 14]] },
|
|
{ kind: 'changed', id: 0, time: 1004, edit: [[175, 175, 'Z']], v: 1 },
|
|
{ kind: 'selectionChanged', id: 0, time: 1006, selection: [[175, 175]] },
|
|
{ kind: 'changed', id: 0, time: 1008, edit: [[7, 7, 'Q']], v: 2 },
|
|
];
|
|
|
|
function record(): IContinuousRecord {
|
|
return { originalRowIndex: 0, value: { entries, entriesSize: 100, ...META } };
|
|
}
|
|
|
|
describe('processContinuousRecord', () => {
|
|
it('synthesizes an oracle-only row and resolves language from the active file', () => {
|
|
const result = processContinuousRecord(record(), 1002);
|
|
expect(result.isOk()).toBe(true);
|
|
if (result.isError()) { return; }
|
|
expect(result.val.row.suggestionStatus).toBe(CONTINUOUS_SUGGESTION_STATUS);
|
|
expect(result.val.row.activeDocumentLanguageId).toBe('typescript');
|
|
result.val.replayer.dispose();
|
|
});
|
|
|
|
it('errors when the recording has no entries', () => {
|
|
const empty: IContinuousRecord = { originalRowIndex: 0, value: { entries: [], entriesSize: 0, ...META } };
|
|
expect(processContinuousRecord(empty, 0).isError()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('processContinuousRecords', () => {
|
|
it('produces identical pivots across runs with the same seed', () => {
|
|
const a = processContinuousRecords([record()], PivotStrategy.Random, 99, 0);
|
|
const b = processContinuousRecords([record()], PivotStrategy.Random, 99, 0);
|
|
expect(a.processed.length).toBe(b.processed.length);
|
|
expect(a.errors).toEqual(b.errors);
|
|
[...a.processed, ...b.processed].forEach(p => p.replayer.dispose());
|
|
});
|
|
|
|
it('every selected pivot is replayable (eligible ⟹ replayable)', () => {
|
|
// Regression guard: a pivot that `selectPivots` deems eligible must always
|
|
// produce a real sample, never a replay error. Sweep seeds so different
|
|
// eligible pivots (the in-window selectionChanged/changed entries) are hit.
|
|
for (let seed = 0; seed < 50; seed++) {
|
|
const { processed, errors } = processContinuousRecords([record()], PivotStrategy.Random, seed, 0);
|
|
expect(errors).toEqual([]);
|
|
expect(processed).toHaveLength(1);
|
|
processed.forEach(p => p.replayer.dispose());
|
|
}
|
|
});
|
|
|
|
it('reports a no-eligible-pivot error when no oracle follows', () => {
|
|
const noOracle: IContinuousRecord = {
|
|
originalRowIndex: 3,
|
|
value: { entries: entries.slice(0, 4), entriesSize: 50, ...META },
|
|
};
|
|
const { processed, errors } = processContinuousRecords([noOracle], PivotStrategy.Random, 1, 0);
|
|
expect(processed).toHaveLength(0);
|
|
expect(errors[0]).toMatchObject({ originalRowIndex: 3 });
|
|
});
|
|
|
|
it('isolates a throwing record so the rest of the batch still produces rows', () => {
|
|
// A malformed oracle edit (overlapping replacements) makes replay throw a
|
|
// `BugIndicatingError` from the `StringEdit` constructor. One bad record
|
|
// must not abort the whole batch: it should surface as a per-record error
|
|
// while its siblings still process — matching the alternative-action path.
|
|
const malformedEntries: LogEntry[] = [
|
|
{ kind: 'meta', data: { repoRootUri: 'file:///ws' } },
|
|
{ kind: 'documentEncountered', id: 0, time: 3000, relativePath: 'src/bad.ts' },
|
|
{ kind: 'setContent', id: 0, time: 3001, content: doc, v: 0 },
|
|
{ kind: 'changed', id: 0, time: 3003, edit: [[5, 5, 'A']], v: 1 },
|
|
{ kind: 'changed', id: 0, time: 3005, edit: [[10, 14, 'X'], [12, 16, 'Y']], v: 2 },
|
|
];
|
|
const malformed: IContinuousRecord = { originalRowIndex: 1, value: { entries: malformedEntries, entriesSize: 80, ...META } };
|
|
const good = (originalRowIndex: number): IContinuousRecord => ({ originalRowIndex, value: { entries, entriesSize: 100, ...META } });
|
|
|
|
const { processed, errors } = processContinuousRecords([good(0), malformed, good(2)], PivotStrategy.Random, 7, 0);
|
|
|
|
expect(processed).toHaveLength(2);
|
|
expect(errors.map(e => ({ originalRowIndex: e.originalRowIndex, isError: e.value instanceof Error }))).toEqual([{ originalRowIndex: 1, isError: true }]);
|
|
processed.forEach(p => p.replayer.dispose());
|
|
});
|
|
});
|