Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions news/857.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deduplicate rows in the table reporter that had identical displayed fields but
different underlying call stacks. Also add a ``--split-threads`` option to the
table reporter, matching the flamegraph reporter's existing option.
6 changes: 6 additions & 0 deletions src/memray/commands/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ def __init__(self) -> None:

def prepare_parser(self, parser: argparse.ArgumentParser) -> None:
super().prepare_parser(parser)
parser.add_argument(
"--split-threads",
help="Do not merge allocations across threads",
action="store_true",
default=False,
)
parser.add_argument(
"--no-web",
help="Use local assets instead of fetching from CDN",
Expand Down
21 changes: 13 additions & 8 deletions src/memray/reporters/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def from_snapshot(
native_traces: bool,
**kwargs: Any,
) -> "TableReporter":
result = []
# Aggregate records that share the same displayed fields, since the
# table only shows the top stack frame and records with different full
# call stacks can map to the same displayed row.
aggregated: Dict[tuple, Dict[str, Any]] = {}
for record in allocations:
stack_trace = (
list(record.hybrid_stack_trace(max_stacks=1))
Expand All @@ -46,17 +49,21 @@ def from_snapshot(
stack = f"{function} at {file}:{line}"

allocator = AllocatorType(record.allocator)
result.append(
{
"tid": format_thread_name(record),
tid = format_thread_name(record)
key = (tid, allocator.name.lower(), stack)
if key in aggregated:
aggregated[key]["size"] += record.size
aggregated[key]["n_allocations"] += record.n_allocations
else:
aggregated[key] = {
"tid": tid,
"size": record.size,
"allocator": allocator.name.lower(),
"n_allocations": record.n_allocations,
"stack_trace": html.escape(stack),
}
)

return cls(result, memory_records=memory_records)
return cls(list(aggregated.values()), memory_records=memory_records)

def render(
self,
Expand All @@ -67,8 +74,6 @@ def render(
inverted: bool,
no_web: bool = False,
) -> None:
if not merge_threads:
raise NotImplementedError("TableReporter only supports merged threads.")
if inverted:
raise NotImplementedError(
"TableReporter does not support inverted argument"
Expand Down
43 changes: 26 additions & 17 deletions tests/integration/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1246,23 +1246,32 @@ def test_reads_from_correct_file(self, tmp_path, simple_test_file):
assert output_file.exists()
assert str(source_file) in output_file.read_text()

def test_no_split_threads(self, tmp_path):
# GIVEN/WHEN/THEN
with pytest.raises(subprocess.CalledProcessError):
subprocess.run(
[
sys.executable,
"-m",
"memray",
"table",
"--split-threads",
"somefile",
],
cwd=str(tmp_path),
check=True,
capture_output=True,
text=True,
)
def test_split_threads(self, tmp_path, simple_test_file):
# GIVEN
results_file, source_file = generate_sample_results(
tmp_path, simple_test_file, native=True
)

# WHEN
subprocess.run(
[
sys.executable,
"-m",
"memray",
"table",
"--split-threads",
str(results_file),
],
cwd=str(tmp_path),
check=True,
capture_output=True,
text=True,
)

# THEN
output_file = tmp_path / "memray-table-result.html"
assert output_file.exists()
assert str(source_file) in output_file.read_text()


class TestReporterSubCommands:
Expand Down
Loading