Skip to content

Commit

Permalink
Merge pull request #188 from ivanyu/ivanyu/gh-147-show-instances-of-type
Browse files Browse the repository at this point in the history
ui: show instances of type
  • Loading branch information
ivanyu authored Nov 20, 2022
2 parents 09e00a4 + bd7d4bb commit 3f89a47
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 29 deletions.
16 changes: 15 additions & 1 deletion integration_tests/test_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,21 @@ def _check_header(heap: Heap, dump_str_repr: bool) -> None:
assert (datetime.now(timezone.utc) - x).total_seconds() < 5 * 60
assert heap.header.flags.with_str_repr is dump_str_repr

for type_name in ["dict", "set", "list", "tuple"]:
for type_name in [
"dict",
"set",
"list",
"tuple",
"str",
"bytes",
"bytearray",
"int",
"bool",
"float",
"object",
"type",
"NoneType",
]:
dict_type_addr = heap.header.well_known_types[type_name]
assert heap.types[dict_type_addr] == type_name
if dump_str_repr:
Expand Down
30 changes: 23 additions & 7 deletions pyheap-ui/src/pyheap_ui/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
#
import argparse
import dataclasses
import functools
import logging
import mmap
import os
import time
import math
from typing import Optional, Any
from typing import Optional, Any, Dict
from flask import Flask, render_template, abort, request
from flask.json.provider import DefaultJSONProvider

from .heap_types import Heap, JsonObject
from .heap_types import Heap, JsonObject, Address
from .heap_reader import HeapReader
from .heap import (
provide_retained_heap_with_caching,
Expand Down Expand Up @@ -109,20 +110,25 @@ def objects(address: int) -> str:

obj = heap.objects[address]

well_known_type = next(
(k for k, v in heap.header.well_known_types.items() if v == obj.type), None
)

is_type_type = obj.type == heap.header.well_known_types.get("type")
type_instances = None
if is_type_type:
type_instances = [
addr for addr, obj in heap.objects.items() if obj.type == address
]
return render_template(
"objects.html",
tab_object_active=True,
address=address,
obj=obj,
type_address=obj.type,
type=heap.types[obj.type],
objects=heap.objects,
types=heap.types,
retained_heap=retained_heap,
well_known_type=well_known_type,
well_known_container_type=well_known_container_types().get(obj.type),
is_type_type=is_type_type,
type_instances=type_instances,
)


Expand Down Expand Up @@ -159,6 +165,16 @@ def big_number(size: int) -> str:
return "&nbsp;".join(chunks)


@functools.lru_cache
def well_known_container_types() -> Dict[Address, str]:
return {
heap.header.well_known_types["dict"]: "dict",
heap.header.well_known_types["list"]: "list",
heap.header.well_known_types["set"]: "set",
heap.header.well_known_types["tuple"]: "tuple",
}


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Heap viewer UI.", allow_abbrev=False)
parser.add_argument("--file", "-f", type=str, required=True, help="heap file name")
Expand Down
7 changes: 6 additions & 1 deletion pyheap-ui/src/pyheap_ui/heap_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ def _read_heap_object(self, address: Address) -> HeapObject:
type_ = self._read(Address)
size_ = self._read(UnsignedInt)

is_well_known_container_type = type_ in self._header.well_known_types.values()
is_well_known_container_type = type_ in {
self._header.well_known_types["dict"],
self._header.well_known_types["list"],
self._header.well_known_types["set"],
self._header.well_known_types["tuple"],
}

content: ObjectContent = None
extra_referents: Set[Address] = set()
Expand Down
32 changes: 27 additions & 5 deletions pyheap-ui/src/pyheap_ui/templates/objects.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@

<div id="object-panel">
<p><strong>Address:</strong> {{ address }}</p>
<p><strong>Type:</strong> <code>{{ type }}</code></p>
<p><strong>Type:</strong> {{ render_object_link(type_address) }} <code>{{ type }}</code></p>
<p><strong>Size:</strong> {{ obj.size | big_number | safe }}&nbsp;B</p>
<p><strong>Retained heap:</strong> {{ retained_heap.get_for_object(address) | big_number | safe }}&nbsp;B</p>
<p><strong>String representation:</strong> <span class="inline-object-str text-muted">
Expand Down Expand Up @@ -137,11 +137,11 @@ <h2 class="accordion-header" id="panelAttributesPublic">
</div>
</div>

{% if well_known_type is not none %}
{% if well_known_container_type is not none %}
<div class="accordion-item">
<h2 class="accordion-header" id="panelElements">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#panelElements-collapse" aria-expanded="false"
data-bs-target="#panelElements-collapse" aria-expanded="true"
aria-controls="panelElements-collapse">
Elements
</button>
Expand All @@ -150,12 +150,12 @@ <h2 class="accordion-header" id="panelElements">
aria-labelledby="panelElements">
<div class="accordion-body text-truncate overflow-scroll">
<ul class="list-unstyled">
{% if well_known_type == "dict" %}
{% if well_known_container_type == "dict" %}
{% for key, value in obj.content.items() %}
<li>{{ render_element(key) }}&nbsp;&nbsp;:&nbsp;&nbsp;{{ render_element(value) }}</li>
{% endfor %}
{% endif %}
{% if well_known_type in ["list", "set", "tuple"] %}
{% if well_known_container_type in ["list", "set", "tuple"] %}
{% for el in obj.content %}
<li>{{ render_element(el) }}</li>
{% endfor %}
Expand All @@ -166,6 +166,28 @@ <h2 class="accordion-header" id="panelElements">
</div>
{% endif %}

{% if is_type_type %}
<div class="accordion-item">
<h2 class="accordion-header" id="panelInstances">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#panelInstances-collapse" aria-expanded="false"
aria-controls="panelInstances-collapse">
Type instances
</button>
</h2>
<div id="panelInstances-collapse" class="accordion-collapse collapse"
aria-labelledby="panelInstances">
<div class="accordion-body text-truncate overflow-scroll">
<ul class="list-unstyled">
{% for addr in type_instances %}
<li>{{ render_element(addr) }}
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}

