Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

osx CPU freq on apple silicon #693

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cmake-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
- name: Configure
run: |
export LLVM_PREFIX="$(brew --prefix llvm)"
export CC="$LLVM_PREFIX/bin/clang"
export CXX="$LLVM_PREFIX/bin/clang++"
export CPPFLAGS="-I$LLVM_PREFIX/include"
export LDFLAGS="-L$LLVM_PREFIX/lib -L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++ -fuse-ld=$LLVM_PREFIX/bin/ld64.lld"
Expand Down
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ project("btop"
VERSION 1.2.13
DESCRIPTION "A monitor of resources"
HOMEPAGE_URL "https://github.com/aristocratos/btop"
LANGUAGES CXX
LANGUAGES C CXX
)

include(CheckCXXCompilerFlag)
Expand Down Expand Up @@ -61,7 +61,7 @@ add_executable(btop
)

if(APPLE)
target_sources(btop PRIVATE src/osx/btop_collect.cpp src/osx/sensors.cpp src/osx/smc.cpp)
target_sources(btop PRIVATE src/osx/btop_collect.cpp src/osx/sensors.cpp src/osx/smc.cpp src/osx/CpuFreq.c)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
target_sources(btop PRIVATE src/freebsd/btop_collect.cpp)
elseif(LINUX)
Expand Down Expand Up @@ -165,6 +165,7 @@ endif()
if(APPLE)
target_link_libraries(btop
$<LINK_LIBRARY:FRAMEWORK,CoreFoundation> $<LINK_LIBRARY:FRAMEWORK,IOKit>
IOReport
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
# Avoid version mismatch for libstdc++ when a specific version of GCC is installed and not the
Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ else ifeq ($(PLATFORM_LC),freebsd)
else ifeq ($(PLATFORM_LC),macos)
PLATFORM_DIR := osx
THREADS := $(shell sysctl -n hw.ncpu || echo 1)
override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation
override ADDFLAGS += -framework IOKit -framework CoreFoundation -lIOReport -Wno-format-truncation
SU_GROUP := wheel
else
$(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m"))
Expand Down Expand Up @@ -205,6 +205,9 @@ SOURCES += $(sort $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEX
SOURCE_COUNT := $(words $(SOURCES))

OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
ifeq ($(PLATFORM_LC),macos)
OBJECTS += $(BUILDDIR)/osx/CpuFreq.o
endif

ifeq ($(shell find $(BUILDDIR) -type f -newermt "$(DATESTAMP)" -name *.o >/dev/null 2>&1; echo $$?),0)
ifneq ($(wildcard $(BUILDDIR)/.*),)
Expand Down Expand Up @@ -378,5 +381,12 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories
@$(CXX) $(CXXFLAGS) $(INC) -MMD -c -o $@ $< || exit 1
@printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"


ifeq ($(PLATFORM_LC),macos)
.ONESHELL:
$(BUILDDIR)/osx/CpuFreq.o: $(SRCDIR)/osx/CpuFreq.c
gcc -c -o $@ $(SRCDIR)/osx/CpuFreq.c
endif

#? Non-File Targets
.PHONY: all msg help pre
200 changes: 200 additions & 0 deletions src/osx/CpuFreq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined (__clang__) && defined (__aarch64__)
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "CpuFreq.h"

#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define kIOMainPortDefault kIOMasterPortDefault
#endif

/* Implementations */
int CpuFreq_init(CpuFreqData *data) {
data->cluster_type_per_cpu = (unsigned int *)malloc(data->existingCPUs * sizeof(uint32_t));
data->frequencies = (double *)malloc(data->existingCPUs * sizeof(double));

/* Determine the cluster type for all CPUs */
char buf[128];

for (uint32_t num_cpus = 0U; num_cpus < data->existingCPUs; num_cpus++) {
snprintf(buf, sizeof(buf), "IODeviceTree:/cpus/cpu%u", num_cpus);
io_registry_entry_t cpu_io_registry_entry =
IORegistryEntryFromPath(kIOMainPortDefault, buf);
if (cpu_io_registry_entry == MACH_PORT_NULL) {
return -1;
}

CFDataRef cluster_type_ref = (CFDataRef)IORegistryEntryCreateCFProperty(
cpu_io_registry_entry, CFSTR("cluster-type"), kCFAllocatorDefault, 0U);
if (cluster_type_ref == NULL) {
IOObjectRelease(cpu_io_registry_entry);
return -1;
}
if (CFDataGetLength(cluster_type_ref) != 2) {
CFRelease(cluster_type_ref);
IOObjectRelease(cpu_io_registry_entry);
return -1;
}
UniChar cluster_type_char;
CFDataGetBytes(cluster_type_ref, CFRangeMake(0, 2),
(uint8_t *)&cluster_type_char);
CFRelease(cluster_type_ref);

uint32_t cluster_type = 0U;
switch (cluster_type_char) {
case L'E':
cluster_type = 0U;
break;
case L'P':
cluster_type = 1U;
break;
default:
/* Unknown cluster type */
IOObjectRelease(cpu_io_registry_entry);
return -1;
}

data->cluster_type_per_cpu[num_cpus] = cluster_type;

IOObjectRelease(cpu_io_registry_entry);
}

/*
* Determine frequencies for per-cluster-type performance states
* Frequencies for the "E" cluster type are stored in voltage-states1,
* frequencies for the "P" cluster type are stored in voltage-states5.
* This seems to be hardcoded.
*/
const CFStringRef voltage_states_key_per_cluster[CPUFREQ_NUM_CLUSTER_TYPES] =
{CFSTR("voltage-states1"), CFSTR("voltage-states5")};

io_registry_entry_t pmgr_registry_entry =
IORegistryEntryFromPath(kIOMainPortDefault, "IODeviceTree:/arm-io/pmgr");
if (pmgr_registry_entry == MACH_PORT_NULL) {
return -1;
}
for (size_t i = 0U; i < CPUFREQ_NUM_CLUSTER_TYPES; i++) {
CFDataRef voltage_states_ref = (CFDataRef)IORegistryEntryCreateCFProperty(
pmgr_registry_entry, voltage_states_key_per_cluster[i],
kCFAllocatorDefault, 0U);
if (voltage_states_ref == NULL) {
IOObjectRelease(pmgr_registry_entry);
return -1;
}

CpuFreqPowerStateFrequencies *cluster_frequencies =
&data->cpu_frequencies_per_cluster_type[i];
cluster_frequencies->num_frequencies =
CFDataGetLength(voltage_states_ref) / 8;
cluster_frequencies->frequencies =
(double *)malloc(cluster_frequencies->num_frequencies * sizeof(double));
const uint8_t *voltage_states_data = CFDataGetBytePtr(voltage_states_ref);
for (size_t j = 0U; j < cluster_frequencies->num_frequencies; j++) {
uint32_t freq_value;
memcpy(&freq_value, voltage_states_data + j * 8, 4);
cluster_frequencies->frequencies[j] = (65536000.0 / freq_value) * 1000000;
}
CFRelease(voltage_states_ref);
}
IOObjectRelease(pmgr_registry_entry);

/* Create subscription for CPU performance states */
CFMutableDictionaryRef channels = IOReportCopyChannelsInGroup(
CFSTR("CPU Stats"), CFSTR("CPU Core Performance States"), NULL, NULL);
if (channels == NULL) {
return -1;
}

data->subscribed_channels = NULL;
data->subscription = IOReportCreateSubscription(
NULL, channels, &data->subscribed_channels, 0U, NULL);

CFRelease(channels);

if (data->subscription == NULL) {
return -1;
}

data->prev_samples = NULL;

return 0;
}

void CpuFreq_update(CpuFreqData *data) {
CFDictionaryRef samples = IOReportCreateSamples(
data->subscription, data->subscribed_channels, NULL);
if (samples == NULL) {
return;
}

if (data->prev_samples == NULL) {
data->prev_samples = samples;
return;
}

/* Residency is cumulative, we have to diff two samples to get a current view
*/
CFDictionaryRef samples_delta =
IOReportCreateSamplesDelta(data->prev_samples, samples, NULL);

/* Iterate through performance state residencies. Index 0 is the idle
* residency, index 1-n is the per-performance state residency. */
__block uint32_t cpu_i = 0U;
IOReportIterate(samples_delta, ^(IOReportSampleRef ch) {
if (cpu_i >= data->existingCPUs) {
/* The report contains more CPUs than we know about. This should not
* happen. */
return kIOReportIterOk; // TODO: find way to possibly stop iteration early
// on error
}
const CpuFreqPowerStateFrequencies *cpu_frequencies =
&data->cpu_frequencies_per_cluster_type
[data->cluster_type_per_cpu[cpu_i]];
uint32_t state_count = IOReportStateGetCount(ch);
if (state_count != cpu_frequencies->num_frequencies + 1) {
/* The number of reported states does not match the number of previously
* queried frequencies. This should not happen. */
return kIOReportIterOk; // TODO: find way to possibly stop iteration early
// on error
}
double freq = 0.0;
double highest_residency = 0.0;
uint32_t highest_i = 0U;
for (uint32_t i = 0U; i < state_count; i++) {
const int64_t residency = IOReportStateGetResidency(ch, i);
if (residency > highest_residency) {
highest_i = i;
highest_residency = residency;
}
}
freq = cpu_frequencies->frequencies[highest_i == 0 ? 0 : highest_i - 1];
data->frequencies[cpu_i] = round(freq);

cpu_i++;
return kIOReportIterOk;
});
CFRelease(samples_delta);

CFRelease(data->prev_samples);
data->prev_samples = samples;
}

void CpuFreq_cleanup(CpuFreqData *data) {
if (data->subscription != NULL) {
CFRelease(data->subscription);
}
if (data->subscribed_channels != NULL) {
CFRelease(data->subscribed_channels);
}
if (data->prev_samples != NULL) {
CFRelease(data->prev_samples);
}
free(data->cluster_type_per_cpu);
free(data->frequencies);
for (size_t i = 0U; i < CPUFREQ_NUM_CLUSTER_TYPES; i++) {
free(data->cpu_frequencies_per_cluster_type[i].frequencies);
}
}
#endif
77 changes: 77 additions & 0 deletions src/osx/CpuFreq.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef HEADER_CpuFreq
#define HEADER_CpuFreq

#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOTypes.h>

/* Private API definitions from libIOReport*/
enum {
kIOReportIterOk = 0,
};
typedef struct IOReportSubscription *IOReportSubscriptionRef;
typedef CFDictionaryRef IOReportSampleRef;
typedef CFDictionaryRef IOReportChannelRef;
typedef int (^io_report_iterate_callback_t)(IOReportSampleRef ch);
extern void IOReportIterate(CFDictionaryRef samples,
io_report_iterate_callback_t callback);
extern CFMutableDictionaryRef
IOReportCopyChannelsInGroup(CFStringRef, CFStringRef, void *, void *);
extern IOReportSubscriptionRef
IOReportCreateSubscription(void *a, CFMutableDictionaryRef desiredChannels,
CFMutableDictionaryRef *subbedChannels,
uint64_t channel_id, CFTypeRef b);
extern CFDictionaryRef
IOReportCreateSamples(IOReportSubscriptionRef iorsub,
CFMutableDictionaryRef subbedChannels, CFTypeRef a);
extern uint32_t IOReportStateGetCount(IOReportChannelRef ch);
extern uint64_t IOReportStateGetResidency(IOReportChannelRef ch,
uint32_t index);
extern CFDictionaryRef IOReportCreateSamplesDelta(CFDictionaryRef prev,
CFDictionaryRef current,
CFTypeRef a);

/* Definitions */
typedef struct {
uint32_t num_frequencies;
double *frequencies;
} CpuFreqPowerStateFrequencies;

/*
* Seems to be hardcoded for now on all Apple Silicon platforms, no way to get
* it dynamically. Current cluster types are "E" for efficiency cores and "P"
* for performance cores.
*/
#define CPUFREQ_NUM_CLUSTER_TYPES 2

typedef struct {
/* Number of CPUs */
unsigned int existingCPUs;

/* existingCPUs records, containing which CPU belongs to which cluster type
* ("E": 0, "P": 1) */
uint32_t *cluster_type_per_cpu;

/* Frequencies for all power states per cluster type */
CpuFreqPowerStateFrequencies
cpu_frequencies_per_cluster_type[CPUFREQ_NUM_CLUSTER_TYPES];

/* IOReport subscription handlers */
IOReportSubscriptionRef subscription;
CFMutableDictionaryRef subscribed_channels;

/* Last IOReport sample */
CFDictionaryRef prev_samples;

/* existingCPUs records, containing last determined frequency per CPU in MHz
*/
double *frequencies;
} CpuFreqData;

int CpuFreq_init(CpuFreqData *data);
void CpuFreq_update(CpuFreqData *data);
void CpuFreq_cleanup(CpuFreqData *data);
#endif
#endif
Loading
Loading