ingest performance metrics to ci-stats (#134792)

This commit is contained in:
Baturalp Gurdin 2022-06-23 00:44:11 +02:00 committed by GitHub
parent b41bc6643d
commit e5d73a1169
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 538 additions and 0 deletions

View file

@ -0,0 +1,124 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "kbn-ci-stats-performance-metrics"
PKG_REQUIRE_NAME = "@kbn/ci-stats-performance-metrics"
SOURCE_FILES = glob(
[
"src/**/*.ts",
],
exclude = [
"**/*.test.*",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
# In this array place runtime dependencies, including other packages and NPM packages
# which must be available for this code to run.
#
# To reference other packages use:
# "//repo/relative/path/to/package"
# eg. "//packages/kbn-utils"
#
# To reference a NPM package use:
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
"//packages/kbn-dev-cli-errors",
"//packages/kbn-dev-cli-runner",
"//packages/kbn-test",
"//packages/kbn-tooling-log",
"//packages/kbn-ci-stats-reporter",
]
# In this array place dependencies necessary to build the types, which will include the
# :npm_module_types target of other packages and packages from NPM, including @types/*
# packages.
#
# To reference the types for another package use:
# "//repo/relative/path/to/package:npm_module_types"
# eg. "//packages/kbn-utils:npm_module_types"
#
# References to NPM packages work the same as RUNTIME_DEPS
TYPES_DEPS = [
"//packages/kbn-dev-cli-errors:npm_module_types",
"//packages/kbn-dev-cli-runner:npm_module_types",
"//packages/kbn-test:npm_module_types",
"//packages/kbn-tooling-log:npm_module_types",
"//packages/kbn-ci-stats-reporter:npm_module_types",
"@npm//@types/node",
"@npm//@types/jest",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
root_dir = "src",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm_types(
name = "npm_module_types",
srcs = SRCS,
deps = [":tsc_types"],
package_name = PKG_REQUIRE_NAME,
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
filegroup(
name = "build_types",
srcs = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,3 @@
# @kbn/ci-stats-performance-metrics
Empty package generated by @kbn/generate

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-ci-stats-performance-metrics'],
};

View file

@ -0,0 +1,7 @@
{
"name": "@kbn/ci-stats-performance-metrics",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -0,0 +1,98 @@
/*
* 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 axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { ToolingLog } from '@kbn/tooling-log';
import { getYearAgoIso } from './utils';
type Environment = 'ENVIRONMENT_ALL' | 'ci' | 'development';
type LatencyAggregationType = 'avg' | 'p95' | 'p99';
type TransactionType = 'page-load' | 'app-change' | 'user-interaction' | 'http-request';
interface MainStatisticsRequestOptions {
ciBuildId: string;
start?: string;
end?: string;
environment?: Environment;
transactionType?: TransactionType;
latencyAggregationType?: LatencyAggregationType;
}
export interface TransactionGroup {
name: string;
latency: number;
throughput: number;
errorRate?: any;
impact: number;
transactionType: TransactionType;
}
export interface MainStatisticsResponse {
transactionGroups: TransactionGroup[];
isAggregationAccurate: boolean;
bucketSize: number;
}
const DEFAULT_BASE_URL =
'https://kibana-ops-e2e-perf.kb.us-central1.gcp.cloud.es.io:9243/internal/apm';
const DEFAULT_CLIENT_TIMEOUT = 120 * 1000;
export class ApmClient {
private readonly client: AxiosInstance;
private readonly logger: ToolingLog;
constructor(config: AxiosRequestConfig, logger: ToolingLog) {
const { baseURL = DEFAULT_BASE_URL, timeout = DEFAULT_CLIENT_TIMEOUT, auth } = config;
this.client = axios.create({
auth,
baseURL,
timeout,
});
this.logger = logger || console;
}
public get baseUrl(): string | undefined {
return this.client.defaults.baseURL;
}
public async mainStatistics(queryParams: MainStatisticsRequestOptions) {
const { now, yearAgo } = getYearAgoIso();
const {
ciBuildId,
start = yearAgo,
end = now,
environment = 'ENVIRONMENT_ALL',
transactionType = 'page-load',
latencyAggregationType = 'avg',
} = queryParams;
try {
const responseRaw = await this.client.get<MainStatisticsResponse>(
`services/kibana-frontend/transactions/groups/main_statistics`,
{
params: {
kuery: `labels.ciBuildId:${ciBuildId}`,
environment,
start,
end,
transactionType,
latencyAggregationType,
},
}
);
return responseRaw.data;
} catch (error) {
this.logger.error(
`Error fetching main statistics from APM, ci build ${ciBuildId}, error message ${error.message}`
);
}
}
}

View file

@ -0,0 +1,81 @@
/*
* 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.
*/
/** ***********************************************************
*
* Run `node scripts/extract_performance_testing_dataset --help` for usage information
*
*************************************************************/
import { run } from '@kbn/dev-cli-runner';
import { createFlagError } from '@kbn/dev-cli-errors';
import { reporter } from './reporter';
export async function runCli() {
run(
async ({ log, flags }) => {
const apmBaseUrl = flags['apm-url'];
if (apmBaseUrl && typeof apmBaseUrl !== 'string') {
throw createFlagError('--apm-url must be a string');
}
if (!apmBaseUrl) {
throw createFlagError('--apm-url must be defined');
}
const apmUsername = flags['apm-username'];
if (apmUsername && typeof apmUsername !== 'string') {
throw createFlagError('--apm-username must be a string');
}
if (!apmUsername) {
throw createFlagError('--apm-username must be defined');
}
const apmPassword = flags['apm-password'];
if (apmPassword && typeof apmPassword !== 'string') {
throw createFlagError('--apm-password must be a string');
}
if (!apmPassword) {
throw createFlagError('--apm-password must be defined');
}
const buildId = flags.buildId;
if (buildId && typeof buildId !== 'string') {
throw createFlagError('--buildId must be a string');
}
if (!buildId) {
throw createFlagError('--buildId must be defined');
}
return reporter({
apmClient: {
auth: {
username: apmUsername,
password: apmPassword,
},
baseURL: apmBaseUrl,
},
param: {
ciBuildId: buildId,
},
log,
});
},
{
description: `CLI to fetch performance metrics and report those to ci-stats`,
flags: {
string: ['buildId', 'apm-url', 'apm-username', 'apm-password'],
help: `
--buildId BUILDKITE_JOB_ID or uuid generated locally, stored in APM-based document as label: 'labels.testBuildId'
--apm-url url for APM cluster
--apm-username username for Apm
--apm-password password for Apm
`,
},
}
);
}

View 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.
*/
export { reporter } from './reporter';
export { runCli } from './cli';

View file

@ -0,0 +1,56 @@
/*
* 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 { ToolingLog } from '@kbn/tooling-log';
import { CiStatsReporter } from '@kbn/ci-stats-reporter';
import { ApmClient } from './apm_client';
interface ReporterOptions {
param: {
ciBuildId: string;
};
apmClient: {
baseURL: string;
auth: {
username: string;
password: string;
};
};
log: ToolingLog;
}
export async function reporter(options: ReporterOptions) {
const {
param: { ciBuildId },
apmClient: apmClientOptions,
log,
} = options;
const apm = new ApmClient(apmClientOptions, log);
const performanceMainStats = await apm.mainStatistics({ ciBuildId });
if (performanceMainStats) {
const { transactionGroups: tg } = performanceMainStats;
const loginStats = tg.find((e) => e.name === '/login');
const appHomeStats = tg.find((e) => e.name === '/app/home');
const appDashboardsStats = tg.find((e) => e.name === '/app/dashboards');
const ciStatsReporter = CiStatsReporter.fromEnv(log);
const body = {
...(loginStats && { page_load_login: loginStats.latency }),
...(appHomeStats && { page_load_app_home: appHomeStats.latency }),
...(appDashboardsStats && { page_load_app_dashboards: appDashboardsStats.latency }),
};
await ciStatsReporter.reportPerformanceMetrics(body);
}
}

View file

@ -0,0 +1,19 @@
/*
* 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.
*/
export function getYearAgoIso() {
const d = new Date();
const nowIso = d.toISOString();
d.setMonth(d.getMonth() - 12);
const yearAgoIso = d.toISOString();
return {
now: nowIso,
yearAgo: yearAgoIso,
};
}

View file

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"rootDir": "src",
"stripInternal": false,
"types": [
"jest",
"node"
]
},
"include": [
"src/**/*"
]
}