io.perfmark.traceviewer.TraceEventViewer Maven / Gradle / Ivy
/*
* 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.PerfMark;
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.
*
* This class is separate from {@link TraceEventWriter}, because it includes a fairly large HTML
* chunk, and brings in a differently licenced piece of code.
*
*
This code is NOT 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());
public static void main(String[] args) throws Exception {
PerfMark.setEnabled(true);
PerfMark.startTask("hio");
PerfMark.attachTag("hi", 123);
PerfMark.stopTask("hio");
writeTraceHtml();
}
// 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 } 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";
}
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("");
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");
}
}