Skip to content
Merged
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
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ devtools-profiler summarize \
/path/to/session
```

JSON responses include a `cliCommand` field for the command that can reproduce
the same analysis selection.

Important result sections:

- `overallProfile`: the whole run from process start to process exit.
Expand All @@ -289,6 +292,7 @@ Important result sections:
need to find the caller chain that led there.
- `methodTable`: DevTools-style method context with callers and callees.
- `memory`: heap and allocation summary when memory capture was available.
- `classes`: memory class rows from `inspect-classes`.
- `regressions` and `trends`: comparison output for reasoning across sessions.

Filtering options keep the output readable:
Expand Down Expand Up @@ -458,6 +462,20 @@ devtools-profiler inspect \
Inspection shows self cost, inclusive cost, callers, callees, and representative
paths.

### Inspect Memory Classes

```bash
devtools-profiler inspect-classes \
--json \
--class Cart \
--min-live-bytes 1048576 \
/path/to/session
```

`inspect-classes` re-reads the stored memory artifact and reports retained class
rows, live instances, and allocation deltas. Use `--limit 0` for an unlimited
class list.

### Compare Two Runs

```bash
Expand Down Expand Up @@ -532,6 +550,7 @@ Commands:
- `compare <baseline> <current>` compares two profiles or sessions.
- `trends <path>...` analyzes a sequence of profiles or sessions.
- `inspect <path>` inspects one method in one profile.
- `inspect-classes <path>` inspects memory classes in one profile.
- `search-methods <path>` searches methods in one profile.
- `compare-method <baseline> <current>` compares one method across two profiles.
- `mcp` starts the local stdio MCP server.
Expand All @@ -554,6 +573,10 @@ Common presentation flags:
- `--tree-depth <n>` controls call-tree depth. `0` means unlimited.
- `--tree-children <n>` controls children per tree node. `0` means unlimited.
- `--method-limit <n>` controls method rows and relations. `0` means unlimited.
- `--min-live-bytes <n>` filters memory class rows for `compare` and
`inspect-classes`.
- `--memory-class-limit <n>` controls compared memory class rows for `compare`.
`0` means unlimited.

`run` options:

Expand All @@ -579,6 +602,7 @@ Path arguments accepted by read/analyze commands:
- a session directory
- a region `summary.json`
- a raw `cpu_profile.json`
- a raw `memory_profile.json` for memory-class inspection

## MCP For AI Agents

Expand All @@ -605,7 +629,8 @@ Tools by workflow:
`profile_get_session`, `profile_list_regions`, `profile_get_region`.
- Read artifacts: `profile_summarize`, `profile_read_artifact`.
- Explain and drill down: `profile_explain_hotspots`,
`profile_search_methods`, `profile_inspect_method`.
`profile_search_methods`, `profile_inspect_method`,
`profile_inspect_classes`.
- Compare: `profile_compare`, `profile_compare_method`,
`profile_find_regressions`, `profile_analyze_trends`.

Expand All @@ -618,7 +643,8 @@ Useful agent pattern:
3. If regions exist, call `profile_explain_hotspots` for the hottest region.
4. Use `profile_search_methods` and `profile_inspect_method` for named
functions mentioned by the explanation.
5. Use `profile_compare` or `profile_find_regressions` after a code change.
5. Use `profile_inspect_classes` when memory summaries show retained growth.
6. Use `profile_compare` or `profile_find_regressions` after a code change.

Most read-only tools accept either direct paths or stored-session selectors:

Expand Down
17 changes: 17 additions & 0 deletions packages/devtools_profiler_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## 0.2.0-wip

- Added `inspect-classes` and the `profile_inspect_classes` MCP tool for
inspecting memory class allocations from stored artifacts.
- Added comparison filters for memory class output, including minimum live bytes
and memory class count limits.
- Added `attach --skip-dtd` and the matching MCP option for whole-session attach
profiling when explicit region markers are unavailable.
- Improved CLI and JSON output by surfacing region preparation warnings,
baseline/current comparison warnings, and sample-count fallback warnings.
- Added warnings when active frame filters remove every CPU frame, plus
reproduction blocks and matching CLI commands in agent-facing JSON responses.
- Improved memory summary tables to show live bytes, live instances, new
instances, and allocation deltas without requiring external JSON tools.
- Improved package-filtered output for local checkout frames when used with a
backend that recognizes local package file paths.

## 0.1.0

- Initial release of the terminal and MCP profiler frontend.
Expand Down
21 changes: 21 additions & 0 deletions packages/devtools_profiler_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ A session can contain:
If the target app has no marked regions, the CLI still captures the whole
session.

JSON responses include a `cliCommand` field for the command that can reproduce
the same analysis selection.

## Read Existing Artifacts

Summarize a session:
Expand Down Expand Up @@ -177,6 +180,19 @@ devtools-profiler inspect \
/path/to/session
```

Inspect memory classes:

```bash
devtools-profiler inspect-classes \
--json \
--class String \
--min-live-bytes 1048576 \
/path/to/session
```

Use `--limit 0` for an unlimited class list. The command can read a session
directory, a region `summary.json`, or a raw `memory_profile.json` artifact.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Compare two sessions:

```bash
Expand Down Expand Up @@ -214,6 +230,10 @@ devtools-profiler trends \
duration. Examples: `15s`, `2m`, `500ms`.
- `--vm-service-timeout <duration>` controls startup wait time before the VM
service is available. Examples: `3m`, `300s`.
- `--min-live-bytes <n>` filters memory class rows for `compare` and
`inspect-classes`.
- `--memory-class-limit <n>` controls compared memory class rows for `compare`.
`0` means unlimited.
Comment thread
kingwill101 marked this conversation as resolved.

Commands that operate on one profile use `--profile-id overall` for the
whole-session profile or a generated region id for a marked region. Region names
Expand Down Expand Up @@ -250,6 +270,7 @@ Agent-facing tools include:
- `profile_explain_hotspots`
- `profile_search_methods`
- `profile_inspect_method`
- `profile_inspect_classes`
- `profile_compare`
- `profile_compare_method`
- `profile_find_regressions`
Expand Down
1 change: 1 addition & 0 deletions packages/devtools_profiler_cli/lib/src/cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Future<int> runCli(
..addCommand(InspectCommand(profiler))
..addCommand(CompareMethodCommand(profiler))
..addCommand(SearchMethodsCommand(profiler))
..addCommand(InspectClassesCommand(profiler))
..addCommand(McpCommand(profiler));

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ class CompareCommand extends ProfilerCommand {
..addOption(
'current-profile-id',
help: 'Profile id to select from the current session directory.',
)
..addOption(
'min-live-bytes',
help:
'Re-read raw memory artifacts and include only classes with at '
'least this many live bytes at the end of each capture window. '
'Useful for surfacing large retained classes missed by the stored '
'top-class list.',
)
..addOption(
'memory-class-limit',
help:
'Maximum memory classes to compare. Use 0 for unlimited. '
'When set, re-reads raw memory artifacts to expand beyond the '
'stored top-class list.',
);
}

Expand All @@ -27,6 +42,16 @@ class CompareCommand extends ProfilerCommand {
@override
String get description => 'Compare two session/profile artifacts.';

@override
String formatUsage({bool includeDescription = true}) => usageWithExamples(
super.formatUsage(includeDescription: includeDescription),
const [
'devtools-profiler compare path/to/baseline path/to/current',
'devtools-profiler compare --method-table path/to/baseline path/to/current',
'devtools-profiler compare --min-live-bytes 524288 path/to/baseline path/to/current',
],
);
Comment thread
kingwill101 marked this conversation as resolved.

@override
Future<int> run() async {
if (argResults!.rest.length != 2) {
Expand All @@ -36,12 +61,25 @@ class CompareCommand extends ProfilerCommand {
}

final options = presentationOptions;

final memoryClassLimitStr = argResults!['memory-class-limit'] as String?;
final memoryClassLimitSpecified = memoryClassLimitStr != null;

final comparison = await prepareProfileComparison(
profileRunner,
baselinePath: argResults!.rest.first,
currentPath: argResults!.rest.last,
baselineProfileId: argResults!['baseline-profile-id'] as String?,
currentProfileId: argResults!['current-profile-id'] as String?,
minLiveBytes: parseNonNegativeInt(
argResults!['min-live-bytes'] as String?,
optionName: 'min-live-bytes',
),
memoryClassLimit: parseLimit(
memoryClassLimitStr,
optionName: 'memory-class-limit',
),
memoryClassLimitSpecified: memoryClassLimitSpecified,
options: options,
);

Expand Down Expand Up @@ -280,3 +318,80 @@ class SearchMethodsCommand extends ProfilerCommand {
return successExitCode;
}
}

/// Command that inspects memory class data in a stored profile artifact.
class InspectClassesCommand extends ProfilerCommand {
/// Creates an inspect-classes command.
InspectClassesCommand(super.profileRunner) {
argParser
..addOption(
'class',
help:
'Filter to classes whose name contains this query (case-insensitive).',
)
..addOption(
'min-live-bytes',
help:
'Only include classes with at least this many live bytes at the '
'end of the capture window.',
)
..addOption(
'limit',
defaultsTo: '$defaultMemoryClassLimit',
help: 'Maximum classes to show. Use 0 for unlimited.',
);
}

@override
String get name => 'inspect-classes';

@override
String get description =>
'Inspect memory class data in a stored session or region artifact.';

@override
String get invocation =>
'${runner!.executableName} inspect-classes [options] <path>';

@override
String formatUsage({bool includeDescription = true}) => usageWithExamples(
super.formatUsage(includeDescription: includeDescription),
const [
'devtools-profiler inspect-classes path/to/session',
'devtools-profiler inspect-classes --class LoveColor path/to/session',
'devtools-profiler inspect-classes --min-live-bytes 1048576 path/to/session',
],
);

@override
Future<int> run() async {
if (argResults!.rest.length != 1) {
usageException(
'Inspect-classes requires exactly one session directory or '
'profile artifact path.',
);
}

final limitStr = argResults!['limit'] as String;
final limit = parseLimit(limitStr, optionName: 'limit');

final inspection = await prepareMemoryClassInspection(
profileRunner,
argResults!.rest.single,
classQuery: argResults!['class'] as String?,
minLiveBytes: parseNonNegativeInt(
argResults!['min-live-bytes'] as String?,
optionName: 'min-live-bytes',
),
topClassCount: limit ?? 0,
);

if (printJson) {
writeJson(memoryClassInspectionJson(inspection));
} else {
writeMemoryClassInspection(io, inspection);
}

return successExitCode;
}
}
Comment thread
kingwill101 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SummarizeCommand extends ProfilerCommand {
prepared.callTree,
prepared.bottomUpTree,
prepared.methodTable,
warnings: prepared.warnings,
),
);
} else {
Expand All @@ -83,6 +84,7 @@ class SummarizeCommand extends ProfilerCommand {
bottomUpTree: prepared.bottomUpTree,
methodTable: prepared.methodTable,
workingDirectory: workingDirectoryFromRegionPath(prepared.region),
warnings: prepared.warnings,
options: options,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import '../constants.dart';
import '../options.dart';
import 'profiler_command.dart';

const _attachRegionWarning =
'Attach mode captures the existing VM-service process, but explicit '
'devtools_region_profiler markers are unavailable unless the target was '
'launched by devtools-profiler run.';

/// Command that launches and profiles a Dart or Flutter process.
class RunCommand extends ProfilerCommand {
/// Creates a run command.
Expand Down Expand Up @@ -141,6 +146,15 @@ class AttachCommand extends ProfilerCommand {
'duration',
help:
'Required profiling duration. Supports raw seconds, "10s", "500ms", or "2m".',
)
..addFlag(
'skip-dtd',
defaultsTo: false,
negatable: false,
help:
'Skip the Dart Tooling Daemon for this attach session. '
'Explicit region markers will be unavailable. Use this when the '
'tooling daemon fails to start or is not needed.',
);
}

Expand All @@ -161,6 +175,7 @@ class AttachCommand extends ProfilerCommand {
const [
'devtools-profiler attach --duration 15s http://127.0.0.1:8181/abcd/',
'devtools-profiler attach --duration 30s --call-tree --hide-sdk http://127.0.0.1:8181/abcd/',
'devtools-profiler attach --skip-dtd --duration 30s http://127.0.0.1:8181/abcd/',
],
);

Expand All @@ -183,12 +198,14 @@ class AttachCommand extends ProfilerCommand {
);
}

io.writelnErr('Warning: $_attachRegionWarning');
final session = await profileRunner.attach(
ProfileAttachRequest(
artifactDirectory: argResults!['artifact-dir'] as String?,
duration: duration,
vmServiceUri: parseVmServiceUriArgument(argResults!.rest.single),
workingDirectory: argResults!['cwd'] as String?,
enableDtd: !(argResults!['skip-dtd'] as bool),
),
);
final options = presentationOptions;
Expand Down Expand Up @@ -228,18 +245,6 @@ class AttachCommand extends ProfilerCommand {
}
}

/// Appends a stable examples section to a formatted command usage string.
String usageWithExamples(String usage, List<String> examples) {
final buffer = StringBuffer(usage.trimRight())
..writeln()
..writeln()
..writeln('Examples:');
for (final example in examples) {
buffer.writeln(' $example');
}
return buffer.toString().trimRight();
}

/// Command that starts the stdio MCP server.
class McpCommand extends Command<int> {
/// Creates an MCP command.
Expand Down
Loading
Loading