Skip to content

Improve profiler artifact diagnostics - #2

Merged
kingwill101 merged 13 commits into
mainfrom
fix/local-package-profile-frames
Apr 26, 2026
Merged

kingwill101 merged 13 commits into
mainfrom
fix/local-package-profile-frames

Conversation

@kingwill101

@kingwill101 kingwill101 commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Accept per-profile artifact directories in summarize and resolve run/attach artifact paths before reporting them.
  • Add pre-capture attach guidance, frame-filter warnings, reproduce commands, and cliCommand hints in JSON/MCP output.
  • Keep generated cliCommand profile selectors and memory-class flags copyable for artifact targets, region-scoped trends, comparisons, and inspect-classes.
  • Normalize CLI/MCP memory-class limits, reject negative memory thresholds, surface raw-memory re-read fallback warnings, and cover inspect-classes in CLI/MCP tests.
  • Harden memory artifact handling for memory-only directories, session.json targets, malformed raw memory payloads, and override-aware comparison warnings.
  • Document memory-class inspection in the root README, CLI README, and local profiler skill.
  • Improve memory class summary columns and start the changed profiler packages at 0.2.0-wip.

Validation

  • dart format .
  • dart analyze .
  • dart test packages/devtools_profiler_protocol
  • dart test packages/devtools_region_profiler
  • dart test packages/devtools_profiler_core
  • dart test packages/devtools_profiler_cli
  • git diff --check

Summary by CodeRabbit

  • New Features

    • New inspect-classes command to list and filter memory classes; memory-class inspection output added
    • New CLI flags: --min-live-bytes and --memory-class-limit; --skip-dtd attach mode
  • Enhancements

    • Memory summaries show live bytes, instance counts, and allocation deltas
    • JSON now includes a reproducible cliCommand
    • Broader warning visibility and sample-count fallback detection
    • Better local package frame detection in CPU profiles
  • Tests / Docs

    • Updated tests and documentation for new workflows and flags

Add a warnings list to PreparedRegionPresentation and propagate it
through prepareRegionPresentation and prepareSessionPresentation. When
a re-read CPU profile yields 0 samples but the stored region summary
reports a positive count (and no frame filter is active), fall back to
the stored summary and emit a warning explaining the mismatch. The
presentation JSON now includes an optional 'preparationWarnings' field
for comparisons, explanations, inspections, and searches when warnings
are present.
- Add enableDtd: bool = true to ProfileAttachRequest so callers can
  skip the Dart Tooling Daemon when region markers are not needed or
  when DTD startup times out (common cause of attach failures).

- Make DartToolingDaemon? nullable in ProfileSessionContext;
  registerServices() and postRegionEvent() become no-ops when dtd
  is null, so whole-session VM-service capture still works without DTD.

- profile_runner.attach() conditionally starts the DTD process based
  on request.enableDtd and records a session warning when DTD is
  skipped.

- Wire --skip-dtd flag into AttachCommand (CLI) and skipDtd boolean
  into profileAttachTool (MCP), both defaulting to DTD enabled.

- Add two integration tests for the prepareRegionPresentation
  sample-count fallback: one that asserts the stored count and warning
  text appear when raw CPU samples re-read as 0, and one that asserts
  no warning is emitted for a healthy profile.
Add the ability to re-read raw memory artifacts and query the full
class list, addressing the gap where only the top-N classes from a
capture were accessible through the existing CLI/MCP surface.

Core (devtools_profiler_core):
- memory_profile_summary.dart: add rebuildMemoryProfileFromArtifact
  and readMemoryClassesFromArtifact, which parse a stored
  ProfileMemoryArtifact JSON file back into AllocationProfile/
  ClassHeapStats objects and call summarizeMemoryProfile with an
  optional includeClass predicate and configurable topClassCount.
- artifacts.dart: add ProfileArtifacts.readMemoryClasses which
  resolves a session directory, region summary.json, or raw
  memory_profile.json to the correct raw artifact path, builds an
  optional class-name / min-live-bytes predicate, and delegates to
  readMemoryClassesFromArtifact.
- profile_runner.dart: expose readMemoryClasses as a public
  ProfileRunner method.

CLI/presentation (devtools_profiler_cli):
- models.dart: add PreparedMemoryClassInspection.
- preparation.dart: add prepareMemoryClassInspection.
- json.dart: add memoryClassInspectionJson.
- terminal.dart: add writeMemoryClassInspection with a class table
  (Class / Library / Live / Live delta / Instances / Inst delta /
  Alloc delta) and heap delta summary header.
- analysis_commands.dart: add InspectClassesCommand (--class,
  --min-live-bytes, --limit options; examples in help output).
- cli.dart: register InspectClassesCommand.
- constants.dart: move usageWithExamples here so all command files
  can share it (removed duplicate from capture_commands.dart).

MCP (devtools_profiler_cli):
- analysis_tools.dart: add profileInspectClassesTool.
- tool_handlers.dart: add profileInspectClasses handler.
- server.dart: register profileInspectClassesTool.
…ss-limit

The compare command previously only diffed the top-N classes stored in
each session summary (default 10, sorted by allocationBytesDelta).
Classes with large stable retained size but small allocation delta were
invisible to the comparison.

Core (devtools_profiler_core):
- profile_region_comparison.dart: add baselineMemoryOverride and
  currentMemoryOverride parameters to compareProfileRegions. When
  supplied, they replace the stored region.memory for the comparison;
  otherwise the stored values are used unchanged.

CLI/presentation (devtools_profiler_cli):
- preparation.dart: add minLiveBytes and memoryClassLimit parameters
  to prepareProfileComparison. When either is set, attempt to re-read
  both raw memory artifacts via runner.readMemoryClasses and pass the
  expanded ProfileMemoryResult objects as overrides; silently fall
  back to stored classes if the raw artifact is unavailable. Also fix
  a latent bug where options.frameLimit (CPU frame limit) was used as
  the memory class limit.
- analysis_commands.dart: add --min-live-bytes and --memory-class-limit
  options to CompareCommand; add usage examples.

MCP (devtools_profiler_cli):
- analysis_tools.dart: add minLiveBytes and memoryClassLimit to
  profileCompareTool input schema.
- tool_handlers.dart: read and forward both fields in profileCompare.
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds memory-class inspection (CLI + MCP), memory-class filters/limits for comparisons, an attach-mode option to skip the Dart Tooling Daemon, broader artifact/raw-memory resolution, propagation of preparation warnings into JSON/terminal output, improved memory-summary and reproduction output, better local package-frame detection, and package version bumps to 0.2.0-wip.

Changes

Cohort / File(s) Summary
Changelog & Version Bumps
packages/devtools_profiler_cli/CHANGELOG.md, packages/devtools_profiler_core/CHANGELOG.md, packages/devtools_profiler_cli/pubspec.yaml, packages/devtools_profiler_core/pubspec.yaml
Bumped packages to 0.2.0-wip and documented new inspect-classes, memory-class filters/limits, skip-DTD attach flow, warnings propagation, memory-summary changes, reproduction output, and artifact resolution changes.
CLI Commands & Options
packages/devtools_profiler_cli/lib/src/cli.dart, .../analysis_commands.dart, .../artifact_commands.dart, .../capture_commands.dart, .../constants.dart, .../options.dart
Added inspect-classes command (--class, --min-live-bytes, --limit); extended compare with --min-live-bytes and --memory-class-limit (tracks specified vs unspecified); added --skip-dtd to attach; added usage-with-examples helper and non-negative-int parser; propagate preparation warnings into summarize output.
MCP Tools & Handlers
.../mcp/server.dart, .../mcp/tool_handlers.dart, .../mcp/tools/analysis_tools.dart, .../mcp/tools/capture_tools.dart
Registered new profile_inspect_classes tool; extended profile_compare schema with minLiveBytes and memoryClassLimit; added skipDtd to profile_attach; handlers parse/validate args and forward memory-class read requests; added profileInspectClasses handler.
Presentation Models & JSON
.../presentation/models.dart, .../presentation/json.dart, .../presentation/options.dart, .../presentation/preparation.dart
Added PreparedMemoryClassInspection model and memoryClassInspectionJson; added cliCommand provenance to JSON shapes; surfaced preparation warnings in region/comparison JSON; added frame-filter description getters; added prepareMemoryClassInspection; extended prepareProfileComparison to accept memory-class re-read controls and capture warnings.
Rendering & Helpers
.../rendering/helpers.dart, .../rendering/methods.dart, .../rendering/terminal.dart
Added uniqueWarnings; aggregated and deduplicated warnings in rendered sections; added reproduction block output; new writeMemoryClassInspection; adjusted memory-summary table columns/order and warnings rendering.
Core: Artifacts & Memory Reconstruction
packages/devtools_profiler_core/lib/src/capture/artifacts.dart, .../memory/memory_profile_summary.dart
Expanded artifact discovery (session/summary/raw memory layouts); added ProfileArtifacts.readMemoryClasses, readMemoryClassesFromArtifact, and rebuildMemoryProfileFromArtifact to resolve, parse, filter, and top-truncate memory-class data from artifacts.
Core: Runner, Attach & DTD Handling
.../profile_attach_request.dart, .../profile_runner.dart, .../profile_session_context.dart, .../profile_session_controller.dart, .../profile_session_region_rpc.dart
Added enableDtd to ProfileAttachRequest; ProfileRunner.attach conditionally starts DTD; made DTD nullable and guarded service registration/posting; added ProfileRunner.readMemoryClasses.
Memory Comparison Logic
packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart
compareProfileRegions accepts optional baseline/current memory overrides and uses them when building memory comparisons and emitting availability warnings.
Package Frame Detection
packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart, packages/devtools_profiler_core/test/profile_frames_test.dart
Refactored package-name extraction into a helper that supports local checkout file paths as well as pub-cache and package: URIs; added tests covering various layouts and version-stripping.
Tests & Fixtures
packages/devtools_profiler_cli/test/cli_test.dart, packages/devtools_profiler_cli/test/mcp_server_test.dart, packages/devtools_profiler_core/test/...
Expanded test coverage for CLI/MCP memory-class inspection, compare memory-limit semantics, attach --skip-dtd stderr guidance, JSON cliCommand fields, sample-count fallback warnings, and added fake runners/fixtures for memory reads and artifact summarization.
Presentation re-export & CLI helpers
packages/devtools_profiler_cli/lib/src/presentation.dart, .../presentation/cli_command.dart, .../cli/options.dart
Re-exported CLI-command provenance helpers; added sessionCliCommand, shell-quoting/joining, attach detection and duration option builder; added parseNonNegativeInt.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI/User
    participant MCP as MCP Handler
    participant Prep as prepareMemoryClassInspection
    participant Artifacts as ProfileArtifacts
    participant Memory as MemoryProfile
    participant JSON as JSON Rendering

    CLI->>MCP: Call profileInspectClasses(path, classQuery, minLiveBytes, limit)
    MCP->>Prep: prepareMemoryClassInspection(runner, path, classQuery, minLiveBytes, limit)
    Prep->>Artifacts: readMemoryClasses(path, classQuery, minLiveBytes, topClassCount)
    Artifacts->>Memory: readMemoryClassesFromArtifact(rawPath, predicate, topClassCount)
    Memory->>Memory: rebuildMemoryProfileFromArtifact -> filter & top-truncate
    Memory-->>Artifacts: ProfileMemoryResult
    Artifacts-->>Prep: ProfileMemoryResult
    Prep-->>MCP: PreparedMemoryClassInspection
    MCP->>JSON: memoryClassInspectionJson(inspection)
    JSON-->>MCP: JSON map with cliCommand and warnings
    MCP-->>CLI: MCP response with memory-class data
Loading
sequenceDiagram
    participant CLI as CLI/User
    participant AttachCmd as AttachCommand
    participant Request as ProfileAttachRequest
    participant Runner as ProfileRunner
    participant SessionCtrl as ProfileSessionController
    participant DTD as DartToolingDaemon

    CLI->>AttachCmd: devtools-profiler attach --skip-dtd
    AttachCmd->>Runner: runner.attach(ProfileAttachRequest(enableDtd=false))
    Runner->>Runner: resolve working/artifact dirs
    alt enableDtd true
        Runner->>DTD: start DtdProcessSession()
        DTD-->>Runner: dtdSession
        Runner->>SessionCtrl: Create with dtd=daemon
    else enableDtd false
        Runner->>SessionCtrl: Create with dtd=null
    end
    SessionCtrl->>SessionCtrl: registerServices()
    alt dtd != null
        SessionCtrl->>DTD: register profiler services
    else dtd == null
        SessionCtrl->>SessionCtrl: return early (no region markers)
    end
    Runner-->>CLI: Attach session started (whole-session capture)
Loading
sequenceDiagram
    participant CLI as CLI/User
    participant Compare as CompareCommand
    participant Prep as prepareProfileComparison
    participant Artifacts as ProfileArtifacts
    participant CompareImpl as compareProfileRegions
    participant Output as Renderer

    CLI->>Compare: devtools-profiler compare --min-live-bytes 1000 --memory-class-limit 25
    Compare->>Prep: prepareProfileComparison(..., minLiveBytes=1000, memoryClassLimit=25, memoryClassLimitSpecified=true)
    Prep->>Artifacts: readMemoryClasses(baselinePath, minLiveBytes=1000, topClassCount=25)
    Artifacts-->>Prep: baselineMemoryOverride
    Prep->>Artifacts: readMemoryClasses(currentPath, minLiveBytes=1000, topClassCount=25)
    Artifacts-->>Prep: currentMemoryOverride
    Prep->>CompareImpl: compareProfileRegions(..., baselineMemoryOverride, currentMemoryOverride)
    CompareImpl->>CompareImpl: build memory comparison using overrides
    CompareImpl-->>Prep: comparison result with memory rows
    Prep-->>Compare: PreparedProfileComparison (includes warnings)
    Compare->>Output: render comparison, memory-class table, and warnings
    Output-->>CLI: Display results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nibbled through classes, counted every byte,
Skipped the daemon when the moon hid the light,
Filters and warnings now hop into view,
Repro commands and summaries tidy and true,
A rabbit applauds — updated tools, fresh delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Improve profiler artifact diagnostics' directly relates to the core objective of enhancing diagnostics through better artifact handling, improved warnings, CLI command reproducibility, and memory class inspection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/local-package-profile-frames

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kingwill101
kingwill101 marked this pull request as ready for review April 26, 2026 11:31
@kingwill101
kingwill101 requested a review from Copilot April 26, 2026 11:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e7f194260

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart Outdated
Comment thread packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves artifact and diagnostic output across the profiler core + CLI/MCP layers, with a focus on better “what happened / how do I reproduce it” guidance and richer post-run inspection of stored artifacts (including memory-class inspection and additional comparison filtering).

Changes:

  • Expanded artifact reading/summarization to accept per-profile artifact directories and added path normalization for artifact outputs.
  • Added additional warnings + reproduction/cliCommand hints to terminal and JSON/MCP outputs; added attach-mode “skip DTD” support.
  • Added stored-artifact memory class inspection utilities and exposed them via a new CLI command (inspect-classes) and MCP tool (profile_inspect_classes).

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/devtools_profiler_core/test/profile_runner_test.dart Updates attach warning expectation and adds coverage for summarizing per-profile artifact directories.
packages/devtools_profiler_core/test/profile_frames_test.dart Adds tests for ProfileFrame.packageName resolution across package/file URI variants.
packages/devtools_profiler_core/pubspec.yaml Bumps core package version to 0.2.0-wip.
packages/devtools_profiler_core/lib/src/memory/memory_profile_summary.dart Adds helpers to rebuild/read memory-class summaries from raw artifacts with optional filtering.
packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart Extends packageName detection to handle local package file://.../<pkg>/lib/... paths.
packages/devtools_profiler_core/lib/src/capture/runner/profile_session_region_rpc.dart Guards DTD event posting when DTD is disabled.
packages/devtools_profiler_core/lib/src/capture/runner/profile_session_controller.dart Makes DTD optional and skips service registration when disabled.
packages/devtools_profiler_core/lib/src/capture/runner/profile_session_context.dart Makes DTD optional in session context and documents attach-mode behavior.
packages/devtools_profiler_core/lib/src/capture/profile_runner.dart Normalizes working/artifact dirs, adds attach enableDtd, and adds memory-class artifact reading API.
packages/devtools_profiler_core/lib/src/capture/profile_attach_request.dart Adds enableDtd toggle for attach sessions.
packages/devtools_profiler_core/lib/src/capture/artifacts.dart Supports resolving/summarizing artifacts from directories and adds memory-class artifact resolution/reading.
packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart Adds optional memory overrides to comparisons (for re-read/filtered memory comparisons).
packages/devtools_profiler_core/CHANGELOG.md Documents 0.2.0-wip changes for core.
packages/devtools_profiler_cli/test/cli_test.dart Updates attach test expectations and adds coverage for filter-removes-all and sample-count fallback warnings.
packages/devtools_profiler_cli/pubspec.yaml Bumps CLI version to 0.2.0-wip and updates dependency constraint on core.
packages/devtools_profiler_cli/lib/src/rendering/terminal.dart Adds reproduction block, warning surfacing, and memory class inspection rendering; improves memory summary columns.
packages/devtools_profiler_cli/lib/src/rendering/methods.dart Surfaces preparation warnings alongside method inspection/search/comparison output.
packages/devtools_profiler_cli/lib/src/rendering/helpers.dart Adds POSIX-style shellJoin/shellQuote helpers for reproducible commands.
packages/devtools_profiler_cli/lib/src/presentation/preparation.dart Aggregates preparation warnings, adds memory-class inspection preparation, and adds sample-count/filter warnings + fallback.
packages/devtools_profiler_cli/lib/src/presentation/options.dart Adds “active filter” detection + user-facing labels for filters.
packages/devtools_profiler_cli/lib/src/presentation/models.dart Adds prepared models for memory class inspection and per-region preparation warnings.
packages/devtools_profiler_cli/lib/src/presentation/json.dart Adds cliCommand hints and preparation warnings to JSON output; adds JSON shape for memory class inspection.
packages/devtools_profiler_cli/lib/src/mcp/tools/capture_tools.dart Adds skipDtd option to attach tool schema.
packages/devtools_profiler_cli/lib/src/mcp/tools/analysis_tools.dart Adds memory-class compare filters to schema and introduces profile_inspect_classes tool.
packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart Wires new MCP arguments (skip DTD, memory filters) and implements profileInspectClasses.
packages/devtools_profiler_cli/lib/src/mcp/server.dart Registers the new MCP tool.
packages/devtools_profiler_cli/lib/src/cli/constants.dart Centralizes usageWithExamples helper.
packages/devtools_profiler_cli/lib/src/cli/commands/capture_commands.dart Adds attach warnings + --skip-dtd flag and examples; uses shared usageWithExamples.
packages/devtools_profiler_cli/lib/src/cli/commands/artifact_commands.dart Propagates preparation warnings into summarize output (terminal + JSON).
packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart Adds compare memory filters and introduces inspect-classes CLI command.
packages/devtools_profiler_cli/lib/src/cli.dart Registers inspect-classes command.
packages/devtools_profiler_cli/CHANGELOG.md Documents 0.2.0-wip changes for CLI.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart Outdated
Comment thread packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart Outdated
Comment thread packages/devtools_profiler_cli/lib/src/presentation/preparation.dart Outdated
Comment thread packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart (1)

40-42: ⚠️ Potential issue | 🟡 Minor

Memory-availability warning ignores the new overrides.

The warning check on line 40 uses only the original baseline.memory and current.memory fields, but the memory comparison logic at lines 79–96 correctly respects the new baselineMemoryOverride and currentMemoryOverride parameters via the fallback pattern override ?? original. This creates an inconsistency: if a caller provides an override to fill missing memory data, the user will still see the "Memory data was only available for one compared profile" warning because the check inspects only the stored memory results.

To align the warning with the override semantics, extract the effective memory values once and use them in both the warning check and the switch:

♻️ Proposed adjustment
+  final effectiveBaselineMemory = baselineMemoryOverride ?? baseline.memory;
+  final effectiveCurrentMemory = currentMemoryOverride ?? current.memory;
-  if ((baseline.memory == null) != (current.memory == null)) {
+  if ((effectiveBaselineMemory == null) != (effectiveCurrentMemory == null)) {
     warnings.add('Memory data was only available for one compared profile.');
   }

…and use those locals in the switch statement below.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart`
around lines 40 - 42, The warning currently checks raw baseline.memory and
current.memory and thus ignores overrides; update the logic in the comparison
routine so you compute effectiveMemoryBaseline = baselineMemoryOverride ??
baseline.memory and effectiveMemoryCurrent = currentMemoryOverride ??
current.memory (or similar local names) before the warning and switch, then use
those locals both for the initial presence check (the warning that memory is
only available for one profile) and inside the existing switch block that
compares memory; this keeps the warning consistent with the override semantics
used by the memory comparison code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart`:
- Around line 622-656: The local variable named `path` inside the
`profileInspectClasses` closure shadows the imported `package:path/path.dart as
path`; rename that local `final path = _requiredStringArgument(...)` to `final
targetPath` (consistent with `prepareMemoryClassInspection` usage elsewhere) and
update its use when calling `prepareMemoryClassInspection` so the code passes
`targetPath` instead of `path`, preventing accidental shadowing of the `path`
library.
- Around line 633-637: The MCP tool profileInspectClasses is using
_treeLimitFromArgument with defaultFrameLimit (12) which diverges from
ProfileArtifacts.readMemoryClasses' default (50); update the call in
profileInspectClasses to use a class-specific default (e.g.,
defaultMemoryClassLimit = 50) instead of defaultFrameLimit so omitted limit
matches the core API, or explicitly pass null/omit the default so
ProfileArtifacts.readMemoryClasses applies its own default; adjust the symbol
_treeLimitFromArgument invocation and introduce/rename the constant
(defaultMemoryClassLimit) near profileInspectClasses to make the intent clear.

In `@packages/devtools_profiler_cli/lib/src/presentation/json.dart`:
- Around line 249-264: _compareCliCommand and _inspectClassesCliCommand
currently omit memory-class flags because PreparedProfileComparison and
PreparedMemoryClassInspection don't carry the memory-class options; update the
preparer types to retain the relevant options (e.g., add memoryClassLimit and
minLiveBytes to PreparedProfileComparison and topClassCount/limit to
PreparedMemoryClassInspection consistent with preparation.dart's topClassCount)
and then modify _compareCliCommand and _inspectClassesCliCommand to emit the
corresponding flags (--memory-class-limit, --min-live-bytes, --limit and any
other memory-class options) when those fields are set so the generated
cliCommand fully reproduces the original invocation (also apply the same change
for the other similar CLI-emitting block around lines 366-380).

In `@packages/devtools_profiler_cli/lib/src/presentation/preparation.dart`:
- Around line 453-472: The PreparedMemoryClassInspection currently drops the
topClassCount used in readMemoryClasses; add a topClassCount (int) property to
the PreparedMemoryClassInspection model, set it in prepareMemoryClassInspection
(pass the topClassCount through when constructing
PreparedMemoryClassInspection), and update the CLI reproduction code paths
(e.g., memoryClassInspectionJson / _inspectClassesCliCommand) to read that
model.topClassCount and emit the corresponding --limit/--topClassCount flag when
it differs from the default so the generated cliCommand faithfully reproduces
the original invocation.
- Around line 516-550: The two warning branches use different checks for "filter
active" (options.hasActiveFrameFilters vs options.framePredicate == null) which
is inconsistent; update the fallback condition that currently checks
options.framePredicate == null to instead use !options.hasActiveFrameFilters (or
the positive/negative of hasActiveFrameFilters you prefer) so both branches use
the same source of truth (options.hasActiveFrameFilters) — change the
conditional guarding the rebuiltRegion.sampleCount==0 && region.sampleCount>0
branch to reference options.hasActiveFrameFilters and keep assigning
regionForSummary = _filterStoredRegion(region, options) when appropriate.
- Around line 107-133: The catch blocks around runner.readMemoryClasses in
preparation.dart currently swallow errors; update them to capture the thrown
exception (and its message) when readMemoryClasses fails for baselineRawPath or
currentRawPath and append a clear, contextual warning string to the prepared
session/comparison warnings collection (the same warnings plumbing used for
preparationWarnings and terminal "Warnings" output) so that
baselineMemoryOverride/currentMemoryOverride fallbacks are reported; ensure you
reference readMemoryClasses, baselineMemoryOverride, currentMemoryOverride,
minLiveBytes and memoryClassLimit in the warning text so consumers know which
override failed.

In `@packages/devtools_profiler_cli/lib/src/rendering/methods.dart`:
- Around line 259-267: The bullet list can show duplicate messages because you
simply concatenate comparison.warnings,
comparison.baseline.presentation.warnings and
comparison.current.presentation.warnings; change the construction of warnings to
deduplicate while preserving order (e.g., iterate those three lists, push into a
Set-backed seen collection and only add unseen entries to the final list) and
then pass the de-duplicated list to
console.section/console.components.bulletList so duplicate warning strings (like
identical preparation/region warnings) are only shown once.

In `@packages/devtools_profiler_cli/lib/src/rendering/terminal.dart`:
- Around line 567-643: The three duplicated helpers (_sessionCliCommand,
_isAttachSession, _durationOptionForSession) and inconsistent shell-joining
should be consolidated into a single shared helper file (e.g., add
cli_command.dart or export them from helpers.dart) and both terminal.dart and
json.dart should import and call those shared functions instead of defining
private copies; update terminal.dart to remove its local
_sessionCliCommand/_isAttachSession/_durationOptionForSession and use the shared
names, ensure the shared helper uses the canonical shellJoin implementation
(replace json.dart’s private _shellJoin if needed), and adjust imports/usages so
the terminal "Profiler command" and JSON cliCommand are produced by the same
code.

In `@packages/devtools_profiler_cli/README.md`:
- Around line 183-194: The README example for the inspect-classes command uses a
CPU/method-table-looking class name ("Parser") which may mislead readers; update
the example invocation of inspect-classes to use a memory/heap-oriented class
name such as "--class _List" or "--class String" (and keep the rest of the flags
like "--json --min-live-bytes 1048576 /path/to/session") so the example clearly
communicates that --class filters heap memory class names; modify the sample
block that shows the inspect-classes usage and replace "Parser" with one of
these heap-oriented names.

In `@packages/devtools_profiler_cli/test/cli_test.dart`:
- Around line 1104-1128: In the test "inspect-classes prints json memory class
output" add an assertion that the JSON includes the emitted CLI command so the
round-trip of flags is verified; specifically assert json['cliCommand'] contains
the reconstructed command with the flags passed to _runJsonCommand (for example
that it includes "devtools-profiler inspect-classes --class Love
--min-live-bytes 512 --limit 1 /tmp/memory/session-1" or at minimum contains the
"--class Love", "--min-live-bytes 512" and "--limit 1" tokens) to ensure the
runner/_runJsonCommand propagation is tested and prevent regressions in the CLI
provenance emitted by the code that builds the cliCommand.

In `@packages/devtools_profiler_core/lib/src/capture/artifacts.dart`:
- Around line 217-272: _resolveRawMemoryPath currently treats a session.json
file as an unsupported artifact because the file-branch only recognizes
ProfileMemoryArtifact and region shapes; detect a session JSON shape by trying
ProfileRunResult.fromJson(map) (or checking for a "regions" key) when the input
is a file and, if found, extract overallProfile?.memory?.rawProfilePath and
return it (throw the same StateError if missing/empty); ensure this uses the
same error message as the directory-branch; alternatively, if you prefer reuse,
convert the file path to its parent directory and call the existing
directory-branch logic (e.g., delegate to readSession or
_sessionFileFor/_summaryFileFor) so session.json paths are resolved consistently
in _resolveRawMemoryPath.
- Around line 198-208: Replace the three-branch predicate construction with a
single closure: keep the same variables (ProfileMemoryClassPredicate? predicate,
classQuery, minLiveBytes) but compute a normalized query (e.g., final query =
classQuery?.toLowerCase().trim()) and then set predicate = (s) => (query == null
|| query.isEmpty || s.className.toLowerCase().contains(query)) && (minLiveBytes
== null || s.liveBytes >= minLiveBytes); this removes duplicated checks while
preserving the original filtering logic in functions/methods that reference
predicate.
- Around line 99-120: summarizeArtifact currently checks _sessionFileFor,
_summaryFileFor, and _rawCpuFileFor but does not mirror readArtifact's fallback
to _rawMemoryFileFor, causing memory-only artifact directories to throw "No
profiler summary found in directory"; update summarizeArtifact to also check
_rawMemoryFileFor(targetPath) and if it exists call
summarizeArtifact(rawMemoryFile.path) so memory-only directories are handled the
same as readArtifact (alternatively, if you prefer to disallow summarizing
memory-only dirs, replace the thrown ArgumentError message with "Memory-only
artifact directories are not summarizable; pass the memory_profile.json path
directly" and keep behavior unchanged).

In `@packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart`:
- Around line 87-108: The local-package fallback in _packageNameFromFilePath
uses lastIndexOf('lib') which can pick a nested lib directory; change it to
locate the first 'lib' segment (e.g., use indexOf('lib') starting from 0 or
search for the first 'lib' whose parent index > 0) and then use
segments[libIndex - 1] as packageDirectoryName, keeping the same validations
(non-empty and not path.separator) and returning
_packageNameFromPubCacheFolder(packageDirectoryName) as before; ensure behavior
mirrors the pub-cache branch that uses indexOf('lib', pubCacheIndex).

In `@packages/devtools_profiler_core/lib/src/memory/memory_profile_summary.dart`:
- Around line 78-83: Add a one-line doc comment to
rebuildMemoryProfileFromArtifact (and mirror in readMemoryClassesFromArtifact if
present) that explains the intentional asymmetry in defaults: these functions
default topClassCount to 50 for deep-dive/inspect-classes artifact-rebuild use,
whereas summarizeMemoryProfile defaults to 10 for brief summaries, so callers
aren’t surprised by the different default behavior.
- Around line 78-108: The parsing in rebuildMemoryProfileFromArtifact
unconditionally casts rawArtifact['start'], rawArtifact['end'], and their nested
'heapSample' which throws on malformed/truncated artifacts; update
rebuildMemoryProfileFromArtifact to validate that rawArtifact contains non-null
Map entries for 'start' and 'end' and that each snapshot contains a non-null Map
'heapSample' (use runtime type checks like `is Map` before calling .cast), and
if any check fails throw a FormatException that includes rawProfilePath (or
return a clear typed error) so callers can handle diagnostics gracefully; keep
using _extractClassStats and HeapSample.fromJson after these guards.

In `@packages/devtools_profiler_core/test/profile_frames_test.dart`:
- Around line 16-81: Tests constructing ProfileFrame instances use path.join and
Uri.file which produce platform-dependent (Windows) paths; update each test that
builds a POSIX-style package layout (the four tests verifying packageName:
"packageName resolves pub-cache file URIs", "packageName resolves local package
file URIs", "packageName strips version suffixes from file package folders", and
"packageName ignores file URIs outside package layouts") to use
path.posix.join(...) and call Uri.file(..., windows: false) for the location
argument so the created URIs are pinned to POSIX semantics and the packageName
parsing remains platform-independent.

---

Outside diff comments:
In
`@packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart`:
- Around line 40-42: The warning currently checks raw baseline.memory and
current.memory and thus ignores overrides; update the logic in the comparison
routine so you compute effectiveMemoryBaseline = baselineMemoryOverride ??
baseline.memory and effectiveMemoryCurrent = currentMemoryOverride ??
current.memory (or similar local names) before the warning and switch, then use
those locals both for the initial presence check (the warning that memory is
only available for one profile) and inside the existing switch block that
compares memory; this keeps the warning consistent with the override semantics
used by the memory comparison code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 763f63f9-6630-463a-bc40-faf065be7f3d

📥 Commits

Reviewing files that changed from the base of the PR and between a22f5ac and f3b3426.

📒 Files selected for processing (37)
  • README.md
  • packages/devtools_profiler_cli/CHANGELOG.md
  • packages/devtools_profiler_cli/README.md
  • packages/devtools_profiler_cli/lib/src/cli.dart
  • packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart
  • packages/devtools_profiler_cli/lib/src/cli/commands/artifact_commands.dart
  • packages/devtools_profiler_cli/lib/src/cli/commands/capture_commands.dart
  • packages/devtools_profiler_cli/lib/src/cli/constants.dart
  • packages/devtools_profiler_cli/lib/src/cli/options.dart
  • packages/devtools_profiler_cli/lib/src/mcp/server.dart
  • packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart
  • packages/devtools_profiler_cli/lib/src/mcp/tools/analysis_tools.dart
  • packages/devtools_profiler_cli/lib/src/mcp/tools/capture_tools.dart
  • packages/devtools_profiler_cli/lib/src/presentation/json.dart
  • packages/devtools_profiler_cli/lib/src/presentation/models.dart
  • packages/devtools_profiler_cli/lib/src/presentation/options.dart
  • packages/devtools_profiler_cli/lib/src/presentation/preparation.dart
  • packages/devtools_profiler_cli/lib/src/rendering/helpers.dart
  • packages/devtools_profiler_cli/lib/src/rendering/methods.dart
  • packages/devtools_profiler_cli/lib/src/rendering/terminal.dart
  • packages/devtools_profiler_cli/pubspec.yaml
  • packages/devtools_profiler_cli/test/cli_test.dart
  • packages/devtools_profiler_cli/test/mcp_server_test.dart
  • packages/devtools_profiler_core/CHANGELOG.md
  • packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart
  • packages/devtools_profiler_core/lib/src/capture/artifacts.dart
  • packages/devtools_profiler_core/lib/src/capture/profile_attach_request.dart
  • packages/devtools_profiler_core/lib/src/capture/profile_runner.dart
  • packages/devtools_profiler_core/lib/src/capture/runner/profile_session_context.dart
  • packages/devtools_profiler_core/lib/src/capture/runner/profile_session_controller.dart
  • packages/devtools_profiler_core/lib/src/capture/runner/profile_session_region_rpc.dart
  • packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart
  • packages/devtools_profiler_core/lib/src/memory/memory_profile_summary.dart
  • packages/devtools_profiler_core/pubspec.yaml
  • packages/devtools_profiler_core/test/profile_frames_test.dart
  • packages/devtools_profiler_core/test/profile_runner_test.dart
  • skills/devtools-profiler-local/SKILL.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test packages/devtools_profiler_core
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~647-~647: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...mory summaries show retained growth. 6. Use profile_compare or `profile_find_regr...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🔇 Additional comments (18)
packages/devtools_profiler_cli/lib/src/cli/options.dart (1)

165-179: LGTM.

parseNonNegativeInt correctly distinguishes itself from parseLimit (which collapses 0 to null) by preserving 0 for callers that want unlimited semantics expressed as a literal integer. Error message matches parseLimit for consistent UX.

packages/devtools_profiler_core/lib/src/capture/runner/profile_session_context.dart (1)

20-25: LGTM.

Nullable dtd with the contract that whole-session VM-service capture continues to work is consistent with the call sites in profile_runner.dart (attach branch passes dtdSession?.daemon), the null-guard in profile_session_controller.registerServices, and the early-return in postRegionEvent.

packages/devtools_profiler_core/lib/src/capture/runner/profile_session_region_rpc.dart (1)

268-295: LGTM — DTD null-guard correctly placed.

The early return is before the try block so no spurious "Failed to post … event to DTD" warning is generated for sessions started with enableDtd: false. postRegionErrorEvent still appends its Region "…" failed: … warning via .then, preserving error visibility when DTD is disabled.

packages/devtools_profiler_core/lib/src/capture/runner/profile_session_controller.dart (1)

49-71: LGTM.

Single early-exit on context.dtd == null keeps the four registerService calls cleanly guarded; the ! accesses are unconditionally safe within this scope.

packages/devtools_profiler_cli/lib/src/rendering/helpers.dart (1)

165-183: LGTM — POSIX-shell quoting is correct.

'\'' escape pattern is the canonical way to embed a single quote inside a single-quoted string, and the empty-string case correctly emits ''. Note that the resulting strings target POSIX shells (bash/zsh); cmd.exe does not treat '…' as quoting, but this matches the rest of the CLI's reproduction-command UX.

packages/devtools_profiler_cli/lib/src/presentation/options.dart (1)

75-122: LGTM.

hasActiveFrameFilters is the union of all four input sources, and using it to short-circuit framePredicate keeps behavior identical to the prior locally-computed gate while also giving the renderer/JSON layers a consistent description set (activeFrameFilterDescriptions / activeFrameFilterLabel) for the new reproduction blocks.

packages/devtools_profiler_cli/pubspec.yaml (1)

25-25: Verify devtools_region_profiler constraint.

PR objectives bump changed profiler packages to 0.2.0-wip and the validation list includes devtools_region_profiler. If its pubspec.yaml was bumped, this dev_dependency ^0.1.0 should be updated for workspace consistency. (Verified by the script attached on the core pubspec review.)

packages/devtools_profiler_cli/lib/src/mcp/server.dart (1)

68-68: LGTM.

Tool registration mirrors the surrounding pattern; both profileInspectClassesTool (analysis_tools.dart) and handlers.profileInspectClasses (tool_handlers.dart) are present.

packages/devtools_profiler_cli/lib/src/cli.dart (1)

45-45: LGTM.

Wiring matches the existing analysis-command pattern.

packages/devtools_profiler_cli/lib/src/cli/commands/artifact_commands.dart (1)

76-87: LGTM.

Region-branch wiring of prepared.warnings matches the new regionPresentationJson / writeRegionSummary signatures introduced elsewhere in the PR.

packages/devtools_profiler_cli/lib/src/mcp/tools/capture_tools.dart (1)

112-117: LGTM.

Schema mirrors the CLI --skip-dtd flag, and the handler in tool_handlers.dart correctly inverts it into enableDtd.

packages/devtools_profiler_cli/lib/src/cli/commands/capture_commands.dart (1)

11-14: LGTM.

Warning text is accurate for attach mode in general, and the !(skip-dtd)enableDtd inversion lines up with the ProfileAttachRequest default of enableDtd: true. The non-negatable defaultsTo: false flag makes the as bool cast safe.

Also applies to: 201-208

packages/devtools_profiler_core/pubspec.yaml (1)

5-23: The devtools_profiler_protocol constraint is correct. Verification shows the protocol package remains at version 0.1.0 and was not bumped to 0.2.0-wip, so the ^0.1.0 constraint on line 16 is consistent with the workspace dependencies.

packages/devtools_profiler_cli/lib/src/cli/constants.dart (1)

17-28: LGTM.

The helper cleanly trims and appends a stable examples section. No issues found.

packages/devtools_profiler_core/test/profile_runner_test.dart (1)

440-510: LGTM.

The directory-summarization test correctly populates overall/cpu_profile.json and overall/summary.json, then verifies summarizeArtifact resolves the directory and round-trips the ProfileRegionResult. The expectations on regionId, rawProfilePath, and the top self frame name align with the artifact-resolution branch in summarizeArtifact.

packages/devtools_profiler_core/lib/src/capture/profile_attach_request.dart (1)

13-13: LGTM.

Adding enableDtd as an optional parameter with true default keeps existing call sites compatible and gives attach mode a safety valve when DTD startup is problematic.

Also applies to: 29-34

packages/devtools_profiler_cli/README.md (1)

138-139: Documentation aligns with the new flags and tools.

cliCommand, --min-live-bytes, --memory-class-limit, and profile_inspect_classes accurately reflect the CLI/MCP additions in this PR.

Also applies to: 233-236, 273-273

packages/devtools_profiler_core/CHANGELOG.md (1)

3-17: LGTM.

Changelog entry accurately summarizes the artifact resolution, attach --skip-dtd, memory-class inspection/filters, and local package frame detection introduced in this PR.

Comment thread packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart
Comment thread packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart
Comment thread packages/devtools_profiler_cli/lib/src/presentation/json.dart
Comment thread packages/devtools_profiler_core/lib/src/capture/artifacts.dart Outdated
Comment thread packages/devtools_profiler_core/lib/src/capture/artifacts.dart
Comment thread packages/devtools_profiler_core/test/profile_frames_test.dart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart`:
- Around line 45-53: Update the usage examples returned by formatUsage (the
override that calls usageWithExamples) to include a sample demonstrating the
--memory-class-limit flag; specifically add an entry like 'devtools-profiler
compare --memory-class-limit 0 path/to/baseline path/to/current' to the const
list of examples so the compare command shows the "unlimited" semantics
alongside the existing --min-live-bytes example.

In `@packages/devtools_profiler_cli/lib/src/presentation/models.dart`:
- Around line 38-41: PreparedProfileComparison's constructor currently forces
callers to pass minLiveBytes and memoryClassLimit; make those parameters
nullable (remove required and give them a nullable type/default null) so callers
aren't forced to thread values, and either (a) remove memoryClassLimitSpecified
and derive it as memoryClassLimit != null inside PreparedProfileComparison (if
you accept losing the "explicitly set to null" distinction), or (b) keep
memoryClassLimitSpecified but still make minLiveBytes/memoryClassLimit nullable;
update the constructor signature and any call sites (notably
prepareProfileComparison) to stop passing the removed/optional args and adjust
logic that relied on memoryClassLimitSpecified accordingly.

In `@packages/devtools_profiler_cli/lib/src/presentation/options.dart`:
- Around line 100-108: The getters framePredicate currently builds
excludePrefixes and includePrefixes before checking hasActiveFrameFilters,
causing unnecessary list allocations; move the construction of excludePrefixes
(which uses excludePackages and hideRuntimeHelpers/runtimeHelperPackagePrefixes)
and includePrefixes (includePackages) to after the if (!hasActiveFrameFilters)
return null check so the lists are only allocated when needed, keeping the
early-return fast and avoiding extra allocations.

In `@packages/devtools_profiler_cli/lib/src/presentation/preparation.dart`:
- Around line 165-177: compareProfileRegions is being called with
memoryClassLimit set to options.frameLimit when memoryClassLimitSpecified is
false, which unintentionally couples CPU frame limits and memory class limits;
update the call site to either compute and pass an explicit
defaultMemoryClassLimit (e.g., defaultMemoryClassLimit =
memoryClassLimitSpecified ? memoryClassLimit : someSeparateDefault) or add a
clear code comment where compareProfileRegions is invoked explaining that using
options.frameLimit as the default memoryClassLimit is intentional and mirrors
prepareProfileTrends; refer to the symbols compareProfileRegions,
memoryClassLimitSpecified, memoryClassLimit, options.frameLimit, and
prepareProfileTrends to locate and implement the change.
- Around line 130-138: The fallback warning is ambiguous for CPU-only artifacts
because it only checks region.memory?.rawProfilePath; update the logic in the
readMemoryClasses/baselineMemoryOverride branch to distinguish when
region.memory is entirely missing vs when region.memory exists but
rawProfilePath is null/empty: if region.memory == null push a message saying “no
memory capture was performed (artifact is CPU-only); --min-live-bytes requires a
memory capture” (include minLiveBytes and memoryLimitDescription for context),
else if region.memory != null && (rawProfilePath == null ||
rawProfilePath.isEmpty) keep the existing “no raw memory artifact path was
stored” message. Ensure you update references to memoryWarnings and the existing
message composition to preserve formatting.

In `@packages/devtools_profiler_cli/lib/src/rendering/terminal.dart`:
- Around line 291-298: The merged warnings list is built by concatenating
explanation.hotspots.warnings and explanation.target.presentation.warnings and
can contain duplicates; change the code that constructs or before printing the
warnings (the local variable warnings used in this block that calls
console.section and console.components.bulletList) to pass the combined list
through the existing uniqueWarnings utility (same approach used in
writeComparisonSummary and writeMethodComparison) so duplicate
preparation/hotspot warnings are removed before calling
console.components.bulletList.

In `@packages/devtools_profiler_cli/README.md`:
- Around line 233-236: Update the README flag descriptions to clarify units and
scope: state that `--min-live-bytes <n>` takes a value in bytes (e.g., 1048576 =
1 MiB) and update `--memory-class-limit <n>` to indicate it controls the number
of compared memory class rows for both `compare` and `inspect-classes` (with `0`
meaning unlimited) so the behavior and units are clear for both flags.

In `@packages/devtools_profiler_cli/test/cli_test.dart`:
- Around line 1306-1333: The negative assertion in the test "no warning emitted
when raw CPU profile produces non-zero samples" uses a different substring than
the positive case; update the isNot(contains(...)) call so it uses the same
substring as the positive assertion (e.g. '0 samples when re-read' or the full
phrase used in preparation.dart) to make the positive and negative checks
symmetric; locate the failing expectation in the test (the isNot(contains(...))
at the end of the test) and replace its argument to match the other assertion's
substring.
- Around line 1271-1304: Rename the test group title string to reflect the
end-to-end CLI behavior being tested: change the group invocation that currently
reads group('prepareRegionPresentation sample-count fallback', ...) to something
like group('run sample-count fallback warnings', ...) so the grouping reflects
that the test exercises the run CLI (runCli) with _FakeRunnerWithZeroSamples and
asserts on stdout rather than directly testing prepareRegionPresentation.
- Around line 1192-1198: Update the test 'inspect-classes rejects negative
min-live-bytes' to pass _FakeMemoryProfileRunner instead of relying on the
default runner; specifically, when calling _runCliCommand in that test provide
_FakeMemoryProfileRunner() to mirror other inspect-classes tests and ensure the
runner used implements readMemoryClasses correctly (avoid _FakeProfileRunner) so
future moves of validation won't mask regressions.

In `@packages/devtools_profiler_core/lib/src/capture/artifacts.dart`:
- Around line 41-62: The directory-probing logic for resolving artifacts is
duplicated across readArtifact, summarizeArtifact, and _resolveRawMemoryPath;
extract a single helper (e.g. _resolveDirectoryArtifact) that, given a directory
path plus parameters like missingMessage and a recurse callback, performs the
ordered checks using the existing helpers _sessionFileFor, _summaryFileFor,
_rawCpuFileFor, and _rawMemoryFileFor (check session → summary → rawCpu →
rawMemory) and either returns the resolved result or throws
ArgumentError.value(targetPath, 'targetPath', missingMessage). Replace the
duplicated sequences in readArtifact, summarizeArtifact, and
_resolveRawMemoryPath to call this helper and pass the appropriate recurse
behavior and error text so all resolution logic is centralized.
- Around line 220-237: The current _resolveRawMemoryPath treats a directory that
only contains a CPU-only artifact as "Artifact not found"; update the branch in
_resolveRawMemoryPath so after checking _sessionFileFor, _summaryFileFor, and
_rawMemoryFileFor you also check if _rawCpuFileFor(targetPath).existsSync() and,
if true, throw an ArgumentError.value using the memory-missing diagnostic used
elsewhere (e.g. "No memory profile is available for the session/region at \"…\".
Re-run the target with memory capture enabled."); this preserves the existing
session/summary/raw-memory checks (session via readSession and
_rawMemoryPathForSession) but ensures CPU-only dirs produce the precise
memory-missing message rather than a generic "Artifact not found".

In `@packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart`:
- Around line 87-108: The _packageNameFromFilePath function can misidentify
non-package lib/ paths and Windows drive roots as package names; update the
guard around packageDirectoryName (used after computing libIndex and
packageDirectoryName) to also treat a Windows drive root as invalid by comparing
packageDirectoryName to path.rootPrefix(packageDirectoryName) (i.e., return null
if packageDirectoryName.isEmpty || packageDirectoryName == path.separator ||
packageDirectoryName == path.rootPrefix(packageDirectoryName)); also add a unit
test covering a non-package lib path such as "/usr/local/lib/foo.dart" that
asserts _packageNameFromFilePath returns null to prevent local directories like
"local" or "var" being treated as package names.

In `@packages/devtools_profiler_core/lib/src/memory/memory_profile_summary.dart`:
- Around line 170-171: The current cast of profiles uses (snapshot['profiles']
as List<Object?>? ?? const []).cast<Map<Object?, Object?>>(), which still throws
a TypeError if profiles exists but is not a List; update the extraction to
explicitly check the runtime type (e.g., if (snapshot['profiles'] is List) {
final raw = snapshot['profiles'] as List; ... } else if
(snapshot.containsKey('profiles')) throw FormatException(..., rawProfilePath); )
so it mirrors how start/end are handled and aligns with _requiredArtifactMap
validation; use rawProfilePath in the FormatException message and then
cast/convert the List entries to Map<Object?, Object?> as before.
- Around line 142-144: Replace the unsafe top-level cast of the JSON payload
with an explicit validation step: assign the result of jsonDecode(await
File(rawProfilePath).readAsString()) to a local (e.g., decoded), then pass
decoded and the rawProfilePath into the existing _requiredArtifactMap helper to
obtain the Map<Object?, Object?> (so a malformed array/scalar/null produces the
same FormatException with the artifact path). Update references that used the
previous `json` variable to use the validated map returned by
_requiredArtifactMap.

In `@packages/devtools_profiler_core/test/profile_artifacts_test.dart`:
- Around line 57-73: Add tests to cover the other required nested labels checked
by _requiredArtifactMap when calling rebuildMemoryProfileFromArtifact: assert
that FormatException messages include 'end', 'start.heapSample', and
'end.heapSample' in addition to 'start'. You can parameterize the existing test
or add separate cases that call rebuildMemoryProfileFromArtifact with artifacts
missing each of those keys (and rawProfilePath set like
'/tmp/malformed_memory_profile.json') and use
throwsA(isA<FormatException>().having((e) => e.message, 'message',
contains('<label>'))) to verify the message contains the specific missing label.

In `@packages/devtools_profiler_core/test/profile_comparison_test.dart`:
- Around line 304-322: Add a new test that covers the asymmetric memory-override
case for compareProfileRegions: call compareProfileRegions with baseline:
_region(regionId: 'baseline') and current: _region(regionId: 'current') where
both regions have memory: null, supply only baselineMemoryOverride:
_memoryBaseline() (leave currentMemoryOverride null) and assert that
comparison.memory is still non-null but comparison.warnings contains the "Memory
data was only available for one compared profile." message; this mirrors the
existing test but uses only one override to ensure the warning is emitted when
overrides are not symmetric.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aed0a36d-e318-44d5-9ca0-739df6aa9a4f

📥 Commits

Reviewing files that changed from the base of the PR and between f3b3426 and 56f8a2a.

📒 Files selected for processing (22)
  • packages/devtools_profiler_cli/README.md
  • packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart
  • packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart
  • packages/devtools_profiler_cli/lib/src/presentation.dart
  • packages/devtools_profiler_cli/lib/src/presentation/cli_command.dart
  • packages/devtools_profiler_cli/lib/src/presentation/json.dart
  • packages/devtools_profiler_cli/lib/src/presentation/models.dart
  • packages/devtools_profiler_cli/lib/src/presentation/options.dart
  • packages/devtools_profiler_cli/lib/src/presentation/preparation.dart
  • packages/devtools_profiler_cli/lib/src/rendering/helpers.dart
  • packages/devtools_profiler_cli/lib/src/rendering/methods.dart
  • packages/devtools_profiler_cli/lib/src/rendering/terminal.dart
  • packages/devtools_profiler_cli/test/cli_test.dart
  • packages/devtools_profiler_cli/test/mcp_server_test.dart
  • packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart
  • packages/devtools_profiler_core/lib/src/capture/artifacts.dart
  • packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart
  • packages/devtools_profiler_core/lib/src/memory/memory_profile_summary.dart
  • packages/devtools_profiler_core/test/profile_artifacts_test.dart
  • packages/devtools_profiler_core/test/profile_comparison_test.dart
  • packages/devtools_profiler_core/test/profile_frames_test.dart
  • skills/devtools-profiler-local/SKILL.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test packages/devtools_profiler_core
🔇 Additional comments (19)
packages/devtools_profiler_core/test/profile_frames_test.dart (1)

1-117: Test coverage and platform-pinning look good.

POSIX pinning via path.posix.join + Uri.file(..., windows: false) is applied consistently, and the cases (package URI, pub-cache, local package, nested-lib precedence, version stripping, non-matching layouts) exercise the behavior contract documented for _packageNameFromFilePath. Consider adding one negative case for a "looks like a path but isn't a package" layout (e.g., /var/lib/foo.dart) to lock in the chosen trade-off — see the related comment on profile_frames.dart.

packages/devtools_profiler_core/lib/src/memory/memory_profile_summary.dart (1)

79-80: Doc note for default-asymmetry resolved.

The new doc comment explicitly calling out the 50 vs. 10 default split between rebuildMemoryProfileFromArtifact/readMemoryClassesFromArtifact and summarizeMemoryProfile addresses the prior concern.

packages/devtools_profiler_core/lib/src/capture/artifacts.dart (2)

195-218: Predicate composition simplified — past comment addressed.

The single-closure form with normalizedQuery and minLiveBytes short-circuits cleanly and resolves the prior verbosity concern.


251-274: Session-JSON file target now supported — past comment addressed.

The new 'regions' pattern at line 263 routes session JSON file inputs through _rawMemoryPathForSession, eliminating the prior dead-end 'Unsupported artifact type' error for session.json file paths.

packages/devtools_profiler_cli/README.md (1)

183-194: Heap-class example resolved — past comment addressed.

The example now uses --class String, which clearly reads as a memory-class filter rather than a CPU/method-table query.

packages/devtools_profiler_core/lib/src/analysis/profile_region_comparison.dart (1)

18-95: Override-aware effective memory threading is consistent.

Both the availability warning at line 42 and the switch at line 81 use effectiveBaselineMemory/effectiveCurrentMemory, so a caller-provided override correctly drives both branches and the empty-fallback timestamps. Behavior matches the new test and prepareProfileComparison plumbing.

packages/devtools_profiler_cli/lib/src/rendering/helpers.dart (1)

172-178: LGTM.

Order-preserving de-dup via seen.add(...) is idiomatic and O(n).

packages/devtools_profiler_cli/lib/src/presentation.dart (1)

1-1: LGTM.

Public re-export aligns with the new cliCommand JSON output surface.

packages/devtools_profiler_cli/lib/src/rendering/methods.dart (1)

259-267: LGTM — addresses prior dedup concern.

The merged warning render now uses uniqueWarnings(...) to dedupe across comparison.warnings, baseline.presentation.warnings, and current.presentation.warnings. Single-source renders in writeMethodInspection/writeMethodSearch correctly skip the dedup wrapper since there is nothing to merge.

packages/devtools_profiler_cli/lib/src/mcp/tool_handlers.dart (1)

622-656: LGTM — both past review concerns addressed.

profileInspectClasses now uses targetPath (no library shadowing) and defaults to defaultMemoryClassLimit (50) so MCP behavior matches the CLI/ProfileArtifacts.readMemoryClasses default. The limit ?? 0 translation correctly forwards 0 as "unlimited" to topClassCount, and _optionalNonNegativeIntArgument rejects negative minLiveBytes (covered by the new test).

packages/devtools_profiler_cli/lib/src/presentation/json.dart (1)

227-250: LGTM — reproducible commands now carry memory-class options.

_compareCliCommand emits --min-live-bytes and --memory-class-limit (using memoryClassLimitSpecified to distinguish "user passed 0" from "omitted"), and _inspectClassesCliCommand emits --class, --min-live-bytes, and --limit whenever the value differs from defaultMemoryClassLimit. This addresses the prior round-trip concern and matches the documented behavior in SKILL.md.

One small note: when the user passes --limit 50 explicitly, the regenerated cliCommand will omit --limit (because it equals the default). That is functionally equivalent on re-run; flagging only as a heads-up since it is a minor round-trip lossiness.

Also applies to: 352-370

packages/devtools_profiler_cli/test/cli_test.dart (1)

1145-1175: cliCommand round-trip assertion for inspect-classes is now covered.

This addresses the previously-flagged gap: the JSON output's cliCommand is now asserted to round-trip --class, --min-live-bytes, and --limit exactly.

packages/devtools_profiler_cli/lib/src/presentation/models.dart (1)

180-205: topClassCount is now persisted on the model.

The earlier concern that PreparedMemoryClassInspection discarded topClassCount (preventing --limit from being reproduced in cliCommand) is resolved — the field is stored and cli_test.dart asserts its round-trip into the JSON cliCommand.

packages/devtools_profiler_cli/lib/src/presentation/preparation.dart (4)

108-163: Memory-override read failures are now warned, not silently swallowed.

The previously-flagged silent fallback is fixed: readMemoryClasses failures (and missing rawProfilePath) now emit explicit warnings that propagate into preparationWarnings and into the JSON cliCommand consumers. cli_test.dart Lines 957–989 lock this in.


543-585: Filter-active checks are now consistent on hasActiveFrameFilters.

The earlier inconsistency between hasActiveFrameFilters and framePredicate == null is resolved — all three branches in this block now key off options.hasActiveFrameFilters, so they share a single source of truth.


1-2: The vm_service import is necessary and should be kept.

CpuSamples is used in the file's own function signature at line 622 (_countCpuSamplesBeforeFilters(CpuSamples cpuSamples)) and is not re-exported from devtools_profiler_core's public API. While devtools_profiler_core internally uses vm_service, it does not expose CpuSamples through its exports, so the direct import is required for type annotations in this presentation layer.

			> Likely an incorrect or invalid review comment.

487-507: MCP entry point properly wraps prepareMemoryClassInspection errors; CLI relies on framework-level handling.

Unlike prepareProfileComparison (Lines 116–129), this helper doesn't try/catch around runner.readMemoryClasses. The MCP tool handler wraps the call via _runTool's try/catch (tool_handlers.dart:658–692), returning a structured CallToolResult with isError: true on failure. The CLI's runCli entry point (cli.dart:46–51) also wraps command execution in a catch-all that prints errors to stderr and returns softwareExitCode. Both approaches handle exceptions appropriately for their contexts, though MCP provides more structured error information.

packages/devtools_profiler_cli/lib/src/cli/commands/analysis_commands.dart (2)

322-397: No action needed. defaultMemoryClassLimit is properly defined in packages/devtools_profiler_cli/lib/src/presentation/options.dart (line 13) and is imported via ../options.dart. The code will compile without issues.


375-387: The defaults are already aligned and consistent. Both readMemoryClasses and the CLI's --limit option use 50 as the default (defaultMemoryClassLimit = 50), and the help text correctly documents that 0 means unlimited. There is no divergent behavior to address.

			> Likely an incorrect or invalid review comment.

Comment thread packages/devtools_profiler_cli/lib/src/presentation/models.dart
Comment thread packages/devtools_profiler_cli/lib/src/presentation/options.dart
Comment thread packages/devtools_profiler_core/lib/src/cpu/profile_frames.dart
Comment thread packages/devtools_profiler_core/test/profile_artifacts_test.dart
Comment thread packages/devtools_profiler_core/test/profile_comparison_test.dart
@kingwill101
kingwill101 merged commit 3d58a61 into main Apr 26, 2026
8 checks passed
@kingwill101
kingwill101 deleted the fix/local-package-profile-frames branch April 26, 2026 17:02
This was referenced Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants