You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/10/31 11:57:35 UTC

[camel] branch main updated: camel-resilienc4j - Add dev console and Camel CLI command

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 a6dcfd06bfe camel-resilienc4j - Add dev console and Camel CLI command
a6dcfd06bfe is described below

commit a6dcfd06bfe14faecf8d2f91927ace44d2147f37
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Oct 31 12:57:15 2022 +0100

    camel-resilienc4j - Add dev console and Camel CLI command
---
 components/camel-resilience4j/pom.xml              |   4 +
 .../org/apache/camel/dev-console/resilience4j      |   2 +
 .../component/resilience4j/ResilienceConsole.java  |  91 +++++++++++
 .../camel/cli/connector/LocalCliConnector.java     |   7 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   2 +
 .../core/commands/process/ListCircuitBreaker.java  | 175 +++++++++++++++++++++
 6 files changed, 281 insertions(+)

diff --git a/components/camel-resilience4j/pom.xml b/components/camel-resilience4j/pom.xml
index e0a0c7aa09a..b9ad920aa3a 100644
--- a/components/camel-resilience4j/pom.xml
+++ b/components/camel-resilience4j/pom.xml
@@ -45,6 +45,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-core-reifier</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-console</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>io.github.resilience4j</groupId>
diff --git a/components/camel-resilience4j/src/generated/resources/META-INF/services/org/apache/camel/dev-console/resilience4j b/components/camel-resilience4j/src/generated/resources/META-INF/services/org/apache/camel/dev-console/resilience4j
new file mode 100644
index 00000000000..0eab670f554
--- /dev/null
+++ b/components/camel-resilience4j/src/generated/resources/META-INF/services/org/apache/camel/dev-console/resilience4j
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.resilience4j.ResilienceConsole
diff --git a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java
new file mode 100644
index 00000000000..1e41d832ee2
--- /dev/null
+++ b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java
@@ -0,0 +1,91 @@
+package org.apache.camel.component.resilience4j;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.Processor;
+import org.apache.camel.Route;
+import org.apache.camel.impl.console.AbstractDevConsole;
+import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.util.json.JsonObject;
+
+@DevConsole("resilience4j")
+public class ResilienceConsole extends AbstractDevConsole {
+
+    public ResilienceConsole() {
+        super("camel", "resilience4j", "Resilience Circuit Breaker", "Display circuit breaker information");
+    }
+
+    @Override
+    protected String doCallText(Map<String, Object> options) {
+        StringBuilder sb = new StringBuilder();
+
+        List<ResilienceProcessor> cbs = new ArrayList<>();
+        for (Route route : getCamelContext().getRoutes()) {
+            List<Processor> list = route.filter("*");
+            for (Processor p : list) {
+                if (p instanceof ResilienceProcessor) {
+                    cbs.add((ResilienceProcessor) p);
+                }
+            }
+        }
+        // sort by ids
+        cbs.sort(Comparator.comparing(ResilienceProcessor::getId));
+
+        for (ResilienceProcessor cb : cbs) {
+            String id = cb.getId();
+            String rid = cb.getRouteId();
+            String state = cb.getCircuitBreakerState();
+            int sc = cb.getNumberOfSuccessfulCalls();
+            int bc = cb.getNumberOfBufferedCalls();
+            int fc = cb.getNumberOfFailedCalls();
+            long npc = cb.getNumberOfNotPermittedCalls();
+            float fr = cb.getFailureRate();
+            if (fr > 0) {
+                sb.append(String.format("    %s/%s: %s (buffered: %d success: %d failure: %d/%.0f%% not-permitted: %d)\n", rid,
+                        id, state, bc, sc, fc, fr, npc));
+            } else {
+                sb.append(String.format("    %s/%s: %s (buffered: %d success: %d failure: 0 not-permitted: %d)\n", rid, id,
+                        state, bc, sc, npc));
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    protected JsonObject doCallJson(Map<String, Object> options) {
+        JsonObject root = new JsonObject();
+
+        List<ResilienceProcessor> cbs = new ArrayList<>();
+        for (Route route : getCamelContext().getRoutes()) {
+            List<Processor> list = route.filter("*");
+            for (Processor p : list) {
+                if (p instanceof ResilienceProcessor) {
+                    cbs.add((ResilienceProcessor) p);
+                }
+            }
+        }
+        // sort by ids
+        cbs.sort(Comparator.comparing(ResilienceProcessor::getId));
+
+        final List<JsonObject> list = new ArrayList<>();
+        for (ResilienceProcessor cb : cbs) {
+            JsonObject jo = new JsonObject();
+            jo.put("id", cb.getId());
+            jo.put("routeId", cb.getRouteId());
+            jo.put("state", cb.getCircuitBreakerState());
+            jo.put("bufferedCalls", cb.getNumberOfBufferedCalls());
+            jo.put("successfulCalls", cb.getNumberOfSuccessfulCalls());
+            jo.put("failedCalls", cb.getNumberOfFailedCalls());
+            jo.put("notPermittedCalls", cb.getNumberOfNotPermittedCalls());
+            jo.put("failureRate", cb.getFailureRate());
+            list.add(jo);
+        }
+        root.put("circuitBreakers", list);
+
+        return root;
+    }
+}
diff --git a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 1d9d82dc330..6e1ba6d7cba 100644
--- a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++ b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -405,6 +405,13 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C
                         root.put("micrometer", json);
                     }
                 }
+                DevConsole dc10 = dcr.resolveById("resilience4j");
+                if (dc10 != null) {
+                    JsonObject json = (JsonObject) dc10.call(DevConsole.MediaType.JSON);
+                    if (json != null && !json.isEmpty()) {
+                        root.put("resilience4j", json);
+                    }
+                }
             }
             // various details
             JsonObject services = collectServices();
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 38645ac1c88..2122d081f12 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
@@ -50,6 +50,7 @@ import org.apache.camel.dsl.jbang.core.commands.process.CamelTop;
 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.ListBlocked;
