mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [@kbn/profiler-cli: collect and display CPU profiles (#216356)](https://github.com/elastic/kibana/pull/216356) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Dario Gieselaar","email":"dario.gieselaar@elastic.co"},"sourceCommit":{"committedDate":"2025-04-02T06:47:33Z","message":"@kbn/profiler-cli: collect and display CPU profiles (#216356)\n\n# @kbn/profiler-cli\n\nProfile Kibana while it's running, and open the CPU profile in\nSpeedscope.\n\n## Usage\n\nRun a command by either preceding it with the profiler script:\n`node scripts/profile.js -- $command`\n\nOr by piping it in:\n`$command | node scripts/profile.js`\n\nYou can also just run it until SIGINT:\n\n`node scripts/profile.js`\n\nOr with a timeout:\n\n`node scripts/profile.js --timeout=10000`\n\n## Examples\n\n### Commands\n\nYou can copy a curl request from the browser, and place it after the\ncommand:\n\n`node scripts/profile.js --connections=10 --amount=50 -- curl ...`\n\nYou can also use stdin for this, for example:\n\n`pbpaste | node scripts/profile.js`\n\nWhen using stdin, take into consideration that there is some lag between\nstarting the script and connecting the profiler, so the profiler might\nmiss the first second or so of the running process.\n\nYou can also use any other command, like `autocannon`, `sleep` or\n`xargs`.\n\n### SigInt\n\nBy default, the profiler will run until the process exits:`node\nscripts/profile.js`. This is useful when you have a long running process\nrunning separately and you want to collect the profile over a longer\ntime period. Be aware that this might cause memory issues because the\nprofile will get huge. When you press Cmd+C, the profiler will\ngracefully exit and first write the profile to disk and open Speedscope.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"d1493b98e71db3a66014e29ba3264fcf21155f2e","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:version","v9.1.0","v8.19.0"],"title":"@kbn/profiler-cli: collect and display CPU profiles","number":216356,"url":"https://github.com/elastic/kibana/pull/216356","mergeCommit":{"message":"@kbn/profiler-cli: collect and display CPU profiles (#216356)\n\n# @kbn/profiler-cli\n\nProfile Kibana while it's running, and open the CPU profile in\nSpeedscope.\n\n## Usage\n\nRun a command by either preceding it with the profiler script:\n`node scripts/profile.js -- $command`\n\nOr by piping it in:\n`$command | node scripts/profile.js`\n\nYou can also just run it until SIGINT:\n\n`node scripts/profile.js`\n\nOr with a timeout:\n\n`node scripts/profile.js --timeout=10000`\n\n## Examples\n\n### Commands\n\nYou can copy a curl request from the browser, and place it after the\ncommand:\n\n`node scripts/profile.js --connections=10 --amount=50 -- curl ...`\n\nYou can also use stdin for this, for example:\n\n`pbpaste | node scripts/profile.js`\n\nWhen using stdin, take into consideration that there is some lag between\nstarting the script and connecting the profiler, so the profiler might\nmiss the first second or so of the running process.\n\nYou can also use any other command, like `autocannon`, `sleep` or\n`xargs`.\n\n### SigInt\n\nBy default, the profiler will run until the process exits:`node\nscripts/profile.js`. This is useful when you have a long running process\nrunning separately and you want to collect the profile over a longer\ntime period. Be aware that this might cause memory issues because the\nprofile will get huge. When you press Cmd+C, the profiler will\ngracefully exit and first write the profile to disk and open Speedscope.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"d1493b98e71db3a66014e29ba3264fcf21155f2e"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/216356","number":216356,"mergeCommit":{"message":"@kbn/profiler-cli: collect and display CPU profiles (#216356)\n\n# @kbn/profiler-cli\n\nProfile Kibana while it's running, and open the CPU profile in\nSpeedscope.\n\n## Usage\n\nRun a command by either preceding it with the profiler script:\n`node scripts/profile.js -- $command`\n\nOr by piping it in:\n`$command | node scripts/profile.js`\n\nYou can also just run it until SIGINT:\n\n`node scripts/profile.js`\n\nOr with a timeout:\n\n`node scripts/profile.js --timeout=10000`\n\n## Examples\n\n### Commands\n\nYou can copy a curl request from the browser, and place it after the\ncommand:\n\n`node scripts/profile.js --connections=10 --amount=50 -- curl ...`\n\nYou can also use stdin for this, for example:\n\n`pbpaste | node scripts/profile.js`\n\nWhen using stdin, take into consideration that there is some lag between\nstarting the script and connecting the profiler, so the profiler might\nmiss the first second or so of the running process.\n\nYou can also use any other command, like `autocannon`, `sleep` or\n`xargs`.\n\n### SigInt\n\nBy default, the profiler will run until the process exits:`node\nscripts/profile.js`. This is useful when you have a long running process\nrunning separately and you want to collect the profile over a longer\ntime period. Be aware that this might cause memory issues because the\nprofile will get huge. When you press Cmd+C, the profiler will\ngracefully exit and first write the profile to disk and open Speedscope.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"d1493b98e71db3a66014e29ba3264fcf21155f2e"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
06012d8772
commit
2089267d9d
20 changed files with 551 additions and 10 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -724,6 +724,7 @@ src/platform/plugins/shared/presentation_util @elastic/kibana-presentation
|
|||
x-pack/packages/ai-infra/product-doc-artifact-builder @elastic/appex-ai-infra
|
||||
x-pack/platform/plugins/shared/ai_infra/product_doc_base @elastic/appex-ai-infra
|
||||
x-pack/platform/packages/shared/ai-infra/product-doc-common @elastic/appex-ai-infra
|
||||
x-pack/platform/packages/shared/kbn-profiler-cli @elastic/obs-knowledge-team
|
||||
x-pack/solutions/observability/plugins/profiling_data_access @elastic/obs-ux-infra_services-team
|
||||
x-pack/solutions/observability/plugins/profiling @elastic/obs-ux-infra_services-team
|
||||
src/platform/packages/shared/kbn-profiling-utils @elastic/obs-ux-infra_services-team
|
||||
|
|
|
@ -741,6 +741,7 @@
|
|||
"@kbn/presentation-util-plugin": "link:src/platform/plugins/shared/presentation_util",
|
||||
"@kbn/product-doc-base-plugin": "link:x-pack/platform/plugins/shared/ai_infra/product_doc_base",
|
||||
"@kbn/product-doc-common": "link:x-pack/platform/packages/shared/ai-infra/product-doc-common",
|
||||
"@kbn/profiler-cli": "link:x-pack/platform/packages/shared/kbn-profiler-cli",
|
||||
"@kbn/profiling-data-access-plugin": "link:x-pack/solutions/observability/plugins/profiling_data_access",
|
||||
"@kbn/profiling-plugin": "link:x-pack/solutions/observability/plugins/profiling",
|
||||
"@kbn/profiling-utils": "link:src/platform/packages/shared/kbn-profiling-utils",
|
||||
|
@ -1109,6 +1110,7 @@
|
|||
"chalk": "^4.1.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"chroma-js": "^2.1.0",
|
||||
"chrome-remote-interface": "^0.33.3",
|
||||
"classnames": "2.2.6",
|
||||
"color": "^4.2.3",
|
||||
"commander": "^4.1.1",
|
||||
|
@ -1592,6 +1594,7 @@
|
|||
"@types/byte-size": "^8.1.2",
|
||||
"@types/chance": "^1.0.0",
|
||||
"@types/chroma-js": "^2.1.0",
|
||||
"@types/chrome-remote-interface": "^0.31.14",
|
||||
"@types/chromedriver": "^81.0.5",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/cli-progress": "^3.11.5",
|
||||
|
@ -1778,7 +1781,7 @@
|
|||
"eslint-plugin-react-perf": "^3.3.1",
|
||||
"eslint-plugin-testing-library": "^7.1.1",
|
||||
"eslint-traverse": "^1.0.0",
|
||||
"exit-hook": "^2.2.0",
|
||||
"exit-hook": "2.2.0",
|
||||
"expect": "^29.7.0",
|
||||
"expose-loader": "^5.0.0",
|
||||
"express": "^4.21.2",
|
||||
|
|
|
@ -1207,6 +1207,25 @@
|
|||
],
|
||||
"minimumReleaseAge": "7 days",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"groupName": "chrome-remote-interface",
|
||||
"matchDepNames": [
|
||||
"chrome-remote-interface",
|
||||
"@types/chrome-remote-interface"
|
||||
],
|
||||
"reviewers": [
|
||||
"team:obs-knowledge-team"
|
||||
],
|
||||
"matchBaseBranches": [
|
||||
"main"
|
||||
],
|
||||
"labels": [
|
||||
"release_note:skip",
|
||||
"backport:all-open"
|
||||
],
|
||||
"minimumReleaseAge": "7 days",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
|
|
15
scripts/profile.js
Normal file
15
scripts/profile.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
require('@babel/register')({
|
||||
extensions: ['.ts', '.js'],
|
||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
|
||||
});
|
||||
|
||||
require('@kbn/profiler-cli/cli');
|
|
@ -101,7 +101,7 @@ function startEluMeasurement<T>(
|
|||
active
|
||||
)}ms out of ${Math.round(duration)}ms) and ${eluThreshold * 100}% (${Math.round(
|
||||
utilization * 100
|
||||
)}%) `,
|
||||
)}%). Run \`node scripts/profile.js\` to find out why.`,
|
||||
{
|
||||
labels: {
|
||||
request_path: path,
|
||||
|
|
|
@ -56,7 +56,7 @@ function shouldWriteType(level: ParsedLogLevel, type: MessageTypes) {
|
|||
return Boolean(level.flags[type === 'success' ? 'info' : type]);
|
||||
}
|
||||
|
||||
function stringifyError(error: string | Error) {
|
||||
function stringifyError(error: string | Error): string {
|
||||
if (typeof error !== 'string' && !(error instanceof Error)) {
|
||||
error = new Error(`"${error}" thrown`);
|
||||
}
|
||||
|
@ -65,7 +65,11 @@ function stringifyError(error: string | Error) {
|
|||
return error;
|
||||
}
|
||||
|
||||
return error.stack || error.message || error;
|
||||
if (error instanceof AggregateError) {
|
||||
return [error.stack, ...error.errors.map(stringifyError)].join('\n');
|
||||
}
|
||||
|
||||
return error.stack || error.message || String(error);
|
||||
}
|
||||
|
||||
export class ToolingLogTextWriter implements Writer {
|
||||
|
|
|
@ -1442,6 +1442,8 @@
|
|||
"@kbn/product-doc-base-plugin/*": ["x-pack/platform/plugins/shared/ai_infra/product_doc_base/*"],
|
||||
"@kbn/product-doc-common": ["x-pack/platform/packages/shared/ai-infra/product-doc-common"],
|
||||
"@kbn/product-doc-common/*": ["x-pack/platform/packages/shared/ai-infra/product-doc-common/*"],
|
||||
"@kbn/profiler-cli": ["x-pack/platform/packages/shared/kbn-profiler-cli"],
|
||||
"@kbn/profiler-cli/*": ["x-pack/platform/packages/shared/kbn-profiler-cli/*"],
|
||||
"@kbn/profiling-data-access-plugin": ["x-pack/solutions/observability/plugins/profiling_data_access"],
|
||||
"@kbn/profiling-data-access-plugin/*": ["x-pack/solutions/observability/plugins/profiling_data_access/*"],
|
||||
"@kbn/profiling-plugin": ["x-pack/solutions/observability/plugins/profiling"],
|
||||
|
|
39
x-pack/platform/packages/shared/kbn-profiler-cli/README.md
Normal file
39
x-pack/platform/packages/shared/kbn-profiler-cli/README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# @kbn/profiler-cli
|
||||
|
||||
Profile Kibana while it's running, and open the CPU profile in Speedscope.
|
||||
|
||||
## Usage
|
||||
|
||||
Run a command by either preceding it with the profiler script:
|
||||
`node scripts/profile.js -- $command`
|
||||
|
||||
Or by piping it in:
|
||||
`$command | node scripts/profile.js`
|
||||
|
||||
You can also just run it until SIGINT:
|
||||
|
||||
`node scripts/profile.js`
|
||||
|
||||
Or with a timeout:
|
||||
|
||||
`node scripts/profile.js --timeout=10000`
|
||||
|
||||
## Examples
|
||||
|
||||
### Commands
|
||||
|
||||
You can copy a curl request from the browser, and place it after the command:
|
||||
|
||||
`node scripts/profile.js --connections=10 --amount=50 -- curl ...`
|
||||
|
||||
You can also use stdin for this, for example:
|
||||
|
||||
`pbpaste | node scripts/profile.js`
|
||||
|
||||
When using stdin, take into consideration that there is some lag between starting the script and connecting the profiler, so the profiler might miss the first second or so of the running process.
|
||||
|
||||
You can also use any other command, like `autocannon`, `sleep` or `xargs`.
|
||||
|
||||
### SigInt
|
||||
|
||||
By default, the profiler will run until the process exits:`node scripts/profile.js`. This is useful when you have a long running process running separately and you want to collect the profile over a longer time period. Be aware that this might cause memory issues because the profile will get huge. When you press Cmd+C, the profiler will gracefully exit and first write the profile to disk and open Speedscope.
|
8
x-pack/platform/packages/shared/kbn-profiler-cli/cli.js
Normal file
8
x-pack/platform/packages/shared/kbn-profiler-cli/cli.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
require('.').cli();
|
105
x-pack/platform/packages/shared/kbn-profiler-cli/index.ts
Normal file
105
x-pack/platform/packages/shared/kbn-profiler-cli/index.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
import { compact, once, uniq } from 'lodash';
|
||||
import { getKibanaProcessId } from './src/get_kibana_process_id';
|
||||
import { runCommand } from './src/run_command';
|
||||
import { runUntilSigInt } from './src/run_until_sigint';
|
||||
import { getProfiler } from './src/get_profiler';
|
||||
import { untilStdinCompletes } from './src/until_stdin_completes';
|
||||
|
||||
export function cli() {
|
||||
run(
|
||||
async ({ flags, log, addCleanupTask }) => {
|
||||
const pid = flags.pid
|
||||
? Number(flags.pid)
|
||||
: await getKibanaProcessId({
|
||||
ports: uniq(compact([Number(flags.port), 5603, 5601])),
|
||||
});
|
||||
|
||||
const controller = new AbortController();
|
||||
if (flags.timeout) {
|
||||
setTimeout(() => {
|
||||
controller.abort();
|
||||
}, Number(flags.timeout));
|
||||
}
|
||||
|
||||
process.kill(pid, 'SIGUSR1');
|
||||
|
||||
const stop = once(await getProfiler({ log, type: flags.heap ? 'heap' : 'cpu' }));
|
||||
|
||||
addCleanupTask(() => {
|
||||
// exit-hook, which is used by addCleanupTask,
|
||||
// only allows for synchronous exits, and 3.x
|
||||
// are on ESM which we currently can't use. so
|
||||
// we do a really gross thing where we make
|
||||
// process.exit a noop for a bit until the
|
||||
// profile has been collected and opened
|
||||
const exit = process.exit.bind(process);
|
||||
|
||||
// @ts-expect-error
|
||||
process.exit = () => {};
|
||||
|
||||
stop()
|
||||
.then(() => {
|
||||
exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error(error);
|
||||
exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
if (!process.stdin.isTTY) {
|
||||
await untilStdinCompletes();
|
||||
} else if (flags._.length) {
|
||||
const connections = Number(flags.c || flags.connections || 1);
|
||||
const amount = Number(flags.a || flags.amount || 1);
|
||||
const command = flags._;
|
||||
|
||||
log.info(`Executing "${command}" ${amount} times, ${connections} at a time`);
|
||||
|
||||
await runCommand({
|
||||
command,
|
||||
connections,
|
||||
amount,
|
||||
signal: controller.signal,
|
||||
});
|
||||
} else {
|
||||
if (flags.timeout) {
|
||||
log.info(`Awaiting timeout of ${flags.timeout}ms`);
|
||||
} else {
|
||||
log.info(`Awaiting SIGINT (Cmd+C)...`);
|
||||
}
|
||||
|
||||
await runUntilSigInt({
|
||||
log,
|
||||
signal: controller.signal,
|
||||
});
|
||||
}
|
||||
|
||||
await stop();
|
||||
},
|
||||
{
|
||||
flags: {
|
||||
string: ['port', 'pid', 't', 'timeout', 'c', 'connections', 'a', 'amount'],
|
||||
boolean: ['heap'],
|
||||
help: `
|
||||
Usage: node scripts/profiler.js <args> <command>
|
||||
|
||||
--port Port on which Kibana is running. Falls back to 5603 & 5601.
|
||||
--pid Process ID to hook into it. Takes precedence over \`port\`.
|
||||
--timeout Run commands until timeout (in milliseconds)
|
||||
--c, --connections Number of commands that can be run in parallel.
|
||||
--a, --amount Amount of times the command should be run
|
||||
--heap Collect a heap snapshot
|
||||
`,
|
||||
allowUnexpected: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/platform/packages/shared/kbn-profiler-cli'],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "shared-server",
|
||||
"id": "@kbn/profiler-cli",
|
||||
"owner": "@elastic/obs-knowledge-team",
|
||||
"group": "platform",
|
||||
"visibility": "shared"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/profiler-cli",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0"
|
||||
}
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import execa from 'execa';
|
||||
|
||||
async function getProcessIdAtPort(port: number) {
|
||||
return await execa
|
||||
.command(`lsof -ti :${port}`)
|
||||
.then(({ stdout }) => {
|
||||
return parseInt(stdout.trim().split('\n')[0], 10);
|
||||
})
|
||||
.catch((error) => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getKibanaProcessId({ ports }: { ports: number[] }): Promise<number> {
|
||||
for (const port of ports) {
|
||||
const pid = await getProcessIdAtPort(port);
|
||||
if (pid) {
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Kibana process id not found at ports ${ports.join(', ')}`);
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import CDP from 'chrome-remote-interface';
|
||||
import { promises as Fs } from 'fs';
|
||||
import Os from 'os';
|
||||
import Path from 'path';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import execa from 'execa';
|
||||
|
||||
async function getHeapProfiler({ client, log }: { client: CDP.Client; log: ToolingLog }) {
|
||||
await client.HeapProfiler.enable();
|
||||
|
||||
log.debug(`Enabled heap profiler`);
|
||||
|
||||
await client.HeapProfiler.startSampling();
|
||||
|
||||
return async () => {
|
||||
log.debug(`Taking heap snapshot`);
|
||||
|
||||
const { profile } = await client.HeapProfiler.stopSampling();
|
||||
|
||||
await client.HeapProfiler.disable();
|
||||
|
||||
return {
|
||||
name: 'heap.heapsnapshot',
|
||||
content: JSON.stringify(profile),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async function getCpuProfiler({ client, log }: { client: CDP.Client; log: ToolingLog }) {
|
||||
log.debug('Enabled profiler');
|
||||
await client.Profiler.enable();
|
||||
await client.Profiler.start();
|
||||
|
||||
return async () => {
|
||||
const { profile } = await client.Profiler.stop();
|
||||
|
||||
await client.Profiler.disable();
|
||||
|
||||
return {
|
||||
name: 'profile.cpuprofile',
|
||||
content: JSON.stringify(profile),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export async function getProfiler({
|
||||
log,
|
||||
type,
|
||||
}: {
|
||||
log: ToolingLog;
|
||||
type: 'cpu' | 'heap';
|
||||
}): Promise<() => Promise<void>> {
|
||||
log.debug(`Attaching to remote debugger at 9229`);
|
||||
const client = await CDP({ port: 9229 });
|
||||
|
||||
log.info(`Attached to remote debugger at 9229`);
|
||||
|
||||
const stop =
|
||||
type === 'cpu' ? await getCpuProfiler({ client, log }) : await getHeapProfiler({ client, log });
|
||||
|
||||
log.debug(`Started profiling session`);
|
||||
|
||||
return async () => {
|
||||
log.debug(`Stopping profiling session`);
|
||||
|
||||
const { name, content } = await stop();
|
||||
|
||||
await client.close();
|
||||
|
||||
log.debug(`Closed connection to remote debugger`);
|
||||
|
||||
// Write the profile data to a file.
|
||||
const tmpDir = await Fs.mkdtemp(Path.join(Os.tmpdir(), 'kbn-profiles'));
|
||||
|
||||
const profileFilePath = Path.join(tmpDir, name);
|
||||
|
||||
await Fs.writeFile(profileFilePath, content, 'utf-8');
|
||||
|
||||
log.info(`Wrote profile to ${profileFilePath}`);
|
||||
|
||||
await execa.command(`npx speedscope ${profileFilePath}`);
|
||||
|
||||
log.info(`Opened Speedscope`);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import execa from 'execa';
|
||||
import { range } from 'lodash';
|
||||
import pLimit from 'p-limit';
|
||||
|
||||
class AbortError extends Error {
|
||||
constructor() {
|
||||
super('Aborted');
|
||||
super.name = 'AbortError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs command n times, in parallel, until completion,
|
||||
* or until abort signal is triggered.
|
||||
*
|
||||
* Executes commands until all have settled, rather than
|
||||
* throwing on the first rejection. If at least one command
|
||||
* fails, or the abort signal is triggered, the function will
|
||||
* throw an error.
|
||||
*/
|
||||
export async function runCommand({
|
||||
command,
|
||||
amount,
|
||||
connections,
|
||||
signal,
|
||||
}: {
|
||||
command: string[];
|
||||
amount: number;
|
||||
connections: number;
|
||||
signal: AbortSignal;
|
||||
}) {
|
||||
const abortPromise = new Promise((resolve, reject) => {
|
||||
if (signal.aborted) {
|
||||
reject(new AbortError());
|
||||
return;
|
||||
}
|
||||
signal.addEventListener('abort', () => {
|
||||
reject(new AbortError());
|
||||
});
|
||||
});
|
||||
|
||||
function executeCommand() {
|
||||
const [file, ...args] = command;
|
||||
return execa(file, args, { stdio: 'ignore' });
|
||||
}
|
||||
|
||||
if (amount === 1) {
|
||||
return await Promise.race([abortPromise, executeCommand()]);
|
||||
}
|
||||
|
||||
const limiter = pLimit(connections);
|
||||
|
||||
await Promise.allSettled(
|
||||
range(0, amount).map(async () => {
|
||||
await limiter(() => Promise.race([abortPromise, executeCommand()]));
|
||||
})
|
||||
).then((results) => {
|
||||
const errors = results.flatMap((result) =>
|
||||
result.status === 'rejected' ? [result.reason] : []
|
||||
);
|
||||
if (errors.length) {
|
||||
throw new AggregateError(errors, `Some executions failed`);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
|
||||
export async function runUntilSigInt({
|
||||
log,
|
||||
signal,
|
||||
}: {
|
||||
log: ToolingLog;
|
||||
signal: AbortSignal;
|
||||
}): Promise<void> {
|
||||
// run until infinity
|
||||
await new Promise<void>((resolve) => {
|
||||
if (signal.aborted) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
signal.addEventListener('abort', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export async function untilStdinCompletes() {
|
||||
if (process.stdin.isTTY) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await new Promise<string>((resolve) => {
|
||||
let buffer = '';
|
||||
process.stdin.on('data', (chunk) => {
|
||||
buffer += chunk.toString('utf-8');
|
||||
});
|
||||
process.stdin.on('end', () => {
|
||||
resolve(buffer);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/tooling-log",
|
||||
]
|
||||
}
|
71
yarn.lock
71
yarn.lock
|
@ -6518,6 +6518,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/profiler-cli@link:x-pack/platform/packages/shared/kbn-profiler-cli":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/profiling-data-access-plugin@link:x-pack/solutions/observability/plugins/profiling_data_access":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -10752,6 +10756,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.1.3.tgz#0b03d737ff28fad10eb884e0c6cedd5ffdc4ba0a"
|
||||
integrity sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==
|
||||
|
||||
"@types/chrome-remote-interface@^0.31.14":
|
||||
version "0.31.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/chrome-remote-interface/-/chrome-remote-interface-0.31.14.tgz#173842a9a8e9995d5111ce209aa883fc90cedd96"
|
||||
integrity sha512-H9hTcLu1y+Ms6GDPXXeGhgxaOSD69yEo674vjJw5EeW1tTwYo8fEkf7A9nWlnO6ArJsS7c41iZeX6mRDQ1LhEw==
|
||||
dependencies:
|
||||
devtools-protocol "0.0.927104"
|
||||
|
||||
"@types/chromedriver@^81.0.5":
|
||||
version "81.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/chromedriver/-/chromedriver-81.0.5.tgz#c7b82f45c1cb9ebe47b7fb24a8641c9adf181b67"
|
||||
|
@ -14442,6 +14453,14 @@ chrome-launcher@^1.1.2:
|
|||
is-wsl "^2.2.0"
|
||||
lighthouse-logger "^2.0.1"
|
||||
|
||||
chrome-remote-interface@^0.33.3:
|
||||
version "0.33.3"
|
||||
resolved "https://registry.yarnpkg.com/chrome-remote-interface/-/chrome-remote-interface-0.33.3.tgz#d8b4339f487b460a9461af7355bda98054e8e1a4"
|
||||
integrity sha512-zNnn0prUL86Teru6UCAZ1yU1XeXljHl3gj7OrfPcarEfU62OUU4IujDPdTDW3dAWwRqN3ZMG/Chhkh2gPL/wiw==
|
||||
dependencies:
|
||||
commander "2.11.x"
|
||||
ws "^7.2.0"
|
||||
|
||||
chrome-trace-event@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
|
||||
|
@ -14874,6 +14893,11 @@ commander@2, commander@^2.15.0, commander@^2.19.0, commander@^2.20.0, commander@
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@2.11.x:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
|
||||
integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==
|
||||
|
||||
commander@6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
|
||||
|
@ -16528,6 +16552,11 @@ devtools-protocol@0.0.1423531:
|
|||
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1423531.tgz#43ba906340fb8ffbda566711ead31f139b2a150a"
|
||||
integrity sha512-z6cOcajZWxk80zvFnkTGa7tj3oqF+C5SnOF1KSMeAr5/WW/nLNHlEpKr7voSzMz8IaUoq5rjdI0Mqv5k/BUkhg==
|
||||
|
||||
devtools-protocol@0.0.927104:
|
||||
version "0.0.927104"
|
||||
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.927104.tgz#3bba0fca644bcdce1bcebb10ae392ab13428a7a0"
|
||||
integrity sha512-5jfffjSuTOv0Lz53wTNNTcCUV8rv7d82AhYcapj28bC2B5tDxEZzVb7k51cNxZP2KHw24QE+sW7ZuSeD9NfMpA==
|
||||
|
||||
dezalgo@^1.0.0, dezalgo@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
|
||||
|
@ -17822,7 +17851,7 @@ executable@^4.1.1:
|
|||
dependencies:
|
||||
pify "^2.2.0"
|
||||
|
||||
exit-hook@^2.2.0:
|
||||
exit-hook@2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.0.tgz#f5502f92179018e867f2d8ee4428392da7f3894e"
|
||||
integrity sha512-YFH+2oGdldRH5GqGpnaiKbBxWHMmuXHmKTMtUC58kWSOrnTf95rKITVSFTTtas14DWvWpih429+ffAvFetPwNA==
|
||||
|
@ -28722,7 +28751,7 @@ string-replace-loader@^3.1.0:
|
|||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
@ -28740,6 +28769,15 @@ string-width@^1.0.1:
|
|||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
|
@ -28832,7 +28870,7 @@ stringify-object@^3.2.1:
|
|||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
@ -28846,6 +28884,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
|||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
|
@ -31562,7 +31607,7 @@ workerpool@^6.5.1:
|
|||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
|
||||
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
@ -31588,6 +31633,15 @@ wrap-ansi@^6.2.0:
|
|||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
@ -31629,7 +31683,7 @@ write-file-atomic@^4.0.2:
|
|||
imurmurhash "^0.1.4"
|
||||
signal-exit "^3.0.7"
|
||||
|
||||
ws@^7.0.0, ws@^7.3.1, ws@^7.4.2:
|
||||
ws@^7.0.0, ws@^7.2.0, ws@^7.3.1, ws@^7.4.2:
|
||||
version "7.5.10"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
|
||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
||||
|
@ -31698,7 +31752,7 @@ xpath@^0.0.33:
|
|||
resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07"
|
||||
integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==
|
||||
|
||||
"xstate5@npm:xstate@^5.19.2", xstate@^5.19.2:
|
||||
"xstate5@npm:xstate@^5.19.2":
|
||||
version "5.19.2"
|
||||
resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.19.2.tgz#db3f1ee614bbb6a49ad3f0c96ddbf98562d456ba"
|
||||
integrity sha512-B8fL2aP0ogn5aviAXFzI5oZseAMqN00fg/TeDa3ZtatyDcViYLIfuQl4y8qmHCiKZgGEzmnTyNtNQL9oeJE2gw==
|
||||
|
@ -31708,6 +31762,11 @@ xstate@^4.38.3:
|
|||
resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075"
|
||||
integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==
|
||||
|
||||
xstate@^5.19.2:
|
||||
version "5.19.2"
|
||||
resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.19.2.tgz#db3f1ee614bbb6a49ad3f0c96ddbf98562d456ba"
|
||||
integrity sha512-B8fL2aP0ogn5aviAXFzI5oZseAMqN00fg/TeDa3ZtatyDcViYLIfuQl4y8qmHCiKZgGEzmnTyNtNQL9oeJE2gw==
|
||||
|
||||
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue