UNPKG

11.4 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
7 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
8 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
9 * Google as part of the polymer project is also subject to an additional IP
10 * rights grant found at http://polymer.github.io/PATENTS.txt
11 */
12var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13 if (k2 === undefined) k2 = k;
14 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
15}) : (function(o, m, k, k2) {
16 if (k2 === undefined) k2 = k;
17 o[k2] = m[k];
18}));
19var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20 Object.defineProperty(o, "default", { enumerable: true, value: v });
21}) : function(o, v) {
22 o["default"] = v;
23});
24var __importStar = (this && this.__importStar) || function (mod) {
25 if (mod && mod.__esModule) return mod;
26 var result = {};
27 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28 __setModuleDefault(result, mod);
29 return result;
30};
31Object.defineProperty(exports, "__esModule", { value: true });
32exports.benchmarkOneLiner = exports.horizontalHtmlResultTable = exports.verticalHtmlResultTable = exports.horizontalTermResultTable = exports.verticalTermResultTable = exports.automaticResultTable = exports.spinner = void 0;
33const stripAnsi = require("strip-ansi");
34const table = __importStar(require("table"));
35const ua_parser_js_1 = require("ua-parser-js");
36const ansi = require("ansi-escape-sequences");
37exports.spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].map((frame) => ansi.format(`[blue]{${frame}}`));
38/**
39 * Create an automatic mode result table.
40 */
41function automaticResultTable(results) {
42 // Typically most dimensions for a set of results share the same value (e.g
43 // because we're only running one benchmark, one browser, etc.). To save
44 // horizontal space and make the results easier to read, we first show the
45 // fixed values in one table, then the unfixed values in another.
46 const fixed = [];
47 const unfixed = [];
48 const possiblyFixed = [
49 benchmarkDimension,
50 versionDimension,
51 browserDimension,
52 sampleSizeDimension,
53 bytesSentDimension,
54 ];
55 for (const dimension of possiblyFixed) {
56 const values = new Set();
57 for (const res of results) {
58 values.add(dimension.format(res));
59 }
60 if (values.size === 1) {
61 fixed.push(dimension);
62 }
63 else {
64 unfixed.push(dimension);
65 }
66 }
67 // These are the primary observed results, so they always go in the main
68 // result table, even if they happen to be the same in one run.
69 unfixed.push(runtimeConfidenceIntervalDimension);
70 if (results.length > 1) {
71 // Create an NxN matrix comparing every result to every other result.
72 const labelFn = makeUniqueLabelFn(results.map((result) => result.result));
73 for (let i = 0; i < results.length; i++) {
74 unfixed.push({
75 label: `vs ${labelFn(results[i].result)}`,
76 tableConfig: {
77 alignment: 'right',
78 },
79 format: (r) => {
80 if (r.differences === undefined) {
81 return '';
82 }
83 const diff = r.differences[i];
84 if (diff === null) {
85 return ansi.format('\n[gray]{-} ');
86 }
87 return formatDifference(diff);
88 },
89 });
90 }
91 }
92 const fixedTable = { dimensions: fixed, results: [results[0]] };
93 const unfixedTable = { dimensions: unfixed, results };
94 return { fixed: fixedTable, unfixed: unfixedTable };
95}
96exports.automaticResultTable = automaticResultTable;
97/**
98 * Format a terminal text result table where each result is a row:
99 *
100 * +--------+--------+
101 * | Header | Header |
102 * +--------+--------+
103 * | Value | Value |
104 * +--------+--------+
105 * | Value | Value |
106 * +--------+--------+
107 */
108function verticalTermResultTable({ dimensions, results }) {
109 const columns = dimensions.map((d) => d.tableConfig || {});
110 const rows = [
111 dimensions.map((d) => ansi.format(`[bold]{${d.label}}`)),
112 ...results.map((r) => dimensions.map((d) => d.format(r))),
113 ];
114 return table.table(rows, {
115 border: table.getBorderCharacters('norc'),
116 columns,
117 });
118}
119exports.verticalTermResultTable = verticalTermResultTable;
120/**
121 * Format a terminal text result table where each result is a column:
122 *
123 * +--------+-------+-------+
124 * | Header | Value | Value |
125 * +--------+-------+-------+
126 * | Header | Value | Value |
127 * +--------+-------+-------+
128 */
129function horizontalTermResultTable({ dimensions, results }) {
130 const columns = [
131 { alignment: 'right' },
132 ...results.map(() => ({ alignment: 'left' })),
133 ];
134 const rows = dimensions.map((d) => {
135 return [
136 ansi.format(`[bold]{${d.label}}`),
137 ...results.map((r) => d.format(r)),
138 ];
139 });
140 return table.table(rows, {
141 border: table.getBorderCharacters('norc'),
142 columns,
143 });
144}
145exports.horizontalTermResultTable = horizontalTermResultTable;
146/**
147 * Format an HTML result table where each result is a row:
148 *
149 * <table>
150 * <tr> <th>Header</th> <th>Header</th> </tr>
151 * <tr> <td>Value</td> <td>Value</td> </tr>
152 * <tr> <td>Value</td> <td>Value</td> </tr>
153 * </table>
154 */
155function verticalHtmlResultTable({ dimensions, results }) {
156 const headers = dimensions.map((d) => `<th>${d.label}</th>`);
157 const rows = [];
158 for (const r of results) {
159 const cells = dimensions.map((d) => `<td>${ansiCellToHtml(d.format(r))}</td>`);
160 rows.push(`<tr>${cells.join('')}</tr>`);
161 }
162 return `<table>
163 <tr>${headers.join('')}</tr>
164 ${rows.join('')}
165 </table>`;
166}
167exports.verticalHtmlResultTable = verticalHtmlResultTable;
168/**
169 * Format an HTML result table where each result is a column:
170 *
171 * <table>
172 * <tr> <th>Header</th> <td>Value</td> <td>Value</td> </tr>
173 * <tr> <th>Header</th> <td>Value</td> <td>Value</td> </tr>
174 * </table>
175 */
176function horizontalHtmlResultTable({ dimensions, results }) {
177 const rows = [];
178 for (const d of dimensions) {
179 const cells = [
180 `<th align="right">${d.label}</th>`,
181 ...results.map((r) => `<td>${ansiCellToHtml(d.format(r))}</td>`),
182 ];
183 rows.push(`<tr>${cells.join('')}</tr>`);
184 }
185 return `<table>${rows.join('')}</table>`;
186}
187exports.horizontalHtmlResultTable = horizontalHtmlResultTable;
188function ansiCellToHtml(ansi) {
189 // For now, just remove ANSI color sequences and prevent line-breaks. We may
190 // want to add an htmlFormat method to each dimension object so that we can
191 // have more advanced control per dimension.
192 return stripAnsi(ansi).replace(/ /g, '&nbsp;');
193}
194/**
195 * Format a confidence interval as "[low, high]".
196 */
197const formatConfidenceInterval = (ci, format) => {
198 return ansi.format(`${format(ci.low)} [gray]{-} ${format(ci.high)}`);
199};
200/**
201 * Prefix positive numbers with a red "+" and negative ones with a green "-".
202 */
203const colorizeSign = (n, format) => {
204 if (n > 0) {
205 return ansi.format(`[red bold]{+}${format(n)}`);
206 }
207 else if (n < 0) {
208 // Negate the value so that we don't get a double negative sign.
209 return ansi.format(`[green bold]{-}${format(-n)}`);
210 }
211 else {
212 return format(n);
213 }
214};
215const benchmarkDimension = {
216 label: 'Benchmark',
217 format: (r) => r.result.name,
218};
219const versionDimension = {
220 label: 'Version',
221 format: (r) => r.result.version || ansi.format('[gray]{<none>}'),
222};
223const browserDimension = {
224 label: 'Browser',
225 format: (r) => {
226 const browser = r.result.browser;
227 let s = browser.name;
228 if (browser.headless) {
229 s += '-headless';
230 }
231 if (browser.remoteUrl) {
232 s += `\n@${browser.remoteUrl}`;
233 }
234 if (r.result.userAgent !== '') {
235 // We'll only have a user agent when using the built-in static server.
236 // TODO Get UA from window.navigator.userAgent so we always have it.
237 const ua = new ua_parser_js_1.UAParser(r.result.userAgent).getBrowser();
238 s += `\n${ua.version}`;
239 }
240 return s;
241 },
242};
243const sampleSizeDimension = {
244 label: 'Sample size',
245 format: (r) => r.result.millis.length.toString(),
246};
247const bytesSentDimension = {
248 label: 'Bytes',
249 format: (r) => (r.result.bytesSent / 1024).toFixed(2) + ' KiB',
250};
251const runtimeConfidenceIntervalDimension = {
252 label: 'Avg time',
253 tableConfig: {
254 alignment: 'right',
255 },
256 format: (r) => formatConfidenceInterval(r.stats.meanCI, milli),
257};
258function formatDifference({ absolute, relative }) {
259 let word, rel, abs;
260 if (absolute.low > 0 && relative.low > 0) {
261 word = `[bold red]{slower}`;
262 rel = formatConfidenceInterval(relative, percent);
263 abs = formatConfidenceInterval(absolute, milli);
264 }
265 else if (absolute.high < 0 && relative.high < 0) {
266 word = `[bold green]{faster}`;
267 rel = formatConfidenceInterval(negate(relative), percent);
268 abs = formatConfidenceInterval(negate(absolute), milli);
269 }
270 else {
271 word = `[bold blue]{unsure}`;
272 rel = formatConfidenceInterval(relative, (n) => colorizeSign(n, percent));
273 abs = formatConfidenceInterval(absolute, (n) => colorizeSign(n, milli));
274 }
275 return ansi.format(`${word}\n${rel}\n${abs}`);
276}
277function percent(n) {
278 return (n * 100).toFixed(0) + '%';
279}
280function milli(n) {
281 return n.toFixed(2) + 'ms';
282}
283function negate(ci) {
284 return {
285 low: -ci.high,
286 high: -ci.low,
287 };
288}
289/**
290 * Create a function that will return the shortest unambiguous label for a
291 * result, given the full array of results.
292 */
293function makeUniqueLabelFn(results) {
294 const names = new Set();
295 const versions = new Set();
296 const browsers = new Set();
297 for (const result of results) {
298 names.add(result.name);
299 versions.add(result.version);
300 browsers.add(result.browser.name);
301 }
302 return (result) => {
303 const fields = [];
304 if (names.size > 1) {
305 fields.push(result.name);
306 }
307 if (versions.size > 1) {
308 fields.push(result.version);
309 }
310 if (browsers.size > 1) {
311 fields.push(result.browser.name);
312 }
313 return fields.join('\n');
314 };
315}
316/**
317 * A one-line summary of a benchmark, e.g. for a progress bar:
318 *
319 * chrome my-benchmark [@my-version]
320 */
321function benchmarkOneLiner(spec) {
322 let maybeVersion = '';
323 if (spec.url.kind === 'local' && spec.url.version !== undefined) {
324 maybeVersion = ` [@${spec.url.version.label}]`;
325 }
326 return `${spec.browser.name} ${spec.name}${maybeVersion}`;
327}
328exports.benchmarkOneLiner = benchmarkOneLiner;
329//# sourceMappingURL=format.js.map
\No newline at end of file