Merge pull request #6582 from theotherjimmy/memap-flamegraph

Implement zoomable html-flamegraph memap output
pull/6969/merge
Cruz Monrreal 2018-06-04 10:48:08 -05:00 committed by GitHub
commit 78d9c4f330
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 190 additions and 2 deletions

View File

@ -613,6 +613,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,80 @@ 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, "value": 0}
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", "value": 0}
tree_bss = {"name": ".bss", "value": 0}
tree_data = {"name": ".data", "value": 0}
for name, dct in self.modules.items():
cur_text = tree_text
cur_bss = tree_bss
cur_data = tree_data
modules = name.split(sep)
while True:
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)
tree_rom = {
"name": "ROM",
"value": tree_text["value"] + tree_data["value"],
"children": [tree_text, tree_data]
}
tree_ram = {
"name": "RAM",
"value": tree_bss["value"] + tree_data["value"],
"children": [tree_bss, tree_data]
}
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,
"rom": json.dumps(tree_rom),
"ram": json.dumps(tree_ram),
}
file_desc.write(template.render(data))
return None
def generate_json(self, file_desc):
"""Generate a json file from a memory map
@ -655,6 +736,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":

103
tools/memap_flamegraph.html Normal file
View File

@ -0,0 +1,103 @@
<!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-rom">
</div>
<hr/>
<div id="chart-ram">
</div>
<hr/>
<div id="details"></div>
</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 + ", bytes: " + d.data.value; });
var flameGraph_rom = d3.flameGraph()
.transitionDuration(250)
.transitionEase(d3.easeCubic)
.sort(true)
.tooltip(tip);
var flameGraph_ram = d3.flameGraph()
.transitionDuration(250)
.transitionEase(d3.easeCubic)
.sort(true)
.tooltip(tip);
var rom_elem = d3.select("#chart-rom");
flameGraph_rom.width(rom_elem.node().getBoundingClientRect().width);
rom_elem.datum({{rom}}).call(flameGraph_rom);
var ram_elem = d3.select("#chart-ram");
flameGraph_ram.width(ram_elem.node().getBoundingClientRect().width);
ram_elem.datum({{ram}}).call(flameGraph_ram);
</script>
</body>
</html>