Skip to content

Commit d802332

Browse files
davsclausclaude
andauthored
CAMEL-23870: Add heap histogram dev console, TUI panel, CLI command, and MCP tool (#24364)
* CAMEL-23870: Add HeapHistogram dev console and TUI panel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * CAMEL-23870: Add camel cmd heap-histogram CLI command Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: use Locale.US consistently across TUI and CLI for number formatting Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: compute heap histogram totals from visible entries only Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: add detail panel to heap histogram tab with package summary and JAR origin Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: reset table selection when heap histogram filter changes Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: move heap histogram summary into table title Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: update heap histogram F1 help text and MCP description Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * CAMEL-23870: add generated docs and metadata for heap-histogram command Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * CAMEL-23870: add heap histogram MCP tool for camel ask Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * CAMEL-23870: add generated doc page for heap-histogram CLI command Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * CAMEL-23870: add generated catalog files for heap-histogram dev console Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> * chore: fix TUI render tests to match CSS theme styles Tests were asserting pre-CSS-theme colors (YELLOW for hint keys, GREEN for success) but the CSS engine resolves to BLACK and LIGHT_GREEN. Also fixed renderNoSelection tests to match current prompt text. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> --------- Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ad0273a commit d802332

23 files changed

Lines changed: 1288 additions & 60 deletions

File tree

catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ gcp-secrets
2020
groovy
2121
hashicorp-secrets
2222
health
23+
heap-histogram
2324
inflight
2425
internal-tasks
2526
java-security
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"console": {
3+
"kind": "console",
4+
"group": "camel",
5+
"name": "heap-histogram",
6+
"title": "Heap Histogram",
7+
"description": "Displays class-level heap memory usage",
8+
"deprecated": false,
9+
"javaType": "org.apache.camel.impl.console.HeapHistogramDevConsole",
10+
"groupId": "org.apache.camel",
11+
"artifactId": "camel-console",
12+
"version": "4.21.0-SNAPSHOT"
13+
}
14+
}
15+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* Generated by camel build tools - do NOT edit this file! */
2+
package org.apache.camel.impl.console;
3+
4+
import javax.annotation.processing.Generated;
5+
import java.util.Map;
6+
7+
import org.apache.camel.CamelContext;
8+
import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
9+
import org.apache.camel.spi.PropertyConfigurerGetter;
10+
import org.apache.camel.spi.ConfigurerStrategy;
11+
import org.apache.camel.spi.GeneratedPropertyConfigurer;
12+
import org.apache.camel.util.CaseInsensitiveMap;
13+
import org.apache.camel.impl.console.HeapHistogramDevConsole;
14+
15+
/**
16+
* Generated by camel build tools - do NOT edit this file!
17+
*/
18+
@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
19+
@SuppressWarnings("unchecked")
20+
public class HeapHistogramDevConsoleConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, ExtendedPropertyConfigurerGetter {
21+
22+
private static final Map<String, Object> ALL_OPTIONS;
23+
static {
24+
Map<String, Object> map = new CaseInsensitiveMap();
25+
map.put("CamelContext", org.apache.camel.CamelContext.class);
26+
ALL_OPTIONS = map;
27+
}
28+
29+
@Override
30+
public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) {
31+
org.apache.camel.impl.console.HeapHistogramDevConsole target = (org.apache.camel.impl.console.HeapHistogramDevConsole) obj;
32+
switch (ignoreCase ? name.toLowerCase() : name) {
33+
case "camelcontext":
34+
case "camelContext": target.setCamelContext(property(camelContext, org.apache.camel.CamelContext.class, value)); return true;
35+
default: return false;
36+
}
37+
}
38+
39+
@Override
40+
public Map<String, Object> getAllOptions(Object target) {
41+
return ALL_OPTIONS;
42+
}
43+
44+
@Override
45+
public Class<?> getOptionType(String name, boolean ignoreCase) {
46+
switch (ignoreCase ? name.toLowerCase() : name) {
47+
case "camelcontext":
48+
case "camelContext": return org.apache.camel.CamelContext.class;
49+
default: return null;
50+
}
51+
}
52+
53+
@Override
54+
public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
55+
org.apache.camel.impl.console.HeapHistogramDevConsole target = (org.apache.camel.impl.console.HeapHistogramDevConsole) obj;
56+
switch (ignoreCase ? name.toLowerCase() : name) {
57+
case "camelcontext":
58+
case "camelContext": return target.getCamelContext();
59+
default: return null;
60+
}
61+
}
62+
}
63+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"console": {
3+
"kind": "console",
4+
"group": "camel",
5+
"name": "heap-histogram",
6+
"title": "Heap Histogram",
7+
"description": "Displays class-level heap memory usage",
8+
"deprecated": false,
9+
"javaType": "org.apache.camel.impl.console.HeapHistogramDevConsole",
10+
"groupId": "org.apache.camel",
11+
"artifactId": "camel-console",
12+
"version": "4.21.0-SNAPSHOT"
13+
}
14+
}
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated by camel build tools - do NOT edit this file!
2+
class=org.apache.camel.impl.console.HeapHistogramDevConsoleConfigurer
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated by camel build tools - do NOT edit this file!
2+
class=org.apache.camel.impl.console.HeapHistogramDevConsole

