1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const fs = require('fs');
|
7 | const path = require('path');
|
8 |
|
9 | const SRC_PATH = path.resolve(__dirname, '..');
|
10 | const NODE_MODULES_PATH = path.resolve(SRC_PATH, 'node_modules');
|
11 | const espree = require(path.resolve(NODE_MODULES_PATH, '@typescript-eslint', 'parser'));
|
12 | const parseOptions = {
|
13 | ecmaVersion: 11,
|
14 | sourceType: 'module',
|
15 | range: true,
|
16 | };
|
17 |
|
18 | const USER_METRICS_ENUM_ENDPOINT = '__lastValidEnumPosition';
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | function isClassNameDeclaration(node, className) {
|
25 | const isClassDeclaration = node.type === 'ExportNamedDeclaration' && node.declaration.type === 'ClassDeclaration';
|
26 | if (className) {
|
27 | return isClassDeclaration && node.declaration.id.name === className;
|
28 | }
|
29 | return isClassDeclaration;
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | function isEnumDeclaration(node, enumName) {
|
37 | const isEnumDeclaration = node.type === 'ExportNamedDeclaration' && node.declaration.type === 'TSEnumDeclaration';
|
38 | if (enumName) {
|
39 | return isEnumDeclaration && node.declaration.id.name === enumName;
|
40 | }
|
41 | return isEnumDeclaration;
|
42 | }
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | function findFunctionInClass(classNode, functionName) {
|
48 | for (const node of classNode.declaration.body.body) {
|
49 | if (node.key.name === functionName) {
|
50 | return node;
|
51 | }
|
52 | }
|
53 | return null;
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | function isExperimentRegistrationCall(node) {
|
60 | return node.expression && node.expression.type === 'CallExpression' &&
|
61 | node.expression.callee.property.name === 'register';
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | function getExperimentNameEnum(mainImplFile) {
|
68 | const mainAST = espree.parse(mainImplFile, parseOptions);
|
69 |
|
70 | let experimentNameEnum;
|
71 | for (const node of mainAST.body) {
|
72 | if (isEnumDeclaration(node, 'ExperimentName')) {
|
73 | experimentNameEnum = node;
|
74 | break;
|
75 | }
|
76 | }
|
77 |
|
78 | const map = new Map();
|
79 | if (!experimentNameEnum) {
|
80 | return map;
|
81 | }
|
82 | for (const member of experimentNameEnum.declaration.members) {
|
83 | map.set(member.id.name, member.initializer.value);
|
84 | }
|
85 | return map;
|
86 | }
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | function isExperimentNameReference(node) {
|
93 | if (node.type !== 'MemberExpression') {
|
94 | return false;
|
95 | }
|
96 | if (node.object.type !== 'MemberExpression' || node.object.property?.name !== 'ExperimentName') {
|
97 | return false;
|
98 | }
|
99 | if (node.object.object.type !== 'MemberExpression' || node.object.object.property?.name !== 'Runtime') {
|
100 | return false;
|
101 | }
|
102 | if (node.object.object.object.type !== 'Identifier' || node.object.object.object.name !== 'Root') {
|
103 | return false;
|
104 | }
|
105 | return node.property.name;
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | function getMainImplExperimentList(mainImplFile, experimentNames) {
|
112 | const mainAST = espree.parse(mainImplFile, parseOptions);
|
113 |
|
114 |
|
115 | let mainImplClassNode;
|
116 | for (const node of mainAST.body) {
|
117 | if (isClassNameDeclaration(node, 'MainImpl')) {
|
118 | mainImplClassNode = node;
|
119 | break;
|
120 | }
|
121 | }
|
122 | if (!mainImplClassNode) {
|
123 | return null;
|
124 | }
|
125 |
|
126 |
|
127 | const initializeExperimentNode = findFunctionInClass(mainImplClassNode, 'initializeExperiments');
|
128 | if (!initializeExperimentNode) {
|
129 | return null;
|
130 | }
|
131 |
|
132 |
|
133 | const experiments = [];
|
134 | for (const statement of initializeExperimentNode.value.body.body) {
|
135 | if (isExperimentRegistrationCall(statement)) {
|
136 |
|
137 | const experimentNameArg = statement.expression.arguments[0];
|
138 |
|
139 | if (experimentNameArg.type === 'Literal') {
|
140 | experiments.push(experimentNameArg.value);
|
141 | } else {
|
142 |
|
143 | const experimentName = isExperimentNameReference(experimentNameArg);
|
144 | if (experimentName) {
|
145 | const translatedName = experimentNames.get(experimentName);
|
146 | if (!translatedName) {
|
147 | console.log('Failed to resolve Root.Runtime.ExperimentName.${experimentName} to a string');
|
148 | process.exit(1);
|
149 | }
|
150 | experiments.push(translatedName);
|
151 | } else {
|
152 | console.log('Unexpected argument to Root.Runtime.experiments.register: ', experimentNameArg);
|
153 | process.exit(1);
|
154 | }
|
155 | }
|
156 | }
|
157 | }
|
158 | return experiments.length ? experiments : null;
|
159 | }
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | function isExperimentEnumDeclaration(node) {
|
165 | return node.type === 'ExportNamedDeclaration' && node.declaration.declarations &&
|
166 | node.declaration.declarations[0].id.name === 'DevtoolsExperiments';
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | function getUserMetricExperimentList(userMetricsFile) {
|
173 | const userMetricsAST = espree.parse(userMetricsFile, {ecmaVersion: 11, sourceType: 'module', range: true});
|
174 | for (const node of userMetricsAST.body) {
|
175 | if (isExperimentEnumDeclaration(node)) {
|
176 | return node.declaration.declarations[0].init.properties.map(property => {
|
177 | return property.key.value;
|
178 | });
|
179 | }
|
180 | }
|
181 | return null;
|
182 | }
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | function compareExperimentLists(mainImplList, userMetricsList) {
|
188 |
|
189 | let errorFound = false;
|
190 | if (!mainImplList) {
|
191 | console.log(
|
192 | 'Changes to Devtools Experiment registration have prevented this check from finding registered experiments.');
|
193 | console.log('Please update scripts/check_experiments.js to account for the new experiment registration.');
|
194 | errorFound = true;
|
195 | }
|
196 | if (!userMetricsList) {
|
197 | console.log(
|
198 | 'Changes to Devtools Experiment UserMetrics enum have prevented this check from finding experiments registered for telemetry.');
|
199 | console.log('Please update scripts/check_experiments.js to account for the new experiment telemetry format.');
|
200 | errorFound = true;
|
201 | }
|
202 | if (errorFound) {
|
203 | process.exit(1);
|
204 | }
|
205 |
|
206 |
|
207 | const missingTelemetry = mainImplList.filter(experiment => !userMetricsList.includes(experiment));
|
208 | const staleTelemetry = userMetricsList.filter(
|
209 | experiment => !mainImplList.includes(experiment) && experiment !== USER_METRICS_ENUM_ENDPOINT);
|
210 | if (missingTelemetry.length) {
|
211 | console.log('Devtools Experiments have been added without corresponding histogram update!');
|
212 | console.log(missingTelemetry.join('\n'));
|
213 | console.log(
|
214 | 'Please ensure that the DevtoolsExperiments enum in UserMetrics.ts is updated with the new experiment.');
|
215 | console.log(
|
216 | 'Please ensure that a corresponding CL is openend against chromium.src/tools/metrics/histograms/enums.xml to update the DevtoolsExperiments enum');
|
217 | errorFound = true;
|
218 | }
|
219 | if (staleTelemetry.length) {
|
220 | console.log('Devtools Experiments that are no longer registered are still listed in the telemetry enum!');
|
221 | console.log(staleTelemetry.join('\n'));
|
222 | console.log(
|
223 | 'Please ensure that the DevtoolsExperiments enum in UserMetrics.ts is updated to remove these stale experiments.');
|
224 | errorFound = true;
|
225 | }
|
226 | if (errorFound) {
|
227 | process.exit(1);
|
228 | }
|
229 | console.log('DevTools Experiment Telemetry checker passed.');
|
230 | }
|
231 |
|
232 | function main() {
|
233 | const mainImplPath = path.resolve(__dirname, '..', 'front_end', 'entrypoints', 'main', 'MainImpl.ts');
|
234 | const mainImplFile = fs.readFileSync(mainImplPath, 'utf-8');
|
235 |
|
236 | const userMetricsPath = path.resolve(__dirname, '..', 'front_end', 'core', 'host', 'UserMetrics.ts');
|
237 | const userMetricsFile = fs.readFileSync(userMetricsPath, 'utf-8');
|
238 |
|
239 | const runtimePath = path.resolve(__dirname, '..', 'front_end', 'core', 'root', 'Runtime.ts');
|
240 | const runtimeFile = fs.readFileSync(runtimePath, 'utf-8');
|
241 | const experimentNames = getExperimentNameEnum(runtimeFile);
|
242 |
|
243 | compareExperimentLists(
|
244 | getMainImplExperimentList(mainImplFile, experimentNames), getUserMetricExperimentList(userMetricsFile));
|
245 | }
|
246 |
|
247 | main();
|