microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c036b0d343245c82886cd5906e37ff50b8b30d34

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/experimentService/experimentService.ts

188lines · 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 Configstore from "configstore";
5import * as https from "https";
6import * as vscode from "vscode";
7import { IExperiment } from "./IExperiment";
8import { PromiseUtil } from "../../common/node/promise";
9import { TelemetryHelper } from "../../common/telemetryHelper";
10import { Telemetry } from "../../common/telemetry";
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 readonly configName: string;
40 private config: Configstore;
41 private downloadedExperimentsConfig: Array<ExperimentConfig> | null;
42 private experimentsInstances: Map<string, IExperiment>;
43 private downloadConfigRequest: Promise<ExperimentConfig[]>;
44 private promiseUtil: PromiseUtil;
45 private cancellationTokenSource: vscode.CancellationTokenSource;
46
47 public static create () {
48 if (!ExperimentService.instance) {
49 ExperimentService.instance = new ExperimentService();
50 }
51
52 return ExperimentService.instance;
53 }
54
55 public async runExperiments(): Promise<void> {
56 if (!this.downloadedExperimentsConfig) {
57 this.downloadedExperimentsConfig = await this.downloadConfigRequest;
58 this.experimentsInstances = await this.initializeExperimentsInstances();
59 }
60
61 let experimentResults: Array<ExperimentResult> = await Promise.all(
62 this.downloadedExperimentsConfig.map(expConfig => this.executeExperiment(expConfig))
63 );
64
65 this.sendExperimentTelemetry(experimentResults);
66 }
67
68 public dispose() {
69 this.cancellationTokenSource.cancel();
70 this.cancellationTokenSource.dispose();
71 }
72
73 private constructor() {
74 this.endpointURL = "https://microsoft.github.io/vscode-react-native/experiments/experimentsConfig.json";
75 this.configName = "reactNativeToolsConfig";
76
77 this.promiseUtil = new PromiseUtil();
78 this.config = new Configstore(this.configName);
79 this.cancellationTokenSource = new vscode.CancellationTokenSource();
80 this.downloadedExperimentsConfig = null;
81
82 this.downloadConfigRequest = this.retryDownloadExperimentsConfig();
83 }
84
85 private async executeExperiment(expConfig: ExperimentConfig): Promise<ExperimentResult> {
86 let curExperimentParameters = this.config.get(expConfig.experimentName);
87 let expInstance = this.experimentsInstances.get(expConfig.experimentName);
88
89 let expResult: ExperimentResult;
90 if (expInstance && expConfig.enabled) {
91 try {
92 expResult = await expInstance.run(expConfig, curExperimentParameters);
93 this.config.set(expConfig.experimentName, expResult.updatedExperimentParameters);
94 } catch (err) {
95 expResult = {
96 resultStatus: ExperimentStatuses.FAILED,
97 updatedExperimentParameters: expConfig,
98 error: err,
99 };
100 }
101 } else {
102 expResult = {
103 resultStatus: ExperimentStatuses.DISABLED,
104 updatedExperimentParameters: expConfig,
105 };
106 }
107
108 return expResult;
109 }
110
111 private async retryDownloadExperimentsConfig(retryCount = 60): Promise<ExperimentConfig[]> {
112 try {
113 return await this.downloadExperimentsConfig();
114 } catch (err) {
115 if (retryCount < 1 || this.cancellationTokenSource.token.isCancellationRequested) {
116 throw err;
117 }
118
119 await this.promiseUtil.delay(2000);
120 return await this.retryDownloadExperimentsConfig(--retryCount);
121 }
122 }
123
124 private async initializeExperimentsInstances(): Promise<Map<string, IExperiment>> {
125 let expInstances = new Map<string, IExperiment>();
126
127 if (this.downloadedExperimentsConfig) {
128 for (let expConfig of this.downloadedExperimentsConfig) {
129 try {
130 let expClass = await import(`./experiments/${expConfig.experimentName}`);
131 expInstances.set(
132 expConfig.experimentName,
133 new expClass.default()
134 );
135 } catch (err) {
136 expConfig.enabled = false;
137 }
138 }
139 }
140
141 return expInstances;
142 }
143
144 private downloadExperimentsConfig(): Promise<ExperimentConfig[]> {
145 return new Promise<ExperimentConfig[]>((resolve, reject) => {
146 https.get(this.endpointURL, (response) => {
147 let data = "";
148 response.setEncoding("utf8");
149 response.on("data", (chunk: string) => (data += chunk));
150 response.on("end", () => {
151 try {
152 resolve(JSON.parse(data));
153 } catch (err) {
154 reject(err);
155 }
156 });
157 response.on("error", reject);
158 }).on("error", reject);
159 });
160 }
161
162 private sendExperimentTelemetry(experimentsResults: ExperimentResult[]): void {
163 const runExperimentsEvent = TelemetryHelper.createTelemetryEvent("runExperiments");
164
165 experimentsResults.forEach(expResult => {
166 if (
167 expResult.resultStatus === ExperimentStatuses.FAILED
168 && expResult.error
169 ) {
170 TelemetryHelper.addTelemetryEventErrorProperty(
171 runExperimentsEvent,
172 expResult.error,
173 undefined,
174 `${expResult.updatedExperimentParameters.experimentName}.`
175 );
176 } else {
177 TelemetryHelper.addTelemetryEventProperty(
178 runExperimentsEvent,
179 expResult.updatedExperimentParameters.experimentName,
180 expResult.resultStatus,
181 false
182 );
183 }
184 });
185
186 Telemetry.send(runExperimentsEvent);
187 }
188}
189