Skip to content

Commit fd066f7

Browse files
Spencerspalgerkibanamachine
authored
[kbn/optimizer] report limits with ci metrics (#78205)
Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 3c75291 commit fd066f7

File tree

17 files changed

+432
-135
lines changed

17 files changed

+432
-135
lines changed

packages/kbn-dev-utils/src/ci_stats_reporter/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,23 @@ This class integrates with the `ciStats.trackBuild {}` Jenkins Pipeline function
88

99
To create an instance of the reporter, import the class and call `CiStatsReporter.fromEnv(log)` (passing it a tooling log).
1010

11-
#### `CiStatsReporter#metrics(metrics: Array<{ group: string, id: string, value: number }>)`
11+
#### `CiStatsReporter#metrics(metrics: Metric[])`
1212

1313
Use this method to record metrics in the Kibana CI Stats service.
1414

15+
```ts
16+
interface Metric {
17+
group: string,
18+
id: string,
19+
value: number,
20+
// optional limit, values which exceed the limit will fail PRs
21+
limit?: number
22+
// optional path, relative to the root of the repo, where config values
23+
// are defined. Will be linked to in PRs which have overages.
24+
limitConfigPath?: string
25+
}
26+
```
27+
1528
Example:
1629

1730
```ts

packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ interface Config {
2929
buildId: string;
3030
}
3131

32-
export type CiStatsMetrics = Array<{ group: string; id: string; value: number }>;
32+
export type CiStatsMetrics = Array<{
33+
group: string;
34+
id: string;
35+
value: number;
36+
limit?: number;
37+
limitConfigPath?: string;
38+
}>;
3339

3440
function parseConfig(log: ToolingLog) {
3541
const configJson = process.env.KIBANA_CI_STATS_CONFIG;

packages/kbn-optimizer/limits.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
pageLoadAssetSize:
2+
advancedSettings: 27_596
3+
alerts: 106_936
4+
apm: 64_385
5+
apmOss: 18_996
6+
beatsManagement: 188_135
7+
bfetch: 41_874
8+
canvas: 1_066_647
9+
charts: 159_211
10+
cloud: 21_076
11+
console: 46_091
12+
core: 692_106
13+
crossClusterReplication: 65_408
14+
dashboard: 374_194
15+
dashboardEnhanced: 65_646
16+
dashboardMode: 22_716
17+
data: 1_170_713
18+
dataEnhanced: 50_420
19+
devTools: 38_637
20+
discover: 105_145
21+
discoverEnhanced: 42_730
22+
embeddable: 312_874
23+
embeddableEnhanced: 41_145
24+
enterpriseSearch: 35_741
25+
esUiShared: 326_654
26+
expressions: 224_136
27+
features: 31_211
28+
fileUpload: 24_717
29+
globalSearch: 43_548
30+
globalSearchBar: 62_888
31+
globalSearchProviders: 25_554
32+
graph: 31_504
33+
grokdebugger: 26_779
34+
home: 41_661
35+
indexLifecycleManagement: 107_090
36+
indexManagement: 140_608
37+
indexPatternManagement: 154_222
38+
infra: 197_873
39+
ingestManager: 415_829
40+
ingestPipelines: 58_003
41+
inputControlVis: 172_675
42+
inspector: 148_711
43+
kibanaLegacy: 107_711
44+
kibanaOverview: 56_279
45+
kibanaReact: 161_921
46+
kibanaUtils: 198_829
47+
lens: 96_624
48+
licenseManagement: 41_817
49+
licensing: 39_008
50+
lists: 183_665
51+
logstash: 53_548
52+
management: 46_112
53+
maps: 183_610
54+
mapsLegacy: 116_817
55+
mapsLegacyLicensing: 20_214
56+
ml: 82_187
57+
monitoring: 268_612
58+
navigation: 37_269
59+
newsfeed: 42_228
60+
observability: 89_709
61+
painlessLab: 179_748
62+
regionMap: 66_098
63+
remoteClusters: 51_327
64+
reporting: 183_418
65+
rollup: 97_204
66+
savedObjects: 108_518
67+
savedObjectsManagement: 100_503
68+
searchprofiler: 67_080
69+
security: 189_428
70+
securityOss: 30_806
71+
securitySolution: 622_387
72+
share: 99_061
73+
snapshotRestore: 79_032
74+
spaces: 387_915
75+
telemetry: 91_832
76+
telemetryManagementSection: 52_443
77+
tileMap: 65_337
78+
timelion: 29_920
79+
transform: 41_007
80+
triggersActionsUi: 170_001
81+
uiActions: 97_717
82+
uiActionsEnhanced: 349_511
83+
upgradeAssistant: 81_241
84+
uptime: 40_825
85+
urlDrilldown: 34_174
86+
urlForwarding: 32_579
87+
usageCollection: 39_762
88+
visDefaultEditor: 50_178
89+
visTypeMarkdown: 30_896
90+
visTypeMetric: 42_790
91+
visTypeTable: 94_934
92+
visTypeTagcloud: 37_575
93+
visTypeTimelion: 51_933
94+
visTypeTimeseries: 155_203
95+
visTypeVega: 153_573
96+
visTypeVislib: 242_838
97+
visTypeXy: 20_255
98+
visualizations: 295_025
99+
visualize: 57_431
100+
watcher: 43_598

packages/kbn-optimizer/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"cpy": "^8.0.0",
2323
"core-js": "^3.6.5",
2424
"css-loader": "^3.4.2",
25+
"dedent": "^0.7.0",
2526
"del": "^5.1.0",
2627
"execa": "^4.0.2",
2728
"file-loader": "^4.2.0",
@@ -38,6 +39,7 @@
3839
"postcss-loader": "^3.0.0",
3940
"raw-loader": "^3.1.0",
4041
"rxjs": "^6.5.5",
42+
"js-yaml": "^3.14.0",
4143
"sass-loader": "^8.0.2",
4244
"source-map-support": "^0.5.19",
4345
"style-loader": "^1.1.3",

packages/kbn-optimizer/src/cli.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { logOptimizerState } from './log_optimizer_state';
2828
import { OptimizerConfig } from './optimizer';
2929
import { reportOptimizerStats } from './report_optimizer_stats';
3030
import { runOptimizer } from './run_optimizer';
31+
import { validateLimitsForAllBundles, updateBundleLimits } from './limits';
3132

3233
run(
3334
async ({ log, flags }) => {
@@ -93,21 +94,36 @@ run(
9394
throw createFlagError('expected --filter to be one or more strings');
9495
}
9596

97+
const validateLimits = flags['validate-limits'] ?? false;
98+
if (typeof validateLimits !== 'boolean') {
99+
throw createFlagError('expected --validate-limits to have no value');
100+
}
101+
102+
const updateLimits = flags['update-limits'] ?? false;
103+
if (typeof updateLimits !== 'boolean') {
104+
throw createFlagError('expected --update-limits to have no value');
105+
}
106+
96107
const config = OptimizerConfig.create({
97108
repoRoot: REPO_ROOT,
98109
watch,
99110
maxWorkerCount,
100-
oss,
101-
dist,
111+
oss: oss && !(validateLimits || updateLimits),
112+
dist: dist || updateLimits,
102113
cache,
103-
examples,
114+
examples: examples && !(validateLimits || updateLimits),
104115
profileWebpack,
105116
extraPluginScanDirs,
106117
inspectWorkers,
107118
includeCoreBundle,
108119
filter,
109120
});
110121

122+
if (validateLimits) {
123+
validateLimitsForAllBundles(log, config);
124+
return;
125+
}
126+
111127
let update$ = runOptimizer(config);
112128

113129
if (reportStats) {
@@ -121,6 +137,10 @@ run(
121137
}
122138

123139
await update$.pipe(logOptimizerState(log, config)).toPromise();
140+
141+
if (updateLimits) {
142+
updateBundleLimits(log, config);
143+
}
124144
},
125145
{
126146
flags: {
@@ -134,6 +154,8 @@ run(
134154
'profile',
135155
'inspect-workers',
136156
'report-stats',
157+
'validate-limits',
158+
'update-limits',
137159
],
138160
string: ['workers', 'scan-dir', 'filter'],
139161
default: {
@@ -152,10 +174,12 @@ run(
152174
--no-cache disable the cache
153175
--filter comma-separated list of bundle id filters, results from multiple flags are merged, * and ! are supported
154176
--no-examples don't build the example plugins
155-
--dist create bundles that are suitable for inclusion in the Kibana distributable
177+
--dist create bundles that are suitable for inclusion in the Kibana distributable, enabled when running with --update-limits
156178
--scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary)
157179
--no-inspect-workers when inspecting the parent process, don't inspect the workers
158180
--report-stats attempt to report stats about this execution of the build to the kibana-ci-stats service using this name
181+
--validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle
182+
--update-limits run a build and rewrite the limits file to include the current bundle sizes +5kb
159183
`,
160184
},
161185
}

