Skip to content

Commit 90163bb

Browse files
author
Cruz Monrreal
authored
Merge pull request #7590 from theotherjimmy/differential-memap
Tools: Differential Memap
2 parents ec4c33c + fc97d77 commit 90163bb

File tree

3 files changed

+123
-24
lines changed

3 files changed

+123
-24
lines changed

tools/memap.py

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
from abc import abstractmethod, ABCMeta
77
from sys import stdout, exit, argv
8-
from os import sep
8+
from os import sep, rename, remove
99
from os.path import (basename, dirname, join, relpath, abspath, commonprefix,
10-
splitext)
10+
splitext, exists)
1111
import re
1212
import csv
1313
import json
@@ -20,6 +20,7 @@
2020

2121
from .utils import (argparse_filestring_type, argparse_lowercase_hyphen_type,
2222
argparse_uppercase_type)
23+
from .settings import COMPARE_FIXED
2324

2425

2526
class _Parser(object):
@@ -455,6 +456,7 @@ class MemapParser(object):
455456
"""
456457

457458
print_sections = ('.text', '.data', '.bss')
459+
delta_sections = ('.text-delta', '.data-delta', '.bss-delta')
458460

459461

460462
# sections to print info (generic for all toolchains)
@@ -466,6 +468,7 @@ def __init__(self):
466468
# list of all modules and their sections
467469
# full list - doesn't change with depth
468470
self.modules = dict()
471+
self.old_modules = None
469472
# short version with specific depth
470473
self.short_modules = dict()
471474

@@ -510,8 +513,17 @@ def reduce_depth(self, depth):
510513
new_name = join(*split_name[:depth])
511514
self.short_modules.setdefault(new_name, defaultdict(int))
512515
for section_idx, value in v.items():
513-
self.short_modules[new_name].setdefault(section_idx, 0)
514516
self.short_modules[new_name][section_idx] += self.modules[module_name][section_idx]
517+
self.short_modules[new_name][section_idx + '-delta'] += self.modules[module_name][section_idx]
518+
if self.old_modules:
519+
for module_name, v in self.old_modules.items():
520+
split_name = module_name.split(sep)
521+
if split_name[0] == '':
522+
split_name = split_name[1:]
523+
new_name = join(*split_name[:depth])
524+
self.short_modules.setdefault(new_name, defaultdict(int))
525+
for section_idx, value in v.items():
526+
self.short_modules[new_name][section_idx + '-delta'] -= self.old_modules[module_name][section_idx]
515527

516528
export_formats = ["json", "csv-ci", "html", "table"]
517529

@@ -557,7 +569,7 @@ def _move_up_tree(tree, next_module):
557569
if child["name"] == next_module:
558570
return child
559571
else:
560-
new_module = {"name": next_module, "value": 0}
572+
new_module = {"name": next_module, "value": 0, "delta": 0}
561573
tree["children"].append(new_module)
562574
return new_module
563575

@@ -567,9 +579,9 @@ def generate_html(self, file_desc):
567579
Positional arguments:
568580
file_desc - the file to write out the final report to
569581
"""
570-
tree_text = {"name": ".text", "value": 0}
571-
tree_bss = {"name": ".bss", "value": 0}
572-
tree_data = {"name": ".data", "value": 0}
582+
tree_text = {"name": ".text", "value": 0, "delta": 0}
583+
tree_bss = {"name": ".bss", "value": 0, "delta": 0}
584+
tree_data = {"name": ".data", "value": 0, "delta": 0}
573585
for name, dct in self.modules.items():
574586
cur_text = tree_text
575587
cur_bss = tree_bss
@@ -578,14 +590,17 @@ def generate_html(self, file_desc):
578590
while True:
579591
try:
580592
cur_text["value"] += dct['.text']
593+
cur_text["delta"] += dct['.text']
581594
except KeyError:
582595
pass
583596
try:
584597
cur_bss["value"] += dct['.bss']
598+
cur_bss["delta"] += dct['.bss']
585599
except KeyError:
586600
pass
587601
try:
588602
cur_data["value"] += dct['.data']
603+
cur_data["delta"] += dct['.data']
589604
except KeyError:
590605
pass
591606
if not modules:
@@ -594,15 +609,44 @@ def generate_html(self, file_desc):
594609
cur_text = self._move_up_tree(cur_text, next_module)
595610
cur_data = self._move_up_tree(cur_data, next_module)
596611
cur_bss = self._move_up_tree(cur_bss, next_module)
612+
if self.old_modules:
613+
for name, dct in self.old_modules.items():
614+
cur_text = tree_text
615+
cur_bss = tree_bss
616+
cur_data = tree_data
617+
modules = name.split(sep)
618+
while True:
619+
try:
620+
cur_text["delta"] -= dct['.text']
621+
except KeyError:
622+
pass
623+
try:
624+
cur_bss["delta"] -= dct['.bss']
625+
except KeyError:
626+
pass
627+
try:
628+
cur_data["delta"] -= dct['.data']
629+
except KeyError:
630+
pass
631+
if not modules:
632+
break
633+
next_module = modules.pop(0)
634+
if not any(cld['name'] == next_module for cld in cur_text['children']):
635+
break
636+
cur_text = self._move_up_tree(cur_text, next_module)
637+
cur_data = self._move_up_tree(cur_data, next_module)
638+
cur_bss = self._move_up_tree(cur_bss, next_module)
597639

