mirror of
https://github.com/aristocratos/btop.git
synced 2024-06-02 02:24:54 +12:00
osx CPU freq on apple silicon
This commit is contained in:
parent
a2325371d4
commit
d273f21917
12
Makefile
12
Makefile
|
@ -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"))
|
||||
|
@ -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)/.*),)
|
||||
|
@ -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
src/osx/CpuFreq.c
Normal file
200
src/osx/CpuFreq.c
Normal file
|
@ -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
src/osx/CpuFreq.h
Normal file
77
src/osx/CpuFreq.h
Normal file
|
@ -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
|
|
@ -50,14 +50,22 @@ tab-size = 4
|
|||
#include <fstream>
|
||||
#include <numeric>
|
||||
#include <ranges>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#include "../btop_config.hpp"
|
||||
#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
|
||||
#ifdef __clang__
|
||||
extern "C" {
|
||||
#include "CpuFreq.h"
|
||||
}
|
||||
#endif
|
||||
#include "sensors.hpp"
|
||||
#endif
|
||||
#include "smc.hpp"
|
||||
|
@ -191,6 +199,9 @@ namespace Cpu {
|
|||
bool has_battery = true;
|
||||
bool macM1 = false;
|
||||
tuple<int, long, string> current_bat;
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
CpuFreqData data;
|
||||
#endif
|
||||
|
||||
const array<string, 10> time_names = {"user", "nice", "system", "idle"};
|
||||
|
||||
|
@ -253,13 +264,15 @@ namespace Cpu {
|
|||
Logger::debug("get_sensors(): show_coretemp=" + std::to_string(Config::getB("show_coretemp")) + " check_temp=" + std::to_string(Config::getB("check_temp")));
|
||||
got_sensors = false;
|
||||
if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
ThermalSensors sensors;
|
||||
if (sensors.getSensors() > 0) {
|
||||
Logger::debug("M1 sensors found");
|
||||
got_sensors = true;
|
||||
cpu_temp_only = true;
|
||||
macM1 = true;
|
||||
data.existingCPUs = Shared::coreCount;
|
||||
CpuFreq_init(&data);
|
||||
} else {
|
||||
#endif
|
||||
// try SMC (intel)
|
||||
|
@ -286,7 +299,7 @@ namespace Cpu {
|
|||
// ignore, we don't have temp
|
||||
got_sensors = false;
|
||||
}
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -297,7 +310,7 @@ namespace Cpu {
|
|||
current_cpu.temp_max = 95; // we have no idea how to get the critical temp
|
||||
try {
|
||||
if (macM1) {
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
ThermalSensors sensors;
|
||||
current_cpu.temp.at(0).push_back(sensors.getSensors());
|
||||
if (current_cpu.temp.at(0).size() > 20)
|
||||
|
@ -324,16 +337,39 @@ namespace Cpu {
|
|||
}
|
||||
}
|
||||
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
uint32_t get_m1_cpuHz() {
|
||||
uint32_t freq = 0;
|
||||
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 = (uint32_t)highest;
|
||||
}
|
||||
#endif
|
||||
|
||||
string get_cpuHz() {
|
||||
unsigned int freq = 1;
|
||||
uint32_t freq = 1;
|
||||
size_t size = sizeof(freq);
|
||||
|
||||
int mib[] = {CTL_HW, HW_CPU_FREQ};
|
||||
|
||||
if (sysctl(mib, 2, &freq, &size, nullptr, 0) < 0) {
|
||||
Logger::debug("sysctl failed");
|
||||
// this fails on Apple Silicon macs. Apparently you're not allowed to know
|
||||
return "";
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
freq = get_m1_cpuHz();
|
||||
if (freq == 0) {
|
||||
// give up
|
||||
#endif
|
||||
return "";
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Logger::debug("freq: " + std::to_string(freq));
|
||||
return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3);
|
||||
}
|
||||
|
||||
|
@ -604,7 +640,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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue