Skip to content

Commit ea35556

Browse files
Merge pull request #3 from ArmDeveloperEcosystem/temp-QDEIG4MDQT
Correct detection of some features
2 parents 03e7c88 + d6929f7 commit ea35556

File tree

6 files changed

+105
-30
lines changed

6 files changed

+105
-30
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/venv
2+
__pycache__
3+
*.bak

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# System Capabilities Reporter
22

3-
Sysreport is a system capability reporting tool that gives application developers a quick summary of what performance features are available on the Linux system the tool is running on.
3+
Sysreport is a system capability reporting tool that gives application developers a quick summary of how the system is configured and what performance features are available, on the Linux system the tool is running on.
44

55
This tool is aimed at anyone trying to profile performance on Arm-based systems; this includes cloud instances, bare metal servers, and small devices such as developer boards and Raspberry Pi devices.
66

77
The tool is invoked on the command-line and takes about a second to run. It displays a single page of output in text format.
88

9-
Sysreport is not a benchmarking tool and does not modify the system in any way.
9+
Sysreport is not a benchmarking tool and does not modify the system in any way. It is also not intended to report current system status, only system configuration.
1010

1111
This tool aims to:
1212
* Report system configuration in a way that is focused on performance analysis, list which performance tools and features are available
@@ -66,4 +66,4 @@ Please consider raising an issue in GitHub if this tool does not work as expecte
6666

6767
## Acknowledgements
6868

