aboutsummaryrefslogtreecommitdiff
path: root/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java
diff options
context:
space:
mode:
Diffstat (limited to 'traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java')
-rw-r--r--traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java226
1 files changed, 226 insertions, 0 deletions
diff --git a/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java b/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java
new file mode 100644
index 0000000..e37b9b4
--- /dev/null
+++ b/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed 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 io.perfmark.traceviewer;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.perfmark.tracewriter.TraceEventWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * This class converts from the Trace Event json data into a full HTML page. It includes the trace
+ * viewer from Catapult, the Chromium trace UI.
+ *
+ * <p>This class is separate from {@link TraceEventWriter}, because it includes a fairly large HTML
+ * chunk, and brings in a differently licenced piece of code.
+ *
+ * <p>This code is <strong>NOT</strong> API stable, and may be removed in the future, or changed
+ * without notice.
+ *
+ * @since 0.17.0
+ */
+public final class TraceEventViewer {
+ private static final Logger logger = Logger.getLogger(TraceEventViewer.class.getName());
+
+ // Copied from trace2html.html in the Catapult tracing code.
+ private static final String INLINE_TRACE_DATA =
+ ""
+ + " const traces = [];\n"
+ + " const viewerDataScripts = Polymer.dom(document).querySelectorAll(\n"
+ + " '#viewer-data');\n"
+ + " for (let i = 0; i < viewerDataScripts.length; i++) {\n"
+ + " let text = Polymer.dom(viewerDataScripts[i]).textContent;\n"
+ + " // Trim leading newlines off the text. They happen during writing.\n"
+ + " while (text[0] === '\\n') {\n"
+ + " text = text.substring(1);\n"
+ + " }\n"
+ + " onResult(tr.b.Base64.atob(text));\n"
+ + " viewer.updateDocumentFavicon();\n"
+ + " viewer.globalMode = true;\n"
+ + " viewer.viewTitle = document.title;\n"
+ + " break;\n"
+ + " }\n";
+
+ /**
+ * A convenience function around {@link #writeTraceHtml(Writer)}. This writes the trace data to a
+ * temporary file and logs the output location.
+ *
+ * @return the Path of the written file.
+ * @throws IOException if it can't write to the destination.
+ */
+ @CanIgnoreReturnValue
+ public static Path writeTraceHtml() throws IOException {
+ Path path = Files.createTempFile("perfmark-trace-", ".html");
+ try (OutputStream os = Files.newOutputStream(path, TRUNCATE_EXISTING);
+ Writer w = new OutputStreamWriter(os, UTF_8)) {
+ writeTraceHtml(w);
+ }
+ logger.log(Level.INFO, "Wrote PerfMark Trace file://{0}", new Object[] {path.toAbsolutePath()});
+ return path;
+ }
+
+ /**
+ * Writes all available trace data as a single HTML file into the given writer.
+ *
+ * @param writer The destination to write all HTML to.
+ * @throws IOException if it can't write to the writer.
+ */
+ public static void writeTraceHtml(Writer writer) throws IOException {
+ InputStream indexStream =
+ TraceEventViewer.class.getResourceAsStream("third_party/catapult/index.html");
+ if (indexStream == null) {
+ throw new IOException("unable to find index.html");
+ }
+ String index = readAll(indexStream);
+
+ InputStream webComponentsStream =
+ TraceEventViewer.class.getResourceAsStream("third_party/polymer/webcomponents.min.js");
+ if (webComponentsStream == null) {
+ throw new IOException("unable to find webcomponents.min.js");
+ }
+ String webComponents = readAll(webComponentsStream);
+
+ InputStream traceViewerStream =
+ TraceEventViewer.class.getResourceAsStream("third_party/catapult/trace_viewer_full.html");
+ if (traceViewerStream == null) {
+ throw new IOException("unable to find trace_viewer_full.html");
+ }
+ String traceViewer = trimTraceViewer(readAll(traceViewerStream));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (OutputStreamWriter w = new OutputStreamWriter(baos, UTF_8)) {
+ TraceEventWriter.writeTraceEvents(w);
+ }
+ byte[] traceData64 = Base64.getEncoder().encode(baos.toByteArray());
+
+ String indexWithWebComponents = replaceIndexWebComponents(index, webComponents);
+
+ String indexWithTraceViewer =
+ replaceIndexTraceImport(
+ indexWithWebComponents, traceViewer, new String(traceData64, UTF_8));
+
+ String fullIndex = replaceIndexTraceData(indexWithTraceViewer, INLINE_TRACE_DATA);
+ writer.write(fullIndex);
+ writer.flush();
+ }
+
+ /**
+ * Replaces the normal {@code <link>} tag in index.html with a custom replacement, and optionally
+ * the inlined Trace data as a base64 script. This is because the trace2html.html file imports the
+ * data as a top level text/plain script.
+ */
+ private static String replaceIndexTraceImport(
+ String index, String replacement, @Nullable String inlineTraceData64) {
+ int start = index.indexOf("IO_PERFMARK_TRACE_IMPORT");
+ if (start == -1) {
+ throw new IllegalArgumentException("index doesn't contain IO_PERFMARK_TRACE_IMPORT");
+ }
+ int line0pos = index.lastIndexOf('\n', start);
+ assert line0pos != -1;
+ int line1pos = index.indexOf('\n', line0pos + 1);
+ assert line1pos != -1;
+ int line2pos = index.indexOf('\n', line1pos + 1);
+ assert line2pos != -1;
+ int line3pos = index.indexOf('\n', line2pos + 1);
+ assert line3pos != -1;
+ String inlineTraceData = "";
+ if (inlineTraceData64 != null) {
+ inlineTraceData =
+ "\n<script id=\"viewer-data\" type=\"text/plain\">" + inlineTraceData64 + "</script>";
+ }
+ return index.substring(0, line0pos + 1)
+ + replacement
+ + inlineTraceData
+ + index.substring(line3pos);
+ }
+
+ private static String replaceIndexWebComponents(String index, String replacement) {
+ int start = index.indexOf("IO_PERFMARK_WEBCOMPONENTS");
+ if (start == -1) {
+ throw new IllegalArgumentException("index doesn't contain IO_PERFMARK_WEBCOMPONENTS");
+ }
+ int line0pos = index.lastIndexOf('\n', start);
+ assert line0pos != -1;
+ int line1pos = index.indexOf('\n', line0pos + 1);
+ assert line1pos != -1;
+
+ return index.substring(0, line0pos + 1) + replacement + index.substring(line1pos);
+ }
+
+ private static String replaceIndexTraceData(String index, String replacement) {
+ int start = index.indexOf("IO_PERFMARK_TRACE_URL");
+ if (start == -1) {
+ throw new IllegalArgumentException("index doesn't contain IO_PERFMARK_TRACE_URL");
+ }
+ int line0pos = index.lastIndexOf('\n', start);
+ assert line0pos != -1;
+ int line1pos = index.indexOf('\n', line0pos + 1);
+ assert line1pos != -1;
+ int line2pos = index.indexOf('\n', line1pos + 1);
+ assert line2pos != -1;
+ int line3pos = index.indexOf('\n', line2pos + 1);
+ assert line3pos != -1;
+ return index.substring(0, line0pos + 1) + replacement + index.substring(line3pos);
+ }
+
+ private static String trimTraceViewer(String traceViewer) {
+ int startpos = traceViewer.indexOf("<template");
+ int lastpos = traceViewer.lastIndexOf("</head>");
+ return traceViewer.substring(startpos, lastpos);
+ }
+
+ private static String readAll(InputStream stream) throws IOException {
+ int available = stream.available();
+ byte[] data;
+ if (available > 0) {
+ data = new byte[available + 1];
+ } else {
+ data = new byte[4096];
+ }
+ int pos = 0;
+ while (true) {
+ int read = stream.read(data, pos, data.length - pos);
+ if (read == -1) {
+ break;
+ } else {
+ pos += read;
+ }
+ if (pos == data.length) {
+ data = Arrays.copyOf(data, data.length + (data.length >> 2));
+ }
+ }
+ return new String(data, 0, pos, UTF_8);
+ }
+
+ private TraceEventViewer() {
+ throw new AssertionError("nope");
+ }
+}