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
28 changes: 28 additions & 0 deletions profiler-cli/guide.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ HANDLE SYSTEM
t-0, t-1 Thread handles (from "profile info")
m-1234 Marker handles (from "thread markers")
f-12 Function handles (from "thread samples", "thread functions")
c-0, c-1 Counter handles (from "counter list" or "profile info")
ts-6 Timestamp handles (named points in time, usable with "zoom push")

Handle lifetime and stability:
Expand All @@ -127,6 +128,8 @@ HANDLE SYSTEM
m-N thread markers No -- rebuilt each time the daemon starts
f-N thread samples, Yes -- direct index into the profile's function
thread functions table; same profile always yields the same f-N
c-N counter list Yes -- direct index into the profile's counter
array; same profile always yields the same c-N
ts-N thread markers No -- position-based, session-scoped
──────────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -215,6 +218,31 @@ FILTERS
profiler-cli filter push --during-marker --search Paint


COUNTERS

Counters are time series the profiler records alongside samples: memory usage,
network bandwidth, process CPU, power, and similar. Each counter has a handle
(c-0, c-1, ...) and carries its own display metadata (label, unit, graph type).

profiler-cli counter list List all counters with one-line summaries
profiler-cli counter info c-0 Detailed info and stats for one counter

Counters also appear in "profile info", listed under their owning process
next to that process's threads (much like the timeline track list).

The stats shown come from the counter's own tooltip schema, so they match the
timeline tooltips. Each counter reports its whole-range aggregates, e.g. the
memory range for Memory, data transferred for Bandwidth, or energy used (with a
CO2e estimate) for Power.

All counter stats respect the current zoom: with no zoom they cover the whole
profile; after "zoom push" they cover the committed range. Combine with zoom to
see, for example, how much memory a specific time window allocated:

profiler-cli zoom push 2.7,3.1
profiler-cli counter info c-0


JSON OUTPUT

Add --json to any command to get structured JSON output, suitable for piping to jq
Expand Down
29 changes: 28 additions & 1 deletion profiler-cli/schemas.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,39 @@ profiler-cli profile info --json
processes: [{
pid, name, cpuMs,
threads: [{ threadHandle, threadIndex, name, tid, cpuMs }],
remainingThreads?: { count, combinedCpuMs, maxCpuMs }
remainingThreads?: { count, combinedCpuMs, maxCpuMs },
counters?: [CounterSummary]
}],
remainingProcesses?: { count, combinedCpuMs, maxCpuMs },
context: SessionContext
}

CounterSummary:
{
counterHandle, counterIndex, name, label, category,
unit, graphType,
color, pid, mainThreadIndex, mainThreadHandle, mainThreadName,
rangeSampleCount,
stats: [{ source, label, labelKey?, value, formattedValue, carbon? }]
}

profiler-cli counter list --json
{
type: "counter-list",
counters: [CounterSummary],
context: SessionContext
}

profiler-cli counter info --json
{
type: "counter-info",
...CounterSummary,
description,
sampleCount,
rangeStart, rangeEnd,
context: SessionContext
}

profiler-cli thread samples --json
{
type: "thread-samples",
Expand Down
49 changes: 49 additions & 0 deletions profiler-cli/src/commands/counter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
* `profiler-cli counter` command.
*/

import type { Command } from 'commander';
import { sendCommand } from '../client';
import { formatOutput } from '../output';
import { addGlobalOptions } from './shared';

export function registerCounterCommand(
program: Command,
sessionDir: string
): void {
const counter = program
.command('counter')
.description('Counter-level commands');

addGlobalOptions(
counter
.command('list')
.description('List all counters with one-line summaries')
).action(async (opts) => {
const result = await sendCommand(
sessionDir,
{ command: 'counter', subcommand: 'list' },
opts.session
);
console.log(formatOutput(result, opts.json ?? false));
});

addGlobalOptions(
counter
.command('info [handle]')
.description('Show detailed information about a counter (e.g. c-0)')
.option('--counter <handle>', 'Counter handle')
).action(async (handleArg: string | undefined, opts) => {
const counterHandle = handleArg ?? opts.counter;
const result = await sendCommand(
sessionDir,
{ command: 'counter', subcommand: 'info', counter: counterHandle },
opts.session
);
console.log(formatOutput(result, opts.json ?? false));
});
}
12 changes: 12 additions & 0 deletions profiler-cli/src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,18 @@ export class Daemon {
default:
throw assertExhaustiveCheck(command);
}
case 'counter':
switch (command.subcommand) {
case 'list':
return this.querier!.counterList();
case 'info':
if (!command.counter) {
throw new Error('counter handle required for counter info');
}
return this.querier!.counterInfo(command.counter);
default:
throw assertExhaustiveCheck(command);
}
case 'sample':
switch (command.subcommand) {
case 'info':
Expand Down
86 changes: 86 additions & 0 deletions profiler-cli/src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import type {
SampleFilterSpec,
ProfileLogsResult,
ThreadSelectResult,
CounterSummary,
CounterListResult,
CounterInfoResult,
} from './protocol';
import { truncateFunctionName } from '../../src/profile-query/function-list';
import { describeSpec } from '../../src/profile-query/filter-stack';
Expand Down Expand Up @@ -428,6 +431,10 @@ Name: ${result.name}\n`;
if (process.remainingThreads) {
output += ` + ${process.remainingThreads.count} more threads with combined CPU time ${process.remainingThreads.combinedCpuMs.toFixed(3)}ms and max CPU time ${process.remainingThreads.maxCpuMs.toFixed(3)}ms (use --all to see all)\n`;
}

for (const counter of process.counters ?? []) {
output += ` ${counter.counterHandle}: ${counter.label}${formatCounterStats(counter)}\n`;
}
}

if (result.remainingProcesses) {
Expand All @@ -451,6 +458,85 @@ Name: ${result.name}\n`;
return output;
}

function formatCounterStatInline(
stat: CounterSummary['stats'][number]
): string {
const value = stat.carbon
? `${stat.formattedValue} (${stat.carbon})`
: stat.formattedValue;
return `${stat.label}: ${value}`;
}

/** The ` - stat; stat [N samples]` trailer shared by counter list and profile info. */
function formatCounterStats(counter: CounterSummary): string {
const stats =
counter.stats.length > 0
? ` - ${counter.stats.map(formatCounterStatInline).join('; ')}`
: '';
return `${stats} [${counter.rangeSampleCount} samples]`;
}

function formatCounterSummaryLine(counter: CounterSummary): string {
return ` ${counter.counterHandle}: ${counter.label} (${counter.category})${formatCounterStats(counter)}`;
}

/**
* Format a CounterListResult as plain text.
*/
export function formatCounterListResult(
result: WithContext<CounterListResult>
): string {
const contextHeader = formatContextHeader(result.context);
if (result.counters.length === 0) {
return `${contextHeader}\n\nNo counters in this profile.`;
}
const lines = result.counters.map(formatCounterSummaryLine);
return `${contextHeader}\n\nCounters (${result.counters.length}):\n${lines.join('\n')}`;
}

/**
* Format a CounterInfoResult as plain text.
*/
export function formatCounterInfoResult(
result: WithContext<CounterInfoResult>
): string {
const contextHeader = formatContextHeader(result.context);
const lines = [
contextHeader,
'',
`Counter ${result.counterHandle}: ${result.label}`,
` Name: ${result.name}`,
` Category: ${result.category}`,
];
if (result.description) {
lines.push(` Description: ${result.description}`);
}
lines.push(` Unit: ${result.unit || '(none)'}`);
lines.push(` Graph type: ${result.graphType}`);
lines.push(
` Main thread: ${result.mainThreadHandle} (${result.mainThreadName})`
);
lines.push(
` Samples: ${result.sampleCount} total, ${result.rangeSampleCount} in current range`
);
if (result.rangeStart !== null && result.rangeEnd !== null) {
const zeroAt = result.context.rootRange.start;
lines.push(
` Time span: ${formatDuration(result.rangeStart - zeroAt)} → ${formatDuration(result.rangeEnd - zeroAt)}`
);
}
if (result.stats.length > 0) {
lines.push(' Stats (current range):');
for (const stat of result.stats) {
const value = stat.carbon
? `${stat.formattedValue} (${stat.carbon})`
: stat.formattedValue;
lines.push(` ${stat.label}: ${value}`);
}
}
return lines.join('\n');
}

/**
* Helper function to format a call tree node recursively.
*
Expand Down
4 changes: 4 additions & 0 deletions profiler-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { registerProfileCommand } from './commands/profile';
import { registerThreadCommand } from './commands/thread';
import { registerMarkerCommand } from './commands/marker';
import { registerFunctionCommand } from './commands/function';
import { registerCounterCommand } from './commands/counter';
import { registerZoomCommand } from './commands/zoom';
import { registerFilterCommand } from './commands/filter';
import { registerSessionCommand } from './commands/session';
Expand Down Expand Up @@ -85,6 +86,8 @@ Examples:
profiler-cli thread samples
profiler-cli thread functions --search GC --min-self 1
profiler-cli thread markers --search DOMEvent --category Graphics
profiler-cli counter list
profiler-cli counter info c-0
profiler-cli zoom push 2.7,3.1
profiler-cli filter push --excludes-function f-184
profiler-cli status
Expand Down Expand Up @@ -180,6 +183,7 @@ Examples:
registerThreadCommand(program, SESSION_DIR);
registerMarkerCommand(program, SESSION_DIR);
registerFunctionCommand(program, SESSION_DIR);
registerCounterCommand(program, SESSION_DIR);
registerZoomCommand(program, SESSION_DIR);
registerFilterCommand(program, SESSION_DIR);
registerSessionCommand(program, SESSION_DIR);
Expand Down
6 changes: 6 additions & 0 deletions profiler-cli/src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
formatProfileLogsResult,
formatThreadPageLoadResult,
formatThreadSelectResult,
formatCounterListResult,
formatCounterInfoResult,
} from './formatters';

/**
Expand Down Expand Up @@ -88,6 +90,10 @@ export function formatOutput(
return formatThreadPageLoadResult(result);
case 'thread-select':
return formatThreadSelectResult(result);
case 'counter-list':
return formatCounterListResult(result);
case 'counter-info':
return formatCounterInfoResult(result);
default:
throw assertExhaustiveCheck(result);
}
Expand Down
14 changes: 13 additions & 1 deletion profiler-cli/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export type {
ProfileInfoResult,
ProfileLogsResult,
ThreadSelectResult,
CounterSummary,
CounterListResult,
CounterInfoResult,
} from '../../src/profile-query/types';
export type { CallTreeCollectionOptions } from '../../src/profile-query/formatters/call-tree';

Expand Down Expand Up @@ -79,6 +82,8 @@ import type {
FilterStackResult,
ProfileLogsResult,
ThreadSelectResult,
CounterListResult,
CounterInfoResult,
} from '../../src/profile-query/types';
import type { CallTreeCollectionOptions } from '../../src/profile-query/formatters/call-tree';

Expand Down Expand Up @@ -141,6 +146,11 @@ export type ClientCommand =
subcommand: 'info' | 'select' | 'stack';
marker?: string;
}
| {
command: 'counter';
subcommand: 'list' | 'info';
counter?: string;
}
| { command: 'sample'; subcommand: 'info' | 'select'; sample?: string }
| {
command: 'function';
Expand Down Expand Up @@ -195,7 +205,9 @@ export type CommandResult =
| WithContext<FunctionAnnotateResult>
| WithContext<ProfileLogsResult>
| WithContext<ThreadPageLoadResult>
| WithContext<ThreadSelectResult>;
| WithContext<ThreadSelectResult>
| WithContext<CounterListResult>
| WithContext<CounterInfoResult>;

export interface SessionMetadata {
id: string;
Expand Down
Loading
Loading