Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions profiler-cli/guide.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ CORE WORKFLOW
All samples commands exclude idle by default so percentages reflect active CPU time.
Use --include-idle to include idle samples (e.g. to see what fraction of wall time is idle).

Use --strategy to summarize allocated bytes instead of CPU time on profiles with
allocation tracking. See DATA SOURCES below.

Use --search to focus the call tree on paths containing a specific function:
profiler-cli thread samples-top-down --search GC
profiler-cli thread samples-bottom-up --search "JS::Compile"
Expand Down Expand Up @@ -249,6 +252,29 @@ COUNTERS
profiler-cli counter info c-0


DATA SOURCES

By default the samples and functions commands summarize sample timing. Profiles
recorded with allocation tracking can be summarized by allocated bytes instead.

timing CPU sample counts (default)
js-allocations Bytes of JavaScript allocated
native-retained-allocations Bytes allocated and never freed
native-allocations Bytes allocated, freed or not
native-deallocations-memory Bytes freed, attributed to the allocation site
native-deallocations-sites Bytes freed, attributed to the free site

Allocation sources report bytes, so "total" and "self" read as sizes (e.g. 1.2MB)
rather than sample counts.

profiler-cli thread info Which sources this thread has
profiler-cli thread strategy native-allocations Sticky: applies to later commands
profiler-cli thread samples --strategy js-allocations Ephemeral: one command only

Asking for a source the thread has no data for is an error, so byte-free output
never gets mistaken for allocation data.


JSON OUTPUT

Add --json to any command to get structured JSON output, suitable for piping to jq
Expand Down
31 changes: 29 additions & 2 deletions profiler-cli/schemas.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ SessionContext (present on all command results):
selectedThreadHandle,
selectedThreads: [{ threadIndex, name }],
currentViewRange: { start, startName, end, endName } | null,
rootRange: { start, end }
rootRange: { start, end },
callTreeSummaryStrategy: CallTreeSummaryStrategy
}

CallTreeSummaryStrategy:
"timing" | "js-allocations" | "native-retained-allocations" |
"native-allocations" | "native-deallocations-memory" |
"native-deallocations-sites"

WeightType:
"samples" | "tracing-ms" | "bytes"
selfSamples and totalSamples keep their names under every strategy; read
weightType to know whether they hold samples or bytes. A result's own
callTreeSummaryStrategy is the one it used, which can differ from the
session's in context.

profiler-cli profile info --json
{
Expand Down Expand Up @@ -105,6 +117,16 @@ profiler-cli thread info --json
cpuActivity: [{ startTime, startTimeName, startTimeStr,
endTime, endTimeName, endTimeStr, cpuMs, depthLevel }] | null,
networkActivity: ThreadNetworkSummary | null,
availableStrategies: [CallTreeSummaryStrategy],
context: SessionContext
}

profiler-cli thread strategy --json
{
type: "strategy-select",
threadHandle,
strategy: CallTreeSummaryStrategy,
availableStrategies: [CallTreeSummaryStrategy],
context: SessionContext
}