core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Generated by camel build tools - do NOT edit this file!
2-
dev-consoles=bean blocked browse circuit-breaker consumer context datasource debug endpoint errors eval-language event gc health inflight internal-tasks java-security 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 transformers type-converters variables
2+
dev-consoles=bean blocked browse circuit-breaker consumer context datasource debug endpoint errors eval-language event gc health heap-histogram inflight internal-tasks java-security 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 transformers type-converters variables
33
groupId=org.apache.camel
44
artifactId=camel-console
55
version=4.21.0-SNAPSHOT
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.camel.impl.console;
18+
19+
import java.lang.management.ManagementFactory;
20+
import java.util.Map;
21+
22+
import javax.management.MBeanServer;
23+
import javax.management.ObjectName;
24+
25+
import org.apache.camel.spi.Configurer;
26+
import org.apache.camel.spi.annotations.DevConsole;
27+
import org.apache.camel.support.console.AbstractDevConsole;
28+
import org.apache.camel.util.json.JsonArray;
29+
import org.apache.camel.util.json.JsonObject;
30+
31+
@DevConsole(name = "heap-histogram", displayName = "Heap Histogram", description = "Displays class-level heap memory usage")
32+
@Configurer(extended = true)
33+
public class HeapHistogramDevConsole extends AbstractDevConsole {
34+
35+
public HeapHistogramDevConsole() {
36+
super("jvm", "heap-histogram", "Heap Histogram", "Displays class-level heap memory usage");
37+
}
38+
39+
@Override
40+
protected String doCallText(Map<String, Object> options) {
41+
String histogram = invokeGcClassHistogram();
42+
if (histogram == null) {
43+
return "Heap histogram not available (DiagnosticCommand MBean not found)";
44+
}
45+
int limit = getLimit(options);
46+
if (limit > 0) {
47+
return truncateText(histogram, limit);
48+
}
49+
return histogram;
50+
}
51+
52+
@Override
53+
protected JsonObject doCallJson(Map<String, Object> options) {
54+
JsonObject root = new JsonObject();
55+
56+
String histogram = invokeGcClassHistogram();
57+
if (histogram == null) {
58+
root.put("classes", new JsonArray());
59+
root.put("totalInstances", 0);
60+
root.put("totalBytes", 0);
61+
return root;
62+
}
63+
64+
int limit = getLimit(options);
65+
return parseHistogram(histogram, limit);
66+
}
67+
68+
private static int getLimit(Map<String, Object> options) {
69+
Object val = options.get("limit");
70+
if (val != null) {
71+
try {
72+
return Integer.parseInt(val.toString());
73+
} catch (NumberFormatException e) {
74+
// use default
75+
}
76+
}
77+
return 100;
78+
}
79+
80+
private static String invokeGcClassHistogram() {
81+
try {
82+
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
83+
ObjectName name = new ObjectName("com.sun.management:type=DiagnosticCommand");
84+
String result = (String) server.invoke(
85+
name, "gcClassHistogram",
86+
new Object[] { null },
87+
new String[] { String[].class.getName() });
88+
return result;
89+
} catch (Exception e) {
90+
return null;
91+
}
92+
}
93+
94+
private static JsonObject parseHistogram(String histogram, int limit) {
95+
JsonObject root = new JsonObject();
96+
JsonArray arr = new JsonArray();
97+
root.put("classes", arr);
98+
99+
long totalInstances = 0;
100+
long totalBytes = 0;
101+
int count = 0;
102+
103+
String[] lines = histogram.split("\n");
104+
for (String line : lines) {
105+
line = line.trim();
106+
if (line.isEmpty() || line.startsWith("-") || line.startsWith("num")) {
107+
continue;
108+
}
109+
if (line.startsWith("Total")) {
110+
String[] parts = line.split("\\s+");
111+
if (parts.length >= 3) {
112+
totalInstances = parseLong(parts[1]);
113+
totalBytes = parseLong(parts[2]);
114+
}
115+
continue;
116+
}
117+
118+
String[] parts = line.split("\\s+");
119+
if (parts.length < 4) {
120+
continue;
121+
}
122+
123+
// parts[0]=num: parts[1]=#instances parts[2]=#bytes parts[3]=class
124+
String numStr = parts[0].replace(":", "");
125+
int num = (int) parseLong(numStr);
126+
long instances = parseLong(parts[1]);
127+
long bytes = parseLong(parts[2]);
128+
String className = parts[3];
129+
130+
if (limit > 0 && count >= limit) {
131+
continue;
132+
}
133+
134+
JsonObject jo = new JsonObject();
135+
jo.put("num", num);
136+
jo.put("instances", instances);
137+
jo.put("bytes", bytes);
138+
jo.put("className", className);
139+
arr.add(jo);
140+
count++;
141+
}
142+
143+
root.put("totalInstances", totalInstances);
144+
root.put("totalBytes", totalBytes);
145+
146+
return root;
147+
}
148+
149+
private static long parseLong(String s) {
150+
try {
151+
return Long.parseLong(s);
152+
} catch (NumberFormatException e) {
153+
return 0;
154+
}
155+
}
156+
157+
private static String truncateText(String histogram, int limit) {
158+
StringBuilder sb = new StringBuilder();
159+
int count = 0;
160+
String[] lines = histogram.split("\n");
161+
for (String line : lines) {
162+
String trimmed = line.trim();
163+
if (trimmed.isEmpty() || trimmed.startsWith("-") || trimmed.startsWith("num")) {
164+
sb.append(line).append('\n');
165+
continue;
166+
}
167+
if (trimmed.startsWith("Total")) {
168+
sb.append(line).append('\n');
169+
continue;
170+
}
171+
if (count < limit) {
172+
sb.append(line).append('\n');
173+
count++;
174+
}
175+
}
176+
return sb.toString();
177+
}
178+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
3+
= camel cmd heap-histogram
4+
5+
Display class-level heap memory usage in a running Camel integration
6+
7+
8+
== Usage
9+
10+
[source,bash]
11+
----
12+
camel cmd heap-histogram [options]
13+
----
14+
15+
16+
17+
== Options
18+
19+
[cols="2,5,1,2",options="header"]
20+
|===
21+
| Option | Description | Default | Type
22+
| `--filter` | Filter class names (use all to include all classes) | all | String
23+
| `--sort` | Sort by bytes, instances, or className | bytes | String
24+
| `--top` | Show only the top N classes | 50 | int
25+
| `--watch` | Execute periodically and showing output fullscreen | | boolean
26+
| `-h,--help` | Display the help and sub-commands | | boolean
27+
|===
28+
29+

docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ camel cmd [options]
2222
| xref:jbang-commands/camel-jbang-cmd-disable-processor.adoc[disable-processor] | Disable Camel processor
2323
| xref:jbang-commands/camel-jbang-cmd-enable-processor.adoc[enable-processor] | Enable Camel processor
2424
| xref:jbang-commands/camel-jbang-cmd-gc.adoc[gc] | Trigger Java Memory Garbage Collector
25+
| xref:jbang-commands/camel-jbang-cmd-heap-histogram.adoc[heap-histogram] | Display class-level heap memory usage in a running Camel integration
2526
| xref:jbang-commands/camel-jbang-cmd-load.adoc[load] | Loads new source files into an existing Camel
2627
| xref:jbang-commands/camel-jbang-cmd-logger.adoc[logger] | List or change logging levels
2728
| xref:jbang-commands/camel-jbang-cmd-receive.adoc[receive] | Receive and dump messages from remote endpoints

0 commit comments

Comments
 (0)