Compare commits

...

1 Commits

Author SHA1 Message Date
110226ee18 Adding code 2024-09-26 21:20:42 -07:00
5 changed files with 368 additions and 1 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
zen-rapl
# ---> C
# Prerequisites
*.d

21
LICENSE Normal file
View File

@ -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.

View File

@ -1,3 +1,58 @@
# zen-rapl
Advanced monitoring of core power and frequencies on AMD Zen/Epyc processors using MSR.
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)

12
makefile Normal file
View File

@ -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)

277
zen-rapl.c Normal file
View File

@ -0,0 +1,277 @@
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <dirent.h>
#include <cpuid.h>
#include <math.h>
#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;
}