-
Notifications
You must be signed in to change notification settings - Fork 1
Add Node/V8 CPU and heap profile adapters #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,6 +72,51 @@ class CaptureInvocation: | |
| expected_overhead="No profiler overhead; process output only.", | ||
| capture_limitations=("No sampled stack or operator evidence is collected.",), | ||
| ), | ||
| BuiltinAdapter( | ||
| name="node-cpu-prof", | ||
| dependency_kind=AdapterDependencyKind.EXECUTABLE, | ||
| dependency="node", | ||
| supported_modes=("record",), | ||
| supported_formats=("v8-cpuprofile",), | ||
| features=("sampled_stacks", "javascript_symbols"), | ||
| remediation=( | ||
| "Install Node.js 20.16+ or 22.4+ which expose stable --cpu-prof flags.", | ||
| ), | ||
| version_args=("--version",), | ||
|
Comment on lines
+82
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a host with Node older than the stated 20.16+/22.4+ stable-support floor, capability discovery and active probing still report these adapters as available because they only resolve AGENTS.md reference: AGENTS.md:L43-L45 Useful? React with 👍 / 👎. |
||
| output_filename="cpu.cpuprofile", | ||
| artifact_kinds=(ArtifactKind.SAMPLE_PROFILE,), | ||
|
Comment on lines
+86
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a Node workload exits nonzero after writing its profile—for example, an uncaught exception—both AGENTS.md reference: AGENTS.md:L31-L34 Useful? React with 👍 / 👎. |
||
| expected_overhead=( | ||
| "V8 CPU sampling overhead; exact rate depends on --cpu-prof-interval." | ||
| ), | ||
| capture_limitations=( | ||
| "Only the main Node.js thread is profiled; worker threads are not sampled.", | ||
| "The CPU profile contains sampled stack locations, not wall-clock or " | ||
| "allocation evidence.", | ||
| ), | ||
| ), | ||
| BuiltinAdapter( | ||
| name="node-heap-prof", | ||
| dependency_kind=AdapterDependencyKind.EXECUTABLE, | ||
| dependency="node", | ||
| supported_modes=("record",), | ||
| supported_formats=("v8-sampling-heap-profile",), | ||
| features=("allocations", "sampled_allocations", "stacks"), | ||
| remediation=( | ||
| "Install Node.js 20.16+ or 22.4+ which expose stable --heap-prof flags.", | ||
| ), | ||
| version_args=("--version",), | ||
| output_filename="heap.heapprofile", | ||
| artifact_kinds=(ArtifactKind.MEMORY_PROFILE,), | ||
| expected_overhead=( | ||
| "V8 heap sampling overhead; exact rate depends on --heap-prof-interval." | ||
| ), | ||
| capture_limitations=( | ||
| "Sampled allocation bytes are an estimate, not the exact retained heap or " | ||
| "process RSS.", | ||
| "Only allocations sampled by V8 are reported; small or short-lived " | ||
| "allocations may be underrepresented.", | ||
| ), | ||
| ), | ||
| BuiltinAdapter( | ||
| name="benchmark-samples", | ||
| dependency_kind=AdapterDependencyKind.INTERNAL, | ||
|
|
@@ -520,6 +565,15 @@ def build_capture_invocation( # noqa: C901 - provider routing is intentionally | |
| output, | ||
| *target, | ||
| ) | ||
| elif adapter_name in {"node-cpu-prof", "node-heap-prof"}: | ||
| return _node_v8_capture_invocation( | ||
| adapter_name, | ||
| adapter, | ||
| workload_argv, | ||
| output_root, | ||
| output, | ||
| executable=executable, | ||
| ) | ||
| elif adapter_name == "torch.profiler": | ||
| return _torch_capture_invocation( | ||
| adapter, | ||
|
|
@@ -742,6 +796,72 @@ def _torch_capture_invocation( | |
| ) | ||
|
|
||
|
|
||
| def _node_v8_capture_invocation( | ||
| adapter_name: str, | ||
| adapter: BuiltinAdapter, | ||
| workload_argv: tuple[str, ...], | ||
| output_root: Path, | ||
| output: str, | ||
| *, | ||
| executable: str | None, | ||
| ) -> CaptureInvocation: | ||
| """Inject Node.js --cpu-prof or --heap-prof flags into a declared Node workload. | ||
|
|
||
| Node.js exposes stable V8 profiling through CLI flags (Node 20.16+ / 22.4+). | ||
| The declared workload argv already starts with the Node executable, so the | ||
| adapter inserts the profiling flags immediately after argv[0] and before the | ||
| user script and its arguments. The output directory and file name are bound | ||
| explicitly so Flameox owns the artifact path and can preserve it. | ||
| """ | ||
| if not workload_argv: | ||
| raise DomainError( | ||
| ErrorCode.INVALID_CAPTURE_PLAN, | ||
| "A declared Node.js workload command is required for V8 profiling.", | ||
| ) | ||
| node_executable = workload_argv[0] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a workload names a custom Node executable such as AGENTS.md reference: AGENTS.md:L31-L35 Useful? React with 👍 / 👎. |
||
| node_name = Path(node_executable).name | ||
| if not ( | ||
| node_name == "node" | ||
| or node_name.startswith("node") | ||
| or node_executable.endswith("node") | ||
| ): | ||
| raise DomainError( | ||
| ErrorCode.INVALID_CAPTURE_PLAN, | ||
| f"V8 profiling requires a Node.js workload; the declared " | ||
| f"executable is {node_executable!r}.", | ||
| remediation=("Declare a Node.js command (e.g. `node script.js`) as the workload.",), | ||
| ) | ||
| if len(workload_argv) < 2: | ||
| raise DomainError( | ||
| ErrorCode.INVALID_CAPTURE_PLAN, | ||
| "A declared Node.js script or module is required after the node executable.", | ||
| ) | ||
| # Compute the directory and file name for the V8 profile output. | ||
| output_path = Path(output) | ||
| prof_dir = str(output_path.parent) | ||
| prof_name = output_path.name | ||
| if adapter_name == "node-cpu-prof": | ||
| prof_flags = ( | ||
| "--cpu-prof", | ||
| "--cpu-prof-dir=" + prof_dir, | ||
| "--cpu-prof-name=" + prof_name, | ||
| ) | ||
| else: | ||
| prof_flags = ( | ||
| "--heap-prof", | ||
| "--heap-prof-dir=" + prof_dir, | ||
| "--heap-prof-name=" + prof_name, | ||
| ) | ||
| argv = (node_executable, *prof_flags, *workload_argv[1:]) | ||
| return CaptureInvocation( | ||
| argv=argv, | ||
| artifact_kinds=adapter.artifact_kinds, | ||
| expected_overhead=adapter.expected_overhead or "", | ||
| limitations=adapter.capture_limitations, | ||
| environment={}, | ||
| ) | ||
|
|
||
|
|
||
| def _compute_sanitizer_capture_invocation( | ||
| adapter: BuiltinAdapter, | ||
| workload_argv: tuple[str, ...], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These exports make the extractors callable only from Python; the reviewed
src/flameox/cli.pyextract command group andsrc/flameox/mcp/server.pytool registrations contain no CPU- or heap-V8 extraction entry point. Thus an agent following the documented capture → extract workflow can capture these artifacts but cannot produce the newly implemented normalized evidence through either supported transport, unlike the existing Memray and other extractors. Add corresponding CLI commands and MCP tools over the same extraction behavior.AGENTS.md reference: AGENTS.md:L40-L45
Useful? React with 👍 / 👎.