diff options
-rw-r--r-- | simpleperf/command.cpp | 9 | ||||
-rw-r--r-- | simpleperf/demo/README.md | 4 | ||||
-rw-r--r-- | simpleperf/doc/README.md | 2 | ||||
-rw-r--r-- | simpleperf/dso.cpp | 20 | ||||
-rw-r--r-- | simpleperf/dso.h | 2 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/data_types.py | 19 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/inferno.py | 36 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/script.js | 232 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/svg_renderer.py | 96 |
9 files changed, 230 insertions, 190 deletions
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp index 034163f9..0d071ab4 100644 --- a/simpleperf/command.cpp +++ b/simpleperf/command.cpp @@ -98,8 +98,15 @@ class CommandRegister { CommandRegister command_register; +static void StderrLogger(android::base::LogId, android::base::LogSeverity severity, + const char*, const char* file, unsigned int line, const char* message) { + static const char log_characters[] = "VDIWEFF"; + char severity_char = log_characters[severity]; + fprintf(stderr, "simpleperf %c %s:%u] %s\n", severity_char, file, line, message); +} + bool RunSimpleperfCmd(int argc, char** argv) { - android::base::InitLogging(argv, android::base::StderrLogger); + android::base::InitLogging(argv, StderrLogger); std::vector<std::string> args; android::base::LogSeverity log_severity = android::base::INFO; diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md index 2bba0434..2c5f2b3f 100644 --- a/simpleperf/demo/README.md +++ b/simpleperf/demo/README.md @@ -10,8 +10,8 @@ ## Introduction Simpleperf is a native profiler used on Android platform. It can be used to profile Android -applications. It's document is at [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md). -Instructions of preparing your Android application for profiling are [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md#Android-application-profiling). +applications. It's document is at [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md). +Instructions of preparing your Android application for profiling are [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md#Android-application-profiling). This directory is to show examples of using simpleperf to profile Android applications. The meaning of each directory is as below: diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md index 078d56b3..0b8d92a4 100644 --- a/simpleperf/doc/README.md +++ b/simpleperf/doc/README.md @@ -6,7 +6,7 @@ profile both Java and C++ code on Android. It can be used on Android L and above. Simpleperf is part of the Android Open Source Project. The source code is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/). -The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md). +The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md). Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/issues. diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp index 4f202bcd..90806406 100644 --- a/simpleperf/dso.cpp +++ b/simpleperf/dso.cpp @@ -153,7 +153,8 @@ Dso::Dso(DsoType type, const std::string& path, bool force_64bit) min_vaddr_(std::numeric_limits<uint64_t>::max()), is_loaded_(false), dump_id_(UINT_MAX), - symbol_dump_id_(0) { + symbol_dump_id_(0), + symbol_warning_loglevel_(android::base::WARNING) { if (type_ == DSO_KERNEL) { min_vaddr_ = 0; } @@ -287,6 +288,8 @@ void Dso::Load() { // dumped_symbols, so later we can merge them with symbols read from file system. dumped_symbols = std::move(symbols_); symbols_.clear(); + // Don't warn missing symbol table if we have dumped symbols in perf.data. + symbol_warning_loglevel_ = android::base::DEBUG; } bool result = false; switch (type_) { @@ -354,11 +357,10 @@ bool Dso::CheckReadSymbolResult(ElfStatus result, const std::string& filename) { return true; } // Lacking symbol table isn't considered as an error but worth reporting. - LOG(WARNING) << filename << " doesn't contain symbol table"; + LOG(symbol_warning_loglevel_) << filename << " doesn't contain symbol table"; return true; } else { - LOG(WARNING) << "failed to read symbols from " << filename - << ": " << result; + LOG(symbol_warning_loglevel_) << "failed to read symbols from " << filename << ": " << result; return false; } } @@ -380,7 +382,7 @@ bool Dso::LoadKernel() { } } if (all_zero) { - LOG(WARNING) + LOG(symbol_warning_loglevel_) << "Symbol addresses in /proc/kallsyms on device are all zero. " "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible."; symbols_.clear(); @@ -396,8 +398,8 @@ bool Dso::LoadKernel() { } bool match = (build_id == real_build_id); if (!match) { - LOG(WARNING) << "failed to read symbols from /proc/kallsyms: Build id " - << "mismatch"; + LOG(symbol_warning_loglevel_) << "failed to read symbols from /proc/kallsyms: Build id " + << "mismatch"; return false; } } @@ -417,8 +419,8 @@ bool Dso::LoadKernel() { } } if (all_zero) { - LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. " - "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible."; + LOG(symbol_warning_loglevel_) << "Symbol addresses in /proc/kallsyms are all zero. " + "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible."; symbols_.clear(); return false; } diff --git a/simpleperf/dso.h b/simpleperf/dso.h index f41b1404..cb0e51d7 100644 --- a/simpleperf/dso.h +++ b/simpleperf/dso.h @@ -22,6 +22,7 @@ #include <unordered_map> #include <vector> +#include <android-base/logging.h> #include <android-base/test_utils.h> #include "build_id.h" @@ -182,6 +183,7 @@ class Dso { uint32_t dump_id_; // Used to assign dump_id for symbols in current dso. uint32_t symbol_dump_id_; + android::base::LogSeverity symbol_warning_loglevel_; }; const char* DsoTypeToString(DsoType dso_type); diff --git a/simpleperf/scripts/inferno/data_types.py b/simpleperf/scripts/inferno/data_types.py index 5341d239..80e633f4 100644 --- a/simpleperf/scripts/inferno/data_types.py +++ b/simpleperf/scripts/inferno/data_types.py @@ -14,22 +14,23 @@ # limitations under the License. # + class CallSite: + def __init__(self, ip, method, dso): self.ip = ip self.method = method self.dso = dso - class Thread: + def __init__(self, tid): self.tid = tid self.samples = [] self.flamegraph = {} self.num_samples = 0 - def add_callchain(self, callchain, symbol, sample): chain = [] self.num_samples += 1 @@ -42,15 +43,14 @@ class Thread: chain.append(CallSite(sample.ip, symbol.symbol_name, symbol.dso_name)) self.samples.append(chain) - def collapse_flamegraph(self): flamegraph = FlameGraphCallSite("root", "") - flamegraph.id = 0 # This is used for wasd navigation, 0 = not a valid target. + flamegraph.id = 0 # This is used for wasd navigation, 0 = not a valid target. self.flamegraph = flamegraph for sample in self.samples: flamegraph = self.flamegraph for callsite in sample: - flamegraph = flamegraph.get_callsite(callsite.method, callsite.dso) + flamegraph = flamegraph.get_callsite(callsite.method, callsite.dso) # Populate root note. for node in self.flamegraph.callsites: @@ -58,6 +58,7 @@ class Thread: class Process: + def __init__(self, name, pid): self.name = name self.pid = pid @@ -73,6 +74,8 @@ class Process: return self.threads[tid] CALLSITE_COUNTER = 0 + + def get_callsite_id(): global CALLSITE_COUNTER CALLSITE_COUNTER += 1 @@ -87,10 +90,9 @@ class FlameGraphCallSite: self.method = method self.dso = dso self.num_samples = 0 - self.offset = 0 # Offset allows position nodes in different branches. + self.offset = 0 # Offset allows position nodes in different branches. self.id = get_callsite_id() - def get_callsite(self, name, dso): for c in self.callsites: if c.equivalent(name, dso): @@ -104,11 +106,10 @@ class FlameGraphCallSite: def equivalent(self, method, dso): return self.method == method and self.dso == dso - def get_max_depth(self): max = 0 for c in self.callsites: depth = c.get_max_depth() if depth > max: max = depth - return max +1
\ No newline at end of file + return max + 1 diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py index 7510cb25..0b592965 100644 --- a/simpleperf/scripts/inferno/inferno.py +++ b/simpleperf/scripts/inferno/inferno.py @@ -46,6 +46,7 @@ except: from data_types import * from svg_renderer import * + def collect_data(args, process): app_profiler_args = [sys.executable, "app_profiler.py", "-nb"] if args.app: @@ -158,20 +159,23 @@ def output_report(process): Threads : %d <br/> Samples : %d</br> Duration: %s seconds<br/>""" % ( - process.name,process.pid, - datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"), - len(process.threads), - process.num_samples, - process.args.capture_duration)) + process.name, process.pid, + datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"), + len(process.threads), + process.num_samples, + process.args.capture_duration)) if 'ro.product.model' in process.props: - f.write("Machine : %s (%s) by %s<br/>" % (process.props["ro.product.model"], - process.props["ro.product.name"], process.props["ro.product.manufacturer"])) + f.write( + "Machine : %s (%s) by %s<br/>" % + (process.props["ro.product.model"], + process.props["ro.product.name"], + process.props["ro.product.manufacturer"])) if process.cmd: f.write("Capture : %s<br/><br/>" % process.cmd) f.write("</div>") f.write("""<br/><br/> <div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>""") - f.write(get_local_asset_content("script.js")) + f.write("<script>%s</script>" % get_local_asset_content("script.js")) # Output tid == pid Thread first. main_thread = [x for _, x in process.threads.items() if x.tid == process.pid] @@ -191,6 +195,7 @@ def output_report(process): f.close() return "file://" + filepath + def generate_flamegraph_offsets(flamegraph): rover = flamegraph.offset for callsite in flamegraph.callsites: @@ -217,12 +222,11 @@ def open_report_in_browser(report_path): browser_key = "" for key, value in webbrowser._browsers.items(): if key.find("chrome") != -1: - browser_key = key + browser_key = key browser = webbrowser.get(browser_key) browser.open(report_path, new=0, autoraise=True) - def main(): parser = argparse.ArgumentParser(description='Report samples in perf.data.') @@ -240,7 +244,7 @@ def main(): parser.add_argument('-c', '--color', default='hot', choices=['hot', 'dso', 'legacy'], help="""Color theme: hot=percentage of samples, dso=callsite DSO name, legacy=brendan style""") - parser.add_argument('-sc','--skip_collection', default=False, help='Skip data collection', + parser.add_argument('-sc', '--skip_collection', default=False, help='Skip data collection', action="store_true") parser.add_argument('-nc', '--skip_recompile', action='store_true', help="""When profiling an Android app, by default we recompile java bytecode to native @@ -248,8 +252,12 @@ def main(): if the code has been compiled or you don't need to profile java code.""") parser.add_argument('-f', '--sample_frequency', type=int, default=6000, help='Sample frequency') parser.add_argument('-w', '--svg_width', type=int, default=1124) - parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.', - default=False, action='store_true') + parser.add_argument( + '-du', + '--dwarf_unwinding', + help='Perform unwinding using dwarf instead of fp.', + default=False, + action='store_true') parser.add_argument('-e', '--events', help="""Sample based on event occurences instead of frequency. Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of event_name: cpu-cycles, @@ -283,4 +291,4 @@ def main(): log_info("Report generated at '%s'." % report_path) if __name__ == "__main__": - main()
\ No newline at end of file + main() diff --git a/simpleperf/scripts/inferno/script.js b/simpleperf/scripts/inferno/script.js index 6a81d731..3288d8be 100644 --- a/simpleperf/scripts/inferno/script.js +++ b/simpleperf/scripts/inferno/script.js @@ -1,146 +1,141 @@ -<script type="text/ecmascript"> +'use strict'; + function init() { - var x = document.getElementsByTagName("svg") - for (i = 0; i < x.length; i=i+1) { + let x = document.getElementsByTagName('svg'); + for (let i = 0; i < x.length; i++) { createZoomHistoryStack(x[i]); } } // Create a stack add the root svg element in it. function createZoomHistoryStack(svgElement) { - stack = []; - svgElement.zoomStack = stack; - stack.push(svgElement.getElementById(svgElement.attributes["rootid"].value)) + svgElement.zoomStack = [svgElement.getElementById(svgElement.attributes['rootid'].value)]; } function dumpStack(svgElement) { // Disable (enable for debugging) - return + return; stack = svgElement.zoomStack; - for (i=0 ; i < stack.length; i++) { - title = stack[i].getElementsByTagName("title")[0]; - console.log("[" +i+ "]-" + title.textContent) + for (i=0; i < stack.length; i++) { + let title = stack[i].getElementsByTagName('title')[0]; + console.log('[' +i+ ']-' + title.textContent); } } function adjust_node_text_size(x) { - title = x.getElementsByTagName("title")[0]; - text = x.getElementsByTagName("text")[0]; - rect = x.getElementsByTagName("rect")[0]; + let title = x.getElementsByTagName('title')[0]; + let text = x.getElementsByTagName('text')[0]; + let rect = x.getElementsByTagName('rect')[0]; - width = parseFloat(rect.attributes["width"].value); + let width = parseFloat(rect.attributes['width'].value); // Don't even bother trying to find a best fit. The area is too small. if (width < 25) { - text.textContent = ""; + text.textContent = ''; return; } // Remove dso and #samples which are here only for mouseover purposes. - methodName = title.textContent.substring(0, title.textContent.indexOf("|")); + let methodName = title.textContent.split(' | ')[0]; - var numCharacters; - for (numCharacters=methodName.length; numCharacters>4; numCharacters--) { + let numCharacters; + for (numCharacters = methodName.length; numCharacters > 4; numCharacters--) { // Avoid reflow by using hard-coded estimate instead of text.getSubStringLength(0, numCharacters) // if (text.getSubStringLength(0, numCharacters) <= width) { if (numCharacters * 7.5 <= width) { - break ; + break; } } if (numCharacters == methodName.length) { text.textContent = methodName; - return + return; } - text.textContent = methodName.substring(0, numCharacters-2) + ".."; + text.textContent = methodName.substring(0, numCharacters-2) + '..'; } function adjust_text_size(svgElement) { - var x = svgElement.getElementsByTagName("g"); - var i; - for (i=0 ; i < x.length ; i=i+1) { - adjust_node_text_size(x[i]) + let x = svgElement.getElementsByTagName('g'); + for (let i = 0; i < x.length; i++) { + adjust_node_text_size(x[i]); } } function zoom(e) { - svgElement = e.ownerSVGElement - zoomStack = svgElement.zoomStack; + let svgElement = e.ownerSVGElement; + let zoomStack = svgElement.zoomStack; zoomStack.push(e); - displayFromElement(e) + displayFromElement(e); select(e); - dumpStack(e.ownerSVGElement); + dumpStack(svgElement); // Show zoom out button. - svgElement.getElementById("zoom_rect").style.display = "block"; - svgElement.getElementById("zoom_text").style.display = "block"; + svgElement.getElementById('zoom_rect').style.display = 'block'; + svgElement.getElementById('zoom_text').style.display = 'block'; } function displayFromElement(e) { - var clicked_rect = e.getElementsByTagName("rect")[0]; - var clicked_origin_x = clicked_rect.attributes["ox"].value; - var clicked_origin_y = clicked_rect.attributes["oy"].value; - var clicked_origin_width = clicked_rect.attributes["owidth"].value; + let clicked_rect = e.getElementsByTagName('rect')[0]; + let clicked_origin_x = clicked_rect.attributes['ox'].value; + let clicked_origin_y = clicked_rect.attributes['oy'].value; + let clicked_origin_width = clicked_rect.attributes['owidth'].value; - var svgBox = e.ownerSVGElement.getBoundingClientRect(); - var svgBoxHeight = svgBox.height - var svgBoxWidth = svgBox.width - var scaleFactor = svgBoxWidth/clicked_origin_width; + let svgBox = e.ownerSVGElement.getBoundingClientRect(); + let svgBoxHeight = svgBox.height; + let svgBoxWidth = svgBox.width; + let scaleFactor = svgBoxWidth / clicked_origin_width; - var callsites = e.ownerSVGElement.getElementsByTagName("g"); - var i; - for (i = 0; i < callsites.length; i=i+1) { - text = callsites[i].getElementsByTagName("text")[0]; - rect = callsites[i].getElementsByTagName("rect")[0]; + let callsites = e.ownerSVGElement.getElementsByTagName('g'); + for (let i = 0; i < callsites.length; i++) { + let text = callsites[i].getElementsByTagName('text')[0]; + let rect = callsites[i].getElementsByTagName('rect')[0]; - rect_o_x = rect.attributes["ox"].value - rect_o_y = parseFloat(rect.attributes["oy"].value) + let rect_o_x = parseFloat(rect.attributes['ox'].value); + let rect_o_y = parseFloat(rect.attributes['oy'].value); // Avoid multiple forced reflow by hiding nodes. if (rect_o_y > clicked_origin_y) { - rect.style.display = "none" - text.style.display = "none" - continue; - } else { - rect.style.display = "block" - text.style.display = "block" + rect.style.display = 'none'; + text.style.display = 'none'; + continue; } + rect.style.display = 'block'; + text.style.display = 'block'; - rect.attributes["x"].value = newrec_x = (rect_o_x - clicked_origin_x) * scaleFactor ; - rect.attributes["y"].value = newrec_y = rect_o_y + (svgBoxHeight - clicked_origin_y - 17 -2); + let newrec_x = rect.attributes['x'].value = (rect_o_x - clicked_origin_x) * scaleFactor; + let newrec_y = rect.attributes['y'].value = rect_o_y + (svgBoxHeight - clicked_origin_y + - 17 - 2); - text.attributes["y"].value = newrec_y + 12; - text.attributes["x"].value = newrec_x + 4; + text.attributes['y'].value = newrec_y + 12; + text.attributes['x'].value = newrec_x + 4; - rect.attributes["width"].value = rect.attributes["owidth"].value * scaleFactor; + rect.attributes['width'].value = rect.attributes['owidth'].value * scaleFactor; } adjust_text_size(e.ownerSVGElement); - } function unzoom(e) { - - var svgOwner = e.ownerSVGElement; - stack = svgOwner.zoomStack; + let svgOwner = e.ownerSVGElement; + let stack = svgOwner.zoomStack; // Unhighlight whatever was selected. - if (selected != null) - selected.classList.remove("s") - + if (selected) { + selected.classList.remove('s'); + } // Stack management: Never remove the last element which is the flamegraph root. if (stack.length > 1) { - previouslySelected = stack.pop(); + let previouslySelected = stack.pop(); select(previouslySelected); } - nextElement = stack[stack.length-1] // stack.peek() + let nextElement = stack[stack.length-1]; // Hide zoom out button. - if (stack.length==1) { - svgOwner.getElementById("zoom_rect").style.display = "none"; - svgOwner.getElementById("zoom_text").style.display = "none"; + if (stack.length == 1) { + svgOwner.getElementById('zoom_rect').style.display = 'none'; + svgOwner.getElementById('zoom_text').style.display = 'none'; } displayFromElement(nextElement); @@ -148,97 +143,96 @@ function unzoom(e) { } function search(e) { - var term = prompt("Search for:", ""); - - var svgOwner = e.ownerSVGElement - var callsites = e.ownerSVGElement.getElementsByTagName("g"); - - if (term == null || term == "") { - for (i = 0; i < callsites.length; i=i+1) { - rect = callsites[i].getElementsByTagName("rect")[0]; - rect.attributes["fill"].value = rect.attributes["ofill"].value; + let term = prompt('Search for:', ''); + let svgOwner = e.ownerSVGElement; + let callsites = e.ownerSVGElement.getElementsByTagName('g'); + + if (!term) { + for (let i = 0; i < callsites.length; i++) { + let rect = callsites[i].getElementsByTagName('rect')[0]; + rect.attributes['fill'].value = rect.attributes['ofill'].value; } return; } - for (i = 0; i < callsites.length; i=i+1) { - title = callsites[i].getElementsByTagName("title")[0]; - rect = callsites[i].getElementsByTagName("rect")[0]; + for (let i = 0; i < callsites.length; i++) { + let title = callsites[i].getElementsByTagName('title')[0]; + let rect = callsites[i].getElementsByTagName('rect')[0]; if (title.textContent.indexOf(term) != -1) { - rect.attributes["fill"].value = "rgb(230,100,230)"; + rect.attributes['fill'].value = 'rgb(230,100,230)'; } else { - rect.attributes["fill"].value = rect.attributes["ofill"].value; + rect.attributes['fill'].value = rect.attributes['ofill'].value; } } } -var selected; +let selected; document.onkeydown = function handle_keyboard_input(e) { - if (selected == null) + if (!selected) { return; + } - title = selected.getElementsByTagName("title")[0]; - nav = selected.attributes["nav"].value.split(",") - navigation_index = -1 + let title = selected.getElementsByTagName('title')[0]; + let nav = selected.attributes['nav'].value.split(','); + let navigation_index; switch (e.keyCode) { - //case 38: // ARROW UP - case 87 : navigation_index = 0;break; //W + // case 38: // ARROW UP + case 87: navigation_index = 0; break; // W - //case 32 : // ARROW LEFT - case 65 : navigation_index = 1;break; //A + // case 32 : // ARROW LEFT + case 65: navigation_index = 1; break; // A // case 43: // ARROW DOWN - case 68 : navigation_index = 3;break; // S + case 68: navigation_index = 3; break; // S // case 39: // ARROW RIGHT - case 83 : navigation_index = 2;break; // D + case 83: navigation_index = 2; break; // D - case 32 : zoom(selected); return false; break; // SPACE + case 32: zoom(selected); return false; // SPACE case 8: // BACKSPACE unzoom(selected); return false; default: return true; } - if (nav[navigation_index] == "0") + if (nav[navigation_index] == '0') { return false; + } - target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]); - select(target_element) + let target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]); + select(target_element); return false; -} +}; function select(e) { - if (selected != null) - selected.classList.remove("s") + if (selected) { + selected.classList.remove('s'); + } selected = e; - selected.classList.add("s") + selected.classList.add('s'); // Update info bar - titleElement = selected.getElementsByTagName("title")[0]; - text = titleElement.textContent; + let titleElement = selected.getElementsByTagName('title')[0]; + let text = titleElement.textContent; // Parse title - method_and_info = text.split(" | "); - methodName = method_and_info[0]; - info = method_and_info[1] + let method_and_info = text.split(' | '); + let methodName = method_and_info[0]; + let info = method_and_info[1]; // Parse info // '/system/lib64/libhwbinder.so (4 samples: 0.28%)' - var regexp = /(.*) \(.* ([0-9**\.[0-9]*%)\)/g; - match = regexp.exec(info); + let regexp = /(.*) \(.* ([0-9**\.[0-9]*%)\)/g; + let match = regexp.exec(info); if (match.length > 2) { - percentage = match[2] + let percentage = match[2]; // Write percentage - percentageTextElement = selected.ownerSVGElement.getElementById("percent_text") - percentageTextElement.textContent = percentage - //console.log("'" + percentage + "'") + let percentageTextElement = selected.ownerSVGElement.getElementById('percent_text'); + percentageTextElement.textContent = percentage; + // console.log("'" + percentage + "'") } // Set fields - barTextElement = selected.ownerSVGElement.getElementById("info_text") - barTextElement.textContent = methodName -} - - -</script>
\ No newline at end of file + let barTextElement = selected.ownerSVGElement.getElementById('info_text'); + barTextElement.textContent = methodName; +}
\ No newline at end of file diff --git a/simpleperf/scripts/inferno/svg_renderer.py b/simpleperf/scripts/inferno/svg_renderer.py index 00f2d759..68559499 100644 --- a/simpleperf/scripts/inferno/svg_renderer.py +++ b/simpleperf/scripts/inferno/svg_renderer.py @@ -22,36 +22,37 @@ FONT_SIZE = 12 def hash_to_float(string): - return hash(string) / float(sys.maxint) + return hash(string) / float(sys.maxsize) -def getLegacyColor(method) : + +def getLegacyColor(method): r = 175 + int(50 * hash_to_float(reversed(method))) g = 60 + int(180 * hash_to_float(method)) - b = 60 +int(55 * hash_to_float(reversed(method))) - return (r,g,b) + b = 60 + int(55 * hash_to_float(reversed(method))) + return (r, g, b) -def getDSOColor(method) : +def getDSOColor(method): r = 170 + int(80 * hash_to_float(reversed(method))) - g = 180 +int(70 * hash_to_float((method))) + g = 180 + int(70 * hash_to_float((method))) b = 170 + int(80 * hash_to_float(reversed(method))) - return (r,g,b) + return (r, g, b) -def getHeatColor(callsite, num_samples) : - r = 245 + 10* (1- float(callsite.num_samples)/ num_samples) - g = 110 + 105* (1-float(callsite.num_samples)/ num_samples) +def getHeatColor(callsite, num_samples): + r = 245 + 10 * (1 - float(callsite.num_samples) / num_samples) + g = 110 + 105 * (1 - float(callsite.num_samples) / num_samples) b = 100 - return (r,g,b) + return (r, g, b) def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav): - x = float(callsite.offset)/float(num_samples)*SVG_CANVAS_WIDTH + x = float(callsite.offset) / float(num_samples) * SVG_CANVAS_WIDTH y = height - (depth * SVG_NODE_HEIGHT) - SVG_NODE_HEIGHT - width = float(callsite.num_samples) /float(num_samples) * SVG_CANVAS_WIDTH + width = float(callsite.num_samples) / float(num_samples) * SVG_CANVAS_WIDTH method = callsite.method.replace(">", ">").replace("<", "<") - if (width <= 0) : + if (width <= 0): return if color_scheme == "dso": @@ -61,8 +62,6 @@ def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav): else: r, g, b = getHeatColor(callsite, num_samples) - - r_border = (r - 50) if r_border < 0: r_border = 0 @@ -76,15 +75,41 @@ def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav): b_border = 0 f.write( - '<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s"> \n\ + '<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s"> \n\ <title>%s | %s (%d samples: %3.2f%%)</title>\n \ <rect x="%f" y="%f" ox="%f" oy="%f" width="%f" owidth="%f" height="15.0" ofill="rgb(%d,%d,%d)" \ fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/>\n \ <text x="%f" y="%f" font-size="%d" font-family="Monospace"></text>\n \ - </g>\n' % (callsite.id, ','.join(str(x) for x in nav), - method, callsite.dso, callsite.num_samples, callsite.num_samples/float(num_samples) * 100, - x, y, x, y, width , width, r, g, b, r, g, b, r_border, g_border, b_border, - x+2, y+12, FONT_SIZE)) + </g>\n' % + (callsite.id, + ','.join( + str(x) for x in nav), + method, + callsite.dso, + callsite.num_samples, + callsite.num_samples / + float(num_samples) * + 100, + x, + y, + x, + y, + width, + width, + r, + g, + b, + r, + g, + b, + r_border, + g_border, + b_border, + x + + 2, + y + + 12, + FONT_SIZE)) def renderSVGNodes(flamegraph, depth, f, num_samples, height, color_scheme): @@ -94,13 +119,12 @@ def renderSVGNodes(flamegraph, depth, f, num_samples, height, color_scheme): if i == 0: left_index = 0 else: - left_index = flamegraph.callsites[i-1].id + left_index = flamegraph.callsites[i - 1].id - if i == len(flamegraph.callsites)-1: + if i == len(flamegraph.callsites) - 1: right_index = 0 else: - right_index = flamegraph.callsites[i+1].id - + right_index = flamegraph.callsites[i + 1].id up_index = 0 max_up = 0 @@ -110,19 +134,19 @@ def renderSVGNodes(flamegraph, depth, f, num_samples, height, color_scheme): up_index = upcallsite.id # up, left, down, right - nav = [up_index, left_index,flamegraph.id,right_index] + nav = [up_index, left_index, flamegraph.id, right_index] createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav) # Recurse down - renderSVGNodes(callsite, depth+1, f, num_samples, height, color_scheme) + renderSVGNodes(callsite, depth + 1, f, num_samples, height, color_scheme) + def renderSearchNode(f): f.write( - '<rect id="search_rect" style="stroke:rgb(0,0,0);" onclick="search(this);" class="t" rx="10" ry="10" \ + '<rect id="search_rect" style="stroke:rgb(0,0,0);" onclick="search(this);" class="t" rx="10" ry="10" \ x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/> \ - <text id="search_text" class="t" x="%d" y="30" onclick="search(this);">Search</text>\n' - % (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80) - ) + <text id="search_text" class="t" x="%d" y="30" onclick="search(this);">Search</text>\n' % + (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80)) def renderUnzoomNode(f): @@ -133,6 +157,7 @@ def renderUnzoomNode(f): onclick="unzoom(this);">Zoom out</text>\n' ) + def renderInfoNode(f): f.write( '<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);" \ @@ -142,28 +167,29 @@ def renderInfoNode(f): <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text>\n' % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325) ) + def renderPercentNode(f): f.write( '<rect id="percent_rect" style="stroke:rgb(0,0,0);" \ rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/> \ - <text id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>\n' % (SVG_CANVAS_WIDTH - (95 * 2),SVG_CANVAS_WIDTH - (125)) + <text id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>\n' % (SVG_CANVAS_WIDTH - (95 * 2), SVG_CANVAS_WIDTH - (125)) ) def renderSVG(flamegraph, f, color_scheme, width): global SVG_CANVAS_WIDTH SVG_CANVAS_WIDTH = width - height = (flamegraph.get_max_depth() + 2 )* SVG_NODE_HEIGHT + height = (flamegraph.get_max_depth() + 2) * SVG_NODE_HEIGHT f.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" \ width="%d" height="%d" style="border: 1px solid black;" \ onload="adjust_text_size(this);" rootid="%d">\n' % (SVG_CANVAS_WIDTH, height, flamegraph.callsites[0].id)) f.write('<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" > \ <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" /> </linearGradient> </defs>') - f.write('<rect x="0.0" y="0" width="%d" height="%d" fill="url(#background_gradiant)" />' % \ + f.write('<rect x="0.0" y="0" width="%d" height="%d" fill="url(#background_gradiant)" />' % (SVG_CANVAS_WIDTH, height)) renderSVGNodes(flamegraph, 0, f, flamegraph.num_samples, height, color_scheme) renderSearchNode(f) renderUnzoomNode(f) renderInfoNode(f) renderPercentNode(f) - f.write("</svg><br/>\n\n")
\ No newline at end of file + f.write("</svg><br/>\n\n") |