|
1 | 1 | from collections import Counter |
| 2 | +from datetime import datetime |
2 | 3 | from typing import List |
3 | 4 | from typing import Optional |
4 | 5 | from typing import Tuple |
| 6 | +from unittest.mock import patch |
5 | 7 |
|
6 | 8 | import pytest |
7 | 9 |
|
8 | 10 | from memray import AllocatorType as AT |
| 11 | +from memray._metadata import Metadata |
| 12 | +from memray._stats import Stats |
| 13 | +from memray.reporters.stats import StatsReporter |
9 | 14 | from memray.reporters.stats import draw_histogram |
10 | 15 | from memray.reporters.stats import get_histogram_databins |
11 | 16 | from tests.utils import MockAllocationRecord |
@@ -62,6 +67,62 @@ def _generate_mock_allocations( |
62 | 67 | return snapshot |
63 | 68 |
|
64 | 69 |
|
| 70 | +# data generator for tests |
| 71 | +@pytest.fixture(scope="module") |
| 72 | +def fake_stats(): |
| 73 | + mem_allocation_list = [ |
| 74 | + 2500, |
| 75 | + 11000, |
| 76 | + 11000, |
| 77 | + 12000, |
| 78 | + 60000, |
| 79 | + 65000, |
| 80 | + 120000, |
| 81 | + 125000, |
| 82 | + 125000, |
| 83 | + 160000, |
| 84 | + 170000, |
| 85 | + 180000, |
| 86 | + 800000, |
| 87 | + 1500000, |
| 88 | + ] |
| 89 | + |
| 90 | + s = Stats( |
| 91 | + metadata=Metadata( |
| 92 | + start_time=datetime(2023, 1, 1, 1), |
| 93 | + end_time=datetime(2023, 1, 1, 2), |
| 94 | + total_allocations=sum(mem_allocation_list), |
| 95 | + total_frames=10, |
| 96 | + peak_memory=max(mem_allocation_list), |
| 97 | + command_line="fake stats", |
| 98 | + pid=123456, |
| 99 | + python_allocator="pymalloc", |
| 100 | + has_native_traces=False, |
| 101 | + ), |
| 102 | + total_num_allocations=20, |
| 103 | + total_memory_allocated=sum(mem_allocation_list), |
| 104 | + peak_memory_allocated=max(mem_allocation_list), |
| 105 | + allocation_count_by_size=Counter(mem_allocation_list), |
| 106 | + allocation_count_by_allocator={ |
| 107 | + AT.MALLOC.name: 1013, |
| 108 | + AT.REALLOC.name: 797, |
| 109 | + AT.CALLOC.name: 152, |
| 110 | + AT.MMAP.name: 4, |
| 111 | + }, |
| 112 | + top_locations_by_count=[ |
| 113 | + (("fake_func", "fake.py", 5), 20), |
| 114 | + (("fake_func2", "fake.py", 10), 50), |
| 115 | + (("__main__", "fake.py", 15), 1), |
| 116 | + ], |
| 117 | + top_locations_by_size=[ |
| 118 | + (("fake_func", "fake.py", 5), 5 * 2**20), |
| 119 | + (("fake_func2", "fake.py", 10), 3 * 2**10), |
| 120 | + (("__main__", "fake.py", 15), 4), |
| 121 | + ], |
| 122 | + ) |
| 123 | + return s |
| 124 | + |
| 125 | + |
65 | 126 | # tests begin here |
66 | 127 | def test_get_histogram_databins(): |
67 | 128 | # GIVEN |
@@ -279,3 +340,102 @@ def test_draw_histogram_invalid_input(): |
279 | 340 | # test#3 - Invalid hist_scale_factor value |
280 | 341 | with pytest.raises(ValueError): |
281 | 342 | _ = draw_histogram([100, 200, 300], bins=5, hist_scale_factor=0) |
| 343 | + |
| 344 | + |
| 345 | +def test_stats_output(fake_stats): |
| 346 | + reporter = StatsReporter(fake_stats, 5) |
| 347 | + with patch("builtins.print") as mocked_print: |
| 348 | + with patch("rich.print", print): |
| 349 | + reporter.render() |
| 350 | + expected = ( |
| 351 | + "📏 [bold]Total allocations:[/]\n" |
| 352 | + "\t20\n" |
| 353 | + "\n" |
| 354 | + "📦 [bold]Total memory allocated:[/]\n" |
| 355 | + "\t3.187MB\n" |
| 356 | + "\n" |
| 357 | + "📊 [bold]Histogram of allocation size:[/]\n" |
| 358 | + "\tmin: 2.441KB\n" |
| 359 | + "\t----------------------------------------\n" |
| 360 | + "\t< 4.628KB : 1 ▇▇▇▇▇\n" |
| 361 | + "\t< 8.775KB : 0 \n" |
| 362 | + "\t< 16.637KB : 3 ▇▇▇▇▇▇▇▇▇▇▇▇▇\n" |
| 363 | + "\t< 31.542KB : 0 \n" |
| 364 | + "\t< 59.802KB : 1 ▇▇▇▇▇\n" |
| 365 | + "\t< 113.378KB: 1 ▇▇▇▇▇\n" |
| 366 | + "\t< 214.954KB: 6 ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇\n" |
| 367 | + "\t< 407.531KB: 0 \n" |
| 368 | + "\t< 772.638KB: 0 \n" |
| 369 | + "\t<=1.431MB : 2 ▇▇▇▇▇▇▇▇▇\n" |
| 370 | + "\t----------------------------------------\n" |
| 371 | + "\tmax: 1.431MB\n" |
| 372 | + "\n" |
| 373 | + "📂 [bold]Allocator type distribution:[/]\n" |
| 374 | + "\t MALLOC: 1013\n" |
| 375 | + "\t REALLOC: 797\n" |
| 376 | + "\t CALLOC: 152\n" |
| 377 | + "\t MMAP: 4\n" |
| 378 | + "\n" |
| 379 | + "🥇 [bold]Top 5 largest allocating locations (by size):[/]\n" |
| 380 | + "\t- fake_func:fake.py:5 -> 5.000MB\n" |
| 381 | + "\t- fake_func2:fake.py:10 -> 3.000KB\n" |
| 382 | + "\t- __main__:fake.py:15 -> 4.000B\n" |
| 383 | + "\n" |
| 384 | + "🥇 [bold]Top 5 largest allocating locations (by number of allocations):[/]\n" |
| 385 | + "\t- fake_func:fake.py:5 -> 20\n" |
| 386 | + "\t- fake_func2:fake.py:10 -> 50\n" |
| 387 | + "\t- __main__:fake.py:15 -> 1" |
| 388 | + ) |
| 389 | + printed = "\n".join(" ".join(x[0]) for x in mocked_print.call_args_list) |
| 390 | + assert expected == printed |
| 391 | + |
| 392 | + |
| 393 | +def test_stats_output_json(fake_stats, tmp_path): |
| 394 | + reporter = StatsReporter(fake_stats, 5) |
| 395 | + with patch("json.dump") as json_dump: |
| 396 | + reporter.render(to_json=True, result_path=tmp_path) |
| 397 | + expected = { |
| 398 | + "total_num_allocations": 20, |
| 399 | + "total_bytes_allocated": 3341500, |
| 400 | + "allocation_size_histogram": [ |
| 401 | + (4739, 1), |
| 402 | + (8986, 0), |
| 403 | + (17036, 3), |
| 404 | + (32299, 0), |
| 405 | + (61237, 1), |
| 406 | + (116099, 1), |
| 407 | + (220113, 6), |
| 408 | + (417312, 0), |
| 409 | + (791181, 0), |
| 410 | + (1500000, 2), |
| 411 | + ], |
| 412 | + "allocator_type_distribution": [ |
| 413 | + ("MALLOC", 1013), |
| 414 | + ("REALLOC", 797), |
| 415 | + ("CALLOC", 152), |
| 416 | + ("MMAP", 4), |
| 417 | + ], |
| 418 | + "top_allocations_by_size": [ |
| 419 | + {"location": "fake_func:fake.py:5", "size": 5242880}, |
| 420 | + {"location": "fake_func2:fake.py:10", "size": 3072}, |
| 421 | + {"location": "__main__:fake.py:15", "size": 4}, |
| 422 | + ], |
| 423 | + "top_allocations_by_count": [ |
| 424 | + {"location": "fake_func:fake.py:5", "count": 20}, |
| 425 | + {"location": "fake_func2:fake.py:10", "count": 50}, |
| 426 | + {"location": "__main__:fake.py:15", "count": 1}, |
| 427 | + ], |
| 428 | + "metadata": { |
| 429 | + "start_time": "2023-01-01 01:00:00", |
| 430 | + "end_time": "2023-01-01 02:00:00", |
| 431 | + "total_allocations": 3341500, |
| 432 | + "total_frames": 10, |
| 433 | + "peak_memory": 1500000, |
| 434 | + "command_line": "fake stats", |
| 435 | + "pid": 123456, |
| 436 | + "python_allocator": "pymalloc", |
| 437 | + "has_native_traces": False, |
| 438 | + }, |
| 439 | + } |
| 440 | + actual = json_dump.call_args[0][0] |
| 441 | + assert expected == actual |
0 commit comments