1 | const MS_IN_MINUTE = 60000;
|
2 | const MS_IN_SECOND = 1000;
|
3 |
|
4 | const chalk = require("chalk");
|
5 | const { fg, bg } = require("./colours");
|
6 | const { groupBy, getAverages, getTotalActiveTime } = require("./utils");
|
7 |
|
8 | const humanTime = (ms, options = {}) => {
|
9 | if (options.verbose) {
|
10 | return ms.toLocaleString() + " ms";
|
11 | }
|
12 |
|
13 | const minutes = Math.floor(ms / MS_IN_MINUTE);
|
14 | const secondsRaw = (ms - minutes * MS_IN_MINUTE) / MS_IN_SECOND;
|
15 | const secondsWhole = Math.floor(secondsRaw);
|
16 | const remainderPrecision = secondsWhole > 0 ? 2 : 3;
|
17 | const secondsRemainder = Math.min(secondsRaw - secondsWhole, 0.99);
|
18 | const seconds =
|
19 | secondsWhole +
|
20 | secondsRemainder
|
21 | .toPrecision(remainderPrecision)
|
22 | .replace(/^0/, "")
|
23 | .replace(/0+$/, "")
|
24 | .replace(/^\.$/, "");
|
25 |
|
26 | let time = "";
|
27 |
|
28 | if (minutes > 0) time += minutes + " min" + (minutes > 1 ? "s" : "") + ", ";
|
29 | time += seconds + " secs";
|
30 |
|
31 | return time;
|
32 | };
|
33 |
|
34 | const smpTag = () => bg(" SMP ") + " ⏱ ";
|
35 | module.exports.smpTag = smpTag;
|
36 |
|
37 | module.exports.getHumanOutput = (outputObj, options = {}) => {
|
38 | const hT = (x) => humanTime(x, options);
|
39 | let output = "\n\n" + smpTag() + "\n";
|
40 |
|
41 | if (outputObj.misc) {
|
42 | output +=
|
43 | "General output time took " +
|
44 | fg(hT(outputObj.misc.compileTime, options), outputObj.misc.compileTime);
|
45 | output += "\n\n";
|
46 | }
|
47 | if (outputObj.plugins) {
|
48 | output += smpTag() + "Plugins\n";
|
49 | Object.keys(outputObj.plugins)
|
50 | .sort(
|
51 | (name1, name2) => outputObj.plugins[name2] - outputObj.plugins[name1]
|
52 | )
|
53 | .forEach((pluginName) => {
|
54 | output +=
|
55 | chalk.bold(pluginName) +
|
56 | " took " +
|
57 | fg(hT(outputObj.plugins[pluginName]), outputObj.plugins[pluginName]);
|
58 | output += "\n";
|
59 | });
|
60 | output += "\n";
|
61 | }
|
62 | if (outputObj.loaders) {
|
63 | output += smpTag() + "Loaders\n";
|
64 | outputObj.loaders.build
|
65 | .sort((obj1, obj2) => obj2.activeTime - obj1.activeTime)
|
66 | .forEach((loaderObj) => {
|
67 | output +=
|
68 | loaderObj.loaders.map(fg).join(", and \n") +
|
69 | " took " +
|
70 | fg(hT(loaderObj.activeTime), loaderObj.activeTime) +
|
71 | "\n";
|
72 |
|
73 | let xEqualsY = [];
|
74 | if (options.verbose) {
|
75 | xEqualsY.push(["median", hT(loaderObj.averages.median)]);
|
76 | xEqualsY.push(["mean", hT(loaderObj.averages.mean)]);
|
77 | if (typeof loaderObj.averages.variance === "number")
|
78 | xEqualsY.push(["s.d.", hT(Math.sqrt(loaderObj.averages.variance))]);
|
79 | xEqualsY.push([
|
80 | "range",
|
81 | "(" +
|
82 | hT(loaderObj.averages.range.start) +
|
83 | " --> " +
|
84 | hT(loaderObj.averages.range.end) +
|
85 | ")",
|
86 | ]);
|
87 | }
|
88 |
|
89 | if (loaderObj.loaders.length > 1) {
|
90 | Object.keys(loaderObj.subLoadersTime).forEach((subLoader) => {
|
91 | xEqualsY.push([subLoader, hT(loaderObj.subLoadersTime[subLoader])]);
|
92 | });
|
93 | }
|
94 |
|
95 | xEqualsY.push(["module count", loaderObj.averages.dataPoints]);
|
96 |
|
97 | if (options.loaderTopFiles) {
|
98 | const loopLen = Math.min(
|
99 | loaderObj.rawStartEnds.length,
|
100 | options.loaderTopFiles
|
101 | );
|
102 | for (let i = 0; i < loopLen; i++) {
|
103 | const rawItem = loaderObj.rawStartEnds[i];
|
104 | xEqualsY.push([rawItem.name, hT(rawItem.end - rawItem.start)]);
|
105 | }
|
106 | }
|
107 |
|
108 | const maxXLength = xEqualsY.reduce(
|
109 | (acc, cur) => Math.max(acc, cur[0].length),
|
110 | 0
|
111 | );
|
112 | xEqualsY.forEach((xY) => {
|
113 | const padEnd = maxXLength - xY[0].length;
|
114 | output += " " + xY[0] + " ".repeat(padEnd) + " = " + xY[1] + "\n";
|
115 | });
|
116 | });
|
117 | }
|
118 |
|
119 | output += "\n\n";
|
120 |
|
121 | return output;
|
122 | };
|
123 |
|
124 | module.exports.getMiscOutput = (data) => ({
|
125 | compileTime: data.compile[0].end - data.compile[0].start,
|
126 | });
|
127 |
|
128 | module.exports.getPluginsOutput = (data) =>
|
129 | Object.keys(data).reduce((acc, key) => {
|
130 | const inData = data[key];
|
131 |
|
132 | const startEndsByName = groupBy("name", inData);
|
133 |
|
134 | return startEndsByName.reduce((innerAcc, startEnds) => {
|
135 | innerAcc[startEnds[0].name] =
|
136 | (innerAcc[startEnds[0].name] || 0) + getTotalActiveTime(startEnds);
|
137 | return innerAcc;
|
138 | }, acc);
|
139 | }, {});
|
140 |
|
141 | module.exports.getLoadersOutput = (data) => {
|
142 | const startEndsByLoader = groupBy("loaders", data.build);
|
143 | const allSubLoaders = data["build-specific"] || [];
|
144 |
|
145 | const buildData = startEndsByLoader.map((startEnds) => {
|
146 | const averages = getAverages(startEnds);
|
147 | const activeTime = getTotalActiveTime(startEnds);
|
148 | const subLoaders = groupBy(
|
149 | "loader",
|
150 | allSubLoaders.filter((l) => startEnds.find((x) => x.name === l.name))
|
151 | );
|
152 | const subLoadersActiveTime = subLoaders.reduce((acc, loaders) => {
|
153 | acc[loaders[0].loader] = getTotalActiveTime(loaders);
|
154 | return acc;
|
155 | }, {});
|
156 |
|
157 | return {
|
158 | averages,
|
159 | activeTime,
|
160 | loaders: startEnds[0].loaders,
|
161 | subLoadersTime: subLoadersActiveTime,
|
162 | rawStartEnds: startEnds.sort(
|
163 | (a, b) => b.end - b.start - (a.end - a.start)
|
164 | ),
|
165 | };
|
166 | });
|
167 |
|
168 | return { build: buildData };
|
169 | };
|