main
js 96 lines 3.2 KB
Raw
1 #!/usr/bin/env node
2
3 'use strict';
4
5 // Combines the per-worker shard timings of the current run
6 // (build/__shard_timings__/*.json, written by scripts/rollup/build.js) into
7 // build-weights.json, which the workflow then saves back to the actions
8 // cache. The previous weights (restored to build-weights.json by the
9 // workflow) are only used to log a diff for variance monitoring; the new
10 // measurement is always written verbatim so that removed bundles drop out
11 // instead of accumulating. If the logged variance turns out to be too high
12 // for stable shards, weights should be aggregated across the last N runs
13 // instead. Weights feed scripts/rollup/sharding.js. This script never fails:
14 // the weights are an optimization, so a broken update must not break
15 // artifact processing.
16
17 const fs = require('fs');
18
19 const TIMINGS_DIR = 'build/__shard_timings__';
20 // The workflow restores the previous weights to OUT_PATH itself, so this
21 // script reads them from there before overwriting with the new measurement.
22 const OUT_PATH = 'build-weights.json';
23
24 function logDiff(fresh, previous) {
25 const deltas = [];
26 Object.keys(fresh).forEach(key => {
27 if (previous[key] !== undefined) {
28 deltas.push({key, delta: fresh[key] - previous[key]});
29 }
30 });
31 if (deltas.length === 0) {
32 return;
33 }
34 deltas.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
35 const absDeltas = deltas
36 .map(entry => Math.abs(entry.delta))
37 .sort((a, b) => a - b);
38 const mean =
39 absDeltas.reduce((sum, delta) => sum + delta, 0) / absDeltas.length;
40 const p95 = absDeltas[Math.floor(absDeltas.length * 0.95)];
41 console.log(
42 `Weight changes vs the previous run: mean |delta| = ${mean.toFixed(2)}s, ` +
43 `p95 = ${p95.toFixed(1)}s across ${deltas.length} bundles. ` +
44 'High variance here means shards should be determined from multiple runs.'
45 );
46 console.log('Largest changes:');
47 deltas.slice(0, 10).forEach(entry => {
48 console.log(
49 ` ${entry.delta >= 0 ? '+' : ''}${entry.delta.toFixed(1)}s ${entry.key}`
50 );
51 });
52 }
53
54 function main() {
55 const fresh = {};
56 const files = fs
57 .readdirSync(TIMINGS_DIR)
58 .filter(name => name.endsWith('.json'));
59 files.forEach(name => {
60 const timings = JSON.parse(
61 fs.readFileSync(TIMINGS_DIR + '/' + name, 'utf8')
62 );
63 Object.keys(timings).forEach(key => {
64 fresh[key] = timings[key];
65 });
66 });
67 const freshKeys = Object.keys(fresh);
68 let previous = {};
69 try {
70 previous = JSON.parse(fs.readFileSync(OUT_PATH, 'utf8')).weights;
71 } catch (error) {
72 if (error.code !== 'ENOENT') {
73 throw error;
74 }
75 // Expected before the first weights have ever been saved.
76 console.log('No previous weights found, skipping the diff.');
77 }
78 logDiff(fresh, previous);
79 fs.writeFileSync(
80 OUT_PATH,
81 JSON.stringify({version: 1, weights: fresh}, null, 2) + '\n'
82 );
83 console.log(`Wrote ${freshKeys.length} weights to ${OUT_PATH}.`);
84 }
85
86 try {
87 main();
88 } catch (error) {
89 console.log(
90 'Could not update build shard weights, keeping the previous ones.',
91 error
92 );
93 // The restored previous weights may still sit at OUT_PATH; delete them so
94 // the save step cannot republish data this run did not produce.
95 fs.rmSync(OUT_PATH, {force: true});
96 }