mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[packages] add kbn-scalability-simulation-generator package (#132631)
* add kbn-scalability-simulation-generator package * update codeowners Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
40acfe365c
commit
6f5b23b221
17 changed files with 757 additions and 0 deletions
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
|
@ -255,6 +255,10 @@
|
|||
#CC# /packages/kbn-expect/ @elastic/kibana-operations
|
||||
/.buildkite/ @elastic/kibana-operations
|
||||
|
||||
# Scalability testing
|
||||
/packages/kbn-performance-testing-dataset-extractor/ @elastic/kibana-scalability-testing
|
||||
/packages/kbn-scalability-simulation-generator/ @elastic/kibana-scalability-testing
|
||||
|
||||
# Quality Assurance
|
||||
/src/dev/code_coverage @elastic/kibana-qa
|
||||
/vars/*Coverage.groovy @elastic/kibana-qa
|
||||
|
|
|
@ -536,6 +536,7 @@
|
|||
"@kbn/plugin-generator": "link:bazel-bin/packages/kbn-plugin-generator",
|
||||
"@kbn/plugin-helpers": "link:bazel-bin/packages/kbn-plugin-helpers",
|
||||
"@kbn/pm": "link:packages/kbn-pm",
|
||||
"@kbn/scalability-simulation-generator": "link:bazel-bin/packages/kbn-scalability-simulation-generator",
|
||||
"@kbn/sort-package-json": "link:bazel-bin/packages/kbn-sort-package-json",
|
||||
"@kbn/spec-to-console": "link:bazel-bin/packages/kbn-spec-to-console",
|
||||
"@kbn/stdio-dev-helpers": "link:bazel-bin/packages/kbn-stdio-dev-helpers",
|
||||
|
@ -715,6 +716,7 @@
|
|||
"@types/kbn__plugin-helpers": "link:bazel-bin/packages/kbn-plugin-helpers/npm_module_types",
|
||||
"@types/kbn__react-field": "link:bazel-bin/packages/kbn-react-field/npm_module_types",
|
||||
"@types/kbn__rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils/npm_module_types",
|
||||
"@types/kbn__scalability-simulation-generator": "link:bazel-bin/packages/kbn-scalability-simulation-generator/npm_module_types",
|
||||
"@types/kbn__securitysolution-autocomplete": "link:bazel-bin/packages/kbn-securitysolution-autocomplete/npm_module_types",
|
||||
"@types/kbn__securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils/npm_module_types",
|
||||
"@types/kbn__securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils/npm_module_types",
|
||||
|
|
|
@ -93,6 +93,7 @@ filegroup(
|
|||
"//packages/kbn-plugin-helpers:build",
|
||||
"//packages/kbn-react-field:build",
|
||||
"//packages/kbn-rule-data-utils:build",
|
||||
"//packages/kbn-scalability-simulation-generator:build",
|
||||
"//packages/kbn-securitysolution-autocomplete:build",
|
||||
"//packages/kbn-securitysolution-es-utils:build",
|
||||
"//packages/kbn-securitysolution-hook-utils:build",
|
||||
|
@ -223,6 +224,7 @@ filegroup(
|
|||
"//packages/kbn-plugin-helpers:build_types",
|
||||
"//packages/kbn-react-field:build_types",
|
||||
"//packages/kbn-rule-data-utils:build_types",
|
||||
"//packages/kbn-scalability-simulation-generator:build_types",
|
||||
"//packages/kbn-securitysolution-autocomplete:build_types",
|
||||
"//packages/kbn-securitysolution-es-utils:build_types",
|
||||
"//packages/kbn-securitysolution-hook-utils:build_types",
|
||||
|
|
120
packages/kbn-scalability-simulation-generator/BUILD.bazel
Normal file
120
packages/kbn-scalability-simulation-generator/BUILD.bazel
Normal file
|
@ -0,0 +1,120 @@
|
|||
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-scalability-simulation-generator"
|
||||
PKG_REQUIRE_NAME = "@kbn/scalability-simulation-generator"
|
||||
|
||||
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-tooling-log",
|
||||
]
|
||||
|
||||
# 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-tooling-log: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"],
|
||||
)
|
31
packages/kbn-scalability-simulation-generator/README.md
Normal file
31
packages/kbn-scalability-simulation-generator/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# @kbn/scalability-simulation-generator
|
||||
|
||||
A library to generate scalability benchmarking simulation file, that can be run by Gatling performance testing tool.
|
||||
|
||||
## Usage
|
||||
|
||||
There are 2 ways to run auto-generated simulation files, using:
|
||||
- Gatling bundle
|
||||
- kibana-load-testing project
|
||||
|
||||
If you plan to use Gatling-bundle, generate simulation using this command:
|
||||
|
||||
```
|
||||
node scripts/generate_scalability_simulations.js \
|
||||
--dir "<path to @kbn/performance-testing-dataset-extractor output>" \
|
||||
--baseUrl "<Kibana server baseURL>"
|
||||
```
|
||||
|
||||
If you plan to use [kibana-load-testing](https://github.com/elastic/kibana-load-testing), use the following command:
|
||||
|
||||
```
|
||||
node scripts/generate_scalability_simulations.js \
|
||||
--dir "<path to @kbn/performance-testing-dataset-extractor output>" \
|
||||
--baseUrl "<Kibana server baseURL>" \
|
||||
--packageName "org.kibanaLoadTest"
|
||||
```
|
||||
|
||||
To run the generated simulation:
|
||||
- Move file to `src/test/scala/org/kibanaLoadTest`
|
||||
- Compile source code `mvn clean compile`
|
||||
- Run simulation `mvn gatling:test -Dgatling.simulationClass=org.kibanaLoadTest.<simulationFileName>`
|
13
packages/kbn-scalability-simulation-generator/jest.config.js
Normal file
13
packages/kbn-scalability-simulation-generator/jest.config.js
Normal 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-scalability-simulation-generator'],
|
||||
};
|
11
packages/kbn-scalability-simulation-generator/package.json
Normal file
11
packages/kbn-scalability-simulation-generator/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "@kbn/scalability-simulation-generator",
|
||||
"description": "A library to generate scalability benchmarking simulation files from APM traces.",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "./target_node/index.js",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"kibana": {
|
||||
"devOnly": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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 { Stage } from './types/journey';
|
||||
import { Header, Request, Simulation } from './types/simulation';
|
||||
|
||||
const AUTH_PATH = '/internal/security/login';
|
||||
const B_SEARCH_PATH = '/internal/bsearch';
|
||||
|
||||
const getHeaders = (headers: readonly Header[]) =>
|
||||
headers
|
||||
.map(
|
||||
(header) =>
|
||||
`"${header.name}" -> ${JSON.stringify(
|
||||
header.name !== 'Cookie' ? header.value : '${Cookie}'
|
||||
)}`
|
||||
)
|
||||
.join(',')
|
||||
.replace(/^/, 'Map(') + ')';
|
||||
|
||||
const getPayload = (body: string) => JSON.stringify(body).replace(/"/g, '\\"');
|
||||
|
||||
const getDuration = (duration: string) => {
|
||||
const value = duration.replace(/\D+/, '');
|
||||
return duration.endsWith('m') ? `${value} * 60` : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds Gatling simulation content from common template
|
||||
* @param packageName scala package name, where simulation file will be placed
|
||||
* @param simulationName scala class name
|
||||
* @param protocol Gatling protocol string
|
||||
* @param scenario Gatling scenario string
|
||||
* @param setup Gatling simulation injection setup string
|
||||
* @returns Gatling simulation content as string
|
||||
*/
|
||||
const buildSimulation = (
|
||||
packageName: string,
|
||||
simulationName: string,
|
||||
protocol: string,
|
||||
scenario: string,
|
||||
setup: string
|
||||
) =>
|
||||
`package ${packageName}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import io.gatling.jdbc.Predef._
|
||||
|
||||
class ${simulationName} extends Simulation {
|
||||
${protocol}
|
||||
|
||||
${scenario}
|
||||
|
||||
${setup}
|
||||
}`;
|
||||
|
||||
const buildAuthenticationExec = (path: string, headers: string, payload: string) =>
|
||||
` .exec(
|
||||
http("${path}")
|
||||
.post("${path}")
|
||||
.body(StringBody("${payload}"))
|
||||
.asJson
|
||||
.headers(${headers})
|
||||
.check(headerRegex("set-cookie", ".+?(?=;)").saveAs("Cookie"))
|
||||
)`;
|
||||
|
||||
const buildBSearchExec = (path: string, headers: string, payload: string) =>
|
||||
` .exec(
|
||||
http("${path}")
|
||||
.post("${path}")
|
||||
.headers(${headers})
|
||||
.body(StringBody(${payload}))
|
||||
.asJson
|
||||
.check(status.is(200).saveAs("status"))
|
||||
.check(jsonPath("$.result.id").find.saveAs("requestId"))
|
||||
.check(jsonPath("$.result.isPartial").find.saveAs("isPartial"))
|
||||
)
|
||||
.exitHereIfFailed
|
||||
// First response might be “partial”. Then we continue to fetch for the results
|
||||
// using the request id returned from the first response
|
||||
.asLongAs(session =>
|
||||
session("status").as[Int] == 200
|
||||
&& session("isPartial").as[Boolean]
|
||||
) {
|
||||
exec(
|
||||
http("${path}")
|
||||
.post("${path}")
|
||||
.headers(${headers})
|
||||
.body(StringBody(${payload}))
|
||||
.asJson
|
||||
.check(status.is(200).saveAs("status"))
|
||||
.check(jsonPath("$.result.isPartial").saveAs("isPartial"))
|
||||
)
|
||||
.exitHereIfFailed
|
||||
.pause(1)
|
||||
}`;
|
||||
|
||||
const buildCommonHttpExec = (path: string, method: string, headers: string) =>
|
||||
` .exec(
|
||||
http("${path}")
|
||||
.${method}("${path}")
|
||||
.headers(${headers})
|
||||
)`;
|
||||
|
||||
const buildCommonHttpBodyExec = (path: string, method: string, headers: string, payload: string) =>
|
||||
` .exec(
|
||||
http("${path}")
|
||||
.${method}("${path}")
|
||||
.body(StringBody("${payload}"))
|
||||
.asJson
|
||||
.headers(${headers})
|
||||
)`;
|
||||
|
||||
const addPause = (delay: number) => ` .pause(${delay}.milliseconds)`;
|
||||
|
||||
const buildProtocol = (baseUrl: string) =>
|
||||
` val httpProtocol = http
|
||||
.baseUrl("${baseUrl}")
|
||||
.inferHtmlResources()
|
||||
.acceptHeader("*/*")
|
||||
.acceptEncodingHeader("gzip, deflate")
|
||||
.acceptLanguageHeader("en-US,en;q=0.9,ru;q=0.8,de;q=0.7")
|
||||
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36")`;
|
||||
|
||||
const buildScenarioDefinition = (phase: string, scenarioName: string) =>
|
||||
` val ${phase} = scenario("${scenarioName} ${phase}")
|
||||
.exec(steps)`;
|
||||
|
||||
/**
|
||||
* Builds Gatling simulation setUp section, that defines injection for warmup and test scenarios
|
||||
* @param warmupStages
|
||||
* @param testStages
|
||||
* @param maxDuration
|
||||
* @returns Gatling simulation setUp as a string
|
||||
*/
|
||||
const buildSetup = (warmupStages: string, testStages: string, maxDuration: string) =>
|
||||
` setUp(
|
||||
warmup
|
||||
.inject(${warmupStages})
|
||||
.protocols(httpProtocol)
|
||||
.andThen(
|
||||
test
|
||||
.inject(${testStages})
|
||||
.protocols(httpProtocol)
|
||||
)
|
||||
).maxDuration(${maxDuration})`;
|
||||
|
||||
const buildExecStep = (request: Request) => {
|
||||
const headers = getHeaders(request.headers);
|
||||
const method = request.method.toLowerCase();
|
||||
if (!request.body) {
|
||||
return buildCommonHttpExec(request.path, method, headers);
|
||||
} else if (request.path.includes(AUTH_PATH)) {
|
||||
return buildAuthenticationExec(request.path, headers, getPayload(request.body));
|
||||
} else if (request.path.includes(B_SEARCH_PATH)) {
|
||||
return buildBSearchExec(request.path, headers, getPayload(request.body));
|
||||
} else {
|
||||
return buildCommonHttpBodyExec(request.path, method, headers, getPayload(request.body));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds Gatling scenario body
|
||||
* @param scenarioName scenario name
|
||||
* @param requests Kibana API requests
|
||||
* @returns Gatling scenario as a string
|
||||
*/
|
||||
const buildScenario = (scenarioName: string, requests: readonly Request[]): string => {
|
||||
const warmupScn = buildScenarioDefinition('warmup', scenarioName);
|
||||
const testScn = buildScenarioDefinition('test', scenarioName);
|
||||
// convert requests into array of Gatling exec http calls
|
||||
const execs = requests.map((request, index, reqArray) => {
|
||||
// construct Gatling exec http calls
|
||||
const exec = buildExecStep(request);
|
||||
// add delay between requests
|
||||
if (index < reqArray.length - 1) {
|
||||
const delay = reqArray[index + 1].timestamp - request.timestamp;
|
||||
if (delay > 0) {
|
||||
return exec + '\n' + addPause(delay);
|
||||
}
|
||||
}
|
||||
return exec;
|
||||
});
|
||||
const steps = execs.join('\n');
|
||||
const finalSteps = steps.slice(0, steps.indexOf('.')) + steps.slice(steps.indexOf('.') + 1);
|
||||
|
||||
return ' val steps =\n' + finalSteps + '\n\n' + warmupScn + '\n' + testScn + '\n';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds injection setup for scenario
|
||||
* @param stages Array of actions to be executed with users count and duration
|
||||
* @returns scenario injection as a string
|
||||
*/
|
||||
const buildBenchmarkingModel = (stages: readonly Stage[]) => {
|
||||
return stages
|
||||
.map((stage) => {
|
||||
return stage.action === 'constantConcurrentUsers'
|
||||
? `${stage.action}(${stage.maxUsersCount}) during (${getDuration(stage.duration)})`
|
||||
: `${stage.action}(${stage.minUsersCount}) to ${stage.maxUsersCount} during (${getDuration(
|
||||
stage.duration
|
||||
)})`;
|
||||
})
|
||||
.join(', ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates Gatling-compatible simulation content
|
||||
* @param params Simulation parameters
|
||||
* @returns Gatling simulation content as string
|
||||
*/
|
||||
export const generateSimulationContent = (params: Simulation) => {
|
||||
const { simulationName, packageName, scenarioName, baseUrl, requests, scalabilitySetup } = params;
|
||||
const protocol = buildProtocol(baseUrl);
|
||||
const scenario = buildScenario(scenarioName, requests);
|
||||
const setup = buildSetup(
|
||||
buildBenchmarkingModel(scalabilitySetup.warmup.stages),
|
||||
buildBenchmarkingModel(scalabilitySetup.test.stages),
|
||||
getDuration(scalabilitySetup.maxDuration)
|
||||
);
|
||||
|
||||
return buildSimulation(packageName, simulationName, protocol, scenario, setup);
|
||||
};
|
73
packages/kbn-scalability-simulation-generator/src/cli.ts
Normal file
73
packages/kbn-scalability-simulation-generator/src/cli.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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/generate_scalability_simulations --help` for usage information
|
||||
*
|
||||
*************************************************************/
|
||||
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { generator } from './generate_files';
|
||||
|
||||
const gatlingBundlePackageName = 'computerdatabase';
|
||||
|
||||
export async function generateScalabilitySimulations() {
|
||||
run(
|
||||
async ({ log, flags }) => {
|
||||
const baseUrl = flags.baseUrl;
|
||||
if (baseUrl && typeof baseUrl !== 'string') {
|
||||
throw createFlagError('--baseUrl must be a string');
|
||||
}
|
||||
if (!baseUrl) {
|
||||
throw createFlagError('--baseUrl must be defined');
|
||||
}
|
||||
|
||||
if (typeof flags.dir !== 'string') {
|
||||
throw createFlagError('--dir must be a string');
|
||||
}
|
||||
|
||||
const dir = path.resolve(flags.dir);
|
||||
if (!dir) {
|
||||
throw createFlagError('--dir must be defined');
|
||||
}
|
||||
if (!fs.existsSync(path.resolve(dir))) {
|
||||
throw createFlagError('--dir must be an existing folder path');
|
||||
}
|
||||
|
||||
if (typeof flags.packageName !== 'undefined' && typeof flags.packageName !== 'string') {
|
||||
throw createFlagError('--packageName is optional, but must be a string');
|
||||
}
|
||||
|
||||
const packageName = !flags.packageName ? gatlingBundlePackageName : flags.packageName;
|
||||
|
||||
return generator({
|
||||
dir,
|
||||
baseUrl,
|
||||
packageName,
|
||||
log,
|
||||
});
|
||||
},
|
||||
{
|
||||
description: `CLI to get scalability simulation file out of single user performance journey APM traces`,
|
||||
flags: {
|
||||
string: ['dir', 'baseUrl', 'packageName'],
|
||||
help: `
|
||||
--dir Path to json files with APM traces, generated using kbn-performance-testing-dataset-extractor
|
||||
--baseUrl Kibana server base url to use for scalability testing
|
||||
--packageName Simulation file package reference: ${gatlingBundlePackageName} is used by default and assumes
|
||||
a run with Gatling bundle. Use 'org.kibanaLoadTest' to run with 'kibana-load-testing' project.
|
||||
`,
|
||||
},
|
||||
usage: '--dir target/scalability_traces --baseUrl http://localhost:5620',
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 fsp from 'fs/promises';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { generateSimulationContent } from './build_simulation';
|
||||
import { getHttpRequests } from './parse_traces';
|
||||
import { Journey } from './types/journey';
|
||||
|
||||
export interface CLIParams {
|
||||
dir: string;
|
||||
baseUrl: string;
|
||||
packageName: string;
|
||||
log: ToolingLog;
|
||||
}
|
||||
|
||||
export const generator = async ({ dir, baseUrl, packageName, log }: CLIParams) => {
|
||||
const jsonInDir = fs.readdirSync(dir).filter((file) => path.extname(file) === '.json');
|
||||
log.info(`Found ${jsonInDir.length} json files in path: ${jsonInDir}`);
|
||||
|
||||
for (const file of jsonInDir) {
|
||||
const jsonPath = path.resolve(dir, file);
|
||||
const journey: Journey = JSON.parse(fs.readFileSync(jsonPath).toString());
|
||||
|
||||
if (!journey.traceItems || journey.traceItems.length === 0) {
|
||||
log.error(`No 'traceItems' found in ${jsonPath}, skipping file`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!journey.scalabilitySetup) {
|
||||
log.error(`No 'scalabilitySetup' found in ${jsonPath}, skipping file`);
|
||||
return;
|
||||
}
|
||||
|
||||
const requests = getHttpRequests(journey.traceItems);
|
||||
requests.forEach((req) =>
|
||||
log.debug(`${req.date} ${req.transactionId} ${req.method} ${req.path}`)
|
||||
);
|
||||
|
||||
const simulationName = journey.journeyName
|
||||
.replace(/[^a-zA-Z ]/g, ' ')
|
||||
.replace(/\b(\w)/g, (match, capture) => capture.toUpperCase())
|
||||
.replace(/\s+/g, '');
|
||||
|
||||
const fileName = `${simulationName}.scala`;
|
||||
const outputDir = path.resolve('target/scalability_simulations');
|
||||
const filePath = path.resolve(outputDir, fileName);
|
||||
|
||||
const fileContent = generateSimulationContent({
|
||||
simulationName,
|
||||
packageName,
|
||||
scenarioName: `${journey.journeyName} ${journey.kibanaVersion}`,
|
||||
baseUrl,
|
||||
scalabilitySetup: journey.scalabilitySetup,
|
||||
requests,
|
||||
});
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
await fsp.mkdir(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const stream = fs.createWriteStream(filePath);
|
||||
stream.write(fileContent);
|
||||
stream.end(() => log.info(`Gatling simulation '${simulationName}' was saved in '${filePath}'`));
|
||||
}
|
||||
};
|
10
packages/kbn-scalability-simulation-generator/src/index.ts
Normal file
10
packages/kbn-scalability-simulation-generator/src/index.ts
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.
|
||||
*/
|
||||
|
||||
export { generator } from './generate_files';
|
||||
export * from './cli';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { TraceItem } from './types/journey';
|
||||
import { Request } from './types/simulation';
|
||||
|
||||
export const getHttpRequests = (traces: readonly TraceItem[]): Request[] => {
|
||||
return traces
|
||||
.map((trace) => {
|
||||
const timestamp = new Date(trace.timestamp).getTime();
|
||||
const date = trace.timestamp;
|
||||
const transactionId = trace.transaction.id;
|
||||
const method = trace.request.method;
|
||||
const path = trace.request.url.path;
|
||||
const rawHeaders = trace.request.headers;
|
||||
const headers = Object.keys(rawHeaders).map((key) => ({
|
||||
name: key,
|
||||
value: String(rawHeaders[key].join('')),
|
||||
}));
|
||||
const body = trace.request.body;
|
||||
return { timestamp, date, transactionId, method, path, headers, body };
|
||||
})
|
||||
.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1));
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 interface Headers {
|
||||
readonly [key: string]: string[];
|
||||
}
|
||||
|
||||
export interface HttpRequest {
|
||||
readonly headers?: Headers[];
|
||||
readonly method?: string;
|
||||
readonly body?: string;
|
||||
}
|
||||
|
||||
export interface HttpResponse {
|
||||
readonly headers?: Headers[];
|
||||
readonly status_code?: number;
|
||||
}
|
||||
|
||||
export interface Http {
|
||||
readonly request?: HttpRequest;
|
||||
readonly response?: HttpResponse;
|
||||
}
|
||||
|
||||
export interface Trace {
|
||||
readonly '@timestamp': string;
|
||||
readonly transaction_id: string;
|
||||
readonly url_path: string;
|
||||
readonly url_base: string;
|
||||
readonly span_id?: string;
|
||||
readonly http?: Http;
|
||||
readonly children?: readonly Trace[];
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
readonly transactionName: string;
|
||||
readonly transactionType: string;
|
||||
readonly service: string;
|
||||
readonly traces: readonly Trace[];
|
||||
}
|
||||
|
||||
export interface Request {
|
||||
readonly url: {
|
||||
readonly path: string;
|
||||
};
|
||||
readonly headers: Headers;
|
||||
readonly method: string;
|
||||
readonly body?: string;
|
||||
}
|
||||
|
||||
export interface TransactionItem {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly type: string;
|
||||
}
|
||||
|
||||
export interface TraceItem {
|
||||
readonly traceId: string;
|
||||
readonly timestamp: string;
|
||||
readonly request: Request;
|
||||
readonly response: {
|
||||
readonly status: string;
|
||||
};
|
||||
readonly transaction: TransactionItem;
|
||||
}
|
||||
|
||||
export interface Stage {
|
||||
readonly action: string;
|
||||
readonly minUsersCount?: number;
|
||||
readonly maxUsersCount: number;
|
||||
readonly duration: string;
|
||||
}
|
||||
|
||||
export interface Setup {
|
||||
readonly warmup: { readonly stages: readonly Stage[] };
|
||||
readonly test: { readonly stages: readonly Stage[] };
|
||||
readonly maxDuration: string;
|
||||
}
|
||||
|
||||
export interface Journey {
|
||||
readonly journeyName: string;
|
||||
readonly kibanaVersion: string;
|
||||
readonly scalabilitySetup: Setup;
|
||||
readonly traceItems: readonly TraceItem[];
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { Setup } from './journey';
|
||||
|
||||
export interface Header {
|
||||
readonly name: string;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
export interface Request {
|
||||
readonly timestamp: number;
|
||||
readonly date: string;
|
||||
readonly transactionId: string;
|
||||
readonly method: string;
|
||||
readonly path: string;
|
||||
readonly headers: readonly Header[];
|
||||
readonly body?: string;
|
||||
}
|
||||
|
||||
export interface Simulation {
|
||||
readonly simulationName: string;
|
||||
readonly packageName: string;
|
||||
readonly scenarioName: string;
|
||||
readonly baseUrl: string;
|
||||
readonly scalabilitySetup: Setup;
|
||||
readonly requests: readonly Request[];
|
||||
}
|
17
packages/kbn-scalability-simulation-generator/tsconfig.json
Normal file
17
packages/kbn-scalability-simulation-generator/tsconfig.json
Normal 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/**/*"
|
||||
]
|
||||
}
|
10
scripts/generate_scalability_simulations.js
Normal file
10
scripts/generate_scalability_simulations.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');
|
||||
require('@kbn/scalability-simulation-generator').generateScalabilitySimulations();
|
|
@ -3247,6 +3247,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/scalability-simulation-generator@link:bazel-bin/packages/kbn-scalability-simulation-generator":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/securitysolution-autocomplete@link:bazel-bin/packages/kbn-securitysolution-autocomplete":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -6582,6 +6586,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@types/kbn__scalability-simulation-generator@link:bazel-bin/packages/kbn-scalability-simulation-generator/npm_module_types":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@types/kbn__securitysolution-autocomplete@link:bazel-bin/packages/kbn-securitysolution-autocomplete/npm_module_types":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue