Skip to content

Commit 701a444

Browse files
authored
Merge pull request #30 from RRZE-HPC/dev/macos-arm64
Support for MacOS on Arm64 (Apple Silicon)
2 parents de72a75 + 2057706 commit 701a444

File tree

2 files changed

+91
-24
lines changed

2 files changed

+91
-24
lines changed

.github/workflows/test-n-publish.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ jobs:
2828
strategy:
2929
fail-fast: false
3030
matrix:
31-
# let's not check MacOS for now (add `macos-latest` later)
32-
os: [ubuntu-latest]
31+
os: [ubuntu-latest, macos-13, macos-latest]
3332
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
3433
runs-on: ${{ matrix.os }}
3534

@@ -45,13 +44,21 @@ jobs:
4544
sudo apt-get update
4645
sudo apt-get install slurm slurm-client openmpi-bin clang gfortran clinfo
4746
sudo apt-get install environment-modules likwid
48-
- name: Pre-Install (MacOS)
49-
if: startsWith(matrix.os, 'macos')
47+
- name: Pre-Install (MacOS x86)
48+
if: startsWith(matrix.os, 'macos-13')
5049
run: |
5150
brew install slurm open-mpi gcc clinfo modules;
5251
export PATH="/usr/local/opt/gcc@14/bin":$PATH;
5352
export CC=gcc-14; export CXX=g++-14;
5453
export FC=gfortran
54+
# do not install clinfo as it is broken for macos-14/15
55+
- name: Pre-Install (MacOS Arm64)
56+
if: startsWith(matrix.os, 'macos') && !contains(matrix.os, 'macos-13')
57+
run: |
58+
brew install slurm open-mpi gcc modules;
59+
export PATH="/usr/local/opt/gcc@14/bin":$PATH;
60+
export CC=gcc-14; export CXX=g++-14;
61+
export FC=gfortran
5562
- name: Pre-Install (generic)
5663
run: |
5764
python -m pip install codecov

machinestate.py

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
import re
100100
import json
101101
import platform
102+
import struct
102103
from subprocess import check_output, DEVNULL
103104
from glob import glob
104105
from os.path import join as pjoin
@@ -562,14 +563,20 @@ def match(self, data):
562563
out = data
563564
if self.regex is not None:
564565
regex = re.compile(self.regex)
566+
m = None
565567
for l in NEWLINE_REGEX.split(data):
566568
m = regex.match(l)
567569
if m:
568570
out = m.group(1)
571+
break
569572
else:
570573
m = regex.search(l)
571574
if m:
572575
out = m.group(1)
576+
break
577+
if not m:
578+
out = ""
579+
self.parser = None
573580
return out
574581
def parse(self, data):
575582
out = data
@@ -1418,20 +1425,45 @@ def __init__(self, extended=False, anonymous=False):
14181425
class CpuInfoMacOS(InfoGroup):
14191426
def __init__(self, extended=False, anonymous=False):
14201427
super(CpuInfoMacOS, self).__init__(name="CpuInfo", extended=extended, anonymous=anonymous)
1421-
self.const("MachineType", platform.machine())
1422-
self.addc("Vendor", "sysctl", "-a", r"machdep.cpu.vendor: (.*)")
1423-
self.addc("Name", "sysctl", "-a", r"machdep.cpu.brand_string: (.*)")
1424-
self.addc("Family", "sysctl", "-a", r"machdep.cpu.family: (\d+)", int)
1425-
self.addc("Model", "sysctl", "-a", r"machdep.cpu.model: (\d+)", int)
1426-
self.addc("Stepping", "sysctl", "-a", r"machdep.cpu.stepping: (\d+)", int)
1427-
if extended:
1428-
self.addc("Flags", "sysctl", "-a", r"machdep.cpu.features: (.*)", tostrlist)
1429-
self.addc("ExtFlags", "sysctl", "-a", r"machdep.cpu.extfeatures: (.*)", tostrlist)
1430-
self.addc("Leaf7Flags", "sysctl", "-a", r"machdep.cpu.leaf7_features: (.*)", tostrlist)
1431-
self.addc("Microcode", "sysctl", "-a", r"machdep.cpu.microcode_version: (.*)")
1432-
self.addc("ExtFamily", "sysctl", "-a", r"machdep.cpu.extfamily: (\d+)", int)
1433-
self.addc("ExtModel", "sysctl", "-a", r"machdep.cpu.extmodel: (\d+)", int)
1434-
self.required(["Vendor", "Family", "Model", "Stepping"])
1428+
march = platform.machine()
1429+
self.const("MachineType", march)
1430+
if march in ["x86_64"]:
1431+
self.addc("Vendor", "sysctl", "-a", r"machdep.cpu.vendor: (.*)")
1432+
self.addc("Name", "sysctl", "-a", r"machdep.cpu.brand_string: (.*)")
1433+
self.addc("Family", "sysctl", "-a", r"machdep.cpu.family: (\d+)", int)
1434+
self.addc("Model", "sysctl", "-a", r"machdep.cpu.model: (\d+)", int)
1435+
self.addc("Stepping", "sysctl", "-a", r"machdep.cpu.stepping: (\d+)", int)
1436+
if extended:
1437+
self.addc("Flags", "sysctl", "-a", r"machdep.cpu.features: (.*)", tostrlist)
1438+
self.addc("ExtFlags", "sysctl", "-a", r"machdep.cpu.extfeatures: (.*)", tostrlist)
1439+
self.addc("Leaf7Flags", "sysctl", "-a", r"machdep.cpu.leaf7_features: (.*)", tostrlist)
1440+
self.addc("Microcode", "sysctl", "-a", r"machdep.cpu.microcode_version: (.*)")
1441+
self.addc("ExtFamily", "sysctl", "-a", r"machdep.cpu.extfamily: (\d+)", int)
1442+
self.addc("ExtModel", "sysctl", "-a", r"machdep.cpu.extmodel: (\d+)", int)
1443+
self.required(["Vendor", "Family", "Model", "Stepping"])
1444+
elif march in ["arm64"]:
1445+
# TODO: Is there a way to get Vendor?
1446+
self.const("Vendor", "Apple")
1447+
self.addc("Name", "sysctl", "-a", r"machdep.cpu.brand_string: (.*)")
1448+
self.addc("Family", "sysctl", "-a", r"hw.cpufamily: (\d+)", int)
1449+
self.addc("Model", "sysctl", "-a", r"hw.cputype: (\d+)", int)
1450+
self.addc("Stepping", "sysctl", "-a", r"hw.cpusubtype: (\d+)", int)
1451+
if extended:
1452+
self.addc("Flags", "sysctl", "-a hw.optional", parse=CpuInfoMacOS.getflags_arm64)
1453+
self.required(["Vendor", "Family", "Model", "Stepping"])
1454+
1455+
@staticmethod
1456+
def getflags_arm64(string):
1457+
outlist = []
1458+
for line in string.split("\n"):
1459+
key, value = [
1460+
field.split(":") for field in line.split("hw.optional.") if len(field)
1461+
][0]
1462+
if int(value):
1463+
key = key.replace("arm.", "")
1464+
outlist.append(key)
1465+
return outlist
1466+
14351467

14361468
class CpuInfo(InfoGroup):
14371469
def __init__(self, extended=False, anonymous=False):
@@ -1670,8 +1702,25 @@ class CpuFrequencyMacOsCpu(InfoGroup):
16701702
def __init__(self, extended=False, anonymous=False):
16711703
super(CpuFrequencyMacOsCpu, self).__init__(extended=extended, anonymous=anonymous)
16721704
self.name = "Cpus"
1673-
self.addc("MaxFreq", "sysctl", "-a", r"hw.cpufrequency_max: (\d+)", int)
1674-
self.addc("MinFreq", "sysctl", "-a", r"hw.cpufrequency_min: (\d+)", int)
1705+
if platform.machine() == "x86_64":
1706+
self.addc("MaxFreq", "sysctl", "-a", r"hw.cpufrequency_max: (\d+)", int)
1707+
self.addc("MinFreq", "sysctl", "-a", r"hw.cpufrequency_min: (\d+)", int)
1708+
elif platform.machine() == "arm64":
1709+
# AppleSilicon
1710+
self.addc("Freqs-E-Core", "ioreg", "-k voltage-states1-sram | grep voltage-states1-sram", parse=CpuFrequencyMacOsCpu.get_ioreg_states)
1711+
self.addc("Freqs-P-Core", "ioreg", "-k voltage-states5-sram | grep voltage-states5-sram", parse=CpuFrequencyMacOsCpu.get_ioreg_states)
1712+
1713+
@staticmethod
1714+
def get_ioreg_states(string):
1715+
bytestr = re.match(r".*<([0-9A-Fa-f]+)>", string)
1716+
if bytestr:
1717+
bytestr = bytestr.group(1)
1718+
# numbers consecutive in 4-byte little-endian
1719+
states_int = struct.unpack("<" + int(len(bytestr)/8) * "I", bytes.fromhex(bytestr))
1720+
# voltage states are in pairs of (freq, voltage)
1721+
states_int = [x for x in states_int if states_int.index(x)%2 == 0]
1722+
return states_int
1723+
return string
16751724

16761725
class CpuFrequencyMacOsBus(InfoGroup):
16771726
def __init__(self, extended=False, anonymous=False):
@@ -1684,7 +1733,12 @@ class CpuFrequencyMacOs(MultiClassInfoGroup):
16841733
def __init__(self, extended=False, anonymous=False):
16851734
super(CpuFrequencyMacOs, self).__init__(extended=extended, anonymous=anonymous)
16861735
self.name = "CpuFrequency"
1687-
self.classlist = [CpuFrequencyMacOsCpu, CpuFrequencyMacOsBus]
1736+
# Apple Silicon with MacOS does not easily print out CPU/BUS freqs, see
1737+
# https://github.com/giampaolo/psutil/issues/1892
1738+
if platform.machine() == "x86_64":
1739+
self.classlist = [CpuFrequencyMacOsCpu, CpuFrequencyMacOsBus]
1740+
elif platform.machine() == "arm64":
1741+
self.classlist = [CpuFrequencyMacOsCpu]
16881742
self.classargs = [{} for c in self.classlist]
16891743
self.addc("TimerFreq", "sysctl", "-a", r"hw.tbfrequency: (\d+)", int)
16901744

@@ -1837,8 +1891,8 @@ def getcpulist(arg):
18371891
ncpus = process_cmd(("sysctl", "-n hw.ncpu", r"(\d+)", int))
18381892
cconfig = process_cmd(("sysctl", "-n hw.cacheconfig", r"([\d\s]+)", tointlist))
18391893
if cconfig and ncpus:
1840-
if len(cconfig) > int(level):
1841-
sharedbycount = int(cconfig[int(level)])
1894+
sharedbycount = int(cconfig[int(level)])
1895+
if sharedbycount:
18421896
for i in range(ncpus//sharedbycount):
18431897
clist.append(list(range(i*sharedbycount, (i+1)*sharedbycount)))
18441898
return clist
@@ -1848,8 +1902,12 @@ def getcpulist(arg):
18481902
class CacheTopologyMacOS(ListInfoGroup):
18491903
def __init__(self, extended=False, anonymous=False):
18501904
super(CacheTopologyMacOS, self).__init__(anonymous=anonymous, extended=extended)
1905+
march = platform.machine()
18511906
self.name = "CacheTopology"
1852-
self.userlist = ["l1i", "l1d", "l2", "l3"]
1907+
if march in ["x86_64"]:
1908+
self.userlist = ["l1i", "l1d", "l2", "l3"]
1909+
elif march in ["arm64"]:
1910+
self.userlist = ["l1i", "l1d", "l2"]
18531911
self.subclass = CacheTopologyMacOSClass
18541912

18551913

@@ -3356,6 +3414,8 @@ def __init__(self, platform, extended=False, anonymous=False, clinfo_path=""):
33563414
self.addc("Vendor", clcmd, cmdopts, r"\s+CL_PLATFORM_VENDOR\s+(.+)", str)
33573415
#self.commands["IcdSuffix"] = (clcmd, cmdopts, r"\s+CL_PLATFORM_ICD_SUFFIX_KHR\s+(.+)", str)
33583416
suffix = process_cmd((clcmd, cmdopts, r"\s+CL_PLATFORM_ICD_SUFFIX_KHR\s+(.+)", str))
3417+
if " " in suffix:
3418+
suffix = "P0"
33593419
self.const("IcdSuffix", suffix)
33603420
num_devs = process_cmd((clcmd, cmdopts, r".*{}.*#DEVICES\s*(\d+)".format(suffix), int))
33613421
if num_devs and num_devs > 0:

0 commit comments

Comments
 (0)