Expand All @@ -121,6 +143,7 @@ profiler-cli thread samples --json
{
type: "thread-samples",
threadHandle, friendlyThreadName, activeOnly?,
callTreeSummaryStrategy: CallTreeSummaryStrategy, weightType: WeightType,
topFunctionsBySelf: [{ functionHandle, functionIndex, name, nameWithLibrary,
library?, selfSamples, selfPercentage,
totalSamples, totalPercentage }],
Expand All @@ -138,6 +161,7 @@ profiler-cli thread samples-top-down --json
{
type: "thread-samples-top-down",
threadHandle, friendlyThreadName, activeOnly?,
callTreeSummaryStrategy: CallTreeSummaryStrategy, weightType: WeightType,
regularCallTree: CallTreeNode,
activeFilters?, ephemeralFilters?,
context: SessionContext
Expand All @@ -147,6 +171,7 @@ profiler-cli thread samples-bottom-up --json
{
type: "thread-samples-bottom-up",
threadHandle, friendlyThreadName, activeOnly?,
callTreeSummaryStrategy: CallTreeSummaryStrategy, weightType: WeightType,
invertedCallTree: CallTreeNode | null,
activeFilters?, ephemeralFilters?,
context: SessionContext
Expand Down Expand Up @@ -185,6 +210,7 @@ profiler-cli thread functions --json
{
type: "thread-functions",
threadHandle, friendlyThreadName, activeOnly?,
callTreeSummaryStrategy: CallTreeSummaryStrategy, weightType: WeightType,
totalFunctionCount, filteredFunctionCount,
functions: [{ functionHandle, name, nameWithLibrary, library?,
selfSamples, selfPercentage, totalSamples, totalPercentage,
Expand Down Expand Up @@ -233,5 +259,6 @@ profiler-cli status --json
selectedThreads: [{ threadIndex, name }],
viewRanges: [{ start, startName, end, endName }],
rootRange: { start, end },
filterStacks: [{ threadHandle, filters: FilterEntry[] }]
filterStacks: [{ threadHandle, filters: FilterEntry[] }],
callTreeSummaryStrategy: CallTreeSummaryStrategy
}
52 changes: 30 additions & 22 deletions profiler-cli/src/commands/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
*/

import type { Command } from 'commander';
import { addGlobalOptions, runCommand } from './shared';
import {
addGlobalOptions,
addStrategyOption,
parseOptionalStrategyArg,
runCommand,
} from './shared';

export function registerFunctionCommand(
program: Command,
Expand Down Expand Up @@ -43,27 +48,29 @@ export function registerFunctionCommand(
);
});

addGlobalOptions(
fn
.command('annotate [handle]')
.description(
'Show annotated source/assembly with timing data (e.g. f-123)'
)
.option('--function <handle>', 'Function handle')
.option(
'--mode <mode>',
'Annotation mode: src, asm, or all (default: src)',
'src'
)
.option(
'--symbol-server <url>',
'Symbol server URL for asm mode. Defaults to the ?symbolServer= value from the loaded URL, or the Mozilla symbol server.'
)
.option(
'--context <context>',
'Source context: number of lines around annotated lines, or "file" for the whole file (default: 2)',
'2'
)
addStrategyOption(
addGlobalOptions(
fn
.command('annotate [handle]')
.description(
'Show annotated source/assembly with timing data (e.g. f-123)'
)
.option('--function <handle>', 'Function handle')
.option(
'--mode <mode>',
'Annotation mode: src, asm, or all (default: src)',
'src'
)
.option(
'--symbol-server <url>',
'Symbol server URL for asm mode. Defaults to the ?symbolServer= value from the loaded URL, or the Mozilla symbol server.'
)
.option(
'--context <context>',
'Source context: number of lines around annotated lines, or "file" for the whole file (default: 2)',
'2'
)
)
).action(async (handleArg: string | undefined, opts) => {
const funcHandle = handleArg ?? opts.function;
await runCommand(
Expand All @@ -75,6 +82,7 @@ export function registerFunctionCommand(
annotateMode: opts.mode,
symbolServerUrl: opts.symbolServer,
annotateContext: opts.context,
strategy: parseOptionalStrategyArg(opts.strategy),
},
opts
);
Expand Down
35 changes: 34 additions & 1 deletion profiler-cli/src/commands/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { Option } from 'commander';
import { collectStrings } from '../utils/parse';
import { sendCommand } from '../client';
import { formatOutput } from '../output';
import type { ClientCommand } from '../protocol';
import { CALL_TREE_SUMMARY_STRATEGIES } from 'firefox-profiler/profile-query/call-tree-strategy';
import type { ClientCommand, CallTreeSummaryStrategy } from '../protocol';

/**
* Options shared by every command action via `addGlobalOptions`.
Expand Down Expand Up @@ -77,6 +78,38 @@ export function parseFloatArg(
return v;
}

/**
* Parse a --strategy value and exit with an error if it is not a valid strategy.
*/
export function parseStrategyArg(value: string): CallTreeSummaryStrategy {
if (!(CALL_TREE_SUMMARY_STRATEGIES as string[]).includes(value)) {
console.error(
`Error: --strategy must be one of: ${CALL_TREE_SUMMARY_STRATEGIES.join(', ')}`
);
process.exit(1);
}
return value as CallTreeSummaryStrategy;
}

/**
* As parseStrategyArg, but for the optional --strategy flag.
*/
export function parseOptionalStrategyArg(
value: string | undefined
): CallTreeSummaryStrategy | undefined {
return value === undefined ? undefined : parseStrategyArg(value);
}

/**
* Add the --strategy option to a command.
*/
export function addStrategyOption(cmd: Command): Command {
return cmd.option(
'--strategy <name>',
`Data source to summarize: ${CALL_TREE_SUMMARY_STRATEGIES.join(', ')}. Allocation strategies report bytes instead of samples.`
);
}

/**
* Returns true if the given subcommand was explicitly typed by the user.
* Used to decide whether to print a "other subcommands" hint after a default action.
Expand Down
79 changes: 56 additions & 23 deletions profiler-cli/src/commands/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import { parseEphemeralFilters } from '../utils/parse';
import {
addGlobalOptions,
addSampleFilterOptions,
addStrategyOption,
parseIntArg,
parseFloatArg,
parseOptionalStrategyArg,
parseStrategyArg,
runCommand,
} from './shared';
import { CALL_TREE_SUMMARY_STRATEGIES } from 'firefox-profiler/profile-query/call-tree-strategy';
import type {
CallTreeScoringStrategy,
MarkerFilterOptions,
Expand All @@ -32,23 +36,25 @@ const VALID_SCORING_STRATEGIES: CallTreeScoringStrategy[] = [
];

function addSamplesOptions(cmd: Command): Command {
return addSampleFilterOptions(
addGlobalOptions(cmd)
.option('--thread <handle>', 'Thread handle (e.g. t-0)')
.option('--include-idle', 'Include idle samples in percentages')
.option(
'--search <term>',
'Keep samples containing this substring in any frame. Comma-separates multiple terms, all must match (AND).'
)
.option('--limit <N>', 'Limit the number of results shown')
return addStrategyOption(
addSampleFilterOptions(
addGlobalOptions(cmd)
.option('--thread <handle>', 'Thread handle (e.g. t-0)')
.option('--include-idle', 'Include idle samples in percentages')
.option(
'--search <term>',
'Keep samples containing this substring in any frame. Comma-separates multiple terms, all must match (AND).'
)
.option('--limit <N>', 'Limit the number of results shown')
)
);
}

function addCallTreeOptions(cmd: Command): Command {
return addSamplesOptions(cmd)
.option('--max-lines <N>', 'Maximum nodes in call tree (default: 100)')
.option(
'--scoring <strategy>',
'--scoring <name>',
`Call tree scoring strategy: ${VALID_SCORING_STRATEGIES.join(', ')}`
);
}
Expand Down Expand Up @@ -116,6 +122,27 @@ export function registerThreadCommand(
);
});

// thread strategy
addGlobalOptions(
thread
.command('strategy <name>')
.description(
`Set the data source for samples and functions commands: ${CALL_TREE_SUMMARY_STRATEGIES.join(', ')}`
)
.option('--thread <handle>', 'Thread handle (e.g. t-0)')
).action(async (nameArg: string, opts) => {
await runCommand(
sessionDir,
{
command: 'thread',
subcommand: 'strategy',
thread: opts.thread,
strategy: parseStrategyArg(nameArg),
},
opts
);
});

// thread samples
addSamplesOptions(
thread
Expand All @@ -131,6 +158,7 @@ export function registerThreadCommand(
thread: opts.thread,
includeIdle: opts.includeIdle || undefined,
search: opts.search,
strategy: parseOptionalStrategyArg(opts.strategy),
sampleFilters: sampleFilters.length ? sampleFilters : undefined,
},
opts
Expand All @@ -152,6 +180,7 @@ export function registerThreadCommand(
thread: opts.thread,
includeIdle: opts.includeIdle || undefined,
search: opts.search,
strategy: parseOptionalStrategyArg(opts.strategy),
callTreeOptions: parseCallTreeOptions(opts),
sampleFilters: sampleFilters.length ? sampleFilters : undefined,
},
Expand All @@ -174,6 +203,7 @@ export function registerThreadCommand(
thread: opts.thread,
includeIdle: opts.includeIdle || undefined,
search: opts.search,
strategy: parseOptionalStrategyArg(opts.strategy),
callTreeOptions: parseCallTreeOptions(opts),
sampleFilters: sampleFilters.length ? sampleFilters : undefined,
},
Expand Down Expand Up @@ -418,19 +448,21 @@ export function registerThreadCommand(
});

// thread functions
addSampleFilterOptions(
addGlobalOptions(
thread
.command('functions')
.description('List all functions with CPU percentages')
.option('--thread <handle>', 'Thread handle (e.g. t-0)')
.option('--search <term>', 'Filter by substring')
.option(
'--min-self <percent>',
'Filter by minimum self time percentage'
)
.option('--limit <N>', 'Limit the number of results shown')
.option('--include-idle', 'Include idle samples in percentages')
addStrategyOption(
addSampleFilterOptions(
addGlobalOptions(
thread
.command('functions')
.description('List all functions with CPU percentages')
.option('--thread <handle>', 'Thread handle (e.g. t-0)')
.option('--search <term>', 'Filter by substring')
.option(
'--min-self <percent>',
'Filter by minimum self time percentage'
)
.option('--limit <N>', 'Limit the number of results shown')
.option('--include-idle', 'Include idle samples in percentages')
)
)
).action(async (opts) => {
let functionFilters: FunctionFilterOptions | undefined;
Expand Down Expand Up @@ -467,6 +499,7 @@ export function registerThreadCommand(
subcommand: 'functions',
thread: opts.thread,
includeIdle: opts.includeIdle || undefined,
strategy: parseOptionalStrategyArg(opts.strategy),
functionFilters,
sampleFilters: sampleFilters.length ? sampleFilters : undefined,
},
Expand Down
Loading
Loading