Skip to content

Commit 3cc88e8

Browse files
dgieselaarqn895
authored andcommitted
[Profiler] Spawn and profile process (elastic#230352)
Adds the possibility to spawn a process and profile it from start to finish, using `--inspect-wait`: ### Spawning a new process You can also spawn a new process, so you can profile start to finish. This is useful for shorter-lived processes. Use the `--spawn` flag for this purpose: `node scripts/profile.js --spawn -- node scripts/my_expensive_script` The script will be executed with `NODE_OPTIONS=inspect-wait`, which will pause the script until the profiler script has attached to the debugger. --------------- Also made Flags a generic type, so specified flags can be fully typed (when the consumer opts in).
1 parent cc74ee2 commit 3cc88e8

File tree

24 files changed

+739
-216
lines changed

24 files changed

+739
-216
lines changed

scripts/profile.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
require('@babel/register')({
11-
extensions: ['.ts', '.js'],
12-
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
13-
});
10+
require('../src/setup_node_env');
1411

1512
require('@kbn/profiler-cli/cli');

src/platform/packages/shared/kbn-dev-cli-runner/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
export * from './src/run';
11-
export * from './src/run_with_commands';
12-
export * from './src/flags';
13-
export * from './src/flags_reader';
10+
export * from './src/run/run';
11+
export type * from './src/run/types';
12+
export * from './src/run/run_with_commands';
13+
export * from './src/flags/flags';
14+
export type * from './src/flags/types';
15+
export * from './src/flags/flags_reader';
16+
1417
export type { CleanupTask } from './src/cleanup';

src/platform/packages/shared/kbn-dev-cli-runner/src/flags.ts renamed to src/platform/packages/shared/kbn-dev-cli-runner/src/flags/flags.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,7 @@
99

1010
import getopts from 'getopts';
1111
import { LOG_LEVEL_FLAGS, DEFAULT_LOG_LEVEL } from '@kbn/tooling-log';
12-
13-
import type { RunOptions } from './run';
14-
15-
export interface Flags {
16-
verbose: boolean;
17-
quiet: boolean;
18-
silent: boolean;
19-
debug: boolean;
20-
help: boolean;
21-
_: string[];
22-
unexpected: string[];
23-
24-
[key: string]: undefined | boolean | string | string[];
25-
}
26-
27-
export interface FlagOptions {
28-
allowUnexpected?: boolean;
29-
guessTypesForUnexpectedFlags?: boolean;
30-
help?: string;
31-
examples?: string;
32-
alias?: { [key: string]: string | string[] };
33-
boolean?: string[];
34-
string?: string[];
35-
default?: { [key: string]: any };
36-
}
12+
import type { FlagOptions, Flags } from './types';
3713

3814
export function mergeFlagOptions(global: FlagOptions = {}, local: FlagOptions = {}): FlagOptions {
3915
return {
@@ -62,7 +38,7 @@ export const DEFAULT_FLAG_ALIASES = {
6238

6339
export function getFlags(
6440
argv: string[],
65-
flagOptions: RunOptions['flags'] = {},
41+
flagOptions: FlagOptions = {},
6642
defaultLogLevel: string = DEFAULT_LOG_LEVEL
6743
): Flags {
6844
const unexpectedNames = new Set<string>();

src/platform/packages/shared/kbn-dev-cli-runner/src/flags_reader.ts renamed to src/platform/packages/shared/kbn-dev-cli-runner/src/flags/flags_reader.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Path from 'path';
1212
import { createFlagError } from '@kbn/dev-cli-errors';
1313
import { LOG_LEVEL_FLAGS } from '@kbn/tooling-log';
1414

15-
type FlagValue = string | string[] | boolean;
15+
type FlagValue = string | string[] | boolean | number;
1616
const FORCED_FLAGS = new Set([...LOG_LEVEL_FLAGS.map((l) => l.name), 'help']);
1717

1818
const makeAbsolute = (rel: string) => Path.resolve(process.cwd(), rel);
@@ -78,6 +78,7 @@ export class FlagsReader {
7878

7979
switch (typeof value) {
8080
case 'boolean':
81+
case 'number':
8182
throw createFlagError(`expected --${key} to be a string`);
8283
case 'string':
8384
return value ? [value] : [];
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
// iterate over the items in order, to find non-nullish types
11+
type Coalesce<T extends any[]> = T extends [infer T1, ...infer TTail]
12+
? T1 extends null | undefined | false
13+
? Coalesce<TTail>
14+
: T1
15+
: T extends [infer T1]
16+
? T1
17+
: false;
18+
19+
// make sure string[] is not converted into { [key:string]: string }
20+
// which is too wide to be useful
21+
type FlagsToObj<T extends string[] | undefined, TValue> = string[] extends T
22+
? {}
23+
: T extends string[]
24+
? {
25+
[key in T[number] & string]?: TValue;
26+
}
27+
: {};
28+
29+
type IsStringLiteral<T extends string[] | undefined> = string[] extends T
30+
? false
31+
: undefined extends T
32+
? false
33+
: true;
34+
35+
type IsTypedFlagOptions<TFlagOptions extends FlagOptions> = Coalesce<
36+
[IsStringLiteral<TFlagOptions['string']>, IsStringLiteral<TFlagOptions['boolean']>]
37+
>;
38+
39+
interface UnspecifiedFlags {
40+
[key: string]: string | string[] | boolean | undefined;
41+
}
42+
43+
/**
44+
* Infer the type of Flags from FlagOptions. Inference only kicks in when
45+
* either `string` or `boolean` consists of string literals (which only
46+
* is the case when the flag options are tagged with the `as const` modifier.)
47+
* Otherwise the type we create is too specific and we'd have to use `as const`
48+
* everywhere.
49+
*/
50+
export type FlagsOf<TFlagOptions extends FlagOptions> =
51+
IsTypedFlagOptions<TFlagOptions> extends true
52+
? BaseFlags<
53+
(true extends TFlagOptions['allowUnexpected'] ? UnspecifiedFlags : {}) &
54+
(TFlagOptions['string'] extends string[]
55+
? FlagsToObj<TFlagOptions['string'], string>
56+
: {}) &
57+
(TFlagOptions['boolean'] extends string[]
58+
? FlagsToObj<TFlagOptions['boolean'], boolean>
59+
: {})
60+
>
61+
: Flags;
62+
63+
/**
64+
* Base variant of Flags that does not automatically set unspecified
65+
* flags `([key:string]: ...)`
66+
*/
67+
export type BaseFlags<TExtraFlags extends UnspecifiedFlags = {}> = {
68+
verbose: boolean;
69+
quiet: boolean;
70+
silent: boolean;
71+
debug: boolean;
72+
help: boolean;
73+
_: string[];
74+
unexpected: string[];
75+
} & TExtraFlags;
76+
77+
export type Flags = BaseFlags<UnspecifiedFlags>;
78+
79+
export interface FlagOptions {
80+
allowUnexpected?: boolean;
81+
guessTypesForUnexpectedFlags?: boolean;
82+
help?: string;
83+
examples?: string;
84+
alias?: { [key: string]: string | string[] };
85+
boolean?: string[];
86+
string?: string[];
87+
default?: { [key: string]: any };
88+
}

src/platform/packages/shared/kbn-dev-cli-runner/src/help.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import { getCommandLevelHelp, getHelp, getHelpForAllCommands } from './help';
11-
import type { Command } from './run_with_commands';
11+
import type { Command } from './run/run_with_commands';
1212

1313
const fooCommand: Command<any> = {
1414
description: `

src/platform/packages/shared/kbn-dev-cli-runner/src/help.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import chalk from 'chalk';
1313
import dedent from 'dedent';
1414
import { getLogLevelFlagsHelp } from '@kbn/tooling-log';
1515

16-
import type { Command } from './run_with_commands';
16+
import type { Command } from './run/run_with_commands';
1717

1818
const DEFAULT_GLOBAL_USAGE = `node ${Path.relative(process.cwd(), process.argv[1])}`;
1919
export const GLOBAL_FLAGS = dedent`

src/platform/packages/shared/kbn-dev-cli-runner/src/run.ts renamed to src/platform/packages/shared/kbn-dev-cli-runner/src/run/run.ts

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,26 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import type { LogLevel } from '@kbn/tooling-log';
11-
import { pickLevelFromFlags, ToolingLog } from '@kbn/tooling-log';
12-
import type { ProcRunner } from '@kbn/dev-proc-runner';
13-
import { withProcRunner } from '@kbn/dev-proc-runner';
1410
import { createFlagError } from '@kbn/dev-cli-errors';
11+
import { withProcRunner } from '@kbn/dev-proc-runner';
12+
import { ToolingLog, pickLevelFromFlags } from '@kbn/tooling-log';
13+
import { Cleanup } from '../cleanup';
14+
import { DEFAULT_FLAG_ALIASES, getFlags } from '../flags/flags';
15+
import { FlagsReader } from '../flags/flags_reader';
16+
import { getHelp } from '../help';
17+
import { Metrics } from '../metrics';
18+
import type { RunFn, RunOptions } from './types';
19+
import type { FlagOptions, Flags, FlagsOf } from '../flags/types';
1520

16-
import type { Flags, FlagOptions } from './flags';
17-
import { getFlags, DEFAULT_FLAG_ALIASES } from './flags';
18-
import { FlagsReader } from './flags_reader';
19-
import { getHelp } from './help';
20-
import type { CleanupTask } from './cleanup';
21-
import { Cleanup } from './cleanup';
22-
import type { MetricsMeta } from './metrics';
23-
import { Metrics } from './metrics';
24-
25-
export interface RunContext {
26-
log: ToolingLog;
27-
flags: Flags;
28-
procRunner: ProcRunner;
29-
statsMeta: MetricsMeta;
30-
addCleanupTask: (task: CleanupTask) => void;
31-
flagsReader: FlagsReader;
32-
}
33-
export type RunFn<T = void> = (context: RunContext) => Promise<T> | void;
34-
35-
export interface RunOptions {
36-
usage?: string;
37-
description?: string;
38-
log?: {
39-
defaultLevel?: LogLevel;
40-
context?: string;
41-
};
42-
flags?: FlagOptions;
43-
}
21+
export async function run<T, TFlagOptions extends FlagOptions = FlagOptions>(
22+
fn: RunFn<T, FlagsOf<TFlagOptions>>,
23+
options?: RunOptions<TFlagOptions>
24+
): Promise<T | undefined>;
4425

45-
export async function run<T>(fn: RunFn<T>, options: RunOptions = {}): Promise<T | undefined> {
26+
export async function run<T>(
27+
fn: RunFn<T, Flags>,
28+
options: RunOptions = {}
29+
): Promise<T | undefined> {
4630
const flags = getFlags(process.argv.slice(2), options.flags, options.log?.defaultLevel);
4731
const log = new ToolingLog(
4832
{

0 commit comments

Comments
 (0)