@tramvai/cli 6.68.0 → 6.68.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/lib/api/benchmark/build.d.ts +1 -6
  2. package/lib/api/benchmark/build.d.ts.map +1 -1
  3. package/lib/api/benchmark/build.js +59 -23
  4. package/lib/api/benchmark/build.js.map +1 -1
  5. package/lib/api/benchmark/const.d.ts +2 -0
  6. package/lib/api/benchmark/const.d.ts.map +1 -0
  7. package/lib/api/benchmark/const.js +5 -0
  8. package/lib/api/benchmark/const.js.map +1 -0
  9. package/lib/api/benchmark/index.d.ts +4 -6
  10. package/lib/api/benchmark/index.d.ts.map +1 -1
  11. package/lib/api/benchmark/index.js.map +1 -1
  12. package/lib/api/benchmark/start.d.ts +1 -7
  13. package/lib/api/benchmark/start.d.ts.map +1 -1
  14. package/lib/api/benchmark/start.js +56 -49
  15. package/lib/api/benchmark/start.js.map +1 -1
  16. package/lib/api/benchmark/types.d.ts +76 -12
  17. package/lib/api/benchmark/types.d.ts.map +1 -1
  18. package/lib/api/benchmark/utils/compilationUtils.d.ts +4 -0
  19. package/lib/api/benchmark/utils/compilationUtils.d.ts.map +1 -0
  20. package/lib/api/benchmark/utils/compilationUtils.js +157 -0
  21. package/lib/api/benchmark/utils/compilationUtils.js.map +1 -0
  22. package/lib/api/benchmark/utils/stats.d.ts +2 -7
  23. package/lib/api/benchmark/utils/stats.d.ts.map +1 -1
  24. package/lib/api/benchmark/utils/stats.js +36 -22
  25. package/lib/api/benchmark/utils/stats.js.map +1 -1
  26. package/lib/api/build/index.d.ts +1 -0
  27. package/lib/api/build/index.d.ts.map +1 -1
  28. package/lib/api/build/index.js.map +1 -1
  29. package/lib/api/start/application.experimental.d.ts.map +1 -1
  30. package/lib/api/start/application.experimental.js +4 -3
  31. package/lib/api/start/application.experimental.js.map +1 -1
  32. package/lib/api/start/index.d.ts +1 -0
  33. package/lib/api/start/index.d.ts.map +1 -1
  34. package/lib/api/start/index.js.map +1 -1
  35. package/lib/builder/webpack/analyzePlugins/rsdoctor.d.ts +3 -3
  36. package/lib/builder/webpack/analyzePlugins/rsdoctor.d.ts.map +1 -1
  37. package/lib/builder/webpack/analyzePlugins/rsdoctor.js +1 -11
  38. package/lib/builder/webpack/analyzePlugins/rsdoctor.js.map +1 -1
  39. package/lib/builder/webpack/providers/shared.d.ts.map +1 -1
  40. package/lib/builder/webpack/providers/shared.js +10 -7
  41. package/lib/builder/webpack/providers/shared.js.map +1 -1
  42. package/lib/commands/benchmark/benchmark.d.ts +3 -1
  43. package/lib/commands/benchmark/benchmark.d.ts.map +1 -1
  44. package/lib/commands/benchmark/benchmark.js +130 -31
  45. package/lib/commands/benchmark/benchmark.js.map +1 -1
  46. package/lib/commands/benchmark/command.d.ts +9 -0
  47. package/lib/commands/benchmark/command.d.ts.map +1 -1
  48. package/lib/commands/benchmark/command.js +18 -0
  49. package/lib/commands/benchmark/command.js.map +1 -1
  50. package/lib/config/configManager.d.ts +1 -0
  51. package/lib/config/configManager.d.ts.map +1 -1
  52. package/lib/config/configManager.js +1 -0
  53. package/lib/config/configManager.js.map +1 -1
  54. package/lib/library/webpack/common/main.d.ts.map +1 -1
  55. package/lib/library/webpack/common/main.js +7 -0
  56. package/lib/library/webpack/common/main.js.map +1 -1
  57. package/lib/library/webpack/utils/threadLoader.d.ts.map +1 -1
  58. package/lib/library/webpack/utils/threadLoader.js +2 -1
  59. package/lib/library/webpack/utils/threadLoader.js.map +1 -1
  60. package/lib/typings/build/Builder.d.ts +3 -2
  61. package/lib/typings/build/Builder.d.ts.map +1 -1
  62. package/package.json +9 -10
  63. package/src/api/benchmark/__integration__/start.test.ts +10 -12
  64. package/src/api/benchmark/build.ts +75 -30
  65. package/src/api/benchmark/const.ts +1 -0
  66. package/src/api/benchmark/index.ts +4 -6
  67. package/src/api/benchmark/start.ts +69 -65
  68. package/src/api/benchmark/types.ts +82 -14
  69. package/src/api/benchmark/utils/compilationUtils.ts +213 -0
  70. package/src/api/benchmark/utils/stats.ts +45 -28
  71. package/src/api/build/index.ts +1 -0
  72. package/src/api/start/application.experimental.ts +4 -3
  73. package/src/api/start/index.ts +1 -0
  74. package/src/builder/webpack/analyzePlugins/rsdoctor.ts +2 -14
  75. package/src/builder/webpack/providers/shared.ts +7 -4
  76. package/src/commands/benchmark/benchmark.ts +168 -33
  77. package/src/commands/benchmark/command.ts +19 -0
  78. package/src/config/configManager.ts +2 -0
  79. package/src/library/webpack/common/main.ts +8 -0
  80. package/src/library/webpack/utils/threadLoader.ts +1 -0
  81. package/src/typings/build/Builder.ts +4 -2
  82. package/lib/builder/webpack/utils/calculateBuildTime.d.ts +0 -3
  83. package/lib/builder/webpack/utils/calculateBuildTime.d.ts.map +0 -1
  84. package/lib/builder/webpack/utils/calculateBuildTime.js +0 -18
  85. package/lib/builder/webpack/utils/calculateBuildTime.js.map +0 -1
  86. package/lib/builder/webpack/utils/maxMemoryRss.d.ts +0 -2
  87. package/lib/builder/webpack/utils/maxMemoryRss.d.ts.map +0 -1
  88. package/lib/builder/webpack/utils/maxMemoryRss.js +0 -15
  89. package/lib/builder/webpack/utils/maxMemoryRss.js.map +0 -1
  90. package/src/api/benchmark/utils/stats.spec.ts +0 -36
  91. package/src/builder/webpack/utils/calculateBuildTime.ts +0 -17
  92. package/src/builder/webpack/utils/maxMemoryRss.ts +0 -12
