Skip to content

Commit 78d9c4f

Browse files
author
Cruz Monrreal
authored
Merge pull request #6582 from theotherjimmy/memap-flamegraph
Implement zoomable html-flamegraph memap output
2 parents 20a4412 + 1bfc809 commit 78d9c4f

File tree

3 files changed

+190
-2
lines changed

3 files changed

+190
-2
lines changed

tools/build_api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,9 @@ def build_project(src_paths, build_path, target, toolchain_name,
613613
map_csv = join(build_path, name + "_map.csv")
614614
memap_instance.generate_output('csv-ci', stats_depth, map_csv)
615615

616+
map_html = join(build_path, name + "_map.html")
617+
memap_instance.generate_output('html', stats_depth, map_html)
618+
616619
resources.detect_duplicates(toolchain)
617620

618621
if report != None:

tools/memap.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
from abc import abstractmethod, ABCMeta
77
from sys import stdout, exit, argv
88
from os import sep
9-
from os.path import basename, dirname, join, relpath, commonprefix
9+
from os.path import (basename, dirname, join, relpath, abspath, commonprefix,
10+
splitext)
1011
import re
1112
import csv
1213
import json
1314
from argparse import ArgumentParser
1415
from copy import deepcopy
1516
from prettytable import PrettyTable
17+
from jinja2 import FileSystemLoader, StrictUndefined
18+
from jinja2.environment import Environment
1619

1720
from .utils import (argparse_filestring_type, argparse_lowercase_hyphen_type,
1821
argparse_uppercase_type)
@@ -477,6 +480,9 @@ def __init__(self):
477480
# Flash no associated with a module
478481
self.misc_flash_mem = 0
479482

483+
# Name of the toolchain, for better headings
484+
self.tc_name = None
485+
480486
def reduce_depth(self, depth):
481487
"""
482488
populates the short_modules attribute with a truncated module list
@@ -505,7 +511,7 @@ def reduce_depth(self, depth):
505511
self.short_modules[new_name].setdefault(section_idx, 0)
506512
self.short_modules[new_name][section_idx] += self.modules[module_name][section_idx]
507513

508-
export_formats = ["json", "csv-ci", "table"]
514+
export_formats = ["json", "csv-ci", "html", "table"]
509515

510516
def generate_output(self, export_format, depth, file_output=None):
511517
""" Generates summary of memory map data
@@ -531,6 +537,7 @@ def generate_output(self, export_format, depth, file_output=None):
531537
return False
532538

533539
to_call = {'json': self.generate_json,
540+
'html': self.generate_html,
534541
'csv-ci': self.generate_csv,
535542
'table': self.generate_table}[export_format]
536543
output = to_call(file_desc)
@@ -540,6 +547,80 @@ def generate_output(self, export_format, depth, file_output=None):
540547

541548
return output
542549

550+
@staticmethod
551+
def _move_up_tree(tree, next_module):
552+
tree.setdefault("children", [])
553+
for child in tree["children"]:
554+
if child["name"] == next_module:
555+
return child
556+
else:
557+
new_module = {"name": next_module, "value": 0}
558+
tree["children"].append(new_module)
559+
return new_module
560+
561+
def generate_html(self, file_desc):
562+
"""Generate a json file from a memory map for D3
563+
564+
Positional arguments:
565+
file_desc - the file to write out the final report to
566+
"""
567+
tree_text = {"name": ".text", "value": 0}
568+
tree_bss = {"name": ".bss", "value": 0}
569+
tree_data = {"name": ".data", "value": 0}
570+
for name, dct in self.modules.items():
571+
cur_text = tree_text
572+
cur_bss = tree_bss
573+
cur_data = tree_data
574+
modules = name.split(sep)
575+
while True:
576+
try:
577+
cur_text["value"] += dct['.text']
578+
except KeyError:
579+
pass
580+
try:
581+
cur_bss["value"] += dct['.bss']
582+
except KeyError:
583+
pass
584+
try:
585+
cur_data["value"] += dct['.data']
586+
except KeyError:
587+
pass
588+
if not modules:
589+
break
590+
next_module = modules.pop(0)
591+
cur_text = self._move_up_tree(cur_text, next_module)
592+
cur_data = self._move_up_tree(cur_data, next_module)
593+
cur_bss = self._move_up_tree(cur_bss, next_module)
594+
595+
tree_rom = {
596+
"name": "ROM",
597+
"value": tree_text["value"] + tree_data["value"],
598+
"children": [tree_text, tree_data]
599+
}
600+
tree_ram = {
601+
"name": "RAM",
602+
"value": tree_bss["value"] + tree_data["value"],
603+
"children": [tree_bss, tree_data]
604+
}
605+
606+
jinja_loader = FileSystemLoader(dirname(abspath(__file__)))
607+
jinja_environment = Environment(loader=jinja_loader,
608+
undefined=StrictUndefined)
609+
610+
template = jinja_environment.get_template("memap_flamegraph.html")
611+
name, _ = splitext(basename(file_desc.name))
612+
if name.endswith("_map"):
613+
name = name[:-4]
614+
if self.tc_name:
615+
name = "%s %s" % (name, self.tc_name)
616+
data = {
617+
"name": name,
618+
"rom": json.dumps(tree_rom),
619+
"ram": json.dumps(tree_ram),
620+
}
621+
file_desc.write(template.render(data))
622+
return None
623+
543624
def generate_json(self, file_desc):
544625
"""Generate a json file from a memory map
545626
@@ -655,6 +736,7 @@ def parse(self, mapfile, toolchain):
655736
mapfile - the file name of the memory map file
656737
toolchain - the toolchain used to create the file
657738
"""
739+
self.tc_name = toolchain.title()
658740
if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"):
659741
parser = _ArmccParser()
660742
elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":

tools/memap_flamegraph.html

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
8+
<link rel="stylesheet" type="text/css"
9+
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
10+
integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w="
11+
crossorigin="anonymous"
12+
/>
13+
<link rel="stylesheet" type="text/css"
14+
href="https://cdn.jsdelivr.net/gh/spiermar/[email protected]/dist/d3.flameGraph.min.css"
15+
integrity="sha256-w762vSe6WGrkVZ7gEOpnn2Y+FSmAGlX77jYj7nhuCyY="
16+
crossorigin="anonymous"
17+
/>
18+
19+
<style>
20+
/* Space out content a bit */
21+
body {
22+
padding-top: 20px;
23+
padding-bottom: 20px;
24+
}
25+
/* Custom page header */
26+
.header {
27+
padding-bottom: 20px;
28+
padding-right: 15px;
29+
padding-left: 15px;
30+
border-bottom: 1px solid #e5e5e5;
31+
}
32+
/* Make the masthead heading the same height as the navigation */
33+
.header h3 {
34+
margin-top: 0;
35+
margin-bottom: 0;
36+
line-height: 40px;
37+
}
38+
</style>
39+
40+
<title>{{name}} Memory Details</title>
41+
42+
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
43+
<!--[if lt IE 9]>
44+
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js" integrity="sha256-4OrICDjBYfKefEbVT7wETRLNFkuq4TJV5WLGvjqpGAk=" crossorigin="anonymous"></script>
45+
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js" integrity="sha256-g6iAfvZp+nDQ2TdTR/VVKJf3bGro4ub5fvWSWVRi2NE=" crossorigin="anonymous"></script>
46+
<![endif]-->
47+
</head>
48+
<body>
49+
<div class="container">
50+
<div class="header clearfix">
51+
<h3 class="text-muted">{{name}} Memory Details</h3>
52+
</div>
53+
<div id="chart-rom">
54+
</div>
55+
<hr/>
56+
<div id="chart-ram">
57+
</div>
58+
<hr/>
59+
<div id="details"></div>
60+
</div>
61+
62+
<script type="text/javascript"
63+
src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"
64+
integrity="sha256-r7j1FXNTvPzHR41+V71Jvej6fIq4v4Kzu5ee7J/RitM="
65+
crossorigin="anonymous">
66+
</script>
67+
<script type="text/javascript"
68+
src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"
69+
integrity="sha256-z0A2CQF8xxCKuOJsn4sJ5HBjxiHHRAfTX8hDF4RSN5s="
70+
crossorigin="anonymous">
71+
</script>
72+
<script type="text/javascript"
73+
src="https://cdn.jsdelivr.net/gh/spiermar/[email protected]/dist/d3.flameGraph.min.js"
74+
integrity="sha256-I1CkrWbmjv+GWjgbulJ4i0vbzdrDGfxqdye2qNlhG3Q="
75+
crossorigin="anonymous">
76+
</script>
77+
78+
<script type="text/javascript">
79+
var tip = d3.tip()
80+
.direction("s")
81+
.offset([8, 0])
82+
.attr('class', 'd3-flame-graph-tip')
83+
.html(function(d) { return "module: " + d.data.name + ", bytes: " + d.data.value; });
84+
var flameGraph_rom = d3.flameGraph()
85+
.transitionDuration(250)
86+
.transitionEase(d3.easeCubic)
87+
.sort(true)
88+
.tooltip(tip);
89+
var flameGraph_ram = d3.flameGraph()
90+
.transitionDuration(250)
91+
.transitionEase(d3.easeCubic)
92+
.sort(true)
93+
.tooltip(tip);
94+
var rom_elem = d3.select("#chart-rom");
95+
flameGraph_rom.width(rom_elem.node().getBoundingClientRect().width);
96+
rom_elem.datum({{rom}}).call(flameGraph_rom);
97+
var ram_elem = d3.select("#chart-ram");
98+
flameGraph_ram.width(ram_elem.node().getBoundingClientRect().width);
99+
ram_elem.datum({{ram}}).call(flameGraph_ram);
100+
</script>
101+
</body>
102+
</html>
103+

0 commit comments

Comments
 (0)