Implement zoomable html-flamegraph memap output

pull/6582/head
Jimmy Brisson 2018-04-09 16:33:32 -05:00
parent 75cb4d7512
commit bfceb1050d
3 changed files with 198 additions and 2 deletions

View File

@ -605,6 +605,9 @@ def build_project(src_paths, build_path, target, toolchain_name,
map_csv = join(build_path, name + "_map.csv")
memap_instance.generate_output('csv-ci', stats_depth, map_csv)
map_html = join(build_path, name + "_map.html")
memap_instance.generate_output('html', stats_depth, map_html)
resources.detect_duplicates(toolchain)
if report != None:

View File

@ -6,13 +6,16 @@ from __future__ import print_function, division, absolute_import
from abc import abstractmethod, ABCMeta
from sys import stdout, exit, argv
from os import sep
from os.path import basename, dirname, join, relpath, commonprefix
from os.path import (basename, dirname, join, relpath, abspath, commonprefix,
splitext)
import re
import csv
import json
from argparse import ArgumentParser
from copy import deepcopy
from prettytable import PrettyTable
from jinja2 import FileSystemLoader, StrictUndefined
from jinja2.environment import Environment
from .utils import (argparse_filestring_type, argparse_lowercase_hyphen_type,
argparse_uppercase_type)
@ -477,6 +480,9 @@ class MemapParser(object):
# Flash no associated with a module
self.misc_flash_mem = 0
# Name of the toolchain, for better headings
self.tc_name = None
def reduce_depth(self, depth):
"""
populates the short_modules attribute with a truncated module list
@ -505,7 +511,7 @@ class MemapParser(object):
self.short_modules[new_name].setdefault(section_idx, 0)
self.short_modules[new_name][section_idx] += self.modules[module_name][section_idx]
export_formats = ["json", "csv-ci", "table"]
export_formats = ["json", "csv-ci", "html", "table"]
def generate_output(self, export_format, depth, file_output=None):
""" Generates summary of memory map data
@ -531,6 +537,7 @@ class MemapParser(object):
return False
to_call = {'json': self.generate_json,
'html': self.generate_html,
'csv-ci': self.generate_csv,
'table': self.generate_table}[export_format]
output = to_call(file_desc)
@ -540,6 +547,75 @@ class MemapParser(object):
return output
@staticmethod
def _move_up_tree(tree, next_module):
tree.setdefault("children", [])
for child in tree["children"]:
if child["name"] == next_module:
return child
else:
new_module = {"name": next_module}
tree["children"].append(new_module)
return new_module
def generate_html(self, file_desc):
"""Generate a json file from a memory map for D3
Positional arguments:
file_desc - the file to write out the final report to
"""
tree_text = {"name": ".text"}
tree_bss = {"name": ".bss"}
tree_data = {"name": ".data"}
for name, dct in self.modules.items():
if ".text" not in dct:
continue
cur_text = tree_text
cur_bss = tree_bss
cur_data = tree_data
modules = name.split(sep)
while True:
cur_text.setdefault("value", 0)
cur_data.setdefault("value", 0)
cur_bss.setdefault("value", 0)
try:
cur_text["value"] += dct['.text']
except KeyError:
pass
try:
cur_bss["value"] += dct['.bss']
except KeyError:
pass
try:
cur_data["value"] += dct['.data']
except KeyError:
pass
if not modules:
break
next_module = modules.pop(0)
cur_text = self._move_up_tree(cur_text, next_module)
cur_data = self._move_up_tree(cur_data, next_module)
cur_bss = self._move_up_tree(cur_bss, next_module)
jinja_loader = FileSystemLoader(dirname(abspath(__file__)))
jinja_environment = Environment(loader=jinja_loader,
undefined=StrictUndefined)
template = jinja_environment.get_template("memap_flamegraph.html")
name, _ = splitext(basename(file_desc.name))
if name.endswith("_map"):
name = name[:-4]
if self.tc_name:
name = "%s %s" % (name, self.tc_name)
data = {
"name": name,
"text": json.dumps(tree_text),
"data": json.dumps(tree_data),
"bss": json.dumps(tree_bss),
}
file_desc.write(template.render(data))
return None
def generate_json(self, file_desc):
"""Generate a json file from a memory map
@ -655,6 +731,7 @@ class MemapParser(object):
mapfile - the file name of the memory map file
toolchain - the toolchain used to create the file
"""
self.tc_name = toolchain.title()
if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"):
parser = _ArmccParser()
elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":

116
tools/memap_flamegraph.html Normal file
View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w="
crossorigin="anonymous"
/>
<link rel="stylesheet" type="text/css"
href="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@1.0.4/dist/d3.flameGraph.min.css"
integrity="sha256-w762vSe6WGrkVZ7gEOpnn2Y+FSmAGlX77jYj7nhuCyY="
crossorigin="anonymous"
/>
<style>
/* Space out content a bit */
body {
padding-top: 20px;
padding-bottom: 20px;
}
/* Custom page header */
.header {
padding-bottom: 20px;
padding-right: 15px;
padding-left: 15px;
border-bottom: 1px solid #e5e5e5;
}
/* Make the masthead heading the same height as the navigation */
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
}
</style>
<title>{{name}} Memory Details</title>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js" integrity="sha256-4OrICDjBYfKefEbVT7wETRLNFkuq4TJV5WLGvjqpGAk=" crossorigin="anonymous"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js" integrity="sha256-g6iAfvZp+nDQ2TdTR/VVKJf3bGro4ub5fvWSWVRi2NE=" crossorigin="anonymous"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="header clearfix">
<h3 class="text-muted">{{name}} Memory Details</h3>
</div>
<div id="chart-text">
</div>
<hr/>
<div id="chart-data">
</div>
<hr/>
<div id="chart-bss">
</div>
<hr/>
</div>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"
integrity="sha256-r7j1FXNTvPzHR41+V71Jvej6fIq4v4Kzu5ee7J/RitM="
crossorigin="anonymous">
</script>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"
integrity="sha256-z0A2CQF8xxCKuOJsn4sJ5HBjxiHHRAfTX8hDF4RSN5s="
crossorigin="anonymous">
</script>
<script type="text/javascript"
src="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@1.0.4/dist/d3.flameGraph.min.js"
integrity="sha256-I1CkrWbmjv+GWjgbulJ4i0vbzdrDGfxqdye2qNlhG3Q="
crossorigin="anonymous">
</script>
<script type="text/javascript">
var tip = d3.tip()
.direction("s")
.offset([8, 0])
.attr('class', 'd3-flame-graph-tip')
.html(function(d) { return "module: " + d.data.name + ", size in bytes: " + d.data.value; });
var flameGraph_text = d3.flameGraph()
.transitionDuration(250)
.transitionEase(d3.easeCubic)
.sort(true)
.title("Code (ROM)")
.tooltip(tip);
var flameGraph_data = d3.flameGraph()
.transitionDuration(250)
.transitionEase(d3.easeCubic)
.sort(true)
.title("Non-zero initialized data (ROM + RAM)")
.tooltip(tip);
var flameGraph_bss = d3.flameGraph()
.transitionDuration(250)
.transitionEase(d3.easeCubic)
.sort(true)
.title("Zero initialized data (RAM)")
.tooltip(tip);
var text_elem = d3.select("#chart-text");
flameGraph_text.width(text_elem.node().getBoundingClientRect().width);
text_elem.datum({{text}}).call(flameGraph_text);
var data_elem = d3.select("#chart-data");
flameGraph_data.width(data_elem.node().getBoundingClientRect().width);
data_elem.datum({{data}}).call(flameGraph_data);
var bss_elem = d3.select("#chart-bss");
flameGraph_bss.width(bss_elem.node().getBoundingClientRect().width);
bss_elem.datum({{bss}}).call(flameGraph_bss);
</script>
</body>
</html>