microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.5.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/experimentService/experimentService.ts

181lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as https from "https";
5import * as vscode from "vscode";
6import { IExperiment } from "./IExperiment";
7import { PromiseUtil } from "../../common/node/promise";
8import { TelemetryHelper } from "../../common/telemetryHelper";
9import { Telemetry } from "../../common/telemetry";
10import { ExtensionConfigManager } from "../extensionConfigManager";
11
12export enum ExperimentStatuses {
13 ENABLED = "enabled",
14 DISABLED = "disabled",
15 FAILED = "failed",
16}
17
18export interface ExperimentConfig {
19 experimentName: string;
20 popCoveragePercent: number;
21 enabled: boolean;
22}
23
24export interface ExperimentParameters extends ExperimentConfig {
25 [key: string]: any;
26 extensionId?: string;
27}
28
29export interface ExperimentResult {
30 resultStatus: ExperimentStatuses;
31 updatedExperimentParameters: ExperimentParameters;
32 error?: Error;
33}
34
35export class ExperimentService implements vscode.Disposable {
36 private static instance: ExperimentService;
37
38 private readonly endpointURL: string;
39 private downloadedExperimentsConfig: Array<ExperimentConfig> | null;
40 private experimentsInstances: Map<string, IExperiment>;
41 private downloadConfigRequest: Promise<ExperimentConfig[]>;
42 private cancellationTokenSource: vscode.CancellationTokenSource;
43
44 public static create(): ExperimentService {
45 if (!ExperimentService.instance) {
46 ExperimentService.instance = new ExperimentService();
47 }
48
49 return ExperimentService.instance;
50 }
51
52 public async runExperiments(): Promise<void> {
53 if (!this.downloadedExperimentsConfig) {
54 this.downloadedExperimentsConfig = await this.downloadConfigRequest;
55 this.experimentsInstances = await this.initializeExperimentsInstances();
56 }
57
58 let experimentResults: Array<ExperimentResult> = await Promise.all(
59 this.downloadedExperimentsConfig.map(expConfig => this.executeExperiment(expConfig)),
60 );
61
62 this.sendExperimentTelemetry(experimentResults);
63 }
64
65 public dispose(): void {
66 this.cancellationTokenSource.cancel();
67 this.cancellationTokenSource.dispose();
68 }
69
70 private constructor() {
71 this.endpointURL =
72 "https://microsoft.github.io/vscode-react-native/experiments/experimentsConfig.json";
73 this.cancellationTokenSource = new vscode.CancellationTokenSource();
74 this.downloadedExperimentsConfig = null;
75
76 this.downloadConfigRequest = this.retryDownloadExperimentsConfig();
77 }
78
79 private async executeExperiment(expConfig: ExperimentConfig): Promise<ExperimentResult> {
80 let curExperimentParameters = ExtensionConfigManager.config.get(expConfig.experimentName);
81 let expInstance = this.experimentsInstances.get(expConfig.experimentName);
82
83 let expResult: ExperimentResult;
84 if (expInstance && expConfig.enabled) {
85 try {
86 expResult = await expInstance.run(expConfig, curExperimentParameters);
87 ExtensionConfigManager.config.set(
88 expConfig.experimentName,
89 expResult.updatedExperimentParameters,
90 );
91 } catch (err) {
92 expResult = {
93 resultStatus: ExperimentStatuses.FAILED,
94 updatedExperimentParameters: expConfig,
95 error: err,
96 };
97 }
98 } else {
99 expResult = {
100 resultStatus: ExperimentStatuses.DISABLED,
101 updatedExperimentParameters: expConfig,
102 };
103 }
104
105 return expResult;
106 }
107
108 private async retryDownloadExperimentsConfig(retryCount = 60): Promise<ExperimentConfig[]> {
109 try {
110 return await this.downloadExperimentsConfig();
111 } catch (err) {
112 if (retryCount < 1 || this.cancellationTokenSource.token.isCancellationRequested) {
113 throw err;
114 }
115
116 await PromiseUtil.delay(2000);
117 return await this.retryDownloadExperimentsConfig(--retryCount);
118 }
119 }
120
121 private async initializeExperimentsInstances(): Promise<Map<string, IExperiment>> {
122 let expInstances = new Map<string, IExperiment>();
123
124 if (this.downloadedExperimentsConfig) {
125 for (let expConfig of this.downloadedExperimentsConfig) {
126 try {
127 let expClass = await import(`./experiments/${expConfig.experimentName}`);
128 expInstances.set(expConfig.experimentName, new expClass.default());
129 } catch (err) {
130 expConfig.enabled = false;
131 }
132 }
133 }
134
135 return expInstances;
136 }
137
138 private downloadExperimentsConfig(): Promise<ExperimentConfig[]> {
139 return new Promise<ExperimentConfig[]>((resolve, reject) => {
140 https
141 .get(this.endpointURL, response => {
142 let data = "";
143 response.setEncoding("utf8");
144 response.on("data", (chunk: string) => (data += chunk));
145 response.on("end", () => {
146 try {
147 resolve(JSON.parse(data));
148 } catch (err) {
149 reject(err);
150 }
151 });
152 response.on("error", reject);
153 })
154 .on("error", reject);
155 });
156 }
157
158 private sendExperimentTelemetry(experimentsResults: ExperimentResult[]): void {
159 const runExperimentsEvent = TelemetryHelper.createTelemetryEvent("runExperiments");
160
161 experimentsResults.forEach(expResult => {
162 if (expResult.resultStatus === ExperimentStatuses.FAILED && expResult.error) {
163 TelemetryHelper.addTelemetryEventErrorProperty(
164 runExperimentsEvent,
165 expResult.error,
166 undefined,
167 `${expResult.updatedExperimentParameters.experimentName}.`,
168 );
169 } else {
170 TelemetryHelper.addTelemetryEventProperty(
171 runExperimentsEvent,
172 expResult.updatedExperimentParameters.experimentName,
173 expResult.resultStatus,
174 false,
175 );
176 }
177 });
178
179 Telemetry.send(runExperimentsEvent);
180 }
181}
182