memory_analyzer/frontend/frontend_utils.py (75 lines of code) (raw):
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import curses
import os
import pickle
import subprocess
import click
import prettytable
from . import memanz_curses
def readable_size(i, snapshot=False):
"""
Pretty-print the integer `i` as a human-readable size representation.
"""
degree = 0
while i > 1024:
i = i / float(1024)
degree += 1
scales = ["B", "KB", "MB", "GB", "TB", "EB"]
if snapshot:
return f"{i:+.2f}{scales[degree]:>5}"
return f"{i:.2f}{scales[degree]:>5}"
def init_table(references, snapshot):
pt = prettytable.PrettyTable()
field_names = ["Object", "Count", "Size"]
if references:
field_names.extend(["References", "Backwards References"])
if snapshot:
field_names[1] += " Diff"
field_names[2] += " Diff"
pt.field_names = field_names
pt.align["Object"] = "l"
pt.align[pt.field_names[1]] = "r"
pt.align[pt.field_names[2]] = "r"
return pt
def format_summary_output(page):
"""
Formats in prettytable style the pympler summary.
"""
references = False
items = page.data
if not items:
items = [[f"No data to display for pid {page.pid}.", 0, 0]]
if any(len(item) == 5 for item in items):
references = True
snapshot = "Snapshot Differences" in page.title
pt = init_table(references, snapshot)
items.sort(key=lambda x: x[2], reverse=True)
for sublist in items:
if snapshot:
sublist[1] = f"{sublist[1]:+}"
sublist[2] = readable_size(sublist[2], snapshot)
if len(sublist) != len(pt.field_names):
# Fill in missing data with "".
sublist.extend(["" for _ in range(len(pt.field_names) - len(sublist))])
pt.add_row(sublist)
return pt
def table_as_list_of_strings(table):
string_table = table.get_string()
return string_table.split("\n")
def get_pages(filename):
"""
Read each pickled object in the given file as a page.
The objects will be lists of lists.
"""
with open(filename, "rb") as fd:
while True:
try:
yield pickle.load(fd)
except EOFError:
break
except pickle.UnpicklingError:
raise
def view(stdscr, pages):
"""
Format the data in a list of pretty tables that will be the pages of the
curses UI, then activate the nCurses UI.
"""
pages_as_tables = []
titles = []
for page in pages:
titles.append(page.title)
table = format_summary_output(page)
pages_as_tables.append(table_as_list_of_strings(table))
if pages_as_tables:
win = memanz_curses.Window(stdscr, pages_as_tables, titles)
win.run()
def initiate_curses(items):
curses.wrapper(view, items)
def echo_error(msg, *args, **kwargs):
click.echo(click.style(f"ERROR: {msg}", fg="red"), *args, **kwargs)
def echo_info(msg, *args, **kwargs):
click.echo(click.style(f"{msg}", fg="green"), *args, **kwargs)