+import org.apache.camel.dsl.jbang.core.commands.process.ListCircuitBreaker;
 import org.apache.camel.dsl.jbang.core.commands.process.ListEvent;
 import org.apache.camel.dsl.jbang.core.commands.process.ListHealth;
 import org.apache.camel.dsl.jbang.core.commands.process.ListInflight;
@@ -82,6 +83,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("inflight", new CommandLine(new ListInflight(main)))
                         .addSubcommand("blocked", new CommandLine(new ListBlocked(main)))
                         .addSubcommand("route-controller", new CommandLine(new RouteControllerAction(main)))
+                        .addSubcommand("circuit-breaker", new CommandLine(new ListCircuitBreaker(main)))
                         .addSubcommand("micrometer", new CommandLine(new ListMicrometer(main)))
                         .addSubcommand("service", new CommandLine(new ListService(main)))
                         .addSubcommand("source", new CommandLine(new CamelSourceAction(main)))
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java
new file mode 100644
index 00000000000..6e520c31726
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java
@@ -0,0 +1,175 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.github.freva.asciitable.AsciiTable;
+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.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "circuit-breaker",
+         description = "Get status of Circuit Breaker EIPs")
+public class ListCircuitBreaker 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 ListCircuitBreaker(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()))
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    // there must be a status file for the running Camel integration
+                    if (root != null) {
+                        Row row = new Row();
+                        JsonObject context = (JsonObject) root.get("context");
+                        if (context == null) {
+                            return;
+                        }
+                        row.name = context.getString("name");
+                        if ("CamelJBang".equals(row.name)) {
+                            row.name = extractName(root, ph);
+                        }
+                        row.pid = "" + ph.pid();
+                        row.uptime = extractSince(ph);
+                        row.age = TimeUtils.printSince(row.uptime);
+                        Row baseRow = row.copy();
+
+                        JsonObject mo = (JsonObject) root.get("resilience4j");
+                        if (mo != null) {
+                            JsonArray arr = (JsonArray) mo.get("circuitBreakers");
+                            if (arr != null) {
+                                for (int i = 0; i < arr.size(); i++) {
+                                    row = baseRow.copy();
+                                    JsonObject jo = (JsonObject) arr.get(i);
+                                    row.component = "camel-resilience4j";
+                                    row.id = jo.getString("id");
+                                    row.routeId = jo.getString("routeId");
+                                    row.state = jo.getString("state");
+                                    row.bufferedCalls = jo.getInteger("bufferedCalls");
+                                    row.successfulCalls = jo.getInteger("successfulCalls");
+                                    row.failedCalls = jo.getInteger("failedCalls");
+                                    row.notPermittedCalls = jo.getLong("notPermittedCalls");
+                                    row.failureRate = jo.getDouble("failureRate");
+                                    rows.add(row);
+                                }
+                            }
+                        }
+                    }
+                });
+
+        // sort rows
+        rows.sort(this::sortRow);
+
+        if (!rows.isEmpty()) {
+            System.out.println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList(
+                    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("COMPONENT").dataAlign(HorizontalAlign.LEFT).with(r -> r.component),
+                    new Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT).with(r -> r.routeId),
+                    new Column().header("ID").dataAlign(HorizontalAlign.LEFT).with(r -> r.id),
+                    new Column().header("STATE").dataAlign(HorizontalAlign.LEFT).with(r -> r.state),
+                    new Column().header("BUFFER").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                            .with(r -> "" + r.bufferedCalls),
+                    new Column().header("SUCCESS").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                            .with(r -> "" + r.successfulCalls),
+                    new Column().header("FAIL").headerAlign(HorizontalAlign.CENTER).dataAlign(HorizontalAlign.RIGHT)
+                            .with(this::getFailure),
+                    new Column().header("REJECT").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                            .with(r -> "" + r.notPermittedCalls))));
+        }
+
+        return 0;
+    }
+
+    protected int sortRow(Row o1, Row o2) {
+        String s = sort;
+        int negate = 1;
+        if (s.startsWith("-")) {
+            s = s.substring(1);
+            negate = -1;
+        }
+        switch (s) {
+            case "pid":
+                return Long.compare(Long.parseLong(o1.pid), Long.parseLong(o2.pid)) * negate;
+            case "name":
+                return o1.name.compareToIgnoreCase(o2.name) * negate;
+            case "age":
+                return Long.compare(o1.uptime, o2.uptime) * negate;
+            default:
+                return 0;
+        }
+    }
+
+    private String getFailure(Row r) {
+        if (r.failedCalls <= 0) {
+            return "";
+        } else if (r.failureRate > 0) {
+            return +r.failedCalls + " (" + String.format("%.0f", r.failureRate) + "%)";
+        } else {
+            return "" + r.failedCalls;
+        }
+    }
+
+    private static class Row implements Cloneable {
+        String pid;
+        String name;
+        String age;
+        long uptime;
+        String component;
+        String id;
+        String routeId;
+        String state;
+        int bufferedCalls;
+        int successfulCalls;
+        int failedCalls;
+        long notPermittedCalls;
+        double failureRate;
+
+        Row copy() {
+            try {
+                return (Row) clone();
+            } catch (CloneNotSupportedException e) {
+                return null;
+            }
+        }
+    }
+
+}