This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-24003-heap-dump
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 8939193b1fe40f5660b4a6d06bf147379c7afa5d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 13 16:34:57 2026 +0200

    CAMEL-24003: Add heap dump command to CLI, TUI, and MCP
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../impl/console/HeapDumpDevConsoleConfigurer.java |  63 +++++++++++
 .../org/apache/camel/dev-console/heap-dump.json    |  15 +++
 ...rg.apache.camel.impl.console.HeapDumpDevConsole |   2 +
 .../org/apache/camel/dev-console/heap-dump         |   2 +
 .../org/apache/camel/dev-consoles.properties       |   2 +-
 .../camel/impl/console/HeapDumpDevConsole.java     |  88 ++++++++++++++++
 .../jbang-commands/camel-jbang-cmd-heap-dump.adoc  |  27 +++++
 .../ROOT/pages/jbang-commands/camel-jbang-cmd.adoc |   1 +
 .../camel/cli/connector/LocalCliConnector.java     |  23 ++++
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../jbang/core/commands/action/CamelHeapDump.java  | 116 +++++++++++++++++++++
 .../dsl/jbang/core/commands/ai/ToolRegistry.java   |  21 ++++
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java  |  19 ++++
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |   1 +
 .../dsl/jbang/core/commands/tui/MemoryLeakTab.java |  44 ++++++++
 .../dsl/jbang/core/commands/tui/MemoryTab.java     |  46 ++++++++
 .../jbang/core/commands/tui/MonitorContext.java    |   2 +
 18 files changed, 473 insertions(+), 2 deletions(-)

diff --git 
a/core/camel-console/src/generated/java/org/apache/camel/impl/console/HeapDumpDevConsoleConfigurer.java
 
b/core/camel-console/src/generated/java/org/apache/camel/impl/console/HeapDumpDevConsoleConfigurer.java
new file mode 100644
index 000000000000..52e790256a81
--- /dev/null
+++ 
b/core/camel-console/src/generated/java/org/apache/camel/impl/console/HeapDumpDevConsoleConfigurer.java
@@ -0,0 +1,63 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.impl.console;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.impl.console.HeapDumpDevConsole;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class HeapDumpDevConsoleConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, ExtendedPropertyConfigurerGetter {
+
+    private static final Map<String, Object> ALL_OPTIONS;
+    static {
+        Map<String, Object> map = new CaseInsensitiveMap();
+        map.put("CamelContext", org.apache.camel.CamelContext.class);
+        ALL_OPTIONS = map;
+    }
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.impl.console.HeapDumpDevConsole target = 
(org.apache.camel.impl.console.HeapDumpDevConsole) obj;
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": target.setCamelContext(property(camelContext, 
org.apache.camel.CamelContext.class, value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Map<String, Object> getAllOptions(Object target) {
+        return ALL_OPTIONS;
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": return org.apache.camel.CamelContext.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.impl.console.HeapDumpDevConsole target = 
(org.apache.camel.impl.console.HeapDumpDevConsole) obj;
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": return target.getCamelContext();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/heap-dump.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/heap-dump.json
new file mode 100644
index 000000000000..0118872af815
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/heap-dump.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "heap-dump",
+    "title": "Heap Dump",
+    "description": "Write a heap dump (.hprof) file for deep memory analysis",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.HeapDumpDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.22.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.HeapDumpDevConsole
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.HeapDumpDevConsole
new file mode 100644
index 000000000000..eb5ec24f36f2
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.HeapDumpDevConsole
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.HeapDumpDevConsoleConfigurer
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/heap-dump
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/heap-dump
new file mode 100644
index 000000000000..264d915a0335
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/heap-dump
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.HeapDumpDevConsole
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
index 95209ff8cce9..9573a4157934 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-dev-consoles=activity bean blocked browse circuit-breaker consumer context 
datasource debug endpoint errors eval-language event gc health heap-histogram 
inflight internal-tasks java-security jfr-memory-leak jvm log memory 
message-history processor producer properties receive reload rest rest-spec 
route route-controller route-dump route-group route-structure route-topology 
send service simple-language source sql-query sql-trace startup-recorder 
system-properties thread top trace transform [...]
+dev-consoles=activity bean blocked browse circuit-breaker consumer context 
datasource debug endpoint errors eval-language event gc health heap-dump 
heap-histogram inflight internal-tasks java-security jfr-memory-leak jvm log 
memory message-history processor producer properties receive reload rest 
rest-spec route route-controller route-dump route-group route-structure 
route-topology send service simple-language source sql-query sql-trace 
startup-recorder system-properties thread top trace [...]
 groupId=org.apache.camel
 artifactId=camel-console
 version=4.22.0-SNAPSHOT
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/HeapDumpDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/HeapDumpDevConsole.java
new file mode 100644
index 000000000000..9b307b8ffb42
--- /dev/null
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/HeapDumpDevConsole.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.impl.console;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+
+import com.sun.management.HotSpotDiagnosticMXBean;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.console.AbstractDevConsole;
+import org.apache.camel.util.json.JsonObject;
+
+@DevConsole(name = "heap-dump", displayName = "Heap Dump",
+            description = "Write a heap dump (.hprof) file for deep memory 
analysis")
+@Configurer(extended = true)
+public class HeapDumpDevConsole extends AbstractDevConsole {
+
+    private static final DateTimeFormatter TIMESTAMP = 
DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
+
+    @Metadata(label = "action", description = "File name for the heap dump 
(without .hprof extension)",
+              javaType = "java.lang.String")
+    public static final String NAME = "name";
+
+    @Metadata(label = "action", description = "Whether to dump only live 
objects (default true)",
+              defaultValue = "true", javaType = "java.lang.Boolean")
+    public static final String LIVE = "live";
+
+    public HeapDumpDevConsole() {
+        super("jvm", "heap-dump", "Heap Dump", "Write a heap dump (.hprof) 
file for deep memory analysis");
+    }
+
+    @Override
+    protected String doCallText(Map<String, Object> options) {
+        JsonObject json = doCallJson(options);
+        String error = json.getString("error");
+        if (error != null) {
+            return "Heap dump failed: " + error;
+        }
+        return "Heap dump written to: " + json.getString("file") + " (" + 
json.getLong("size") + " bytes)";
+    }
+
+    @Override
+    protected JsonObject doCallJson(Map<String, Object> options) {
+        JsonObject root = new JsonObject();
+
+        String name = optionString(options, NAME);
+        if (name == null || name.isBlank()) {
+            name = "heap-dump-" + TIMESTAMP.format(LocalDateTime.now());
+        }
+        if (!name.endsWith(".hprof")) {
+            name = name + ".hprof";
+        }
+
+        boolean live = optionBoolean(options, LIVE, true);
+
+        try {
+            HotSpotDiagnosticMXBean bean = 
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
+            bean.dumpHeap(name, live);
+
+            File file = new File(name);
+            root.put("file", file.getAbsolutePath());
+            root.put("size", file.length());
+        } catch (Exception e) {
+            root.put("error", e.getMessage());
+        }
+
+        return root;
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-heap-dump.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-heap-dump.adoc
new file mode 100644
index 000000000000..eb49ad8ae2b4
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-heap-dump.adoc
@@ -0,0 +1,27 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel cmd heap-dump
+
+Write a heap dump (.hprof) file for deep memory analysis
+
+
+== Usage
+
+[source,bash]
+----
+camel cmd heap-dump [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--dump-name` | File name for the heap dump (without .hprof extension) |  | 
String
+| `--live` | Whether to dump only live objects | true | boolean
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc
index 1ae76dc01fe4..e6edad675139 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc
@@ -22,6 +22,7 @@ camel cmd [options]
 | 
xref:jbang-commands/camel-jbang-cmd-disable-processor.adoc[disable-processor] | 
Disable Camel processor
 | xref:jbang-commands/camel-jbang-cmd-enable-processor.adoc[enable-processor] 
| Enable Camel processor
 | xref:jbang-commands/camel-jbang-cmd-gc.adoc[gc] | Trigger Java Memory 
Garbage Collector
+| xref:jbang-commands/camel-jbang-cmd-heap-dump.adoc[heap-dump] | Write a heap 
dump (.hprof) file for deep memory analysis
 | xref:jbang-commands/camel-jbang-cmd-heap-histogram.adoc[heap-histogram] | 
Display class-level heap memory usage in a running Camel integration
 | xref:jbang-commands/camel-jbang-cmd-load.adoc[load] | Loads new source files 
into an existing Camel
 | xref:jbang-commands/camel-jbang-cmd-logger.adoc[logger] | List or change 
logging levels
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index eee6b7a0c13d..4fe345bf8c0d 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -380,6 +380,8 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                 doActionSqlQueryTask(root);
             } else if ("sql-update-row".equals(action)) {
                 doActionSqlUpdateRowTask(root);
+            } else if ("heap-dump".equals(action)) {
+                doActionHeapDumpTask(root);
             } else if ("jfr-memory-leak".equals(action)) {
                 doActionJfrMemoryLeakTask(root);
             } else if ("cli-debug".equals(action)) {
@@ -855,6 +857,27 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
         }
     }
 
+    private void doActionHeapDumpTask(JsonObject root) throws IOException {
+        DevConsole dc = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+                .resolveById("heap-dump");
+        if (dc != null) {
+            Map<String, Object> params = new HashMap<>();
+            String name = root.getString("name");
+            if (name != null) {
+                params.put("name", name);
+            }
+            String live = root.getString("live");
+            if (live != null) {
+                params.put("live", live);
+            }
+            JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON, 
params);
+            LOG.trace("Updating output file: {}", outputFile);
+            IOHelper.writeText(json.toJson(), outputFile);
+        } else {
+            IOHelper.writeText("{}", outputFile);
+        }
+    }
+
     private void doActionJfrMemoryLeakTask(JsonObject root) throws IOException 
{
         DevConsole dc = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
                 .resolveById("jfr-memory-leak");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 092515439667..aad2e662ac5d 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -3,7 +3,7 @@
     { "name": "ask", "fullName": "ask", "description": "Ask a question about a 
running Camel application using AI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Ask", "options": [ { "names": 
"--api-key", "description": "API key. Also reads ANTHROPIC_API_KEY, 
OPENAI_API_KEY, or LLM_API_KEY env vars", "javaType": "java.lang.String", 
"type": "string" }, { "names": "--api-type", "description": "API type: 
'ollama', 'openai', or 'anthropic'", "javaType": "LlmClient.ApiType", "type" 
[...]
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind 
source and sink Kamelets as a new Camel integration", "deprecated": true, 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": 
[ { "names": "--error-handler", "description": "Add error handler 
(none|log|sink:<endpoint>). Sink endpoints are expected in the format 
[[apigroup\/]version:]kind:[namespace\/]name, plain Camel URIs or Kamelet 
name.", "javaType": "java.lang.String", "type": "stri [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts 
from Camel Catalog", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"component", "fullName": "catalog component", "description": "List components 
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "To  [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index 87014e76d6f5..5bfd9a8042b9 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -110,6 +110,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("disable-processor", new 
CommandLine(new CamelProcessorDisableAction(this)))
                         .addSubcommand("enable-processor", new CommandLine(new 
CamelProcessorEnableAction(this)))
                         .addSubcommand("gc", new CommandLine(new 
CamelGCAction(this)))
+                        .addSubcommand("heap-dump", new CommandLine(new 
CamelHeapDump(this)))
                         .addSubcommand("heap-histogram", new CommandLine(new 
CamelHeapHistogram(this)))
                         .addSubcommand("memory-leak", new CommandLine(new 
CamelMemoryLeak(this)))
                         .addSubcommand("load", new CommandLine(new 
CamelLoadAction(this)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHeapDump.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHeapDump.java
new file mode 100644
index 000000000000..b2974a04368e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHeapDump.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "heap-dump", description = "Write a heap dump (.hprof) file 
for deep memory analysis", sortOptions = false,
+         showDefaultValues = true,
+         footer = {
+                 "%nExamples:",
+                 "  camel cmd heap-dump",
+                 "  camel cmd heap-dump --name=mydump",
+                 "  camel cmd heap-dump --name=mydump --live=false" })
+public class CamelHeapDump extends ActionBaseCommand {
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--dump-name" },
+                        description = "File name for the heap dump (without 
.hprof extension)")
+    String dumpName;
+
+    @CommandLine.Option(names = { "--live" },
+                        description = "Whether to dump only live objects", 
defaultValue = "true")
+    boolean live = true;
+
+    public CamelHeapDump(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        List<Long> pids = findPids(name);
+        if (pids.isEmpty()) {
+            return 1;
+        } else if (pids.size() > 1) {
+            printer().println("Name or pid " + name + " matches " + pids.size()
+                              + " running Camel integrations. Specify a name 
or PID that matches exactly one.");
+            return 1;
+        }
+
+        long pid = pids.get(0);
+
+        // ensure output file is deleted before executing action
+        Path outputFile = getOutputFile(Long.toString(pid));
+        PathUtils.deleteFile(outputFile);
+
+        JsonObject root = new JsonObject();
+        root.put("action", "heap-dump");
+        if (dumpName != null) {
+            root.put("name", dumpName);
+        }
+        root.put("live", Boolean.toString(live));
+
+        Path f = getActionFile(Long.toString(pid));
+        try {
+            Files.writeString(f, root.toJson());
+        } catch (Exception e) {
+            // ignore
+        }
+
+        JsonObject jo = getJsonObject(outputFile, 60000);
+        if (jo != null) {
+            String error = jo.getString("error");
+            if (error != null) {
+                printer().println("Heap dump failed: " + error);
+                return 1;
+            }
+            String file = jo.getString("file");
+            long size = jo.getLongOrDefault("size", 0);
+            printer().printf("Heap dump written to: %s (%s)%n", file, 
formatSize(size));
+        } else {
+            printer().println("Response from running Camel with PID " + pid + 
" not received within 60 seconds");
+            return 1;
+        }
+
+        // delete output file after use
+        PathUtils.deleteFile(outputFile);
+
+        return 0;
+    }
+
+    private static String formatSize(long bytes) {
+        if (bytes >= 1024 * 1024 * 1024) {
+            return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 
1024.0));
+        } else if (bytes >= 1024 * 1024) {
+            return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
+        } else if (bytes >= 1024) {
+            return String.format("%.1f KB", bytes / 1024.0);
+        } else {
+            return bytes + " bytes";
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index 53aa43a55795..04825e196bdf 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -340,6 +340,27 @@ public final class ToolRegistry {
                                             + "Useful for diagnosing memory 
leaks and understanding which classes dominate heap usage.")
                 .executor((ctx, args) -> ctx.executeAction("heap-histogram", 
null)));
 
+        register(tool("write_heap_dump",
+                "Write a heap dump (.hprof) file for deep memory analysis with 
tools like Eclipse MAT or VisualVM. "
+                                         + "Returns the absolute file path and 
size of the dump.")
+                .param("name", "string",
+                        "File name for the heap dump (without .hprof 
extension). Defaults to heap-dump-<timestamp>", false)
+                .param("live", "string",
+                        "Whether to dump only live objects (default true). 
Live dumps trigger a GC first", false)
+                .readOnly(false)
+                .executor((ctx, args) -> {
+                    String name = args.get("name");
+                    String live = args.get("live");
+                    return ctx.executeAction("heap-dump", root -> {
+                        if (name != null && !name.isBlank()) {
+                            root.put("name", name);
+                        }
+                        if (live != null && !live.isBlank()) {
+                            root.put("live", live);
+                        }
+                    });
+                }));
+
         register(tool("execute_sql",
                 "Execute a SQL query against a DataSource in the running Camel 
application. "
                                      + "Returns structured JSON with columns, 
rows, and metadata for SELECT queries, "
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index aa17b63a9980..d0affc50fc51 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -342,6 +342,25 @@ public class RuntimeTools {
         return delegateToRegistry("get_heap_histogram", nameOrPid, Map.of());
     }
 
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = false, 
destructiveHint = false, openWorldHint = false),
+          description = """
+                  Write a heap dump (.hprof) file from a running Camel 
integration for deep memory analysis \
+                  with tools like Eclipse MAT, VisualVM, or jhat. The dump is 
written to the process working directory.""")
+    public JsonObject camel_runtime_heap_dump(
+            @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+            @ToolArg(description = "File name for the heap dump (without 
.hprof extension). Defaults to heap-dump-<timestamp>") String name,
+            @ToolArg(description = "Whether to dump only live objects (default 
true). Live dumps trigger a GC first") String live) {
+        RuntimeService.ProcessInfo p = 
runtimeService.findSingleProcess(nameOrPid);
+        return runtimeService.executeAction(p.pid(), "heap-dump", root -> {
+            if (name != null && !name.isBlank()) {
+                root.put("name", name);
+            }
+            if (live != null && !live.isBlank()) {
+                root.put("live", live);
+            }
+        });
+    }
+
     @Tool(annotations = @Tool.Annotations(readOnlyHint = false, 
destructiveHint = false, openWorldHint = false),
           description = """
                   Diagnose memory leaks in a running Camel integration using 
Java Flight Recorder (JFR). \
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 2de53f6ac5cf..95d1e3ce11d6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -267,6 +267,7 @@ public class CamelMonitor extends CamelCommand {
         actionsPopup.setContext(ctx);
         actionsPopup.setMonitorContext(ctx);
         actionsPopup.setNotificationCallback((msg, error) -> 
setNotification(msg, error));
+        ctx.notificationCallback = (msg, error) -> setNotification(msg, error);
         actionsPopup.setResetStatsAction(this::resetStats);
         shellPanel.setContext(ctx);
         aiPanel.setContext(ctx);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
index 983fe07f3e10..44cab307a2fe 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
@@ -127,6 +127,10 @@ class MemoryLeakTab extends AbstractTab {
 
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
+        if (ke.isChar('h')) {
+            triggerHeapDump();
+            return true;
+        }
         if (state == State.IDLE || state == State.HAS_RESULTS) {
             if (ke.isCharIgnoreCase('r')) {
                 startRecording();
@@ -725,10 +729,12 @@ class MemoryLeakTab extends AbstractTab {
                 hint(spans, "R", "record");
                 hint(spans, "d", "mode [" + modeLabel + "]");
                 hint(spans, "+/-", "duration [" + duration + "s]");
+                hint(spans, "h", "heap dump");
             }
             case RECORDING -> {
                 hint(spans, "Esc", "back");
                 hint(spans, "X", "stop");
+                hint(spans, "h", "heap dump");
             }
             case HAS_RESULTS -> {
                 hint(spans, "Esc", "back");
@@ -739,6 +745,7 @@ class MemoryLeakTab extends AbstractTab {
                 hint(spans, "R", "new recording");
                 hint(spans, "d", "mode [" + modeLabel + "]");
                 hint(spans, "+/-", "duration [" + duration + "s]");
+                hint(spans, "h", "heap dump");
                 hintLast(spans, "PgUp/Dn", "detail");
             }
             default -> hintLast(spans, "Esc", "back");
@@ -1592,6 +1599,43 @@ class MemoryLeakTab extends AbstractTab {
                         || method.startsWith("jdk.") || 
method.startsWith("sun.") || method.startsWith("com.sun."));
     }
 
+    private void triggerHeapDump() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            return;
+        }
+        notify("Writing heap dump...", false);
+        String pid = info.pid;
+        startDaemonThread("heap-dump-" + pid, () -> {
+            Path outputFile = ctx.getOutputFile(pid);
+            PathUtils.deleteFile(outputFile);
+            JsonObject root = new JsonObject();
+            root.put("action", "heap-dump");
+            Path actionFile = ctx.getActionFile(pid);
+            PathUtils.writeTextSafely(root.toJson(), actionFile);
+            JsonObject jo = TuiHelper.pollJsonResponse(outputFile, 60000);
+            if (jo != null) {
+                String error = jo.getString("error");
+                if (error != null) {
+                    notify("Heap dump failed: " + error, true);
+                } else {
+                    String file = jo.getString("file");
+                    long size = jo.getLongOrDefault("size", 0);
+                    notify("Heap dump: " + file + " (" + 
TuiHelper.formatBytes(size) + ")", false);
+                }
+            } else {
+                notify("Heap dump: no response within 60s", true);
+            }
+            PathUtils.deleteFile(outputFile);
+        });
+    }
+
+    private void notify(String message, boolean error) {
+        if (ctx.notificationCallback != null) {
+            ctx.notificationCallback.accept(message, error);
+        }
+    }
+
     private static void startDaemonThread(String name, Runnable task) {
         Thread t = new Thread(task);
         t.setDaemon(true);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
index 539e4789a2de..88baf2b7c76f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
@@ -64,6 +64,10 @@ class MemoryTab extends AbstractTab {
             triggerGC();
             return true;
         }
+        if (ke.isChar('h')) {
+            triggerHeapDump();
+            return true;
+        }
         return false;
     }
 
@@ -437,6 +441,47 @@ class MemoryTab extends AbstractTab {
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
         hint(spans, "g", "gc");
+        hint(spans, "h", "heap dump");
+    }
+
+    private void triggerHeapDump() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            return;
+        }
+        notify("Writing heap dump...", false);
+        String pid = info.pid;
+        Thread t = new Thread(() -> {
+            Path outputFile = ctx.getOutputFile(pid);
+            PathUtils.deleteFile(outputFile);
+            JsonObject root = new JsonObject();
+            root.put("action", "heap-dump");
+            Path actionFile = ctx.getActionFile(pid);
+            PathUtils.writeTextSafely(root.toJson(), actionFile);
+            JsonObject jo = TuiHelper.pollJsonResponse(outputFile, 60000);
+            if (jo != null) {
+                String error = jo.getString("error");
+                if (error != null) {
+                    notify("Heap dump failed: " + error, true);
+                } else {
+                    String file = jo.getString("file");
+                    long size = jo.getLongOrDefault("size", 0);
+                    notify("Heap dump: " + file + " (" + formatBytes(size) + 
")", false);
+                }
+            } else {
+                notify("Heap dump: no response within 60s", true);
+            }
+            PathUtils.deleteFile(outputFile);
+        });
+        t.setDaemon(true);
+        t.setName("heap-dump-" + pid);
+        t.start();
+    }
+
+    private void notify(String message, boolean error) {
+        if (ctx.notificationCallback != null) {
+            ctx.notificationCallback.accept(message, error);
+        }
     }
 
     private void triggerGC() {
@@ -540,6 +585,7 @@ class MemoryTab extends AbstractTab {
                 ## Keys
 
                 - `g` — trigger garbage collection on the running integration 
(sends a GC request to the JVM — useful for testing if high usage is just 
uncollected garbage)
+                - `h` — write a heap dump (.hprof) file for deep analysis with 
tools like Eclipse MAT or VisualVM
                 - `Esc` — back
                 """;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
index 995c9b1f0bcf..e298e7664358 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 
 import dev.tamboui.tui.TuiRunner;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
@@ -39,6 +40,7 @@ class MonitorContext {
     int logPinPercent;
     boolean logPinVisible;
     boolean ratePerMinute;
+    BiConsumer<String, Boolean> notificationCallback;
 
     MonitorContext(
                    AtomicReference<List<IntegrationInfo>> data,


Reply via email to