microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.0.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/experimentService/experimentService.ts

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