Skip to content

Commit 8c6b471

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 b8c5ed1 commit 8c6b471

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

scripts/memory-test/memory.py

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

0 commit comments

Comments
 (0)