New version - code rewritten to c, added GTK3 gui, added Core and Package Power monitoring via MSR

This commit is contained in:
Ondrej Čerman 2019-06-15 16:51:21 +02:00
parent afdd058cfb
commit 37fc68ca76
12 changed files with 542 additions and 46 deletions

View file

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

2
makefile Executable file
View file

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

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

164
src/gui.c Normal file
View file

@ -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;
}

1
src/include/gui.h Normal file
View file

@ -0,0 +1 @@
int start_gui();

3
src/include/msr.h Normal file
View file

@ -0,0 +1,3 @@
gboolean msr_init();
void msr_update();
GSList* msr_get_sensors();

23
src/include/zenmonitor.h Normal file
View file

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

3
src/include/zenpower.h Normal file
View file

@ -0,0 +1,3 @@
gboolean zenpower_init();
GSList* zenpower_get_sensors();
void zenpower_update();

156
src/ss/msr.c Normal file
View file

@ -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;
}

91
src/ss/zenpower.c Normal file
View file

@ -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;
}

57
src/zenmonitor.c Normal file
View file

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

View file

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