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/23 09:27:39 UTC

[camel] branch main updated (40d826e2580 -> 814356b9e31)

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

davsclaus pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


    from 40d826e2580 (chores) camel-catalog-maven: reduce method complexity (#8608)
     new ff79be2fe85 CAMEL-18630: camel-jbang - Add get health command
     new c1396ea75a5 CAMEL-18630: camel-jbang - Add get health command
     new 0a542f7daae CAMEL-18639: camel-health - Add detail for last time there was a failure
     new 02ca24721d7 CAMEL-18639: camel-health - Add detail for last time there was a failure
     new 814356b9e31 CAMEL-18630: camel-jbang - Add get health command

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/camel/health/HealthCheck.java  |   3 +
 .../camel/impl/health/AbstractHealthCheck.java     |  29 +++
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   2 +
 .../jbang/core/commands/process/ListHealth.java    | 256 +++++++++++++++++++++
 4 files changed, 290 insertions(+)
 create mode 100644 dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealth.java


[camel] 04/05: CAMEL-18639: camel-health - Add detail for last time there was a failure

Posted by da...@apache.org.
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

commit 02ca24721d70614816d64767fac465ae8fa6978d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun Oct 23 11:27:06 2022 +0200

    CAMEL-18639: camel-health - Add detail for last time there was a failure
---
 .../java/org/apache/camel/impl/health/AbstractHealthCheck.java   | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java b/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
index a40322ec5aa..654c47e2e83 100644
--- a/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
+++ b/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
@@ -185,6 +185,11 @@ public abstract class AbstractHealthCheck implements HealthCheck, CamelContextAw
             // reset failure since it ok
             failureCount = 0;
             successCount++;
+            if (successTime == null) {
+                // first time we are OK, then reset failure as we only want to capture
+                // failures that is happening after we have been ok (such as during bootstrap)
+                failureTime = null;
+            }
             successTime = invocationTime;
         }
 
@@ -193,10 +198,14 @@ public abstract class AbstractHealthCheck implements HealthCheck, CamelContextAw
         meta.put(FAILURE_COUNT, failureCount);
         if (failureTime != null) {
             meta.put(FAILURE_TIME, failureTime);
+        } else {
+            meta.remove(FAILURE_TIME);
         }
         meta.put(SUCCESS_COUNT, successCount);
         if (successTime != null) {
             meta.put(SUCCESS_TIME, successTime);
+        } else {
+            meta.remove(SUCCESS_TIME);
         }
 
         // Copy some meta-data bits to the response attributes so the


[camel] 03/05: CAMEL-18639: camel-health - Add detail for last time there was a failure

Posted by da...@apache.org.
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

commit 0a542f7daae3dabe4a11034ecaf2a0391fa5077d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun Oct 23 11:00:44 2022 +0200

    CAMEL-18639: camel-health - Add detail for last time there was a failure
---
 .../main/java/org/apache/camel/health/HealthCheck.java   |  3 +++
 .../apache/camel/impl/health/AbstractHealthCheck.java    | 16 ++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java b/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java
