scripts: Adopted ctx.py-related changes in other result scripts

- Adopted higher-level collect data structures:

  - high-level DwarfEntry/DwarfInfo class
  - high-level SymInfo class
  - high-level LineInfo class

  Note these had to be moved out of function scope due to pickling
  issues in perf.py/perfbd.py. These were only function-local to
  minimize scope leak so this fortunately was an easy change.

- Adopted better list-default patterns in Result types:

    def __new__(..., children=None):
        return Result(..., children if children is not None else [])

  A classic python footgun.

- Adopted notes rendering, though this is only used by ctx.py at the
  moment.

- Reverted to sorting children entries, for now.

  Unfortunately there's no easy way to sort the result entries in
  perf.py/perfbd.py before folding. Folding is going to make a mess
  of more complicated children anyways, so another solution is
  needed...

And some other shared miscellany.
This commit is contained in:
Christopher Haster
2024-12-01 16:59:51 -06:00
parent b4c79c53d2
commit 512cf5ad4b
9 changed files with 805 additions and 510 deletions
+148 -163
View File
@@ -160,61 +160,67 @@ def openio(path, mode='r', buffering=-1):
else:
return open(path, mode, buffering)
def collect_syms(obj_path, global_only=False, *,
objdump_path=None,
**args):
class SymInfo:
def __init__(self, syms):
self.syms = syms
class SymInfo:
def __init__(self, syms):
self.syms = syms
def get(self, k, d=None):
# allow lookup by both symbol and address
if isinstance(k, str):
# organize by symbol, note multiple symbols can share a name
if not hasattr(self, '_by_sym'):
self._by_sym = {}
for sym, addr, size in self.syms:
self._by_sym[sym] = (addr, size)
return self._by_sym.get(k, d)
def get(self, k, d=None):
# allow lookup by both symbol and address
if isinstance(k, str):
# organize by symbol, note multiple symbols can share a name
if not hasattr(self, '_by_sym'):
by_sym = {}
for sym, addr, size in self.syms:
if sym not in by_sym:
by_sym[sym] = []
if (addr, size) not in by_sym[sym]:
by_sym[sym].append((addr, size))
self._by_sym = by_sym
return self._by_sym.get(k, d)
else:
import bisect
# organize by address
if not hasattr(self, '_by_addr'):
# sort and keep largest/first when duplicates
syms = self.syms.copy()
syms.sort(key=lambda x: (x[1], -x[2], x[0]))
by_addr = []
for name, addr, size in syms:
if (len(by_addr) == 0
or by_addr[-1][0] != addr):
by_addr.append((name, addr, size))
self._by_addr = by_addr
# find sym by range
i = bisect.bisect(self._by_addr, k,
key=lambda x: x[1])
# check that we're actually in this sym's size
if i > 0 and k < self._by_addr[i-1][1]+self._by_addr[i-1][2]:
return self._by_addr[i-1][0]
else:
import bisect
return d
# organize by address
if not hasattr(self, '_by_addr'):
# sort and keep largest/first when duplicates
syms = self.syms.copy()
syms.sort(key=lambda x: (x[1], -x[2], x[0]))
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
self._by_addr = []
for name, addr, size in syms:
if (len(self._by_addr) == 0
or self._by_addr[-1][0] != addr):
self._by_addr.append((name, addr, size))
def __contains__(self, k):
return self.get(k) is not None
# find sym by range
i = bisect.bisect(self._by_addr, k, key=lambda x: x[1])
# check that we're actually in this sym's size
if i > 0 and k < self._by_addr[i-1][1]+self._by_addr[i-1][2]:
return self._by_addr[i-1]
else:
return None
def __len__(self):
return len(self.syms)
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
def __contains__(self, k):
return self.get(k) is not None
def __len__(self):
return len(self.syms)
def __iter__(self):
return iter(self.syms)
def __iter__(self):
return iter(self.syms)
def collect_syms(obj_path, global_only=False, *,
objdump_path=OBJDUMP_PATH,
**args):
symbol_pattern = re.compile(
'^(?P<addr>[0-9a-fA-F]+)'
' (?P<scope>.).*'
@@ -260,28 +266,6 @@ def collect_syms(obj_path, global_only=False, *,
def collect_dwarf_files(obj_path, *,
objdump_path=OBJDUMP_PATH,
**args):
class FileInfo:
def __init__(self, files):
self.files = files
def get(self, k, d=None):
return self.files.get(k, d)
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
def __contains__(self, k):
return self.get(k) is not None
def __len__(self):
return len(self.files)
def __iter__(self):
return (v for k, v in self.files.items())
line_pattern = re.compile(
'^\s*(?P<no>[0-9]+)'
'(?:\s+(?P<dir>[0-9]+))?'
@@ -322,7 +306,7 @@ def collect_dwarf_files(obj_path, *,
raise sp.CalledProcessError(proc.returncode, proc.args)
# simplify paths
files_ = {}
files_ = co.OrderedDict()
for no, file in files.items():
if os.path.commonpath([
os.getcwd(),
@@ -332,104 +316,104 @@ def collect_dwarf_files(obj_path, *,
files_[no] = os.path.abspath(file)
files = files_
return FileInfo(files)
return files
# each dwarf entry can have attrs and children entries
class DwarfEntry:
def __init__(self, level, off, tag, ats={}, children=[]):
self.level = level
self.off = off
self.tag = tag
self.ats = ats or {}
self.children = children or []
def get(self, k, d=None):
return self.ats.get(k, d)
def __getitem__(self, k):
return self.ats[k]
def __contains__(self, k):
return k in self.ats
def __repr__(self):
return '%s(%d, 0x%x, %r, %r)' % (
self.__class__.__name__,
self.level,
self.off,
self.tag,
self.ats)
@ft.cached_property
def name(self):
if 'DW_AT_name' in self:
name = self['DW_AT_name'].split(':')[-1].strip()
# prefix with struct/union/enum
if self.tag == 'DW_TAG_structure_type':
name = 'struct ' + name
elif self.tag == 'DW_TAG_union_type':
name = 'union ' + name
elif self.tag == 'DW_TAG_enumeration_type':
name = 'enum ' + name
return name
else:
return None
# a collection of dwarf entries
class DwarfInfo:
def __init__(self, entries):
self.entries = entries
def get(self, k, d=None):
# allow lookup by both offset and dwarf name
if not isinstance(k, str):
return self.entries.get(k, d)
else:
import difflib
# organize entries by name
if not hasattr(self, '_by_name'):
self._by_name = {}
for entry in self.entries.values():
if entry.name is not None:
self._by_name[entry.name] = entry
# exact match? avoid difflib if we can for speed
if k in self._by_name:
return self._by_name[k]
# find the best matching dwarf entry with difflib
#
# this can be different from the actual symbol because
# of optimization passes
else:
name, entry = max(
self._by_name.items(),
key=lambda entry: difflib.SequenceMatcher(
None, entry[0], k, False).ratio(),
default=(None, None))
return entry
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
def __contains__(self, k):
return self.get(k) is not None
def __len__(self):
return len(self.entries)
def __iter__(self):
return (v for k, v in self.entries.items())
def collect_dwarf_info(obj_path, filter=None, *,
objdump_path=OBJDUMP_PATH,
**args):
filter_, filter = filter, __builtins__.filter
# each dwarf entry can have attrs and children entries
class DwarfEntry:
def __init__(self, level, off, tag, ats={}, children=[]):
self.level = level
self.off = off
self.tag = tag
self.ats = ats or {}
self.children = children or []
def get(self, k, d=None):
return self.ats.get(k, d)
def __getitem__(self, k):
return self.ats[k]
def __contains__(self, k):
return k in self.ats
def __repr__(self):
return '%s(%d, 0x%x, %r, %r)' % (
self.__class__.__name__,
self.level,
self.off,
self.tag,
self.ats)
@ft.cached_property
def name(self):
if 'DW_AT_name' in self:
name = self['DW_AT_name'].split(':')[-1].strip()
# prefix with struct/union/enum
if self.tag == 'DW_TAG_structure_type':
name = 'struct ' + name
elif self.tag == 'DW_TAG_union_type':
name = 'union ' + name
elif self.tag == 'DW_TAG_enumeration_type':
name = 'enum ' + name
return name
else:
return None
# a collection of dwarf entries
class DwarfInfo:
def __init__(self, entries):
self.entries = entries
def get(self, k, d=None):
# allow lookup by both offset and dwarf name
if not isinstance(k, str):
return self.entries.get(k, d)
else:
import difflib
# organize entries by name
if not hasattr(self, '_by_name'):
self._by_name = {}
for entry in self.entries.values():
if entry.name is not None:
self._by_name[entry.name] = entry
# exact match? avoid difflib if we can for speed
if k in self._by_name:
return self._by_name[k]
# find the best matching dwarf entry with difflib
#
# this can be different from the actual symbol because
# of optimization passes
else:
name, entry = max(
self._by_name.items(),
key=lambda entry: difflib.SequenceMatcher(
None, entry[0], k, False).ratio(),
default=(None, None))
return entry
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
def __contains__(self, k):
return self.get(k) is not None
def __len__(self):
return len(self.entries)
def __iter__(self):
return (v for k, v in self.entries.items())
info_pattern = re.compile(
'^\s*(?:<(?P<level>[^>]*)>'
'\s*<(?P<off>[^>]*)>'
@@ -925,7 +909,8 @@ def table(Result, results, diff_results=None, *,
for r in results_}
names_ = list(table_.keys())
# only sort the children layer if explicitly requested
# sort the children layer
names_.sort()
if sort:
for k, reverse in reversed(sort):
names_.sort(