mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4bf2f618a4
commit
d3c37cde22
33 changed files with 519 additions and 354 deletions
|
@ -555,6 +555,7 @@
|
||||||
"@types/webpack": "^4.41.3",
|
"@types/webpack": "^4.41.3",
|
||||||
"@types/webpack-env": "^1.15.3",
|
"@types/webpack-env": "^1.15.3",
|
||||||
"@types/webpack-merge": "^4.1.5",
|
"@types/webpack-merge": "^4.1.5",
|
||||||
|
"@types/webpack-sources": "^0.1.4",
|
||||||
"@types/write-pkg": "^3.1.0",
|
"@types/write-pkg": "^3.1.0",
|
||||||
"@types/xml-crypto": "^1.4.1",
|
"@types/xml-crypto": "^1.4.1",
|
||||||
"@types/xml2js": "^0.4.5",
|
"@types/xml2js": "^0.4.5",
|
||||||
|
@ -840,6 +841,7 @@
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^3.3.12",
|
||||||
"webpack-dev-server": "^3.11.0",
|
"webpack-dev-server": "^3.11.0",
|
||||||
"webpack-merge": "^4.2.2",
|
"webpack-merge": "^4.2.2",
|
||||||
|
"webpack-sources": "^1.4.1",
|
||||||
"write-pkg": "^4.0.0",
|
"write-pkg": "^4.0.0",
|
||||||
"xml-crypto": "^2.0.0",
|
"xml-crypto": "^2.0.0",
|
||||||
"xmlbuilder": "13.0.2",
|
"xmlbuilder": "13.0.2",
|
||||||
|
|
|
@ -7,3 +7,4 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './ci_stats_reporter';
|
export * from './ci_stats_reporter';
|
||||||
|
export * from './ship_ci_stats_cli';
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Path from 'path';
|
||||||
|
import Fs from 'fs';
|
||||||
|
|
||||||
|
import { CiStatsReporter } from './ci_stats_reporter';
|
||||||
|
import { run, createFlagError } from '../run';
|
||||||
|
|
||||||
|
export function shipCiStatsCli() {
|
||||||
|
run(
|
||||||
|
async ({ log, flags }) => {
|
||||||
|
let metricPaths = flags.metrics;
|
||||||
|
if (typeof metricPaths === 'string') {
|
||||||
|
metricPaths = [metricPaths];
|
||||||
|
} else if (!Array.isArray(metricPaths) || !metricPaths.every((p) => typeof p === 'string')) {
|
||||||
|
throw createFlagError('expected --metrics to be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const reporter = CiStatsReporter.fromEnv(log);
|
||||||
|
for (const path of metricPaths) {
|
||||||
|
// resolve path from CLI relative to CWD
|
||||||
|
const abs = Path.resolve(path);
|
||||||
|
const json = Fs.readFileSync(abs, 'utf8');
|
||||||
|
await reporter.metrics(JSON.parse(json));
|
||||||
|
log.success('shipped metrics from', path);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'ship ci-stats which have been written to files',
|
||||||
|
usage: `node scripts/ship_ci_stats`,
|
||||||
|
log: {
|
||||||
|
defaultLevel: 'debug',
|
||||||
|
},
|
||||||
|
flags: {
|
||||||
|
string: ['metrics'],
|
||||||
|
help: `
|
||||||
|
--metrics [path] A path to a JSON file that includes metrics which should be sent. Multiple instances supported
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -12,11 +12,10 @@ import Path from 'path';
|
||||||
|
|
||||||
import { REPO_ROOT } from '@kbn/utils';
|
import { REPO_ROOT } from '@kbn/utils';
|
||||||
import { lastValueFrom } from '@kbn/std';
|
import { lastValueFrom } from '@kbn/std';
|
||||||
import { run, createFlagError, CiStatsReporter } from '@kbn/dev-utils';
|
import { run, createFlagError } from '@kbn/dev-utils';
|
||||||
|
|
||||||
import { logOptimizerState } from './log_optimizer_state';
|
import { logOptimizerState } from './log_optimizer_state';
|
||||||
import { OptimizerConfig } from './optimizer';
|
import { OptimizerConfig } from './optimizer';
|
||||||
import { reportOptimizerStats } from './report_optimizer_stats';
|
|
||||||
import { runOptimizer } from './run_optimizer';
|
import { runOptimizer } from './run_optimizer';
|
||||||
import { validateLimitsForAllBundles, updateBundleLimits } from './limits';
|
import { validateLimitsForAllBundles, updateBundleLimits } from './limits';
|
||||||
|
|
||||||
|
@ -120,17 +119,7 @@ run(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let update$ = runOptimizer(config);
|
const update$ = runOptimizer(config);
|
||||||
|
|
||||||
if (reportStats) {
|
|
||||||
const reporter = CiStatsReporter.fromEnv(log);
|
|
||||||
|
|
||||||
if (!reporter.isEnabled()) {
|
|
||||||
log.warning('Unable to initialize CiStatsReporter from env');
|
|
||||||
}
|
|
||||||
|
|
||||||
update$ = update$.pipe(reportOptimizerStats(reporter, config, log));
|
|
||||||
}
|
|
||||||
|
|
||||||
await lastValueFrom(update$.pipe(logOptimizerState(log, config)));
|
await lastValueFrom(update$.pipe(logOptimizerState(log, config)));
|
||||||
|
|
||||||
|
@ -153,7 +142,6 @@ run(
|
||||||
'cache',
|
'cache',
|
||||||
'profile',
|
'profile',
|
||||||
'inspect-workers',
|
'inspect-workers',
|
||||||
'report-stats',
|
|
||||||
'validate-limits',
|
'validate-limits',
|
||||||
'update-limits',
|
'update-limits',
|
||||||
],
|
],
|
||||||
|
@ -179,7 +167,6 @@ run(
|
||||||
--dist create bundles that are suitable for inclusion in the Kibana distributable, enabled when running with --update-limits
|
--dist create bundles that are suitable for inclusion in the Kibana distributable, enabled when running with --update-limits
|
||||||
--scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary)
|
--scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary)
|
||||||
--no-inspect-workers when inspecting the parent process, don't inspect the workers
|
--no-inspect-workers when inspecting the parent process, don't inspect the workers
|
||||||
--report-stats attempt to report stats about this execution of the build to the kibana-ci-stats service using this name
|
|
||||||
--validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle
|
--validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle
|
||||||
--update-limits run a build and rewrite the limits file to include the current bundle sizes +5kb
|
--update-limits run a build and rewrite the limits file to include the current bundle sizes +5kb
|
||||||
`,
|
`,
|
||||||
|
|
|
@ -42,6 +42,7 @@ it('creates cache keys', () => {
|
||||||
"id": "bar",
|
"id": "bar",
|
||||||
"manifestPath": undefined,
|
"manifestPath": undefined,
|
||||||
"outputDir": "/foo/bar/target",
|
"outputDir": "/foo/bar/target",
|
||||||
|
"pageLoadAssetSizeLimit": undefined,
|
||||||
"publicDirNames": Array [
|
"publicDirNames": Array [
|
||||||
"public",
|
"public",
|
||||||
],
|
],
|
||||||
|
@ -79,6 +80,7 @@ it('parses bundles from JSON specs', () => {
|
||||||
"id": "bar",
|
"id": "bar",
|
||||||
"manifestPath": undefined,
|
"manifestPath": undefined,
|
||||||
"outputDir": "/foo/bar/target",
|
"outputDir": "/foo/bar/target",
|
||||||
|
"pageLoadAssetSizeLimit": undefined,
|
||||||
"publicDirNames": Array [
|
"publicDirNames": Array [
|
||||||
"public",
|
"public",
|
||||||
],
|
],
|
||||||
|
|
|
@ -36,6 +36,8 @@ export interface BundleSpec {
|
||||||
readonly banner?: string;
|
readonly banner?: string;
|
||||||
/** Absolute path to a kibana.json manifest file, if omitted we assume there are not dependenices */
|
/** Absolute path to a kibana.json manifest file, if omitted we assume there are not dependenices */
|
||||||
readonly manifestPath?: string;
|
readonly manifestPath?: string;
|
||||||
|
/** Maximum allowed page load asset size for the bundles page load asset */
|
||||||
|
readonly pageLoadAssetSizeLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Bundle {
|
export class Bundle {
|
||||||
|
@ -63,6 +65,8 @@ export class Bundle {
|
||||||
* Every bundle mentioned in the `requiredBundles` must be built together.
|
* Every bundle mentioned in the `requiredBundles` must be built together.
|
||||||
*/
|
*/
|
||||||
public readonly manifestPath: BundleSpec['manifestPath'];
|
public readonly manifestPath: BundleSpec['manifestPath'];
|
||||||
|
/** Maximum allowed page load asset size for the bundles page load asset */
|
||||||
|
public readonly pageLoadAssetSizeLimit: BundleSpec['pageLoadAssetSizeLimit'];
|
||||||
|
|
||||||
public readonly cache: BundleCache;
|
public readonly cache: BundleCache;
|
||||||
|
|
||||||
|
@ -75,8 +79,9 @@ export class Bundle {
|
||||||
this.outputDir = spec.outputDir;
|
this.outputDir = spec.outputDir;
|
||||||
this.manifestPath = spec.manifestPath;
|
this.manifestPath = spec.manifestPath;
|
||||||
this.banner = spec.banner;
|
this.banner = spec.banner;
|
||||||
|
this.pageLoadAssetSizeLimit = spec.pageLoadAssetSizeLimit;
|
||||||
|
|
||||||
this.cache = new BundleCache(Path.resolve(this.outputDir, '.kbn-optimizer-cache'));
|
this.cache = new BundleCache(this.outputDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,6 +112,7 @@ export class Bundle {
|
||||||
outputDir: this.outputDir,
|
outputDir: this.outputDir,
|
||||||
manifestPath: this.manifestPath,
|
manifestPath: this.manifestPath,
|
||||||
banner: this.banner,
|
banner: this.banner,
|
||||||
|
pageLoadAssetSizeLimit: this.pageLoadAssetSizeLimit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +228,13 @@ export function parseBundles(json: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { pageLoadAssetSizeLimit } = spec;
|
||||||
|
if (pageLoadAssetSizeLimit !== undefined) {
|
||||||
|
if (!(typeof pageLoadAssetSizeLimit === 'number')) {
|
||||||
|
throw new Error('`bundles[]` must have a numeric `pageLoadAssetSizeLimit` property');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Bundle({
|
return new Bundle({
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
|
@ -231,6 +244,7 @@ export function parseBundles(json: string) {
|
||||||
outputDir,
|
outputDir,
|
||||||
banner,
|
banner,
|
||||||
manifestPath,
|
manifestPath,
|
||||||
|
pageLoadAssetSizeLimit,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,12 +25,12 @@ beforeEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`doesn't complain if files are not on disk`, () => {
|
it(`doesn't complain if files are not on disk`, () => {
|
||||||
const cache = new BundleCache('/foo/bar.json');
|
const cache = new BundleCache('/foo');
|
||||||
expect(cache.get()).toEqual({});
|
expect(cache.get()).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`updates files on disk when calling set()`, () => {
|
it(`updates files on disk when calling set()`, () => {
|
||||||
const cache = new BundleCache('/foo/bar.json');
|
const cache = new BundleCache('/foo');
|
||||||
cache.set(SOME_STATE);
|
cache.set(SOME_STATE);
|
||||||
expect(mockReadFileSync).not.toHaveBeenCalled();
|
expect(mockReadFileSync).not.toHaveBeenCalled();
|
||||||
expect(mockMkdirSync.mock.calls).toMatchInlineSnapshot(`
|
expect(mockMkdirSync.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
@ -46,7 +46,7 @@ it(`updates files on disk when calling set()`, () => {
|
||||||
expect(mockWriteFileSync.mock.calls).toMatchInlineSnapshot(`
|
expect(mockWriteFileSync.mock.calls).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
"/foo/bar.json",
|
"/foo/.kbn-optimizer-cache",
|
||||||
"{
|
"{
|
||||||
\\"cacheKey\\": \\"abc\\",
|
\\"cacheKey\\": \\"abc\\",
|
||||||
\\"files\\": [
|
\\"files\\": [
|
||||||
|
@ -61,7 +61,7 @@ it(`updates files on disk when calling set()`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`serves updated state from memory`, () => {
|
it(`serves updated state from memory`, () => {
|
||||||
const cache = new BundleCache('/foo/bar.json');
|
const cache = new BundleCache('/foo');
|
||||||
cache.set(SOME_STATE);
|
cache.set(SOME_STATE);
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ it(`serves updated state from memory`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads state from disk on get() after refresh()', () => {
|
it('reads state from disk on get() after refresh()', () => {
|
||||||
const cache = new BundleCache('/foo/bar.json');
|
const cache = new BundleCache('/foo');
|
||||||
cache.set(SOME_STATE);
|
cache.set(SOME_STATE);
|
||||||
cache.refresh();
|
cache.refresh();
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
@ -83,7 +83,7 @@ it('reads state from disk on get() after refresh()', () => {
|
||||||
expect(mockReadFileSync.mock.calls).toMatchInlineSnapshot(`
|
expect(mockReadFileSync.mock.calls).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Array [
|
Array [
|
||||||
"/foo/bar.json",
|
"/foo/.kbn-optimizer-cache",
|
||||||
"utf8",
|
"utf8",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -91,7 +91,7 @@ it('reads state from disk on get() after refresh()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('provides accessors to specific state properties', () => {
|
it('provides accessors to specific state properties', () => {
|
||||||
const cache = new BundleCache('/foo/bar.json');
|
const cache = new BundleCache('/foo');
|
||||||
|
|
||||||
expect(cache.getModuleCount()).toBe(undefined);
|
expect(cache.getModuleCount()).toBe(undefined);
|
||||||
expect(cache.getReferencedFiles()).toEqual(undefined);
|
expect(cache.getReferencedFiles()).toEqual(undefined);
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
import Fs from 'fs';
|
import Fs from 'fs';
|
||||||
import Path from 'path';
|
import Path from 'path';
|
||||||
|
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import { RawSource } from 'webpack-sources';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
optimizerCacheKey?: unknown;
|
optimizerCacheKey?: unknown;
|
||||||
cacheKey?: unknown;
|
cacheKey?: unknown;
|
||||||
|
@ -20,13 +23,17 @@ export interface State {
|
||||||
|
|
||||||
const DEFAULT_STATE: State = {};
|
const DEFAULT_STATE: State = {};
|
||||||
const DEFAULT_STATE_JSON = JSON.stringify(DEFAULT_STATE);
|
const DEFAULT_STATE_JSON = JSON.stringify(DEFAULT_STATE);
|
||||||
|
const CACHE_FILENAME = '.kbn-optimizer-cache';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to read and update metadata for bundles.
|
* Helper to read and update metadata for bundles.
|
||||||
*/
|
*/
|
||||||
export class BundleCache {
|
export class BundleCache {
|
||||||
private state: State | undefined = undefined;
|
private state: State | undefined = undefined;
|
||||||
constructor(private readonly path: string | false) {}
|
private readonly path: string | false;
|
||||||
|
constructor(outputDir: string | false) {
|
||||||
|
this.path = outputDir === false ? false : Path.resolve(outputDir, CACHE_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.state = undefined;
|
this.state = undefined;
|
||||||
|
@ -63,6 +70,7 @@ export class BundleCache {
|
||||||
|
|
||||||
set(updated: State) {
|
set(updated: State) {
|
||||||
this.state = updated;
|
this.state = updated;
|
||||||
|
|
||||||
if (this.path) {
|
if (this.path) {
|
||||||
const directory = Path.dirname(this.path);
|
const directory = Path.dirname(this.path);
|
||||||
Fs.mkdirSync(directory, { recursive: true });
|
Fs.mkdirSync(directory, { recursive: true });
|
||||||
|
@ -107,4 +115,16 @@ export class BundleCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public writeWebpackAsset(compilation: webpack.compilation.Compilation) {
|
||||||
|
if (!this.path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = new RawSource(JSON.stringify(this.state, null, 2));
|
||||||
|
|
||||||
|
// see https://github.com/jantimon/html-webpack-plugin/blob/33d69f49e6e9787796402715d1b9cd59f80b628f/index.js#L266
|
||||||
|
// @ts-expect-error undocumented, used to add assets to the output
|
||||||
|
compilation.emitAsset(CACHE_FILENAME, source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,5 @@
|
||||||
export { OptimizerConfig } from './optimizer';
|
export { OptimizerConfig } from './optimizer';
|
||||||
export * from './run_optimizer';
|
export * from './run_optimizer';
|
||||||
export * from './log_optimizer_state';
|
export * from './log_optimizer_state';
|
||||||
export * from './report_optimizer_stats';
|
|
||||||
export * from './node';
|
export * from './node';
|
||||||
export * from './limits';
|
export * from './limits';
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -16,13 +16,7 @@ import del from 'del';
|
||||||
import { tap, filter } from 'rxjs/operators';
|
import { tap, filter } from 'rxjs/operators';
|
||||||
import { REPO_ROOT } from '@kbn/utils';
|
import { REPO_ROOT } from '@kbn/utils';
|
||||||
import { ToolingLog } from '@kbn/dev-utils';
|
import { ToolingLog } from '@kbn/dev-utils';
|
||||||
import {
|
import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '@kbn/optimizer';
|
||||||
runOptimizer,
|
|
||||||
OptimizerConfig,
|
|
||||||
OptimizerUpdate,
|
|
||||||
logOptimizerState,
|
|
||||||
readLimits,
|
|
||||||
} from '@kbn/optimizer';
|
|
||||||
|
|
||||||
import { allValuesFrom } from '../common';
|
import { allValuesFrom } from '../common';
|
||||||
|
|
||||||
|
@ -69,9 +63,6 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
|
||||||
dist: false,
|
dist: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(config.limits).toEqual(readLimits());
|
|
||||||
(config as any).limits = '<Limits>';
|
|
||||||
|
|
||||||
expect(config).toMatchSnapshot('OptimizerConfig');
|
expect(config).toMatchSnapshot('OptimizerConfig');
|
||||||
|
|
||||||
const msgs = await allValuesFrom(
|
const msgs = await allValuesFrom(
|
||||||
|
@ -231,6 +222,10 @@ it('prepares assets for distribution', async () => {
|
||||||
|
|
||||||
await allValuesFrom(runOptimizer(config).pipe(logOptimizerState(log, config)));
|
await allValuesFrom(runOptimizer(config).pipe(logOptimizerState(log, config)));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/foo/target/public/metrics.json'), 'utf8')
|
||||||
|
).toMatchSnapshot('metrics.json');
|
||||||
|
|
||||||
expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle');
|
expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle');
|
||||||
expectFileMatchesSnapshotWithCompression(
|
expectFileMatchesSnapshotWithCompression(
|
||||||
'plugins/foo/target/public/foo.chunk.1.js',
|
'plugins/foo/target/public/foo.chunk.1.js',
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Fs from 'fs';
|
import Fs from 'fs';
|
||||||
|
import Path from 'path';
|
||||||
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import Yaml from 'js-yaml';
|
import Yaml from 'js-yaml';
|
||||||
import { createFailError, ToolingLog } from '@kbn/dev-utils';
|
import { createFailError, ToolingLog, CiStatsMetrics } from '@kbn/dev-utils';
|
||||||
|
|
||||||
import { OptimizerConfig, getMetrics, Limits } from './optimizer';
|
import { OptimizerConfig, Limits } from './optimizer';
|
||||||
|
|
||||||
const LIMITS_PATH = require.resolve('../limits.yml');
|
const LIMITS_PATH = require.resolve('../limits.yml');
|
||||||
const DEFAULT_BUDGET = 15000;
|
const DEFAULT_BUDGET = 15000;
|
||||||
|
@ -33,7 +34,7 @@ export function readLimits(): Limits {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateLimitsForAllBundles(log: ToolingLog, config: OptimizerConfig) {
|
export function validateLimitsForAllBundles(log: ToolingLog, config: OptimizerConfig) {
|
||||||
const limitBundleIds = Object.keys(config.limits.pageLoadAssetSize || {});
|
const limitBundleIds = Object.keys(readLimits().pageLoadAssetSize || {});
|
||||||
const configBundleIds = config.bundles.map((b) => b.id);
|
const configBundleIds = config.bundles.map((b) => b.id);
|
||||||
|
|
||||||
const missingBundleIds = diff(configBundleIds, limitBundleIds);
|
const missingBundleIds = diff(configBundleIds, limitBundleIds);
|
||||||
|
@ -75,15 +76,21 @@ interface UpdateBundleLimitsOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateBundleLimits({ log, config, dropMissing }: UpdateBundleLimitsOptions) {
|
export function updateBundleLimits({ log, config, dropMissing }: UpdateBundleLimitsOptions) {
|
||||||
const metrics = getMetrics(log, config);
|
const limits = readLimits();
|
||||||
|
const metrics: CiStatsMetrics = config.bundles
|
||||||
|
.map((bundle) =>
|
||||||
|
JSON.parse(Fs.readFileSync(Path.resolve(bundle.outputDir, 'metrics.json'), 'utf-8'))
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
|
||||||
const pageLoadAssetSize: NonNullable<Limits['pageLoadAssetSize']> = dropMissing
|
const pageLoadAssetSize: NonNullable<Limits['pageLoadAssetSize']> = dropMissing
|
||||||
? {}
|
? {}
|
||||||
: config.limits.pageLoadAssetSize ?? {};
|
: limits.pageLoadAssetSize ?? {};
|
||||||
|
|
||||||
for (const metric of metrics.sort((a, b) => a.id.localeCompare(b.id))) {
|
for (const metric of metrics) {
|
||||||
if (metric.group === 'page load bundle size') {
|
if (metric.group === 'page load bundle size') {
|
||||||
const existingLimit = config.limits.pageLoadAssetSize?.[metric.id];
|
const existingLimit = limits.pageLoadAssetSize?.[metric.id];
|
||||||
pageLoadAssetSize[metric.id] =
|
pageLoadAssetSize[metric.id] =
|
||||||
existingLimit != null && existingLimit >= metric.value
|
existingLimit != null && existingLimit >= metric.value
|
||||||
? existingLimit
|
? existingLimit
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Fs from 'fs';
|
|
||||||
import Path from 'path';
|
|
||||||
|
|
||||||
import { ToolingLog, CiStatsMetrics } from '@kbn/dev-utils';
|
|
||||||
import { OptimizerConfig } from './optimizer_config';
|
|
||||||
|
|
||||||
const flatten = <T>(arr: Array<T | T[]>): T[] =>
|
|
||||||
arr.reduce((acc: T[], item) => acc.concat(item), []);
|
|
||||||
|
|
||||||
interface Entry {
|
|
||||||
relPath: string;
|
|
||||||
stats: Fs.Stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
const IGNORED_EXTNAME = ['.map', '.br', '.gz'];
|
|
||||||
|
|
||||||
const getFiles = (dir: string, parent?: string) =>
|
|
||||||
flatten(
|
|
||||||
Fs.readdirSync(dir).map((name): Entry | Entry[] => {
|
|
||||||
const absPath = Path.join(dir, name);
|
|
||||||
const relPath = parent ? Path.join(parent, name) : name;
|
|
||||||
const stats = Fs.statSync(absPath);
|
|
||||||
|
|
||||||
if (stats.isDirectory()) {
|
|
||||||
return getFiles(absPath, relPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
relPath,
|
|
||||||
stats,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
).filter((file) => {
|
|
||||||
const filename = Path.basename(file.relPath);
|
|
||||||
if (filename.startsWith('.')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext = Path.extname(filename);
|
|
||||||
if (IGNORED_EXTNAME.includes(ext)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
export function getMetrics(log: ToolingLog, config: OptimizerConfig) {
|
|
||||||
return flatten(
|
|
||||||
config.bundles.map((bundle) => {
|
|
||||||
// make the cache read from the cache file since it was likely updated by the worker
|
|
||||||
bundle.cache.refresh();
|
|
||||||
|
|
||||||
const outputFiles = getFiles(bundle.outputDir);
|
|
||||||
const entryName = `${bundle.id}.${bundle.type}.js`;
|
|
||||||
const entry = outputFiles.find((f) => f.relPath === entryName);
|
|
||||||
if (!entry) {
|
|
||||||
throw new Error(
|
|
||||||
`Unable to find bundle entry named [${entryName}] in [${bundle.outputDir}]`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkPrefix = `${bundle.id}.chunk.`;
|
|
||||||
const asyncChunks = outputFiles.filter((f) => f.relPath.startsWith(chunkPrefix));
|
|
||||||
const miscFiles = outputFiles.filter((f) => f !== entry && !asyncChunks.includes(f));
|
|
||||||
|
|
||||||
if (asyncChunks.length) {
|
|
||||||
log.verbose(bundle.id, 'async chunks', asyncChunks);
|
|
||||||
}
|
|
||||||
if (miscFiles.length) {
|
|
||||||
log.verbose(bundle.id, 'misc files', asyncChunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sumSize = (files: Entry[]) => files.reduce((acc: number, f) => acc + f.stats!.size, 0);
|
|
||||||
|
|
||||||
const bundleMetrics: CiStatsMetrics = [
|
|
||||||
{
|
|
||||||
group: `@kbn/optimizer bundle module count`,
|
|
||||||
id: bundle.id,
|
|
||||||
value: bundle.cache.getModuleCount() || 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: `page load bundle size`,
|
|
||||||
id: bundle.id,
|
|
||||||
value: entry.stats!.size,
|
|
||||||
limit: config.limits.pageLoadAssetSize?.[bundle.id],
|
|
||||||
limitConfigPath: `packages/kbn-optimizer/limits.yml`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: `async chunks size`,
|
|
||||||
id: bundle.id,
|
|
||||||
value: sumSize(asyncChunks),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: `async chunk count`,
|
|
||||||
id: bundle.id,
|
|
||||||
value: asyncChunks.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: `miscellaneous assets size`,
|
|
||||||
id: bundle.id,
|
|
||||||
value: sumSize(miscFiles),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
log.debug(bundle.id, 'metrics', bundleMetrics);
|
|
||||||
|
|
||||||
return bundleMetrics;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -48,7 +48,12 @@ it('returns a bundle for core and each plugin', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'/repo',
|
'/repo',
|
||||||
'/output'
|
'/output',
|
||||||
|
{
|
||||||
|
pageLoadAssetSize: {
|
||||||
|
box: 123,
|
||||||
|
},
|
||||||
|
}
|
||||||
).map((b) => b.toSpec())
|
).map((b) => b.toSpec())
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
|
@ -58,6 +63,7 @@ it('returns a bundle for core and each plugin', () => {
|
||||||
"id": "foo",
|
"id": "foo",
|
||||||
"manifestPath": <repoRoot>/plugins/foo/kibana.json,
|
"manifestPath": <repoRoot>/plugins/foo/kibana.json,
|
||||||
"outputDir": <outputRoot>/plugins/foo/target/public,
|
"outputDir": <outputRoot>/plugins/foo/target/public,
|
||||||
|
"pageLoadAssetSizeLimit": undefined,
|
||||||
"publicDirNames": Array [
|
"publicDirNames": Array [
|
||||||
"public",
|
"public",
|
||||||
],
|
],
|
||||||
|
@ -70,6 +76,7 @@ it('returns a bundle for core and each plugin', () => {
|
||||||
"id": "baz",
|
"id": "baz",
|
||||||
"manifestPath": <outsideOfRepo>/plugins/baz/kibana.json,
|
"manifestPath": <outsideOfRepo>/plugins/baz/kibana.json,
|
||||||
"outputDir": <outsideOfRepo>/plugins/baz/target/public,
|
"outputDir": <outsideOfRepo>/plugins/baz/target/public,
|
||||||
|
"pageLoadAssetSizeLimit": undefined,
|
||||||
"publicDirNames": Array [
|
"publicDirNames": Array [
|
||||||
"public",
|
"public",
|
||||||
],
|
],
|
||||||
|
@ -84,6 +91,7 @@ it('returns a bundle for core and each plugin', () => {
|
||||||
"id": "box",
|
"id": "box",
|
||||||
"manifestPath": <repoRoot>/x-pack/plugins/box/kibana.json,
|
"manifestPath": <repoRoot>/x-pack/plugins/box/kibana.json,
|
||||||
"outputDir": <outputRoot>/x-pack/plugins/box/target/public,
|
"outputDir": <outputRoot>/x-pack/plugins/box/target/public,
|
||||||
|
"pageLoadAssetSizeLimit": 123,
|
||||||
"publicDirNames": Array [
|
"publicDirNames": Array [
|
||||||
"public",
|
"public",
|
||||||
],
|
],
|
||||||
|
|
|
@ -9,13 +9,15 @@
|
||||||
import Path from 'path';
|
import Path from 'path';
|
||||||
|
|
||||||
import { Bundle } from '../common';
|
import { Bundle } from '../common';
|
||||||
|
import { Limits } from './optimizer_config';
|
||||||
|
|
||||||
import { KibanaPlatformPlugin } from './kibana_platform_plugins';
|
import { KibanaPlatformPlugin } from './kibana_platform_plugins';
|
||||||
|
|
||||||
export function getPluginBundles(
|
export function getPluginBundles(
|
||||||
plugins: KibanaPlatformPlugin[],
|
plugins: KibanaPlatformPlugin[],
|
||||||
repoRoot: string,
|
repoRoot: string,
|
||||||
outputRoot: string
|
outputRoot: string,
|
||||||
|
limits: Limits
|
||||||
) {
|
) {
|
||||||
const xpackDirSlash = Path.resolve(repoRoot, 'x-pack') + Path.sep;
|
const xpackDirSlash = Path.resolve(repoRoot, 'x-pack') + Path.sep;
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ export function getPluginBundles(
|
||||||
? `/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. \n` +
|
? `/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. \n` +
|
||||||
` * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */\n`
|
` * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */\n`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
pageLoadAssetSizeLimit: limits.pageLoadAssetSize?.[p.id],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,3 @@ export * from './watch_bundles_for_changes';
|
||||||
export * from './run_workers';
|
export * from './run_workers';
|
||||||
export * from './bundle_cache';
|
export * from './bundle_cache';
|
||||||
export * from './handle_optimizer_completion';
|
export * from './handle_optimizer_completion';
|
||||||
export * from './get_output_stats';
|
|
||||||
|
|
|
@ -435,7 +435,6 @@ describe('OptimizerConfig::create()', () => {
|
||||||
"cache": Symbol(parsed cache),
|
"cache": Symbol(parsed cache),
|
||||||
"dist": Symbol(parsed dist),
|
"dist": Symbol(parsed dist),
|
||||||
"inspectWorkers": Symbol(parsed inspect workers),
|
"inspectWorkers": Symbol(parsed inspect workers),
|
||||||
"limits": Symbol(limits),
|
|
||||||
"maxWorkerCount": Symbol(parsed max worker count),
|
"maxWorkerCount": Symbol(parsed max worker count),
|
||||||
"plugins": Symbol(new platform plugins),
|
"plugins": Symbol(new platform plugins),
|
||||||
"profileWebpack": Symbol(parsed profile webpack),
|
"profileWebpack": Symbol(parsed profile webpack),
|
||||||
|
@ -457,7 +456,7 @@ describe('OptimizerConfig::create()', () => {
|
||||||
[Window],
|
[Window],
|
||||||
],
|
],
|
||||||
"invocationCallOrder": Array [
|
"invocationCallOrder": Array [
|
||||||
21,
|
22,
|
||||||
],
|
],
|
||||||
"results": Array [
|
"results": Array [
|
||||||
Object {
|
Object {
|
||||||
|
@ -480,7 +479,7 @@ describe('OptimizerConfig::create()', () => {
|
||||||
[Window],
|
[Window],
|
||||||
],
|
],
|
||||||
"invocationCallOrder": Array [
|
"invocationCallOrder": Array [
|
||||||
24,
|
25,
|
||||||
],
|
],
|
||||||
"results": Array [
|
"results": Array [
|
||||||
Object {
|
Object {
|
||||||
|
@ -498,13 +497,14 @@ describe('OptimizerConfig::create()', () => {
|
||||||
Symbol(new platform plugins),
|
Symbol(new platform plugins),
|
||||||
Symbol(parsed repo root),
|
Symbol(parsed repo root),
|
||||||
Symbol(parsed output root),
|
Symbol(parsed output root),
|
||||||
|
Symbol(limits),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"instances": Array [
|
"instances": Array [
|
||||||
[Window],
|
[Window],
|
||||||
],
|
],
|
||||||
"invocationCallOrder": Array [
|
"invocationCallOrder": Array [
|
||||||
22,
|
23,
|
||||||
],
|
],
|
||||||
"results": Array [
|
"results": Array [
|
||||||
Object {
|
Object {
|
||||||
|
|
|
@ -211,6 +211,7 @@ export class OptimizerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(inputOptions: Options) {
|
static create(inputOptions: Options) {
|
||||||
|
const limits = readLimits();
|
||||||
const options = OptimizerConfig.parseOptions(inputOptions);
|
const options = OptimizerConfig.parseOptions(inputOptions);
|
||||||
const plugins = findKibanaPlatformPlugins(options.pluginScanDirs, options.pluginPaths);
|
const plugins = findKibanaPlatformPlugins(options.pluginScanDirs, options.pluginPaths);
|
||||||
const bundles = [
|
const bundles = [
|
||||||
|
@ -223,10 +224,11 @@ export class OptimizerConfig {
|
||||||
sourceRoot: options.repoRoot,
|
sourceRoot: options.repoRoot,
|
||||||
contextDir: Path.resolve(options.repoRoot, 'src/core'),
|
contextDir: Path.resolve(options.repoRoot, 'src/core'),
|
||||||
outputDir: Path.resolve(options.outputRoot, 'src/core/target/public'),
|
outputDir: Path.resolve(options.outputRoot, 'src/core/target/public'),
|
||||||
|
pageLoadAssetSizeLimit: limits.pageLoadAssetSize?.core,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...getPluginBundles(plugins, options.repoRoot, options.outputRoot),
|
...getPluginBundles(plugins, options.repoRoot, options.outputRoot, limits),
|
||||||
];
|
];
|
||||||
|
|
||||||
return new OptimizerConfig(
|
return new OptimizerConfig(
|
||||||
|
@ -239,8 +241,7 @@ export class OptimizerConfig {
|
||||||
options.maxWorkerCount,
|
options.maxWorkerCount,
|
||||||
options.dist,
|
options.dist,
|
||||||
options.profileWebpack,
|
options.profileWebpack,
|
||||||
options.themeTags,
|
options.themeTags
|
||||||
readLimits()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,8 +255,7 @@ export class OptimizerConfig {
|
||||||
public readonly maxWorkerCount: number,
|
public readonly maxWorkerCount: number,
|
||||||
public readonly dist: boolean,
|
public readonly dist: boolean,
|
||||||
public readonly profileWebpack: boolean,
|
public readonly profileWebpack: boolean,
|
||||||
public readonly themeTags: ThemeTags,
|
public readonly themeTags: ThemeTags
|
||||||
public readonly limits: Limits
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getWorkerConfig(optimizerCacheKey: unknown): WorkerConfig {
|
getWorkerConfig(optimizerCacheKey: unknown): WorkerConfig {
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { materialize, mergeMap, dematerialize } from 'rxjs/operators';
|
|
||||||
import { CiStatsReporter, ToolingLog } from '@kbn/dev-utils';
|
|
||||||
|
|
||||||
import { OptimizerUpdate$ } from './run_optimizer';
|
|
||||||
import { OptimizerConfig, getMetrics } from './optimizer';
|
|
||||||
import { pipeClosure } from './common';
|
|
||||||
|
|
||||||
export function reportOptimizerStats(
|
|
||||||
reporter: CiStatsReporter,
|
|
||||||
config: OptimizerConfig,
|
|
||||||
log: ToolingLog
|
|
||||||
) {
|
|
||||||
return pipeClosure((update$: OptimizerUpdate$) =>
|
|
||||||
update$.pipe(
|
|
||||||
materialize(),
|
|
||||||
mergeMap(async (n) => {
|
|
||||||
if (n.kind === 'C') {
|
|
||||||
const metrics = getMetrics(log, config);
|
|
||||||
|
|
||||||
await reporter.metrics(metrics);
|
|
||||||
|
|
||||||
for (const metric of metrics) {
|
|
||||||
if (metric.limit != null && metric.value > metric.limit) {
|
|
||||||
const value = metric.value.toLocaleString();
|
|
||||||
const limit = metric.limit.toLocaleString();
|
|
||||||
log.warning(
|
|
||||||
`Metric [${metric.group}] for [${metric.id}] of [${value}] over the limit of [${limit}]`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n;
|
|
||||||
}),
|
|
||||||
dematerialize()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
108
packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts
Normal file
108
packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Path from 'path';
|
||||||
|
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import { RawSource } from 'webpack-sources';
|
||||||
|
import { CiStatsMetrics } from '@kbn/dev-utils';
|
||||||
|
|
||||||
|
import { Bundle } from '../common';
|
||||||
|
|
||||||
|
interface Asset {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IGNORED_EXTNAME = ['.map', '.br', '.gz'];
|
||||||
|
|
||||||
|
export class BundleMetricsPlugin {
|
||||||
|
constructor(private readonly bundle: Bundle) {}
|
||||||
|
|
||||||
|
public apply(compiler: webpack.Compiler) {
|
||||||
|
const { bundle } = this;
|
||||||
|
|
||||||
|
compiler.hooks.emit.tap('BundleMetricsPlugin', (compilation) => {
|
||||||
|
const assets = Object.entries(compilation.assets)
|
||||||
|
.map(
|
||||||
|
([name, source]: [string, any]): Asset => ({
|
||||||
|
name,
|
||||||
|
size: source.size(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.filter((asset) => {
|
||||||
|
const filename = Path.basename(asset.name);
|
||||||
|
if (filename.startsWith('.')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = Path.extname(filename);
|
||||||
|
if (IGNORED_EXTNAME.includes(ext)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const entryName = `${bundle.id}.${bundle.type}.js`;
|
||||||
|
const entry = assets.find((a) => a.name === entryName);
|
||||||
|
if (!entry) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to find bundle entry named [${entryName}] in [${bundle.outputDir}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkPrefix = `${bundle.id}.chunk.`;
|
||||||
|
const asyncChunks = assets.filter((a) => a.name.startsWith(chunkPrefix));
|
||||||
|
const miscFiles = assets.filter((a) => a !== entry && !asyncChunks.includes(a));
|
||||||
|
|
||||||
|
const sumSize = (files: Asset[]) => files.reduce((acc: number, a) => acc + a.size, 0);
|
||||||
|
|
||||||
|
const moduleCount = bundle.cache.getModuleCount();
|
||||||
|
if (moduleCount === undefined) {
|
||||||
|
throw new Error(`moduleCount wasn't populated by PopulateBundleCachePlugin`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleMetrics: CiStatsMetrics = [
|
||||||
|
{
|
||||||
|
group: `@kbn/optimizer bundle module count`,
|
||||||
|
id: bundle.id,
|
||||||
|
value: moduleCount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `page load bundle size`,
|
||||||
|
id: bundle.id,
|
||||||
|
value: entry.size,
|
||||||
|
limit: bundle.pageLoadAssetSizeLimit,
|
||||||
|
limitConfigPath: `packages/kbn-optimizer/limits.yml`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `async chunks size`,
|
||||||
|
id: bundle.id,
|
||||||
|
value: sumSize(asyncChunks),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `async chunk count`,
|
||||||
|
id: bundle.id,
|
||||||
|
value: asyncChunks.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `miscellaneous assets size`,
|
||||||
|
id: bundle.id,
|
||||||
|
value: sumSize(miscFiles),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const metricsSource = new RawSource(JSON.stringify(bundleMetrics, null, 2));
|
||||||
|
|
||||||
|
// see https://github.com/jantimon/html-webpack-plugin/blob/33d69f49e6e9787796402715d1b9cd59f80b628f/index.js#L266
|
||||||
|
// @ts-expect-error undocumented, used to add assets to the output
|
||||||
|
compilation.emitAsset('metrics.json', metricsSource);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
34
packages/kbn-optimizer/src/worker/emit_stats_plugin.ts
Normal file
34
packages/kbn-optimizer/src/worker/emit_stats_plugin.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Fs from 'fs';
|
||||||
|
import Path from 'path';
|
||||||
|
|
||||||
|
import webpack from 'webpack';
|
||||||
|
|
||||||
|
import { Bundle } from '../common';
|
||||||
|
|
||||||
|
export class EmitStatsPlugin {
|
||||||
|
constructor(private readonly bundle: Bundle) {}
|
||||||
|
|
||||||
|
public apply(compiler: webpack.Compiler) {
|
||||||
|
compiler.hooks.done.tap(
|
||||||
|
{
|
||||||
|
name: 'EmitStatsPlugin',
|
||||||
|
// run at the very end, ensure that it's after clean-webpack-plugin
|
||||||
|
stage: 10,
|
||||||
|
},
|
||||||
|
(stats) => {
|
||||||
|
Fs.writeFileSync(
|
||||||
|
Path.resolve(this.bundle.outputDir, 'stats.json'),
|
||||||
|
JSON.stringify(stats.toJson())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import webpack from 'webpack';
|
||||||
|
|
||||||
|
import Path from 'path';
|
||||||
|
import { inspect } from 'util';
|
||||||
|
|
||||||
|
import { Bundle, WorkerConfig, ascending, parseFilePath } from '../common';
|
||||||
|
import { BundleRefModule } from './bundle_ref_module';
|
||||||
|
import {
|
||||||
|
isExternalModule,
|
||||||
|
isNormalModule,
|
||||||
|
isIgnoredModule,
|
||||||
|
isConcatenatedModule,
|
||||||
|
getModulePath,
|
||||||
|
} from './webpack_helpers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sass-loader creates about a 40% overhead on the overall optimizer runtime, and
|
||||||
|
* so this constant is used to indicate to assignBundlesToWorkers() that there is
|
||||||
|
* extra work done in a bundle that has a lot of scss imports. The value is
|
||||||
|
* arbitrary and just intended to weigh the bundles so that they are distributed
|
||||||
|
* across mulitple workers on machines with lots of cores.
|
||||||
|
*/
|
||||||
|
const EXTRA_SCSS_WORK_UNITS = 100;
|
||||||
|
|
||||||
|
export class PopulateBundleCachePlugin {
|
||||||
|
constructor(private readonly workerConfig: WorkerConfig, private readonly bundle: Bundle) {}
|
||||||
|
|
||||||
|
public apply(compiler: webpack.Compiler) {
|
||||||
|
const { bundle, workerConfig } = this;
|
||||||
|
|
||||||
|
compiler.hooks.emit.tap(
|
||||||
|
{
|
||||||
|
name: 'PopulateBundleCachePlugin',
|
||||||
|
before: ['BundleMetricsPlugin'],
|
||||||
|
},
|
||||||
|
(compilation) => {
|
||||||
|
const bundleRefExportIds: string[] = [];
|
||||||
|
const referencedFiles = new Set<string>();
|
||||||
|
let moduleCount = 0;
|
||||||
|
let workUnits = compilation.fileDependencies.size;
|
||||||
|
|
||||||
|
if (bundle.manifestPath) {
|
||||||
|
referencedFiles.add(bundle.manifestPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const module of compilation.modules) {
|
||||||
|
if (isNormalModule(module)) {
|
||||||
|
moduleCount += 1;
|
||||||
|
const path = getModulePath(module);
|
||||||
|
const parsedPath = parseFilePath(path);
|
||||||
|
|
||||||
|
if (!parsedPath.dirs.includes('node_modules')) {
|
||||||
|
referencedFiles.add(path);
|
||||||
|
|
||||||
|
if (path.endsWith('.scss')) {
|
||||||
|
workUnits += EXTRA_SCSS_WORK_UNITS;
|
||||||
|
|
||||||
|
for (const depPath of module.buildInfo.fileDependencies) {
|
||||||
|
referencedFiles.add(depPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nmIndex = parsedPath.dirs.lastIndexOf('node_modules');
|
||||||
|
const isScoped = parsedPath.dirs[nmIndex + 1].startsWith('@');
|
||||||
|
referencedFiles.add(
|
||||||
|
Path.join(
|
||||||
|
parsedPath.root,
|
||||||
|
...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)),
|
||||||
|
'package.json'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module instanceof BundleRefModule) {
|
||||||
|
bundleRefExportIds.push(module.ref.exportId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isConcatenatedModule(module)) {
|
||||||
|
moduleCount += module.modules.length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExternalModule(module) || isIgnoredModule(module)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unexpected module type: ${inspect(module)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = Array.from(referencedFiles).sort(ascending((p) => p));
|
||||||
|
const mtimes = new Map(
|
||||||
|
files.map((path): [string, number | undefined] => {
|
||||||
|
try {
|
||||||
|
return [path, compiler.inputFileSystem.statSync(path)?.mtimeMs];
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code === 'ENOENT') {
|
||||||
|
return [path, undefined];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
bundle.cache.set({
|
||||||
|
bundleRefExportIds: bundleRefExportIds.sort(ascending((p) => p)),
|
||||||
|
optimizerCacheKey: workerConfig.optimizerCacheKey,
|
||||||
|
cacheKey: bundle.createCacheKey(files, mtimes),
|
||||||
|
moduleCount,
|
||||||
|
workUnits,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
|
||||||
|
// write the cache to the compilation so that it isn't cleaned by clean-webpack-plugin
|
||||||
|
bundle.cache.writeWebpackAsset(compilation);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,46 +8,16 @@
|
||||||
|
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
|
|
||||||
import Fs from 'fs';
|
|
||||||
import Path from 'path';
|
|
||||||
import { inspect } from 'util';
|
|
||||||
|
|
||||||
import webpack, { Stats } from 'webpack';
|
import webpack, { Stats } from 'webpack';
|
||||||
import * as Rx from 'rxjs';
|
import * as Rx from 'rxjs';
|
||||||
import { mergeMap, map, mapTo, takeUntil } from 'rxjs/operators';
|
import { mergeMap, map, mapTo, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import { CompilerMsgs, CompilerMsg, maybeMap, Bundle, WorkerConfig, BundleRefs } from '../common';
|
||||||
CompilerMsgs,
|
|
||||||
CompilerMsg,
|
|
||||||
maybeMap,
|
|
||||||
Bundle,
|
|
||||||
WorkerConfig,
|
|
||||||
ascending,
|
|
||||||
parseFilePath,
|
|
||||||
BundleRefs,
|
|
||||||
} from '../common';
|
|
||||||
import { BundleRefModule } from './bundle_ref_module';
|
|
||||||
import { getWebpackConfig } from './webpack.config';
|
import { getWebpackConfig } from './webpack.config';
|
||||||
import { isFailureStats, failedStatsToErrorMessage } from './webpack_helpers';
|
import { isFailureStats, failedStatsToErrorMessage } from './webpack_helpers';
|
||||||
import {
|
|
||||||
isExternalModule,
|
|
||||||
isNormalModule,
|
|
||||||
isIgnoredModule,
|
|
||||||
isConcatenatedModule,
|
|
||||||
getModulePath,
|
|
||||||
} from './webpack_helpers';
|
|
||||||
|
|
||||||
const PLUGIN_NAME = '@kbn/optimizer';
|
const PLUGIN_NAME = '@kbn/optimizer';
|
||||||
|
|
||||||
/**
|
|
||||||
* sass-loader creates about a 40% overhead on the overall optimizer runtime, and
|
|
||||||
* so this constant is used to indicate to assignBundlesToWorkers() that there is
|
|
||||||
* extra work done in a bundle that has a lot of scss imports. The value is
|
|
||||||
* arbitrary and just intended to weigh the bundles so that they are distributed
|
|
||||||
* across mulitple workers on machines with lots of cores.
|
|
||||||
*/
|
|
||||||
const EXTRA_SCSS_WORK_UNITS = 100;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an Observable<CompilerMsg> for a specific child compiler + bundle
|
* Create an Observable<CompilerMsg> for a specific child compiler + bundle
|
||||||
*/
|
*/
|
||||||
|
@ -80,13 +50,6 @@ const observeCompiler = (
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workerConfig.profileWebpack) {
|
|
||||||
Fs.writeFileSync(
|
|
||||||
Path.resolve(bundle.outputDir, 'stats.json'),
|
|
||||||
JSON.stringify(stats.toJson())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!workerConfig.watch) {
|
if (!workerConfig.watch) {
|
||||||
process.nextTick(() => done$.next());
|
process.nextTick(() => done$.next());
|
||||||
}
|
}
|
||||||
|
@ -97,88 +60,11 @@ const observeCompiler = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const bundleRefExportIds: string[] = [];
|
const moduleCount = bundle.cache.getModuleCount();
|
||||||
const referencedFiles = new Set<string>();
|
if (moduleCount === undefined) {
|
||||||
let moduleCount = 0;
|
throw new Error(`moduleCount wasn't populated by PopulateBundleCachePlugin`);
|
||||||
let workUnits = stats.compilation.fileDependencies.size;
|
|
||||||
|
|
||||||
if (bundle.manifestPath) {
|
|
||||||
referencedFiles.add(bundle.manifestPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const module of stats.compilation.modules) {
|
|
||||||
if (isNormalModule(module)) {
|
|
||||||
moduleCount += 1;
|
|
||||||
const path = getModulePath(module);
|
|
||||||
const parsedPath = parseFilePath(path);
|
|
||||||
|
|
||||||
if (!parsedPath.dirs.includes('node_modules')) {
|
|
||||||
referencedFiles.add(path);
|
|
||||||
|
|
||||||
if (path.endsWith('.scss')) {
|
|
||||||
workUnits += EXTRA_SCSS_WORK_UNITS;
|
|
||||||
|
|
||||||
for (const depPath of module.buildInfo.fileDependencies) {
|
|
||||||
referencedFiles.add(depPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nmIndex = parsedPath.dirs.lastIndexOf('node_modules');
|
|
||||||
const isScoped = parsedPath.dirs[nmIndex + 1].startsWith('@');
|
|
||||||
referencedFiles.add(
|
|
||||||
Path.join(
|
|
||||||
parsedPath.root,
|
|
||||||
...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)),
|
|
||||||
'package.json'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module instanceof BundleRefModule) {
|
|
||||||
bundleRefExportIds.push(module.ref.exportId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isConcatenatedModule(module)) {
|
|
||||||
moduleCount += module.modules.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isExternalModule(module) || isIgnoredModule(module)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unexpected module type: ${inspect(module)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = Array.from(referencedFiles).sort(ascending((p) => p));
|
|
||||||
const mtimes = new Map(
|
|
||||||
files.map((path): [string, number | undefined] => {
|
|
||||||
try {
|
|
||||||
return [path, compiler.inputFileSystem.statSync(path)?.mtimeMs];
|
|
||||||
} catch (error) {
|
|
||||||
if (error?.code === 'ENOENT') {
|
|
||||||
return [path, undefined];
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
bundle.cache.set({
|
|
||||||
bundleRefExportIds: bundleRefExportIds.sort(ascending((p) => p)),
|
|
||||||
optimizerCacheKey: workerConfig.optimizerCacheKey,
|
|
||||||
cacheKey: bundle.createCacheKey(files, mtimes),
|
|
||||||
moduleCount,
|
|
||||||
workUnits,
|
|
||||||
files,
|
|
||||||
});
|
|
||||||
|
|
||||||
return compilerMsgs.compilerSuccess({
|
return compilerMsgs.compilerSuccess({
|
||||||
moduleCount,
|
moduleCount,
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,9 @@ import * as UiSharedDeps from '@kbn/ui-shared-deps';
|
||||||
|
|
||||||
import { Bundle, BundleRefs, WorkerConfig } from '../common';
|
import { Bundle, BundleRefs, WorkerConfig } from '../common';
|
||||||
import { BundleRefsPlugin } from './bundle_refs_plugin';
|
import { BundleRefsPlugin } from './bundle_refs_plugin';
|
||||||
|
import { BundleMetricsPlugin } from './bundle_metrics_plugin';
|
||||||
|
import { EmitStatsPlugin } from './emit_stats_plugin';
|
||||||
|
import { PopulateBundleCachePlugin } from './populate_bundle_cache_plugin';
|
||||||
|
|
||||||
const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset');
|
const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset');
|
||||||
|
|
||||||
|
@ -65,6 +68,9 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker:
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin(),
|
new CleanWebpackPlugin(),
|
||||||
new BundleRefsPlugin(bundle, bundleRefs),
|
new BundleRefsPlugin(bundle, bundleRefs),
|
||||||
|
new PopulateBundleCachePlugin(worker, bundle),
|
||||||
|
new BundleMetricsPlugin(bundle),
|
||||||
|
...(worker.profileWebpack ? [new EmitStatsPlugin(bundle)] : []),
|
||||||
...(bundle.banner ? [new webpack.BannerPlugin({ banner: bundle.banner, raw: true })] : []),
|
...(bundle.banner ? [new webpack.BannerPlugin({ banner: bundle.banner, raw: true })] : []),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -74,13 +74,14 @@ it('builds a generated plugin into a viable archive', async () => {
|
||||||
|
|
||||||
await extract(PLUGIN_ARCHIVE, { dir: TMP_DIR });
|
await extract(PLUGIN_ARCHIVE, { dir: TMP_DIR });
|
||||||
|
|
||||||
const files = await globby(['**/*'], { cwd: TMP_DIR });
|
const files = await globby(['**/*'], { cwd: TMP_DIR, dot: true });
|
||||||
files.sort((a, b) => a.localeCompare(b));
|
files.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
expect(files).toMatchInlineSnapshot(`
|
expect(files).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
"kibana/fooTestPlugin/common/index.js",
|
"kibana/fooTestPlugin/common/index.js",
|
||||||
"kibana/fooTestPlugin/kibana.json",
|
"kibana/fooTestPlugin/kibana.json",
|
||||||
|
"kibana/fooTestPlugin/node_modules/.yarn-integrity",
|
||||||
"kibana/fooTestPlugin/package.json",
|
"kibana/fooTestPlugin/package.json",
|
||||||
"kibana/fooTestPlugin/server/index.js",
|
"kibana/fooTestPlugin/server/index.js",
|
||||||
"kibana/fooTestPlugin/server/plugin.js",
|
"kibana/fooTestPlugin/server/plugin.js",
|
||||||
|
|
|
@ -34,9 +34,15 @@ export async function optimize({ log, plugin, sourceDir, buildDir }: BuildContex
|
||||||
pluginScanDirs: [],
|
pluginScanDirs: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const target = Path.resolve(sourceDir, 'target');
|
||||||
|
|
||||||
await runOptimizer(config).pipe(logOptimizerState(log, config)).toPromise();
|
await runOptimizer(config).pipe(logOptimizerState(log, config)).toPromise();
|
||||||
|
|
||||||
|
// clean up unnecessary files
|
||||||
|
Fs.unlinkSync(Path.resolve(target, 'public/metrics.json'));
|
||||||
|
Fs.unlinkSync(Path.resolve(target, 'public/.kbn-optimizer-cache'));
|
||||||
|
|
||||||
// move target into buildDir
|
// move target into buildDir
|
||||||
await asyncRename(Path.resolve(sourceDir, 'target'), Path.resolve(buildDir, 'target'));
|
await asyncRename(target, Path.resolve(buildDir, 'target'));
|
||||||
log.indent(-2);
|
log.indent(-2);
|
||||||
}
|
}
|
||||||
|
|
10
scripts/ship_ci_stats.js
Normal file
10
scripts/ship_ci_stats.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('../src/setup_node_env/no_transpilation');
|
||||||
|
require('@kbn/dev-utils').shipCiStatsCli();
|
|
@ -6,20 +6,18 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { REPO_ROOT } from '@kbn/utils';
|
import Path from 'path';
|
||||||
import { CiStatsReporter } from '@kbn/dev-utils';
|
|
||||||
import {
|
|
||||||
runOptimizer,
|
|
||||||
OptimizerConfig,
|
|
||||||
logOptimizerState,
|
|
||||||
reportOptimizerStats,
|
|
||||||
} from '@kbn/optimizer';
|
|
||||||
|
|
||||||
import { Task } from '../lib';
|
import { REPO_ROOT } from '@kbn/utils';
|
||||||
|
import { lastValueFrom } from '@kbn/std';
|
||||||
|
import { CiStatsMetrics } from '@kbn/dev-utils';
|
||||||
|
import { runOptimizer, OptimizerConfig, logOptimizerState } from '@kbn/optimizer';
|
||||||
|
|
||||||
|
import { Task, deleteAll, write, read } from '../lib';
|
||||||
|
|
||||||
export const BuildKibanaPlatformPlugins: Task = {
|
export const BuildKibanaPlatformPlugins: Task = {
|
||||||
description: 'Building distributable versions of Kibana platform plugins',
|
description: 'Building distributable versions of Kibana platform plugins',
|
||||||
async run(_, log, build) {
|
async run(buildConfig, log, build) {
|
||||||
const config = OptimizerConfig.create({
|
const config = OptimizerConfig.create({
|
||||||
repoRoot: REPO_ROOT,
|
repoRoot: REPO_ROOT,
|
||||||
outputRoot: build.resolvePath(),
|
outputRoot: build.resolvePath(),
|
||||||
|
@ -31,12 +29,27 @@ export const BuildKibanaPlatformPlugins: Task = {
|
||||||
includeCoreBundle: true,
|
includeCoreBundle: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reporter = CiStatsReporter.fromEnv(log);
|
await lastValueFrom(runOptimizer(config).pipe(logOptimizerState(log, config)));
|
||||||
|
|
||||||
await runOptimizer(config)
|
const combinedMetrics: CiStatsMetrics = [];
|
||||||
.pipe(reportOptimizerStats(reporter, config, log), logOptimizerState(log, config))
|
const metricFilePaths: string[] = [];
|
||||||
.toPromise();
|
for (const bundle of config.bundles) {
|
||||||
|
const path = Path.resolve(bundle.outputDir, 'metrics.json');
|
||||||
|
const metrics: CiStatsMetrics = JSON.parse(await read(path));
|
||||||
|
combinedMetrics.push(...metrics);
|
||||||
|
metricFilePaths.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write combined metrics to target
|
||||||
|
await write(
|
||||||
|
buildConfig.resolveFromTarget('optimizer_bundle_metrics.json'),
|
||||||
|
JSON.stringify(combinedMetrics, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete all metric files
|
||||||
|
await deleteAll(metricFilePaths, log);
|
||||||
|
|
||||||
|
// delete all bundle cache files
|
||||||
await Promise.all(config.bundles.map((b) => b.cache.clear()));
|
await Promise.all(config.bundles.map((b) => b.cache.clear()));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,10 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh"
|
||||||
|
|
||||||
echo " -> building and extracting OSS Kibana distributable for use in functional tests"
|
echo " -> building and extracting OSS Kibana distributable for use in functional tests"
|
||||||
node scripts/build --debug --oss
|
node scripts/build --debug --oss
|
||||||
|
|
||||||
|
echo " -> shipping metrics from build to ci-stats"
|
||||||
|
node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json
|
||||||
|
|
||||||
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
|
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
|
||||||
installDir="$PARENT_DIR/install/kibana"
|
installDir="$PARENT_DIR/install/kibana"
|
||||||
mkdir -p "$installDir"
|
mkdir -p "$installDir"
|
||||||
|
|
|
@ -15,5 +15,8 @@ node scripts/ensure_all_tests_in_ci_group;
|
||||||
echo " -> building and extracting OSS Kibana distributable for use in functional tests"
|
echo " -> building and extracting OSS Kibana distributable for use in functional tests"
|
||||||
node scripts/build --debug --oss
|
node scripts/build --debug --oss
|
||||||
|
|
||||||
|
echo " -> shipping metrics from build to ci-stats"
|
||||||
|
node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json
|
||||||
|
|
||||||
mkdir -p "$WORKSPACE/kibana-build-oss"
|
mkdir -p "$WORKSPACE/kibana-build-oss"
|
||||||
cp -pR build/oss/kibana-*-SNAPSHOT-linux-x86_64/. $WORKSPACE/kibana-build-oss/
|
cp -pR build/oss/kibana-*-SNAPSHOT-linux-x86_64/. $WORKSPACE/kibana-build-oss/
|
||||||
|
|
|
@ -6,6 +6,10 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh"
|
||||||
echo " -> building and extracting default Kibana distributable"
|
echo " -> building and extracting default Kibana distributable"
|
||||||
cd "$KIBANA_DIR"
|
cd "$KIBANA_DIR"
|
||||||
node scripts/build --debug --no-oss
|
node scripts/build --debug --no-oss
|
||||||
|
|
||||||
|
echo " -> shipping metrics from build to ci-stats"
|
||||||
|
node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json
|
||||||
|
|
||||||
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
|
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
|
||||||
installDir="$KIBANA_DIR/install/kibana"
|
installDir="$KIBANA_DIR/install/kibana"
|
||||||
mkdir -p "$installDir"
|
mkdir -p "$installDir"
|
||||||
|
|
|
@ -30,6 +30,10 @@ node scripts/functional_tests --assert-none-excluded \
|
||||||
echo " -> building and extracting default Kibana distributable for use in functional tests"
|
echo " -> building and extracting default Kibana distributable for use in functional tests"
|
||||||
cd "$KIBANA_DIR"
|
cd "$KIBANA_DIR"
|
||||||
node scripts/build --debug --no-oss
|
node scripts/build --debug --no-oss
|
||||||
|
|
||||||
|
echo " -> shipping metrics from build to ci-stats"
|
||||||
|
node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json
|
||||||
|
|
||||||
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
|
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
|
||||||
installDir="$KIBANA_DIR/install/kibana"
|
installDir="$KIBANA_DIR/install/kibana"
|
||||||
mkdir -p "$installDir"
|
mkdir -p "$installDir"
|
||||||
|
|
|
@ -6803,7 +6803,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/webpack" "*"
|
"@types/webpack" "*"
|
||||||
|
|
||||||
"@types/webpack-sources@*":
|
"@types/webpack-sources@*", "@types/webpack-sources@^0.1.4":
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.5.tgz#be47c10f783d3d6efe1471ff7f042611bd464a92"
|
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.5.tgz#be47c10f783d3d6efe1471ff7f042611bd464a92"
|
||||||
integrity sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==
|
integrity sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue