/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 com.android.ide.eclipse.ddms.systrace;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/** {@link SystraceOutputParser} receives the output of atrace command run on the device,
* parses it and generates html based on the trace */
public class SystraceOutputParser {
private static final String TRACE_START = "TRACE:\n"; //$NON-NLS-1$
private static final String HTML_PREFIX = "<!DOCTYPE HTML>\n"
+ "<html>\n"
+ "<head i18n-values=\"dir:textdirection;\">\n"
+ "<title>Android System Trace</title>\n"
+ "%s\n"
+ "%s\n"
+ "<script language=\"javascript\">\n"
+ "document.addEventListener('DOMContentLoaded', function() {\n"
+ " if (!linuxPerfData)\n"
+ " return;\n"
+ " var m = new tracing.TimelineModel(linuxPerfData);\n"
+ " var timelineViewEl = document.querySelector('.view');\n"
+ " base.ui.decorate(timelineViewEl, tracing.TimelineView);\n"
+ " timelineViewEl.model = m;\n"
+ " timelineViewEl.tabIndex = 1;\n"
+ " timelineViewEl.timeline.focusElement = timelineViewEl;\n"
+ "});\n"
+ "</script>\n"
+ "<style>\n"
+ " .view {\n"
+ " overflow: hidden;\n"
+ " position: absolute;\n"
+ " top: 0;\n"
+ " bottom: 0;\n"
+ " left: 0;\n"
+ " right: 0;\n"
+ " }\n"
+ "</style>\n"
+ "</head>\n"
+ "<body>\n"
+ " <div class=\"view\">\n"
+ " </div>\n"
+ " <script>\n"
+ " var linuxPerfData = \"\\\n";
private static final String HTML_SUFFIX =
" dummy-0000 [000] 0.0: 0: trace_event_clock_sync: parent_ts=0.0\\n\";\n"
+ " </script>\n"
+ "</body>\n"
+ "</html>\n";
private final boolean mUncompress;
private final String mJs;
private final String mCss;
private byte[] mAtraceOutput;
private int mAtraceLength;
private int mSystraceIndex = -1;
/**
* Constructs a atrace output parser.
* @param compressedStream Is the input stream compressed using zlib?
* @param systraceJs systrace javascript content
* @param systraceCss systrace css content
*/
public SystraceOutputParser(boolean compressedStream, String systraceJs, String systraceCss) {
mUncompress = compressedStream;
mJs = systraceJs;
mCss = systraceCss;
}
/**
* Parses the atrace output for systrace content.
* @param atraceOutput output bytes from atrace
*/
public void parse(byte[] atraceOutput) {
mAtraceOutput = atraceOutput;
mAtraceLength = atraceOutput.length;
removeCrLf();
// locate the trace start marker within the first hundred bytes
String header = new String(mAtraceOutput, 0, Math.min(100, mAtraceLength));
mSystraceIndex = locateSystraceData(header);
if (mSystraceIndex < 0) {
throw new RuntimeException("Unable to find trace start marker 'TRACE:':\n" + header);
}
}
/** Replaces \r\n with \n in {@link #mAtraceOutput}. */
private void removeCrLf() {
int dst = 0;
for (int src = 0; src < mAtraceLength - 1; src++, dst++) {
byte copy;
if (mAtraceOutput[src] == '\r' && mAtraceOutput[src + 1] == '\n') {
copy = '\n';
src++;
} else {
copy = mAtraceOutput[src];
}
mAtraceOutput[dst] = copy;
}
mAtraceLength = dst;
}
private int locateSystraceData(String header) {
int index = header.indexOf(TRACE_START);
if (index < 0) {
return -1;
} else {
return index + TRACE_START.length();
}
}
public String getSystraceHtml() {
if (mSystraceIndex < 0) {
return "";
}
String trace = "";
if (mUncompress) {
Inflater decompressor = new Inflater();
decompressor.setInput(mAtraceOutput, mSystraceIndex, mAtraceLength - mSystraceIndex);
byte[] buf = new byte[4096];
int n;
StringBuilder sb = new StringBuilder(1000);
try {
while ((n = decompressor.inflate(buf)) > 0) {
sb.append(new String(buf, 0, n));
}
} catch (DataFormatException e) {
throw new RuntimeException(e);
}
decompressor.end();
trace = sb.toString();
} else {
trace = new String(mAtraceOutput, mSystraceIndex, mAtraceLength - mSystraceIndex);
}
// each line should end with the characters \n\ followed by a newline
String html_out = trace.replaceAll("\n", "\\\\n\\\\\n");
String header = String.format(HTML_PREFIX, mCss, mJs);
String footer = HTML_SUFFIX;
return header + html_out + footer;
}
public static String getJs(File assetsFolder) {
try {
return String.format("<script language=\"javascript\">%s</script>",
Files.toString(new File(assetsFolder, "script.js"), Charsets.UTF_8));
} catch (IOException e) {
return "";
}
}
public static String getCss(File assetsFolder) {
try {
return String.format("<style type=\"text/css\">%s</style>",
Files.toString(new File(assetsFolder, "style.css"), Charsets.UTF_8));
} catch (IOException e) {
return "";
}
}
}