Skip to content

Commit 6dc1a0f

Browse files
committed
Merge remote-tracking branch 'upstream/master' into async-profile
2 parents e8d6bf7 + 895c4ac commit 6dc1a0f

4 files changed

Lines changed: 139 additions & 12 deletions

File tree

README.rst

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@ this would result in::
6464

6565
Output will follow::
6666

67-
Line # Mem usage Increment Line Contents
68-
==============================================
69-
3 @profile
70-
4 5.97 MB 0.00 MB def my_func():
71-
5 13.61 MB 7.64 MB a = [1] * (10 ** 6)
72-
6 166.20 MB 152.59 MB b = [2] * (2 * 10 ** 7)
73-
7 13.61 MB -152.59 MB del b
74-
8 13.61 MB 0.00 MB return a
67+
Line # Mem usage Increment Occurences Line Contents
68+
============================================================
69+
3 38.816 MiB 38.816 MiB 1 @profile
70+
4 def my_func():
71+
5 46.492 MiB 7.676 MiB 1 a = [1] * (10 ** 6)
72+
6 199.117 MiB 152.625 MiB 1 b = [2] * (2 * 10 ** 7)
73+
7 46.629 MiB -152.488 MiB 1 del b
74+
8 46.629 MiB 0.000 MiB 1 return a
7575

7676

7777
The first column represents the line number of the code that has been
@@ -214,6 +214,21 @@ You can also hide the function timestamps using the ``n`` flag, such as
214214

215215
mprof plot -n
216216

217+
Trend lines and its numeric slope can be plotted using the ``s`` flag, such as
218+
219+
mprof plot -s
220+
221+
.. image:: ./images/trend_slope.png
222+
:height: 350px
223+
224+
The intended usage of the -s switch is to check the labels' numerical slope over a significant time period for :
225+
226+
- ``>0`` it might mean a memory leak.
227+
- ``~0`` if 0 or near 0, the memory usage may be considered stable.
228+
- ``<0`` to be interpreted depending on the expected process memory usage patterns, also might mean that the sampling period is too small.
229+
230+
The trend lines are for ilustrative purposes and are plotted as (very) small dashed lines.
231+
217232

218233
Setting debugger breakpoints
219234
=============================

images/trend_slope.png

55.6 KB
Loading

mprof.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,10 @@ def run_action():
230230
if not program[0].startswith("python"):
231231
program.insert(0, sys.executable)
232232
cmd_line = get_cmd_line(program)
233-
program[1:1] = ("-m", "memory_profiler", "--timestamp",
234-
"-o", mprofile_output)
233+
extra_args = ["-m", "memory_profiler", "--timestamp", "-o", mprofile_output]
234+
if args.include_children:
235+
extra_args.append("--include-children")
236+
program[1:1] = extra_args
235237
p = subprocess.Popen(program)
236238
else:
237239
cmd_line = get_cmd_line(program)
@@ -402,13 +404,28 @@ def plot_file(filename, index=0, timestamps=True, children=True, options=None):
402404

403405
all_colors = ("c", "y", "g", "r", "b")
404406
mem_line_colors = ("k", "b", "r", "g", "c", "y", "m")
407+
408+
show_trend_slope = options is not None and hasattr(options, 'slope') and options.slope is True
409+
405410
mem_line_label = time.strftime("%d / %m / %Y - start at %H:%M:%S",
406411
time.localtime(global_start)) \
407412
+ ".{0:03d}".format(int(round(math.modf(global_start)[0] * 1000)))
408413

414+
mem_trend = None
415+
if show_trend_slope:
416+
# Compute trend line
417+
mem_trend = np.polyfit(t, mem, 1)
418+
419+
# Append slope to label
420+
mem_line_label = mem_line_label + " slope {0:.5f}".format(mem_trend[0])
421+
409422
pl.plot(t, mem, "+-" + mem_line_colors[index % len(mem_line_colors)],
410423
label=mem_line_label)
411424

425+
if show_trend_slope:
426+
# Plot the trend line
427+
pl.plot(t, t*mem_trend[0] + mem_trend[1], "--", linewidth=0.5, color="#00e3d8")
428+
412429
bottom, top = pl.ylim()
413430
bottom += 0.001
414431
top -= 0.001
@@ -422,9 +439,21 @@ def plot_file(filename, index=0, timestamps=True, children=True, options=None):
422439
cts = np.asarray([item[1] for item in data]) - global_start
423440
cmem = np.asarray([item[0] for item in data])
424441

442+
cmem_trend = None
443+
child_mem_trend_label = ""
444+
if show_trend_slope:
445+
# Compute trend line
446+
cmem_trend = np.polyfit(cts, cmem, 1)
447+
448+
child_mem_trend_label = " slope {0:.5f}".format(cmem_trend[0])
449+
425450
# Plot the line to the figure
426-
pl.plot(cts, cmem, "+-" + mem_line_colors[(idx+1) % len(mem_line_colors)],
427-
label="child {}".format(proc))
451+
pl.plot(cts, cmem, "+-" + mem_line_colors[(idx + 1) % len(mem_line_colors)],
452+
label="child {}{}".format(proc, child_mem_trend_label))
453+
454+
if show_trend_slope:
455+
# Plot the trend line
456+
pl.plot(cts, cts*cmem_trend[0] + cmem_trend[1], "--", linewidth=0.5, color="black")
428457

429458
# Detect the maximal child memory point
430459
cmax_mem = cmem.max()
@@ -710,6 +739,8 @@ def xlim_type(value):
710739
help="Plot a time-subset of the data. E.g. to plot between 0 and 20.5 seconds: --window 0,20.5")
711740
parser.add_argument("--flame", "-f", dest="flame_mode", action="store_true",
712741
help="Plot the timestamps as a flame-graph instead of the default brackets")
742+
parser.add_argument("--slope", "-s", dest="slope", action="store_true",
743+
help="Plot a trend line and its numerical slope")
713744
parser.add_argument("--backend",
714745
help="Specify the Matplotlib backend to use")
715746
parser.add_argument("profiles", nargs="*",

test/test_increment_display.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import unittest
2+
3+
from memory_profiler import LineProfiler, profile, show_results
4+
from io import StringIO
5+
6+
7+
class TestIncrementDisplay(unittest.TestCase):
8+
"""Tests memory incrementation / decrementation display"""
9+
10+
def test_loop_count(self):
11+
12+
def some_loop():
13+
for i in range(12): # line -2
14+
a = 1 # line -1
15+
16+
profiler = LineProfiler()
17+
wrapped = profiler(some_loop)
18+
wrapped()
19+
show_results(profiler)
20+
for_line = list(list(profiler.code_map.values())[0].values())[-2]
21+
looped_instruction = list(list(profiler.code_map.values())[0].values())[-1]
22+
23+
self.assertEqual(for_line[2], 13)
24+
self.assertEqual(looped_instruction[2], 12)
25+
26+
def test_normal_incr(self):
27+
28+
def normal_incr():
29+
use_some_memory = [1] * (10 ** 6)
30+
31+
profiler = LineProfiler()
32+
wrapped = profiler(normal_incr)
33+
wrapped()
34+
35+
show_results(profiler)
36+
results = list(list(profiler.code_map.values())[0].values())[-1]
37+
38+
self.assertGreater(results[0], 0)
39+
self.assertGreater(results[1], results[0])
40+
self.assertEqual(results[2], 1)
41+
42+
def test_loop_incr(self):
43+
44+
def loop_incr():
45+
a = []
46+
b = [2] * (2 * 10 ** 7) # line -4
47+
for i in range(3):
48+
c = [2] * (2 * 10 ** 7) # line -2
49+
a.append(c)
50+
51+
profiler = LineProfiler()
52+
wrapped = profiler(loop_incr)
53+
wrapped()
54+
55+
show_results(profiler)
56+
b_line = list(list(profiler.code_map.values())[0].values())[-4]
57+
c_line = list(list(profiler.code_map.values())[0].values())[-2]
58+
self.assertAlmostEqual(b_line[2] * 3, c_line[2], delta=1)
59+
self.assertEqual(c_line[2], 3)
60+
61+
def test_decr(self):
62+
63+
def del_stuff():
64+
b = [2] * (2 * 10 ** 7)
65+
del b
66+
67+
profiler = LineProfiler()
68+
wrapped = profiler(del_stuff)
69+
wrapped()
70+
71+
show_results(profiler)
72+
b_line = list(list(profiler.code_map.values())[0].values())[-2]
73+
del_line = list(list(profiler.code_map.values())[0].values())[-1]
74+
75+
self.assertGreater(0, del_line[0])
76+
self.assertGreater(del_line[1], 0)
77+
self.assertAlmostEqual(-del_line[0], b_line[0], delta=1)
78+
79+
80+
if __name__ == '__main__':
81+
unittest.main()

0 commit comments

Comments
 (0)