Skip to content

Commit 847fab7

Browse files
committed
memory.py: Compute a diff over Massif heap memory profiles
This tool will enable memory regression tests by comparing heap memory profiles generated using valgrind's Massif tool (or any other tool that can generate compatible output).
1 parent 450ee48 commit 847fab7

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

scripts/memory-test/memory.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env python
2+
3+
from __future__ import print_function
4+
5+
import argparse
6+
import difflib
7+
import msparser
8+
import os
9+
# import pprint
10+
import sys
11+
12+
13+
def near_eq(x, y):
14+
fx = float(x)
15+
fy = float(y)
16+
return abs(fy - fx) <= 0.1 * abs(fx)
17+
18+
19+
class snapshot:
20+
def __init__(self, s, is_peak):
21+
self.value = s['mem_heap']
22+
self.is_peak = is_peak
23+
self.data = s
24+
25+
def __cmp__(self, other):
26+
if self.__eq__(other):
27+
return 0
28+
else:
29+
return -1 if(self.value < other.value) else 1
30+
31+
def __eq__(self, other):
32+
if not near_eq(self.value, other.value):
33+
return False
34+
35+
if self.data.get('heap_tree') and other.data.get('heap_tree'):
36+
ds = self.data['heap_tree']['children'][0]
37+
do = other.data['heap_tree']['children'][0]
38+
if ds['details']['function'] != do['details']['function'] or (
39+
not near_eq(ds['nbytes'], do['nbytes'])):
40+
return False
41+
42+
return True
43+
# pprint.pprint(self.data['heap_tree'], depth=2)
44+
# pprint.pprint(other.data['heap_tree'], depth=2)
45+
46+
def __radd__(self, other):
47+
s = other + str(self.value)
48+
if self.is_peak:
49+
s += ' *peak*'
50+
if self.data.get('heap_tree'):
51+
d = self.data['heap_tree']['children'][0]
52+
s += ' {}: {}'.format(d['details']['function'], d['nbytes'])
53+
return s
54+
55+
def __hash__(self):
56+
"""
57+
Make sure all values end up in the same hash bucket to enforce
58+
comparision via ==/__eq__ as overridden above.
59+
"""
60+
return 0
61+
62+
63+
# based on https://chezsoi.org/lucas/blog/colored-diff-output-with-python.html
64+
try:
65+
from colorama import Fore, init
66+
init()
67+
except ImportError: # fallback so that the imported classes always exist
68+
class ColorFallback():
69+
# simulate a subset of Colorama's features (Colorama knows how to
70+
# support Windows, we just don't support colours there)
71+
if sys.stdout.isatty and os.name != 'nt':
72+
GREEN = '\033[32m'
73+
RED = '\033[31m'
74+
RESET = '\033[0m'
75+
else:
76+
GREEN = RED = RESET = ''
77+
Fore = ColorFallback()
78+
79+
80+
def color_diff(diff):
81+
for line in diff:
82+
if line.startswith('+'):
83+
yield Fore.GREEN + line + Fore.RESET
84+
elif line.startswith('-'):
85+
yield Fore.RED + line + Fore.RESET
86+
else:
87+
yield line
88+
89+
90+
def parse_args():
91+
parser = argparse.ArgumentParser()
92+
parser.add_argument('-r', '--reference', type=str, required=True,
93+
help='Massif reference output')
94+
parser.add_argument('file', type=str,
95+
help='Massif output to validate')
96+
97+
args = parser.parse_args()
98+
99+
return args
100+
101+
102+
def main():
103+
args = parse_args()
104+
105+
reference_data = ()
106+
with open(args.reference) as r:
107+
reference_data = msparser.parse(r)
108+
109+
data = ()
110+
with open(args.file) as f:
111+
data = msparser.parse(f)
112+
113+
r_peak_index = reference_data['peak_snapshot_index']
114+
r_peak = reference_data['snapshots'][r_peak_index]
115+
peak_index = data['peak_snapshot_index']
116+
peak = data['snapshots'][peak_index]
117+
118+
print("snapshots: ref={} cur={}".format(
119+
len(reference_data['snapshots']), len(data['snapshots'])))
120+
print("peak idx : ref={} cur={}".format(r_peak_index, peak_index))
121+
print("peak [kB]: ref={0:.2f} cur={1:.2f}".format(
122+
r_peak['mem_heap'] / 1024.0, peak['mem_heap'] / 1024.0))
123+
124+
"""
125+
snaps = min(len(reference_data['snapshots']), len(data['snapshots']))
126+
for i in range(0, snaps):
127+
print("mem_heap [kB]: ref={0:.2f} cur={1:.2f}".format(
128+
reference_data['snapshots'][i]['mem_heap'] / 1024.0,
129+
data['snapshots'][i]['mem_heap'] / 1024.0))
130+
print(snapshot(reference_data['snapshots'][i], False) ==
131+
snapshot(data['snapshots'][i], False))
132+
"""
133+
134+
reference_seq = []
135+
for rs in range(0, len(reference_data['snapshots'])):
136+
reference_seq.append(
137+
snapshot(
138+
reference_data['snapshots'][rs],
139+
rs == r_peak_index))
140+
141+
data_seq = []
142+
for rd in range(0, len(data['snapshots'])):
143+
data_seq.append(
144+
snapshot(data['snapshots'][rd], rd == peak_index))
145+
146+
ret_code = 0
147+
diff = color_diff(
148+
difflib.unified_diff(
149+
reference_seq, data_seq, 'ref', 'cur', n=1, lineterm=''))
150+
for l in diff:
151+
ret_code = 1
152+
print(l)
153+
154+
return ret_code
155+
156+
157+
if __name__ == '__main__':
158+
rc = main()
159+
sys.exit(rc)

0 commit comments

Comments
 (0)