rykit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rykit-0.1.0/PKG-INFO +10 -0
- rykit-0.1.0/README.md +0 -0
- rykit-0.1.0/pyproject.toml +20 -0
- rykit-0.1.0/src/rykit/__init__.py +2 -0
- rykit-0.1.0/src/rykit/cmd.py +48 -0
- rykit-0.1.0/src/rykit/intel_tools.py +10 -0
- rykit-0.1.0/src/rykit/linux_tools.py +108 -0
- rykit-0.1.0/src/rykit/perf_sample.py +172 -0
- rykit-0.1.0/src/rykit/perf_sample_amd.py +29 -0
- rykit-0.1.0/src/rykit/perf_sample_intel.py +193 -0
rykit-0.1.0/PKG-INFO
ADDED
rykit-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rykit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Ryan Kosta", email = "rkosta@ucsd.edu" }
|
|
8
|
+
]
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[tool.uv]
|
|
14
|
+
packages = ["rykit"]
|
|
15
|
+
src = "src"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["uv_build>=0.9.18,<0.10.0"]
|
|
20
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
def run_command_read_stderr(cmd: str) -> str:
|
|
3
|
+
"""
|
|
4
|
+
Run a shell command and capture stderr output.
|
|
5
|
+
|
|
6
|
+
Args:
|
|
7
|
+
cmd (str): The shell command to execute.
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
str: The stderr output of the command (perf writes stats here).
|
|
11
|
+
"""
|
|
12
|
+
print(f"running cmd: {cmd}")
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
|
15
|
+
)
|
|
16
|
+
output: str = result.stderr # perf outputs stats to stderr
|
|
17
|
+
if result.returncode == 124: # 124 is timeout exit code
|
|
18
|
+
print("Command timed out as expected.")
|
|
19
|
+
elif result.returncode != 0:
|
|
20
|
+
# output is stderr
|
|
21
|
+
print(output)
|
|
22
|
+
raise ValueError(f"Command failed with exit code {result.returncode}.")
|
|
23
|
+
else:
|
|
24
|
+
print("command returned 0")
|
|
25
|
+
return output
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_command_read_stdout(cmd: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Run a shell command and capture stdout output.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
cmd (str): The shell command to execute.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
str: The stderr output of the command (perf writes stats here).
|
|
37
|
+
"""
|
|
38
|
+
print(f"running cmd: {cmd}")
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
|
41
|
+
)
|
|
42
|
+
output: str = result.stdout
|
|
43
|
+
if result.returncode == 124: # 124 is timeout exit code
|
|
44
|
+
print("Command timed out as expected.")
|
|
45
|
+
elif result.returncode != 0:
|
|
46
|
+
print(result.stderr)
|
|
47
|
+
raise ValueError(f"Command failed with exit code {result.returncode}.")
|
|
48
|
+
return output
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import Dict,List
|
|
2
|
+
from rykit.cmd import run_command_read_stdout
|
|
3
|
+
def lscpu() -> Dict[str, str]:
|
|
4
|
+
"""
|
|
5
|
+
Parse the output of `lscpu` into a dictionary.
|
|
6
|
+
|
|
7
|
+
Returns:
|
|
8
|
+
Dict[str, str]: Mapping of lscpu fields to their values.
|
|
9
|
+
"""
|
|
10
|
+
res = {}
|
|
11
|
+
for line in run_command_read_stdout("lscpu").split("\n"):
|
|
12
|
+
if ":" not in line:
|
|
13
|
+
continue
|
|
14
|
+
segments = [x.strip() for x in line.split(":")]
|
|
15
|
+
res[segments[0]] = segments[1]
|
|
16
|
+
return res
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def normalize(x: str, units: Dict[str, int], default: str):
|
|
20
|
+
"""
|
|
21
|
+
Normalize a string containing a number and a unit into the default unit.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
x (str): Input string, e.g., "64KB".
|
|
25
|
+
units (Dict[str, int]): Dictionary mapping unit strings to their scale factors.
|
|
26
|
+
default (str): The default unit to normalize to.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
int: The value converted to the default unit.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
AssertionError: If the default unit is not in the units dictionary.
|
|
33
|
+
ValueError: If the input string does not contain a recognized unit.
|
|
34
|
+
"""
|
|
35
|
+
assert default in units
|
|
36
|
+
units_longest_first = sorted(list(units.keys()), key=len, reverse=True)
|
|
37
|
+
for unit in units_longest_first:
|
|
38
|
+
if unit in x:
|
|
39
|
+
val = int(x.split(unit)[0].strip())
|
|
40
|
+
scale_factor = units[unit] / units[default]
|
|
41
|
+
return int(val * scale_factor)
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"{x} did not contain a valid unit out of choices {units_longest_first}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def lscpu_cache() -> Dict[str, Dict[str, str]]:
|
|
48
|
+
"""
|
|
49
|
+
Parse `lscpu -C` output to get per-CPU cache and CPU info.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dict[str, Dict[str, str]]: Mapping from CPU ID to its properties.
|
|
53
|
+
"""
|
|
54
|
+
rows = run_command_read_stdout("lscpu -C").split("\n")
|
|
55
|
+
col_names = rows[0].split()
|
|
56
|
+
res = {}
|
|
57
|
+
for row in rows[1:]:
|
|
58
|
+
cells = row.split()
|
|
59
|
+
if len(cells) < 1:
|
|
60
|
+
continue
|
|
61
|
+
key = cells[0]
|
|
62
|
+
val = {k: v for k, v in zip(col_names[1:], cells[1:])}
|
|
63
|
+
res[key] = val
|
|
64
|
+
return res
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_range_list(s: str) -> List[int]:
|
|
68
|
+
"""
|
|
69
|
+
Convert a string representing ranges into a list of integers.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
s (str): Range string, e.g., "0-3,5,7-8".
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List[int]: Expanded list of integers from the range string.
|
|
76
|
+
"""
|
|
77
|
+
result = []
|
|
78
|
+
for part in s.split(","):
|
|
79
|
+
if "-" in part:
|
|
80
|
+
start, end = map(int, part.split("-"))
|
|
81
|
+
result.extend(range(start, end + 1))
|
|
82
|
+
else:
|
|
83
|
+
result.append(int(part))
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_socket(skt: int) -> List[int]:
|
|
88
|
+
"""
|
|
89
|
+
Get the list of CPU IDs belonging to a specific NUMA socket.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
skt (int): NUMA socket index (0-based).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List[int]: List of CPU IDs for the socket.
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
AssertionError: If the socket index is invalid.
|
|
99
|
+
"""
|
|
100
|
+
assert skt >= 0
|
|
101
|
+
|
|
102
|
+
info = lscpu()
|
|
103
|
+
node_ct = int(info["NUMA node(s)"])
|
|
104
|
+
|
|
105
|
+
assert skt < node_ct
|
|
106
|
+
nodestr = info[f"NUMA node{skt} CPU(s)"]
|
|
107
|
+
|
|
108
|
+
return parse_range_list(nodestr)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from rykit.cmd import run_command_read_stdout
|
|
2
|
+
from rykit.cmd import run_command_read_stderr
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
def set_perf_event_paranoid(level: int):
|
|
6
|
+
"""
|
|
7
|
+
Sets the kernel's perf_event_paranoid level.
|
|
8
|
+
|
|
9
|
+
The perf_event_paranoid setting controls the restrictions on
|
|
10
|
+
performance monitoring for non-root users:
|
|
11
|
+
-1 : No restrictions
|
|
12
|
+
0 : Normal access, but system-wide tracepoints may be restricted
|
|
13
|
+
1 : Restricted access to CPU performance events
|
|
14
|
+
2 : Disallow CPU performance events for unprivileged users
|
|
15
|
+
3 : Maximum restriction (default on many systems)
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
level (int): The desired paranoid level (-1 to 3).
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
AssertionError: If level is not an int or not in the allowed range.
|
|
22
|
+
"""
|
|
23
|
+
assert type(level) == int
|
|
24
|
+
assert -1 <= level and level <= 3, f"tried to set perf_event_paranoid to {level}, allowed values are -1 through 3"
|
|
25
|
+
cmd = f"sudo sysctl -w kernel.perf_event_paranoid={level}"
|
|
26
|
+
run_command_read_stdout(cmd)
|
|
27
|
+
|
|
28
|
+
def get_perf_event_paranoid() -> int:
|
|
29
|
+
"""
|
|
30
|
+
Returns the current kernel perf_event_paranoid level
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
with open("/proc/sys/kernel/perf_event_paranoid", "r") as f:
|
|
34
|
+
return int(f.read().strip())
|
|
35
|
+
except Exception as e:
|
|
36
|
+
raise RuntimeError(f"Failed to read perf_event_paranoid: {e}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def interpret_umask(binval: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Convert a binary string umask into its hexadecimal representation.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
binval (str): A string representing a binary number (e.g., "1101").
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
str: The hexadecimal representation of the binary umask
|
|
48
|
+
(e.g., "0xd" for "1101").
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If `binval` is not a valid binary string.
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
val: int = int(binval, 2)
|
|
55
|
+
except:
|
|
56
|
+
raise ValueError(f"mask {binval} was not a valid binary string")
|
|
57
|
+
|
|
58
|
+
if val > 255:
|
|
59
|
+
raise ValueError(f"{binval} was more then 8 bits")
|
|
60
|
+
|
|
61
|
+
hex_str = str(hex(val))
|
|
62
|
+
return hex_str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def interpret_core_events(output: str, core_events: List[str]) -> Dict[str, int]:
|
|
68
|
+
"""
|
|
69
|
+
Parse perf output for core events.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
output (str): Raw stderr output from perf.
|
|
73
|
+
core_events (List[str]): List of event names to extract.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dict[str,int]: Mapping of event name -> event counter value.
|
|
77
|
+
"""
|
|
78
|
+
lines = output.split("\n")
|
|
79
|
+
res: Dict[str, int] = {}
|
|
80
|
+
for line in lines:
|
|
81
|
+
for event in core_events:
|
|
82
|
+
if event in line:
|
|
83
|
+
# remove name of event
|
|
84
|
+
valstr = line.split(event)[0]
|
|
85
|
+
valstr = valstr.strip()
|
|
86
|
+
|
|
87
|
+
# remove commas
|
|
88
|
+
valstr = valstr.replace(",", "")
|
|
89
|
+
|
|
90
|
+
if "Byte" in valstr:
|
|
91
|
+
|
|
92
|
+
valstr = valstr.split("Byte")[0]
|
|
93
|
+
|
|
94
|
+
# TODO this is debatable whether you want this,
|
|
95
|
+
# Many events which are labeled byte
|
|
96
|
+
# cast from cache line to byte (ie: *64)
|
|
97
|
+
# so casting back (ie /64) is natural in most cases
|
|
98
|
+
val = int(int(valstr) / 64)
|
|
99
|
+
else:
|
|
100
|
+
val = int(valstr)
|
|
101
|
+
res[event] = val
|
|
102
|
+
return res
|
|
103
|
+
def interpret_per_core_event(output:str,event:str,socket:int) -> Dict[str,int]:
|
|
104
|
+
data : Dict[int,Dict[str,int]] = {skt:{} for skt in range(2)}
|
|
105
|
+
for line in output.split("\n"):
|
|
106
|
+
if event not in line:
|
|
107
|
+
continue
|
|
108
|
+
fields : List[str] = [x for x in line.split(";") if x != ""]
|
|
109
|
+
#print(fields)
|
|
110
|
+
#[S0,D0,C0]
|
|
111
|
+
core_code = fields[0].split("-")
|
|
112
|
+
socket_num = int(core_code[0][1:])
|
|
113
|
+
core = core_code[2][1:]
|
|
114
|
+
ctr = int(fields[2])
|
|
115
|
+
data[socket_num][core] = ctr
|
|
116
|
+
#print(data[socket])
|
|
117
|
+
return data[socket]
|
|
118
|
+
def perf_sample_per_core_event(cmd:str,event:str,socket:int) -> Dict[str,int]:
|
|
119
|
+
perf_cmd = f"sudo perf stat --per-core -x \\; -a -e {event} {cmd}"
|
|
120
|
+
output = run_command_read_stderr(perf_cmd)
|
|
121
|
+
return interpret_per_core_event(output,event,socket)
|
|
122
|
+
def perf_sample_per_core_events(cmd:str,events:List[str],socket:int) -> Dict[str,Dict[str,int]]:
|
|
123
|
+
eventstr = " ".join([f"-e {event}" for event in events])
|
|
124
|
+
perf_cmd = f"sudo perf stat --per-core -x \\; -a {eventstr} {cmd}"
|
|
125
|
+
output = run_command_read_stderr(perf_cmd)
|
|
126
|
+
return {event:interpret_per_core_event(output,event,socket) for event in events}
|
|
127
|
+
def perf_normalize_per_core_events(cmd:str,events:List[str],socket:int) -> Dict[str,Dict[str,float]]:
|
|
128
|
+
events += ["cycles"]
|
|
129
|
+
res = perf_sample_per_core_events(cmd,events,socket)
|
|
130
|
+
cycles = res["cycles"]
|
|
131
|
+
normalized_res = {event:{core:ctr/cycles[core] for core,ctr in percore.items()} for event,percore in res.items()}
|
|
132
|
+
return normalized_res
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def add_zeroes_to_eventcode(eventcode: str, zeroct: int):
|
|
141
|
+
raw_hex_str = eventcode.split("0x")[1]
|
|
142
|
+
return "0x" + ("0" * zeroct) + raw_hex_str
|
|
143
|
+
|
|
144
|
+
def perf_sample_core_events(cmd: str, core_events: List[str]) -> Dict[str, int]:
|
|
145
|
+
"""
|
|
146
|
+
Run perf sampling for core events.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
cmd (str): Command to run under perf.
|
|
150
|
+
core_events (List[str]): List of core event names.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dict[str,int]: Mapping of event name -> event counter value.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
event_flags = [f"-e {e}" for e in core_events]
|
|
157
|
+
event_flag_str = " ".join(event_flags)
|
|
158
|
+
|
|
159
|
+
output = run_command_read_stderr(f"sudo perf stat {event_flag_str} {cmd}")
|
|
160
|
+
return interpret_core_events(output, core_events)
|
|
161
|
+
def perf_sample_core_event(cmd: str, core_event: str) -> int:
|
|
162
|
+
"""
|
|
163
|
+
Run perf sampling for core event.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
cmd (str): Command to run under perf.
|
|
167
|
+
core_event (str): core event name.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
int: core event value
|
|
171
|
+
"""
|
|
172
|
+
return perf_sample_core_events(cmd,[core_event])[core_event]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Dict, List, Tuple
|
|
2
|
+
from rykit.perf_sample import perf_sample_core_events,interpret_umask
|
|
3
|
+
def perf_sample_amd_uncore_event_many(
|
|
4
|
+
cmd: str, unc_events: List[Tuple[str, str]]
|
|
5
|
+
) -> Dict[str, int]:
|
|
6
|
+
"""
|
|
7
|
+
Run perf sampling for multiple uncore events.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
cmd (str): Command to run under perf.
|
|
11
|
+
unc_events (List[Tuple[str,str]]): List of (event, binary umask) pairs.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Dict[str,Dict[str,int]]: Mapping of event code -> event counter value.
|
|
15
|
+
"""
|
|
16
|
+
events = [f"amd_df/event={event},umask={interpret_umask(umask)}/" for event,umask in unc_events]
|
|
17
|
+
return perf_sample_core_events(cmd,events)
|
|
18
|
+
def perf_sample_core_event(cmd: str, core_event: str) -> int:
|
|
19
|
+
"""
|
|
20
|
+
Run perf sampling for core event.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
cmd (str): Command to run under perf.
|
|
24
|
+
core_event (str): core event name.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
int: core event value
|
|
28
|
+
"""
|
|
29
|
+
return perf_sample_core_events(cmd,[core_event])[core_event]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import glob
|
|
3
|
+
from typing import Dict, List, Tuple
|
|
4
|
+
from rykit.perf_sample import interpret_umask, add_zeroes_to_eventcode
|
|
5
|
+
from rykit.intel_tools import get_cha_count
|
|
6
|
+
from rykit.cmd import run_command_read_stderr
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_unc_cha_event(chanum: int, event: str, hexmask: str) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Create a perf event string for a single CHA.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
chanum (int): CHA index.
|
|
17
|
+
event (str): Event code in hexadecimal (e.g., "0xb3").
|
|
18
|
+
hexmask (str): Umask in hexadecimal (e.g., "0x8").
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
str: Perf event string for the CHA.
|
|
22
|
+
"""
|
|
23
|
+
return f"uncore_cha_{chanum}/event={event},umask={hexmask}/"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_unc_cha_events(event: str, hexmask: str) -> List[str]:
|
|
27
|
+
"""
|
|
28
|
+
Create perf event strings for all CHAs on the system.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
event (str): Event code in hexadecimal.
|
|
32
|
+
hexmask (str): Umask in hexadecimal.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List[str]: Perf event strings for each CHA.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
num_chas = get_cha_count()
|
|
39
|
+
return [create_unc_cha_event(chanum, event, hexmask) for chanum in range(num_chas)]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_perf_sample_uncore_cmd(
|
|
43
|
+
program_cmd: str, unc_events: List[Tuple[str, str]]
|
|
44
|
+
) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Build a perf command that samples multiple uncore events.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
program_cmd (str): Command to run under perf.
|
|
50
|
+
unc_events (List[Tuple[str,str]]): List of (event, binary umask) pairs.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: The full perf command string.
|
|
54
|
+
"""
|
|
55
|
+
events: List[str] = []
|
|
56
|
+
for event, mask in unc_events:
|
|
57
|
+
hexmask = interpret_umask(mask)
|
|
58
|
+
events += create_unc_cha_events(event, hexmask)
|
|
59
|
+
event_args: List[str] = [f"-e {e}" for e in events]
|
|
60
|
+
event_arg_str = " ".join(event_args)
|
|
61
|
+
return f"sudo perf stat -a {event_arg_str} -- {program_cmd}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def interpret_uncore_event(output: str, event: str) -> Dict[str, int]:
|
|
65
|
+
"""
|
|
66
|
+
Parse perf output for a single uncore event across CHAs.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
output (str): Raw stderr output from perf.
|
|
70
|
+
event (str): Event code in hexadecimal.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict[str,int]: Mapping of CHA index (as str) -> event counter value.
|
|
74
|
+
"""
|
|
75
|
+
infix = "uncore_cha_"
|
|
76
|
+
number_suffix = f"/event={event}"
|
|
77
|
+
result: Dict[str, int] = {}
|
|
78
|
+
for line in output.split("\n"):
|
|
79
|
+
# check to ensure this is a cha line
|
|
80
|
+
if infix not in line or number_suffix not in line:
|
|
81
|
+
continue
|
|
82
|
+
# Example:
|
|
83
|
+
# ex: 8,795 uncore_cha_1/event=0xb3,umask=0x8/
|
|
84
|
+
|
|
85
|
+
# remove name of event
|
|
86
|
+
# ex ->: 8,795
|
|
87
|
+
count_str_with_commas = line.split(infix)[0]
|
|
88
|
+
|
|
89
|
+
# remove ,
|
|
90
|
+
# ex ->: 8795
|
|
91
|
+
count_str = count_str_with_commas.replace(",", "")
|
|
92
|
+
|
|
93
|
+
count = int(count_str)
|
|
94
|
+
|
|
95
|
+
# chanum comes right after infix
|
|
96
|
+
chanum = line.split(infix)[1].split(number_suffix)[0]
|
|
97
|
+
result[chanum] = count
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def interpret_uncore_event_many(
|
|
102
|
+
output: str, events: List[str]
|
|
103
|
+
) -> Dict[str, Dict[str, int]]:
|
|
104
|
+
"""
|
|
105
|
+
Parse perf output for multiple uncore events.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
output (str): Raw stderr output from perf.
|
|
109
|
+
events (List[str]): List of event codes in hexadecimal.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Dict[str,Dict[str,int]]: Mapping of event code ->
|
|
113
|
+
CHA index (as str) -> event counter value.
|
|
114
|
+
"""
|
|
115
|
+
return {e: interpret_uncore_event(output, e) for e in events}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def perf_sample_uncore_event_many(
|
|
119
|
+
program_cmd: str, unc_events: List[Tuple[str, str]]
|
|
120
|
+
) -> Dict[str, Dict[str, int]]:
|
|
121
|
+
"""
|
|
122
|
+
Run perf sampling for multiple uncore events.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
program_cmd (str): Command to run under perf.
|
|
126
|
+
unc_events (List[Tuple[str,str]]): List of (event, binary umask) pairs.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Dict[str,Dict[str,int]]: Mapping of event code ->
|
|
130
|
+
CHA index (as str) -> event counter value.
|
|
131
|
+
"""
|
|
132
|
+
assert isinstance(program_cmd, str), "cmd must be passed as string (passed non-str)"
|
|
133
|
+
assert len(unc_events) <= 4, "can only run upto 4 uncore events at once"
|
|
134
|
+
|
|
135
|
+
events = [e for e, _ in unc_events]
|
|
136
|
+
if len(events) != len(set(events)):
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f'this function does not support passing two identical event codes (passed {events})\n\t hint: prepend 0 ie ["0xF", "0x0F","0x00F","0x000F"]'
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for _, mask in unc_events:
|
|
142
|
+
assert isinstance(mask, str), "mask must be a binary string (passed non-str)"
|
|
143
|
+
assert set(mask) <= {"0", "1"}, f"mask must be a binary string (passed {mask})"
|
|
144
|
+
|
|
145
|
+
cmd = build_perf_sample_uncore_cmd(program_cmd, unc_events)
|
|
146
|
+
|
|
147
|
+
output = run_command_read_stderr(cmd)
|
|
148
|
+
|
|
149
|
+
events = [unce[0] for unce in unc_events]
|
|
150
|
+
|
|
151
|
+
return interpret_uncore_event_many(output, events)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def perf_sample_uncore_event(program_cmd: str, event: str, mask: str) -> Dict[str, int]:
|
|
155
|
+
"""
|
|
156
|
+
Run perf sampling for a single uncore event.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
program_cmd (str): Command to run under perf.
|
|
160
|
+
event (str): Event code in hexadecimal.
|
|
161
|
+
mask (str): Umask in binary string form.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Dict[str,int]: Mapping of CHA index (as str) -> event counter value.
|
|
165
|
+
"""
|
|
166
|
+
res = perf_sample_uncore_event_many(program_cmd, [(event, mask)])
|
|
167
|
+
return list(res.values())[0]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def perf_sample_uncore_event_many_named_masks(
|
|
173
|
+
cmd: str, eventcode: str, masks: Dict[str, str]
|
|
174
|
+
) -> Dict[str, Dict[str, int]]:
|
|
175
|
+
# create a list of unique eventcodes for each mask by adding zeroes to RHS (ie 0xF, 0x0F, 0x00F, ...) so that we have U UID for event info from perf
|
|
176
|
+
name_to_code: dict[str, str] = {
|
|
177
|
+
k: add_zeroes_to_eventcode(eventcode, zeroct)
|
|
178
|
+
for zeroct, k in enumerate(masks.keys())
|
|
179
|
+
}
|
|
180
|
+
code_to_name: dict[str, str] = {v: k for k, v in name_to_code.items()}
|
|
181
|
+
|
|
182
|
+
uncore_events: List[Tuple[str, str]] = [
|
|
183
|
+
(code, masks[name]) for name, code in name_to_code.items()
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
res_by_event_code: Dict[str, Dict[str, int]] = perf_sample_uncore_event_many(
|
|
187
|
+
cmd, uncore_events
|
|
188
|
+
)
|
|
189
|
+
# convert keys from eventcode to eventname
|
|
190
|
+
res_by_event_name: Dict[str, Dict[str, int]] = {
|
|
191
|
+
code_to_name[code]: ctr for code, ctr in res_by_event_code.items()
|
|
192
|
+
}
|
|
193
|
+
return res_by_event_name
|