blob: 80b521c2e1af9bea93806bcfb3452e346e016991 [file] [log] [blame]
Serhiy Storchaka2e576f52017-04-24 09:05:00 +03001from collections.abc import Sequence, Iterable
Victor Stinnered3b0bc2013-11-23 12:27:24 +01002from functools import total_ordering
3import fnmatch
Victor Stinner23f628d2014-02-16 23:53:38 +01004import linecache
Victor Stinnered3b0bc2013-11-23 12:27:24 +01005import os.path
6import pickle
7
8# Import types and functions implemented in C
9from _tracemalloc import *
10from _tracemalloc import _get_object_traceback, _get_traces
11
12
13def _format_size(size, sign):
14 for unit in ('B', 'KiB', 'MiB', 'GiB', 'TiB'):
15 if abs(size) < 100 and unit != 'B':
16 # 3 digits (xx.x UNIT)
17 if sign:
18 return "%+.1f %s" % (size, unit)
19 else:
20 return "%.1f %s" % (size, unit)
21 if abs(size) < 10 * 1024 or unit == 'TiB':
22 # 4 or 5 digits (xxxx UNIT)
23 if sign:
24 return "%+.0f %s" % (size, unit)
25 else:
26 return "%.0f %s" % (size, unit)
27 size /= 1024
28
29
30class Statistic:
31 """
32 Statistic difference on memory allocations between two Snapshot instance.
33 """
34
35 __slots__ = ('traceback', 'size', 'count')
36
37 def __init__(self, traceback, size, count):
38 self.traceback = traceback
39 self.size = size
40 self.count = count
41
42 def __hash__(self):
Victor Stinner802a4842013-11-26 10:16:25 +010043 return hash((self.traceback, self.size, self.count))
Victor Stinnered3b0bc2013-11-23 12:27:24 +010044
45 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +030046 if not isinstance(other, Statistic):
47 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +010048 return (self.traceback == other.traceback
49 and self.size == other.size
50 and self.count == other.count)
51
52 def __str__(self):
53 text = ("%s: size=%s, count=%i"
54 % (self.traceback,
55 _format_size(self.size, False),
56 self.count))
57 if self.count:
58 average = self.size / self.count
59 text += ", average=%s" % _format_size(average, False)
60 return text
61
62 def __repr__(self):
63 return ('<Statistic traceback=%r size=%i count=%i>'
64 % (self.traceback, self.size, self.count))
65
66 def _sort_key(self):
67 return (self.size, self.count, self.traceback)
68
69
70class StatisticDiff:
71 """
72 Statistic difference on memory allocations between an old and a new
73 Snapshot instance.
74 """
75 __slots__ = ('traceback', 'size', 'size_diff', 'count', 'count_diff')
76
77 def __init__(self, traceback, size, size_diff, count, count_diff):
78 self.traceback = traceback
79 self.size = size
80 self.size_diff = size_diff
81 self.count = count
82 self.count_diff = count_diff
83
84 def __hash__(self):
Victor Stinner802a4842013-11-26 10:16:25 +010085 return hash((self.traceback, self.size, self.size_diff,
86 self.count, self.count_diff))
Victor Stinnered3b0bc2013-11-23 12:27:24 +010087
88 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +030089 if not isinstance(other, StatisticDiff):
90 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +010091 return (self.traceback == other.traceback
92 and self.size == other.size
93 and self.size_diff == other.size_diff
94 and self.count == other.count
95 and self.count_diff == other.count_diff)
96
97 def __str__(self):
98 text = ("%s: size=%s (%s), count=%i (%+i)"
99 % (self.traceback,
100 _format_size(self.size, False),
101 _format_size(self.size_diff, True),
102 self.count,
103 self.count_diff))
104 if self.count:
105 average = self.size / self.count
106 text += ", average=%s" % _format_size(average, False)
107 return text
108
109 def __repr__(self):
110 return ('<StatisticDiff traceback=%r size=%i (%+i) count=%i (%+i)>'
111 % (self.traceback, self.size, self.size_diff,
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100112 self.count, self.count_diff))
113
114 def _sort_key(self):
115 return (abs(self.size_diff), self.size,
116 abs(self.count_diff), self.count,
117 self.traceback)
118
119
120def _compare_grouped_stats(old_group, new_group):
121 statistics = []
122 for traceback, stat in new_group.items():
123 previous = old_group.pop(traceback, None)
124 if previous is not None:
125 stat = StatisticDiff(traceback,
Victor Stinnerd81999a2014-03-06 17:06:04 +0100126 stat.size, stat.size - previous.size,
127 stat.count, stat.count - previous.count)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100128 else:
129 stat = StatisticDiff(traceback,
Victor Stinnerd81999a2014-03-06 17:06:04 +0100130 stat.size, stat.size,
131 stat.count, stat.count)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100132 statistics.append(stat)
133
134 for traceback, stat in old_group.items():
135 stat = StatisticDiff(traceback, 0, -stat.size, 0, -stat.count)
136 statistics.append(stat)
137 return statistics
138
139
140@total_ordering
141class Frame:
142 """
143 Frame of a traceback.
144 """
145 __slots__ = ("_frame",)
146
147 def __init__(self, frame):
Victor Stinner733e50a2014-03-06 17:06:41 +0100148 # frame is a tuple: (filename: str, lineno: int)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100149 self._frame = frame
150
151 @property
152 def filename(self):
153 return self._frame[0]
154
155 @property
156 def lineno(self):
157 return self._frame[1]
158
159 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300160 if not isinstance(other, Frame):
161 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100162 return (self._frame == other._frame)
163
164 def __lt__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300165 if not isinstance(other, Frame):
166 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100167 return (self._frame < other._frame)
168
169 def __hash__(self):
170 return hash(self._frame)
171
172 def __str__(self):
173 return "%s:%s" % (self.filename, self.lineno)
174
175 def __repr__(self):
176 return "<Frame filename=%r lineno=%r>" % (self.filename, self.lineno)
177
178
179@total_ordering
180class Traceback(Sequence):
181 """
Jesse-Bakker706e10b2017-11-30 00:05:07 +0100182 Sequence of Frame instances sorted from the oldest frame
183 to the most recent frame.
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100184 """
185 __slots__ = ("_frames",)
186
187 def __init__(self, frames):
188 Sequence.__init__(self)
Victor Stinner733e50a2014-03-06 17:06:41 +0100189 # frames is a tuple of frame tuples: see Frame constructor for the
Jesse-Bakker706e10b2017-11-30 00:05:07 +0100190 # format of a frame tuple; it is reversed, because _tracemalloc
191 # returns frames sorted from most recent to oldest, but the
192 # Python API expects oldest to most recent
193 self._frames = tuple(reversed(frames))
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100194
195 def __len__(self):
196 return len(self._frames)
197
198 def __getitem__(self, index):
Victor Stinner524be302014-02-01 04:07:02 +0100199 if isinstance(index, slice):
200 return tuple(Frame(trace) for trace in self._frames[index])
201 else:
202 return Frame(self._frames[index])
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100203
204 def __contains__(self, frame):
205 return frame._frame in self._frames
206
207 def __hash__(self):
208 return hash(self._frames)
209
210 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300211 if not isinstance(other, Traceback):
212 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100213 return (self._frames == other._frames)
214
215 def __lt__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300216 if not isinstance(other, Traceback):
217 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100218 return (self._frames < other._frames)
219
220 def __str__(self):
221 return str(self[0])
222
223 def __repr__(self):
224 return "<Traceback %r>" % (tuple(self),)
225
Jesse-Bakker706e10b2017-11-30 00:05:07 +0100226 def format(self, limit=None, most_recent_first=False):
Victor Stinner23f628d2014-02-16 23:53:38 +0100227 lines = []
Jesse-Bakker706e10b2017-11-30 00:05:07 +0100228 if limit is not None:
229 if limit > 0:
230 frame_slice = self[-limit:]
231 else:
232 frame_slice = self[:limit]
233 else:
234 frame_slice = self
235
236 if most_recent_first:
237 frame_slice = reversed(frame_slice)
238 for frame in frame_slice:
Victor Stinner23f628d2014-02-16 23:53:38 +0100239 lines.append(' File "%s", line %s'
240 % (frame.filename, frame.lineno))
241 line = linecache.getline(frame.filename, frame.lineno).strip()
242 if line:
243 lines.append(' %s' % line)
244 return lines
245
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100246
247def get_object_traceback(obj):
248 """
249 Get the traceback where the Python object *obj* was allocated.
250 Return a Traceback instance.
251
252 Return None if the tracemalloc module is not tracing memory allocations or
253 did not trace the allocation of the object.
254 """
255 frames = _get_object_traceback(obj)
256 if frames is not None:
257 return Traceback(frames)
258 else:
259 return None
260
261
262class Trace:
263 """
264 Trace of a memory block.
265 """
266 __slots__ = ("_trace",)
267
268 def __init__(self, trace):
Victor Stinnere492ae52016-03-22 12:58:23 +0100269 # trace is a tuple: (domain: int, size: int, traceback: tuple).
270 # See Traceback constructor for the format of the traceback tuple.
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100271 self._trace = trace
272
273 @property
Victor Stinnere492ae52016-03-22 12:58:23 +0100274 def domain(self):
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100275 return self._trace[0]
276
277 @property
Victor Stinnere492ae52016-03-22 12:58:23 +0100278 def size(self):
279 return self._trace[1]
280
281 @property
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100282 def traceback(self):
Victor Stinnere492ae52016-03-22 12:58:23 +0100283 return Traceback(self._trace[2])
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100284
285 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300286 if not isinstance(other, Trace):
287 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100288 return (self._trace == other._trace)
289
290 def __hash__(self):
291 return hash(self._trace)
292
293 def __str__(self):
294 return "%s: %s" % (self.traceback, _format_size(self.size, False))
295
296 def __repr__(self):
Victor Stinnere492ae52016-03-22 12:58:23 +0100297 return ("<Trace domain=%s size=%s, traceback=%r>"
298 % (self.domain, _format_size(self.size, False), self.traceback))
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100299
300
301class _Traces(Sequence):
302 def __init__(self, traces):
303 Sequence.__init__(self)
Victor Stinner733e50a2014-03-06 17:06:41 +0100304 # traces is a tuple of trace tuples: see Trace constructor
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100305 self._traces = traces
306
307 def __len__(self):
308 return len(self._traces)
309
310 def __getitem__(self, index):
Victor Stinner524be302014-02-01 04:07:02 +0100311 if isinstance(index, slice):
312 return tuple(Trace(trace) for trace in self._traces[index])
313 else:
314 return Trace(self._traces[index])
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100315
316 def __contains__(self, trace):
317 return trace._trace in self._traces
318
319 def __eq__(self, other):
Serhiy Storchaka662db122019-08-08 08:42:54 +0300320 if not isinstance(other, _Traces):
321 return NotImplemented
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100322 return (self._traces == other._traces)
323
324 def __repr__(self):
325 return "<Traces len=%s>" % len(self)
326
327
328def _normalize_filename(filename):
329 filename = os.path.normcase(filename)
Brett Cannonf299abd2015-04-13 14:21:02 -0400330 if filename.endswith('.pyc'):
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100331 filename = filename[:-1]
332 return filename
333
334
Victor Stinnere492ae52016-03-22 12:58:23 +0100335class BaseFilter:
336 def __init__(self, inclusive):
337 self.inclusive = inclusive
338
339 def _match(self, trace):
340 raise NotImplementedError
341
342
343class Filter(BaseFilter):
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100344 def __init__(self, inclusive, filename_pattern,
Victor Stinnere492ae52016-03-22 12:58:23 +0100345 lineno=None, all_frames=False, domain=None):
346 super().__init__(inclusive)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100347 self.inclusive = inclusive
348 self._filename_pattern = _normalize_filename(filename_pattern)
349 self.lineno = lineno
350 self.all_frames = all_frames
Victor Stinnere492ae52016-03-22 12:58:23 +0100351 self.domain = domain
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100352
353 @property
354 def filename_pattern(self):
355 return self._filename_pattern
356
Victor Stinnere492ae52016-03-22 12:58:23 +0100357 def _match_frame_impl(self, filename, lineno):
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100358 filename = _normalize_filename(filename)
359 if not fnmatch.fnmatch(filename, self._filename_pattern):
360 return False
361 if self.lineno is None:
362 return True
363 else:
364 return (lineno == self.lineno)
365
366 def _match_frame(self, filename, lineno):
Victor Stinnere492ae52016-03-22 12:58:23 +0100367 return self._match_frame_impl(filename, lineno) ^ (not self.inclusive)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100368
369 def _match_traceback(self, traceback):
370 if self.all_frames:
Victor Stinnere492ae52016-03-22 12:58:23 +0100371 if any(self._match_frame_impl(filename, lineno)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100372 for filename, lineno in traceback):
373 return self.inclusive
374 else:
375 return (not self.inclusive)
376 else:
377 filename, lineno = traceback[0]
378 return self._match_frame(filename, lineno)
379
Victor Stinnere492ae52016-03-22 12:58:23 +0100380 def _match(self, trace):
381 domain, size, traceback = trace
382 res = self._match_traceback(traceback)
383 if self.domain is not None:
384 if self.inclusive:
385 return res and (domain == self.domain)
386 else:
387 return res or (domain != self.domain)
388 return res
389
390
391class DomainFilter(BaseFilter):
392 def __init__(self, inclusive, domain):
393 super().__init__(inclusive)
394 self._domain = domain
395
396 @property
397 def domain(self):
398 return self._domain
399
400 def _match(self, trace):
401 domain, size, traceback = trace
402 return (domain == self.domain) ^ (not self.inclusive)
403
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100404
405class Snapshot:
406 """
407 Snapshot of traces of memory blocks allocated by Python.
408 """
409
410 def __init__(self, traces, traceback_limit):
Victor Stinner733e50a2014-03-06 17:06:41 +0100411 # traces is a tuple of trace tuples: see _Traces constructor for
412 # the exact format
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100413 self.traces = _Traces(traces)
414 self.traceback_limit = traceback_limit
415
416 def dump(self, filename):
417 """
418 Write the snapshot into a file.
419 """
420 with open(filename, "wb") as fp:
421 pickle.dump(self, fp, pickle.HIGHEST_PROTOCOL)
422
423 @staticmethod
424 def load(filename):
425 """
426 Load a snapshot from a file.
427 """
428 with open(filename, "rb") as fp:
429 return pickle.load(fp)
430
431 def _filter_trace(self, include_filters, exclude_filters, trace):
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100432 if include_filters:
Victor Stinnere492ae52016-03-22 12:58:23 +0100433 if not any(trace_filter._match(trace)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100434 for trace_filter in include_filters):
435 return False
436 if exclude_filters:
Victor Stinnere492ae52016-03-22 12:58:23 +0100437 if any(not trace_filter._match(trace)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100438 for trace_filter in exclude_filters):
439 return False
440 return True
441
442 def filter_traces(self, filters):
443 """
444 Create a new Snapshot instance with a filtered traces sequence, filters
Victor Stinnere492ae52016-03-22 12:58:23 +0100445 is a list of Filter or DomainFilter instances. If filters is an empty
446 list, return a new Snapshot instance with a copy of the traces.
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100447 """
Victor Stinner8ce8ff92014-03-10 11:05:07 +0100448 if not isinstance(filters, Iterable):
449 raise TypeError("filters must be a list of filters, not %s"
450 % type(filters).__name__)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100451 if filters:
452 include_filters = []
453 exclude_filters = []
454 for trace_filter in filters:
455 if trace_filter.inclusive:
456 include_filters.append(trace_filter)
457 else:
458 exclude_filters.append(trace_filter)
459 new_traces = [trace for trace in self.traces._traces
460 if self._filter_trace(include_filters,
461 exclude_filters,
462 trace)]
463 else:
464 new_traces = self.traces._traces.copy()
465 return Snapshot(new_traces, self.traceback_limit)
466
467 def _group_by(self, key_type, cumulative):
468 if key_type not in ('traceback', 'filename', 'lineno'):
469 raise ValueError("unknown key_type: %r" % (key_type,))
470 if cumulative and key_type not in ('lineno', 'filename'):
471 raise ValueError("cumulative mode cannot by used "
472 "with key type %r" % key_type)
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100473
474 stats = {}
475 tracebacks = {}
476 if not cumulative:
477 for trace in self.traces._traces:
Victor Stinnere492ae52016-03-22 12:58:23 +0100478 domain, size, trace_traceback = trace
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100479 try:
480 traceback = tracebacks[trace_traceback]
481 except KeyError:
482 if key_type == 'traceback':
483 frames = trace_traceback
484 elif key_type == 'lineno':
485 frames = trace_traceback[:1]
486 else: # key_type == 'filename':
487 frames = ((trace_traceback[0][0], 0),)
488 traceback = Traceback(frames)
489 tracebacks[trace_traceback] = traceback
490 try:
491 stat = stats[traceback]
492 stat.size += size
493 stat.count += 1
494 except KeyError:
495 stats[traceback] = Statistic(traceback, size, 1)
496 else:
497 # cumulative statistics
498 for trace in self.traces._traces:
Victor Stinnere492ae52016-03-22 12:58:23 +0100499 domain, size, trace_traceback = trace
Victor Stinnered3b0bc2013-11-23 12:27:24 +0100500 for frame in trace_traceback:
501 try:
502 traceback = tracebacks[frame]
503 except KeyError:
504 if key_type == 'lineno':
505 frames = (frame,)
506 else: # key_type == 'filename':
507 frames = ((frame[0], 0),)
508 traceback = Traceback(frames)
509 tracebacks[frame] = traceback
510 try:
511 stat = stats[traceback]
512 stat.size += size
513 stat.count += 1
514 except KeyError:
515 stats[traceback] = Statistic(traceback, size, 1)
516 return stats
517
518 def statistics(self, key_type, cumulative=False):
519 """
520 Group statistics by key_type. Return a sorted list of Statistic
521 instances.
522 """
523 grouped = self._group_by(key_type, cumulative)
524 statistics = list(grouped.values())
525 statistics.sort(reverse=True, key=Statistic._sort_key)
526 return statistics
527
528 def compare_to(self, old_snapshot, key_type, cumulative=False):
529 """
530 Compute the differences with an old snapshot old_snapshot. Get
531 statistics as a sorted list of StatisticDiff instances, grouped by
532 group_by.
533 """
534 new_group = self._group_by(key_type, cumulative)
535 old_group = old_snapshot._group_by(key_type, cumulative)
536 statistics = _compare_grouped_stats(old_group, new_group)
537 statistics.sort(reverse=True, key=StatisticDiff._sort_key)
538 return statistics
539
540
541def take_snapshot():
542 """
543 Take a snapshot of traces of memory blocks allocated by Python.
544 """
545 if not is_tracing():
546 raise RuntimeError("the tracemalloc module must be tracing memory "
547 "allocations to take a snapshot")
548 traces = _get_traces()
549 traceback_limit = get_traceback_limit()
550 return Snapshot(traces, traceback_limit)