Repository: ocerman/zenmonitor
Branch: master
Commit: fa17e94b3061
Files: 18
Total size: 37.4 KB
Directory structure:
gitextract_ikdbt1cl/
├── LICENSE
├── README.md
├── data/
│ ├── org.pkexec.zenmonitor.policy.in
│ ├── zenmonitor-root.desktop.in
│ └── zenmonitor.desktop.in
├── makefile
└── src/
├── gui.c
├── include/
│ ├── gui.h
│ ├── msr.h
│ ├── os.h
│ ├── sysfs.h
│ ├── zenmonitor.h
│ └── zenpower.h
├── ss/
│ ├── msr.c
│ ├── os.c
│ └── zenpower.c
├── sysfs.c
└── zenmonitor.c
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# 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 (RAPL)
- Core Frequency (from OS)

## Dependencies
- [zenpower driver](https://github.com/ocerman/zenpower/) - For monitoring CPU temperature and SVI2 sensors
- MSR driver - For monitoring Package/Core Power (RAPL)
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
```
## Launching
You can launch app by `sudo ./zenmonitor`, or you can install it to your system and then launch it from your OS menu.
Note: Because superuser privileges are usually needed to access data from MSR driver, you need to launch zenmonitor as root for monitoring CPU power usage (RAPL).
Alternatively, you can set capabilities to zenmonitor executable: `sudo setcap cap_sys_rawio,cap_dac_read_search+ep ./zenmonitor`
## Command line arguments
``--coreid`` - Display core_id instead of core index
## Installing
By default, Zenmonitor will be installed to /usr/local.
```
sudo make install
```
To add menu item for launching zenpower as root (Polkit is required):
```
sudo make install-polkit
```
## Uninstalling
```
sudo make uninstall
```
## 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 make install
sudo make install-polkit
```
## Setup on Arch
You may use the AUR package [zenmonitor-git](https://aur.archlinux.org/packages/zenmonitor-git/) to install via [traditional method](https://wiki.archlinux.org/index.php/Arch_User_Repository) or using an AUR helper (like yay)
================================================
FILE: data/org.pkexec.zenmonitor.policy.in
================================================
Run Zenmonitor as root
Authentication is required to run Zenmonitor as root.
auth_admin
auth_admin
auth_admin
@APP_EXEC@
true
================================================
FILE: data/zenmonitor-root.desktop.in
================================================
[Desktop Entry]
Name=Zenmonitor (root)
Comment=Monitoring software for AMD Zen-based CPUs
Exec=pkexec @APP_EXEC@
Type=Application
Categories=GTK;System;
Terminal=false
Keywords=CPU;AMD;zen;system;core;speed;clock;temperature;voltage;
================================================
FILE: data/zenmonitor.desktop.in
================================================
[Desktop Entry]
Name=Zenmonitor
Comment=Monitoring software for AMD Zen-based CPUs
Exec=@APP_EXEC@
Type=Application
Categories=GTK;System;
Terminal=false
Keywords=CPU;AMD;zen;system;core;speed;clock;temperature;voltage;
================================================
FILE: makefile
================================================
ifeq ($(PREFIX),)
PREFIX := /usr/local
endif
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 -Wall
install:
mkdir -p $(DESTDIR)$(PREFIX)/bin
install -m 755 zenmonitor $(DESTDIR)$(PREFIX)/bin
mkdir -p $(DESTDIR)$(PREFIX)/share/applications
sed -e "s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|" \
data/zenmonitor.desktop.in > \
$(DESTDIR)$(PREFIX)/share/applications/zenmonitor.desktop
install-polkit:
sed -e "s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|" \
data/zenmonitor-root.desktop.in > \
$(DESTDIR)$(PREFIX)/share/applications/zenmonitor-root.desktop
sed -e "s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|" \
data/org.pkexec.zenmonitor.policy.in > \
$(DESTDIR)/usr/share/polkit-1/actions/org.pkexec.zenmonitor.policy
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/zenmonitor
rm -f $(DESTDIR)$(PREFIX)/share/applications/zenmonitor.desktop
rm -f $(DESTDIR)$(PREFIX)/share/applications/zenmonitor-root.desktop
rm -f $(DESTDIR)/usr/share/polkit-1/actions/org.pkexec.zenmonitor.policy
clean:
rm -f zenmonitor
================================================
FILE: src/gui.c
================================================
#include
#include
#include "gui.h"
#include "zenmonitor.h"
GtkWidget *window;
static GtkTreeModel *model = NULL;
static guint timeout = 0;
static SensorSource *sensor_sources;
static const guint defaultHeight = 350;
enum {
COLUMN_NAME,
COLUMN_HINT,
COLUMN_VALUE,
COLUMN_MIN,
COLUMN_MAX,
NUM_COLUMNS
};
static void init_sensors() {
GtkTreeIter iter;
GSList *sensor;
GtkListStore *store;
SensorSource *source;
const SensorInit *data;
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_HINT, data->hint,
COLUMN_VALUE, " --- ",
COLUMN_MIN, " --- ",
COLUMN_MAX, " --- ",
-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, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
return GTK_TREE_MODEL (store);
}
static void set_list_column_value(float num, const gchar *printf_format, GtkTreeIter *iter, gint column){
gchar *value;
if (num != ERROR_VALUE)
value = g_strdup_printf(printf_format, num);
else
value = g_strdup(" ? ? ?");
gtk_list_store_set(GTK_LIST_STORE (model), iter, column, value, -1);
g_free(value);
}
static gboolean update_data (gpointer data) {
GtkTreeIter iter;
GSList *node;
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;
set_list_column_value(*(sensorData->value), sensorData->printf_format, &iter, COLUMN_VALUE);
set_list_column_value(*(sensorData->min), sensorData->printf_format, &iter, COLUMN_MIN);
set_list_column_value(*(sensorData->max), sensorData->printf_format, &iter, COLUMN_MAX);
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;
// 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);
//MIN
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Min", renderer,
"text", COLUMN_MIN,
NULL);
g_object_set(renderer, "family", "monotype", NULL);
gtk_tree_view_append_column (treeview, column);
//MAX
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Max", renderer,
"text", COLUMN_MAX,
NULL);
g_object_set(renderer, "family", "monotype", NULL);
gtk_tree_view_append_column (treeview, column);
}
static void about_btn_clicked(GtkButton *button, gpointer user_data) {
GtkWidget *dialog;
const gchar *website = "https://github.com/ocerman/zenmonitor";
const gchar *msg = "Zen Monitor %s\n"
"Monitoring software for AMD Zen-based CPUs\n"
"%s\n\n"
"Created by: Ondrej Čerman";
dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW (window),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
msg, VERSION, website, website);
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
static void clear_btn_clicked(GtkButton *button, gpointer user_data) {
SensorSource *source;
for (source = sensor_sources; source->drv; source++) {
if (!source->enabled)
continue;
source->func_clear_minmax();
}
}
static gboolean mid_search_eq_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter) {
gchar *iter_string = NULL, *lc_iter_string = NULL, *lc_key = NULL;
gboolean result;
gtk_tree_model_get(model, iter, column, &iter_string, -1);
lc_iter_string = g_utf8_strdown(iter_string, -1);
lc_key = g_utf8_strdown(key, -1);
result = (g_strrstr(lc_iter_string, lc_key) == NULL);
g_free(iter_string);
g_free(lc_iter_string);
g_free(lc_key);
return result;
}
static void resize_to_treeview(GtkWindow* window, GtkTreeView* treeview) {
gint uiHeight, cellHeight, vSeparator, rows;
GdkRectangle r;
GtkTreeViewColumn *col = gtk_tree_view_get_column(treeview, 0);
if (!col)
return;
gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL, NULL, &cellHeight);
gtk_widget_style_get(GTK_WIDGET(treeview), "vertical-separator", &vSeparator, NULL);
rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(treeview), NULL);
gtk_tree_view_get_visible_rect(treeview, &r);
uiHeight = defaultHeight - r.height;
gtk_window_resize(window, 500, uiHeight + (vSeparator + cellHeight) * rows);
}
int start_gui (SensorSource *ss) {
GtkWidget *about_btn;
GtkWidget *clear_btn;
GtkWidget *box;
GtkWidget *header;
GtkWidget *treeview;
GtkWidget *sw;
GtkWidget *vbox;
GtkWidget *dialog;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 500, defaultHeight);
header = gtk_header_bar_new();
gtk_header_bar_set_show_close_button(GTK_HEADER_BAR (header), TRUE);
gtk_header_bar_set_title(GTK_HEADER_BAR (header), "Zen monitor");
gtk_header_bar_set_has_subtitle(GTK_HEADER_BAR (header), TRUE);
gtk_header_bar_set_subtitle(GTK_HEADER_BAR (header), cpu_model());
gtk_window_set_titlebar (GTK_WINDOW (window), header);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_style_context_add_class (gtk_widget_get_style_context (box), "linked");
about_btn = gtk_button_new();
gtk_container_add(GTK_CONTAINER(about_btn), gtk_image_new_from_icon_name("dialog-information", GTK_ICON_SIZE_BUTTON));
gtk_container_add(GTK_CONTAINER(box), about_btn);
gtk_widget_set_tooltip_text(about_btn, "About Zen monitor");
clear_btn = gtk_button_new();
gtk_container_add(GTK_CONTAINER(clear_btn), gtk_image_new_from_icon_name("edit-clear-all", GTK_ICON_SIZE_BUTTON));
gtk_container_add(GTK_CONTAINER(box), clear_btn);
gtk_widget_set_tooltip_text(clear_btn, "Clear Min/Max");
gtk_header_bar_pack_start(GTK_HEADER_BAR(header), box);
g_signal_connect(about_btn, "clicked", G_CALLBACK(about_btn_clicked), NULL);
g_signal_connect(clear_btn, "clicked", G_CALLBACK(clear_btn_clicked), NULL);
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);
gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), COLUMN_HINT);
gtk_container_add (GTK_CONTAINER(sw), treeview);
add_columns(GTK_TREE_VIEW(treeview));
gtk_widget_show_all(window);
gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), COLUMN_NAME);
gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(treeview),
(GtkTreeViewSearchEqualFunc)mid_search_eq_func, model, NULL);
g_object_unref(model);
if (check_zen()){
sensor_sources = ss;
init_sensors();
resize_to_treeview(GTK_WINDOW(window), GTK_TREE_VIEW(treeview));
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;
}
================================================
FILE: src/include/gui.h
================================================
int start_gui();
================================================
FILE: src/include/msr.h
================================================
gboolean msr_init();
void msr_update();
void msr_clear_minmax();
GSList* msr_get_sensors();
================================================
FILE: src/include/os.h
================================================
gboolean os_init(void);
void os_update(void);
void os_clear_minmax(void);
GSList* os_get_sensors(void);
================================================
FILE: src/include/sysfs.h
================================================
#define SYSFS_DIR_CPUS "/sys/devices/system/cpu"
struct cpudev {
gshort coreid;
gshort cpuid;
};
struct cpudev * get_cpu_dev_ids(void);
================================================
FILE: src/include/zenmonitor.h
================================================
#define ERROR_VALUE -999.0
#define VERSION "1.4.2"
typedef struct
{
gchar *label;
gchar *hint;
float *value;
float *min;
float *max;
const gchar *printf_format;
}
SensorInit;
typedef struct {
const gchar *drv;
gboolean (*func_init)();
GSList* (*func_get_sensors)();
void (*func_update)();
void (*func_clear_minmax)();
gboolean enabled;
GSList *sensors;
} SensorSource;
SensorInit* sensor_init_new(void);
void sensor_init_free(SensorInit *s);
gboolean check_zen();
gchar *cpu_model();
guint get_core_count();
extern gboolean display_coreid;
================================================
FILE: src/include/zenpower.h
================================================
gboolean zenpower_init();
GSList* zenpower_get_sensors();
void zenpower_update();
void zenpower_clear_minmax();
================================================
FILE: src/ss/msr.c
================================================
#include
#include
#include
#include
#include
#include
#include
#include
#include "zenmonitor.h"
#include "msr.h"
#include "sysfs.h"
#define MSR_PWR_PRINTF_FORMAT " %8.3f W"
#define MSR_FID_PRINTF_FORMAT " %8.3f GHz"
#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
static guint cores = 0;
static gdouble energy_unit = 0;
static struct cpudev *cpu_dev_ids;
static gint *msr_files = NULL;
static gulong package_eng_b = 0;
static gulong package_eng_a = 0;
static gulong *core_eng_b = NULL;
static gulong *core_eng_a = NULL;
gfloat package_power;
gfloat package_power_min;
gfloat package_power_max;
gfloat *core_power;
gfloat *core_fid;
gfloat *core_power_min;
gfloat *core_power_max;
gfloat *core_fid_min;
gfloat *core_fid_max;
static gint open_msr(gshort devid) {
gchar msr_path[20];
sprintf(msr_path, "/dev/cpu/%d/msr", devid);
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;
}
gdouble get_core_fid(gint core) {
gdouble ratio;
gulong 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 = (gdouble)(data & 0xff) / (gdouble)((data >> 8) & 0x3F);
// The effective ratio is based on increments of 200 MHz.
return ratio * 200.0 / 1000.0;
}
gboolean msr_init() {
guint i;
if (!check_zen())
return FALSE;
cores = get_core_count();
if (cores == 0)
return FALSE;
cpu_dev_ids = get_cpu_dev_ids();
msr_files = malloc(cores * sizeof (gint));
for (i = 0; i < cores; i++) {
msr_files[i] = open_msr(cpu_dev_ids[i].cpuid);
}
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));
core_fid = malloc(cores * sizeof (gfloat));
core_power_min = malloc(cores * sizeof (gfloat));
core_power_max = malloc(cores * sizeof (gfloat));
core_fid_min = malloc(cores * sizeof (gfloat));
core_fid_max = malloc(cores * sizeof (gfloat));
msr_update();
memcpy(core_power_min, core_power, cores * sizeof (gfloat));
memcpy(core_power_max, core_power, cores * sizeof (gfloat));
memcpy(core_fid_min, core_fid, cores * sizeof (gfloat));
memcpy(core_fid_max, core_fid, cores * sizeof (gfloat));
package_power_min = package_power;
package_power_max = package_power;
return TRUE;
}
void msr_update() {
guint 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;
if (package_power < package_power_min)
package_power_min = package_power;
if (package_power > package_power_max)
package_power_max = package_power;
}
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;
if (core_power[i] < core_power_min[i])
core_power_min[i] = core_power[i];
if (core_power[i] > core_power_max[i])
core_power_max[i] = core_power[i];
}
core_fid[i] = get_core_fid(i);
if (core_fid[i] < core_fid_min[i])
core_fid_min[i] = core_fid[i];
if (core_fid[i] > core_fid_max[i])
core_fid_max[i] = core_fid[i];
}
}
void msr_clear_minmax() {
guint i;
package_power_min = package_power;
package_power_max = package_power;
for (i = 0; i < cores; i++) {
core_power_min[i] = core_power[i];
core_power_max[i] = core_power[i];
core_fid_min[i] = core_fid[i];
core_fid_max[i] = core_fid[i];
}
}
GSList* msr_get_sensors() {
GSList *list = NULL;
SensorInit *data;
guint i;
data = sensor_init_new();
data->label = g_strdup("Package Power");
data->hint = g_strdup("Package Power reported by RAPL\nSource: cpu0 MSR");
data->value = &package_power;
data->min = &package_power_min;
data->max = &package_power_max;
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 Effective Frequency", display_coreid ? cpu_dev_ids[i].coreid: i);
data->hint = g_strdup_printf("Source: cpu%d MSR", cpu_dev_ids[i].cpuid);
data->value = &(core_fid[i]);
data->min = &(core_fid_min[i]);
data->max = &(core_fid_max[i]);
data->printf_format = MSR_FID_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", display_coreid ? cpu_dev_ids[i].coreid: i);
data->hint = g_strdup_printf("Core Power reported by RAPL\nSource: cpu%d MSR", cpu_dev_ids[i].cpuid);
data->value = &(core_power[i]);
data->min = &(core_power_min[i]);
data->max = &(core_power_max[i]);
data->printf_format = MSR_PWR_PRINTF_FORMAT;
list = g_slist_append(list, data);
}
return list;
}
================================================
FILE: src/ss/os.c
================================================
#include
#include
#include
#include "zenmonitor.h"
#include "sysfs.h"
#include "os.h"
#define OS_FREQ_PRINTF_FORMAT " %8.3f GHz"
static gchar **frq_files = NULL;
static guint cores;
static struct cpudev *cpu_dev_ids;
gfloat *core_freq;
gfloat *core_freq_min;
gfloat *core_freq_max;
static gdouble get_frequency(guint corei) {
gchar *data;
gdouble freq;
if (!g_file_get_contents(frq_files[corei], &data, NULL, NULL))
return 0.0;
freq = atoi(data) / 1000000.0;
g_free(data);
return freq;
}
gboolean os_init(void) {
guint i;
if (!check_zen())
return FALSE;
cores = get_core_count();
if (cores == 0)
return FALSE;
cpu_dev_ids = get_cpu_dev_ids();
frq_files = malloc(cores * sizeof (gchar*));
for (i = 0; i < cores; i++) {
frq_files[i] = g_strdup_printf(
"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq",
cpu_dev_ids[i].cpuid);
}
core_freq = malloc(cores * sizeof (gfloat));
core_freq_min = malloc(cores * sizeof (gfloat));
core_freq_max = malloc(cores * sizeof (gfloat));
os_update();
memcpy(core_freq_min, core_freq, cores * sizeof (gfloat));
memcpy(core_freq_max, core_freq, cores * sizeof (gfloat));
return TRUE;
}
void os_update(void) {
guint i;
for (i = 0; i < cores; i++) {
core_freq[i] = get_frequency(i);
if (core_freq[i] < core_freq_min[i])
core_freq_min[i] = core_freq[i];
if (core_freq[i] > core_freq_max[i])
core_freq_max[i] = core_freq[i];
}
}
void os_clear_minmax(void) {
guint i;
for (i = 0; i < cores; i++) {
core_freq_min[i] = core_freq[i];
core_freq_max[i] = core_freq[i];
}
}
GSList* os_get_sensors(void) {
GSList *list = NULL;
SensorInit *data;
guint i;
for (i = 0; i < cores; i++) {
data = sensor_init_new();
data->label = g_strdup_printf("Core %d Frequency", display_coreid ? cpu_dev_ids[i].coreid: i);
data->hint = g_strdup_printf("Current frequency of the CPU as determined by the governor and cpufreq core.\n Source: %s", frq_files[i]);
data->value = &(core_freq[i]);
data->min = &(core_freq_min[i]);
data->max = &(core_freq_max[i]);
data->printf_format = OS_FREQ_PRINTF_FORMAT;
list = g_slist_append(list, data);
}
return list;
}
================================================
FILE: src/ss/zenpower.c
================================================
#include
#include
#include
#include "zenmonitor.h"
#include "zenpower.h"
GSList *zp_sensors = NULL;
static int nodes = 0;
typedef struct
{
const gchar *label;
const gchar *hint;
const gchar *file;
const gchar *printf_format;
const double adjust_ratio;
} HwmonSensorType;
typedef struct
{
float current_value;
float min;
float max;
HwmonSensorType *type;
gchar *hwmon_dir;
int node;
} HwmonSensor;
static HwmonSensorType hwmon_stype[] = {
{"CPU Temperature (tCtl)", "Reported CPU Temperature", "temp1_input", " %6.2f°C", 1000.0},
{"CPU Temperature (tDie)", "Reported CPU Temperature - offset", "temp2_input", " %6.2f°C", 1000.0},
{"CCD1 Temperature", "Core Complex Die 1 Temperature", "temp3_input", " %6.2f°C", 1000.0},
{"CCD2 Temperature", "Core Complex Die 2 Temperature", "temp4_input", " %6.2f°C", 1000.0},
{"CCD3 Temperature", "Core Complex Die 3 Temperature", "temp5_input", " %6.2f°C", 1000.0},
{"CCD4 Temperature", "Core Complex Die 4 Temperature", "temp6_input", " %6.2f°C", 1000.0},
{"CCD5 Temperature", "Core Complex Die 5 Temperature", "temp7_input", " %6.2f°C", 1000.0},
{"CCD6 Temperature", "Core Complex Die 6 Temperature", "temp8_input", " %6.2f°C", 1000.0},
{"CCD7 Temperature", "Core Complex Die 7 Temperature", "temp9_input", " %6.2f°C", 1000.0},
{"CCD8 Temperature", "Core Complex Die 8 Temperature", "temp10_input", " %6.2f°C", 1000.0},
{"CPU Core Voltage (SVI2)", "Core Voltage reported by SVI2 telemetry", "in1_input", " %8.3f V", 1000.0},
{"SOC Voltage (SVI2)", "SOC Voltage reported by SVI2 telemetry", "in2_input", " %8.3f V", 1000.0},
{"CPU Core Current (SVI2)", "Core Current reported by SVI2 telemetry\n"
"Note: May not be accurate on some systems", "curr1_input", " %8.3f A", 1000.0},
{"SOC Current (SVI2)", "SOC Current reported by SVI2 telemetry\n"
"Note: May not be accurate on some systems", "curr2_input", " %8.3f A", 1000.0},
{"CPU Core Power (SVI2)", "Core Voltage * Current\n"
"Note: May not be accurate on some systems", "power1_input", " %8.3f W", 1000000.0},
{"SOC Power (SVI2)", "Core Voltage * Current\n"
"Note: May not be accurate on some systems", "power2_input", " %8.3f W", 1000000.0},
{0, NULL}
};
static gboolean hwmon_file_exists(const gchar *dir, const gchar *file) {
gchar *full_path;
gboolean result;
full_path = g_strdup_printf("/sys/class/hwmon/%s/%s", dir, file);
result = g_file_test(full_path, G_FILE_TEST_EXISTS);
g_free(full_path);
return result;
}
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;
}
static HwmonSensor *hwmon_sensor_new(HwmonSensorType *type, const gchar *dir, gint node) {
HwmonSensor *s;
s = g_new0(HwmonSensor, 1);
s->min = 999.0;
s->type = type;
s->hwmon_dir = g_strdup(dir);
s->node = node;
return s;
}
gboolean zenpower_init() {
GDir *hwmon;
const gchar *entry;
gchar *name = NULL;
HwmonSensorType *type;
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(g_strchomp(name), "zenpower") == 0) {
for (type = hwmon_stype; type->label; type++) {
if (hwmon_file_exists(entry, type->file)) {
zp_sensors = g_slist_append(zp_sensors, hwmon_sensor_new(type, entry, nodes));
}
}
nodes++;
}
g_free(name);
}
if (zp_sensors == NULL)
return FALSE;
return TRUE;
}
void zenpower_update() {
gchar *tmp = NULL;
GSList *node;
HwmonSensor *sensor;
node = zp_sensors;
while(node) {
sensor = (HwmonSensor *)node->data;
if (read_raw_hwmon_value(sensor->hwmon_dir, sensor->type->file, &tmp)){
sensor->current_value = atof(tmp) / sensor->type->adjust_ratio;
if (sensor->current_value < sensor->min)
sensor->min = sensor->current_value;
if (sensor->current_value > sensor->max)
sensor->max = sensor->current_value;
g_free(tmp);
}
else{
sensor->current_value = ERROR_VALUE;
}
node = node->next;
}
}
void zenpower_clear_minmax() {
HwmonSensor *sensor;
GSList *node;
node = zp_sensors;
while(node) {
sensor = (HwmonSensor *)node->data;
sensor->min = sensor->current_value;
sensor->max = sensor->current_value;
node = node->next;
}
}
GSList* zenpower_get_sensors() {
GSList *list = NULL;
HwmonSensor *sensor;
GSList *node;
SensorInit *data;
node = zp_sensors;
while(node) {
sensor = (HwmonSensor *)node->data;
data = sensor_init_new();
if (nodes > 1){
data->label = g_strdup_printf("Node %d - %s", sensor->node, sensor->type->label);
}
else{
data->label = g_strdup(sensor->type->label);
}
data->hint = g_strdup_printf("%s\nSource: zenpower %s/%s", sensor->type->hint, sensor->hwmon_dir, sensor->type->file);
data->value = &sensor->current_value;
data->min = &sensor->min;
data->max = &sensor->max;
data->printf_format = sensor->type->printf_format;
list = g_slist_append(list, data);
node = node->next;
}
return list;
}
================================================
FILE: src/sysfs.c
================================================
#include
#include
#include
#include
#include "sysfs.h"
#include "zenmonitor.h"
#define CPUD_MAX 512
struct bitset {
guint bits[CPUD_MAX/32];
};
static int bitset_set(struct bitset *set, int id) {
if (id < CPUD_MAX) {
int v = (set->bits[id/32] >> (id & 31)) & 1;
set->bits[id/32] |= 1 << (id & 31);
return v;
}
return 1;
}
static int cmp_cpudev(const void *ap, const void *bp) {
return ((struct cpudev *)ap)->cpuid - ((struct cpudev *)bp)->cpuid;
}
struct cpudev* get_cpu_dev_ids(void) {
struct cpudev *cpu_dev_ids;
gshort coreid, cpuid, siblingid;
GDir *dir;
const gchar *entry;
gchar *filename, *buffer;
gchar **cpusiblings;
gchar **ptr;
guint cores;
gboolean found;
struct bitset seen = { 0 };
int i;
cores = get_core_count();
cpu_dev_ids = malloc(cores * sizeof (*cpu_dev_ids));
for (i=0;i
#include
#include
#include
#include "zenmonitor.h"
#include "zenpower.h"
#include "msr.h"
#include "os.h"
#include "gui.h"
#define AMD_STRING "AuthenticAMD"
#define ZEN_FAMILY 0x17
// AMD PPR = https://www.amd.com/system/files/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf
gboolean check_zen() {
guint32 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;
}
gchar *cpu_model() {
guint32 eax = 0, ebx = 0, ecx = 0, edx = 0;
char model[48];
// AMD PPR: page 65-68 - CPUID_Fn80000002_EAX-CPUID_Fn80000004_EDX
__get_cpuid(0x80000002, &eax, &ebx, &ecx, &edx);
memcpy(model, &eax, 4);
memcpy(model+4, &ebx, 4);
memcpy(model+8, &ecx, 4);
memcpy(model+12, &edx, 4);
__get_cpuid(0x80000003, &eax, &ebx, &ecx, &edx);
memcpy(model+16, &eax, 4);
memcpy(model+20, &ebx, 4);
memcpy(model+24, &ecx, 4);
memcpy(model+28, &edx, 4);
__get_cpuid(0x80000004, &eax, &ebx, &ecx, &edx);
memcpy(model+32, &eax, 4);
memcpy(model+36, &ebx, 4);
memcpy(model+40, &ecx, 4);
memcpy(model+44, &edx, 4);
model[48] = 0;
return g_strdup(g_strchomp(model));
}
guint get_core_count() {
guint eax = 0, ebx = 0, ecx = 0, edx = 0;
guint 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 SensorSource sensor_sources[] = {
{
"zenpower",
zenpower_init, zenpower_get_sensors, zenpower_update, zenpower_clear_minmax,
FALSE, NULL
},
{
"msr",
msr_init, msr_get_sensors, msr_update, msr_clear_minmax,
FALSE, NULL
},
{
"os",
os_init, os_get_sensors, os_update, os_clear_minmax,
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->hint);
g_free(s);
}
}
gboolean display_coreid = 0;
static GOptionEntry options[] =
{
{ "coreid", 'c', 0, G_OPTION_ARG_NONE, &display_coreid, "Display core_id instead of core index", NULL },
{ NULL }
};
int main (int argc, char *argv[])
{
GError *error = NULL;
GOptionContext *context;
context = g_option_context_new ("- Zenmonitor display options");
g_option_context_add_main_entries(context, options, NULL);
g_option_context_add_group(context, gtk_get_option_group (TRUE));
if (!g_option_context_parse(context, &argc, &argv, &error)) {
g_print ("option parsing failed: %s\n", error->message);
exit (1);
}
start_gui(sensor_sources);
}