Skip to content

Commit 087c85f

Browse files
committed
[kbn/optimizer] report limits with ci metrics
1 parent acedb35 commit 087c85f

File tree

15 files changed

+323
-76
lines changed

15 files changed

+323
-76
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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
pageLoadAssetSize:
2+
advancedSettings: 17_596
3+
alerts: 96_936
4+
apm: 50_340
5+
apmOss: 8_996
6+
beatsManagement: 455_715
7+
bfetch: 31_874
8+
Bundle:
9+
canvas: 1_055_965
10+
charts: 149_211
11+
cloud: 11_076
12+
console: 36_091
13+
core: 681_949
14+
crossClusterReplication: 55_408
15+
dashboard: 588_912
16+
dashboardEnhanced: 49_143
17+
dashboardMode: 12_716
18+
data: 1_415_843
19+
dataEnhanced: 40_162
20+
devTools: 28_637
21+
discover: 94_982
22+
discoverEnhanced: 34_904
23+
embeddable: 302_688
24+
embeddableEnhanced: 31_145
25+
enterpriseSearch: 25_484
26+
esUiShared: 314_930
27+
expressions: 214_136
28+
features: 21_211
29+
fileUpload: 14_717
30+
globalSearch: 33_548
31+
globalSearchBar: 34_422
32+
globalSearchProviders: 15_554
33+
graph: 21_215
34+
grokdebugger: 16_779
35+
home: 32_372
36+
indexLifecycleManagement: 97_032
37+
indexManagement: 130_599
38+
indexPatternManagement: 144_222
39+
infra: 187_885
40+
ingestManager: 528_995
41+
ingestPipelines: 48_003
42+
inputControlVis: 162_675
43+
inspector: 138_711
44+
kibanaLegacy: 97_711
45+
kibanaReact: 132_073
46+
kibanaUtils: 188_829
47+
lens: 82_860
48+
licenseManagement: 31_817
49+
licensing: 28_693
50+
lists: 173_697
51+
logstash: 43_548
52+
management: 36_112
53+
maps: 173_393
54+
mapsLegacy: 106_817
55+
mapsLegacyLicensing: 10_214
56+
ml: 64_944
57+
monitoring: 233_047
58+
navigation: 27_443
59+
newsfeed: 27_628
60+
observability: 58_642
61+
painlessLab: 169_748
62+
regionMap: 56_105
63+
remoteClusters: 41_327
64+
reporting: 173_418
65+
rollup: 87_204
66+
savedObjects: 98_518
67+
savedObjectsManagement: 90_503
68+
searchprofiler: 57_080
69+
security: 172_250
70+
securitySolution: 606_205
71+
share: 86_784
72+
snapshotRestore: 69_032
73+
spaces: 357_173
74+
telemetry: 81_832
75+
telemetryManagementSection: 42_443
76+
tileMap: 55_257
77+
timelion: 19_920
78+
transform: 31_007
79+
triggers_actions_ui: 156_543
80+
uiActions: 87_533
81+
uiActionsEnhanced: 333_430
82+
upgradeAssistant: 71_251
83+
uptime: 30_833
84+
urlDrilldown: 24_174
85+
urlForwarding: 22_554
86+
usageCollection: 29_762
87+
visDefaultEditor: 40_178
88+
visTypeMarkdown: 20_896
89+
visTypeMetric: 32_820
90+
visTypeTable: 84_706
91+
visTypeTagcloud: 27_605
92+
visTypeTimelion: 41_933
93+
visTypeTimeseries: 145_195
94+
visTypeVega: 142_183
95+
visTypeVislib: 233_049
96+
visTypeXy: 10_255
97+
visualizations: 283_763
98+
visualize: 47_384
99+
watcher: 33_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: 15 additions & 2 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 } from './limits';
3132

3233
run(
3334
async ({ log, flags }) => {
@@ -93,21 +94,31 @@ 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+
96102
const config = OptimizerConfig.create({
97103
repoRoot: REPO_ROOT,
98104
watch,
99105
maxWorkerCount,
100-
oss,
106+
oss: oss && !validateLimits,
101107
dist,
102108
cache,
103-
examples,
109+
examples: examples && !validateLimits,
104110
profileWebpack,
105111
extraPluginScanDirs,
106112
inspectWorkers,
107113
includeCoreBundle,
108114
filter,
109115
});
110116

117+
if (validateLimits) {
118+
validateLimitsForAllBundles(log, config);
119+
return;
120+
}
121+
111122
let update$ = runOptimizer(config);
112123

113124
if (reportStats) {
@@ -134,6 +145,7 @@ run(
134145
'profile',
135146
'inspect-workers',
136147
'report-stats',
148+
'validate-limits',
137149
],
138150
string: ['workers', 'scan-dir', 'filter'],
139151
default: {
@@ -156,6 +168,7 @@ run(
156168
--scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary)
157169
--no-inspect-workers when inspecting the parent process, don't inspect the workers
158170
--report-stats attempt to report stats about this execution of the build to the kibana-ci-stats service using this name
171+
--validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle
159172
`,
160173
},
161174
}

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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 } from './optimizer';
27+
28+
const diff = <T>(a: T[], b: T[]): T[] => a.filter((item) => !b.includes(item));
29+
30+
export function readLimits() {
31+
return Yaml.safeLoad(Fs.readFileSync(require.resolve('../limits.yml'), 'utf8'));
32+
}
33+
34+
export function validateLimitsForAllBundles(log: ToolingLog, config: OptimizerConfig) {
35+
const limitBundleIds = Object.keys(config.limits.pageLoadAssetSize);
36+
const configBundleIds = config.bundles.map((b) => b.id);
37+
38+
const missingBundleIds = diff(configBundleIds, limitBundleIds);
39+
const extraBundleIds = diff(limitBundleIds, configBundleIds);
40+
41+
const issues = [];
42+
if (missingBundleIds.length) {
43+
issues.push(`missing: ${missingBundleIds.join(', ')}`);
44+
}
45+
if (extraBundleIds.length) {
46+
issues.push(`extra: ${extraBundleIds.join(', ')}`);
47+
}
48+
if (issues.length) {
49+
throw createFailError(
50+
dedent`
51+
The limits defined in packages/kbn-optimizer/limits.yml are outdated. Please update
52+
this file with a limit (in bytes) for every production bundle.
53+
54+
${issues.join('\n ')}
55+
56+
To validate your changes locally, run:
57+
58+
node scripts/build_kibana_platform_plugins.js --validate-limits
59+
` + '\n'
60+
);
61+
}
62+
63+
log.success('limits.yml file valid');
64+
}

packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jest.mock('./kibana_platform_plugins.ts');
2222
jest.mock('./get_plugin_bundles.ts');
2323
jest.mock('../common/theme_tags.ts');
2424
jest.mock('./filter_by_id.ts');
25+
jest.mock('../limits.ts');
2526

2627
jest.mock('os', () => {
2728
const realOs = jest.requireActual('os');
@@ -385,6 +386,7 @@ describe('OptimizerConfig::create()', () => {
385386
.findKibanaPlatformPlugins;
386387
const getPluginBundles: jest.Mock = jest.requireMock('./get_plugin_bundles.ts').getPluginBundles;
387388
const filterById: jest.Mock = jest.requireMock('./filter_by_id.ts').filterById;
389+
const readLimits: jest.Mock = jest.requireMock('../limits.ts').readLimits;
388390

389391
beforeEach(() => {
390392
if ('mock' in OptimizerConfig.parseOptions) {
@@ -398,6 +400,7 @@ describe('OptimizerConfig::create()', () => {
398400
findKibanaPlatformPlugins.mockReturnValue(Symbol('new platform plugins'));
399401
getPluginBundles.mockReturnValue([Symbol('bundle1'), Symbol('bundle2')]);
400402
filterById.mockReturnValue(Symbol('filtered bundles'));
403+
readLimits.mockReturnValue(Symbol('limits'));
401404

402405
jest.spyOn(OptimizerConfig, 'parseOptions').mockImplementation((): {
403406
[key in keyof ParsedOptions]: any;
@@ -429,6 +432,7 @@ describe('OptimizerConfig::create()', () => {
429432
"cache": Symbol(parsed cache),
430433
"dist": Symbol(parsed dist),
431434
"inspectWorkers": Symbol(parsed inspect workers),
435+
"limits": Symbol(limits),
432436
"maxWorkerCount": Symbol(parsed max worker count),
433437
"plugins": Symbol(new platform plugins),
434438
"profileWebpack": Symbol(parsed profile webpack),

0 commit comments

Comments
 (0)