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 9390d3a011d CAMEL-18406: camel-jbang - Status command 9390d3a011d is described below commit 9390d3a011d0663f2587ac60b77dd3becf77c92c Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Tue Aug 23 11:13:00 2022 +0200 CAMEL-18406: camel-jbang - Status command --- .../apache/camel/impl/console/RouteDevConsole.java | 11 ++ .../modules/ROOT/pages/camel-jbang.adoc | 64 ++++++-- .../dsl/jbang/core/commands/CamelJBangMain.java | 6 +- .../apache/camel/dsl/jbang/core/commands/Run.java | 64 ++++---- .../jbang/core/commands/process/CamelStatus.java | 106 +------------ .../{CamelStatus.java => CamelStatusContext.java} | 25 ++- .../core/commands/process/CamelStatusRoute.java | 171 +++++++++++++++++++++ .../core/commands/process/ProcessBaseCommand.java | 7 + 8 files changed, 307 insertions(+), 147 deletions(-) diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java index bfe154d7100..f8f84cdbae6 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java @@ -17,6 +17,7 @@ package org.apache.camel.impl.console; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -68,6 +69,11 @@ public class RouteDevConsole extends AbstractDevConsole { sb.append(String.format("\n Mean Time: %s", TimeUtils.printDuration(mrb.getMeanProcessingTime(), true))); sb.append(String.format("\n Max Time: %s", TimeUtils.printDuration(mrb.getMaxProcessingTime(), true))); sb.append(String.format("\n Min Time: %s", TimeUtils.printDuration(mrb.getMinProcessingTime(), true))); + Date last = mrb.getLastExchangeCreatedTimestamp(); + if (last != null) { + String ago = TimeUtils.printSince(last.getTime()); + sb.append(String.format("\n Since Last: %s", ago)); + } sb.append("\n"); return null; }; @@ -98,6 +104,11 @@ public class RouteDevConsole extends AbstractDevConsole { stats.put("meanProcessingTime", mrb.getMeanProcessingTime()); stats.put("maxProcessingTime", mrb.getMaxProcessingTime()); stats.put("minProcessingTime", mrb.getMinProcessingTime()); + Date last = mrb.getLastExchangeCreatedTimestamp(); + if (last != null) { + String ago = TimeUtils.printSince(last.getTime()); + stats.put("sinceLastExchange", ago); + } jo.put("statistics", stats); return null; }; diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc index 3a6c67f5e00..3097090709b 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc @@ -128,7 +128,7 @@ using the `--dev` options as shown: camel run foo.yaml --dev ---- -Then while the Camel application is running, you can update the YAML route and update when saving. +Then while the Camel integration is running, you can update the YAML route and update when saving. This works for all DLS even java, so you can do: @@ -138,7 +138,7 @@ camel run hello.java --dev ---- NOTE: The live reload is meant for development purposes, and if you encounter problems with reloading -such as JVM class loading issues, then you may need to restart the application. +such as JVM class loading issues, then you may need to restart the integration. === Developer Console @@ -152,7 +152,7 @@ camel run hello.java --console The console is then accessible from a web browser at: http://localhost:8080/q/dev (by default). The link is also shown in the log when Camel is starting up. -The console can give you insights into your running Camel application, such as reporting the top +The console can give you insights into your running Camel integration, such as reporting the top routes that takes the longest time to process messages. You can then drill down to pin-point, exactly which individual EIPs in these routes are the slowest. @@ -519,7 +519,7 @@ For example. you can copy this to your clipboard and then run it afterwards: camel run clipboard.xml ---- -=== Controlling local Camel applications +=== Controlling local Camel integrations To list the currently running Camel JBang integrations you use the `ps` command: @@ -530,7 +530,7 @@ camel ps 44805 camel run dude.java (age: 58s) ---- -This lists the PID, the name and age of the application. +This lists the PID, the name and age of the integration. You can use the `stop` command to stop any of these running Camel integrations. For example to stop dude, you can do @@ -562,9 +562,55 @@ Stopping running Camel integration (pid: 42171) Stopping running Camel integration (pid: 44805) ---- +=== Status of local Camel integrations + +The status command in Camel JBang is used for displaying Camel specific information +for one or all of the running Camel integrations. + +To display the state of the running Camel integrations: + +[source,bash] +---- +camel status integration ++------+----------+---------+-------+---------+----------+------------+------------+ +| PID | Name | State | Age | Total # | Failed # | Inflight # | Since Last | ++------+----------+---------+-------+---------+----------+------------+------------+ +| 2617 | bar.yaml | started | 1h27m | 5231 | 0 | 0 | 0s | +| 2630 | foo.java | started | 1h27m | 1050 | 0 | 0 | 4s | ++------+----------+---------+-------+---------+----------+------------+------------+ +---- + +TIP: You can use `camel status int` or `camel status context` as a short-hand +command for displaying the status as above. + +This displays overall information for every Camel integration, where you can see +the total number of messages processed. The column _Since Last_ +shows how long time ago the integration was active processing +incoming messages. + +You can also see the status of every routes, from all the local Camel integrations: + +[source,bash] +---- +camel status route ++------+------------+-----------------------+-------------------------------------+---------+--------+---------+--------+----------+--------+--------+--------+----------+ +| PID | Name | Route ID | From | State | Uptime | Total | Failed | Inflight | Mean | Max | Min | Since | +| | | | | | | # | # | # | (ms) | (ms) | (ms) | Last | ++------+------------+-----------------------+-------------------------------------+---------+--------+---------+--------+----------+--------+--------+--------+----------+ +| 2617 | bar.yaml | route1 | timer://yaml2?period=1000 | started | 1h31m | 5483 | 0 | 0 | 0 | 59 | 0 | 0s | +| 7313 | chuck.yaml | chuck | kamelet://chuck-norris-source | started | 12s | 2 | 0 | 0 | 0 | 1 | 0 | 1s | +| | | chuck-norris-source-1 | timer://chuck?period=10000 | started | 12s | 2 | 0 | 0 | 105 | 133 | 78 | 1s | +| | | log-sink-2 | kamelet://source?routeId=log-sink-2 | started | 12s | 2 | 0 | 0 | 0 | 0 | 0 | 1s | +| 2630 | foo.java | java | timer://java?period=5000 | started | 1h31m | 1101 | 0 | 0 | 0 | 10 | 0 | 1s | ++------+------------+-----------------------+-------------------------------------+---------+--------+---------+--------+----------+--------+--------+--------+----------+ +---- + +TIP: Use `camel status --help` to display all the available commands as additional will be added in upcoming releases. + + === Using Jolokia and Hawtio -The https://hawt.io/[Hawtio] web console allows inspecting running Camel applications, such +The https://hawt.io/[Hawtio] web console allows inspecting running Camel integrations, such as all the JMX management information, and not but least to visualize the Camel routes with live performance metrics. Hawtio is a handy tool for many years, and we have made it easy to use Hawtio with Camel JBang. @@ -616,7 +662,7 @@ to multiple different Camel integrations and from this list select which to load Click the green _lightning_ icon to connect to running Camel integration (of choice). -You can uninstall the Jolokia JVM Agent in a running Camel application when no longer needed: +You can uninstall the Jolokia JVM Agent in a running Camel integration when no longer needed: [source,bash] ---- @@ -750,7 +796,7 @@ IMPORTANT: The Java source files cannot use package names. This may change in th === Dependency Injection in Java classes -When running Camel applications with camel-jbang, then the runtime is `camel-main` based. This means +When running Camel integrations with camel-jbang, then the runtime is `camel-main` based. This means there is no Spring Boot, or Quarkus available. However, we have added support for using annotation based dependency injection in Java classes. @@ -821,7 +867,7 @@ See the https://github.com/apache/camel-kamelets-examples/tree/main/jbang/open-a == Creating Projects -You can _export_ your Camel JBang application to a traditional Java based project such as Spring Boot or Quarkus. +You can _export_ your Camel JBang integration to a traditional Java based project such as Spring Boot or Quarkus. You may want to do this after you have built a prototype using Camel JBang, and are in need of a traditional Java based project with more need for Java coding, or wanting to use the powerful diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java index 3e77d1153d1..7528e772e7b 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java @@ -21,6 +21,8 @@ import java.util.concurrent.Callable; import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; import org.apache.camel.dsl.jbang.core.commands.process.CamelStatus; +import org.apache.camel.dsl.jbang.core.commands.process.CamelStatusContext; +import org.apache.camel.dsl.jbang.core.commands.process.CamelStatusRoute; import org.apache.camel.dsl.jbang.core.commands.process.Hawtio; import org.apache.camel.dsl.jbang.core.commands.process.Jolokia; import org.apache.camel.dsl.jbang.core.commands.process.ListProcess; @@ -39,7 +41,9 @@ public class CamelJBangMain implements Callable<Integer> { .addSubcommand("run", new CommandLine(new Run(main))) .addSubcommand("ps", new CommandLine(new ListProcess(main))) .addSubcommand("stop", new CommandLine(new StopProcess(main))) - .addSubcommand("status", new CommandLine(new CamelStatus(main))) + .addSubcommand("status", new CommandLine(new CamelStatus(main)) + .addSubcommand("context", new CommandLine(new CamelStatusContext(main))) + .addSubcommand("route", new CommandLine(new CamelStatusRoute(main)))) .addSubcommand("generate", new CommandLine(new CodeGenerator(main)) .addSubcommand("rest", new CommandLine(new CodeRestGenerator(main)))) .addSubcommand("jolokia", new CommandLine(new Jolokia(main))) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java index 9e9f11097e9..38298304958 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java @@ -274,7 +274,6 @@ class Run extends CamelCommand { return prop; } - // CHECKSTYLE:OFF private int run() throws Exception { File work = new File(WORK_DIR); removeDir(work); @@ -408,32 +407,7 @@ class Run extends CamelCommand { writeSetting(main, profileProperties, "camel.jbang.jfr-profile", jfrProfile != null ? jfrProfile : null); if (fileLock) { - lockFile = createLockFile(getPid()); - if (lockFile != null) { - statusFile = createLockFile(lockFile.getName() + "-status.json"); - } - // to trigger shutdown on file lock deletion - executor = Executors.newSingleThreadScheduledExecutor(); - executor.scheduleWithFixedDelay(() -> { - // if the lock file is deleted then stop - if (!lockFile.exists()) { - context.stop(); - return; - } - // update status file with details from the context console - try { - DevConsole dc = main.getCamelContext().adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("context"); - if (dc != null) { - JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON); - if (json != null) { - IOHelper.writeText(json.toJson(), statusFile); - } - } - } catch (Throwable e) { - // ignore - } - }, 1000, 1000, TimeUnit.MILLISECONDS); + initLockFile(main); } StringJoiner js = new StringJoiner(","); @@ -593,7 +567,41 @@ class Run extends CamelCommand { return main.getExitCode(); } - // CHECKSTYLE:ON + + private void initLockFile(KameletMain main) { + lockFile = createLockFile(getPid()); + if (lockFile != null) { + statusFile = createLockFile(lockFile.getName() + "-status.json"); + } + // to trigger shutdown on file lock deletion + executor = Executors.newSingleThreadScheduledExecutor(); + executor.scheduleWithFixedDelay(() -> { + // if the lock file is deleted then stop + if (!lockFile.exists()) { + context.stop(); + return; + } + // update status file with details from the context console + try { + DevConsole dc = main.getCamelContext().adapt(ExtendedCamelContext.class) + .getDevConsoleResolver().resolveDevConsole("context"); + DevConsole dc2 = main.getCamelContext().adapt(ExtendedCamelContext.class) + .getDevConsoleResolver().resolveDevConsole("route"); + if (dc != null && dc2 != null) { + JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON); + JsonObject json2 = (JsonObject) dc2.call(DevConsole.MediaType.JSON); + if (json != null && json2 != null) { + JsonObject root = new JsonObject(); + root.put("context", json); + root.put("routes", json2.get("routes")); + IOHelper.writeText(root.toJson(), statusFile); + } + } + } catch (Throwable e) { + // ignore + } + }, 2000, 2000, TimeUnit.MILLISECONDS); + } private String evalGistSource(KameletMain main, String file) throws Exception { StringJoiner routes = new StringJoiner(","); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java index b4f8d98f053..b3064d1fd02 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java @@ -16,31 +16,13 @@ */ package org.apache.camel.dsl.jbang.core.commands.process; -import java.io.File; -import java.io.FileInputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import com.github.freva.asciitable.AsciiTable; -import com.github.freva.asciitable.Column; +import org.apache.camel.dsl.jbang.core.commands.CamelCommand; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; -import org.apache.camel.util.IOHelper; -import org.apache.camel.util.ObjectHelper; -import org.apache.camel.util.TimeUtils; -import org.apache.camel.util.json.JsonObject; -import org.apache.camel.util.json.Jsoner; import picocli.CommandLine; -import picocli.CommandLine.Command; - -@Command(name = "status", description = "List status of the running Camel integrations") -public class CamelStatus extends ProcessBaseCommand { - @CommandLine.Option(names = { "--sort" }, - description = "Sort by pid, name or age", defaultValue = "pid") - String sort; +@CommandLine.Command(name = "status", + description = "List status of the running Camel integrations (use --help to see sub commands)") +public class CamelStatus extends CamelCommand { public CamelStatus(CamelJBangMain main) { super(main); @@ -48,85 +30,7 @@ public class CamelStatus extends ProcessBaseCommand { @Override public Integer call() throws Exception { - List<Row> rows = new ArrayList<>(); - - ProcessHandle.allProcesses() - .sorted((o1, o2) -> { - switch (sort) { - case "pid": - return Long.compare(o1.pid(), o2.pid()); - case "name": - return extractName(o1).compareTo(extractName(o2)); - case "age": - // we want newest in top - return Long.compare(extractSince(o1), extractSince(o2)) * -1; - default: - return 0; - } - }) - .forEach(ph -> { - Row row = new Row(); - row.name = extractName(ph); - if (ObjectHelper.isNotEmpty(row.name)) { - row.pid = "" + ph.pid(); - row.ago = TimeUtils.printSince(extractSince(ph)); - JsonObject status = loadStatus(ph.pid()); - if (status != null) { - row.state = status.getString("state").toLowerCase(Locale.ROOT); - Map<String, ?> stats = status.getMap("statistics"); - if (stats != null) { - row.total = stats.get("exchangesTotal").toString(); - row.inflight = stats.get("exchangesInflight").toString(); - row.failed = stats.get("exchangesFailed").toString(); - Object last = stats.get("sinceLastExchange"); - if (last != null) { - row.sinceLast = last.toString(); - } - } - } - rows.add(row); - } - }); - - if (!rows.isEmpty()) { - System.out.println(AsciiTable.getTable(AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS, rows, Arrays.asList( - new Column().header("PID").with(r -> r.pid), - new Column().header("Name").maxColumnWidth(30).with(r -> r.name), - new Column().header("State").with(r -> r.state), - new Column().header("Age").with(r -> r.ago), - new Column().header("Since Last").with(r -> r.sinceLast), - new Column().header("Total #").with(r -> r.total), - new Column().header("Failed #").with(r -> r.failed), - new Column().header("Inflight #").with(r -> r.inflight)))); - } - + new CommandLine(this).execute("--help"); return 0; } - - private JsonObject loadStatus(long pid) { - try { - File f = getStatusFile("" + pid); - if (f != null) { - FileInputStream fis = new FileInputStream(f); - String text = IOHelper.loadText(fis); - IOHelper.close(fis); - return (JsonObject) Jsoner.deserialize(text); - } - } catch (Throwable e) { - // ignore - } - return null; - } - - private static class Row { - String pid; - String name; - String ago; - String state; - String total; - String failed; - String inflight; - String sinceLast; - } - } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatusContext.java similarity index 84% copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java copy to dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatusContext.java index b4f8d98f053..ac8f56779b1 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatus.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatusContext.java @@ -26,6 +26,7 @@ import java.util.Map; import com.github.freva.asciitable.AsciiTable; import com.github.freva.asciitable.Column; +import com.github.freva.asciitable.HorizontalAlign; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.util.IOHelper; import org.apache.camel.util.ObjectHelper; @@ -35,14 +36,18 @@ import org.apache.camel.util.json.Jsoner; import picocli.CommandLine; import picocli.CommandLine.Command; -@Command(name = "status", description = "List status of the running Camel integrations") -public class CamelStatus extends ProcessBaseCommand { +@Command(name = "integration", aliases = {"int", "integration", "context"}, + description = "List status of the running Camel integrations") +public class CamelStatusContext extends ProcessBaseCommand { - @CommandLine.Option(names = { "--sort" }, - description = "Sort by pid, name or age", defaultValue = "pid") + @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "0..1") + String name = "*"; + + @CommandLine.Option(names = {"--sort"}, + description = "Sort by pid, name or age", defaultValue = "pid") String sort; - public CamelStatus(CamelJBangMain main) { + public CamelStatusContext(CamelJBangMain main) { super(main); } @@ -50,7 +55,9 @@ public class CamelStatus extends ProcessBaseCommand { public Integer call() throws Exception { List<Row> rows = new ArrayList<>(); + List<Long> pids = findPids(name); ProcessHandle.allProcesses() + .filter(ph -> pids.contains(ph.pid())) .sorted((o1, o2) -> { switch (sort) { case "pid": @@ -72,6 +79,7 @@ public class CamelStatus extends ProcessBaseCommand { row.ago = TimeUtils.printSince(extractSince(ph)); JsonObject status = loadStatus(ph.pid()); if (status != null) { + status = (JsonObject) status.get("context"); row.state = status.getString("state").toLowerCase(Locale.ROOT); Map<String, ?> stats = status.getMap("statistics"); if (stats != null) { @@ -91,13 +99,14 @@ public class CamelStatus extends ProcessBaseCommand { if (!rows.isEmpty()) { System.out.println(AsciiTable.getTable(AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS, rows, Arrays.asList( new Column().header("PID").with(r -> r.pid), - new Column().header("Name").maxColumnWidth(30).with(r -> r.name), + new Column().header("Name").dataAlign(HorizontalAlign.LEFT).maxColumnWidth(30) + .with(r -> maxWidth(r.name, 28)), new Column().header("State").with(r -> r.state), new Column().header("Age").with(r -> r.ago), - new Column().header("Since Last").with(r -> r.sinceLast), new Column().header("Total #").with(r -> r.total), new Column().header("Failed #").with(r -> r.failed), - new Column().header("Inflight #").with(r -> r.inflight)))); + new Column().header("Inflight #").with(r -> r.inflight), + new Column().header("Since Last").with(r -> r.sinceLast)))); } return 0; diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatusRoute.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatusRoute.java new file mode 100644 index 00000000000..6998a7d1f7b --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelStatusRoute.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands.process; + +import java.io.File; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import com.github.freva.asciitable.AsciiTable; +import com.github.freva.asciitable.Column; +import com.github.freva.asciitable.HorizontalAlign; +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +@Command(name = "route", description = "List status of the running Camel routes") +public class CamelStatusRoute extends ProcessBaseCommand { + + @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "0..1") + String name = "*"; + + @CommandLine.Option(names = { "--sort" }, + description = "Sort by pid, name or age", defaultValue = "pid") + String sort; + + public CamelStatusRoute(CamelJBangMain main) { + super(main); + } + + @Override + public Integer call() throws Exception { + List<Row> rows = new ArrayList<>(); + + List<Long> pids = findPids(name); + ProcessHandle.allProcesses() + .filter(ph -> pids.contains(ph.pid())) + .sorted((o1, o2) -> { + switch (sort) { + case "pid": + return Long.compare(o1.pid(), o2.pid()); + case "name": + return extractName(o1).compareTo(extractName(o2)); + case "age": + // we want newest in top + return Long.compare(extractSince(o1), extractSince(o2)) * -1; + default: + return 0; + } + }) + .forEach(ph -> { + String name = extractName(ph); + if (ObjectHelper.isNotEmpty(name)) { + JsonObject status = loadStatus(ph.pid()); + if (status != null) { + JsonArray array = (JsonArray) status.get("routes"); + for (int i = 0; i < array.size(); i++) { + JsonObject o = (JsonObject) array.get(i); + Row row = new Row(); + rows.add(row); + if (i == 0) { + // we only want pid/name in 1st row per camel integration (to nest sub routes) + row.pid = "" + ph.pid(); + row.name = name; + } + row.routeId = o.getString("routeId"); + row.from = o.getString("from"); + row.source = o.getString("source"); + row.state = o.getString("state").toLowerCase(Locale.ROOT); + row.uptime = o.getString("uptime"); + Map<String, ?> stats = o.getMap("statistics"); + if (stats != null) { + row.total = stats.get("exchangesTotal").toString(); + row.inflight = stats.get("exchangesInflight").toString(); + row.failed = stats.get("exchangesFailed").toString(); + row.mean = stats.get("meanProcessingTime").toString(); + if ("-1".equals(row.mean)) { + row.mean = null; + } + row.max = stats.get("maxProcessingTime").toString(); + row.min = stats.get("minProcessingTime").toString(); + Object last = stats.get("sinceLastExchange"); + if (last != null) { + row.sinceLast = last.toString(); + } + } + } + } + } + }); + + if (!rows.isEmpty()) { + System.out.println(AsciiTable.getTable(AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS, rows, Arrays.asList( + new Column().header("PID").with(r -> r.pid), + new Column().header("Name").dataAlign(HorizontalAlign.LEFT).maxColumnWidth(30) + .with(r -> maxWidth(r.name, 28)), + new Column().header("Route ID").dataAlign(HorizontalAlign.LEFT).maxColumnWidth(30) + .with(r -> maxWidth(r.routeId, 28)), + new Column().header("From").dataAlign(HorizontalAlign.LEFT).maxColumnWidth(40) + .with(r -> maxWidth(r.from, 38)), + new Column().header("State").with(r -> r.state), + new Column().header("Uptime").with(r -> r.uptime), + new Column().header("Total\n#").headerAlign(HorizontalAlign.CENTER).with(r -> r.total), + new Column().header("Failed\n#").headerAlign(HorizontalAlign.CENTER).maxColumnWidth(8).with(r -> r.failed), + new Column().header("Inflight\n#").headerAlign(HorizontalAlign.CENTER).maxColumnWidth(10) + .with(r -> r.inflight), + new Column().header("Mean\n(ms)").headerAlign(HorizontalAlign.CENTER).maxColumnWidth(8).with(r -> r.mean), + new Column().header("Max\n(ms)").headerAlign(HorizontalAlign.CENTER).maxColumnWidth(8).with(r -> r.max), + new Column().header("Min\n(ms)").headerAlign(HorizontalAlign.CENTER).maxColumnWidth(8).with(r -> r.min), + new Column().header("Since\nLast").headerAlign(HorizontalAlign.CENTER).maxColumnWidth(10) + .with(r -> r.sinceLast)))); + } + + return 0; + } + + private JsonObject loadStatus(long pid) { + try { + File f = getStatusFile("" + pid); + if (f != null) { + FileInputStream fis = new FileInputStream(f); + String text = IOHelper.loadText(fis); + IOHelper.close(fis); + return (JsonObject) Jsoner.deserialize(text); + } + } catch (Throwable e) { + // ignore + } + return null; + } + + private static class Row { + String pid; + String name; + String routeId; + String from; + String source; + String uptime; + String state; + String total; + String failed; + String inflight; + String mean; + String max; + String min; + String sinceLast; + } + +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java index 40e5651f0db..519de022c41 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java @@ -77,4 +77,11 @@ abstract class ProcessBaseCommand extends CamelCommand { return since; } + static String maxWidth(String text, int max) { + if (text == null || text.length() <= max) { + return text; + } + return text.substring(0, max); + } + }