Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf script python: Improve physical mem type resolution

Previously system RAM and persistent memory were hard code matched,
change so that the label of the memory region is just read from
/proc/iomem. This avoids frequent N/A samples.

Change the /proc/iomem reading, event processing and output so that
nested entries appear and their counts count toward their parent. As
labels may be repeated, include the memory ranges in the output to make
it clear why, for example, "System RAM" appears twice.

Before:

Event: mem_inst_retired.all_loads:P
Memory type count percentage
---------------------------------------- ---------- ----------
System RAM 9460 96.5%
N/A 998 3.5%

After:

Event: mem_inst_retired.all_loads:P
Memory type count percentage
---------------------------------------- ---------- ----------
100000000-105f7fffff : System RAM 36741 96.5
841400000-8416599ff : Kernel data 89 0.2
840800000-8412a6fff : Kernel rodata 60 0.2
841ebe000-8423fffff : Kernel bss 34 0.1
0-fff : Reserved 1345 3.5
100000-89dd9fff : System RAM 2 0.0

Before:

Event: mem_inst_retired.any:P
Memory type count percentage
---------------------------------------- ----------- -----------
System RAM 9460 90.5%
N/A 998 9.5%

After:

Event: mem_inst_retired.any:P
Memory type count percentage
---------------------------------------- ---------- ----------
100000000-105f7fffff : System RAM 9460 90.5
841400000-8416599ff : Kernel data 45 0.4
840800000-8412a6fff : Kernel rodata 19 0.2
841ebe000-8423fffff : Kernel bss 12 0.1
0-fff : Reserved 998 9.5

The code has been updated to python 3 with type hints and resolving
issues reported by mypy and pylint. Tabs are swapped to spaces as
preferred in PEP8, because most lines of code were modified (of this
small file) and this makes pylint significantly less noisy.

Committer testing:

root@number:/tmp# grep -m1 "model name" /proc/cpuinfo
model name : Intel(R) Core(TM) i7-14700K
root@number:/tmp#
root@number:/tmp# perf script mem-phys-addr -a find /
/bin
/lib
/lib64
/sbin
Warning:
744 out of order events recorded.
Event: cpu_core/mem_inst_retired.all_loads/P
Memory type count percentage
---------------------------------------- ---------- ----------
100000000-8bfbfffff : System RAM 364561 76.5
621400000-6223a6fff : Kernel rodata 10474 2.2
622400000-62283d4bf : Kernel data 4828 1.0
623304000-6237fffff : Kernel bss 1063 0.2
620000000-6213fffff : Kernel code 98 0.0
0-fff : Reserved 111480 23.4
100000-2b0ca017 : System RAM 337 0.1
2fbad000-30d92fff : System RAM 44 0.0
2c79d000-2fbabfff : System RAM 30 0.0
30d94000-316d5fff : System RAM 16 0.0
2b131a58-2c71dfff : System RAM 7 0.0
root@number:/tmp#

Signed-off-by: Ian Rogers <irogers@google.com>
Acked-by: Kan Liang <kan.liang@linux.intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20241119180130.19160-1-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Ian Rogers and committed by
Arnaldo Carvalho de Melo
d78e20c0 b2b95a2d

+103 -76
+103 -76
tools/perf/scripts/python/mem-phys-addr.py
··· 3 3 # 4 4 # Copyright (c) 2018, Intel Corporation. 5 5 6 - from __future__ import division 7 - from __future__ import print_function 8 - 9 6 import os 10 7 import sys 11 - import struct 12 8 import re 13 9 import bisect 14 10 import collections 11 + from dataclasses import dataclass 12 + from typing import (Dict, Optional) 15 13 16 14 sys.path.append(os.environ['PERF_EXEC_PATH'] + \ 17 - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') 15 + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') 18 16 19 - #physical address ranges for System RAM 20 - system_ram = [] 21 - #physical address ranges for Persistent Memory 22 - pmem = [] 23 - #file object for proc iomem 24 - f = None 25 - #Count for each type of memory 26 - load_mem_type_cnt = collections.Counter() 27 - #perf event name 28 - event_name = None 17 + @dataclass(frozen=True) 18 + class IomemEntry: 19 + """Read from a line in /proc/iomem""" 20 + begin: int 21 + end: int 22 + indent: int 23 + label: str 24 + 25 + # Physical memory layout from /proc/iomem. Key is the indent and then 26 + # a list of ranges. 27 + iomem: Dict[int, list[IomemEntry]] = collections.defaultdict(list) 28 + # Child nodes from the iomem parent. 29 + children: Dict[IomemEntry, set[IomemEntry]] = collections.defaultdict(set) 30 + # Maximum indent seen before an entry in the iomem file. 31 + max_indent: int = 0 32 + # Count for each range of memory. 33 + load_mem_type_cnt: Dict[IomemEntry, int] = collections.Counter() 34 + # Perf event name set from the first sample in the data. 35 + event_name: Optional[str] = None 29 36 30 37 def parse_iomem(): 31 - global f 32 - f = open('/proc/iomem', 'r') 33 - for i, j in enumerate(f): 34 - m = re.split('-|:',j,2) 35 - if m[2].strip() == 'System RAM': 36 - system_ram.append(int(m[0], 16)) 37 - system_ram.append(int(m[1], 16)) 38 - if m[2].strip() == 'Persistent Memory': 39 - pmem.append(int(m[0], 16)) 40 - pmem.append(int(m[1], 16)) 38 + """Populate iomem from /proc/iomem file""" 39 + global iomem 40 + global max_indent 41 + global children 42 + with open('/proc/iomem', 'r', encoding='ascii') as f: 43 + for line in f: 44 + indent = 0 45 + while line[indent] == ' ': 46 + indent += 1 47 + if indent > max_indent: 48 + max_indent = indent 49 + m = re.split('-|:', line, 2) 50 + begin = int(m[0], 16) 51 + end = int(m[1], 16) 52 + label = m[2].strip() 53 + entry = IomemEntry(begin, end, indent, label) 54 + # Before adding entry, search for a parent node using its begin. 55 + if indent > 0: 56 + parent = find_memory_type(begin) 57 + assert parent, f"Given indent expected a parent for {label}" 58 + children[parent].add(entry) 59 + iomem[indent].append(entry) 60 + 61 + def find_memory_type(phys_addr) -> Optional[IomemEntry]: 62 + """Search iomem for the range containing phys_addr with the maximum indent""" 63 + for i in range(max_indent, -1, -1): 64 + if i not in iomem: 65 + continue 66 + position = bisect.bisect_right(iomem[i], phys_addr, 67 + key=lambda entry: entry.begin) 68 + if position is None: 69 + continue 70 + iomem_entry = iomem[i][position-1] 71 + if iomem_entry.begin <= phys_addr <= iomem_entry.end: 72 + return iomem_entry 73 + print(f"Didn't find {phys_addr}") 74 + return None 41 75 42 76 def print_memory_type(): 43 - print("Event: %s" % (event_name)) 44 - print("%-40s %10s %10s\n" % ("Memory type", "count", "percentage"), end='') 45 - print("%-40s %10s %10s\n" % ("----------------------------------------", 46 - "-----------", "-----------"), 47 - end=''); 48 - total = sum(load_mem_type_cnt.values()) 49 - for mem_type, count in sorted(load_mem_type_cnt.most_common(), \ 50 - key = lambda kv: (kv[1], kv[0]), reverse = True): 51 - print("%-40s %10d %10.1f%%\n" % 52 - (mem_type, count, 100 * count / total), 53 - end='') 77 + print(f"Event: {event_name}") 78 + print(f"{'Memory type':<40} {'count':>10} {'percentage':>10}") 79 + print(f"{'-' * 40:<40} {'-' * 10:>10} {'-' * 10:>10}") 80 + total = sum(load_mem_type_cnt.values()) 81 + # Add count from children into the parent. 82 + for i in range(max_indent, -1, -1): 83 + if i not in iomem: 84 + continue 85 + for entry in iomem[i]: 86 + global children 87 + for child in children[entry]: 88 + if load_mem_type_cnt[child] > 0: 89 + load_mem_type_cnt[entry] += load_mem_type_cnt[child] 90 + 91 + def print_entries(entries): 92 + """Print counts from parents down to their children""" 93 + global children 94 + for entry in sorted(entries, 95 + key = lambda entry: load_mem_type_cnt[entry], 96 + reverse = True): 97 + count = load_mem_type_cnt[entry] 98 + if count > 0: 99 + mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}" 100 + percent = 100 * count / total 101 + print(f"{mem_type:<40} {count:>10} {percent:>10.1f}") 102 + print_entries(children[entry]) 103 + 104 + print_entries(iomem[0]) 54 105 55 106 def trace_begin(): 56 - parse_iomem() 107 + parse_iomem() 57 108 58 109 def trace_end(): 59 - print_memory_type() 60 - f.close() 61 - 62 - def is_system_ram(phys_addr): 63 - #/proc/iomem is sorted 64 - position = bisect.bisect(system_ram, phys_addr) 65 - if position % 2 == 0: 66 - return False 67 - return True 68 - 69 - def is_persistent_mem(phys_addr): 70 - position = bisect.bisect(pmem, phys_addr) 71 - if position % 2 == 0: 72 - return False 73 - return True 74 - 75 - def find_memory_type(phys_addr): 76 - if phys_addr == 0: 77 - return "N/A" 78 - if is_system_ram(phys_addr): 79 - return "System RAM" 80 - 81 - if is_persistent_mem(phys_addr): 82 - return "Persistent Memory" 83 - 84 - #slow path, search all 85 - f.seek(0, 0) 86 - for j in f: 87 - m = re.split('-|:',j,2) 88 - if int(m[0], 16) <= phys_addr <= int(m[1], 16): 89 - return m[2] 90 - return "N/A" 110 + print_memory_type() 91 111 92 112 def process_event(param_dict): 93 - name = param_dict["ev_name"] 94 - sample = param_dict["sample"] 95 - phys_addr = sample["phys_addr"] 113 + if "sample" not in param_dict: 114 + return 96 115 97 - global event_name 98 - if event_name == None: 99 - event_name = name 100 - load_mem_type_cnt[find_memory_type(phys_addr)] += 1 116 + sample = param_dict["sample"] 117 + if "phys_addr" not in sample: 118 + return 119 + 120 + phys_addr = sample["phys_addr"] 121 + entry = find_memory_type(phys_addr) 122 + if entry: 123 + load_mem_type_cnt[entry] += 1 124 + 125 + global event_name 126 + if event_name is None: 127 + event_name = param_dict["ev_name"]