69-
This project was created on behalf of the [Arm Software Developers](https://www.arm.com/developer-hub/) team, follow them on Twitter: [@ArmSoftwareDev](https://twitter.com/armsoftwaredev) and YouTube: [Arm Software Developers](https://www.youtube.com/channel/UCHUAckhCfRom2EHDGxwhfOg) for more resources!
69+
This project was created on behalf of the [Arm Software Developers](https://www.arm.com/developer-hub/) team, follow them on Twitter: [@ArmSoftwareDev](https://twitter.com/armsoftwaredev) and YouTube: [Arm Software Developers](https://www.youtube.com/channel/UCHUAckhCfRom2EHDGxwhfOg) for more resources!

src/cpulist.py

100644100755
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060

6161
from __future__ import print_function
6262

63-
import platform, sys, os, itertools
63+
import platform, sys, os, itertools, re
6464

6565
o_verbose = 0
6666

@@ -452,7 +452,8 @@ def read_sys_cpus():
452452
0x50: ("APM", {}),
453453
0x51: ("Qualcomm", {}),
454454
0x56: ("Marvell", {}),
455-
0x69: ("Intel", {})
455+
0x69: ("Intel", {}),
456+
0x6d: ("Microsoft", {0xd49: "Cobalt 100"}),
456457
}
457458

458459

@@ -487,7 +488,7 @@ def cpu_mask(self):
487488
return 1 << self.cpuno
488489

489490
def sysfs(self):
490-
s = "/sys/devices/system/cpu/cpu" + int(self.cpuno)
491+
s = "/sys/devices/system/cpu/cpu" + str(self.cpuno)
491492
assert os.path.isdir(s)
492493
return s
493494

@@ -613,7 +614,7 @@ def discover(self):
613614
self.max_cpuno = 0
614615
self.n_cpus_online = 0
615616
self.cpus_by_core = {} # map 'core_id' -> CPU
616-
self.cpus_by_node = {} # map NUMA node number -> CPU
617+
self.numa_nodes = {} # each NUMA node is a tuple of: (size, cpu list)
617618
self.cpus_by_package = {}
618619
self.groups_by_cpumask = {}
619620
allcpu_mask = 0
@@ -711,9 +712,10 @@ def discover(self):
711712
if not os.path.exists(node):
712713
# Maybe built with CONFIG_NUMA=n
713714
break
714-
node_cpus = intmask_list(cpusetstr_mask(file_word(node + "/cpulist")))
715-
self.cpus_by_node[n] = node_cpus
716-
for i in node_cpus:
715+
size = int(re.search(r"MemTotal:\s*([0-9]*)", file_word(node + "/meminfo")).group(1))
716+
cpu_list = intmask_list(cpusetstr_mask(file_word(node + "/cpulist")))
717+
self.numa_nodes[n] = (size, cpu_list)
718+
for i in cpu_list:
717719
c = self.cpus_by_number[i]
718720
c.numa_node = n
719721
self.top = self.group(allcpu_mask)
@@ -826,7 +828,7 @@ def n_nodes(self):
826828
Number of NUMA nodes. Sometimes corresponds to packages, but not always.
827829
If the kernel has been built with CONFIG_NUMA=n, this will return 0.
828830
"""
829-
return len(self.cpus_by_node)
831+
return len(self.numa_nodes)
830832

831833
def n_packages(self):
832834
"""

src/hugepages.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Show huge pages
5+
"""
6+
7+
from __future__ import print_function
8+
9+
import os
10+
11+
hpdir = "/sys/kernel/mm/hugepages"
12+
13+
def file_int(fn):
14+
return int(open(fn).read().strip())
15+
16+
for d in os.listdir(hpdir):
17+
dp = os.path.join(hpdir, d)
18+
print(" %20s %5u " % (d, file_int(dp + "/nr_hugepages")))

src/strcolor.py

100644100755
File mode changed.

src/sysreport.py

100644100755
Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from __future__ import print_function
3535

3636
import os, sys, platform, subprocess, multiprocessing, json, datetime, struct
37+
3738
# import gzip for reading /proc/config.gz
3839
try:
3940
import gzip
@@ -131,6 +132,9 @@ def run_cmd(cmd):
131132
args = cmd.split()
132133
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
133134
(out, err) = p.communicate()
135+
if o_verbose:
136+
print("#o# %s" % (out), file=sys.stderr)
137+
print("#e# %s" % (err), file=sys.stderr)
134138
return (out, err)
135139

136140

@@ -242,6 +246,7 @@ def acpi_irqs():
242246
(0x41, 0xd40): (8, 4), # Neoverse V1
243247
(0x41, 0xd49): (9, 0), # Neoverse N2
244248
(0x41, 0xd4f): (9, 0), # Neoverse V2
249+
(0x6d, 0xd49): (9, 0), # Azure Cobalt 100
245250
}
246251

247252
def arm_arch(s):
@@ -313,6 +318,9 @@ def get_distribution(self):
313318
s = "<unknown>"
314319
return s
315320

321+
def is_ACPI(self):
322+
return boot_info_type() == "ACPI"
323+
316324
def get_kernel_version(self):
317325
"""
318326
Get kernel version, as a string, e.g. "6.5.1-rc7" returns "6.5.1"
@@ -515,15 +523,45 @@ def pyperf_installed():
515523
g_perf_installed = None
516524

517525

526+
def is_exec_shell_script(file):
527+
"""
528+
Returns a boolean indicating whether the specified file is an executable
529+
shell script or not.
530+
"""
531+
is_script = False
532+
if not os.path.exists(file):
533+
return False
534+
with open(file, "rb") as f:
535+
bytes = f.read(2)
536+
if bytes == b"#!" and os.access(file, os.X_OK):
537+
is_script = True
538+
return is_script
539+
540+
518541
def perf_binary():
519542
"""
520543
Get the canonical location of the perf binary. This might not exist.
544+
545+
/usr/bin/perf is usually a redirector script, but it's possible that perf
546+
was built by hand and copied to this location.
547+
548+
# If $PATH contains a redirector script, return the actual install path.
549+
# Otherwise assume it's a user-installed binary and honour that path.
521550
"""
522-
return "/usr/lib/linux-tools/" + platform.release() + "/perf"
551+
cmd = "which perf"
552+
(out, err) = run_cmd(cmd)
553+
path = out.decode().strip()
554+
if is_exec_shell_script(path):
555+
return "/usr/lib/linux-tools/" + platform.release() + "/perf"
556+
else:
557+
return path
523558

524559

525560
def perf_binary_imports(lib):
526-
p = subprocess.Popen(["ldd", perf_binary()], stdout=subprocess.PIPE)
561+
perf_bin = perf_binary()
562+
if not os.path.exists(perf_bin):
563+
return False
564+
p = subprocess.Popen(["ldd", perf_bin], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
527565
(out, err) = p.communicate()
528566
return lib in out.decode()
529567

@@ -539,7 +577,9 @@ def perf_installed():
539577
global g_perf_installed
540578
if g_perf_installed is not None:
541579
return g_perf_installed
542-
rc = os.system("perf stat -- true >/dev/null 2>/dev/null")
580+
# Run a very simple "perf" subcommand - don't use "perf stat" as this may fail
581+
# for unprivileged due to security settings, even if events are available.
582+
rc = os.system("perf config >/dev/null 2>/dev/null")
543583
g_perf_installed = (rc == 0)
544584
return rc == 0
545585

@@ -702,6 +742,9 @@ def kernel_hugepages(s, skip_zero=False):
702742
"""
703743
# return file_int("/proc/sys/vm/nr_hugepages")
704744
hpdir = "/sys/kernel/mm/hugepages"
745+
if not os.path.exists(hpdir):
746+
return
747+
705748
for d in os.listdir(hpdir):
706749
if not d.startswith("hugepages-"):
707750
continue
@@ -712,7 +755,10 @@ def kernel_hugepages(s, skip_zero=False):
712755

713756

714757
def kernel_hugepages_str(s, skip_zero=False):
715-
return ", ".join(["%s: %u" % (sz, nr) for (sz, nr) in kernel_hugepages(s, skip_zero=skip_zero)])
758+
huge_pages = list(kernel_hugepages(s, skip_zero=skip_zero))
759+
if huge_pages:
760+
return ", ".join(["%s: %u" % (sz, nr) for (sz, nr) in huge_pages])
761+
return "disabled"
716762

717763

718764
def kernel_thp(s):
@@ -812,11 +858,12 @@ def advice(s):
812858
# Amazon Linux: "yum install perf"
813859
yield ("perf tools not installed", ["install perf package (see https://learn.arm.com/install-guides/perf)", "or build from kernel sources"])
814860
else:
861+
# ensure perf is built with OpenCSD
815862
if _is_arm and not perf_binary_has_opencsd():
816863
yield ("perf tools cannot decode hardware trace", ["build with CORESIGHT=1"])
817864
if perf_event_paranoid() > 0:
818865
yield ("System-level events can only be monitored by privileged users", ["sysctl kernel.perf_event_paranoid=0"])
819-
if not s.perf_max_counters():
866+
if perf_event_paranoid() <= 3 and not s.perf_max_counters():
820867
corrs = []
821868
if _is_arm and not s.has_irq("PMU"):
822869
corrs.append("ensure APIC table describes PMU interrupt")
@@ -836,6 +883,8 @@ def advice(s):
836883
corrs = []
837884
if not perf_noninvasive_sampling(s):
838885
# CPU has SPE, but apparently not available in perf
886+
if s.is_KPTI_enabled():
887+
corrs.append("disable kernel page-table isolation: boot with kpti=off")
839888
if not s.has_irq("SPE"):
840889
corrs.append("ensure APIC table describes SPE interrupt")
841890
ck = s.get_kernel_config("CONFIG_ARM_SPE_PMU")
@@ -854,14 +903,15 @@ def advice(s):
854903
# advice for v9:
855904
# - describe TRBE interrupt in ACPI APIC (or DT equivalent)
856905
# - rebuild with CONFIG_CORESIGHT
857-
# for both: ensure perf is built with OpenCSD
858906
if not s.kernel_config_enabled("CONFIG_CORESIGHT"):
859907
corrs.append("rebuild kernel with CONFIG_CORESIGHT")
860908
if s.is_arm_architecture(9):
861-
if not s.has_irq("TRBE"):
862-
corrs.append("ensure APIC table describes TRBE interrupt")
909+
if s.is_ACPI():
910+
if not s.has_irq("TRBE"):
911+
corrs.append("ensure APIC table describes TRBE interrupt")
863912
else:
864-
corrs.append("ensure ACPI describes CoreSight trace fabric")
913+
if s.is_ACPI():
914+
corrs.append("ensure ACPI describes CoreSight trace fabric")
865915
yield ("hardware trace not enabled", corrs)
866916

867917

@@ -897,16 +947,19 @@ def show(s):
897947
print(" Atomic operations: %s" % (colorize(has_atomics(s.system))))
898948
(itype, n) = s.system_interconnect()
899949
print(" interconnect: %s x %u" % (colorize(itype), n))
900-
print(" NUMA nodes: %u" % (s.system.n_nodes()), end="")
950+
n_nodes = s.system.n_nodes()
951+
print(" NUMA nodes: %u" % n_nodes)
901952
if not s.kernel_config_enabled("CONFIG_NUMA"):
902953
print(" (CONFIG_NUMA=n)", end="")
903-
print()
954+
if n_nodes > 1:
955+
for node in range(n_nodes):
956+
print(" node %u: size: %10skB, cpu_list: %s" % (node, s.system.numa_nodes[node][0], s.system.numa_nodes[node][1]))
904957
print(" Sockets: %u" % (s.system.n_packages()))
905958
# Kernel features
906959
print("OS configuration:")
907960
print(" Kernel: %s" % (s.get_kernel_version()))
908961
print(" config: %s" % (colorize(kernel_config_file())))
909-
print(" 32-bit support: %s" % (colorize(s.kernel_config_enabled("CONFIG_COMPAT"))))
962+
#print(" 32-bit support: %s" % (colorize(s.kernel_config_enabled("CONFIG_COMPAT"))))
910963
print(" build dir: %s" % (colorize(kernel_build_dir())))
911964
print(" uses atomics: %s" % (colorize(kernel_uses_atomics(s))))
912965
print(" huge pages: %s" % (kernel_hugepages_str(s)))
@@ -917,17 +970,16 @@ def show(s):
917970
print(" Distribution: %s" % (s.get_distribution()))
918971
print(" libc version: %s" % (s.get_libc_version()))
919972
print(" boot info: %s" % (colorize(boot_info_type())))
920-
print(" KPTI enabled: %s" % (colorize(s.is_KPTI_enabled(), invert=True)))
973+
print(" KPTI enforced: %s" % (s.is_KPTI_enabled()))
921974
print(" Lockdown: %s" % (lockdown_str(s.get_lockdown())))
922975
print(" Mitigations: %s" % (vulnerabilities_str(s.vulnerabilities())))
923976
# Perf features
924977
print("Performance features:")
925-
print(" perf tools: %s" % (colorize(perf_installed())))
926-
print(" perf installed at: %s" % (perf_binary()), end="")
927-
if not os.path.exists(perf_binary()):
928-
print(colorize(" (does not exist)", "red"), end="")
929-
print()
930-
print(" perf with OpenCSD: %s" % (colorize_greenred(perf_binary_has_opencsd())))
978+
perf_inst = perf_installed()
979+
print(" perf tools: %s" % (colorize(perf_inst)))
980+
if perf_inst:
981+
print(" perf installed at: %s" % (perf_binary()))
982+
print(" perf with OpenCSD: %s" % (colorize(perf_binary_has_opencsd())))
931983
print(" perf counters: %s" % (s.perf_max_counters()))
932984
print(" perf sampling: %s" % (colorize_greenred(perf_noninvasive_sampling(s))))
933985
print(" perf HW trace: %s" % (colorize_greenred(perf_hardware_trace(s))))

0 commit comments

Comments
 (0)