[JAVA] Versuchen Sie, mithilfe der JFR-API einen Zeitleistenbericht über die Ausführungszeit einer Methode zu erstellen

Einführung

Verwenden Sie Java 11? Was mir an dieser Version am besten gefällt, ist, dass die JFR-API geöffnet / standardisiert wurde. Also "[Mikro-Benchmark mit JFR-API](https://qiita.com/koduki/items/6e8fbcbafdcc2f743f56#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0% Benutzerdefiniertes Ereignis wie im Artikel "E3% 82% A4% E3% 83% 99% E3% 83% B3% E3% 83% 88% E3% 81% AE% E4% BD% 9C% E6% 88% 90)" Sie können auch leicht machen.

Hast du übrigens jemals darüber nachgedacht? "JFR ist praktisch, unterscheidet sich jedoch von den Informationen, die Sie anzeigen möchten." "Da es sich um eine Binärdatei handelt, kann sie nicht mit Kibana oder vorhandenen Tools verknüpft werden." "Usw."

Ich habe mich gewundert! Es wäre praktisch, wenn ich den Inhalt von JFR überprüfen könnte, ohne JMC selbst zu verwenden. Also lass es uns tatsächlich tun. Basierend auf der Stichprobe der Methode, die von JFR erhalten werden kann, möchte ich den Ausführungsstatus der Methode in HTML wie APM umwandeln, was üblich ist.

Der diesmal verwendete Quellcode ist unten. https://github.com/koduki/jfr-viewer

Versuchen Sie, JFR zu laden

Laden wir zunächst den vorhandenen JFR. Hier ist test.jfr die vorhandene JFR. Als Einschränkung scheint es, dass JFR, das in Java 9 oder höher aufgezeichnet wurde, zum Lesen mit der JFR-API erforderlich ist. Es kann nicht geholfen werden, da sich die API geändert hat, aber es ist ein wenig problematisch. Ich muss die Produktionsumgebung so schnell wie möglich auf Java 11 ändern!

Verwenden Sie zum Lesen "RecordingFile # readAllEvents" zum Lesen. Es gibt andere Methoden, aber diese Methode wird empfohlen, da der Rückgabewert List ist und mit der Stream-API einfach zu handhaben ist.

RecordingFile.readAllEvents(path).stream()

Als nächstes filtern wir nach Ereignisnamen usw. Diesmal entspricht .ExecutionSample dem. Mit dem JMC-Ereignisbrowser können Sie den Namen leicht herausfinden. 001.png

Path path = Paths.get("test.jfr");
RecordingFile.readAllEvents(path).stream()
        .filter((e) -> e.getEventType().getName().endsWith(".ExecutionSample"))

Aggregierte Methoden aus der Stapelverfolgung

Unten erhalten wir den Stack-Trace aus dem ExecutionSample-Ereignis und konvertieren ihn in die folgende Datenstruktur.

+Methodenname 1
      -Erfassungszeit 1
      -Erfassungszeit 2
+Methodenname 2
      -Erfassungszeit 1
      -Erfassungszeit 2
      - ...
 

Eigentlich sollte die Aufrufreihenfolge der Methoden korrekt grafisch dargestellt werden, aber dieses Mal habe ich die Reihenfolge der Stapelverfolgung übersprungen und beibehalten, sodass es sich anfühlt, als ob die übergeordnete Methode ganz oben steht. Es mag also so aussehen, als ob es einen Fehler gibt, der es seltsam macht, wenn dieselbe Methode in einer anderen Hierarchie aufgerufen wird, aber es ist eine Spezifikation. Wenn es eine Spezifikation ist, ist es eine Spezifikation!

Sie können auch die Art der Methode ändern, die Sie erhalten möchten, indem Sie mit addMthods spielen. Dieses Mal versuche ich, die JFR-API abzuspielen, aber es ist beispielsweise möglich, anhand des Paketnamens zu überprüfen und nur Ihren eigenen Code zu überprüfen.

static Tuple2<LinkedHashMap<String, Set<Instant>>, List<Instant>> readMethod(Path path) throws IOException {
    var times = new ArrayList<Instant>();
    var methods = new LinkedHashMap<String, Set<Instant>>();
    RecordingFile.readAllEvents(path).stream()
            .filter((e) -> e.getEventType().getName().endsWith(".ExecutionSample"))
            .forEach((event) -> {
                RecordedStackTrace stacktrace = event.getStackTrace();
                if (stacktrace != null) {
                    times.add(event.getStartTime());
                    addMthods(methods, stacktrace, event);
                }
            });

    return $(methods, times);
}

static void addMthods(LinkedHashMap<String, Set<Instant>> methods, RecordedStackTrace stacktrace, RecordedEvent event) {
    stacktrace.getFrames().stream()
            .filter(x -> x.isJavaFrame())
            .filter(x -> !x.getMethod().getType().getName().startsWith("jdk.jfr."))
            .collect(Collectors.toCollection(ArrayDeque::new))
            .descendingIterator()
            .forEachRemaining(x -> {
                RecordedMethod method = x.getMethod();
                String key = method.getType().getName() + "#" + method.getName();
                Set<Instant> span = methods.getOrDefault(key, new HashSet<>());
                span.add(event.getStartTime());
                methods.put(key, span);
            });
}

In Zeitleiste konvertieren

Nachdem wir JFR analysiert haben, ist es in Ordnung, wenn wir es in HTML konvertieren. Von diesem Zeitpunkt an ist JFR völlig irrelevant geworden, und ich denke, die optimale Lösung besteht darin, ein Tool zu verwenden, das sich gut anfühlt, wie z. B. CSV. Diesmal ist es jedoch schwierig, eine Umgebung zu erstellen. Deshalb habe ich es als Testversion geschrieben.

Konvertieren Sie zunächst die Datenstruktur wie folgt.

- [Methodenname 1, [Startzeit,Endzeit]]
- [Methodenname 2, [Startzeit,Endzeit]]
- [Methodenname 3, [Startzeit,Endzeit]]
- [Methodenname 4, [Startzeit,Endzeit]]

Im Standardprofil zeigt das Methodenbeispiel jedoch nicht die Endzeit der Methode. Es ist nur Zeit, um die Stapelverfolgung abzurufen. Daher wird angenommen, dass "Methoden, die in aufeinanderfolgenden Erfassungszeiten existieren, noch ausgeführt werden". Diese Definition ist nicht immer korrekt, aber ich denke, sie ist ein ziemlich brauchbarer Index für die Stapelverzögerungsanalyse.

Konvertieren Sie mit der folgenden Methode.

static List<Tuple2<String, List<Tuple2<Instant, Instant>>>> parseSpan(LinkedHashMap<String, Set<Instant>> methods, List<Instant> times) {
    var span = new ArrayList<Tuple2<String, List<Tuple2<Instant, Instant>>>>();
    for (Entry<String, Set<Instant>> m : methods.entrySet()) {
        var key = m.getKey();
        var value = m.getValue();

        var xs = new ArrayList<Tuple2<Instant, Instant>>();
        Instant start = null;
        Instant last = null;
        for (Instant t : times) {
            if (value.contains(t)) {
                if (start == null) {
                    start = t;
                }
                last = t;
            } else {
                if (last != null) {
                    xs.add($(start, last));
                }
                start = null;
                last = null;
            }
        }
        span.add($(key, xs));
    }
    return span;
}

Bericht an HTML

Lassen Sie uns nun den analysierten Wert in HTML auf der Timeline anzeigen. Ich habe versucht, mit vis.js eine Zeitleiste zu erstellen.

