Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect more build metrics #65408

Merged
merged 22 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4333116
[CiStatsReporter] add metrics() method
spalger May 6, 2020
80429b8
[kbn/optimizer] report module counts of each bundle when building
spalger May 6, 2020
bee5d15
report size of each distributable asset
spalger May 6, 2020
a9c6a1b
Merge branch 'master' of github.com:elastic/kibana into implement/mor…
spalger May 6, 2020
ada8782
use named params, actual private method
spalger May 6, 2020
09667b8
rename metric param names
spalger May 6, 2020
ac47850
Merge branch 'master' of github.com:elastic/kibana into implement/mor…
spalger May 6, 2020
a9e83b7
remove unused import
spalger May 6, 2020
5e122d2
report oss and default distributable sizes in separate groups
spalger May 6, 2020
5fa047e
Merge branch 'master' of github.com:elastic/kibana into implement/mor…
spalger May 6, 2020
6d63d50
implement v1 api, remove single metric reporting method
spalger May 6, 2020
0988afa
quick cleanup
spalger May 6, 2020
52b1c6d
Merge branch 'master' of github.com:elastic/kibana into implement/mor…
spalger May 6, 2020
d695de9
Send buildId in body of metrics request
spalger May 6, 2020
ada870f
remove unused abstraction
spalger May 6, 2020
0e97c3c
remove unused import
spalger May 6, 2020
f4127f6
test branch of pipeline-library
spalger May 6, 2020
b62f32f
update kbn/pm dist
spalger May 6, 2020
40b392b
remove use of pipeline-library branch
spalger May 6, 2020
b30ccd5
Merge branch 'master' of github.com:elastic/kibana into implement/mor…
spalger May 6, 2020
b89f06a
Merge branch 'master' into implement/more-build-metrics
elasticmachine May 7, 2020
b820bec
Merge branch 'master' into implement/more-build-metrics
elasticmachine May 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/kbn-dev-utils/src/ci_stats_reporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This class integrates with the `ciStats.trackBuild {}` Jenkins Pipeline function

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

#### `CiStatsReporter#metric(name: string, subName: string, value: number)`
#### `CiStatsReporter#metrics(metrics: Array<{ group: string, id: string, value: number }>)`

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

Expand All @@ -19,5 +19,11 @@ import { CiStatsReporter, ToolingLog } from '@kbn/dev-utils';

const log = new ToolingLog(...);
const reporter = CiStatsReporter.fromEnv(log)
reporter.metric('Build speed', specificBuildName, timeToRunBuild)
reporter.metrics([
{
group: 'Build size',
id: specificBuildName,
value: sizeOfBuild
}
])
```
19 changes: 9 additions & 10 deletions packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,32 +84,31 @@ export class CiStatsReporter {
return !!this.config;
}

async metric(name: string, subName: string, value: number) {
async metrics(metrics: Array<{ group: string; id: string; value: number }>) {
if (!this.config) {
return;
}

let attempt = 0;
const maxAttempts = 5;
const bodySummary = metrics
.map(({ group, id, value }) => `[${group}/${id}=${value}]`)
.join(' ');

while (true) {
attempt += 1;

try {
await Axios.request({
method: 'POST',
url: '/metric',
url: '/v1/metrics',
baseURL: this.config.apiUrl,
params: {
buildId: this.config.buildId,
},
headers: {
Authorization: `token ${this.config.apiToken}`,
},
data: {
name,
subName,
value,
buildId: this.config.buildId,
metrics,
},
});

Expand All @@ -125,14 +124,14 @@ export class CiStatsReporter {
this.log.warning(
`error recording metric [status=${error.response.status}] [resp=${inspect(
error.response.data
)}] [${name}/${subName}=${value}]`
)}] ${bodySummary}`
);
return;
}

if (attempt === maxAttempts) {
this.log.warning(
`failed to reach kibana-ci-stats service too many times, unable to record metric [${name}/${subName}=${value}]`
`failed to reach kibana-ci-stats service too many times, unable to record metric ${bodySummary}`
);
return;
}
Expand Down
30 changes: 20 additions & 10 deletions packages/kbn-optimizer/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import 'source-map-support/register';

import Path from 'path';

import { run, REPO_ROOT, createFlagError, createFailError, CiStatsReporter } from '@kbn/dev-utils';
import { run, REPO_ROOT, createFlagError, CiStatsReporter } from '@kbn/dev-utils';

import { logOptimizerState } from './log_optimizer_state';
import { OptimizerConfig } from './optimizer';
Expand Down Expand Up @@ -82,9 +82,9 @@ run(
throw createFlagError('expected --scan-dir to be a string');
}

const reportStatsName = flags['report-stats'];
if (reportStatsName !== undefined && typeof reportStatsName !== 'string') {
throw createFlagError('expected --report-stats to be a string');
const reportStats = flags['report-stats'] ?? false;
if (typeof reportStats !== 'boolean') {
throw createFlagError('expected --report-stats to have no value');
}

const config = OptimizerConfig.create({
Expand All @@ -103,22 +103,32 @@ run(

let update$ = runOptimizer(config);

if (reportStatsName) {
if (reportStats) {
const reporter = CiStatsReporter.fromEnv(log);

if (!reporter.isEnabled()) {
throw createFailError('Unable to initialize CiStatsReporter from env');
log.warning('Unable to initialize CiStatsReporter from env');
}

update$ = update$.pipe(reportOptimizerStats(reporter, reportStatsName));
update$ = update$.pipe(reportOptimizerStats(reporter, config));
}

await update$.pipe(logOptimizerState(log, config)).toPromise();
},
{
flags: {
boolean: ['core', 'watch', 'oss', 'examples', 'dist', 'cache', 'profile', 'inspect-workers'],
string: ['workers', 'scan-dir', 'report-stats'],
boolean: [
'core',
'watch',
'oss',
'examples',
'dist',
'cache',
'profile',
'inspect-workers',
'report-stats',
],
string: ['workers', 'scan-dir'],
default: {
core: true,
examples: true,
Expand All @@ -136,7 +146,7 @@ run(
--dist create bundles that are suitable for inclusion in the Kibana distributable
--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
--report-stats=[name] attempt to report stats about this execution of the build to the kibana-ci-stats service using this name
--report-stats attempt to report stats about this execution of the build to the kibana-ci-stats service using this name
`,
},
}
Expand Down
17 changes: 14 additions & 3 deletions packages/kbn-optimizer/src/report_optimizer_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import { materialize, mergeMap, dematerialize } from 'rxjs/operators';
import { CiStatsReporter } from '@kbn/dev-utils';

import { OptimizerUpdate$ } from './run_optimizer';
import { OptimizerState } from './optimizer';
import { OptimizerState, OptimizerConfig } from './optimizer';
import { pipeClosure } from './common';

export function reportOptimizerStats(reporter: CiStatsReporter, name: string) {
export function reportOptimizerStats(reporter: CiStatsReporter, config: OptimizerConfig) {
return pipeClosure((update$: OptimizerUpdate$) => {
let lastState: OptimizerState | undefined;
return update$.pipe(
Expand All @@ -35,7 +35,18 @@ export function reportOptimizerStats(reporter: CiStatsReporter, name: string) {
}

if (n.kind === 'C' && lastState) {
await reporter.metric('@kbn/optimizer build time', name, lastState.durSec);
await reporter.metrics(
config.bundles.map(bundle => {
// make the cache read from the cache file since it was likely updated by the worker
bundle.cache.refresh();

return {
group: `@kbn/optimizer bundle module count`,
id: bundle.id,
value: bundle.cache.getModuleCount() || 0,
mshustov marked this conversation as resolved.
Show resolved Hide resolved
};
})
);
}

return n;
Expand Down
19 changes: 9 additions & 10 deletions packages/kbn-pm/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43933,30 +43933,29 @@ class CiStatsReporter {
isEnabled() {
return !!this.config;
}
async metric(name, subName, value) {
async metrics(metrics) {
var _a, _b, _c, _d;
if (!this.config) {
return;
}
let attempt = 0;
const maxAttempts = 5;
const bodySummary = metrics
.map(({ group, id, value }) => `[${group}/${id}=${value}]`)
.join(' ');
while (true) {
attempt += 1;
try {
await axios_1.default.request({
method: 'POST',
url: '/metric',
url: '/v1/metrics',
baseURL: this.config.apiUrl,
params: {
buildId: this.config.buildId,
},
headers: {
Authorization: `token ${this.config.apiToken}`,
},
data: {
name,
subName,
value,
buildId: this.config.buildId,
metrics,
},
});
return;
Expand All @@ -43968,11 +43967,11 @@ class CiStatsReporter {
}
if (((_b = error) === null || _b === void 0 ? void 0 : _b.response) && error.response.status !== 502) {
// error response from service was received so warn the user and move on
this.log.warning(`error recording metric [status=${error.response.status}] [resp=${util_1.inspect(error.response.data)}] [${name}/${subName}=${value}]`);
this.log.warning(`error recording metric [status=${error.response.status}] [resp=${util_1.inspect(error.response.data)}] ${bodySummary}`);
return;
}
if (attempt === maxAttempts) {
this.log.warning(`failed to reach kibana-ci-stats service too many times, unable to record metric [${name}/${subName}=${value}]`);
this.log.warning(`failed to reach kibana-ci-stats service too many times, unable to record metric ${bodySummary}`);
return;
}
// we failed to reach the backend and we have remaining attempts, lets retry after a short delay
Expand Down
3 changes: 1 addition & 2 deletions src/dev/build/tasks/build_kibana_platform_plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,10 @@ export const BuildKibanaPlatformPluginsTask = {
});

const reporter = CiStatsReporter.fromEnv(log);
const reportStatsName = build.isOss() ? 'oss distributable' : 'default distributable';

await runOptimizer(optimizerConfig)
.pipe(
reportOptimizerStats(reporter, reportStatsName),
reportOptimizerStats(reporter, optimizerConfig),
logOptimizerState(log, optimizerConfig)
)
.toPromise();
Expand Down
40 changes: 37 additions & 3 deletions src/dev/build/tasks/create_archives_task.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,38 @@
* under the License.
*/

import path from 'path';
import Path from 'path';
import Fs from 'fs';
import { promisify } from 'util';

import { CiStatsReporter } from '@kbn/dev-utils';

import { mkdirp, compress } from '../lib';

const asyncStat = promisify(Fs.stat);

export const CreateArchivesTask = {
description: 'Creating the archives for each platform',

async run(config, log, build) {
const archives = [];

// archive one at a time, parallel causes OOM sometimes
for (const platform of config.getTargetPlatforms()) {
const source = build.resolvePathForPlatform(platform, '.');
const destination = build.getPlatformArchivePath(platform);

log.info('archiving', source, 'to', destination);

await mkdirp(path.dirname(destination));
await mkdirp(Path.dirname(destination));

switch (path.extname(destination)) {
switch (Path.extname(destination)) {
case '.zip':
archives.push({
format: 'zip',
path: destination,
});

await compress(
'zip',
{
Expand All @@ -51,6 +65,11 @@ export const CreateArchivesTask = {
break;

case '.gz':
archives.push({
format: 'tar',
path: destination,
});

await compress(
'tar',
{
Expand All @@ -71,5 +90,20 @@ export const CreateArchivesTask = {
throw new Error(`Unexpected extension for archive destination: ${destination}`);
}
}

const reporter = CiStatsReporter.fromEnv(log);
if (reporter.isEnabled()) {
await reporter.metrics(
await Promise.all(
archives.map(async ({ format, path }) => {
return {
group: `${build.isOss() ? 'oss ' : ''}distributable size`,
id: format,
value: (await asyncStat(path)).size,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it collect stats per archive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the basename of each archive is unique and includes things like the os, architecture, and archive format. We currently only create archives for the current platform on CI but the change in size should be pretty consistent between the distributable for each platform so I'm not particularly concerned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should standardize on some formats, ala ECS. For instance, version and platform would be easier to search for in this instance than having to figure out what the filename is. It also allows for us to apply a filter to a dashboard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think searching by version or platform will be very useful here though. And I'm not opposed to using a standardized format, but ECS doesn't feel appropriate for the intermediate representation we're creating here.

If anything I think we might want our groups to be

${oss ? 'oss ' : ''}distributable size

and our metric ids to be the archive type. Version/branch information is really a property of the job and less a property of the metric.

The way I think of the data we're collecting here is how it will look as a table formatted as:

${group}

id value Change from {mergeBase}
${id} ${valueAsMB} ${difference}

Once we're done with getting tables like that reporting on PRs we will work on putting this data into a format compatible with searching/filtering by summarizing the data into a single document and then indexing it into kibana-stats. With that it will be easy to have something like a chart of the average "distributable size" broken down by archive type, base branch, and spreading it out over time. That said, slower and longer term reporting/trends is something we plan to tackle after we get reporting into each PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented alternate metric group/id in 5e122d2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sync'd with Spencer on this - the portion I was missing was the summary document will contain the version/etc. 👍

Copy link
Contributor

@mshustov mshustov May 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If what format & units we report size? I hope we won't have to convert GB <--> MB in the storage 😅
ok, it's in bytes https://nodejs.org/api/fs.html#fs_stats_size

};
})
)
);
}
},
};