This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 633e1bf0b75d CAMEL-23688: camel-jbang - Tests for get route,
processor, metric and health (#27974)
633e1bf0b75d is described below
commit 633e1bf0b75d3d6bf778354ebdf9275969de1c27
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 16 23:27:16 2026 +0200
CAMEL-23688: camel-jbang - Tests for get route, processor, metric and
health (#27974)
Tests for camel get route (65% covered), get processor (59%), get metric
(58%) and get health (60%), with status files as the route, micrometer
and health dev consoles write them. They found these bugs, fixed here:
* camel get metric skipped an integration that has a distribution
summary, with a warning: it read the count of the summary from a field
the dev console does not have.
* camel get metric --filter=timer*, the example of its help, showed
nothing; the filter takes a pattern now. --custom left out the metrics
of Camel in the classic naming (CamelExchangesTotal) only, not in the
default naming of Micrometer (camel.exchanges.total).
* camel get route --error --limit limited the routes before it picked
the ones with an error, so it could show none, and --note did not show
the notes of the routes with an error.
* camel get processor --filter showed the matching processors without
their route, and so without the name and pid of their integration;
--filter-mean left out the faster routes but not the faster processors.
* camel get health --level with a value other than full or default was
taken as full; now a usage error.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 13 +
.../modules/ROOT/pages/camel-jbang-managing.adoc | 3 +
.../commands/process/CamelProcessorStatus.java | 58 +--
.../core/commands/process/CamelRouteStatus.java | 13 +-
.../jbang/core/commands/process/ListHealth.java | 10 +-
.../jbang/core/commands/process/ListMetric.java | 56 ++-
.../commands/process/CamelProcessorStatusTest.java | 278 +++++++++-----
.../commands/process/CamelRouteStatusTest.java | 410 +++++++++++++--------
.../core/commands/process/ListHealthTest.java | 272 +++++++++-----
.../core/commands/process/ListMetricTest.java | 250 +++++++++----
10 files changed, 907 insertions(+), 456 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index b332441dfd88..a64f86317060 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -409,6 +409,19 @@ The JSON of the listings (`--json`) changes, so that a
script can use the values
the state is `status` (it was `state` in `camel get circuit-breaker`).
`camel get consumer` has `total` instead of
`poll`. Every row of `camel get processor` has the `name` of its integration.
+=== camel-jbang - camel get route, processor, metric and health
+
+* `camel get route --error --limit` shows that number of routes with an error
(it limited the routes first, and then
+ showed the ones with an error among them), and `--note` shows the notes of
the routes with an error too.
+* `camel get processor --filter` shows the matching processors with their
route, and so with the name and pid of their
+ integration (it showed the processors alone). `--filter-mean` leaves out the
faster processors too, as its
+ description says (it left out the faster routes only).
+* `camel get metric` shows the distribution summaries: an integration that has
one was skipped with a warning.
+ `--filter` takes a pattern such as `timer*`, as in its help (it showed
nothing), and `--custom` leaves out the metrics
+ of Camel in the default naming of Micrometer, such as
`camel.exchanges.total`, not only in the classic naming
+ (`CamelExchangesTotal`).
+* `camel get health --level` other than `full` or `default` exits with `2` (it
showed every check, as `full` does).
+
=== camel-jbang - camel stop and camel restart wait for the integration
* `camel stop` waits until the integration has stopped, up to `--timeout` (60
seconds by default), and exits with `124`
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index 3088a9644d4c..d46af59be16d 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -824,6 +824,9 @@ $ camel get metric
11562 MyCoolCamel gauge system.load.average.1m
3.58935546875
----
+Use `--filter` for the metrics of a type, or with a name or tags that contain
the text or match a pattern such as
+`timer*`, `--custom` to leave out the metrics of Camel itself, and `--all` to
include the metrics that are still `0`.
+
== Circuit breaker status
View the state of xref:components:eips:circuitBreaker-eip.adoc[Circuit
Breakers]:
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatus.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatus.java
index b85032610f45..1b8a23a573f6 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatus.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatus.java
@@ -22,6 +22,7 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import com.github.freva.asciitable.AsciiTable;
@@ -211,28 +212,13 @@ public class CamelProcessorStatus extends
ProcessWatchCommand {
// filter rows
if (running || filter != null || group != null) {
- rows.removeIf(r -> {
- boolean keep = true;
- if (filter != null) {
- keep = PatternHelper.matchPatterns(r.processorId, filter);
- }
- if (!keep && filter != null) {
- for (String f : filter) {
- if (!keep) {
- String w = f.endsWith("*") ? f : f + "*"; // use
wildcard in matching processor
- keep = PatternHelper.matchPattern(r.processor, w);
- }
- }
- }
- // group take precedence
- if (keep && group != null) {
- keep = PatternHelper.matchPatterns(r.group, group);
- }
- if (keep && running) {
- keep = "Started".equals(r.state);
- }
- return !keep;
- });
+ // a route stays with its processors that are kept, so they are
shown with their integration and route
+ Set<String> routesOfProcessors = rows.stream()
+ .filter(r -> r.processorId != null && accept(r))
+ .map(r -> r.pid + "/" + r.routeId)
+ .collect(Collectors.toSet());
+ rows.removeIf(r -> !accept(r)
+ && (r.processorId != null ||
!routesOfProcessors.contains(r.pid + "/" + r.routeId)));
}
// sort rows
@@ -245,6 +231,29 @@ public class CamelProcessorStatus extends
ProcessWatchCommand {
return 0;
}
+ private boolean accept(Row r) {
+ boolean keep = true;
+ if (filter != null) {
+ keep = PatternHelper.matchPatterns(r.processorId, filter);
+ }
+ if (!keep && filter != null) {
+ for (String f : filter) {
+ if (!keep) {
+ String w = f.endsWith("*") ? f : f + "*"; // use wildcard
in matching processor
+ keep = PatternHelper.matchPattern(r.processor, w);
+ }
+ }
+ }
+ // group take precedence
+ if (keep && group != null) {
+ keep = PatternHelper.matchPatterns(r.group, group);
+ }
+ if (keep && running) {
+ keep = "Started".equals(r.state);
+ }
+ return keep;
+ }
+
private void addProcessors(Row route, List<Row> rows, List<JsonObject>
processors) {
for (JsonObject o : processors) {
Row row = new Row();
@@ -252,7 +261,6 @@ public class CamelProcessorStatus extends
ProcessWatchCommand {
row.name = route.name;
row.routeId = route.routeId;
row.group = route.group;
- rows.add(row);
row.processorId = o.getString("id");
row.nodePrefixId = o.getString("nodePrefixId");
row.processor = o.getString("processor");
@@ -305,6 +313,10 @@ public class CamelProcessorStatus extends
ProcessWatchCommand {
}
}
}
+ // with --filter-mean only the slower processors, as for the routes
+ if (mean <= 0 || (row.mean != null && Long.parseLong(row.mean) >=
mean)) {
+ rows.add(row);
+ }
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
index d7477ccc66bc..d8c7f3ce2027 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
@@ -244,17 +244,20 @@ public class CamelRouteStatus extends ProcessWatchCommand
{
// sort rows
rows.sort(order());
+ if (error) {
+ // the limit is a number of routes with an error
+ rows.removeIf(r -> r.lastErrorPhase == null);
+ }
if (limit > 0 && rows.size() > limit) {
rows.subList(limit, rows.size()).clear();
}
if (!rows.isEmpty() || jsonOutput) {
if (error) {
- List<Row> failed = rows.stream().filter(r -> r.lastErrorPhase
!= null).toList();
if (jsonOutput) {
-
printer().println(Jsoner.serialize(failed.stream().map(this::toErrorJson).collect(Collectors.toList())));
+
printer().println(Jsoner.serialize(rows.stream().map(this::toErrorJson).collect(Collectors.toList())));
} else {
- for (Row r : failed) {
+ for (Row r : rows) {
printErrorTable(r, remoteVisible.get());
}
}
@@ -382,10 +385,10 @@ public class CamelRouteStatus extends ProcessWatchCommand
{
new
Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid),
new
Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(30,
OverflowBehaviour.ELLIPSIS_RIGHT)
.with(r -> r.name),
- new
Column().header("ID").visible(!description).dataAlign(HorizontalAlign.LEFT)
+ new Column().header("ID").visible(!description &&
!note).dataAlign(HorizontalAlign.LEFT)
.maxWidth(20, OverflowBehaviour.ELLIPSIS_RIGHT)
.with(this::getId),
- new
Column().header("ID").visible(description).dataAlign(HorizontalAlign.LEFT)
+ new Column().header("ID").visible(description ||
note).dataAlign(HorizontalAlign.LEFT)
.maxWidth(errIdDescW, OverflowBehaviour.NEWLINE)
.with(this::getIdAndNoteDescription),
new
Column().header("FROM").visible(!wideUri).dataAlign(HorizontalAlign.LEFT)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealth.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealth.java
index 0ff5cd2938f4..fb88d8614e47 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealth.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealth.java
@@ -20,6 +20,7 @@ import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -30,6 +31,7 @@ import com.github.freva.asciitable.Column;
import com.github.freva.asciitable.HorizontalAlign;
import com.github.freva.asciitable.OverflowBehaviour;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
import org.apache.camel.dsl.jbang.core.common.PidNameAgeCompletionCandidates;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
import org.apache.camel.dsl.jbang.core.common.RowSort;
@@ -93,6 +95,12 @@ public class ListHealth extends ProcessWatchCommand {
public Integer doProcessWatchCall() throws Exception {
final List<Row> rows = new ArrayList<>();
+ // the options are wrong for every integration, so before reading them
+ Comparator<Row> order = ORDER.comparator(sort);
+ if (level != null && !"default".equals(level) &&
!"full".equals(level)) {
+ throw CliException.usage("--level must be full or default, was: "
+ level);
+ }
+
// include stack-traces
if (trace && depth <= 1) {
depth = Integer.MAX_VALUE;
@@ -193,7 +201,7 @@ public class ListHealth extends ProcessWatchCommand {
});
// sort rows
- rows.sort(ORDER.comparator(sort));
+ rows.sort(order);
if (!rows.isEmpty() || jsonOutput) {
if (jsonOutput) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
index 91deab646e3b..0aa05a1dedbf 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
@@ -33,6 +33,7 @@ import
org.apache.camel.dsl.jbang.core.common.PidNameAgeCompletionCandidates;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
import org.apache.camel.dsl.jbang.core.common.RowSort;
import org.apache.camel.dsl.jbang.core.common.TerminalWidthHelper;
+import org.apache.camel.support.PatternHelper;
import org.apache.camel.util.TimeUtils;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
@@ -117,14 +118,7 @@ public class ListMetric extends ProcessWatchCommand {
row.tags = extractTags(jo);
row.count = jo.getDouble("count");
- if (custom && row.metricName.startsWith("Camel")) {
- continue; // skip internal camel metrics
- }
- if (!all && getNumber(row.count).isEmpty()) {
- continue;
- }
- if (filter == null || row.type.equals(filter) ||
row.metricName.contains(filter)
- || row.tags.contains(filter)) {
+ if (accept(row)) {
rows.add(row);
}
}
@@ -144,14 +138,7 @@ public class ListMetric extends ProcessWatchCommand {
row.max = jo.getDouble("max");
row.total = jo.getDouble("total");
- if (custom && row.metricName.startsWith("Camel")) {
- continue; // skip internal camel metrics
- }
- if (!all && getNumber(row.count).isEmpty()) {
- continue;
- }
- if (filter == null || row.type.equals(filter) ||
row.metricName.contains(filter)
- || row.tags.contains(filter)) {
+ if (accept(row)) {
rows.add(row);
}
}
@@ -171,14 +158,7 @@ public class ListMetric extends ProcessWatchCommand {
row.tags = extractTags(jo);
row.count = jo.getDouble("value");
- if (custom && row.metricName.startsWith("Camel")) {
- continue; // skip internal camel metrics
- }
- if (!all && getNumber(row.count).isEmpty()) {
- continue;
- }
- if (filter == null || row.type.equals(filter) ||
row.metricName.contains(filter)
- || row.tags.contains(filter)) {
+ if (accept(row)) {
rows.add(row);
}
}
@@ -193,19 +173,12 @@ public class ListMetric extends ProcessWatchCommand {
row.metricDescription = jo.getString("description");
row.metricId = extractId(jo);
row.tags = extractTags(jo);
- row.count = jo.getDouble("value");
+ row.count = jo.getDouble("count");
row.mean = jo.getDouble("mean");
row.max = jo.getDouble("max");
row.total = jo.getDouble("totalAmount");
- if (custom && row.metricName.startsWith("Camel")) {
- continue; // skip internal camel metrics
- }
- if (!all && getNumber(row.count).isEmpty()) {
- continue;
- }
- if (filter == null || row.type.equals(filter) ||
row.metricName.contains(filter)
- || row.tags.contains(filter)) {
+ if (accept(row)) {
rows.add(row);
}
}
@@ -271,6 +244,23 @@ public class ListMetric extends ProcessWatchCommand {
return 0;
}
+ // whether the metric is listed
+ private boolean accept(Row row) {
+ if (custom && (row.metricName.startsWith("Camel") ||
row.metricName.startsWith("camel."))) {
+ return false; // skip internal camel metrics (in the classic and
the micrometer naming)
+ }
+ if (!all && getNumber(row.count).isEmpty()) {
+ return false;
+ }
+ return filter == null || PatternHelper.matchPattern(row.type, filter)
|| matches(row.metricName)
+ || matches(row.tags);
+ }
+
+ // the filter is a part of the text, or a pattern such as timer*
+ private boolean matches(String text) {
+ return text.contains(filter) || PatternHelper.matchPattern(text,
filter);
+ }
+
private String getNumber(double d) {
String s = Double.toString(d);
if (s.equals("0.0") || s.equals("0,0")) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
index 92a0a56acaf3..fd869702d6b0 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
@@ -16,153 +16,259 @@
*/
package org.apache.camel.dsl.jbang.core.commands.process;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
+/**
+ * camel get processor: the routes of the integrations with their processors
as a tree, their statistics, and filtered.
+ */
@ExtendWith(MockitoExtension.class)
class CamelProcessorStatusTest extends ProcessCommandTestSupport {
@Test
- void testDisplaysProcessorRow() throws Exception {
- writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "25", "0", "0"));
-
- CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
-
+ void testEmptyOutputWhenNoProcesses() throws Exception {
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
ProcessHandle current = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(current);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
-
- int exit = command.doCall();
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("myApp"), "Should show integration
name");
- assertTrue(output.contains("myRoute"), "Should show route ID");
- assertTrue(output.contains("25"), "Should show mean processing
time");
+ assertThat(command().doCall()).isZero();
}
+
+ assertThat(printer.getStdout()).isEmpty();
+ assertThat(printer.getStderr()).isEqualTo("No running Camel
integrations");
}
@Test
- void testFilterMeanExcludesSlowProcessors() throws Exception {
- // processor with mean=5 should be excluded when --filter-mean=10
- writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "5", "0", "0"));
+ void processorsOfTheRoutesAsATree() throws Exception {
+ writeStatusFile(TEST_PID, integration(route("orders", "timer:tick",
"Started", 25,
+ processor("orders-log1", "log", 1, 5),
+ processor("orders-to1", "to[mock:out]", 2, 20))));
+
+ assertThat(call(command())).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("PID", "NAME", "ID", "PROCESSOR",
"STATUS", "TOTAL", "MEAN");
+ assertThat(lines[1]).contains("12345", "myApp", "orders",
"timer:tick", "Started", "25");
+ // the processors under their route, the id without the node prefix
and indented by level
+ assertThat(lines[2]).doesNotContain("12345", "myApp").contains(" log1
", " log ", "5");
+ assertThat(lines[3]).contains(" to1 ", " to[mock:out]", "20");
+ }
- CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
- command.mean = 10;
+ @Test
+ void jsonHasTheRoutesAndTheirProcessors() throws Exception {
+ writeStatusFile(TEST_PID, integration(route("orders", "timer:tick",
"Started", 25,
+ processor("orders-log1", "log", 1, 5))));
+
+ assertThat(call(command("--json"))).isZero();
+
+ JsonArray rows = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ assertThat(rows).hasSize(2);
+ JsonObject route = rows.getMap(0);
+ assertThat(route.getLong("pid")).isEqualTo(TEST_PID);
+ assertThat(route.getString("id")).isEqualTo("orders");
+ assertThat(route.getString("processor")).isEqualTo("timer:tick");
+ assertThat(route.getLong("mean")).isEqualTo(25);
+ JsonObject log = rows.getMap(1);
+ assertThat(log.getString("routeId")).isEqualTo("orders");
+ assertThat(log.getString("id")).isEqualTo("log1");
+ assertThat(log.getString("processor")).isEqualTo("log");
+ assertThat(log.getString("status")).isEqualTo("Started");
+ assertThat(log.getLong("total")).isEqualTo(10);
+ assertThat(log.getLong("mean")).isEqualTo(5);
+ }
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle current = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(current);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ @Test
+ void disabledProcessor() throws Exception {
+ JsonObject log = processor("orders-log1", "log", 1, 5);
+ log.put("disabled", true);
+ writeStatusFile(TEST_PID, integration(route("orders", "timer:tick",
"Started", 25, log)));
- int exit = command.doCall();
+ assertThat(call(command())).isZero();
- assertEquals(0, exit);
- assertEquals("", printer.getOutput().trim(), "Processor below mean
threshold should be filtered out");
- }
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[2]).contains("log1", "Disabled");
}
@Test
- void testFilterMeanIncludesSlowEnoughProcessors() throws Exception {
- writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "50", "0", "0"));
+ void limitIsANumberOfRoutes() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ route("orders", "timer:a", "Started", 25,
processor("orders-log1", "log", 1, 5)),
+ route("payments", "timer:b", "Started", 25,
processor("payments-log2", "log", 1, 5))));
- CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
- command.mean = 10;
+ assertThat(call(command("--limit=1"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle current = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(current);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ // with the processors of the route
+ assertThat(printer.getOutput()).contains("orders",
"log1").doesNotContain("payments", "log2");
+ }
- int exit = command.doCall();
+ @Test
+ void filterShowsTheMatchingProcessorsWithTheirRoute() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ route("orders", "timer:a", "Started", 25,
+ processor("orders-log1", "log", 1, 5),
+ processor("orders-to1", "to[mock:out]", 1, 20)),
+ route("payments", "timer:b", "Started", 25,
processor("payments-to2", "to[mock:pay]", 1, 5))));
+
+ assertThat(call(command("--filter=log*"))).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines).hasSize(3);
+ assertThat(lines[1]).contains("12345", "myApp", "orders");
+ assertThat(lines[2]).contains("log1");
+ }
- assertEquals(0, exit);
- assertTrue(printer.getOutput().contains("50"), "Processor above
mean threshold should be visible");
- }
+ @Test
+ void filterMeanShowsTheSlowerProcessors() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ route("orders", "timer:a", "Started", 25,
+ processor("orders-log1", "log", 1, 2),
+ processor("orders-to1", "to[mock:out]", 1, 20)),
+ route("payments", "timer:b", "Started", 5,
processor("payments-log2", "log", 1, 5))));
+
+ assertThat(call(command("--filter-mean=10"))).isZero();
+
+ assertThat(printer.getOutput()).contains("orders",
"to1").doesNotContain("log1", "payments", "log2");
}
@Test
- void testEmptyOutputWhenNoProcesses() throws Exception {
- CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
+ void groupShowsTheRoutesOfTheGroupWithTheirProcessors() throws Exception {
+ JsonObject orders = route("orders", "timer:a", "Started", 25,
processor("orders-log1", "log", 1, 5));
+ orders.put("group", "sales");
+ JsonObject audit = route("audit", "timer:b", "Started", 25,
processor("audit-log2", "log", 1, 5));
+ audit.put("group", "ops");
+ writeStatusFile(TEST_PID, integration(orders, audit));
+
+ assertThat(call(command("--group=sales", "--show-group"))).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("GROUP");
+ assertThat(printer.getOutput()).contains("sales", "orders",
"log1").doesNotContain("audit", "log2");
+ }
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle current = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(current);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+ @Test
+ void runningHidesTheRoutesThatAreNotStarted() throws Exception {
+ JsonObject stoppedLog = processor("stopped-log2", "log", 1, 5);
+ stoppedLog.put("state", "Stopped");
+ writeStatusFile(TEST_PID, integration(
+ route("started", "timer:a", "Started", 25,
processor("started-log1", "log", 1, 5)),
+ route("stopped", "timer:b", "Stopped", 25, stoppedLog)));
- int exit = command.doCall();
+ assertThat(call(command("--running"))).isZero();
- assertEquals(0, exit);
- assertEquals("", printer.getStdout());
- assertEquals("No running Camel integrations", printer.getStderr());
- }
+ assertThat(printer.getOutput()).contains("started",
"log1").doesNotContain("stopped", "log2");
+ }
+
+ @Test
+ void sourceShowsTheSourceLocationAndTheCodeOfTheProcessors() throws
Exception {
+ JsonObject log = processor("orders-log1", "log", 1, 5);
+ log.put("source", "file:src/main/resources/camel/app.yaml:14");
+ log.put("code", new JsonArray(
+ List.of(
+ new JsonObject(Map.of("line", 13, "code", "- from:")),
+ new JsonObject(Map.of("line", 14, "code", " - log:
\\\"hi\\\"", "match", true)))));
+ writeStatusFile(TEST_PID, integration(route("orders", "timer:tick",
"Started", 25, log)));
+
+ assertThat(call(command("--source"))).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[2]).contains("app.yaml:14", "- log:
\"hi\"").doesNotContain("log1");
}
@Test
- void testJsonOutput() throws Exception {
- writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "30", "2", "0"));
+ void descriptionInTheIdColumn() throws Exception {
+ JsonObject log = processor("orders-log1", "log", 1, 5);
+ log.put("description", "Logs the order");
+ writeStatusFile(TEST_PID, integration(route("orders", "timer:tick",
"Started", 25, log)));
+
+ assertThat(call(command("--description"))).isZero();
+
+ assertThat(printer.getOutput()).contains("log1", "Logs the order");
+ }
+
+ @Test
+ void testFilterMeanExcludesSlowProcessors() throws Exception {
+ // the route with mean=5 is excluded when --filter-mean=10
+ writeStatusFile(TEST_PID, integration(route("myRoute", "log:output",
"Started", 5)));
+ assertThat(call(command("--filter-mean=10"))).isZero();
+
+ assertThat(printer.getOutput()).isEmpty();
+ }
+
+ private CamelProcessorStatus command(String... args) {
CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
- command.jsonOutput = true;
+ CommandLine.populateCommand(command, args);
+ return command;
+ }
+ // runs the command with the integration of the status file of the test
+ private static int call(CamelProcessorStatus command) throws Exception {
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
ProcessHandle ph = mockProcessHandle(TEST_PID);
ProcessHandle current = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(current);
mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
-
- int exit = command.doCall();
-
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.startsWith("["), "JSON output should be an
array");
- assertTrue(output.contains("myRoute"), "JSON should contain route
ID");
+ return command.doCall();
}
}
- private static JsonObject buildStatusWithProcessor(
- String routeId, String processorId, String from,
- String state, String mean, String failed, String inflight) {
- JsonObject stats = new JsonObject();
- stats.put("exchangesTotal", "10");
- stats.put("exchangesFailed", failed);
- stats.put("exchangesInflight", inflight);
- stats.put("meanProcessingTime", mean);
- stats.put("maxProcessingTime", mean);
- stats.put("minProcessingTime", mean);
+ private static JsonObject integration(JsonObject... routes) {
+ JsonObject context = new JsonObject();
+ context.put("name", "myApp");
+ context.put("phase", 5);
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routes", new JsonArray(Arrays.asList(routes)));
+ return root;
+ }
+ // as the route dev console has them, with the processors
+ private static JsonObject route(String routeId, String from, String state,
long mean, JsonObject... processors) {
JsonObject route = new JsonObject();
route.put("routeId", routeId);
- route.put("processorId", processorId);
+ route.put("nodePrefixId", routeId + "-");
route.put("from", from);
route.put("state", state);
- route.put("statistics", stats);
-
- JsonArray routes = new JsonArray();
- routes.add(route);
+ route.put("statistics", stats(mean));
+ route.put("processors", new JsonArray(Arrays.asList(processors)));
+ return route;
+ }
- JsonObject context = new JsonObject();
- context.put("name", "myApp");
- context.put("phase", 5);
+ private static JsonObject processor(String id, String name, int level,
long mean) {
+ JsonObject processor = new JsonObject();
+ processor.put("id", id);
+ processor.put("nodePrefixId", id.substring(0, id.indexOf('-') + 1));
+ processor.put("processor", name);
+ processor.put("level", level);
+ processor.put("state", "Started");
+ processor.put("statistics", stats(mean));
+ return processor;
+ }
- JsonObject root = new JsonObject();
- root.put("context", context);
- root.put("routes", routes);
- return root;
+ private static JsonObject stats(long mean) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", 10);
+ stats.put("exchangesFailed", 0);
+ stats.put("exchangesInflight", 0);
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", mean);
+ stats.put("minProcessingTime", mean);
+ return stats;
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
index 8ad3b6dabd4d..109011a05f9a 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
@@ -16,241 +16,347 @@
*/
package org.apache.camel.dsl.jbang.core.commands.process;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.List;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
+/**
+ * camel get route: the routes of the integrations, their state and
statistics, filtered, and the last error of the
+ * routes that have one.
+ */
@ExtendWith(MockitoExtension.class)
class CamelRouteStatusTest extends ProcessCommandTestSupport {
+ private static final long OTHER_PID = 23456L;
+
@Test
void testEmptyOutputWhenNoProcesses() throws Exception {
- CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
+ assertThat(call(command())).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+ assertThat(printer.getStdout()).isEmpty();
+ assertThat(printer.getStderr()).isEqualTo("No running Camel
integrations");
+ }
- int exit = command.doCall();
+ @Test
+ void routesWithTheirStateAndStatistics() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp",
+ route("route1", "timer:tick", "Started", stats(12, 1, 2, 25)),
+ route("route2", "timer:tock", "Suspended", stats(0, 0, 0,
-1))));
+
+ assertThat(call(command())).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("PID", "NAME", "ID", "FROM", "STATUS",
"TOTAL", "FAIL", "INFLIGHT", "MEAN")
+ .doesNotContain("GROUP", "REMOTE");
+ assertThat(lines[1]).contains("12345", "myApp", "route1",
"timer:tick", "Started", "12", "25");
+ assertThat(lines[2]).contains("route2", "timer:tock", "Suspended");
+ }
- assertEquals(0, exit);
- assertEquals("", printer.getStdout());
- assertEquals("No running Camel integrations", printer.getStderr());
- }
+ @Test
+ void jsonHasTheNumbersOfTheRoutes() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp",
+ route("route1", "timer:tick", "Started", stats(12, 1, 2, 25)),
+ route("route2", "timer:tock", "Started", stats(0, 0, 0, -1))));
+
+ assertThat(call(command("--json"))).isZero();
+
+ JsonArray routes = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ assertThat(routes).hasSize(2);
+ JsonObject route1 = routes.getMap(0);
+ assertThat(route1.getLong("pid")).isEqualTo(TEST_PID);
+ assertThat(route1.getString("routeId")).isEqualTo("route1");
+ assertThat(route1.getString("status")).isEqualTo("Started");
+ assertThat(route1.getLong("total")).isEqualTo(12);
+ assertThat(route1.getLong("failed")).isEqualTo(1);
+ assertThat(route1.getLong("inflight")).isEqualTo(2);
+ assertThat(route1.getLong("mean")).isEqualTo(25);
+ // no exchange yet: no mean
+ JsonObject route2 = routes.getMap(1);
+ assertThat(route2.get("mean")).isNull();
}
@Test
- void testDisplaysSingleRoute() throws Exception {
- JsonObject status = buildRouteStatus("route1", "Started");
- writeStatusFile(TEST_PID, status);
+ void jsonWithoutRoutesIsAnEmptyArray() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp"));
- CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThat(call(command("--json"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ assertThat(printer.getOutput()).isEqualTo("[]");
+ }
- int exit = command.doCall();
+ @Test
+ void errorShowsTheRoutesWithAnErrorAndTheirStackTrace() throws Exception {
+ JsonObject failed = route("failed", "kafka:in", "Stopped", stats(0, 0,
0, -1));
+ failed.put("lastError", lastError("start", "Cannot connect"));
+ writeStatusFile(TEST_PID, integration("myApp", route("ok",
"timer:tick", "Started", stats(1, 0, 0, 5)), failed));
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("route1"), "Should show route ID");
- assertTrue(output.contains("timer:tick"), "Should show from URI");
- assertTrue(output.contains("Started"), "Should show route state");
- }
+ assertThat(call(command("--error"))).isZero();
+
+ assertThat(printer.getOutput())
+ .contains("PHASE", "MESSAGE", "failed", "kafka:in", "Error",
"start", "Cannot connect", "STACK-TRACE",
+ "java.lang.IllegalStateException: Cannot connect", "at
org.example.Kafka.start(Kafka.java:42)")
+ .doesNotContain("timer:tick");
}
@Test
- void testDisplaysMultipleRoutes() throws Exception {
- JsonObject routeStats = buildRouteStats();
- JsonObject route1 = buildRoute("route1", "timer:tick1", routeStats);
- JsonObject route2 = buildRoute("route2", "timer:tick2", routeStats);
+ void errorAsJson() throws Exception {
+ JsonObject failed = route("failed", "kafka:in", "Stopped", stats(0, 0,
0, -1));
+ failed.put("lastError", lastError("start", "Cannot connect"));
+ writeStatusFile(TEST_PID, integration("myApp", route("ok",
"timer:tick", "Started", stats(1, 0, 0, 5)), failed));
+
+ assertThat(call(command("--error", "--json"))).isZero();
+
+ JsonArray routes = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ assertThat(routes).hasSize(1);
+ JsonObject route = routes.getMap(0);
+ assertThat(route.getString("routeId")).isEqualTo("failed");
+ assertThat(route.getString("status")).isEqualTo("Error");
+ JsonObject error = route.getMap("lastError");
+ assertThat(error.getString("phase")).isEqualTo("start");
+ assertThat(error.getString("message")).isEqualTo("Cannot connect");
+ assertThat(error.getString("ago")).isNotBlank();
+ List<String> stackTrace = error.getCollection("stackTrace");
+ assertThat(stackTrace).hasSize(2);
+ }
- JsonArray routes = new JsonArray();
- routes.add(route1);
- routes.add(route2);
+ @Test
+ void limitCountsTheRoutesWithAnError() throws Exception {
+ JsonObject failed = route("failed", "kafka:in", "Stopped", stats(0, 0,
0, -1));
+ failed.put("lastError", lastError("start", "Cannot connect"));
+ writeStatusFile(TEST_PID, integration("myApp", route("ok",
"timer:tick", "Started", stats(1, 0, 0, 5)), failed));
- JsonObject context = new JsonObject();
- context.put("name", "multiRouteApp");
- context.put("phase", 5);
+ assertThat(call(command("--error", "--limit=1"))).isZero();
- JsonObject root = new JsonObject();
- root.put("context", context);
- root.put("routes", routes);
- writeStatusFile(TEST_PID, root);
+ assertThat(printer.getOutput()).contains("failed", "Cannot connect");
+ }
- CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ @Test
+ void noteOfARouteWithAnError() throws Exception {
+ JsonObject failed = route("failed", "kafka:in", "Stopped", stats(0, 0,
0, -1));
+ failed.put("note", "Needs the broker");
+ failed.put("lastError", lastError("start", "Cannot connect"));
+ writeStatusFile(TEST_PID, integration("myApp", failed));
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ assertThat(call(command("--error", "--note"))).isZero();
- int exit = command.doCall();
+ assertThat(printer.getOutput()).contains("Needs the broker");
+ }
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("route1"));
- assertTrue(output.contains("route2"));
- }
+ @Test
+ void filterByRouteIdOrEndpoint() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp",
+ route("orders", "kafka:orders?groupId=a", "Started", stats(0,
0, 0, -1)),
+ route("payments", "jms:payments", "Started", stats(0, 0, 0,
-1)),
+ route("audit", "timer:audit", "Started", stats(0, 0, 0, -1))));
+
+ // the route id, or the start of the endpoint without its query
parameters
+ assertThat(call(command("--filter=orders",
"--filter=jms:pay"))).isZero();
+
+ assertThat(printer.getOutput()).contains("orders",
"payments").doesNotContain("audit");
}
@Test
- void testSuspendedRouteShownCorrectly() throws Exception {
- JsonObject status = buildRouteStatus("suspendedRoute", "Suspended");
- writeStatusFile(TEST_PID, status);
+ void filterMeanShowsTheSlowerRoutes() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp",
+ route("slow", "timer:slow", "Started", stats(5, 0, 0, 50)),
+ route("fast", "timer:fast", "Started", stats(5, 0, 0, 5)),
+ route("idle", "timer:idle", "Started", stats(0, 0, 0, -1))));
- CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThat(call(command("--filter-mean=10"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
assertThat(printer.getOutput()).contains("slow").doesNotContain("fast", "idle");
+ }
- int exit = command.doCall();
+ @Test
+ void groupFilterAndColumn() throws Exception {
+ JsonObject orders = route("orders", "timer:orders", "Started",
stats(0, 0, 0, -1));
+ orders.put("group", "sales");
+ JsonObject audit = route("audit", "timer:audit", "Started", stats(0,
0, 0, -1));
+ audit.put("group", "ops");
+ writeStatusFile(TEST_PID, integration("myApp", orders, audit));
+
+ assertThat(call(command("--group=sales", "--show-group"))).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("GROUP");
+ assertThat(lines).hasSize(2);
+ assertThat(lines[1]).contains("sales", "orders");
+ }
- assertEquals(0, exit);
- assertTrue(printer.getOutput().contains("Suspended"));
- }
+ @Test
+ void runningHidesTheRoutesThatAreNotStarted() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp",
+ route("started", "timer:a", "Started", stats(0, 0, 0, -1)),
+ route("stopped", "timer:b", "Stopped", stats(0, 0, 0, -1)),
+ route("suspended", "timer:c", "Suspended", stats(0, 0, 0,
-1))));
+
+ assertThat(call(command("--running"))).isZero();
+
+
assertThat(printer.getOutput()).contains("started").doesNotContain("stopped",
"suspended");
}
@Test
- void testJsonOutput() throws Exception {
- JsonObject status = buildRouteStatus("route1", "Started");
- writeStatusFile(TEST_PID, status);
+ void shortUriLeavesOutTheQueryParameters() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp", route("route1",
"timer:t?period=5", "Started", stats(0, 0, 0, -1))));
- CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.jsonOutput = true;
+ assertThat(call(command("--short-uri"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
assertThat(printer.getOutput()).contains("timer:t").doesNotContain("period");
+ }
- int exit = command.doCall();
+ @Test
+ void wideUriShowsTheWholeEndpoint() throws Exception {
+ writeStatusFile(TEST_PID, integration("myApp",
+ route("route1",
"timer:tick?period=5000&delay=1000&repeatCount=10", "Started", stats(0, 0, 0,
-1))));
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.startsWith("["), "JSON output should be array");
- assertTrue(output.contains("route1"));
- }
+ assertThat(call(command("--wide-uri"))).isZero();
+
+ // across lines when wider than the column
+ assertThat(printer.getOutput().replaceAll("\\s",
"")).contains("repeatCount=10");
}
@Test
- void testFilterByRouteId() throws Exception {
- JsonObject routeStats = buildRouteStats();
- JsonObject route1 = buildRoute("matchingRoute", "timer:tick1",
routeStats);
- JsonObject route2 = buildRoute("otherRoute", "timer:tick2",
routeStats);
+ void sourceShowsTheSourceLocationInsteadOfTheId() throws Exception {
+ JsonObject route = route("route1", "timer:tick", "Started", stats(0,
0, 0, -1));
+ route.put("source", "file:src/main/resources/camel/orders.yaml:12");
+ writeStatusFile(TEST_PID, integration("myApp", route));
- JsonArray routes = new JsonArray();
- routes.add(route1);
- routes.add(route2);
+ assertThat(call(command("--source"))).isZero();
- JsonObject context = new JsonObject();
- context.put("name", "myApp");
- context.put("phase", 5);
+
assertThat(printer.getOutput()).contains("orders.yaml:12").doesNotContain("route1");
+ }
- JsonObject root = new JsonObject();
- root.put("context", context);
- root.put("routes", routes);
- writeStatusFile(TEST_PID, root);
+ @Test
+ void descriptionAndNoteInTheIdColumn() throws Exception {
+ JsonObject route = route("route1", "timer:tick", "Started", stats(0,
0, 0, -1));
+ route.put("description", "Polls the orders");
+ route.put("note", "Owned by sales");
+ writeStatusFile(TEST_PID, integration("myApp", route));
- CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.filter = new String[] { "matchingRoute" };
+ assertThat(call(command("--description", "--note"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ assertThat(printer.getOutput()).contains("route1", "Polls the orders",
"Owned by sales");
+ }
- int exit = command.doCall();
+ @Test
+ void remoteColumnWhenTheIntegrationTellsWhichEndpointsAreRemote() throws
Exception {
+ JsonObject kafka = route("kafka", "kafka:in", "Started", stats(0, 0,
0, -1));
+ kafka.put("remote", true);
+ JsonObject timer = route("timer", "timer:tick", "Started", stats(0, 0,
0, -1));
+ timer.put("remote", false);
+ writeStatusFile(TEST_PID, integration("myApp", kafka, timer));
+
+ assertThat(call(command())).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("REMOTE");
+ assertThat(lines[1]).contains("kafka", " x ");
+ assertThat(lines[2]).contains("timer").doesNotContain(" x ");
+ }
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("matchingRoute"), "Filtered route
should appear");
- assertFalse(output.contains("otherRoute"), "Non-matching route
should be hidden by filter");
- }
+ @Test
+ void sortByNameOfTheIntegrations() throws Exception {
+ writeStatusFile(TEST_PID, integration("zeta", route("z1", "timer:z",
"Started", stats(0, 0, 0, -1))));
+ writeStatusFile(OTHER_PID, integration("alpha", route("a1", "timer:a",
"Started", stats(0, 0, 0, -1))));
+
+ assertThat(call(command("--sort=name"))).isZero();
+ String output = printer.getOutput();
+ assertThat(output.indexOf("alpha")).isLessThan(output.indexOf("zeta"));
+
+ printer = new StringPrinter();
+ assertThat(call(command("--sort=-name"))).isZero();
+ output = printer.getOutput();
+ assertThat(output.indexOf("zeta")).isLessThan(output.indexOf("alpha"));
}
@Test
void testLimitReducesRows() throws Exception {
- JsonObject routeStats = buildRouteStats();
- JsonArray routes = new JsonArray();
- for (int i = 1; i <= 5; i++) {
- routes.add(buildRoute("route" + i, "timer:tick" + i, routeStats));
+ JsonObject[] routes = new JsonObject[5];
+ for (int i = 0; i < routes.length; i++) {
+ routes[i] = route("route" + (i + 1), "timer:tick" + (i + 1),
"Started", stats(0, 0, 0, -1));
}
+ writeStatusFile(TEST_PID, integration("bigApp", routes));
- JsonObject context = new JsonObject();
- context.put("name", "bigApp");
- context.put("phase", 5);
+ assertThat(call(command("--limit=2"))).isZero();
- JsonObject root = new JsonObject();
- root.put("context", context);
- root.put("routes", routes);
- writeStatusFile(TEST_PID, root);
+ assertThat(printer.getOutput()).contains("route1",
"route2").doesNotContain("route3", "route5");
+ }
+ private CamelRouteStatus command(String... args) {
CamelRouteStatus command = new CamelRouteStatus(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.limit = 2;
+ CommandLine.populateCommand(command, args);
+ return command;
+ }
+ // runs the command with the integrations of the status files of the test
+ private static int call(CamelRouteStatus command) throws Exception {
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
+ List<ProcessHandle> handles = Stream.of(TEST_PID, OTHER_PID)
+ .filter(pid ->
Files.exists(CommandLineHelper.getCamelDir().resolve(pid + "-status.json")))
+
.map(ProcessCommandTestSupport::mockProcessHandle).toList();
ProcessHandle currentHandle = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
-
- int exit = command.doCall();
-
- assertEquals(0, exit);
- String output = printer.getOutput();
- // Only 2 routes should appear — limit is enforced during row
collection
- assertTrue(output.contains("route1"));
- assertTrue(output.contains("route2"));
- assertFalse(output.contains("route5"), "route5 should be excluded
by limit=2");
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
handles.stream());
+ return command.doCall();
}
}
- private static JsonObject buildRouteStats() {
- JsonObject stats = new JsonObject();
- stats.put("exchangesTotal", "0");
- stats.put("exchangesFailed", "0");
- stats.put("exchangesInflight", "0");
- stats.put("meanProcessingTime", "-1");
- stats.put("maxProcessingTime", "0");
- stats.put("minProcessingTime", "0");
- return stats;
+ private static JsonObject integration(String name, JsonObject... routes) {
+ JsonObject context = new JsonObject();
+ context.put("name", name);
+ context.put("phase", 5);
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routes", new JsonArray(Arrays.asList(routes)));
+ return root;
}
- private static JsonObject buildRoute(String routeId, String fromUri,
JsonObject stats) {
+ // as the route dev console has them
+ private static JsonObject route(String routeId, String from, String state,
JsonObject stats) {
JsonObject route = new JsonObject();
route.put("routeId", routeId);
- route.put("from", fromUri);
- route.put("state", "Started");
+ route.put("from", from);
+ route.put("state", state);
route.put("uptime", "1m");
route.put("statistics", stats);
return route;
}
+
+ private static JsonObject stats(long total, long failed, long inflight,
long mean) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", total);
+ stats.put("exchangesFailed", failed);
+ stats.put("exchangesInflight", inflight);
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", Math.max(mean, 0));
+ stats.put("minProcessingTime", Math.max(mean, 0));
+ return stats;
+ }
+
+ private static JsonObject lastError(String phase, String message) {
+ JsonObject error = new JsonObject();
+ error.put("phase", phase);
+ error.put("timestamp", System.currentTimeMillis() - 5000);
+ error.put("message", message);
+ error.put("stackTrace", new JsonArray(
+ List.of("java.lang.IllegalStateException: " + message,
+ "\tat org.example.Kafka.start(Kafka.java:42)")));
+ return error;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java
index d72fe9382e42..aad02cd58be3 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java
@@ -16,160 +16,246 @@
*/
package org.apache.camel.dsl.jbang.core.commands.process;
-import java.util.Collections;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mockStatic;
+/**
+ * camel get health: the health checks of the integrations, as the health dev
console has them in the status file.
+ */
@ExtendWith(MockitoExtension.class)
class ListHealthTest extends ProcessCommandTestSupport {
@Test
void testEmptyOutputWhenNoProcesses() throws Exception {
- ListHealth command = new ListHealth(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
-
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
ProcessHandle currentHandle = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(currentHandle);
mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
- int exit = command.doCall();
-
- assertEquals(0, exit);
- assertEquals("", printer.getStdout());
- assertEquals("No running Camel integrations", printer.getStderr());
+ assertThat(command().doCall()).isZero();
}
+
+ assertThat(printer.getStdout()).isEmpty();
+ assertThat(printer.getStderr()).isEqualTo("No running Camel
integrations");
}
@Test
- void testShowsUpHealthCheck() throws Exception {
- JsonObject root = buildHealthStatus(check("context", "UP", true));
- writeStatusFile(TEST_PID, root);
+ void checksWithTheirStateRateAndSince() throws Exception {
+ JsonObject context = check("context", "UP", true, true);
+ context.put("details", details(10, 9, 1, "check.kind", "ALL"));
+ writeStatusFile(TEST_PID, integration(context));
- ListHealth command = new ListHealth(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThat(call(command())).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("PID", "NAME", "AGE", "ID", "RL",
"STATUS", "RATE", "SINCE", "MESSAGE");
+ assertThat(lines[1]).contains("12345", "myApp", "camel/context", " RL
", "UP", "10/9/1", "5s/");
+ }
- int exit = command.doCall();
+ @Test
+ void jsonOfTheChecks() throws Exception {
+ JsonObject context = check("context", "UP", true, true);
+ context.put("details", details(10, 9, 1, "check.kind", "ALL"));
+ writeStatusFile(TEST_PID, integration(context));
+
+ assertThat(call(command("--json"))).isZero();
+
+ JsonArray checks = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ assertThat(checks).hasSize(1);
+ JsonObject check = checks.getMap(0);
+ assertThat(check.getLong("pid")).isEqualTo(TEST_PID);
+ assertThat(check.getString("id")).isEqualTo("camel/context");
+ assertThat(check.getString("status")).isEqualTo("UP");
+ assertThat(check.getBoolean("readiness")).isTrue();
+ assertThat(check.getBoolean("liveness")).isTrue();
+ assertThat(check.getLong("total")).isEqualTo(10);
+ assertThat(check.getLong("success")).isEqualTo(9);
+ assertThat(check.getLong("failure")).isEqualTo(1);
+ assertThat(check.getString("sinceLast")).isNotBlank();
+ }
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("UP"), "Should show UP state");
- assertTrue(output.contains("context"), "Should show check ID");
- }
+ @Test
+ void theRoutesAndConsumersThatAreUpOnlyAtFullLevel() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ check("context", "UP", true, true),
+ check("route:orders", "UP", true, false),
+ check("consumer:orders", "UP", true, false),
+ check("route:payments", "DOWN", true, false)));
+
+ assertThat(call(command("--json"))).isZero();
+ assertThat(ids()).containsExactly("camel/context",
"camel/route:payments");
+
+ printer = new StringPrinter();
+ assertThat(call(command("--level=full", "--json"))).isZero();
+ assertThat(ids()).containsExactly("camel/context",
"camel/route:orders", "camel/consumer:orders",
+ "camel/route:payments");
}
@Test
- void testShowsDownHealthCheck() throws Exception {
- JsonObject root = buildHealthStatus(check("context", "DOWN", true));
- writeStatusFile(TEST_PID, root);
+ void unknownLevelIsAUsageError() {
+ ListHealth command = command("--level=all");
- ListHealth command = new ListHealth(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThatThrownBy(command::doCall)
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE))
+ .hasMessage("--level must be full or default, was: all");
+ }
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ @Test
+ void downShowsTheChecksThatAreDown() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ check("context", "UP", true, true),
+ check("route:payments", "DOWN", true, false)));
- int exit = command.doCall();
+ assertThat(call(command("--down", "--json"))).isZero();
- assertEquals(0, exit);
- assertTrue(printer.getOutput().contains("DOWN"), "Should show DOWN
state");
- }
+ assertThat(ids()).containsExactly("camel/route:payments");
+ }
+
+ @Test
+ void liveAndReadyByTheKindOfTheCheck() throws Exception {
+ // readiness and liveness, but the kind of the check says which one it
is
+ JsonObject ready = check("ready-only", "UP", true, true);
+ ready.put("details", details(1, 1, 0, "check.kind", "READINESS"));
+ JsonObject live = check("live-only", "UP", true, true);
+ live.put("details", details(1, 1, 0, "check.kind", "LIVENESS"));
+ writeStatusFile(TEST_PID, integration(ready, live));
+
+ assertThat(call(command("--live", "--json"))).isZero();
+ assertThat(ids()).containsExactly("camel/live-only");
+
+ printer = new StringPrinter();
+ assertThat(call(command("--ready", "--json"))).isZero();
+ assertThat(ids()).containsExactly("camel/ready-only");
}
@Test
- void testJsonOutput() throws Exception {
- JsonObject root = buildHealthStatus(check("context", "UP", true));
- writeStatusFile(TEST_PID, root);
+ void traceShowsTheStackTraceAndTheDetailsOfTheCheck() throws Exception {
+ writeStatusFile(TEST_PID, integration(failedKafkaCheck()));
- ListHealth command = new ListHealth(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.jsonOutput = true;
+ assertThat(call(command("--trace"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ assertThat(printer.getOutput()).contains("STACK-TRACE", "CHECK-ID:
camel/consumer:kafka", "STATE: DOWN",
+ "METADATA:", "bootstrap.servers = localhost:9092", "MESSAGE:
Broker not available",
+ "org.apache.kafka.common.errors.TimeoutException: Broker not
available",
+ "at org.apache.kafka.Client.poll(Client.java:10)", "at
org.apache.kafka.Client.run(Client.java:20)");
+ }
+
+ @Test
+ void depthLimitsTheStackTrace() throws Exception {
+ writeStatusFile(TEST_PID, integration(failedKafkaCheck()));
- int exit = command.doCall();
+ assertThat(call(command("--trace", "--depth=2"))).isZero();
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.startsWith("["), "JSON output should be an
array");
- assertTrue(output.contains("context"));
- }
+ assertThat(printer.getOutput()).contains("at
org.apache.kafka.Client.poll(Client.java:10)")
+ .doesNotContain("at
org.apache.kafka.Client.run(Client.java:20)");
}
@Test
- void testFilterReadyOnly() throws Exception {
- JsonObject root = buildHealthStatus(
- check("context", "UP", true),
- check("consumer", "DOWN", false));
- writeStatusFile(TEST_PID, root);
+ void traceAsJson() throws Exception {
+ writeStatusFile(TEST_PID, integration(failedKafkaCheck()));
+
+ assertThat(call(command("--trace", "--json"))).isZero();
+
+ JsonArray checks = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ JsonObject check = checks.getMap(0);
+ assertThat(check.getString("message")).isEqualTo("Broker not
available");
+ List<String> stackTrace = check.getCollection("stackTrace");
+ assertThat(stackTrace).hasSize(3);
+ Map<String, Object> metadata = check.getMap("metadata");
+ assertThat(metadata).containsEntry("bootstrap.servers",
"localhost:9092");
+ }
+ private List<String> ids() {
+ JsonArray checks = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ return checks.stream().map(c -> ((JsonObject)
c).getString("id")).toList();
+ }
+
+ private ListHealth command(String... args) {
ListHealth command = new ListHealth(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.ready = true;
+ CommandLine.populateCommand(command, args);
+ return command;
+ }
+ // runs the command with the integration of the status file of the test
+ private static int call(ListHealth command) throws Exception {
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
ProcessHandle ph = mockProcessHandle(TEST_PID);
ProcessHandle currentHandle = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(currentHandle);
mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
-
- int exit = command.doCall();
-
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("context"), "Readiness check should be
shown");
- assertFalse(output.contains("consumer"), "Non-readiness check
should be hidden with --ready");
+ return command.doCall();
}
}
- private static JsonObject buildHealthStatus(JsonObject... checks) {
- JsonArray checksArr = new JsonArray();
- Collections.addAll(checksArr, checks);
-
- JsonObject hc = new JsonObject();
- hc.put("checks", checksArr);
-
- JsonObject context = new JsonObject();
- context.put("name", "myApp");
-
+ private static JsonObject integration(JsonObject... checks) {
+ JsonObject health = new JsonObject();
+ health.put("up", true);
+ health.put("checks", new JsonArray(Arrays.asList(checks)));
JsonObject root = new JsonObject();
- root.put("context", context);
- root.put("healthChecks", hc);
+ root.put("context", new JsonObject(Map.of("name", "myApp")));
+ root.put("healthChecks", health);
return root;
}
- private static JsonObject check(String id, String state, boolean
readiness) {
- JsonObject c = new JsonObject();
- c.put("id", id);
- c.put("group", "camel");
- c.put("state", state);
- c.put("readiness", readiness);
- c.put("liveness", true);
- return c;
+ // as the health dev console has them
+ private static JsonObject check(String id, String state, boolean
readiness, boolean liveness) {
+ JsonObject check = new JsonObject();
+ check.put("id", id);
+ check.put("group", "camel");
+ check.put("up", "UP".equals(state));
+ check.put("state", state);
+ check.put("enabled", true);
+ check.put("readiness", readiness);
+ check.put("liveness", liveness);
+ return check;
+ }
+
+ // the details are text, the times as the health checks print them
+ private static JsonObject details(int total, int success, int failure,
String... more) {
+ String fiveSecondsAgo =
ZonedDateTime.now().minusSeconds(5).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
+ Map<String, Object> details = new LinkedHashMap<>();
+ details.put("invocation.count", String.valueOf(total));
+ details.put("invocation.time", fiveSecondsAgo);
+ details.put("success.count", String.valueOf(success));
+ details.put("failure.count", String.valueOf(failure));
+ for (int i = 0; i < more.length; i += 2) {
+ details.put(more[i], more[i + 1]);
+ }
+ return new JsonObject(details);
+ }
+
+ private static JsonObject failedKafkaCheck() {
+ JsonObject check = check("consumer:kafka", "DOWN", true, false);
+ check.put("message", "Broker not available");
+ check.put("stackTrace", new JsonArray(
+ List.of(
+ "org.apache.kafka.common.errors.TimeoutException:
Broker not available",
+ "\tat org.apache.kafka.Client.poll(Client.java:10)",
+ "\tat org.apache.kafka.Client.run(Client.java:20)")));
+ check.put("details", details(3, 0, 3, "bootstrap.servers",
"localhost:9092"));
+ return check;
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java
index 40f6909a0a8a..c2d56339cdb8 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java
@@ -16,117 +16,241 @@
*/
package org.apache.camel.dsl.jbang.core.commands.process;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.Map;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
+/**
+ * camel get metric: the Micrometer metrics of the integrations (counters,
timers, gauges and distribution summaries),
+ * as the micrometer dev console has them in the status file.
+ */
@ExtendWith(MockitoExtension.class)
class ListMetricTest extends ProcessCommandTestSupport {
@Test
void testEmptyOutputWhenNoMetrics() throws Exception {
JsonObject root = new JsonObject();
- root.put("context", contextObj());
+ root.put("context", new JsonObject(Map.of("name", "myApp")));
writeStatusFile(TEST_PID, root);
- ListMetric command = new ListMetric(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
+ assertThat(call(command())).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ assertThat(printer.getOutput()).isEmpty();
+ }
- int exit = command.doCall();
+ @Test
+ void theMetricsOfEachType() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ counter("orders.placed", 42, tag("routeId", "orders")),
+ timer("orders.time", 10, 25, 80, 250, tag("routeId",
"orders"), tag("nodeId", "to1")),
+ gauge("orders.queue", 7),
+ distribution("orders.size", 4, 1.5, 3, 6)));
- assertEquals(0, exit);
- assertEquals("", printer.getOutput().trim());
- }
+ assertThat(call(command())).isZero();
+
+ String[] lines = printer.getOutput().split(System.lineSeparator());
+ assertThat(lines[0]).contains("PID", "NAME", "TYPE", "METRIC", "ID",
"VALUE", "MEAN", "MAX", "TOTAL")
+ .doesNotContain("TAGS");
+ assertThat(lines).hasSize(5);
+ // the metrics by name
+ assertThat(lines[1]).contains("12345", "myApp", "counter",
"orders.placed", "orders", "42");
+ assertThat(lines[2]).contains("gauge", "orders.queue", "7");
+ assertThat(lines[3]).contains("distribution", "orders.size", "4",
"1.5", "3", "6");
+ assertThat(lines[4]).contains("timer", "orders.time", "orders/to1",
"10", "25", "80", "250");
}
@Test
- void testShowsCounterMetric() throws Exception {
- JsonObject root = new JsonObject();
- root.put("context", contextObj());
- root.put("micrometer", micrometerObj(counterEntry(42.0)));
- writeStatusFile(TEST_PID, root);
+ void jsonOfTheMetrics() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ timer("orders.time", 10, 25, 80, 250, tag("routeId",
"orders"), tag("nodeId", "to1")),
+ distribution("orders.size", 4, 1.5, 3, 6)));
- ListMetric command = new ListMetric(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.all = true;
+ assertThat(call(command("--json"))).isZero();
- try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph = mockProcessHandle(TEST_PID);
- ProcessHandle currentHandle = mockCurrentHandle();
- mocked.when(ProcessHandle::current).thenReturn(currentHandle);
- mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ JsonArray metrics = Jsoner.deserialize(printer.getOutput(),
(JsonArray) null);
+ assertThat(metrics).hasSize(2);
+ JsonObject size = metrics.getMap(0);
+ assertThat(size.getLong("pid")).isEqualTo(TEST_PID);
+ assertThat(size.getString("type")).isEqualTo("distribution");
+ assertThat(size.getString("metric")).isEqualTo("orders.size");
+ assertThat(size.getDouble("value")).isEqualTo(4);
+ assertThat(size.getDouble("mean")).isEqualTo(1.5);
+ assertThat(size.getDouble("total")).isEqualTo(6);
+ JsonObject time = metrics.getMap(1);
+ assertThat(time.getString("type")).isEqualTo("timer");
+ assertThat(time.getString("id")).isEqualTo("orders/to1");
+ assertThat(time.getDouble("max")).isEqualTo(80);
+ assertThat(time.getString("tags")).isEqualTo("routeId=orders
nodeId=to1");
+ }
+
+ @Test
+ void unusedMetricsOnlyWithAll() throws Exception {
+ writeStatusFile(TEST_PID, integration(counter("orders.placed", 42),
counter("orders.lost", 0)));
- int exit = command.doCall();
+ assertThat(call(command())).isZero();
+
assertThat(printer.getOutput()).contains("orders.placed").doesNotContain("orders.lost");
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.contains("myCounter"), "Should show metric
name");
- }
+ printer = new StringPrinter();
+ assertThat(call(command("--all"))).isZero();
+ assertThat(printer.getOutput()).contains("orders.placed",
"orders.lost");
}
@Test
- void testJsonOutput() throws Exception {
- JsonObject root = new JsonObject();
- root.put("context", contextObj());
- root.put("micrometer", micrometerObj(counterEntry(10.0)));
- writeStatusFile(TEST_PID, root);
+ void filterByTypeAsInTheHelp() throws Exception {
+ writeStatusFile(TEST_PID, integration(counter("orders.placed", 42),
timer("orders.time", 10, 25, 80, 250)));
+
+ assertThat(call(command("--filter=timer*"))).isZero();
+
+
assertThat(printer.getOutput()).contains("orders.time").doesNotContain("orders.placed");
+ }
+
+ @Test
+ void filterByNameOrTag() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ counter("orders.placed", 42, tag("routeId", "orders")),
+ counter("payments.made", 5, tag("routeId", "payments")),
+ counter("audit.logged", 3, tag("routeId", "audit"))));
+ assertThat(call(command("--filter=placed"))).isZero();
+
assertThat(printer.getOutput()).contains("orders.placed").doesNotContain("payments.made",
"audit.logged");
+
+ printer = new StringPrinter();
+ assertThat(call(command("--filter=routeId=payments"))).isZero();
+
assertThat(printer.getOutput()).contains("payments.made").doesNotContain("orders.placed",
"audit.logged");
+ }
+
+ @Test
+ void customLeavesOutTheMetricsOfCamel() throws Exception {
+ writeStatusFile(TEST_PID, integration(
+ // the micrometer naming (the default) and the classic naming
of the metrics of camel
+ counter("camel.exchanges.total", 42),
+ counter("CamelExchangesTotal", 42),
+ counter("orders.placed", 5)));
+
+ assertThat(call(command("--custom"))).isZero();
+
+ // not even the start of the names, as the column is narrow
+
assertThat(printer.getOutput()).contains("orders.placed").doesNotContain("camel.",
"CamelExch");
+ }
+
+ @Test
+ void tagsColumn() throws Exception {
+ writeStatusFile(TEST_PID, integration(counter("orders.placed", 42,
tag("routeId", "orders"), tag("nodeId", "to1"))));
+
+ assertThat(call(command("--tags"))).isZero();
+
+
assertThat(printer.getOutput().split(System.lineSeparator())[0]).contains("TAGS");
+ // across lines when wider than the column
+ assertThat(printer.getOutput().replaceAll("\\s",
"")).contains("routeId=ordersnodeId=to1");
+ }
+
+ @Test
+ void theInfoGaugeOfTheApplicationIsNotAMetric() throws Exception {
+ writeStatusFile(TEST_PID, integration(gauge("app.info", 1),
gauge("orders.queue", 7)));
+
+ assertThat(call(command())).isZero();
+
+
assertThat(printer.getOutput()).contains("orders.queue").doesNotContain("app.info");
+ }
+
+ private ListMetric command(String... args) {
ListMetric command = new ListMetric(new
CamelJBangMain().withPrinter(printer));
- command.sort = "pid";
- command.all = true;
- command.jsonOutput = true;
+ CommandLine.populateCommand(command, args);
+ return command;
+ }
+ // runs the command with the integration of the status file of the test
+ private static int call(ListMetric command) throws Exception {
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
ProcessHandle ph = mockProcessHandle(TEST_PID);
ProcessHandle currentHandle = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(currentHandle);
mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ return command.doCall();
+ }
+ }
- int exit = command.doCall();
-
- assertEquals(0, exit);
- String output = printer.getOutput();
- assertTrue(output.startsWith("["), "JSON output should be array");
- assertTrue(output.contains("myCounter"));
+ private static JsonObject integration(JsonObject... metrics) {
+ JsonArray counters = new JsonArray();
+ JsonArray timers = new JsonArray();
+ JsonArray gauges = new JsonArray();
+ JsonArray distribution = new JsonArray();
+ for (JsonObject metric : metrics) {
+ String type = (String) metric.remove("type");
+ switch (type) {
+ case "counter" -> counters.add(metric);
+ case "timer" -> timers.add(metric);
+ case "gauge" -> gauges.add(metric);
+ default -> distribution.add(metric);
+ }
}
+ JsonObject micrometer = new JsonObject();
+ micrometer.put("counters", counters);
+ micrometer.put("timers", timers);
+ micrometer.put("gauges", gauges);
+ micrometer.put("distribution", distribution);
+ JsonObject root = new JsonObject();
+ root.put("context", new JsonObject(Map.of("name", "myApp")));
+ root.put("micrometer", micrometer);
+ return root;
}
- private static JsonObject contextObj() {
- JsonObject ctx = new JsonObject();
- ctx.put("name", "myApp");
- return ctx;
+ // as the micrometer dev console has them
+ private static JsonObject counter(String name, long count, JsonObject...
tags) {
+ JsonObject metric = metric("counter", name, tags);
+ metric.put("count", count);
+ return metric;
}
- private static JsonObject micrometerObj(JsonObject... counters) {
- JsonArray arr = new JsonArray();
- Collections.addAll(arr, counters);
- JsonObject mo = new JsonObject();
- mo.put("counters", arr);
- return mo;
+ private static JsonObject timer(String name, long count, long mean, long
max, long total, JsonObject... tags) {
+ JsonObject metric = metric("timer", name, tags);
+ metric.put("count", count);
+ metric.put("mean", mean);
+ metric.put("max", max);
+ metric.put("total", total);
+ return metric;
+ }
+
+ private static JsonObject gauge(String name, double value) {
+ JsonObject metric = metric("gauge", name);
+ metric.put("value", value);
+ return metric;
+ }
+
+ private static JsonObject distribution(String name, long count, double
mean, double max, double totalAmount) {
+ JsonObject metric = metric("distribution", name);
+ metric.put("count", count);
+ metric.put("mean", mean);
+ metric.put("max", max);
+ metric.put("totalAmount", totalAmount);
+ return metric;
+ }
+
+ private static JsonObject metric(String type, String name, JsonObject...
tags) {
+ JsonObject metric = new JsonObject();
+ metric.put("type", type);
+ metric.put("name", name);
+ if (tags.length > 0) {
+ metric.put("tags", new JsonArray(Arrays.asList(tags)));
+ }
+ return metric;
}
- private static JsonObject counterEntry(double count) {
- JsonObject c = new JsonObject();
- c.put("name", "myCounter");
- c.put("description", "");
- c.put("count", count);
- return c;
+ private static JsonObject tag(String key, String value) {
+ return new JsonObject(Map.of("key", key, "value", value));
}
}