@@ -1,24 +1,19 @@
1
+ /* eslint-disable max-statements */
1
2
  import type { Container } from '@tinkoff/dippy';
2
3
  import type { Params as OriginalStartParams, Result as OriginalStartResult } from '../start/index';
3
4
  import { COMMAND_PARAMETERS_TOKEN, COMMAND_RUNNER_TOKEN } from '../../di/tokens';
4
5
  import type { Params, Result } from './index';
5
- import type { Samples, RunStats } from './types';
6
+ import type { Samples, CompilationStats } from './types';
6
7
  import { clearCacheDirectory } from './utils/clearCache';
7
8
  import { getResultStats } from './utils/stats';
8
-
9
- const REBUILD_WARMUP_TIMES = 3;
9
+ import { getClientCompilationTimings, getServerCompilationTimings } from './utils/compilationUtils';
10
+ import { DEFAULT_TIMES } from './const';
10
11
 
11
12
  export interface StartParams extends Params {
12
13
  command: 'start';
13
14
  commandOptions: OriginalStartParams;
14
15
  }
15
16
 
16
- export interface StartResult extends Result {
17
- noCache?: RunStats;
18
- cache?: RunStats;
19
- rebuild?: RunStats;
20
- }
21
-
22
17
  const runStartCommand = async (
23
18
  di: Container,
24
19
  {
@@ -29,17 +24,29 @@ const runStartCommand = async (
29
24
  shouldClearCache: boolean;
30
25
  }
31
26
  ): Promise<Samples> => {
32
- const clientSamples: number[] = Array(times);
33
- const serverSamples: number[] = Array(times);
34
- const maxMemoryRssSamples: number[] = Array(times);
27
+ const clientBuildTimeSamples: number[] = [];
28
+ const serverBuildTimeSamples: number[] = [];
29
+ const clientMaxMemoryRssSamples: number[] = [];
30
+ const serverMaxMemoryRssSamples: number[] = [];
31
+
32
+ const serverCompilationTimings: CompilationStats[] = [];
33
+ const clientCompilationTimings: CompilationStats[] = [];
34
+
35
+ const maxMemoryRssSamples: number[] = [];
35
36
 
36
37
  const { commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as StartParams;
38
+ commandOptions.benchmark = true;
39
+
40
+ const buildType = commandOptions.buildType ?? 'all';
41
+
42
+ const benchmarkStartTime = new Date().toISOString();
37
43
 
38
44
  for (let i = 0; i < times; i++) {
39
45
  if (shouldClearCache) {
40
46
  await clearCacheDirectory(di);
41
47
  }
42
48
 
49
+ const attemptStartTime = Date.now();
43
50
  const { close, getBuildStats } = await (di
44
51
  .get(COMMAND_RUNNER_TOKEN)
45
52
  .run('start', commandOptions) as OriginalStartResult);
@@ -47,70 +54,67 @@ const runStartCommand = async (
47
54
 
48
55
  await close();
49
56
 
50
- clientSamples[i] = stats.clientBuildTime;
51
- serverSamples[i] = stats.serverBuildTime;
52
- maxMemoryRssSamples[i] = stats.maxMemoryRss;
53
- }
54
-
55
- return {
56
- clientSamples,
57
- serverSamples,
58
- maxMemoryRssSamples,
59
- };
60
- };
61
-
62
- const runRebuild = async (di: Container, { times }: { times: number }): Promise<Samples> => {
63
- const clientSamples: number[] = Array(times);
64
- const serverSamples: number[] = Array(times);
65
- const maxMemoryRssSamples: number[] = Array(times);
66
- const { commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as StartParams;
57
+ // at first attempt do not save metrics if cache is used
58
+ if (!shouldClearCache && i === 0) {
59
+ continue;
60
+ }
67
61
 
68
- const { close, invalidate, getBuildStats } = await (di
69
- .get(COMMAND_RUNNER_TOKEN)
70
- .run('start', commandOptions) as OriginalStartResult);
62
+ if (buildType === 'all' || buildType === 'server') {
63
+ serverCompilationTimings.push(
64
+ await getServerCompilationTimings(benchmarkStartTime, attemptStartTime, i)
65
+ );
66
+ serverBuildTimeSamples.push(stats.server.buildTime);
71
67
 
72
- // warmup rebuild as it usually pretty slow at first runs
73
- for (let i = 0; i < REBUILD_WARMUP_TIMES; i++) {
74
- await invalidate();
75
- }
68
+ if (stats.server.maxMemoryRss) {
69
+ serverMaxMemoryRssSamples.push(stats.server.maxMemoryRss);
70
+ }
71
+ }
76
72
 
77
- for (let i = 0; i < times; i++) {
78
- await invalidate();
73
+ if (buildType === 'all' || buildType === 'client') {
74
+ clientCompilationTimings.push(
75
+ await getClientCompilationTimings(benchmarkStartTime, attemptStartTime, i)
76
+ );
77
+ clientBuildTimeSamples.push(stats.client.buildTime);
79
78
 
80
- const stats = getBuildStats();
79
+ if (stats.client.maxMemoryRss) {
80
+ clientMaxMemoryRssSamples.push(stats.client.maxMemoryRss);
81
+ }
82
+ }
81
83
 
82
- clientSamples[i] = stats.clientBuildTime;
83
- serverSamples[i] = stats.serverBuildTime;
84
- maxMemoryRssSamples[i] = stats.maxMemoryRss;
84
+ if (stats.maxMemoryRss) {
85
+ maxMemoryRssSamples.push(stats.maxMemoryRss);
86
+ }
85
87
  }
86
88
 
87
- await close();
88
-
89
89
  return {
90
- clientSamples,
91
- serverSamples,
90
+ serverCompilationTimings,
91
+ clientCompilationTimings,
92
+ clientMaxMemoryRssSamples,
93
+ serverMaxMemoryRssSamples,
94
+ clientBuildTimeSamples,
95
+ serverBuildTimeSamples,
92
96
  maxMemoryRssSamples,
93
97
  };
94
98
  };
95
99
 
96
- export const benchmarkStart = async (di: Container): Promise<StartResult> => {
97
- const { times = 5 } = di.get(COMMAND_PARAMETERS_TOKEN) as Params;
98
-
99
- const noCache = await runStartCommand(di, {
100
- times: Math.max(Math.floor(times / 3), 2),
101
- shouldClearCache: true,
102
- });
103
-
104
- const cache = await runStartCommand(di, {
105
- times: Math.max(Math.floor(times / 2), 2),
106
- shouldClearCache: false,
107
- });
108
-
109
- const rebuild = await runRebuild(di, { times });
100
+ export const benchmarkStart = async (di: Container): Promise<Result> => {
101
+ const { times = DEFAULT_TIMES, commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as Params;
102
+ const noCache = !commandOptions.fileCache;
103
+
104
+ let result;
105
+
106
+ if (noCache) {
107
+ result = await runStartCommand(di, {
108
+ times,
109
+ shouldClearCache: true,
110
+ });
111
+ } else {
112
+ result = await runStartCommand(di, {
113
+ // additional first attempt for cache warmup
114
+ times: times + 1,
115
+ shouldClearCache: false,
116
+ });
117
+ }
110
118
 
111
- return {
112
- cache: getResultStats(cache),
113
- noCache: getResultStats(noCache),
114
- rebuild: getResultStats(rebuild),
115
- };
119
+ return getResultStats(result);
116
120
  };
@@ -1,19 +1,87 @@
1
- export interface Stats {
2
- samples: number[];
3
- mean: number;
4
- std: number;
5
-
6
- variance: number;
7
- }
8
-
9
1
  export type Samples = {
10
- clientSamples: Stats['samples'];
11
- serverSamples: Stats['samples'];
12
- maxMemoryRssSamples: Stats['samples'];
2
+ serverCompilationTimings: CompilationStats[];
3
+ serverBuildTimeSamples: number[];
4
+ clientCompilationTimings: CompilationStats[];
5
+ clientBuildTimeSamples: number[];
6
+ maxMemoryRssSamples: number[];
7
+ clientMaxMemoryRssSamples: number[];
8
+ serverMaxMemoryRssSamples: number[];
9
+ };
10
+
11
+ export type CompilationStats = {
12
+ totalBuildCosts: Record<string, number>;
13
+ loaderBuildCosts: Record<string, number>;
14
+ pluginBuildCosts: Record<string, number>;
13
15
  };
14
16
 
15
17
  export type RunStats = {
16
- client: Stats;
17
- server: Stats;
18
- maxMemoryRss: Stats;
18
+ serverCompilationStats: CompilationStats | undefined;
19
+ clientCompilationStats: CompilationStats | undefined;
20
+ clientBuildTime: number | undefined;
21
+ serverBuildTime: number | undefined;
22
+ clientMaxMemoryRss: number | undefined;
23
+ serverMaxMemoryRss: number | undefined;
24
+ maxMemoryRss: number | undefined;
25
+ };
26
+
27
+ type PlainObject<T = any> = {
28
+ [key: string]: T;
19
29
  };
30
+
31
+ interface ProcessData {
32
+ /**
33
+ * process id
34
+ */
35
+ pid: number;
36
+ /**
37
+ * parent process id
38
+ */
39
+ ppid: number | null;
40
+ }
41
+
42
+ export interface LoaderTransformData extends ProcessData {
43
+ /** loader name */
44
+ loader: string;
45
+ /**
46
+ * loader index
47
+ */
48
+ loaderIndex: number;
49
+ /** loader path */
50
+ path: string;
51
+ input: string | null | undefined;
52
+ /**
53
+ * - isPitch: true: the result of loader.pitch()
54
+ * - isPitch: false: the code result of loader()
55
+ */
56
+ result: string | null | undefined;
57
+ /** Timestamp when called */
58
+ startAt: number;
59
+ endAt: number;
60
+ /** loader configuration */
61
+ options: PlainObject;
62
+ /** pitching loader */
63
+ isPitch: boolean;
64
+ /**
65
+ * is sync
66
+ */
67
+ sync: boolean;
68
+ /** Error during conversion */
69
+ // errors: DevToolErrorInstance[];
70
+ /** module layer */
71
+ layer?: string;
72
+ }
73
+
74
+ export type MinimalLoaderData = Pick<LoaderTransformData, 'startAt' | 'endAt' | 'pid' | 'loader'>;
75
+
76
+ export interface PluginData {
77
+ /** hook tap name */
78
+ tapName: string;
79
+ /** hook call time-consuming */
80
+ costs: number;
81
+ startAt: number;
82
+ endAt: number;
83
+ /** hook function type */
84
+ type: 'sync' | 'async' | 'promise';
85
+ /** hook function result */
86
+ result: any;
87
+ }
@@ -0,0 +1,213 @@
1
+ import fs from 'node:fs/promises';
2
+
3
+ import { CompilationStats, LoaderTransformData, MinimalLoaderData, PluginData } from '../types';
4
+
5
+ export async function getServerCompilationTimings(
6
+ benchmarkStartTime: string,
7
+ attemptStartTime: number,
8
+ attempt: number
9
+ ) {
10
+ return getCompilationTimings(benchmarkStartTime, attemptStartTime, attempt, 'server');
11
+ }
12
+
13
+ export async function getClientCompilationTimings(
14
+ benchmarkStartTime: string,
15
+ attemptStartTime: number,
16
+ attempt: number
17
+ ) {
18
+ return getCompilationTimings(benchmarkStartTime, attemptStartTime, attempt, 'client');
19
+ }
20
+
21
+ async function getCompilationTimings(
22
+ benchmarkStartTime: string,
23
+ attemptStartTime: number,
24
+ attempt: number,
25
+ type: 'client' | 'server'
26
+ ): Promise<CompilationStats> {
27
+ try {
28
+ const rsdoctorData = await getReportData(
29
+ await processReport(benchmarkStartTime, attempt, type)
30
+ );
31
+
32
+ const { summary, loader, plugin } = rsdoctorData;
33
+
34
+ const totalBuildCosts = calculateTotalCosts(summary, attemptStartTime);
35
+ const loaderBuildCosts = calculateLoadersCosts(loader);
36
+ const pluginBuildCosts = calculatePluginsCosts(plugin);
37
+
38
+ return { totalBuildCosts, loaderBuildCosts, pluginBuildCosts };
39
+ } catch (err) {
40
+ throw new Error(`Failed to get rsdoctor report!\n${err}`);
41
+ }
42
+ }
43
+
44
+ async function getReportData(reportPath: string) {
45
+ const rsdoctorRawStats = await fs.readFile(reportPath, 'utf-8');
46
+ const rsdoctorStats = JSON.parse(rsdoctorRawStats);
47
+
48
+ return rsdoctorStats.data;
49
+ }
50
+
51
+ async function processReport(
52
+ benchmarkStartTime: string,
53
+ attempt: number,
54
+ type: 'client' | 'server'
55
+ ) {
56
+ const reportBasePath = `./.rsdoctor/${benchmarkStartTime}`;
57
+ const reportPath = `${reportBasePath}/${type}-rsdoctor-data-${attempt + 1}.json`;
58
+
59
+ await fs.mkdir(reportBasePath, { recursive: true });
60
+ await fs.rename(`./.rsdoctor/${type}-rsdoctor-data.json`, reportPath);
61
+
62
+ return reportPath;
63
+ }
64
+
65
+ function mergeIntervals(intervals: [number, number][]) {
66
+ // Sort from small to large
67
+ intervals.sort((a, b) => a[0] - b[0]);
68
+ // The previous interval, the next interval, store the result
69
+ let previous;
70
+ let current;
71
+ const result: [number, number][] = [];
72
+
73
+ for (let i = 0; i < intervals.length; i++) {
74
+ current = intervals[i];
75
+ // If the first interval or the current interval does not overlap with the previous interval, add the current interval to the result
76
+ if (!previous || current[0] > previous[1]) {
77
+ // Assign the current interval to the previous interval
78
+ previous = current;
79
+ result.push(current);
80
+ } else {
81
+ // Otherwise, the two intervals overlap
82
+ // Update the end time of the previous interval
83
+ previous[1] = Math.max(previous[1], current[1]);
84
+ }
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ function getLoadersCosts(
91
+ filter: (loader: MinimalLoaderData) => boolean,
92
+ loaders: MinimalLoaderData[]
93
+ ) {
94
+ const match: { [pid: number | string]: [start: number, end: number][] } = {};
95
+ const others: { [pid: number | string]: [start: number, end: number][] } = {};
96
+
97
+ loaders.forEach((e) => {
98
+ if (filter(e)) {
99
+ if (!match[e.pid]) match[e.pid] = [];
100
+ match[e.pid].push([e.startAt, e.endAt]);
101
+ } else {
102
+ if (!others[e.pid]) others[e.pid] = [];
103
+ others[e.pid].push([e.startAt, e.endAt]);
104
+ }
105
+ });
106
+
107
+ let costs = 0;
108
+
109
+ const pids = Object.keys(match);
110
+
111
+ for (let i = 0; i < pids.length; i++) {
112
+ const pid = pids[i];
113
+ const _match = mergeIntervals(match[pid]);
114
+ // between in loader.startAt and loader.endAt
115
+ const _others = mergeIntervals(others[pid] || []).filter(([s, e]) =>
116
+ _match.some((el) => s >= el[0] && e <= el[1])
117
+ );
118
+
119
+ // eslint-disable-next-line no-param-reassign
120
+ const matchSum = _match.length ? _match.reduce((t, c) => (t += c[1] - c[0]), 0) : 0;
121
+ // eslint-disable-next-line no-param-reassign
122
+ const othersSum = _others.length ? _others.reduce((t, c) => (t += c[1] - c[0]), 0) : 0;
123
+
124
+ costs += matchSum - othersSum;
125
+ }
126
+
127
+ return costs;
128
+ }
129
+
130
+ function calculateTotalCosts(
131
+ summary: { costs: { name: string; cost: string }[] },
132
+ attemptStartTime: number
133
+ ) {
134
+ const { costs: buildCosts } = summary;
135
+ const buildCostsMap = buildCosts.reduce((acc, cost) => {
136
+ acc[cost.name] = cost;
137
+ return acc;
138
+ }, {});
139
+
140
+ const result: Record<string, number> = {};
141
+
142
+ for (const buildCostName in buildCostsMap) {
143
+ if (buildCostName === 'bootstrap->beforeCompile') {
144
+ // start of bootstrap is start of first build, so use attemptStarTime instead
145
+ result[buildCostName] =
146
+ buildCostsMap['beforeCompile->afterCompile'].startAt - attemptStartTime;
147
+ } else {
148
+ result[buildCostName] = buildCostsMap[buildCostName].costs;
149
+ }
150
+ }
151
+
152
+ return result;
153
+ }
154
+
155
+ function calculateLoadersCosts(loaders: { loaders: LoaderTransformData[] }[]) {
156
+ const filteredLoaders: MinimalLoaderData[] = [];
157
+ const uniqueLoaders = new Map<
158
+ string,
159
+ {
160
+ files: number;
161
+ path: string;
162
+ }
163
+ >();
164
+
165
+ loaders.forEach((data) => {
166
+ data.loaders.forEach((fl) => {
167
+ const uniqueLoader = uniqueLoaders.get(fl.loader);
168
+ if (uniqueLoader) {
169
+ uniqueLoaders.set(fl.loader, {
170
+ files: uniqueLoader.files + 1,
171
+ path: fl.path,
172
+ });
173
+ } else {
174
+ uniqueLoaders.set(fl.loader, { files: 1, path: fl.path });
175
+ }
176
+
177
+ return filteredLoaders.push({
178
+ loader: fl.loader,
179
+ startAt: fl.startAt,
180
+ endAt: fl.endAt,
181
+ pid: fl.pid,
182
+ });
183
+ });
184
+ });
185
+
186
+ const costs: Record<string, number> = {};
187
+ uniqueLoaders.forEach((_, loaderName: string) => {
188
+ costs[loaderName] = getLoadersCosts((item) => item.loader === loaderName, filteredLoaders);
189
+ });
190
+
191
+ return costs;
192
+ }
193
+
194
+ function calculatePluginsCosts(plugins: PluginData) {
195
+ const pluginCosts: Record<string, number> = {};
196
+
197
+ for (const hookName in plugins) {
198
+ const hookCalls = plugins[hookName];
199
+
200
+ hookCalls.forEach((plugin) => {
201
+ const pluginName = plugin.tapName;
202
+ const pluginCost = plugin.costs;
203
+
204
+ if (pluginCosts[pluginName]) {
205
+ pluginCosts[pluginName] += pluginCost;
206
+ } else {
207
+ pluginCosts[pluginName] = pluginCost;
208
+ }
209
+ });
210
+ }
211
+
212
+ return pluginCosts;
213
+ }
@@ -1,45 +1,62 @@
1
- import type { RunStats, Stats } from '../types';
1
+ import type { RunStats, Samples, CompilationStats } from '../types';
2
2
 
3
- export const getSamplesStats = (samples: number[]): Stats => {
4
- const n = samples.length;
3
+ function getMeanValue(samples: number[]) {
4
+ if (samples.length === 0) {
5
+ return;
6
+ }
5
7
 
6
8
  let sum = 0;
7
9
 
8
- for (let i = 0; i < n; i++) {
10
+ for (let i = 0; i < samples.length; i++) {
9
11
  sum += samples[i];
10
12
  }
11
13
 
12
- const mean = sum / n;
14
+ return sum / samples.length;
15
+ }
13
16
 
14
- sum = 0;
17
+ function getCompilationMeanStats(allStats) {
18
+ const result = {} as CompilationStats;
15
19
 
16
- for (let i = 0; i < n; i++) {
17
- sum += (samples[i] - mean) * (samples[i] - mean);
18
- }
20
+ const stats: CompilationStats = allStats[0];
19
21
 
20
- const std = Math.sqrt(sum / n);
21
- const variance = (100 * std) / mean;
22
+ for (const section in stats) {
23
+ for (const metricName in stats[section]) {
24
+ const samples = allStats.reduce((acc, item) => {
25
+ acc.push(item[section][metricName]);
26
+ return acc;
27
+ }, []);
22
28
 
23
- return {
24
- samples,
25
- mean,
26
- std,
27
- variance,
28
- };
29
- };
29
+ if (!result[section]) {
30
+ result[section] = {};
31
+ }
32
+ const meanValue = getMeanValue(samples);
33
+
34
+ // ignore values less than 10ms
35
+ if (meanValue > 10) {
36
+ result[section][metricName] = meanValue;
37
+ }
38
+ }
39
+ }
40
+
41
+ return result;
42
+ }
30
43
 
31
44
  export const getResultStats = ({
32
- clientSamples,
33
- serverSamples,
45
+ clientBuildTimeSamples,
46
+ clientCompilationTimings,
47
+ clientMaxMemoryRssSamples,
48
+ serverBuildTimeSamples,
49
+ serverCompilationTimings,
50
+ serverMaxMemoryRssSamples,
34
51
  maxMemoryRssSamples,
35
- }: {
36
- clientSamples: number[];
37
- serverSamples: number[];
38
- maxMemoryRssSamples: number[];
39
- }): RunStats => {
52
+ }: Samples): RunStats => {
40
53
  return {
41
- client: getSamplesStats(clientSamples),
42
- server: getSamplesStats(serverSamples),
43
- maxMemoryRss: getSamplesStats(maxMemoryRssSamples),
54
+ serverBuildTime: getMeanValue(serverBuildTimeSamples),
55
+ serverCompilationStats: getCompilationMeanStats(serverCompilationTimings),
56
+ clientBuildTime: getMeanValue(clientBuildTimeSamples),
57
+ clientCompilationStats: getCompilationMeanStats(clientCompilationTimings),
58
+ maxMemoryRss: getMeanValue(maxMemoryRssSamples),
59
+ clientMaxMemoryRss: getMeanValue(clientMaxMemoryRssSamples),
60
+ serverMaxMemoryRss: getMeanValue(serverMaxMemoryRssSamples),
44
61
  };
45
62
  };
@@ -21,6 +21,7 @@ export type Params = WithConfig<{
21
21
  env?: Record<string, string>;
22
22
  fileCache?: boolean;
23
23
  withBuildStats?: boolean;
24
+ benchmark?: boolean;
24
25
  withModulesStats?: boolean;
25
26
 
26
27
  // `package` target parameters
@@ -24,6 +24,7 @@ export const startApplicationExperimental = async (di: Container): Result => {
24
24
  const inputParameters: StartParameters = {
25
25
  name: configEntry.name,
26
26
  mode: 'development',
27
+ benchmark: options.benchmark,
27
28
  buildType: options.buildType,
28
29
  port: options.port,
29
30
  host: options.host,
@@ -101,7 +102,7 @@ export const startApplicationExperimental = async (di: Container): Result => {
101
102
  await devServer.invalidate();
102
103
  },
103
104
  getBuildStats: () => {
104
- return {};
105
+ return devServer.getStats();
105
106
  },
106
107
  builder: {
107
108
  name: '@tramvai/plugin-webpack-builder',
@@ -114,7 +115,7 @@ export const startApplicationExperimental = async (di: Container): Result => {
114
115
  await devServer.invalidate();
115
116
  },
116
117
  getBuildStats: () => {
117
- return {};
118
+ return devServer.getStats();
118
119
  },
119
120
  };
120
121
  },
@@ -210,7 +211,7 @@ function mapTramvaiJsonToNewTsConfig({ rootDir }: { rootDir: string }) {
210
211
  }
211
212
  if (applicationProject.polyfill) {
212
213
  try {
213
- if (require.resolve(mappedProject.polyfill).includes('node_modules')) {
214
+ if (require.resolve(applicationProject.polyfill).includes('node_modules')) {
214
215
  mappedProject.polyfill = require.resolve(applicationProject.polyfill);
215
216
  } else {
216
217
  mappedProject.polyfill = path.resolve(applicationProject.polyfill);
@@ -24,6 +24,7 @@ export type Params = WithConfig<{
24
24
  noClientRebuild?: boolean;
25
25
  resolveSymlinks?: boolean;
26
26
  showConfig?: boolean;
27
+ benchmark?: boolean;
27
28
  withBuildStats?: boolean;
28
29
  // @todo: not working?
29
30
  env?: Record<string, string>;
@@ -1,26 +1,14 @@
1
- import type Config from 'webpack-chain';
2
1
  import { RsdoctorWebpackPlugin } from '@rsdoctor/webpack-plugin';
3
2
  import { AnalyzePlugin } from '../types';
4
3
 
4
+ type options = ConstructorParameters<typeof RsdoctorWebpackPlugin<[]>>;
5
5
  export class RsdoctorAnalyzePlugin extends AnalyzePlugin {
6
6
  requireDeps = [];
7
7
 
8
- options: [RsdoctorWebpackPlugin<[]>['options']] = [
9
- {
10
- features: ['loader', 'plugins', 'resolver'],
11
- },
12
- ];
8
+ options: options = [];
13
9
 
14
10
  plugin = RsdoctorWebpackPlugin;
15
11
 
16
- patchConfig = (config: Config): Config => {
17
- super.patchConfigInternal(config);
18
- // https://github.com/web-infra-dev/rsdoctor/issues/717
19
- config.set('experiments', { backCompat: true });
20
-
21
- return config;
22
- };
23
-
24
12
  // rsdoctor поднимает dev server
25
13
  afterBuild = () => new Promise(() => null);
26
14
  }