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 2023/10/05 18:17:24 UTC

[camel] branch main updated: CAMEL-19960: camel-jbang - Add camel get startup-recorder

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 2f0e8903e90 CAMEL-19960: camel-jbang - Add camel get startup-recorder
2f0e8903e90 is described below

commit 2f0e8903e904542bb2d13aa20e0f3ff2c37cf428
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Oct 5 20:17:11 2023 +0200

    CAMEL-19960: camel-jbang - Add camel get startup-recorder
---
 .../org/apache/camel/main/BaseMainSupport.java     |  15 +-
 .../camel/cli/connector/LocalCliConnector.java     |   8 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   2 +
 .../action/CamelStartupRecorderAction.java         | 196 +++++++++++++++++++++
 .../java/org/apache/camel/main/KameletMain.java    |   3 +
 5 files changed, 220 insertions(+), 4 deletions(-)

diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index b529a16f91f..bc9760e909e 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -39,6 +39,7 @@ import org.apache.camel.CamelConfiguration;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Component;
 import org.apache.camel.Configuration;
+import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.NoSuchLanguageException;
 import org.apache.camel.PropertiesLookupListener;
 import org.apache.camel.RuntimeCamelException;
@@ -484,6 +485,8 @@ public abstract class BaseMainSupport extends BaseService {
     }
 
     protected void configureStartupRecorder(CamelContext camelContext) {
+        ExtendedCamelContext ecc = camelContext.getCamelContextExtension();
+
         // we need to load these configurations early as they control the startup recorder when using camel-jfr
         // and we want to start jfr recording as early as possible to also capture details during bootstrapping Camel
 
@@ -516,16 +519,20 @@ public abstract class BaseMainSupport extends BaseService {
 
         if ("off".equals(mainConfigurationProperties.getStartupRecorder())
                 || "false".equals(mainConfigurationProperties.getStartupRecorder())) {
-            camelContext.getCamelContextExtension().getStartupStepRecorder().setEnabled(false);
+            ecc.getStartupStepRecorder().setEnabled(false);
         } else if ("logging".equals(mainConfigurationProperties.getStartupRecorder())) {
-            camelContext.getCamelContextExtension().setStartupStepRecorder(new LoggingStartupStepRecorder());
+            if (!(ecc.getStartupStepRecorder() instanceof LoggingStartupStepRecorder)) {
+                ecc.setStartupStepRecorder(new LoggingStartupStepRecorder());
+            }
         } else if ("backlog".equals(mainConfigurationProperties.getStartupRecorder())) {
-            camelContext.getCamelContextExtension().setStartupStepRecorder(new BacklogStartupStepRecorder());
+            if (!(ecc.getStartupStepRecorder() instanceof BacklogStartupStepRecorder)) {
+                ecc.setStartupStepRecorder(new BacklogStartupStepRecorder());
+            }
         } else if ("jfr".equals(mainConfigurationProperties.getStartupRecorder())
                 || "java-flight-recorder".equals(mainConfigurationProperties.getStartupRecorder())
                 || mainConfigurationProperties.getStartupRecorder() == null) {
             // try to auto discover camel-jfr to use
-            StartupStepRecorder fr = camelContext.getCamelContextExtension().getBootstrapFactoryFinder()
+            StartupStepRecorder fr = ecc.getBootstrapFactoryFinder()
                     .newInstance(StartupStepRecorder.FACTORY, StartupStepRecorder.class).orElse(null);
             if (fr != null) {
                 LOG.debug("Discovered startup recorder: {} from classpath", fr);
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 365f13b6908..9ff207ec741 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
@@ -324,6 +324,14 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C
                     LOG.trace("Updating output file: {}", outputFile);
                     IOHelper.writeText(json.toJson(), outputFile);
                 }
+            } else if ("startup-recorder".equals(action)) {
+                DevConsole dc = camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+                        .resolveById("startup-recorder");
+                if (dc != null) {
+                    JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON);
+                    LOG.trace("Updating output file: {}", outputFile);
+                    IOHelper.writeText(json.toJson(), outputFile);
+                }
             } else if ("stub".equals(action)) {
                 String filter = root.getString("filter");
                 String limit = root.getString("limit");
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 4f052d10534..27235d7e27c 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
@@ -31,6 +31,7 @@ import org.apache.camel.dsl.jbang.core.commands.action.CamelRouteStopAction;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelSendAction;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelSourceAction;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelSourceTop;
+import org.apache.camel.dsl.jbang.core.commands.action.CamelStartupRecorderAction;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelStubAction;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelThreadDump;
 import org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction;
@@ -108,6 +109,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("service", new CommandLine(new ListService(main)))
                         .addSubcommand("source", new CommandLine(new CamelSourceAction(main)))
                         .addSubcommand("route-dump", new CommandLine(new CamelRouteDumpAction(main)))
+                        .addSubcommand("startup-recorder", new CommandLine(new CamelStartupRecorderAction(main)))
                         .addSubcommand("vault", new CommandLine(new ListVault(main))))
                 .addSubcommand("top", new CommandLine(new CamelTop(main))
                         .addSubcommand("context", new CommandLine(new CamelContextTop(main)))
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderAction.java
new file mode 100644
index 00000000000..33d51103982
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderAction.java
@@ -0,0 +1,196 @@
+/*
+ * 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.action;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+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.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.StopWatch;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "startup-recorder",
+                     description = "Display startup recording", sortOptions = false)
+public class CamelStartupRecorderAction extends ActionWatchCommand {
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--sort" }, completionCandidates = DurationTypeCompletionCandidates.class,
+                        description = "Sort by duration, or type")
+    String sort;
+
+    private volatile long pid;
+
+    public CamelStartupRecorderAction(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doWatchCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        List<Long> pids = findPids(name);
+        if (pids.isEmpty()) {
+            return 0;
+        } else if (pids.size() > 1) {
+            System.out.println("Name or pid " + name + " matches " + pids.size()
+                               + " running Camel integrations. Specify a name or PID that matches exactly one.");
+            return 0;
+        }
+
+        this.pid = pids.get(0);
+
+        // ensure output file is deleted before executing action
+        File outputFile = getOutputFile(Long.toString(pid));
+        FileUtil.deleteFile(outputFile);
+
+        JsonObject root = new JsonObject();
+        root.put("action", "startup-recorder");
+        File f = getActionFile(Long.toString(pid));
+        try {
+            IOHelper.writeText(root.toJson(), f);
+        } catch (Exception e) {
+            // ignore
+        }
+
+        JsonObject jo = waitForOutputFile(outputFile);
+        if (jo != null) {
+            JsonArray arr = (JsonArray) jo.get("steps");
+            for (int i = 0; arr != null && i < arr.size(); i++) {
+                JsonObject o = (JsonObject) arr.get(i);
+                Row row = new Row();
+                row.id = o.getInteger("id");
+                row.parentId = o.getInteger("parentId");
+                row.level = o.getInteger("level");
+                row.name = o.getString("name");
+                row.type = o.getString("type");
+                row.description = o.getString("description");
+                row.duration = o.getLong("duration");
+                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("DURATION").dataAlign(HorizontalAlign.RIGHT).with(this::getDuration),
+                    new Column().header("TYPE").dataAlign(HorizontalAlign.LEFT).with(r -> r.type),
+                    new Column().header("STEP (END)").dataAlign(HorizontalAlign.LEFT)
+                            .maxWidth(80, OverflowBehaviour.ELLIPSIS_RIGHT)
+                            .with(this::getStep))));
+        }
+
+        // delete output file after use
+        FileUtil.deleteFile(outputFile);
+
+        return 0;
+    }
+
+    private String getStep(Row r) {
+        String pad = StringHelper.padString(r.level);
+        String out = r.description;
+        if (r.name != null && !r.name.equals("null")) {
+            out = String.format("%s(%s)", r.description, r.name);
+        }
+        return pad + out;
+    }
+
+    private String getDuration(Row r) {
+        if (r.duration > 0) {
+            return "" + r.duration;
+        }
+        return "";
+    }
+
+    protected int sortRow(Row o1, Row o2) {
+        String s = sort != null ? sort : "";
+        int negate = 1;
+        if (s.startsWith("-")) {
+            s = s.substring(1);
+            negate = -1;
+        }
+        switch (s) {
+            case "duration":
+                return Long.compare(o1.duration, o2.duration) * negate;
+            case "type":
+                return o1.type.compareToIgnoreCase(o2.type) * negate;
+            default:
+                return 0;
+        }
+    }
+
+    protected JsonObject waitForOutputFile(File outputFile) {
+        StopWatch watch = new StopWatch();
+        while (watch.taken() < 5000) {
+            try {
+                // give time for response to be ready
+                Thread.sleep(100);
+
+                if (outputFile.exists()) {
+                    FileInputStream fis = new FileInputStream(outputFile);
+                    String text = IOHelper.loadText(fis);
+                    IOHelper.close(fis);
+                    return (JsonObject) Jsoner.deserialize(text);
+                }
+
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return null;
+    }
+
+    private static class Row {
+        int id;
+        int parentId;
+        int level;
+        String name;
+        String type;
+        String description;
+        long duration;
+    }
+
+    public static class DurationTypeCompletionCandidates implements Iterable<String> {
+
+        public DurationTypeCompletionCandidates() {
+        }
+
+        @Override
+        public Iterator<String> iterator() {
+            return List.of("duration", "type").iterator();
+        }
+
+    }
+
+}
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 6bdbf41ceab..a7a4388faf8 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -82,6 +82,7 @@ import org.apache.camel.support.DefaultContextReloadStrategy;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.RouteOnDemandReloadStrategy;
 import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.startup.BacklogStartupStepRecorder;
 import org.apache.camel.tooling.maven.MavenGav;
 
 /**
@@ -347,6 +348,8 @@ public class KameletMain extends MainCommandLineSupport {
 
         // do not build/init camel context yet
         DefaultCamelContext answer = new DefaultCamelContext(false);
+        // setup backlog recorder from very start
+        answer.getCamelContextExtension().setStartupStepRecorder(new BacklogStartupStepRecorder());
         if (download) {
             ClassLoader dynamicCL = createApplicationContextClassLoader(answer);
             answer.setApplicationContextClassLoader(dynamicCL);