<div class="accordion-item">
<h2 class="accordion-header" id="panelReferents">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
Expand Down
46 changes: 31 additions & 15 deletions pyheap/src/dumper_inferior.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def __init__(self, *, f: BinaryIO, with_str_repr: bool) -> None:
if with_str_repr:
self._flags = self._flags | self._FLAG_WITH_STR_REPR

def write_header(self) -> None:
def write_header(self) -> List[Type]:
self._write_magic()

self.write_unsigned_int(self._VERSION)
Expand All @@ -95,19 +95,29 @@ def write_header(self) -> None:

self.write_unsigned_long(self._flags)

self._write_well_known_types()
return self._write_well_known_types()

def _write_well_known_types(self) -> None:
def _write_well_known_types(self) -> List[Type]:
well_known_types = {
"list": id(list),
"tuple": id(tuple),
"dict": id(dict),
"set": id(set),
"list": list,
"tuple": tuple,
"dict": dict,
"set": set,
"str": str,
"bytes": bytes,
"bytearray": bytearray,
"int": int,
"bool": bool,
"float": float,
"object": object,
"type": type,
"NoneType": type(None),
}
self.write_unsigned_int(len(well_known_types))
for type_name, type_addr in well_known_types.items():
for type_name, type_ in well_known_types.items():
self.write_long_string(type_name)
self.write_unsigned_long(type_addr)
self.write_unsigned_long(id(type_))
return list(well_known_types.values())

def write_footer(self) -> None:
self._write_magic()
Expand Down Expand Up @@ -172,19 +182,23 @@ def _dump_heap() -> str:
gc_tracked_objects = _get_gc_tracked_objects()
with open(heap_file, "wb") as f:
writer = _HeapWriter(f=f, with_str_repr=str_repr_len >= 0)
writer.write_header()
well_known_types = writer.write_header()

messages = []
all_locals = _write_threads_and_return_locals(writer, messages)

frequent_attributes = _write_frequent_attributes(writer)

attribute_errors_total = 0
common_types, objects_to_visit, attribute_errors = _write_common_types(
writer, frequent_attributes
)
(
common_types,
objects_to_visit,
attribute_errors,
) = _write_common_type_attributes(writer, frequent_attributes)
attribute_errors_total += attribute_errors

objects_to_visit.extend(well_known_types)

with closing(ProgressReporter(progress_file)) as progress_reporter:
types, visited, attribute_errors = _write_objects_and_return_types(
writer=writer,
Expand All @@ -201,6 +215,8 @@ def _dump_heap() -> str:
if attribute_errors_total > 0:
messages.append(f"Errors getting attribute: {attribute_errors_total}")

for t in well_known_types:
types[id(t)] = t.__name__
writer.write_unsigned_int(len(types))
for addr, type_name in types.items():
writer.write_unsigned_long(addr)
Expand Down Expand Up @@ -228,7 +244,7 @@ def _get_gc_tracked_objects() -> List[Any]:
invisible_objects.add(id(_dump_heap))
invisible_objects.add(id(_write_objects_and_return_types))
invisible_objects.add(id(_write_frequent_attributes))
invisible_objects.add(id(_write_common_types))
invisible_objects.add(id(_write_common_type_attributes))
invisible_objects.add(id(_write_threads_and_return_locals))
invisible_objects.add(id(_shadowed_dict_orig))
invisible_objects.add(id(_check_class_orig))
Expand Down Expand Up @@ -422,7 +438,7 @@ def _write_frequent_attributes(writer: _HeapWriter) -> Dict[str, int]:
return result


def _write_common_types(
def _write_common_type_attributes(
writer: _HeapWriter, frequent_attributes: Dict[str, int]
) -> Tuple[Set[Type], List[Any], int]:
"""Write attributes of "common" types.
Expand Down

0 comments on commit 3f89a47

Please sign in to comment.