packages/kbn-optimizer/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from './run_optimizer';
2222
export * from './log_optimizer_state';
2323
export * from './report_optimizer_stats';
2424
export * from './node';
25+
export * from './limits';

packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ import del from 'del';
2727
import { toArray, tap, filter } from 'rxjs/operators';
2828
import { REPO_ROOT } from '@kbn/utils';
2929
import { ToolingLog } from '@kbn/dev-utils';
30-
import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '@kbn/optimizer';
30+
import {
31+
runOptimizer,
32+
OptimizerConfig,
33+
OptimizerUpdate,
34+
logOptimizerState,
35+
readLimits,
36+
} from '@kbn/optimizer';
3137

3238
const TMP_DIR = Path.resolve(__dirname, '../__fixtures__/__tmp__');
3339
const MOCK_REPO_SRC = Path.resolve(__dirname, '../__fixtures__/mock_repo');
@@ -72,6 +78,9 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
7278
dist: false,
7379
});
7480

81+
expect(config.limits).toEqual(readLimits());
82+
(config as any).limits = '<Limits>';
83+
7584
expect(config).toMatchSnapshot('OptimizerConfig');
7685

7786
const msgs = await runOptimizer(config)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import Fs from 'fs';
21+
22+
import dedent from 'dedent';
23+
import Yaml from 'js-yaml';
24+
import { createFailError, ToolingLog } from '@kbn/dev-utils';
25+
26+
import { OptimizerConfig, getMetrics } from './optimizer';
27+
28+
const LIMITS_PATH = require.resolve('../limits.yml');
29+
const DEFAULT_BUDGET = 15000;
30+
31+
const diff = <T>(a: T[], b: T[]): T[] => a.filter((item) => !b.includes(item));
32+
33+
export function readLimits() {
34+
return Yaml.safeLoad(Fs.readFileSync(LIMITS_PATH, 'utf8'));
35+
}
36+
37+
export function validateLimitsForAllBundles(log: ToolingLog, config: OptimizerConfig) {
38+
const limitBundleIds = Object.keys(config.limits.pageLoadAssetSize);
39+
const configBundleIds = config.bundles.map((b) => b.id);
40+
41+
const missingBundleIds = diff(configBundleIds, limitBundleIds);
42+
const extraBundleIds = diff(limitBundleIds, configBundleIds);
43+
44+
const issues = [];
45+
if (missingBundleIds.length) {
46+
issues.push(`missing: ${missingBundleIds.join(', ')}`);
47+
}
48+
if (extraBundleIds.length) {
49+
issues.push(`extra: ${extraBundleIds.join(', ')}`);
50+
}
51+
if (issues.length) {
52+
throw createFailError(
53+
dedent`
54+
The limits defined in packages/kbn-optimizer/limits.yml are outdated. Please update
55+
this file with a limit (in bytes) for every production bundle.
56+
57+
${issues.join('\n ')}
58+
59+
To validate your changes locally, run:
60+
61+
node scripts/build_kibana_platform_plugins.js --validate-limits
62+
` + '\n'
63+
);
64+
}
65+
66+
log.success('limits.yml file valid');
67+
}
68+
69+
export function updateBundleLimits(log: ToolingLog, config: OptimizerConfig) {
70+
const metrics = getMetrics(log, config);
71+
72+
const number = (input: number) => input.toLocaleString('en').split(',').join('_');
73+
74+
let yaml = `pageLoadAssetSize:\n`;
75+
for (const metric of metrics.sort((a, b) => a.id.localeCompare(b.id))) {
76+
if (metric.group === 'page load bundle size') {
77+
yaml += ` ${metric.id}: ${number(metric.value + DEFAULT_BUDGET)}\n`;
78+
}
79+
}
80+
81+
Fs.writeFileSync(LIMITS_PATH, yaml);
82+
log.success(`wrote updated limits to ${LIMITS_PATH}`);
83+
}

0 commit comments

Comments
 (0)