From 5ecf670731e0e2a1a4cfb7066e1ded0dc5618f26 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 1 Nov 2021 13:43:43 -0400 Subject: [PATCH 1/4] Initial assignment service work --- .eslintrc.json | 3 +- .../sharedProcess/sharedProcessMain.ts | 7 +- .../assignment/common/assignmentService.ts | 263 ++++++++++++++++++ .../telemetry/node/appInsightsAppender.ts | 7 +- 4 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 src/vs/platform/assignment/common/assignmentService.ts diff --git a/.eslintrc.json b/.eslintrc.json index fc92f326b7cca..e55fa836ac905 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -216,7 +216,8 @@ "vs/nls", "**/vs/base/common/**", "**/vs/base/parts/*/common/**", - "**/vs/platform/*/common/**" + "**/vs/platform/*/common/**", + "tas-client-umd" ] }, { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index e8c22a0c75c1e..32377a687dc0f 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -95,6 +95,7 @@ import { SharedProcessTunnelService } from 'vs/platform/remote/node/sharedProces import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService'; import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService'; import { IUserConfigurationFileService, UserConfigurationFileServiceId } from 'vs/platform/configuration/common/userConfigurationFileService'; +import { AssignmentService } from 'vs/platform/assignment/common/assignmentService'; class SharedProcessMain extends Disposable { @@ -240,6 +241,9 @@ class SharedProcessMain extends Disposable { const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(this.server.getChannel('extensionRecommendationNotification', activeWindowRouter))); + // Assignment Service (Experiment service w/out scorecards) + const assignmentService = new AssignmentService(this.configuration.machineId, configurationService, productService); + // Telemetry let telemetryService: ITelemetryService; const appenders: ITelemetryAppender[] = []; @@ -250,7 +254,8 @@ class SharedProcessMain extends Disposable { // Application Insights if (productService.aiConfig && productService.aiConfig.asimovKey) { - const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey); + const testCollector = await assignmentService.getTreatment('vscode.telemetryMigration') ?? false; + const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, testCollector); this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data appenders.push(appInsightsAppender); } diff --git a/src/vs/platform/assignment/common/assignmentService.ts b/src/vs/platform/assignment/common/assignmentService.ts new file mode 100644 index 0000000000000..5a7ee57a534a1 --- /dev/null +++ b/src/vs/platform/assignment/common/assignmentService.ts @@ -0,0 +1,263 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/base/common/platform'; +import type { IExperimentationTelemetry, IExperimentationFilterProvider, ExperimentationService as TASClient } from 'tas-client-umd'; +import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; + +export interface IAssignmentService { + readonly _serviceBrand: undefined; + getTreatment(name: string): Promise; + getCurrentExperiments(): Promise; +} + +const storageKey = 'VSCode.ABExp.FeatureData'; +const refetchInterval = 0; // no polling + +// class MementoKeyValueStorage implements IKeyValueStorage { +// private mementoObj: MementoObject; +// constructor(private memento: Memento) { +// this.mementoObj = memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); +// } + +// async getValue(key: string, defaultValue?: T | undefined): Promise { +// const value = await this.mementoObj[key]; +// return value || defaultValue; +// } + +// setValue(key: string, value: T): void { +// this.mementoObj[key] = value; +// this.memento.saveMemento(); +// } +//} + +class AssignmentServiceTelemetry implements IExperimentationTelemetry { + private _lastAssignmentContext: string | undefined; + constructor( + private productService: IProductService + ) { } + + get assignmentContext(): string[] | undefined { + return this._lastAssignmentContext?.split(';'); + } + + setSharedProperty(name: string, value: string): void { + if (name === this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { + this._lastAssignmentContext = value; + } + } + + postEvent(eventName: string, props: Map): void { + // noop due to lack of telemetry service + } +} + +class ExperimentServiceFilterProvider implements IExperimentationFilterProvider { + constructor( + private version: string, + private appName: string, + private machineId: string, + private targetPopulation: TargetPopulation + ) { } + + getFilterValue(filter: string): string | null { + switch (filter) { + case Filters.ApplicationVersion: + return this.version; // productService.version + case Filters.Build: + return this.appName; // productService.nameLong + case Filters.ClientId: + return this.machineId; + case Filters.Language: + return platform.language; + case Filters.ExtensionName: + return 'vscode-core'; // always return vscode-core for exp service + case Filters.TargetPopulation: + return this.targetPopulation; + default: + return ''; + } + } + + getFilters(): Map { + let filters: Map = new Map(); + let filterValues = Object.values(Filters); + for (let value of filterValues) { + filters.set(value, this.getFilterValue(value)); + } + + return filters; + } +} + +/* +Based upon the official VSCode currently existing filters in the +ExP backend for the VSCode cluster. +https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster +"X-MSEdge-Market": "detection.market", +"X-FD-Corpnet": "detection.corpnet", +"X-VSCode–AppVersion": "appversion", +"X-VSCode-Build": "build", +"X-MSEdge-ClientId": "clientid", +"X-VSCode-ExtensionName": "extensionname", +"X-VSCode-TargetPopulation": "targetpopulation", +"X-VSCode-Language": "language" +*/ + +enum Filters { + /** + * The market in which the extension is distributed. + */ + Market = 'X-MSEdge-Market', + + /** + * The corporation network. + */ + CorpNet = 'X-FD-Corpnet', + + /** + * Version of the application which uses experimentation service. + */ + ApplicationVersion = 'X-VSCode-AppVersion', + + /** + * Insiders vs Stable. + */ + Build = 'X-VSCode-Build', + + /** + * Client Id which is used as primary unit for the experimentation. + */ + ClientId = 'X-MSEdge-ClientId', + + /** + * Extension header. + */ + ExtensionName = 'X-VSCode-ExtensionName', + + /** + * The language in use by VS Code + */ + Language = 'X-VSCode-Language', + + /** + * The target population. + * This is used to separate internal, early preview, GA, etc. + */ + TargetPopulation = 'X-VSCode-TargetPopulation', +} + +enum TargetPopulation { + Team = 'team', + Internal = 'internal', + Insiders = 'insider', + Public = 'public', +} + +export class AssignmentService implements IAssignmentService { + _serviceBrand: undefined; + private tasClient: Promise | undefined; + private telemetry: AssignmentServiceTelemetry | undefined; + // private static MEMENTO_ID = 'experiment.service.memento'; + private networkInitialized = false; + + private overrideInitDelay: Promise; + + private get experimentsEnabled(): boolean { + return this.configurationService.getValue('workbench.enableExperiments') === true; + } + + constructor( + private readonly machineId: string, + @IConfigurationService private configurationService: IConfigurationService, + @IProductService private productService: IProductService + ) { + + if (productService.tasConfig && this.experimentsEnabled && getTelemetryLevel(this.configurationService) === TelemetryLevel.USAGE) { + this.tasClient = this.setupTASClient(); + } + + // For development purposes, configure the delay until tas local tas treatment ovverrides are available + const overrideDelaySetting = this.configurationService.getValue('experiments.overrideDelay'); + const overrideDelay = typeof overrideDelaySetting === 'number' ? overrideDelaySetting : 0; + this.overrideInitDelay = new Promise(resolve => setTimeout(resolve, overrideDelay)); + } + + async getTreatment(name: string): Promise { + // For development purposes, allow overriding tas assignments to test variants locally. + await this.overrideInitDelay; + const override = this.configurationService.getValue('experiments.override.' + name); + if (override !== undefined) { + return override; + } + + if (!this.tasClient) { + return undefined; + } + + if (!this.experimentsEnabled) { + return undefined; + } + + let result: T | undefined; + const client = await this.tasClient; + if (this.networkInitialized) { + result = client.getTreatmentVariable('vscode', name); + } else { + result = await client.getTreatmentVariableAsync('vscode', name, true); + } + return result; + } + + async getCurrentExperiments(): Promise { + if (!this.tasClient) { + return undefined; + } + + if (!this.experimentsEnabled) { + return undefined; + } + + await this.tasClient; + + return this.telemetry?.assignmentContext; + } + + private async setupTASClient(): Promise { + const targetPopulation = this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders; + const machineId = this.machineId; + const filterProvider = new ExperimentServiceFilterProvider( + this.productService.version, + this.productService.nameLong, + machineId, + targetPopulation + ); + + //const keyValueStorage = new MementoKeyValueStorage(new Memento(AssignmentService.MEMENTO_ID, this.storageService)); + + this.telemetry = new AssignmentServiceTelemetry(this.productService); + + const tasConfig = this.productService.tasConfig!; + const tasClient = new (await import('tas-client-umd')).ExperimentationService({ + filterProviders: [filterProvider], + telemetry: this.telemetry, + storageKey: storageKey, + keyValueStorage: undefined, + featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName, + assignmentContextTelemetryPropertyName: tasConfig.assignmentContextTelemetryPropertyName, + telemetryEventName: tasConfig.telemetryEventName, + endpoint: tasConfig.endpoint, + refetchInterval: refetchInterval, + }); + + await tasClient.initializePromise; + + tasClient.initialFetch.then(() => this.networkInitialized = true); + return tasClient; + } +} diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts index 1ce90fd63dad0..f3dbc2e705271 100644 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { mixin } from 'vs/base/common/objects'; import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; -async function getClient(aiKey: string): Promise { +async function getClient(aiKey: string, testCollector: boolean): Promise { const appInsights = await import('applicationinsights'); let client: TelemetryClient; if (appInsights.defaultClient) { @@ -29,7 +29,7 @@ async function getClient(aiKey: string): Promise { } if (aiKey.indexOf('AIF-') === 0) { - client.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1'; + client.config.endpointUrl = testCollector ? 'https://mobile.events.data.microsoft.com/collect/v1' : 'https://vortex.data.microsoft.com/collect/v1'; } return client; } @@ -44,6 +44,7 @@ export class AppInsightsAppender implements ITelemetryAppender { private _eventPrefix: string, private _defaultData: { [key: string]: any } | null, aiKeyOrClientFactory: string | (() => TelemetryClient), // allow factory function for testing + private readonly testCollector?: boolean ) { if (!this._defaultData) { this._defaultData = Object.create(null); @@ -68,7 +69,7 @@ export class AppInsightsAppender implements ITelemetryAppender { } if (!this._asyncAIClient) { - this._asyncAIClient = getClient(this._aiClient); + this._asyncAIClient = getClient(this._aiClient, this.testCollector ?? false); } this._asyncAIClient.then( From 886b4f7f9b907d0fcfe4bdfc3002a0102e57d5fb Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 1 Nov 2021 14:27:49 -0400 Subject: [PATCH 2/4] Modify useragent for experiment --- src/tsconfig.monaco.json | 3 ++- .../sharedProcess/sharedProcessMain.ts | 10 ++++++++- .../assignment/common/assignmentService.ts | 22 ++----------------- .../telemetry/node/appInsightsAppender.ts | 7 +++++- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 601aca6f9f021..5d97fdf7095af 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -26,6 +26,7 @@ ], "exclude": [ "node_modules/*", - "vs/platform/files/browser/htmlFileSystemProvider.ts" + "vs/platform/files/browser/htmlFileSystemProvider.ts", + "vs/platform/assignment/*" ] } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 32377a687dc0f..e97c135d99123 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -255,7 +255,15 @@ class SharedProcessMain extends Disposable { // Application Insights if (productService.aiConfig && productService.aiConfig.asimovKey) { const testCollector = await assignmentService.getTreatment('vscode.telemetryMigration') ?? false; - const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, testCollector); + const insiders = productService.quality !== 'stable'; + // Insiders send to both collector and vortex if assigned. + // Stable only send to one + if (insiders && testCollector) { + const collectorAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, testCollector, true); + this._register(toDisposable(() => collectorAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data + appenders.push(collectorAppender); + } + const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, insiders ? false : testCollector); this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data appenders.push(appInsightsAppender); } diff --git a/src/vs/platform/assignment/common/assignmentService.ts b/src/vs/platform/assignment/common/assignmentService.ts index 5a7ee57a534a1..77f10f20cc99d 100644 --- a/src/vs/platform/assignment/common/assignmentService.ts +++ b/src/vs/platform/assignment/common/assignmentService.ts @@ -13,7 +13,6 @@ import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; export interface IAssignmentService { readonly _serviceBrand: undefined; getTreatment(name: string): Promise; - getCurrentExperiments(): Promise; } const storageKey = 'VSCode.ABExp.FeatureData'; @@ -39,7 +38,6 @@ const refetchInterval = 0; // no polling class AssignmentServiceTelemetry implements IExperimentationTelemetry { private _lastAssignmentContext: string | undefined; constructor( - private productService: IProductService ) { } get assignmentContext(): string[] | undefined { @@ -47,9 +45,7 @@ class AssignmentServiceTelemetry implements IExperimentationTelemetry { } setSharedProperty(name: string, value: string): void { - if (name === this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { - this._lastAssignmentContext = value; - } + // noop due to lack of telemetry service } postEvent(eventName: string, props: Map): void { @@ -214,20 +210,6 @@ export class AssignmentService implements IAssignmentService { return result; } - async getCurrentExperiments(): Promise { - if (!this.tasClient) { - return undefined; - } - - if (!this.experimentsEnabled) { - return undefined; - } - - await this.tasClient; - - return this.telemetry?.assignmentContext; - } - private async setupTASClient(): Promise { const targetPopulation = this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders; const machineId = this.machineId; @@ -240,7 +222,7 @@ export class AssignmentService implements IAssignmentService { //const keyValueStorage = new MementoKeyValueStorage(new Memento(AssignmentService.MEMENTO_ID, this.storageService)); - this.telemetry = new AssignmentServiceTelemetry(this.productService); + this.telemetry = new AssignmentServiceTelemetry(); const tasConfig = this.productService.tasConfig!; const tasClient = new (await import('tas-client-umd')).ExperimentationService({ diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts index f3dbc2e705271..32eda54fc0dd4 100644 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts @@ -44,7 +44,8 @@ export class AppInsightsAppender implements ITelemetryAppender { private _eventPrefix: string, private _defaultData: { [key: string]: any } | null, aiKeyOrClientFactory: string | (() => TelemetryClient), // allow factory function for testing - private readonly testCollector?: boolean + private readonly testCollector?: boolean, + private readonly mirrored?: boolean ) { if (!this._defaultData) { this._defaultData = Object.create(null); @@ -90,6 +91,10 @@ export class AppInsightsAppender implements ITelemetryAppender { data = mixin(data, this._defaultData); data = validateTelemetryData(data); + if (this.testCollector) { + data.properties['common.useragent'] = this.mirrored ? 'mirror-collector++' : 'collector++'; + } + this._withAIClient((aiClient) => aiClient.trackEvent({ name: this._eventPrefix + '/' + eventName, properties: data.properties, From 4c197b0e6a1e3bd90df3eecfe123ea39380b6668 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 1 Nov 2021 12:03:09 -0700 Subject: [PATCH 3/4] minor refactor --- .../platform/assignment/common/assignment.ts | 116 ++++++++++++++ .../assignment/common/assignmentService.ts | 143 +----------------- .../experiment/common/experimentService.ts | 120 +-------------- 3 files changed, 128 insertions(+), 251 deletions(-) create mode 100644 src/vs/platform/assignment/common/assignment.ts diff --git a/src/vs/platform/assignment/common/assignment.ts b/src/vs/platform/assignment/common/assignment.ts new file mode 100644 index 0000000000000..513296737f0b3 --- /dev/null +++ b/src/vs/platform/assignment/common/assignment.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/base/common/platform'; +import { IExperimentationFilterProvider } from 'tas-client-umd'; + +export const ASSIGNMENT_STORAGE_KEY = 'VSCode.ABExp.FeatureData'; +export const ASSIGNMENT_REFETCH_INTERVAL = 0; // no polling + +export interface IAssignmentService { + readonly _serviceBrand: undefined; + getTreatment(name: string): Promise; +} + +export enum TargetPopulation { + Team = 'team', + Internal = 'internal', + Insiders = 'insider', + Public = 'public', +} + +/* +Based upon the official VSCode currently existing filters in the +ExP backend for the VSCode cluster. +https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster +"X-MSEdge-Market": "detection.market", +"X-FD-Corpnet": "detection.corpnet", +"X-VSCode–AppVersion": "appversion", +"X-VSCode-Build": "build", +"X-MSEdge-ClientId": "clientid", +"X-VSCode-ExtensionName": "extensionname", +"X-VSCode-TargetPopulation": "targetpopulation", +"X-VSCode-Language": "language" +*/ +export enum Filters { + /** + * The market in which the extension is distributed. + */ + Market = 'X-MSEdge-Market', + + /** + * The corporation network. + */ + CorpNet = 'X-FD-Corpnet', + + /** + * Version of the application which uses experimentation service. + */ + ApplicationVersion = 'X-VSCode-AppVersion', + + /** + * Insiders vs Stable. + */ + Build = 'X-VSCode-Build', + + /** + * Client Id which is used as primary unit for the experimentation. + */ + ClientId = 'X-MSEdge-ClientId', + + /** + * Extension header. + */ + ExtensionName = 'X-VSCode-ExtensionName', + + /** + * The language in use by VS Code + */ + Language = 'X-VSCode-Language', + + /** + * The target population. + * This is used to separate internal, early preview, GA, etc. + */ + TargetPopulation = 'X-VSCode-TargetPopulation', +} + +export class AssignmentFilterProvider implements IExperimentationFilterProvider { + constructor( + private version: string, + private appName: string, + private machineId: string, + private targetPopulation: TargetPopulation + ) { } + + getFilterValue(filter: string): string | null { + switch (filter) { + case Filters.ApplicationVersion: + return this.version; // productService.version + case Filters.Build: + return this.appName; // productService.nameLong + case Filters.ClientId: + return this.machineId; + case Filters.Language: + return platform.language; + case Filters.ExtensionName: + return 'vscode-core'; // always return vscode-core for exp service + case Filters.TargetPopulation: + return this.targetPopulation; + default: + return ''; + } + } + + getFilters(): Map { + let filters: Map = new Map(); + let filterValues = Object.values(Filters); + for (let value of filterValues) { + filters.set(value, this.getFilterValue(value)); + } + + return filters; + } +} diff --git a/src/vs/platform/assignment/common/assignmentService.ts b/src/vs/platform/assignment/common/assignmentService.ts index 77f10f20cc99d..6be849330c5ad 100644 --- a/src/vs/platform/assignment/common/assignmentService.ts +++ b/src/vs/platform/assignment/common/assignmentService.ts @@ -3,47 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as platform from 'vs/base/common/platform'; -import type { IExperimentationTelemetry, IExperimentationFilterProvider, ExperimentationService as TASClient } from 'tas-client-umd'; +import type { IExperimentationTelemetry, ExperimentationService as TASClient } from 'tas-client-umd'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProductService } from 'vs/platform/product/common/productService'; import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; - -export interface IAssignmentService { - readonly _serviceBrand: undefined; - getTreatment(name: string): Promise; -} - -const storageKey = 'VSCode.ABExp.FeatureData'; -const refetchInterval = 0; // no polling - -// class MementoKeyValueStorage implements IKeyValueStorage { -// private mementoObj: MementoObject; -// constructor(private memento: Memento) { -// this.mementoObj = memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); -// } - -// async getValue(key: string, defaultValue?: T | undefined): Promise { -// const value = await this.mementoObj[key]; -// return value || defaultValue; -// } - -// setValue(key: string, value: T): void { -// this.mementoObj[key] = value; -// this.memento.saveMemento(); -// } -//} +import { AssignmentFilterProvider, ASSIGNMENT_REFETCH_INTERVAL, ASSIGNMENT_STORAGE_KEY, IAssignmentService, TargetPopulation } from 'vs/platform/assignment/common/assignment'; class AssignmentServiceTelemetry implements IExperimentationTelemetry { - private _lastAssignmentContext: string | undefined; constructor( ) { } - get assignmentContext(): string[] | undefined { - return this._lastAssignmentContext?.split(';'); - } - setSharedProperty(name: string, value: string): void { // noop due to lack of telemetry service } @@ -53,113 +23,10 @@ class AssignmentServiceTelemetry implements IExperimentationTelemetry { } } -class ExperimentServiceFilterProvider implements IExperimentationFilterProvider { - constructor( - private version: string, - private appName: string, - private machineId: string, - private targetPopulation: TargetPopulation - ) { } - - getFilterValue(filter: string): string | null { - switch (filter) { - case Filters.ApplicationVersion: - return this.version; // productService.version - case Filters.Build: - return this.appName; // productService.nameLong - case Filters.ClientId: - return this.machineId; - case Filters.Language: - return platform.language; - case Filters.ExtensionName: - return 'vscode-core'; // always return vscode-core for exp service - case Filters.TargetPopulation: - return this.targetPopulation; - default: - return ''; - } - } - - getFilters(): Map { - let filters: Map = new Map(); - let filterValues = Object.values(Filters); - for (let value of filterValues) { - filters.set(value, this.getFilterValue(value)); - } - - return filters; - } -} - -/* -Based upon the official VSCode currently existing filters in the -ExP backend for the VSCode cluster. -https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster -"X-MSEdge-Market": "detection.market", -"X-FD-Corpnet": "detection.corpnet", -"X-VSCode–AppVersion": "appversion", -"X-VSCode-Build": "build", -"X-MSEdge-ClientId": "clientid", -"X-VSCode-ExtensionName": "extensionname", -"X-VSCode-TargetPopulation": "targetpopulation", -"X-VSCode-Language": "language" -*/ - -enum Filters { - /** - * The market in which the extension is distributed. - */ - Market = 'X-MSEdge-Market', - - /** - * The corporation network. - */ - CorpNet = 'X-FD-Corpnet', - - /** - * Version of the application which uses experimentation service. - */ - ApplicationVersion = 'X-VSCode-AppVersion', - - /** - * Insiders vs Stable. - */ - Build = 'X-VSCode-Build', - - /** - * Client Id which is used as primary unit for the experimentation. - */ - ClientId = 'X-MSEdge-ClientId', - - /** - * Extension header. - */ - ExtensionName = 'X-VSCode-ExtensionName', - - /** - * The language in use by VS Code - */ - Language = 'X-VSCode-Language', - - /** - * The target population. - * This is used to separate internal, early preview, GA, etc. - */ - TargetPopulation = 'X-VSCode-TargetPopulation', -} - -enum TargetPopulation { - Team = 'team', - Internal = 'internal', - Insiders = 'insider', - Public = 'public', -} - export class AssignmentService implements IAssignmentService { _serviceBrand: undefined; private tasClient: Promise | undefined; private telemetry: AssignmentServiceTelemetry | undefined; - // private static MEMENTO_ID = 'experiment.service.memento'; private networkInitialized = false; private overrideInitDelay: Promise; @@ -213,7 +80,7 @@ export class AssignmentService implements IAssignmentService { private async setupTASClient(): Promise { const targetPopulation = this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders; const machineId = this.machineId; - const filterProvider = new ExperimentServiceFilterProvider( + const filterProvider = new AssignmentFilterProvider( this.productService.version, this.productService.nameLong, machineId, @@ -228,13 +95,13 @@ export class AssignmentService implements IAssignmentService { const tasClient = new (await import('tas-client-umd')).ExperimentationService({ filterProviders: [filterProvider], telemetry: this.telemetry, - storageKey: storageKey, + storageKey: ASSIGNMENT_STORAGE_KEY, keyValueStorage: undefined, featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName, assignmentContextTelemetryPropertyName: tasConfig.assignmentContextTelemetryPropertyName, telemetryEventName: tasConfig.telemetryEventName, endpoint: tasConfig.endpoint, - refetchInterval: refetchInterval, + refetchInterval: ASSIGNMENT_REFETCH_INTERVAL, }); await tasClient.initializePromise; diff --git a/src/vs/workbench/services/experiment/common/experimentService.ts b/src/vs/workbench/services/experiment/common/experimentService.ts index c9d0c8d54a514..6605f3545c1f8 100644 --- a/src/vs/workbench/services/experiment/common/experimentService.ts +++ b/src/vs/workbench/services/experiment/common/experimentService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as platform from 'vs/base/common/platform'; -import type { IKeyValueStorage, IExperimentationTelemetry, IExperimentationFilterProvider, ExperimentationService as TASClient } from 'tas-client-umd'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import type { IKeyValueStorage, IExperimentationTelemetry, ExperimentationService as TASClient } from 'tas-client-umd'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -12,18 +12,14 @@ import { ITelemetryData } from 'vs/base/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProductService } from 'vs/platform/product/common/productService'; +import { AssignmentFilterProvider, ASSIGNMENT_REFETCH_INTERVAL, ASSIGNMENT_STORAGE_KEY, IAssignmentService, TargetPopulation } from 'vs/platform/assignment/common/assignment'; export const ITASExperimentService = createDecorator('TASExperimentService'); -export interface ITASExperimentService { - readonly _serviceBrand: undefined; - getTreatment(name: string): Promise; +export interface ITASExperimentService extends IAssignmentService { getCurrentExperiments(): Promise; } -const storageKey = 'VSCode.ABExp.FeatureData'; -const refetchInterval = 0; // no polling - class MementoKeyValueStorage implements IKeyValueStorage { private mementoObj: MementoObject; constructor(private memento: Memento) { @@ -77,108 +73,6 @@ class ExperimentServiceTelemetry implements IExperimentationTelemetry { } } -class ExperimentServiceFilterProvider implements IExperimentationFilterProvider { - constructor( - private version: string, - private appName: string, - private machineId: string, - private targetPopulation: TargetPopulation - ) { } - - getFilterValue(filter: string): string | null { - switch (filter) { - case Filters.ApplicationVersion: - return this.version; // productService.version - case Filters.Build: - return this.appName; // productService.nameLong - case Filters.ClientId: - return this.machineId; - case Filters.Language: - return platform.language; - case Filters.ExtensionName: - return 'vscode-core'; // always return vscode-core for exp service - case Filters.TargetPopulation: - return this.targetPopulation; - default: - return ''; - } - } - - getFilters(): Map { - let filters: Map = new Map(); - let filterValues = Object.values(Filters); - for (let value of filterValues) { - filters.set(value, this.getFilterValue(value)); - } - - return filters; - } -} - -/* -Based upon the official VSCode currently existing filters in the -ExP backend for the VSCode cluster. -https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster -"X-MSEdge-Market": "detection.market", -"X-FD-Corpnet": "detection.corpnet", -"X-VSCode–AppVersion": "appversion", -"X-VSCode-Build": "build", -"X-MSEdge-ClientId": "clientid", -"X-VSCode-ExtensionName": "extensionname", -"X-VSCode-TargetPopulation": "targetpopulation", -"X-VSCode-Language": "language" -*/ - -enum Filters { - /** - * The market in which the extension is distributed. - */ - Market = 'X-MSEdge-Market', - - /** - * The corporation network. - */ - CorpNet = 'X-FD-Corpnet', - - /** - * Version of the application which uses experimentation service. - */ - ApplicationVersion = 'X-VSCode-AppVersion', - - /** - * Insiders vs Stable. - */ - Build = 'X-VSCode-Build', - - /** - * Client Id which is used as primary unit for the experimentation. - */ - ClientId = 'X-MSEdge-ClientId', - - /** - * Extension header. - */ - ExtensionName = 'X-VSCode-ExtensionName', - - /** - * The language in use by VS Code - */ - Language = 'X-VSCode-Language', - - /** - * The target population. - * This is used to separate internal, early preview, GA, etc. - */ - TargetPopulation = 'X-VSCode-TargetPopulation', -} - -enum TargetPopulation { - Team = 'team', - Internal = 'internal', - Insiders = 'insider', - Public = 'public', -} - export class ExperimentService implements ITASExperimentService { _serviceBrand: undefined; private tasClient: Promise | undefined; @@ -274,7 +168,7 @@ export class ExperimentService implements ITASExperimentService { const telemetryInfo = await this.telemetryService.getTelemetryInfo(); const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); const machineId = telemetryInfo.machineId; - const filterProvider = new ExperimentServiceFilterProvider( + const filterProvider = new AssignmentFilterProvider( this.productService.version, this.productService.nameLong, machineId, @@ -289,13 +183,13 @@ export class ExperimentService implements ITASExperimentService { const tasClient = new (await import('tas-client-umd')).ExperimentationService({ filterProviders: [filterProvider], telemetry: this.telemetry, - storageKey: storageKey, + storageKey: ASSIGNMENT_STORAGE_KEY, keyValueStorage: keyValueStorage, featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName, assignmentContextTelemetryPropertyName: tasConfig.assignmentContextTelemetryPropertyName, telemetryEventName: tasConfig.telemetryEventName, endpoint: tasConfig.endpoint, - refetchInterval: refetchInterval, + refetchInterval: ASSIGNMENT_REFETCH_INTERVAL, }); await tasClient.initializePromise; From 6e3d8eff2ff83fdcd2bad2ad36a0e41ed4254b2a Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 1 Nov 2021 12:32:53 -0700 Subject: [PATCH 4/4] remove extraneous comment --- src/vs/platform/assignment/common/assignmentService.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/platform/assignment/common/assignmentService.ts b/src/vs/platform/assignment/common/assignmentService.ts index 6be849330c5ad..51e1d1d95de05 100644 --- a/src/vs/platform/assignment/common/assignmentService.ts +++ b/src/vs/platform/assignment/common/assignmentService.ts @@ -87,8 +87,6 @@ export class AssignmentService implements IAssignmentService { targetPopulation ); - //const keyValueStorage = new MementoKeyValueStorage(new Memento(AssignmentService.MEMENTO_ID, this.storageService)); - this.telemetry = new AssignmentServiceTelemetry(); const tasConfig = this.productService.tasConfig!;