index b2372468f51..e02a4c402f2 100644
--- a/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java
+++ b/core/camel-api/src/main/java/org/apache/camel/health/HealthCheck.java
@@ -35,11 +35,14 @@ public interface HealthCheck extends HasGroup, HasId, Ordered {
     String CHECK_ENABLED = "check.enabled";
     String INVOCATION_COUNT = "invocation.count";
     String INVOCATION_TIME = "invocation.time";
+    @Deprecated
     String INVOCATION_ATTEMPT_TIME = "invocation.attempt.time";
     String FAILURE_COUNT = "failure.count";
+    String FAILURE_TIME = "failure.time";
     String ENDPOINT_URI = "endpoint.uri";
     String FAILURE_ERROR_COUNT = "failure.error.count";
     String SUCCESS_COUNT = "success.count";
+    String SUCCESS_TIME = "success.time";
     String HTTP_RESPONSE_CODE = "http.response.code";
     /**
      * Use ENDPOINT_URI
diff --git a/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java b/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
index d5d218d18c8..a40322ec5aa 100644
--- a/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
+++ b/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
@@ -152,7 +152,9 @@ public abstract class AbstractHealthCheck implements HealthCheck, CamelContextAw
         // Extract relevant information from meta data.
         int invocationCount = (Integer) meta.getOrDefault(INVOCATION_COUNT, 0);
         int failureCount = (Integer) meta.getOrDefault(FAILURE_COUNT, 0);
+        String failureTime = (String) meta.get(FAILURE_TIME);
         int successCount = (Integer) meta.getOrDefault(SUCCESS_COUNT, 0);
+        String successTime = (String) meta.get(SUCCESS_TIME);
 
         String invocationTime = ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
 
@@ -178,23 +180,37 @@ public abstract class AbstractHealthCheck implements HealthCheck, CamelContextAw
             // reset success since it failed
             successCount = 0;
             failureCount++;
+            failureTime = invocationTime;
         } else if (builder.state() == State.UP) {
             // reset failure since it ok
             failureCount = 0;
             successCount++;
+            successTime = invocationTime;
         }
 
         meta.put(INVOCATION_TIME, invocationTime);
         meta.put(INVOCATION_COUNT, ++invocationCount);
         meta.put(FAILURE_COUNT, failureCount);
+        if (failureTime != null) {
+            meta.put(FAILURE_TIME, failureTime);
+        }
         meta.put(SUCCESS_COUNT, successCount);
+        if (successTime != null) {
+            meta.put(SUCCESS_TIME, successTime);
+        }
 
         // Copy some meta-data bits to the response attributes so the
         // response caches the health-check state at the time of the invocation.
         builder.detail(INVOCATION_TIME, meta.get(INVOCATION_TIME));
         builder.detail(INVOCATION_COUNT, meta.get(INVOCATION_COUNT));
         builder.detail(FAILURE_COUNT, meta.get(FAILURE_COUNT));
+        if (meta.containsKey(FAILURE_TIME)) {
+            builder.detail(FAILURE_TIME, meta.get(FAILURE_TIME));
+        }
         builder.detail(SUCCESS_COUNT, meta.get(SUCCESS_COUNT));
+        if (meta.containsKey(SUCCESS_TIME)) {
+            builder.detail(SUCCESS_TIME, meta.get(SUCCESS_TIME));
+        }
 
         return builder;
     }


[camel] 05/05: CAMEL-18630: camel-jbang - Add get health command

Posted by da...@apache.org.
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

commit 814356b9e315a1529e146f2e800e6ecea68743c2
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun Oct 23 11:27:25 2022 +0200

    CAMEL-18630: camel-jbang - Add get health command
---
 .../jbang/core/commands/process/ListHealth.java    | 45 +++++++++++++++++-----
 1 file changed, 36 insertions(+), 9 deletions(-)

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 9af128516c6..e42979a0569 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
@@ -113,7 +113,23 @@ public class ListHealth extends ProcessBaseCommand {
                                     ZonedDateTime zdt = ZonedDateTime.parse(time);
                                     if (zdt != null) {
                                         long delta = Math.abs(ZonedDateTime.now().until(zdt, ChronoUnit.MILLIS));
-                                        row.since = TimeUtils.printAge(delta);
+                                        row.sinceLast = TimeUtils.printAge(delta);
+                                    }
+                                }
+                                time = d.getString("success.time");
+                                if (time != null) {
+                                    ZonedDateTime zdt = ZonedDateTime.parse(time);
+                                    if (zdt != null) {
+                                        long delta = Math.abs(ZonedDateTime.now().until(zdt, ChronoUnit.MILLIS));
+                                        row.sinceSuccess = TimeUtils.printAge(delta);
+                                    }
+                                }
+                                time = d.getString("failure.time");
+                                if (time != null) {
+                                    ZonedDateTime zdt = ZonedDateTime.parse(time);
+                                    if (zdt != null) {
+                                        long delta = Math.abs(ZonedDateTime.now().until(zdt, ChronoUnit.MILLIS));
+                                        row.sinceFailure = TimeUtils.printAge(delta);
                                     }
                                 }
                             }
@@ -152,11 +168,12 @@ public class ListHealth extends ProcessBaseCommand {
                     new Column().header("STATE").headerAlign(HorizontalAlign.CENTER)
                             .dataAlign(HorizontalAlign.CENTER)
                             .with(r -> r.state),
-                    new Column().header("SINCE").headerAlign(HorizontalAlign.RIGHT)
+                    new Column().header("RATE").headerAlign(HorizontalAlign.CENTER)
                             .dataAlign(HorizontalAlign.RIGHT)
-                            .with(r -> r.since),
-                    new Column().header("TOTAL").headerAlign(HorizontalAlign.RIGHT).with(r -> r.total),
-                    new Column().header("OK/KO").headerAlign(HorizontalAlign.RIGHT).with(this::getRate),
+                            .with(this::getRate),
+                    new Column().header("SINCE").headerAlign(HorizontalAlign.CENTER)
+                            .dataAlign(HorizontalAlign.RIGHT)
+                            .with(this::getSince),
                     new Column().header("MESSAGE").dataAlign(HorizontalAlign.LEFT)
                             .maxWidth(80, OverflowBehaviour.NEWLINE)
                             .with(r -> r.message))));
@@ -204,9 +221,17 @@ public class ListHealth extends ProcessBaseCommand {
     }
 
     protected String getRate(Row r) {
-        String s1 = r.success != null && !"0".equals(r.success) ? r.success : "-";
-        String s2 = r.failure != null && !"0".equals(r.failure) ? r.failure : "-";
-        return s1 + "/" + s2;
+        String s1 = r.total != null && !"0".equals(r.total) ? r.total : "-";
+        String s2 = r.success != null && !"0".equals(r.success) ? r.success : "-";
+        String s3 = r.failure != null && !"0".equals(r.failure) ? r.failure : "-";
+        return s1 + "/" + s2 + "/" + s3;
+    }
+
+    protected String getSince(Row r) {
+        String s1 = r.sinceLast != null ? r.sinceLast : "-";
+        String s2 = r.sinceSuccess != null ? r.sinceSuccess : "-";
+        String s3 = r.sinceFailure != null ? r.sinceFailure : "-";
+        return s1 + "/" + s2 + "/" + s3;
     }
 
     private static class Row {
@@ -222,7 +247,9 @@ public class ListHealth extends ProcessBaseCommand {
         String total;
         String success;
         String failure;
-        String since;
+        String sinceLast;
+        String sinceSuccess;
+        String sinceFailure;
         String message;
     }
 


[camel] 02/05: CAMEL-18630: camel-jbang - Add get health command

Posted by da...@apache.org.
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

commit c1396ea75a54b01d390589838b33ef4d02a33732
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun Oct 23 10:41:49 2022 +0200

    CAMEL-18630: camel-jbang - Add get health command
---
 .../apache/camel/dsl/jbang/core/commands/process/ListHealth.java  | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

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 0e991e024dd..9af128516c6 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
@@ -148,13 +148,13 @@ public class ListHealth extends ProcessBaseCommand {
                     new Column().header("ID").dataAlign(HorizontalAlign.LEFT)
                             .maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT)
                             .with(this::getId),
-                    new Column().header("SINCE").headerAlign(HorizontalAlign.RIGHT)
-                            .dataAlign(HorizontalAlign.RIGHT)
-                            .with(r -> r.since),
+                    new Column().header("RL").minWidth(4).maxWidth(4).with(this::getLR),
                     new Column().header("STATE").headerAlign(HorizontalAlign.CENTER)
                             .dataAlign(HorizontalAlign.CENTER)
                             .with(r -> r.state),
-                    new Column().header("RL").minWidth(4).maxWidth(4).with(this::getLR),
+                    new Column().header("SINCE").headerAlign(HorizontalAlign.RIGHT)
+                            .dataAlign(HorizontalAlign.RIGHT)
+                            .with(r -> r.since),
                     new Column().header("TOTAL").headerAlign(HorizontalAlign.RIGHT).with(r -> r.total),
                     new Column().header("OK/KO").headerAlign(HorizontalAlign.RIGHT).with(this::getRate),
                     new Column().header("MESSAGE").dataAlign(HorizontalAlign.LEFT)


[camel] 01/05: CAMEL-18630: camel-jbang - Add get health command

Posted by da...@apache.org.
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

commit ff79be2fe85469e74acd7e0641b375b01c835f4a
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun Oct 23 10:36:00 2022 +0200

    CAMEL-18630: camel-jbang - Add get health command
---
 .../camel/impl/health/AbstractHealthCheck.java     |   4 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   2 +
 .../jbang/core/commands/process/ListHealth.java    | 229 +++++++++++++++++++++
 3 files changed, 235 insertions(+)

diff --git a/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java b/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
index 3b1767ade2a..d5d218d18c8 100644
--- a/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
+++ b/core/camel-health/src/main/java/org/apache/camel/impl/health/AbstractHealthCheck.java
@@ -145,6 +145,10 @@ public abstract class AbstractHealthCheck implements HealthCheck, CamelContextAw
             kind = isLiveness() ? Kind.LIVENESS : Kind.READINESS;
         }
         builder.detail(CHECK_KIND, kind);
+        builder.detail(CHECK_ID, meta.get(CHECK_ID));
+        if (meta.containsKey(CHECK_GROUP)) {
+            builder.detail(CHECK_GROUP, meta.get(CHECK_GROUP));
+        }
         // Extract relevant information from meta data.
         int invocationCount = (Integer) meta.getOrDefault(INVOCATION_COUNT, 0);
         int failureCount = (Integer) meta.getOrDefault(FAILURE_COUNT, 0);
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 65999c69f5a..44edb4a1862 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
@@ -51,6 +51,7 @@ 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.ListEvent;
+import org.apache.camel.dsl.jbang.core.commands.process.ListHealth;
 import org.apache.camel.dsl.jbang.core.commands.process.ListInflight;
 import org.apache.camel.dsl.jbang.core.commands.process.ListProcess;
 import org.apache.camel.dsl.jbang.core.commands.process.ListService;
@@ -74,6 +75,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("context", new CommandLine(new CamelContextStatus(main)))
                         .addSubcommand("route", new CommandLine(new CamelRouteStatus(main)))
                         .addSubcommand("processor", new CommandLine(new CamelProcessorStatus(main)))
+                        .addSubcommand("health", new CommandLine(new ListHealth(main)))
                         .addSubcommand("endpoint", new CommandLine(new CamelEndpointStatus(main)))
                         .addSubcommand("event", new CommandLine(new ListEvent(main)))
                         .addSubcommand("inflight", new CommandLine(new ListInflight(main)))
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
new file mode 100644
index 00000000000..0e991e024dd
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealth.java
@@ -0,0 +1,229 @@
+/*
+ * 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.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+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 = "health", description = "Get health check status of running Camel integrations")
+public class ListHealth extends ProcessBaseCommand {
+
+    @CommandLine.Option(names = { "--sort" },
+                        description = "Sort by pid, name or age", defaultValue = "pid")
+    String sort;
+
+    @CommandLine.Option(names = { "--level" },
+                        description = "Level of details: full, oneline, or default", defaultValue = "default")
+    String level;
+
+    @CommandLine.Option(names = { "--down" },
+                        description = "Show only checks which are DOWN")
+    boolean down;
+
+    @CommandLine.Option(names = { "--ready" },
+                        description = "Show only readiness checks")
+    boolean ready;
+
+    @CommandLine.Option(names = { "--live" },
+                        description = "Show only liveness checks")
+    boolean live;
+
+    public ListHealth(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer call() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        List<Long> pids = findPids("*");
+        ProcessHandle.allProcesses()
+                .filter(ph -> pids.contains(ph.pid()))
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    if (root != null) {
+                        JsonObject context = (JsonObject) root.get("context");
+                        if (context == null) {
+                            return;
+                        }
+                        JsonObject hc = (JsonObject) root.get("healthChecks");
+                        if (hc == null) {
+                            return;
+                        }
+                        JsonArray array = (JsonArray) hc.get("checks");
+                        for (int i = 0; i < array.size(); i++) {
+                            JsonObject o = (JsonObject) array.get(i);
+                            Row row = new Row();
+                            row.pid = "" + ph.pid();
+                            row.uptime = extractSince(ph);
+                            row.ago = TimeUtils.printSince(row.uptime);
+                            row.name = context.getString("name");
+                            if ("CamelJBang".equals(row.name)) {
+                                row.name = extractName(root, ph);
+                            }
+                            row.id = o.getString("id");
+                            row.group = o.getString("group");
+                            row.state = o.getString("state");
+                            row.readiness = o.getBoolean("readiness");
+                            row.liveness = o.getBoolean("liveness");
+                            row.message = o.getString("message");
+
+                            JsonObject d = (JsonObject) o.get("details");
+                            if (d != null) {
+                                row.total = d.getString("invocation.count");
+                                row.success = d.getString("success.count");
+                                row.failure = d.getString("failure.count");
+                                String kind = d.getString("check.kind");
+                                if ("READINESS".equals(kind)) {
+                                    row.liveness = false;
+                                } else if ("LIVENESS".equals(kind)) {
+                                    row.readiness = false;
+                                }
+                                // calc how long time since the check was invoked
+                                String time = d.getString("invocation.time");
+                                if (time != null) {
+                                    ZonedDateTime zdt = ZonedDateTime.parse(time);
+                                    if (zdt != null) {
+                                        long delta = Math.abs(ZonedDateTime.now().until(zdt, ChronoUnit.MILLIS));
+                                        row.since = TimeUtils.printAge(delta);
+                                    }
+                                }
+                            }
+
+                            boolean add = true;
+                            if (live && !row.liveness) {
+                                add = false;
+                            }
+                            if (ready && !row.readiness) {
+                                add = false;
+                            }
+                            if (down && !row.state.equals("DOWN")) {
+                                add = false;
+                            }
+                            if (add) {
+                                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(40, OverflowBehaviour.ELLIPSIS_RIGHT)
+                            .with(r -> r.name),
+                    new Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.ago),
+                    new Column().header("ID").dataAlign(HorizontalAlign.LEFT)
+                            .maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT)
+                            .with(this::getId),
+                    new Column().header("SINCE").headerAlign(HorizontalAlign.RIGHT)
+                            .dataAlign(HorizontalAlign.RIGHT)
+                            .with(r -> r.since),
+                    new Column().header("STATE").headerAlign(HorizontalAlign.CENTER)
+                            .dataAlign(HorizontalAlign.CENTER)
+                            .with(r -> r.state),
+                    new Column().header("RL").minWidth(4).maxWidth(4).with(this::getLR),
+                    new Column().header("TOTAL").headerAlign(HorizontalAlign.RIGHT).with(r -> r.total),
+                    new Column().header("OK/KO").headerAlign(HorizontalAlign.RIGHT).with(this::getRate),
+                    new Column().header("MESSAGE").dataAlign(HorizontalAlign.LEFT)
+                            .maxWidth(80, OverflowBehaviour.NEWLINE)
+                            .with(r -> r.message))));
+        }
+
+        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;
+        }
+    }
+
+    protected String getId(Row r) {
+        if (r.group != null) {
+            return r.group + "/" + r.id;
+        } else {
+            return r.id;
+        }
+    }
+
+    protected String getLR(Row r) {
+        if (r.readiness && r.liveness) {
+            return "RL";
+        } else if (r.readiness) {
+            return "R";
+        } else if (r.liveness) {
+            return "L";
+        }
+        return "";
+    }
+
+    protected String getRate(Row r) {
+        String s1 = r.success != null && !"0".equals(r.success) ? r.success : "-";
+        String s2 = r.failure != null && !"0".equals(r.failure) ? r.failure : "-";
+        return s1 + "/" + s2;
+    }
+
+    private static class Row {
+        String pid;
+        String name;
+        String ago;
+        long uptime;
+        String id;
+        String group;
+        String state;
+        boolean readiness;
+        boolean liveness;
+        String total;
+        String success;
+        String failure;
+        String since;
+        String message;
+    }
+
+}