Skip to content

Commit

Permalink
osx CPU freq on apple silicon
Browse files Browse the repository at this point in the history
  • Loading branch information
joske committed Dec 20, 2023
1 parent a232537 commit aac3f73
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 4 deletions.
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
197 changes: 197 additions & 0 deletions src/osx/CpuFreq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "CpuFreq.h"

/* Implementations */
int CpuFreq_init(CpuFreqData *data) {
data->existingCPUs = 8;
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
#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
38 changes: 35 additions & 3 deletions src/osx/btop_collect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ tab-size = 4
#include "../btop_shared.hpp"
#include "../btop_tools.hpp"

#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define IOMainPort IOMasterPort
#endif

#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
extern "C" {
#include "CpuFreq.h"
}
#include "sensors.hpp"
#endif
#include "smc.hpp"
Expand Down Expand Up @@ -191,6 +198,9 @@ namespace Cpu {
bool has_battery = true;
bool macM1 = false;
tuple<int, long, string> current_bat;
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
CpuFreqData data;
#endif

const array<string, 10> time_names = {"user", "nice", "system", "idle"};

Expand Down Expand Up @@ -260,6 +270,7 @@ namespace Cpu {
got_sensors = true;
cpu_temp_only = true;
macM1 = true;
CpuFreq_init(&data);
} else {
#endif
// try SMC (intel)
Expand Down Expand Up @@ -324,15 +335,36 @@ namespace Cpu {
}
}

#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
unsigned int get_m1_cpuHz() {
int32_t freq = -1;
CpuFreq_update(&data);
double highest = 0;
for (unsigned int i = 0; i < data.existingCPUs; i++) {
if (data.frequencies[i] > highest)
highest = data.frequencies[i];
}
return freq = (int32_t)highest;
}
#endif

string get_cpuHz() {
unsigned int freq = 1;
int32_t freq = 1;
size_t size = sizeof(freq);

int mib[] = {CTL_HW, HW_CPU_FREQ};

if (sysctl(mib, 2, &freq, &size, nullptr, 0) < 0) {
// this fails on Apple Silicon macs. Apparently you're not allowed to know
return "";
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
freq = get_m1_cpuHz();
if (freq < 0) {
// give up
#endif
return "";
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
}
#endif
}
return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3);
}
Expand Down Expand Up @@ -604,7 +636,7 @@ namespace Mem {
io_iterator_t drive_list;

mach_port_t libtop_master_port;
if (IOMasterPort(bootstrap_port, &libtop_master_port)) {
if (IOMainPort(bootstrap_port, &libtop_master_port)) {
Logger::error("errot getting master port");
return;
}
Expand Down

0 comments on commit aac3f73

Please sign in to comment.