598640
tree_rom = {
599641
"name": "ROM",
600642
"value": tree_text["value"] + tree_data["value"],
643+
"delta": tree_text["delta"] + tree_data["delta"],
601644
"children": [tree_text, tree_data]
602645
}
603646
tree_ram = {
604647
"name": "RAM",
605648
"value": tree_bss["value"] + tree_data["value"],
649+
"delta": tree_bss["delta"] + tree_data["delta"],
606650
"children": [tree_bss, tree_data]
607651
}
608652

@@ -634,6 +678,14 @@ def generate_json(self, file_desc):
634678
file_desc.write('\n')
635679
return None
636680

681+
RAM_FORMAT_STR = (
682+
"Total Static RAM memory (data + bss): {}({:+}) bytes\n"
683+
)
684+
685+
ROM_FORMAT_STR = (
686+
"Total Flash memory (text + data): {}({:+}) bytes\n"
687+
)
688+
637689
def generate_csv(self, file_desc):
638690
"""Generate a CSV file from a memoy map
639691
@@ -646,7 +698,7 @@ def generate_csv(self, file_desc):
646698
module_section = []
647699
sizes = []
648700
for i in sorted(self.short_modules):
649-
for k in self.print_sections:
701+
for k in self.print_sections + self.delta_sections:
650702
module_section.append((i + k))
651703
sizes += [self.short_modules[i][k]]
652704

@@ -681,23 +733,29 @@ def generate_table(self, file_desc):
681733
row = [i]
682734

683735
for k in self.print_sections:
684-
row.append(self.short_modules[i][k])
736+
row.append("{}({:+})".format(self.short_modules[i][k],
737+
self.short_modules[i][k + "-delta"]))
685738

686739
table.add_row(row)
687740

688741
subtotal_row = ['Subtotals']
689742
for k in self.print_sections:
690-
subtotal_row.append(self.subtotal[k])
743+
subtotal_row.append("{}({:+})".format(
744+
self.subtotal[k], self.subtotal[k + '-delta']))
691745

692746
table.add_row(subtotal_row)
693747

694748
output = table.get_string()
695749
output += '\n'
696750

697-
output += "Total Static RAM memory (data + bss): %s bytes\n" % \
698-
str(self.mem_summary['static_ram'])
699-
output += "Total Flash memory (text + data): %s bytes\n" % \
700-
str(self.mem_summary['total_flash'])
751+
output += self.RAM_FORMAT_STR.format(
752+
self.mem_summary['static_ram'],
753+
self.mem_summary['static_ram_delta']
754+
)
755+
output += self.ROM_FORMAT_STR.format(
756+
self.mem_summary['total_flash'],
757+
self.mem_summary['total_flash_delta']
758+
)
701759

702760
return output
703761

@@ -706,16 +764,24 @@ def generate_table(self, file_desc):
706764
def compute_report(self):
707765
""" Generates summary of memory usage for main areas
708766
"""
709-
for k in self.sections:
710-
self.subtotal[k] = 0
767+
self.subtotal = defaultdict(int)
711768

712769
for mod in self.modules.values():
713770
for k in self.sections:
714771
self.subtotal[k] += mod[k]
772+
self.subtotal[k + '-delta'] += mod[k]
773+
if self.old_modules:
774+
for mod in self.old_modules.values():
775+
for k in self.sections:
776+
self.subtotal[k + '-delta'] -= mod[k]
715777

716778
self.mem_summary = {
717-
'static_ram': (self.subtotal['.data'] + self.subtotal['.bss']),
779+
'static_ram': self.subtotal['.data'] + self.subtotal['.bss'],
780+
'static_ram_delta':
781+
self.subtotal['.data-delta'] + self.subtotal['.bss-delta'],
718782
'total_flash': (self.subtotal['.text'] + self.subtotal['.data']),
783+
'total_flash_delta':
784+
self.subtotal['.text-delta'] + self.subtotal['.data-delta'],
719785
}
720786

721787
self.mem_report = []
@@ -724,7 +790,8 @@ def compute_report(self):
724790
self.mem_report.append({
725791
"module": name,
726792
"size":{
727-
k: sizes.get(k, 0) for k in self.print_sections
793+
k: sizes.get(k, 0) for k in (self.print_sections +
794+
self.delta_sections)
728795
}
729796
})
730797

@@ -741,16 +808,26 @@ def parse(self, mapfile, toolchain):
741808
"""
742809
self.tc_name = toolchain.title()
743810
if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"):
744-
parser = _ArmccParser()
811+
parser = _ArmccParser
745812
elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":
746-
parser = _GccParser()
813+
parser = _GccParser
747814
elif toolchain == "IAR":
748-
parser = _IarParser()
815+
parser = _IarParser
749816
else:
750817
return False
751818
try:
752819
with open(mapfile, 'r') as file_input:
753-
self.modules = parser.parse_mapfile(file_input)
820+
self.modules = parser().parse_mapfile(file_input)
821+
try:
822+
with open("%s.old" % mapfile, 'r') as old_input:
823+
self.old_modules = parser().parse_mapfile(old_input)
824+
except IOError:
825+
self.old_modules = None
826+
if not COMPARE_FIXED:
827+
old_mapfile = "%s.old" % mapfile
828+
if exists(old_mapfile):
829+
remove(old_mapfile)
830+
rename(mapfile, old_mapfile)
754831
return True
755832

756833
except IOError as error:

tools/memap_flamegraph.html

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,35 @@ <h3 class="text-muted">{{name}} Memory Details</h3>
8080
.direction("s")
8181
.offset([8, 0])
8282
.attr('class', 'd3-flame-graph-tip')
83-
.html(function(d) { return "module: " + d.data.name + ", bytes: " + d.data.value; });
83+
.html(function(d) { return "module: " + d.data.name + ", bytes: " + d.data.value + ", delta: " + d.data.delta; });
84+
var colorizer = function (d) {
85+
if (d.data.delta > 0) {
86+
ratio = (d.data.value - d.data.delta) / d.data.value;
87+
green = ("0" + (Number(ratio * 0xFF | 0).toString(16))).slice(-2).toUpperCase();
88+
blue = ("0" + (Number(ratio * 0xEE | 0).toString(16))).slice(-2).toUpperCase();
89+
console.log(d.data.name, green, blue);
90+
return "#EE" + green + blue
91+
} else if (d.data.delta < 0) {
92+
ratio = (d.data.value + d.data.delta) / d.data.value;
93+
green = ("0" + (Number(ratio * 0xFF | 0).toString(16))).slice(-2).toUpperCase();
94+
red = ("0" + (Number(ratio * 0xFF | 0).toString(16))).slice(-2).toUpperCase();
95+
console.log(d.data.name, red, green);
96+
return "#" + red + green + "EE";
97+
} else {
98+
return "#FFFFEE";
99+
}
100+
}
84101
var flameGraph_rom = d3.flameGraph()
85102
.transitionDuration(250)
86103
.transitionEase(d3.easeCubic)
87104
.sort(true)
105+
.color(colorizer)
88106
.tooltip(tip);
89107
var flameGraph_ram = d3.flameGraph()
90108
.transitionDuration(250)
91109
.transitionEase(d3.easeCubic)
92110
.sort(true)
111+
.color(colorizer)
93112
.tooltip(tip);
94113
var rom_elem = d3.select("#chart-rom");
95114
flameGraph_rom.width(rom_elem.node().getBoundingClientRect().width);

tools/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
# Print compiler warnings and errors as link format
5959
PRINT_COMPILER_OUTPUT_AS_LINK = False
6060

61+
# Compare against a fixed build of the project for space consumption
62+
COMPARE_FIXED = False
63+
6164
# Print warnings/errors in color
6265
COLOR = False
6366

@@ -91,7 +94,7 @@
9194
print("WARNING: MBED_%s set as environment variable but doesn't"
9295
" exist" % _n)
9396

94-
_ENV_VARS = ['PRINT_COMPILER_OUTPUT_AS_LINK', 'COLOR']
97+
_ENV_VARS = ['PRINT_COMPILER_OUTPUT_AS_LINK', 'COLOR', 'COMPARE_FIXED']
9598
for _n in _ENV_VARS:
9699
value = getenv('MBED_%s' % _n)
97100
if value:

0 commit comments

Comments
 (0)