diff --git a/README.md b/README.md index 258f2da..9d94c3f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# zenmonitor -Sensors monitor for [zenpower](https://github.com/ocerman/zenpower/) +# Zen monitor +Zen monitor is monitoring software for AMD Zen-based CPUs. + +It can monitor these values: + - CPU Temperature + - CPU Core (SVI2) Voltage, Current and Power + - SOC (SVI2) Voltage, Current and Power + - Package and Core Power + +![screenshot](screenshot.png) + +## Dependencies + - [zenpower driver](https://github.com/ocerman/zenpower/) - For monitoring CPU temperature and SVI2 sensors + - MSR driver - For monitoring Package/Core Power + +Follow [zenpower README.md](https://github.com/ocerman/zenpower/blob/master/README.md) to install and activate zenpower module. +Enter `sudo modprobe msr` to enable MSR driver. + +## Building +Make sure that GTK3 dev package and common build tools are installed. +``` +make +``` + +## Running +``` +sudo ./zenpower +``` + +## Setup on ubuntu +First follow [installation instructions on zenpower](https://github.com/ocerman/zenpower/blob/master/README.md#installation-commands-for-ubuntu) +Then: +``` +sudo modprobe msr +sudo bash -c 'echo "msr" > /etc/modules-load.d/msr.conf' +sudo apt install build-essential libgtk-3-dev git +cd ~ +git clone https://github.com/ocerman/zenmonitor +cd zenmonitor +make +sudo ./zenmonitor +``` diff --git a/makefile b/makefile new file mode 100755 index 0000000..643bd12 --- /dev/null +++ b/makefile @@ -0,0 +1,2 @@ +build: + cc -Isrc/include `pkg-config --cflags gtk+-3.0` src/*.c src/ss/*.c -o zenmonitor `pkg-config --libs gtk+-3.0` -lm -no-pie diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..3666da4 Binary files /dev/null and b/screenshot.png differ diff --git a/src/gui.c b/src/gui.c new file mode 100644 index 0000000..c59b19d --- /dev/null +++ b/src/gui.c @@ -0,0 +1,164 @@ +#include +#include +#include "gui.h" +#include "zenmonitor.h" + +static GtkTreeModel *model = NULL; +static guint timeout = 0; +static SensorSource *sensor_sources; + +enum { + COLUMN_NAME, + COLUMN_VALUE, + NUM_COLUMNS +}; + +static void init_sensors() { + GtkTreeIter iter; + GSList *sensor; + GtkListStore *store; + SensorSource *source; + const SensorInit *data; + gboolean added; + guint i = 0; + + store = GTK_LIST_STORE(model); + for (source = sensor_sources; source->drv; source++) { + if (source->func_init()){ + source->sensors = source->func_get_sensors(); + if (source->sensors != NULL) { + source->enabled = TRUE; + + sensor = source->sensors; + while (sensor) { + data = (SensorInit*)sensor->data; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + COLUMN_NAME, data->label, + COLUMN_VALUE, " --- ", + -1); + sensor = sensor->next; + i++; + } + } + } + } +} + +static GtkTreeModel* create_model (void) { + GtkListStore *store; + store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING); + return GTK_TREE_MODEL (store); +} + +static gboolean update_data (gpointer data) { + GtkTreeIter iter; + guint number; + GSList *node; + gchar *value; + SensorSource *source; + const SensorInit *sensorData; + + if (model == NULL) + return G_SOURCE_REMOVE; + + if (!gtk_tree_model_get_iter_first (model, &iter)) + return G_SOURCE_REMOVE; + + for (source = sensor_sources; source->drv; source++) { + if (!source->enabled) + continue; + + source->func_update(); + if (source->sensors){ + node = source->sensors; + + while(node) { + sensorData = (SensorInit*)node->data; + if (*(sensorData->value) != ERROR_VALUE) + value = g_strdup_printf(sensorData->printf_format, *(sensorData->value)); + else + value = g_strdup(" ? ? ?"); + + gtk_list_store_set(GTK_LIST_STORE (model), &iter, COLUMN_VALUE, value, -1); + node = node->next; + if (!gtk_tree_model_iter_next(model, &iter)) + break; + } + } + } + return G_SOURCE_CONTINUE; +} + +static void add_columns (GtkTreeView *treeview) { + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + + // NAME + renderer = gtk_cell_renderer_text_new (); + + column = gtk_tree_view_column_new_with_attributes ("Sensor", renderer, + "text", COLUMN_NAME, + NULL); + g_object_set(renderer, "family", "monotype", NULL); + gtk_tree_view_append_column (treeview, column); + + //VALUE + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Value", renderer, + "text", COLUMN_VALUE, + NULL); + g_object_set(renderer, "family", "monotype", NULL); + gtk_tree_view_append_column (treeview, column); +} + + +int start_gui (SensorSource *ss) { + GtkWidget *window; + GtkWidget *treeview; + GtkWidget *sw; + GtkWidget *vbox; + GtkWidget *dialog; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "Zen monitor"); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(window), 330, 300); + + g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add(GTK_CONTAINER (window), vbox); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start(GTK_BOX (vbox), sw, TRUE, TRUE, 0); + + model = create_model(); + treeview = gtk_tree_view_new_with_model(model); + g_object_unref(model); + + gtk_container_add (GTK_CONTAINER(sw), treeview); + add_columns(GTK_TREE_VIEW(treeview)); + gtk_widget_show_all(window); + + if (check_zen()){ + sensor_sources = ss; + init_sensors(); + timeout = g_timeout_add(300, update_data, NULL); + } + else{ + dialog = gtk_message_dialog_new(GTK_WINDOW (window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "Zen CPU not detected!"); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } + + gtk_main(); + return 0; +} + diff --git a/src/include/gui.h b/src/include/gui.h new file mode 100644 index 0000000..0bc2501 --- /dev/null +++ b/src/include/gui.h @@ -0,0 +1 @@ +int start_gui(); diff --git a/src/include/msr.h b/src/include/msr.h new file mode 100644 index 0000000..33471ca --- /dev/null +++ b/src/include/msr.h @@ -0,0 +1,3 @@ +gboolean msr_init(); +void msr_update(); +GSList* msr_get_sensors(); diff --git a/src/include/zenmonitor.h b/src/include/zenmonitor.h new file mode 100644 index 0000000..3a753c7 --- /dev/null +++ b/src/include/zenmonitor.h @@ -0,0 +1,23 @@ +#define ERROR_VALUE -999.0 + +typedef struct +{ + gchar *label; + float *value; + const gchar *printf_format; +} +SensorInit; + +typedef struct { + const gchar *drv; + gboolean (*func_init)(); + GSList* (*func_get_sensors)(); + void (*func_update)(); + gboolean enabled; + GSList *sensors; + +} SensorSource; + +SensorInit* sensor_init_new(void); +void sensor_init_free(SensorInit *s); +gboolean check_zen(); diff --git a/src/include/zenpower.h b/src/include/zenpower.h new file mode 100644 index 0000000..4feeb15 --- /dev/null +++ b/src/include/zenpower.h @@ -0,0 +1,3 @@ +gboolean zenpower_init(); +GSList* zenpower_get_sensors(); +void zenpower_update(); diff --git a/src/ss/msr.c b/src/ss/msr.c new file mode 100644 index 0000000..0085363 --- /dev/null +++ b/src/ss/msr.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include "zenmonitor.h" +#include "msr.h" + +#define MSR_PWR_PRINTF_FORMAT " %8.3f W" +#define MESUREMENT_TIME 0.1 + +// AMD PPR = https://www.amd.com/system/files/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf +// AMD OSRR = https://developer.amd.com/wp-content/resources/56255_3_03.PDF + +guint cores = 0; +guint threads_per_code = 0; +gdouble energy_unit = 0; + +gint *msr_files = NULL; + +gulong package_eng_b = 0; +gulong package_eng_a = 0; +gulong *core_eng_b = NULL; +gulong *core_eng_a = NULL; + +gfloat package_power; +gfloat *core_power; + +static guint get_core_count() { + guint eax = 0, ebx = 0, ecx = 0, edx = 0; + guint logical_cpus; + + // 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 gint open_msr(gshort core) { + gchar msr_path[20]; + sprintf(msr_path, "/dev/cpu/%d/msr", core * threads_per_code); + return open(msr_path, O_RDONLY); +} + +static gboolean read_msr(gint file, guint index, gulong *data) { + if (file < 0) + return FALSE; + + return pread(file, data, sizeof *data, index) == sizeof *data; +} + +gdouble get_energy_unit() { + gulong 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)); +} + +gulong get_package_energy() { + gulong data; + // AMD OSRR: page 139 - MSRC001_029B + if (!read_msr(msr_files[0], 0xC001029B, &data)) + return 0; + + return data; +} + +gulong get_core_energy(gint core) { + gulong data; + // AMD OSRR: page 139 - MSRC001_029A + if (!read_msr(msr_files[core], 0xC001029A, &data)) + return 0; + + return data; +} + +gboolean msr_init() { + int i; + + if (!check_zen()) + return FALSE; + + cores = get_core_count(); + if (cores == 0) + return FALSE; + + msr_files = malloc(cores * sizeof (gint)); + for (i = 0; i < cores; i++) { + msr_files[i] = open_msr(i); + } + + energy_unit = get_energy_unit(); + if (energy_unit == 0) + return FALSE; + + core_eng_b = malloc(cores * sizeof (gulong)); + core_eng_a = malloc(cores * sizeof (gulong)); + core_power = malloc(cores * sizeof (gfloat)); + + return TRUE; +} + +void msr_update() { + GSList *list = NULL; + gint 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); + } + + package_power = (package_eng_a - package_eng_b) * energy_unit / MESUREMENT_TIME; + for (i = 0; i < cores; i++) { + core_power[i] = (core_eng_a[i] - core_eng_b[i]) * energy_unit / MESUREMENT_TIME; + } +} + +GSList* msr_get_sensors() { + GSList *list = NULL; + SensorInit *data; + gint i; + + data = sensor_init_new(); + data->label = g_strdup("Package Power"); + data->value = &package_power; + data->printf_format = MSR_PWR_PRINTF_FORMAT; + list = g_slist_append(list, data); + + for (i = 0; i < cores; i++) { + data = sensor_init_new(); + data->label = g_strdup_printf("Core %d Power", i); + data->value = &(core_power[i]); + data->printf_format = MSR_PWR_PRINTF_FORMAT; + list = g_slist_append(list, data); + } + + return list; +} diff --git a/src/ss/zenpower.c b/src/ss/zenpower.c new file mode 100644 index 0000000..e67211e --- /dev/null +++ b/src/ss/zenpower.c @@ -0,0 +1,91 @@ +#include +#include "zenmonitor.h" +#include "zenpower.h" + +static gchar *zenpowerDir = NULL; + +typedef struct +{ + const gchar *label; + const gchar *file; + const gchar *printf_format; + const double adjust_ratio; + float current_value; +} HwmonSensor; + +HwmonSensor hwmon_sensors[] = { + {"CPU Temperature (tCtrl)", "temp1_input", " %6.2f°C", 1000.0, 0.0}, + {"CPU Temperature (tDie)", "temp2_input", " %6.2f°C", 1000.0, 0.0}, + {"CPU Core Voltage (SVI2)", "in1_input", " %8.3f V", 1000.0, 0.0}, + {"SOC Voltage (SVI2)", "in2_input", " %8.3f V", 1000.0, 0.0}, + {"CPU Core Current (SVI2)", "curr1_input", " %8.3f A", 1000.0, 0.0}, + {"SOC Current (SVI2)", "curr2_input", " %8.3f A", 1000.0, 0.0}, + {"CPU Core Power (SVI2)", "power1_input", " %8.3f W", 1000000.0, 0.0}, + {"SOC Power (SVI2)", "power2_input", " %8.3f W", 1000000.0, 0.0}, + {0, NULL} +}; + +static gboolean read_raw_hwmon_value(const gchar *dir, const gchar *file, gchar **result) { + gchar *full_path; + gboolean file_result; + + full_path = g_strdup_printf("/sys/class/hwmon/%s/%s", dir, file); + file_result = g_file_get_contents(full_path, result, NULL, NULL); + + g_free(full_path); + return file_result; +} + +gboolean zenpower_init() { + GDir *hwmon; + const gchar *entry; + gchar* name = NULL; + + hwmon = g_dir_open("/sys/class/hwmon", 0, NULL); + if (!hwmon) + return FALSE; + + while ((entry = g_dir_read_name(hwmon))) { + read_raw_hwmon_value(entry, "name", &name); + if (strcmp(name, "zenpower")) { + zenpowerDir = g_strdup(entry); + break; + } + g_free(name); + } + + if (!zenpowerDir) + return FALSE; +} + +void zenpower_update() { + gchar *tmp = NULL; + GSList *list = NULL; + HwmonSensor *sensor; + + for (sensor = hwmon_sensors; sensor->label; sensor++) { + if (read_raw_hwmon_value(zenpowerDir, sensor->file, &tmp)){ + sensor->current_value = atof(tmp) / sensor->adjust_ratio; + g_free(tmp); + } + else{ + sensor->current_value = ERROR_VALUE; + } + } +} + +GSList* zenpower_get_sensors() { + GSList *list = NULL; + HwmonSensor *sensor; + SensorInit *data; + + for (sensor = hwmon_sensors; sensor->label; sensor++) { + data = sensor_init_new(); + data->label = g_strdup(sensor->label); + data->value = &sensor->current_value; + data->printf_format = sensor->printf_format; + list = g_slist_append(list, data); + } + + return list; +} diff --git a/src/zenmonitor.c b/src/zenmonitor.c new file mode 100644 index 0000000..5fe042a --- /dev/null +++ b/src/zenmonitor.c @@ -0,0 +1,57 @@ +#include +#include +#include "zenmonitor.h" +#include "zenpower.h" +#include "msr.h" +#include "gui.h" + +#define AMD_STRING "AuthenticAMD" +#define ZEN_FAMILY 0x17 + +gboolean 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 FALSE; + } + + __get_cpuid(1, &eax, &ebx, &ecx, &edx); + + ext_family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF); + if (ext_family != ZEN_FAMILY){ + return FALSE; + } + + return TRUE; +} + +static SensorSource sensor_sources[] = { + { "zenpower", zenpower_init, zenpower_get_sensors, zenpower_update, FALSE, NULL }, + { "msr", msr_init, msr_get_sensors, msr_update, FALSE, NULL }, + { NULL } +}; + +SensorInit *sensor_init_new() { + return g_new0(SensorInit, 1); +} + +void sensor_init_free(SensorInit *s) { + if (s) { + g_free(s->label); + g_free(s); + } +} + +int main (int argc, char *argv[]) +{ + gtk_init(&argc, &argv); + start_gui(sensor_sources); +} diff --git a/zenmonitor b/zenmonitor deleted file mode 100755 index 4f31716..0000000 --- a/zenmonitor +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -hwmon="/sys/class/hwmon" -mdevs=`ls $hwmon` -zenmon="" - -echo -n "Looking for zenpower ..." - -for dev in $mdevs; do - path="$hwmon/$dev" - devname=`cat $path/name` - if [ "$devname" == "zenpower" ]; then - zenmon="$path" - fi -done - -if [ -z "$zenmon" ]; then - echo "NOT FOUND" - exit -else - echo "Found" -fi - -echo Starting Monitor... -echo Press Q to exit. -echo - -while true; do - core=(`cat $zenmon/in1_input $zenmon/curr1_input $zenmon/power1_input`) - soc=(`cat $zenmon/in2_input $zenmon/curr2_input $zenmon/power2_input`) - temps=(`cat $zenmon/temp1_input $zenmon/temp2_input`) - - echo "${core[0]} ${core[1]} ${core[2]}" | awk '{ printf " Core: %7.3fV %7.3fA %7.3fW\n", $1 / 1000, $2 / 1000, $3 / 1000000 }' - echo "${soc[0]} ${soc[1]} ${soc[2]}" | awk '{ printf " SoC: %7.3fV %7.3fA %7.3fW\n", $1 / 1000, $2 / 1000, $3 / 1000000 }' - echo "${temps[0]} ${temps[1]}" | awk '{ printf " tDie: %6.2f°C\ntCtrl: %6.2f°C\n", $1 / 1000, $2 / 1000 }' - - read -t 0.2 -N 1 input - if [[ $input = "q" ]] || [[ $input = "Q" ]]; then - echo - break - fi - - echo -en "\e[4A" -done