aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOndrej Čerman2019-06-15 16:51:21 +0200
committerOndrej Čerman2019-06-15 16:51:21 +0200
commit37fc68ca76571bbb1ccf8c21ea159e5273bcde4a (patch)
tree352b1cde0b3b331f10cd38538315df1197615019
parentafdd058cfb2e06dee04a373ab98b380e4e3e5ad9 (diff)
New version - code rewritten to c, added GTK3 gui, added Core and Package Power monitoring via MSR
-rw-r--r--README.md44
-rwxr-xr-xmakefile2
-rw-r--r--screenshot.pngbin0 -> 29300 bytes
-rw-r--r--src/gui.c164
-rw-r--r--src/include/gui.h1
-rw-r--r--src/include/msr.h3
-rw-r--r--src/include/zenmonitor.h23
-rw-r--r--src/include/zenpower.h3
-rw-r--r--src/ss/msr.c156
-rw-r--r--src/ss/zenpower.c91
-rw-r--r--src/zenmonitor.c57
-rwxr-xr-xzenmonitor44
12 files changed, 542 insertions, 46 deletions
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
--- /dev/null
+++ b/screenshot.png
Binary files 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 <cpuid.h>
+#include <gtk/gtk.h>
+#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 <glib.h>
+#include <cpuid.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#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 <glib.h>
+#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 <gtk/gtk.h>
+#include <cpuid.h>
+#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