summaryrefslogtreecommitdiff
path: root/tools/perf/python/mem-phys-addr.py
blob: 53ef7e9a14b5dad36ee693eed7976d138db851d2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""mem-phys-addr.py: Resolve physical address samples"""
from __future__ import annotations
import argparse
import bisect
import collections
from dataclasses import dataclass
import re
from typing import (Dict, List, Optional)

import perf

@dataclass(frozen=True)
class IomemEntry:
    """Read from a line in /proc/iomem"""
    begin: int
    end: int
    indent: int
    label: str

    def __lt__(self, other) -> bool:
        if isinstance(other, int):
            return self.begin < other
        return self.begin < other.begin

    def __gt__(self, other) -> bool:
        if isinstance(other, int):
            return self.begin > other
        return self.begin > other.begin

# Physical memory layout from /proc/iomem. Key is the indent and then
# a list of ranges.
iomem: Dict[int, List[IomemEntry]] = collections.defaultdict(list)
# Child nodes from the iomem parent.
children: Dict[IomemEntry, List[IomemEntry]] = collections.defaultdict(list)
# Maximum indent seen before an entry in the iomem file.
_STATE: Dict[str, int] = {"max_indent": 0}
# Per-event counts for each range of memory.
event_counts: Dict[str, collections.Counter] = collections.defaultdict(collections.Counter)

def parse_iomem(iomem_path: str):
    """Populate iomem from iomem file"""
    with open(iomem_path, 'r', encoding='ascii') as f:
        for line in f:
            line = line.rstrip('\n')
            if not line or line.isspace():
                continue
            indent = 0
            while indent < len(line) and line[indent] == ' ':
                indent += 1
            _STATE["max_indent"] = max(_STATE["max_indent"], indent)
            m = re.split('-|:', line, maxsplit=2)
            if len(m) < 3:
                continue
            begin = int(m[0].strip(), 16)
            end = int(m[1].strip(), 16)
            label = m[2].strip()
            entry = IomemEntry(begin, end, indent, label)
            # Before adding entry, search for a parent node using its begin.
            if indent > 0:
                parent = find_memory_type(begin)
                assert parent, f"Given indent expected a parent for {label}"
                children[parent].append(entry)
            iomem[indent].append(entry)

def find_memory_type(phys_addr) -> Optional[IomemEntry]:
    """Search iomem for the range containing phys_addr with the maximum indent"""
    for i in range(_STATE["max_indent"], -1, -1):
        if i not in iomem:
            continue
        position = bisect.bisect_right(iomem[i], phys_addr)
        if position == 0:
            continue
        iomem_entry = iomem[i][position-1]
        if  iomem_entry.begin <= phys_addr <= iomem_entry.end:
            return iomem_entry
    return None

def _print_entries(entries, load_mem_type_cnt, total):
    """Print counts from parents down to their children"""
    for entry in sorted(entries,
                        key=lambda e: (-load_mem_type_cnt[e], e.begin)):
        count = load_mem_type_cnt[entry]
        if count > 0:
            mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}"
            percent = 100 * count / total
            print(f"{mem_type:<40}  {count:>10}  {percent:>10.1f}")
            _print_entries(children[entry], load_mem_type_cnt, total)

def print_memory_type():
    """Print the resolved memory types and their counts."""
    if not event_counts:
        print("No valid physical address samples found in perf data.")
        return

    for event_name, load_mem_type_cnt in event_counts.items():
        print(f"Event: {event_name}")
        print(f"{'Memory type':<40}  {'count':>10}  {'percentage':>10}")
        print(f"{'-' * 40:<40}  {'-' * 10:>10}  {'-' * 10:>10}")
        total = sum(load_mem_type_cnt.values())
        if total == 0:
            continue

        # Add count from children into the parent.
        for i in range(_STATE["max_indent"], -1, -1):
            if i not in iomem:
                continue
            for entry in iomem[i]:
                for child in children[entry]:
                    if load_mem_type_cnt[child] > 0:
                        load_mem_type_cnt[entry] += load_mem_type_cnt[child]

        _print_entries(iomem[0], load_mem_type_cnt, total)
        print()
if __name__ == "__main__":
    ap = argparse.ArgumentParser(description="Resolve physical address samples")
    ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
    ap.add_argument("--iomem", default="/proc/iomem", help="Path to iomem file")
    args = ap.parse_args()

    def process_event(sample):
        """Process a single sample event."""
        phys_addr = sample.sample_phys_addr or 0
        if not phys_addr:
            return
        entry = find_memory_type(phys_addr)
        if entry:
            event_name = str(sample.evsel)
            if event_name.startswith("evsel(") and event_name.endswith(")"):
                event_name = event_name[6:-1]
            event_counts[event_name][entry] += 1

    parse_iomem(args.iomem)
    perf.session(perf.data(args.input), sample=process_event).process_events()
    print_memory_type()