Path: blob/main/extensions/copilot/test/simulation/workbench/stores/simulationBaseline.ts
13399 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 * as fs from 'fs';6import * as mobx from 'mobx';7import * as path from 'path';8import { RunOnceScheduler } from '../../../../src/util/vs/base/common/async';9import { Disposable, toDisposable } from '../../../../src/util/vs/base/common/lifecycle';10import { RUN_METADATA, SIMULATION_FOLDER_NAME } from '../../shared/sharedTypes';11import { REPO_ROOT, genericEquals } from '../utils/utils';12import { SimulationRunner } from './simulationRunner';13import { SimulationStorage, SimulationStorageValue } from './simulationStorage';1415const SIMULATION_FOLDER_PATH = path.join(REPO_ROOT, SIMULATION_FOLDER_NAME);1617class SimulationRun {1819/** Shown in UI */20public readonly friendlyName: string;2122/**23* @param name is the name of the run that is also the name of the directory that contains the run information (usually in `.simulation` directory)24*/25constructor(26public readonly name: string,27public readonly label?: string28) {29// example: out-20230804-105913 or out-external-20230804-10591330const m = name.match(/^out-(?:\w+-)?(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/);31if (m) {32const year = m ? parseInt(m[1], 10) : 0;33const month = m ? parseInt(m[2], 10) : 0;34const day = m ? parseInt(m[3], 10) : 0;35const hour = m ? parseInt(m[4], 10) : 0;36const minute = m ? parseInt(m[5], 10) : 0;37const second = m ? parseInt(m[6], 10) : 0;38const twodigits = (n: number) => String(n).padStart(2, '0');39this.friendlyName = `${twodigits(hour)}:${twodigits(minute)}:${twodigits(second)} -- ${twodigits(day)}/${twodigits(month)}/${year}`;40} else {41this.friendlyName = name;42}43if (this.label?.length) {44this.friendlyName = `${this.friendlyName} (${this.label})`;45}46}47}484950/**51* Detects possible baseline runs52*/53export class SimulationRunsProvider extends Disposable {54private static readonly COMPARE_AGAINST_RUN_STORAGE_KEY = 'selectedBaseline';5556private readonly _updateSoon = this._register(new RunOnceScheduler(() => this._update(), 50));5758@mobx.observable59public runs: SimulationRun[] = [];6061public selectedBaselineRunName: SimulationStorageValue<string>;6263@mobx.computed64public get selectedBaselineRun(): SimulationRun | undefined {65return this.runs.find(r => r.name === this.selectedBaselineRunName.value);66}6768constructor(69_storage: SimulationStorage,70private readonly _runner: SimulationRunner71) {72super();73mobx.makeObservable(this);7475this.selectedBaselineRunName = new SimulationStorageValue(_storage, SimulationRunsProvider.COMPARE_AGAINST_RUN_STORAGE_KEY, '');7677const listener = () => {78if (!this._updateSoon.isScheduled()) {79this._updateSoon.schedule();80}81};8283fs.promises.mkdir(SIMULATION_FOLDER_PATH, { recursive: true }).then(() => {84fs.watch(SIMULATION_FOLDER_PATH, { recursive: false }, listener);85this._register(toDisposable(() => fs.unwatchFile(SIMULATION_FOLDER_PATH, listener)));86this._update();87});88}8990private _excludedFiles = new Set(['cache.sqlite', 'cache.version', 'token_cache.json']);9192private async _update(): Promise<void> {93let entries = (await fs.promises.readdir(SIMULATION_FOLDER_PATH)).map((e) => ({ timestamp: e, label: undefined }));94entries = entries.filter(entry => !entry.timestamp.startsWith('.') && !entry.timestamp.startsWith('tmp-') && !this._excludedFiles.has(entry.timestamp)); // ignore hidden & tmp- directories & cache-related files95// Sort descending96entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));9798if (genericEquals(entries, this.runs.map(r => r.name))) {99return;100}101102for (const entry of entries) {103const runMetadata = path.join(SIMULATION_FOLDER_PATH, entry.timestamp, RUN_METADATA);104try {105const data = (await fs.promises.readFile(runMetadata, 'utf-8')).toString();106entry.label = JSON.parse(data).label;107} catch { }108}109110mobx.runInAction(() => {111// Try to reuse the old objects112const existingRuns = new Map<string, SimulationRun>(this.runs.map(r => [r.name, r]));113this.runs = entries.map(entry => existingRuns.get(entry.timestamp) ?? new SimulationRun(entry.timestamp, entry.label));114});115}116117public async renameRun(oldName: string, newName: string): Promise<boolean> {118if (!this._runner) {119console.log('Cannot rename: no runner available');120return false;121}122123console.log('Attempting to rename run from', oldName, 'to', newName);124const success = await this._runner.renameRun(oldName, newName);125126if (success) {127// Update selected baseline if it was renamed128if (this.selectedBaselineRunName.value === oldName) {129console.log('Updating selected baseline name from', oldName, 'to', newName);130mobx.runInAction(() => {131this.selectedBaselineRunName.value = newName;132});133}134} else {135console.log('Failed to rename run');136}137return success;138}139}140141142