Skip to content

Commit 50b628e

Browse files
committed
Merge branch 'main' of https://github.com/intel-innersource/applications.analyzers.pcm into rdementi-2025-03-16
Change-Id: I7d660c1ecbfcd15d4f2cc09a66cbff8e7b28d87a
2 parents 86f0fe1 + 25c57a0 commit 50b628e

File tree

1 file changed

+95
-8
lines changed

1 file changed

+95
-8
lines changed

scripts/topotop

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Usage: topotop [options]
77
Options:
88
-h, --help print help.
99
-d, --delay SECONDS delay between updates. The default is "1.0".
10+
-l, --largest-share show largest share of each busy CPU that a
11+
single process got. The top dot stands for
12+
at least 99 % CPU. Requires "bpftrace".
1013
-m, --memory include memory bandwidth, requires "pcm-memory".
1114
-u, --upi include UPI traffic, requires "pcm".
1215
"""
@@ -145,6 +148,57 @@ class Bitmap:
145148
braille[y4].append(color + brbits2chr(braille_char_bits) + nocolor)
146149
return '\n'.join(''.join(c for c in line) for line in braille)
147150

151+
_bpftrace_cpu_pid_process = None
152+
def cpu_pid_stats():
153+
global _bpftrace_cpu_pid_process
154+
if _bpftrace_cpu_pid_process is None:
155+
bpfprog = (
156+
"tracepoint:sched:sched_stat_runtime{ if (@active[0]==0) { @run0[cpu,args->pid]+=args->runtime; } else { @run1[cpu,args->pid]+=args->runtime; } }" + # @run: {(cpu, pid): runtime}
157+
"interval:ms:%d{ @active[0]=1-@active[0]; if (@active[0]==1) { print(@run0); clear(@run0); } else { print(@run1); clear(@run1); } print(\"---\"); }" % (int(opt_delay * 1000),)
158+
)
159+
_bpftrace_cpu_pid_process = subprocess.Popen(
160+
["bpftrace", "-e", bpfprog],
161+
bufsize=0,
162+
universal_newlines=True,
163+
stdout=subprocess.PIPE)
164+
# @MAP0[cpu,pid]: count
165+
stat_cpu_pid = {
166+
"run": {},
167+
}
168+
while True:
169+
line = _bpftrace_cpu_pid_process.stdout.readline().strip()
170+
if line == "":
171+
raise Exception("bpftrace exited")
172+
if line == "---":
173+
break
174+
if line.startswith("@"):
175+
stat = line[1:4]
176+
cpu_pid_value = stat_cpu_pid[stat]
177+
cpu_pid, count = line.split(":")
178+
# cpu_pid: "@XXX0[cpu, pid]"
179+
cpu = int(cpu_pid.split(",")[0][6:]) # cut "@XXX0["
180+
pid = int(cpu_pid.split(",")[1][:-1]) # cut "]"
181+
if not cpu in cpu_pid_value:
182+
cpu_pid_value[cpu] = {}
183+
cpu_pid_value[cpu][pid] = int(count.strip())
184+
return stat_cpu_pid
185+
186+
def cpu_lshare(stat_cpu_pid, cpu_usage):
187+
"""Return largest share (lshare) that the most running process got from a CPU"""
188+
cpu_lshare = {}
189+
for cpu, pid_runtime in stat_cpu_pid["run"].items():
190+
# show largest share only on CPUs with at least 25 % usage
191+
if cpu_usage.get(cpu, 0) < 0.25:
192+
cpu_lshare[cpu] = 0
193+
continue
194+
max_time = max(pid_runtime.values())
195+
sum_time = sum(pid_runtime.values())
196+
if sum_time == 0:
197+
cpu_lshare[cpu] = 0
198+
continue
199+
cpu_lshare[cpu] = max_time / sum_time
200+
return cpu_lshare
201+
148202
_cpu_load_total, _cpu_load_idle = {}, {}
149203
def cpu_usage_from_proc():
150204
"""Read CPU usage statistics from /proc/stat"""
@@ -439,12 +493,11 @@ def print_sysmbw_usage(data):
439493
bm.set_line(-1, write_bar)
440494
print(f"sysR+W {rgbps+wgbps:6.1f} GB/s{bm.to_braille()} {_max_sys_bw:6.1f} GB/s")
441495

442-
def print_cpu_load2(pkg, cpus, cpu_usage):
496+
def print_cpu_load2(pkg, cpus, cpu_usage, cpu_ls):
443497
try:
444498
width = os.get_terminal_size().columns
445499
except:
446500
width = 72
447-
448501
height_pts = 8 # resolution in braille points
449502
lines = []
450503
prev_pkg = None
@@ -453,6 +506,7 @@ def print_cpu_load2(pkg, cpus, cpu_usage):
453506
line1 = []
454507
line2 = []
455508
usage = []
509+
lshare = [] # largest share that any process got on a CPU
456510
for cpu in cpus:
457511
if cpu[0] != node or cpu[1] != pkg:
458512
continue
@@ -466,12 +520,29 @@ def print_cpu_load2(pkg, cpus, cpu_usage):
466520
line2.append(f"coreid")
467521
vcpu = cpu[-1]
468522
usage.append(round(height_pts * cpu_usage[vcpu]))
523+
if cpu_ls:
524+
# 99 % CPU usage is shown as 100 % in the braille,
525+
# anything lower than that lacks the last braille dot.
526+
lshare.append(int(height_pts * min(1.0, .01 + cpu_ls.get(vcpu, 0))))
469527
if len(usage) == 2:
470528
bm = Bitmap()
471-
bm.add_col([0]*height_pts)
529+
if cpu_ls and lshare:
530+
lshare_col0 = [0]*height_pts
531+
if lshare[0] > 0:
532+
lshare_col0[height_pts-lshare[0]] = 1
533+
bm.add_col(lshare_col0)
534+
else:
535+
bm.add_col([0]*height_pts)
472536
for u in usage:
473537
coldata = [0]*(height_pts-u) + [1]*u
474538
bm.add_col(coldata)
539+
if cpu_ls and len(lshare) == 2:
540+
lshare_col1 = [0]*height_pts
541+
if lshare[1] > 0:
542+
lshare_col1[height_pts-lshare[1]] = 1
543+
bm.add_col(lshare_col1)
544+
else:
545+
bm.add_col([0]*height_pts)
475546
s = bm.to_braille()
476547
if cpu[-2] % 4 == 0:
477548
bgon = "\x1b[48;5;14m"
@@ -483,6 +554,7 @@ def print_cpu_load2(pkg, cpus, cpu_usage):
483554
line1.append(bgon + (s.split("\n")[1]) + bgoff)
484555
line2.append(bgon + (" " + str(cpu[-2]))[-2:] + bgoff)
485556
usage = []
557+
lshare = []
486558
if line0:
487559
lines.append(line0)
488560
lines.append(line1)
@@ -494,11 +566,13 @@ if __name__ == "__main__":
494566
opt_delay = 1.0
495567
opt_memory = False
496568
opt_upi = False
569+
opt_largest_share = False
497570

571+
# get stats on cpu pid events
498572
try:
499573
opts, remainder = getopt.gnu_getopt(
500-
sys.argv[1:], 'hd:mu',
501-
['help', 'delay=', 'memory', 'upi'])
574+
sys.argv[1:], 'hd:lmu',
575+
['help', 'delay=', 'largest-share', 'memory', 'upi'])
502576
except Exception as e:
503577
error(str(e))
504578
for opt, arg in opts:
@@ -510,19 +584,28 @@ if __name__ == "__main__":
510584
opt_delay = float(arg)
511585
except:
512586
error("invalid delay: %s" % (arg,))
587+
elif opt in ["-l", "--largest-share"]:
588+
opt_largest_share = True
513589
elif opt in ["-m", "--memory"]:
514590
opt_memory = True
515591
elif opt in ["-u", "--upi"]:
516592
opt_upi = True
517593
else:
518-
error("unknown option: %s" % (opt,))
594+
error("internal error: unhandled option: %s" % (opt,))
519595

520596
cpus = read_topology()
521597
add_numa_info_to_pkg(cpus)
522598
max_pkg = max([cpu[1] for cpu in cpus])
523599
membw_usage, upi_usage = None, None
524600
cpu_usage_from_proc() # read initial values
525601

602+
if opt_largest_share:
603+
print("starting bpftrace")
604+
try:
605+
cpu_pid_stats()
606+
except Exception as err:
607+
error("cannot run bpftrace to show -l/--largest-share data: %s" % (err,))
608+
526609
if opt_upi:
527610
print("starting pcm")
528611
try:
@@ -545,6 +628,7 @@ if __name__ == "__main__":
545628
else:
546629
membw_from_pcm = lambda: time.sleep(opt_delay)
547630

631+
cpu_ls = {}
548632
# clear terminal
549633
print("\x1b[2J", end="")
550634
while True:
@@ -560,8 +644,11 @@ if __name__ == "__main__":
560644
print()
561645

562646
cpu_usage = cpu_usage_from_proc()
647+
if opt_largest_share:
648+
cps = cpu_pid_stats()
649+
cpu_ls = cpu_lshare(cps, cpu_usage)
563650

564-
print_cpu_load2(0, cpus, cpu_usage)
651+
print_cpu_load2(0, cpus, cpu_usage, cpu_ls)
565652

566653
if max_pkg > 0:
567654
if opt_upi:
@@ -570,7 +657,7 @@ if __name__ == "__main__":
570657
print()
571658
if opt_upi:
572659
print_upi_usage(pkg, upi_usage, "\u2191")
573-
print_cpu_load2(pkg, cpus, cpu_usage)
660+
print_cpu_load2(pkg, cpus, cpu_usage, cpu_ls)
574661

575662
if membw_usage:
576663
for pkg in range(1, max_pkg + 1):

0 commit comments

Comments
 (0)