static void report(List<Tuple2<String, List<Tuple2<Instant, Instant>>>> spans, Path path) throws IOException {
    var names = spans.stream().map(xs -> xs._1()).distinct().collect(Collectors.toList());

    var htmlNames = names.stream().map(x -> String.format("\"%s\"", x)).collect(Collectors.toList());
    var htmlItems = new ArrayList<String>();
    int index = 0;
    for (int i = 0; i < spans.size(); i++) {
        var s = spans.get(i);
        String msg = "{ id: %d, group: %d, content: \"\", start: \"%s\", end: \"%s\" }";
        for (int j = 0; j < s._2().size(); j++) {
            var x = s._2().get(j);
            htmlItems.add(String.format(msg, index++, names.indexOf(s._1()), x._1(), x._2()));
        }
    }

    var html = "<!DOCTYPE html>\n"
            + "<html>\n"
            + "  <head>\n"
            + "    <title>Timeline</title>\n"
            + "\n"
            + "    <style type=\"text/css\">\n"
            + "      body,\n"
            + "      html {\n"
            + "        font-family: sans-serif;\n"
            + "      }\n"
            + "    </style>\n"
            + "\n"
            + "    <script src=\"http://visjs.org/dist/vis.js\"></script>\n"
            + "    <link\n"
            + "      href=\"http://visjs.org/dist/vis-timeline-graph2d.min.css\"\n"
            + "      rel=\"stylesheet\"\n"
            + "      type=\"text/css\"\n"
            + "    />\n"
            + "  </head>\n"
            + "  <body>\n"
            + "    <p>\n"
            + "      A Simple Timeline\n"
            + "    </p>\n"
            + "\n"
            + "    <div id=\"visualization\"></div>\n"
            + "\n"
            + "    <script type=\"text/javascript\">\n"
            + "            // DOM element where the Timeline will be attached\n"
            + "            var container = document.getElementById(\"visualization\");\n"
            + "\n"
            + "            // create a data set with groups\n"
            + "            var names = [" + String.join(",", htmlNames) + "];\n"
            + "            var groups = new vis.DataSet();\n"
            + "            for (var g = 0; g < names.length; g++) {\n"
            + "              groups.add({ id: g, content: names[g] });\n"
            + "            }\n"
            + "\n"
            + "   \n"
            + "            // Create a DataSet (allows two way data-binding)\n"
            + "            var items = new vis.DataSet([" + String.join(",", htmlItems) + "]);\n"
            + "\n"
            + "            // Configuration for the Timeline\n"
            + "            function customOrder(a, b) {\n"
            + "              // order by id\n"
            + "              return a.id - b.id;\n"
            + "            }\n"
            + "\n"
            + "            // Configuration for the Timeline\n"
            + "            var options = {\n"
            + "              order: customOrder,\n"
            + "              editable: true,\n"
            + "              margin: { item: 0 }\n"
            + "            };\n"
            + "\n"
            + "            // Create a Timeline\n"
            + "            var timeline = new vis.Timeline(container);\n"
            + "            timeline.setOptions(options);\n"
            + "            timeline.setGroups(groups);\n"
            + "            timeline.setItems(items);\n"
            + "    </script>\n"
            + "  </body>\n"
            + "</html>";

    Files.writeString(path, html);
}

Führen Sie zum Abschluss den Vorgang wie unten gezeigt aus, um den Vorgang abzuschließen.

public static void main(String[] args) throws IOException {
    var methods = readMethod(Paths.get("test.jfr"));
    report(parseSpan(methods._1(), methods._2()), Paths.get("target/test.html"));
}

Klicken Sie hier für den generierten HTML-Code. Es fühlt sich so an! 002.png

Zusammenfassung

Ich habe einen Zeitleistenbericht mit der JFR-API erstellt. Ich bin der Meinung, dass Sie etwas Nützliches machen können, wenn Sie eine weitere Benutzeroberfläche entwickeln und einen Drilldown durchführen. Alternativ kann es jederzeit möglich sein, mit fluentd oder logstash zu analysieren und Profilerinformationen an Kibana usw. zu senden oder eine benutzerdefinierte API zur Unterstützung von OpenTracing zu entwickeln.

Es hat einen eingebauten Low-Load-Profiler und es ist ein Traum zu sagen, dass Sie frei damit spielen können!

Da ich den Beispielcode ziemlich grob geschrieben habe, habe ich außerdem das Gefühl, ineffiziente Dinge zu tun, aber das interessiert mich nicht. Auch lokale Inferenz ist schließlich praktisch. Es ist eine große Hilfe für Fälle, in denen diesmal Taples verwendet werden.

Dann wird nächstes Jahr Happy Hacking!

Recommended Posts

Versuchen Sie, mithilfe der JFR-API einen Zeitleistenbericht über die Ausführungszeit einer Methode zu erstellen
Versuchen Sie, einen einfachen Rückruf zu tätigen
Versuchen Sie, einen Iterator zu erstellen, der einen Blick darauf werfen kann
Versuchen Sie, mithilfe der API eine Karte von Jave an Trello auszustellen oder zu erhalten
Stellen Sie die Zeit von LocalDateTime auf eine bestimmte Zeit ein
Methode zur Erfassung von Datum und Uhrzeit mithilfe der DateAndTime-API
Ich möchte eine Methode einer anderen Klasse aufrufen
Initialisierung von for Versuchen Sie, das Java-Problem in TypeScript 5-4 zu ändern
Versuchen Sie, mit Docker eine Java-Entwicklungsumgebung zu erstellen
Befehl, um Docker vorerst zu verwenden
Versuchen Sie es mit der Methode each_with_index
[Anfänger] Versuchen Sie, mit Java ein einfaches RPG-Spiel zu erstellen ①
Ich möchte ein bestimmtes Modell von ActiveRecord ReadOnly erstellen
[swift5] Versuchen Sie, einen API-Client auf verschiedene Arten zu erstellen
Ich habe einen RESAS-API-Client in Java erstellt
[Rails] Implementierung einer mehrschichtigen Kategoriefunktion unter Verwendung der Abstammung "Ich habe versucht, ein Fenster mit Bootstrap 3 zu erstellen"