From 110226ee18ee806131979fbb84a35831c247a119 Mon Sep 17 00:00:00 2001 From: moosecrap Date: Thu, 26 Sep 2024 21:19:46 -0700 Subject: [PATCH] Adding code --- .gitignore | 2 + LICENSE | 21 ++++ README.md | 57 ++++++++++- makefile | 12 +++ zen-rapl.c | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 makefile create mode 100644 zen-rapl.c diff --git a/.gitignore b/.gitignore index 0a8416d..8814e09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +zen-rapl + # ---> C # Prerequisites *.d diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a005a76 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2020 Ondrej Čerman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a6293dc..fb54299 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,58 @@ # zen-rapl -Advanced monitoring of core power and frequencies on AMD Zen/Epyc processors using MSR. \ No newline at end of file +Advanced monitoring of core power and frequencies on AMD Zen processors using MSR. + +This uses model-specific registers (MSRs) to read the running average power levels (RAPL) the processor. It is designed to compliment the [Zenpower3 driver](https://github.com/koweda/zenpower3). + +The output has been formatted to resemble those from the `sensors` utility so scripts that use the output from that command can parse it the same. + +## Example output +``` +zen3-rapl +Package: 109.59 W +Core_0: 4.50 W +Core_1: 5.02 W +Core_2: 4.38 W +Core_3: 4.71 W +Core_4: 3.60 W +Core_5: 3.91 W +Core_6: 3.42 W +Core_7: 4.00 W +Core_8: 1.20 W +Core_9: 2.46 W +Core_10: 2.26 W +Core_11: 2.44 W +Core_12: 0.97 W +Core_13: 1.62 W +Core_14: 0.08 W +Core_15: 1.52 W +Core_0_eff: 4.600 GHz +Core_1_eff: 3.680 GHz +Core_2_eff: 3.680 GHz +Core_3_eff: 3.680 GHz +Core_4_eff: 3.680 GHz +Core_5_eff: 3.680 GHz +Core_6_eff: 3.680 GHz +Core_7_eff: 3.680 GHz +Core_8_eff: 3.680 GHz +Core_9_eff: 4.600 GHz +Core_10_eff: 3.680 GHz +Core_11_eff: 3.680 GHz +Core_12_eff: 3.680 GHz +Core_13_eff: 3.680 GHz +Core_14_eff: 3.680 GHz +Core_15_eff: 3.680 GHz +``` + +## Requirements +* AMD family 17h or 19h processor (Zen, Zen2, Zen3) +* MSR kernel module (`modprobe msr`) +* Access to the MSR device files at `/dev/cpu/*/msr` (usually requires root) + +## Building +1. `make` +2. `sudo ./zen-rapl` + +## Acknowlegements +Based upon some code from [Zenmonitor 3](https://github.com/jpr999/zenmonitor3) + diff --git a/makefile b/makefile new file mode 100644 index 0000000..674c200 --- /dev/null +++ b/makefile @@ -0,0 +1,12 @@ +.PHONY : clean debug + +LDLIBS = -lm +objects = zen-rapl.o + +zen-rapl : $(objects) + +debug : CFLAGS += -g +debug : zen-rapl + +clean : + rm zen-rapl $(objects) diff --git a/zen-rapl.c b/zen-rapl.c new file mode 100644 index 0000000..e60faf5 --- /dev/null +++ b/zen-rapl.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AMD_STRING "AuthenticAMD" +#define ZEN_FAMILY 0x17 +#define ZEN3_FAMILY 0x19 + +#define MESUREMENT_TIME 0.1 + +static unsigned int cores = 0; +static double energy_unit = 0; +static struct cpudev *cpu_dev_ids; + +static int *msr_files = NULL; + +static unsigned long package_eng_b = 0; +static unsigned long package_eng_a = 0; +static unsigned long *core_eng_b = NULL; +static unsigned long *core_eng_a = NULL; + +float package_power; +float *core_power; +float *core_fid; + +struct cpudev { + short coreid; + short cpuid; +}; + + +static int check_zen() { + unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0, ext_family; + char vendor[13]; + + __get_cpuid(0, &eax, &ebx, &ecx, &edx); + + memcpy(vendor, &ebx, 4); + memcpy(vendor+4, &edx, 4); + memcpy(vendor+8, &ecx, 4); + vendor[12] = 0; + + if (strcmp(vendor, AMD_STRING) != 0){ + return 0; + } + + __get_cpuid(1, &eax, &ebx, &ecx, &edx); + + ext_family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF); + if (ext_family != ZEN_FAMILY && ext_family != ZEN3_FAMILY){ + return 0; + } + + return 1; +} + +static unsigned int get_core_count() { + unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0; + unsigned int logical_cpus, threads_per_code; + + // AMD PPR: page 57 - CPUID_Fn00000001_EBX + __get_cpuid(1, &eax, &ebx, &ecx, &edx); + logical_cpus = (ebx >> 16) & 0xFF; + + // AMD PPR: page 82 - CPUID_Fn8000001E_EBX + __get_cpuid(0x8000001E, &eax, &ebx, &ecx, &edx); + threads_per_code = ((ebx >> 8) & 0xF) + 1; + + if (threads_per_code == 0) + return logical_cpus; + + return logical_cpus / threads_per_code; +} + +static struct cpudev* get_cpu_dev_ids() { + unsigned int num_cores = get_core_count(); + struct cpudev* cores = malloc(num_cores * sizeof(struct cpudev)); + + for (int i = 0; i < num_cores; ++i) { + cores[i].coreid = i; + cores[i].cpuid = -1; + } + + DIR* d; + struct dirent* dir; + d = opendir("/sys/devices/system/cpu"); + + if (d) { + while ((dir = readdir(d)) != NULL) { + if (strncmp(dir->d_name, "cpu", 3) == 0 && atoi(dir->d_name + 3) > 0) { + char path[256]; + int core_id; + FILE* f; + + snprintf(path, sizeof(path), "/sys/devices/system/cpu/%s/topology/core_id", dir->d_name); + f = fopen(path, "r"); + if (f) { + fscanf(f, "%d", &core_id); + fclose(f); + } + + snprintf(path, sizeof(path), "/sys/devices/system/cpu/%s/topology/thread_siblings_list", dir->d_name); + f = fopen(path, "r"); + + if (f) { + int cpuid, sibling; + + if (fscanf(f, "%d,%d", &cpuid, &sibling) == 2) { + // The lower numbered CPU is considered the representative of the core + if (cores[core_id].cpuid == -1 || cores[core_id].cpuid > cpuid) { + cores[core_id].cpuid = cpuid; + } + } + fclose(f); + } + } + } + closedir(d); + } + + return cores; +} + +static int open_msr(short devid) { + char msr_path[20]; + sprintf(msr_path, "/dev/cpu/%d/msr", devid); + return open(msr_path, O_RDONLY); +} + +static int read_msr(int file, unsigned int index, unsigned long long *data) { + if (file < 0) + return 0; + + return pread(file, data, sizeof *data, index) == sizeof *data; +} + +static double get_energy_unit() { + unsigned long long data; + // AMD OSRR: page 139 - MSRC001_0299 + if (!read_msr(msr_files[0], 0xC0010299, &data)) + return 0.0; + + return pow(1.0/2.0, (double)((data >> 8) & 0x1F)); +} + +static unsigned long get_package_energy() { + unsigned long long data; + // AMD OSRR: page 139 - MSRC001_029B + if (!read_msr(msr_files[0], 0xC001029B, &data)) + return 0; + + return data; +} + +static unsigned long get_core_energy(int core) { + unsigned long long data; + // AMD OSRR: page 139 - MSRC001_029A + if (!read_msr(msr_files[core], 0xC001029A, &data)) + return 0; + + return data; +} + +static double get_core_fid(int core) { + double ratio; + unsigned long long data; + + // By reverse-engineering Ryzen Master, we know that + // this undocumented MSR is responsible for returning + // the FID and FDID for the core used for calculating the + // effective frequency. + // + // The FID is returned in bits [8:0] + // The FDID is returned in bits [14:8] + if (!read_msr(msr_files[core], 0xC0010293, &data)) + return 0; + + ratio = (double)(data & 0xff) / (double)((data >> 8) & 0x3F); + + // The effective ratio is based on increments of 200 MHz. + return ratio * 200.0 / 1000.0; +} + +static int msr_init() { + unsigned int i; + + if (!check_zen()) + return 0; + + cores = get_core_count(); + if (cores == 0) + return 0; + + cpu_dev_ids = get_cpu_dev_ids(); + msr_files = malloc(cores * sizeof (int)); + for (i = 0; i < cores; i++) { + msr_files[i] = open_msr(cpu_dev_ids[i].cpuid); + if (msr_files[i] < 0) + return 0; + } + + energy_unit = get_energy_unit(); + if (energy_unit == 0) + return 0; + + core_eng_b = malloc(cores * sizeof (unsigned long)); + core_eng_a = malloc(cores * sizeof (unsigned long)); + core_power = malloc(cores * sizeof (float)); + core_fid = malloc(cores * sizeof (float)); + + /*msr_update();*/ + + return 1; +} + +static void msr_update() { + int i; + + package_eng_b = get_package_energy(); + for (i = 0; i < cores; i++) { + core_eng_b[i] = get_core_energy(i); + } + + usleep(MESUREMENT_TIME*1000000); + + package_eng_a = get_package_energy(); + for (i = 0; i < cores; i++) { + core_eng_a[i] = get_core_energy(i); + } + + if (package_eng_a >= package_eng_b) { + package_power = (package_eng_a - package_eng_b) * energy_unit / MESUREMENT_TIME; + } + + for (i = 0; i < cores; i++) { + if (core_eng_a[i] >= core_eng_b[i]) { + core_power[i] = (core_eng_a[i] - core_eng_b[i]) * energy_unit / MESUREMENT_TIME; + } + + core_fid[i] = get_core_fid(i); + } +} + + +int main(int argc, char *argv[]) { + int init_ok, i; + init_ok = msr_init(); + + if (init_ok) { + msr_update(); + } else { + printf("Error reading RAPL (running average power limit) sensors.\nAre you root? Did you `modprobe msr`? Are you on AMD Zen?\n"); + return 1; + } + + printf("zen3-rapl\n"); + printf("%-12s %6.2f W\n", "Package:", package_power); + char core_name[256]; + /* Print core powers */ + for (i = 0; i < cores; i++) { + sprintf(core_name, "Core_%d:", i); + printf("%-12s %6.2f W\n", core_name, i, core_power[i]); + } + + /* Print core effective frequencies */ + for (i = 0; i < cores; i++) { + sprintf(core_name, "Core_%d_eff:", i); + printf("%-12s %6.3f GHz\n", core_name, i, core_fid[i]); + } + return 0; +}