├── LICENSE
├── README.md
├── data
    ├── org.pkexec.zenmonitor.policy.in
    ├── zenmonitor-root.desktop.in
    └── zenmonitor.desktop.in
├── makefile
├── screenshot.png
└── 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
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2018-2020 Ondrej Čerman
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Zen monitor
 2 | Zen monitor is monitoring software for AMD Zen-based CPUs.
 3 | 
 4 | It can monitor these values:
 5 |  - CPU Temperature
 6 |  - CPU Core (SVI2) Voltage, Current and Power
 7 |  - SOC (SVI2) Voltage, Current and Power
 8 |  - Package and Core Power (RAPL)
 9 |  - Core Frequency (from OS)
10 | 
11 | 
12 | 
13 | ## Dependencies
14 |  - [zenpower driver](https://github.com/ocerman/zenpower/) - For monitoring CPU temperature and SVI2 sensors
15 |  - MSR driver - For monitoring Package/Core Power (RAPL)
16 | 
17 | Follow [zenpower README.md](https://github.com/ocerman/zenpower/blob/master/README.md) to install and activate zenpower module.
18 | Enter `sudo modprobe msr` to enable MSR driver.
19 | 
20 | ## Building 
21 | Make sure that GTK3 dev package and common build tools are installed.
22 | ```
23 | make
24 | ```
25 | 
26 | ## Launching
27 | You can launch app by `sudo ./zenmonitor`, or you can install it to your system and then launch it from your OS menu.
28 | 
29 | 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).
30 | Alternatively, you can set capabilities to zenmonitor executable: `sudo setcap cap_sys_rawio,cap_dac_read_search+ep ./zenmonitor`
31 | 
32 | ## Command line arguments
33 | 
34 | ``--coreid`` - Display core_id instead of core index
35 | 
36 | ## Installing
37 | By default, Zenmonitor will be installed to /usr/local.
38 | ```
39 | sudo make install
40 | ```
41 | 
42 | To add menu item for launching zenpower as root (Polkit is required):
43 | ```
44 | sudo make install-polkit
45 | ```
46 | 
47 | ## Uninstalling
48 | ```
49 | sudo make uninstall
50 | ```
51 | 
52 | ## Setup on ubuntu
53 | First follow [installation instructions on zenpower](https://github.com/ocerman/zenpower/blob/master/README.md#installation-commands-for-ubuntu)
54 | Then:
55 | ```
56 | sudo modprobe msr
57 | sudo bash -c 'echo "msr" > /etc/modules-load.d/msr.conf'
58 | sudo apt install build-essential libgtk-3-dev git
59 | cd ~
60 | git clone https://github.com/ocerman/zenmonitor
61 | cd zenmonitor
62 | make
63 | sudo make install
64 | sudo make install-polkit
65 | ```
66 | ## Setup on Arch
67 | 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)
68 | 
--------------------------------------------------------------------------------
/data/org.pkexec.zenmonitor.policy.in:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 4 | 
 5 | 
 6 | 	
 7 | 		Run Zenmonitor as root
 8 | 		Authentication is required to run Zenmonitor as root.
 9 | 		
10 | 			auth_admin
11 | 			auth_admin
12 | 			auth_admin
13 | 		
14 | 		@APP_EXEC@
15 | 		true
16 | 	
17 | 
18 | 
19 | 
--------------------------------------------------------------------------------
/data/zenmonitor-root.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Zenmonitor (root)
3 | Comment=Monitoring software for AMD Zen-based CPUs
4 | Exec=pkexec @APP_EXEC@
5 | Type=Application
6 | Categories=GTK;System;
7 | Terminal=false
8 | Keywords=CPU;AMD;zen;system;core;speed;clock;temperature;voltage;
9 | 
--------------------------------------------------------------------------------
/data/zenmonitor.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Zenmonitor
3 | Comment=Monitoring software for AMD Zen-based CPUs
4 | Exec=@APP_EXEC@
5 | Type=Application
6 | Categories=GTK;System;
7 | Terminal=false
8 | Keywords=CPU;AMD;zen;system;core;speed;clock;temperature;voltage;
9 | 
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
 1 | ifeq ($(PREFIX),)
 2 | 	PREFIX := /usr/local
 3 | endif
 4 | 
 5 | build:
 6 | 	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
 7 | 
 8 | install:
 9 | 	mkdir -p $(DESTDIR)$(PREFIX)/bin
10 | 	install -m 755 zenmonitor $(DESTDIR)$(PREFIX)/bin
11 | 
12 | 	mkdir -p $(DESTDIR)$(PREFIX)/share/applications
13 | 	sed -e "s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|" \
14 | 			data/zenmonitor.desktop.in > \
15 | 			$(DESTDIR)$(PREFIX)/share/applications/zenmonitor.desktop
16 | 
17 | install-polkit:
18 | 	sed -e "s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|" \
19 | 			data/zenmonitor-root.desktop.in > \
20 | 			$(DESTDIR)$(PREFIX)/share/applications/zenmonitor-root.desktop
21 | 
22 | 	sed -e "s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|" \
23 | 			data/org.pkexec.zenmonitor.policy.in > \
24 | 			$(DESTDIR)/usr/share/polkit-1/actions/org.pkexec.zenmonitor.policy
25 | 
26 | uninstall:
27 | 	rm -f $(DESTDIR)$(PREFIX)/bin/zenmonitor
28 | 	rm -f $(DESTDIR)$(PREFIX)/share/applications/zenmonitor.desktop
29 | 	rm -f $(DESTDIR)$(PREFIX)/share/applications/zenmonitor-root.desktop
30 | 	rm -f $(DESTDIR)/usr/share/polkit-1/actions/org.pkexec.zenmonitor.policy
31 | 
32 | clean:
33 | 	rm -f zenmonitor
34 | 
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ocerman/zenmonitor/fa17e94b30611513c7b863422a1215fcea74f369/screenshot.png
--------------------------------------------------------------------------------
/src/gui.c:
--------------------------------------------------------------------------------
  1 | #include 
  2 | #include 
  3 | #include "gui.h"
  4 | #include "zenmonitor.h"
  5 | 
  6 | GtkWidget *window;
  7 | 
  8 | static GtkTreeModel *model = NULL;
  9 | static guint timeout = 0;
 10 | static SensorSource *sensor_sources;
 11 | static const guint defaultHeight = 350;
 12 | 
 13 | enum {
 14 |     COLUMN_NAME,
 15 |     COLUMN_HINT,
 16 |     COLUMN_VALUE,
 17 |     COLUMN_MIN,
 18 |     COLUMN_MAX,
 19 |     NUM_COLUMNS
 20 | };
 21 | 
 22 | static void init_sensors() {
 23 |     GtkTreeIter iter;
 24 |     GSList *sensor;
 25 |     GtkListStore *store;
 26 |     SensorSource *source;
 27 |     const SensorInit *data;
 28 |     guint i = 0;
 29 | 
 30 |     store = GTK_LIST_STORE(model);
 31 |     for (source = sensor_sources; source->drv; source++) {
 32 |         if (source->func_init()){
 33 |             source->sensors = source->func_get_sensors();
 34 |             if (source->sensors != NULL) {
 35 |                 source->enabled = TRUE;
 36 | 
 37 |                 sensor = source->sensors;
 38 |                 while (sensor) {
 39 |                     data = (SensorInit*)sensor->data;
 40 |                     gtk_list_store_append(store, &iter);
 41 |                     gtk_list_store_set(store, &iter,
 42 |                                        COLUMN_NAME,  data->label,
 43 |                                        COLUMN_HINT,  data->hint,
 44 |                                        COLUMN_VALUE, " --- ",
 45 |                                        COLUMN_MIN,   " --- ",
 46 |                                        COLUMN_MAX,   " --- ",
 47 |                                        -1);
 48 |                     sensor = sensor->next;
 49 |                     i++;
 50 |                 }
 51 |             }
 52 |         }
 53 |     }
 54 | }
 55 | 
 56 | static GtkTreeModel* create_model (void) {
 57 |     GtkListStore *store;
 58 |     store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
 59 |     return GTK_TREE_MODEL (store);
 60 | }
 61 | 
 62 | static void set_list_column_value(float num, const gchar *printf_format, GtkTreeIter *iter, gint column){
 63 |     gchar *value;
 64 |     if (num != ERROR_VALUE)
 65 |         value = g_strdup_printf(printf_format, num);
 66 |     else
 67 |         value = g_strdup("    ? ? ?");
 68 |     gtk_list_store_set(GTK_LIST_STORE (model), iter, column, value, -1);
 69 |     g_free(value);
 70 | }
 71 | 
 72 | static gboolean update_data (gpointer data) {
 73 |     GtkTreeIter iter;
 74 |     GSList *node;
 75 |     SensorSource *source;
 76 |     const SensorInit *sensorData;
 77 | 
 78 |     if (model == NULL)
 79 |         return G_SOURCE_REMOVE;
 80 | 
 81 |     if (!gtk_tree_model_get_iter_first (model, &iter))
 82 |         return G_SOURCE_REMOVE;
 83 | 
 84 |     for (source = sensor_sources; source->drv; source++) {
 85 |         if (!source->enabled)
 86 |             continue;
 87 |             
 88 |         source->func_update();
 89 |         if (source->sensors){
 90 |             node = source->sensors;
 91 | 
 92 |             while(node) {
 93 |                 sensorData = (SensorInit*)node->data;
 94 |                 set_list_column_value(*(sensorData->value), sensorData->printf_format, &iter, COLUMN_VALUE);
 95 |                 set_list_column_value(*(sensorData->min), sensorData->printf_format, &iter, COLUMN_MIN);
 96 |                 set_list_column_value(*(sensorData->max), sensorData->printf_format, &iter, COLUMN_MAX);
 97 | 
 98 |                 node = node->next;
 99 |                 if (!gtk_tree_model_iter_next(model, &iter))
100 |                     break;
101 |             }
102 |         }
103 |     }
104 |     return G_SOURCE_CONTINUE;
105 | }
106 | 
107 | static void add_columns (GtkTreeView *treeview) {
108 |     GtkCellRenderer *renderer;
109 |     GtkTreeViewColumn *column;
110 | 
111 |     // NAME
112 |     renderer = gtk_cell_renderer_text_new ();
113 |     column = gtk_tree_view_column_new_with_attributes ("Sensor", renderer,
114 |                                                      "text", COLUMN_NAME,
115 |                                                      NULL);
116 |     g_object_set(renderer, "family", "monotype", NULL);
117 |     gtk_tree_view_append_column (treeview, column);
118 | 
119 |     //VALUE
120 |     renderer = gtk_cell_renderer_text_new ();
121 |     column = gtk_tree_view_column_new_with_attributes ("Value", renderer,
122 |                                                      "text", COLUMN_VALUE,
123 |                                                      NULL);
124 |     g_object_set(renderer, "family", "monotype", NULL);
125 |     gtk_tree_view_append_column (treeview, column);
126 | 
127 |     //MIN
128 |     renderer = gtk_cell_renderer_text_new ();
129 |     column = gtk_tree_view_column_new_with_attributes ("Min", renderer,
130 |                                                      "text", COLUMN_MIN,
131 |                                                      NULL);
132 |     g_object_set(renderer, "family", "monotype", NULL);
133 |     gtk_tree_view_append_column (treeview, column);
134 | 
135 |     //MAX
136 |     renderer = gtk_cell_renderer_text_new ();
137 |     column = gtk_tree_view_column_new_with_attributes ("Max", renderer,
138 |                                                      "text", COLUMN_MAX,
139 |                                                      NULL);
140 |     g_object_set(renderer, "family", "monotype", NULL);
141 |     gtk_tree_view_append_column (treeview, column);
142 | }
143 | 
144 | static void about_btn_clicked(GtkButton *button, gpointer user_data) {
145 |     GtkWidget *dialog;
146 |     const gchar *website = "https://github.com/ocerman/zenmonitor";
147 |     const gchar *msg = "Zen Monitor %s\n"
148 |                        "Monitoring software for AMD Zen-based CPUs\n"
149 |                        "%s\n\n"
150 |                        "Created by: Ondrej Čerman";
151 | 
152 |     dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW (window),
153 |                                     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
154 |                                     GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
155 |                                     msg, VERSION, website, website);
156 | 
157 |     gtk_dialog_run(GTK_DIALOG(dialog));
158 |     gtk_widget_destroy(dialog);
159 | }
160 | 
161 | static void clear_btn_clicked(GtkButton *button, gpointer user_data) {
162 |     SensorSource *source;
163 | 
164 |     for (source = sensor_sources; source->drv; source++) {
165 |         if (!source->enabled)
166 |             continue;
167 | 
168 |         source->func_clear_minmax();
169 |     }
170 | }
171 | 
172 | static gboolean mid_search_eq_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter) {
173 |     gchar *iter_string = NULL, *lc_iter_string = NULL, *lc_key = NULL;
174 |     gboolean result;
175 | 
176 |     gtk_tree_model_get(model, iter, column, &iter_string, -1);
177 |     lc_iter_string = g_utf8_strdown(iter_string, -1);
178 |     lc_key = g_utf8_strdown(key, -1);
179 | 
180 |     result = (g_strrstr(lc_iter_string, lc_key) == NULL);
181 | 
182 |     g_free(iter_string);
183 |     g_free(lc_iter_string);
184 |     g_free(lc_key);
185 | 
186 |     return result;
187 | }
188 | 
189 | static void resize_to_treeview(GtkWindow* window, GtkTreeView* treeview) {
190 |     gint uiHeight, cellHeight, vSeparator, rows;
191 |     GdkRectangle r;
192 | 
193 |     GtkTreeViewColumn *col = gtk_tree_view_get_column(treeview, 0);
194 |     if (!col)
195 |         return;
196 | 
197 |     gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL, NULL, &cellHeight);
198 |     gtk_widget_style_get(GTK_WIDGET(treeview), "vertical-separator", &vSeparator, NULL);
199 |     rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(treeview), NULL);
200 | 
201 |     gtk_tree_view_get_visible_rect(treeview, &r);
202 |     uiHeight = defaultHeight - r.height;
203 | 
204 |     gtk_window_resize(window, 500, uiHeight + (vSeparator + cellHeight) * rows);
205 | }
206 | 
207 | int start_gui (SensorSource *ss) {
208 |     GtkWidget *about_btn;
209 |     GtkWidget *clear_btn;
210 |     GtkWidget *box;
211 |     GtkWidget *header;
212 |     GtkWidget *treeview;
213 |     GtkWidget *sw;
214 |     GtkWidget *vbox;
215 |     GtkWidget *dialog;
216 | 
217 |     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
218 |     gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
219 |     gtk_window_set_default_size(GTK_WINDOW(window), 500, defaultHeight);
220 | 
221 |     header = gtk_header_bar_new();
222 |     gtk_header_bar_set_show_close_button(GTK_HEADER_BAR (header), TRUE);
223 |     gtk_header_bar_set_title(GTK_HEADER_BAR (header), "Zen monitor");
224 |     gtk_header_bar_set_has_subtitle(GTK_HEADER_BAR (header), TRUE);
225 |     gtk_header_bar_set_subtitle(GTK_HEADER_BAR (header), cpu_model());
226 |     gtk_window_set_titlebar (GTK_WINDOW (window), header);
227 | 
228 |     box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
229 |     gtk_style_context_add_class (gtk_widget_get_style_context (box), "linked");
230 | 
231 |     about_btn = gtk_button_new();
232 |     gtk_container_add(GTK_CONTAINER(about_btn), gtk_image_new_from_icon_name("dialog-information", GTK_ICON_SIZE_BUTTON));
233 |     gtk_container_add(GTK_CONTAINER(box), about_btn);
234 |     gtk_widget_set_tooltip_text(about_btn, "About Zen monitor");
235 | 
236 |     clear_btn = gtk_button_new();
237 |     gtk_container_add(GTK_CONTAINER(clear_btn), gtk_image_new_from_icon_name("edit-clear-all", GTK_ICON_SIZE_BUTTON));
238 |     gtk_container_add(GTK_CONTAINER(box), clear_btn);
239 |     gtk_widget_set_tooltip_text(clear_btn, "Clear Min/Max");
240 | 
241 |     gtk_header_bar_pack_start(GTK_HEADER_BAR(header), box);
242 |     g_signal_connect(about_btn, "clicked", G_CALLBACK(about_btn_clicked), NULL);
243 |     g_signal_connect(clear_btn, "clicked", G_CALLBACK(clear_btn_clicked), NULL);
244 |     g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
245 | 
246 |     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
247 |     gtk_container_add(GTK_CONTAINER (window), vbox);
248 | 
249 |     sw = gtk_scrolled_window_new (NULL, NULL);
250 |     gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
251 |     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
252 |     gtk_box_pack_start(GTK_BOX (vbox), sw, TRUE, TRUE, 0);
253 | 
254 |     model = create_model();
255 |     treeview = gtk_tree_view_new_with_model(model);
256 |     gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), COLUMN_HINT);
257 | 
258 |     gtk_container_add (GTK_CONTAINER(sw), treeview);
259 |     add_columns(GTK_TREE_VIEW(treeview));
260 |     gtk_widget_show_all(window);
261 | 
262 |     gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), COLUMN_NAME);
263 |     gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(treeview),
264 |         (GtkTreeViewSearchEqualFunc)mid_search_eq_func, model, NULL);
265 | 
266 |     g_object_unref(model);
267 | 
268 |     if (check_zen()){
269 |         sensor_sources = ss;
270 |         init_sensors();
271 | 
272 |         resize_to_treeview(GTK_WINDOW(window), GTK_TREE_VIEW(treeview));
273 |         timeout = g_timeout_add(300, update_data, NULL);
274 |     }
275 |     else{
276 |         dialog = gtk_message_dialog_new(GTK_WINDOW (window),
277 |                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
278 |                                         GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
279 |                                         "Zen CPU not detected!");
280 |         gtk_dialog_run(GTK_DIALOG(dialog));
281 |         gtk_widget_destroy(dialog);
282 |     }
283 | 
284 |     gtk_main();
285 |     return 0;
286 | }
287 | 
--------------------------------------------------------------------------------
/src/include/gui.h:
--------------------------------------------------------------------------------
1 | int start_gui();
2 | 
--------------------------------------------------------------------------------
/src/include/msr.h:
--------------------------------------------------------------------------------
1 | gboolean msr_init();
2 | void msr_update();
3 | void msr_clear_minmax();
4 | GSList* msr_get_sensors();
5 | 
--------------------------------------------------------------------------------
/src/include/os.h:
--------------------------------------------------------------------------------
1 | gboolean os_init(void);
2 | void os_update(void);
3 | void os_clear_minmax(void);
4 | GSList* os_get_sensors(void);
5 | 
--------------------------------------------------------------------------------
/src/include/sysfs.h:
--------------------------------------------------------------------------------
1 | #define SYSFS_DIR_CPUS "/sys/devices/system/cpu"
2 | 
3 | struct cpudev {
4 | 	gshort coreid;
5 | 	gshort cpuid;
6 | };
7 | 
8 | struct cpudev * get_cpu_dev_ids(void);
9 | 
--------------------------------------------------------------------------------
/src/include/zenmonitor.h:
--------------------------------------------------------------------------------
 1 | #define ERROR_VALUE -999.0
 2 | #define VERSION "1.4.2"
 3 | 
 4 | typedef struct
 5 | {
 6 |     gchar *label;
 7 |     gchar *hint;
 8 |     float *value;
 9 |     float *min;
10 |     float *max;
11 |     const gchar *printf_format;
12 | }
13 | SensorInit;
14 | 
15 | typedef struct {
16 |     const gchar *drv;
17 |     gboolean  (*func_init)();
18 |     GSList* (*func_get_sensors)();
19 |     void (*func_update)();
20 |     void (*func_clear_minmax)();
21 |     gboolean enabled;
22 |     GSList *sensors;
23 | } SensorSource;
24 | 
25 | SensorInit* sensor_init_new(void);
26 | void sensor_init_free(SensorInit *s);
27 | gboolean check_zen();
28 | gchar *cpu_model();
29 | guint get_core_count();
30 | extern gboolean display_coreid;
31 | 
--------------------------------------------------------------------------------
/src/include/zenpower.h:
--------------------------------------------------------------------------------
1 | gboolean zenpower_init();
2 | GSList* zenpower_get_sensors();
3 | void zenpower_update();
4 | void zenpower_clear_minmax();
5 | 
--------------------------------------------------------------------------------
/src/ss/msr.c:
--------------------------------------------------------------------------------
  1 | #include 
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include 
  6 | #include 
  7 | #include 
  8 | #include 
  9 | #include "zenmonitor.h"
 10 | #include "msr.h"
 11 | #include "sysfs.h"
 12 | 
 13 | #define MSR_PWR_PRINTF_FORMAT " %8.3f W"
 14 | #define MSR_FID_PRINTF_FORMAT " %8.3f GHz"
 15 | #define MESUREMENT_TIME 0.1
 16 | 
 17 | // AMD PPR  = https://www.amd.com/system/files/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf
 18 | // AMD OSRR = https://developer.amd.com/wp-content/resources/56255_3_03.PDF
 19 | 
 20 | static guint cores = 0;
 21 | static gdouble energy_unit = 0;
 22 | static struct cpudev *cpu_dev_ids;
 23 | 
 24 | static gint *msr_files = NULL;
 25 | 
 26 | static gulong package_eng_b = 0;
 27 | static gulong package_eng_a = 0;
 28 | static gulong *core_eng_b = NULL;
 29 | static gulong *core_eng_a = NULL;
 30 | 
 31 | gfloat package_power;
 32 | gfloat package_power_min;
 33 | gfloat package_power_max;
 34 | gfloat *core_power;
 35 | gfloat *core_fid;
 36 | gfloat *core_power_min;
 37 | gfloat *core_power_max;
 38 | gfloat *core_fid_min;
 39 | gfloat *core_fid_max;
 40 | 
 41 | 
 42 | static gint open_msr(gshort devid) {
 43 |     gchar msr_path[20];
 44 |     sprintf(msr_path, "/dev/cpu/%d/msr", devid);
 45 |     return open(msr_path, O_RDONLY);
 46 | }
 47 | 
 48 | static gboolean read_msr(gint file, guint index, gulong *data) {
 49 |     if (file < 0)
 50 |         return FALSE;
 51 | 
 52 |     return pread(file, data, sizeof *data, index) == sizeof *data;
 53 | }
 54 | 
 55 | gdouble get_energy_unit() {
 56 |     gulong data;
 57 |     // AMD OSRR: page 139 - MSRC001_0299
 58 |     if (!read_msr(msr_files[0], 0xC0010299, &data))
 59 |         return 0.0;
 60 | 
 61 |     return pow(1.0/2.0, (double)((data >> 8) & 0x1F));
 62 | }
 63 | 
 64 | gulong get_package_energy() {
 65 |     gulong data;
 66 |     // AMD OSRR: page 139 - MSRC001_029B
 67 |     if (!read_msr(msr_files[0], 0xC001029B, &data))
 68 |         return 0;
 69 | 
 70 |     return data;
 71 | }
 72 | 
 73 | gulong get_core_energy(gint core) {
 74 |     gulong data;
 75 |     // AMD OSRR: page 139 - MSRC001_029A
 76 |     if (!read_msr(msr_files[core], 0xC001029A, &data))
 77 |         return 0;
 78 | 
 79 |     return data;
 80 | }
 81 | 
 82 | gdouble get_core_fid(gint core) {
 83 |     gdouble ratio;
 84 |     gulong data;
 85 | 
 86 |     // By reverse-engineering Ryzen Master, we know that
 87 |     //  this undocumented MSR is responsible for returning
 88 |     //  the FID and FDID for the core used for calculating the
 89 |     //  effective frequency.
 90 |     //
 91 |     // The FID is returned in bits [8:0]
 92 |     // The FDID is returned in bits [14:8]
 93 |     if (!read_msr(msr_files[core], 0xC0010293, &data))
 94 |         return 0;
 95 | 
 96 |     ratio = (gdouble)(data & 0xff) / (gdouble)((data >> 8) & 0x3F);
 97 | 
 98 |     // The effective ratio is based on increments of 200 MHz.
 99 |     return ratio * 200.0 / 1000.0;
100 | }
101 | 
102 | gboolean msr_init() {
103 |     guint i;
104 | 
105 |     if (!check_zen())
106 |         return FALSE;
107 | 
108 |     cores = get_core_count();
109 |     if (cores == 0)
110 |         return FALSE;
111 | 
112 |     cpu_dev_ids = get_cpu_dev_ids();
113 |     msr_files = malloc(cores * sizeof (gint));
114 |     for (i = 0; i < cores; i++) {
115 |         msr_files[i] = open_msr(cpu_dev_ids[i].cpuid);
116 |     }
117 | 
118 |     energy_unit = get_energy_unit();
119 |     if (energy_unit == 0)
120 |         return FALSE;
121 | 
122 |     core_eng_b = malloc(cores * sizeof (gulong));
123 |     core_eng_a = malloc(cores * sizeof (gulong));
124 |     core_power = malloc(cores * sizeof (gfloat));
125 |     core_fid = malloc(cores * sizeof (gfloat));
126 |     core_power_min = malloc(cores * sizeof (gfloat));
127 |     core_power_max = malloc(cores * sizeof (gfloat));
128 |     core_fid_min = malloc(cores * sizeof (gfloat));
129 |     core_fid_max = malloc(cores * sizeof (gfloat));
130 | 
131 |     msr_update();
132 |     memcpy(core_power_min, core_power, cores * sizeof (gfloat));
133 |     memcpy(core_power_max, core_power, cores * sizeof (gfloat));
134 |     memcpy(core_fid_min, core_fid, cores * sizeof (gfloat));
135 |     memcpy(core_fid_max, core_fid, cores * sizeof (gfloat));
136 |     package_power_min = package_power;
137 |     package_power_max = package_power;
138 | 
139 |     return TRUE;
140 | }
141 | 
142 | void msr_update() {
143 |     guint i;
144 | 
145 |     package_eng_b = get_package_energy();
146 |     for (i = 0; i < cores; i++) {
147 |         core_eng_b[i] = get_core_energy(i);
148 |     }
149 | 
150 |     usleep(MESUREMENT_TIME*1000000);
151 | 
152 |     package_eng_a = get_package_energy();
153 |     for (i = 0; i < cores; i++) {
154 |         core_eng_a[i] = get_core_energy(i);
155 |     }
156 | 
157 |     if (package_eng_a >= package_eng_b) {
158 |         package_power = (package_eng_a - package_eng_b) * energy_unit / MESUREMENT_TIME;
159 | 
160 |         if (package_power < package_power_min)
161 |             package_power_min = package_power;
162 |         if (package_power > package_power_max)
163 |             package_power_max = package_power;
164 |     }
165 | 
166 |     for (i = 0; i < cores; i++) {
167 |         if (core_eng_a[i] >= core_eng_b[i]) {
168 |             core_power[i] = (core_eng_a[i] - core_eng_b[i]) * energy_unit / MESUREMENT_TIME;
169 | 
170 |             if (core_power[i] < core_power_min[i])
171 |                 core_power_min[i] = core_power[i];
172 |             if (core_power[i] > core_power_max[i])
173 |                 core_power_max[i] = core_power[i];
174 |         }
175 | 
176 |         core_fid[i] = get_core_fid(i);
177 | 
178 |         if (core_fid[i] < core_fid_min[i])
179 |             core_fid_min[i] = core_fid[i];
180 |         if (core_fid[i] > core_fid_max[i])
181 |             core_fid_max[i] = core_fid[i];
182 |     }
183 | }
184 | 
185 | void msr_clear_minmax() {
186 |     guint i;
187 | 
188 |     package_power_min = package_power;
189 |     package_power_max = package_power;
190 |     for (i = 0; i < cores; i++) {
191 |         core_power_min[i] = core_power[i];
192 |         core_power_max[i] = core_power[i];
193 |         core_fid_min[i] = core_fid[i];
194 |         core_fid_max[i] = core_fid[i];
195 |     }
196 | }
197 | 
198 | GSList* msr_get_sensors() {
199 |     GSList *list = NULL;
200 |     SensorInit *data;
201 |     guint i;
202 | 
203 |     data = sensor_init_new();
204 |     data->label = g_strdup("Package Power");
205 |     data->hint = g_strdup("Package Power reported by RAPL\nSource: cpu0 MSR");
206 |     data->value = &package_power;
207 |     data->min = &package_power_min;
208 |     data->max = &package_power_max;
209 |     data->printf_format = MSR_PWR_PRINTF_FORMAT;
210 |     list = g_slist_append(list, data);
211 | 
212 |     for (i = 0; i < cores; i++) {
213 |         data = sensor_init_new();
214 |         data->label = g_strdup_printf("Core %d Effective Frequency", display_coreid ? cpu_dev_ids[i].coreid: i);
215 |         data->hint = g_strdup_printf("Source: cpu%d MSR", cpu_dev_ids[i].cpuid);
216 |         data->value = &(core_fid[i]);
217 |         data->min = &(core_fid_min[i]);
218 |         data->max = &(core_fid_max[i]);
219 |         data->printf_format = MSR_FID_PRINTF_FORMAT;
220 |         list = g_slist_append(list, data);
221 |     }
222 | 
223 |     for (i = 0; i < cores; i++) {
224 |         data = sensor_init_new();
225 |         data->label = g_strdup_printf("Core %d Power", display_coreid ? cpu_dev_ids[i].coreid: i);
226 |         data->hint = g_strdup_printf("Core Power reported by RAPL\nSource: cpu%d MSR", cpu_dev_ids[i].cpuid);
227 |         data->value = &(core_power[i]);
228 |         data->min = &(core_power_min[i]);
229 |         data->max = &(core_power_max[i]);
230 |         data->printf_format = MSR_PWR_PRINTF_FORMAT;
231 |         list = g_slist_append(list, data);
232 |     }
233 | 
234 |     return list;
235 | }
236 | 
--------------------------------------------------------------------------------
/src/ss/os.c:
--------------------------------------------------------------------------------
 1 | #include 
 2 | #include 
 3 | #include 
 4 | #include "zenmonitor.h"
 5 | #include "sysfs.h"
 6 | #include "os.h"
 7 | 
 8 | #define OS_FREQ_PRINTF_FORMAT " %8.3f GHz"
 9 | 
10 | static gchar **frq_files = NULL;
11 | static guint cores;
12 | static struct cpudev *cpu_dev_ids;
13 | 
14 | gfloat *core_freq;
15 | gfloat *core_freq_min;
16 | gfloat *core_freq_max;
17 | 
18 | static gdouble get_frequency(guint corei) {
19 |     gchar *data;
20 |     gdouble freq;
21 | 
22 |     if (!g_file_get_contents(frq_files[corei], &data, NULL, NULL))
23 |         return 0.0;
24 | 
25 |     freq = atoi(data) / 1000000.0;
26 |     g_free(data);
27 | 
28 |     return freq;
29 | }
30 | 
31 | gboolean os_init(void) {
32 |     guint i;
33 | 
34 |     if (!check_zen())
35 |         return FALSE;
36 | 
37 |     cores = get_core_count();
38 |     if (cores == 0)
39 |         return FALSE;
40 | 
41 |     cpu_dev_ids = get_cpu_dev_ids();
42 |     frq_files = malloc(cores * sizeof (gchar*));
43 |     for (i = 0; i < cores; i++) {
44 |         frq_files[i] = g_strdup_printf(
45 |                         "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq",
46 |                         cpu_dev_ids[i].cpuid);
47 |     }
48 | 
49 |     core_freq = malloc(cores * sizeof (gfloat));
50 |     core_freq_min = malloc(cores * sizeof (gfloat));
51 |     core_freq_max = malloc(cores * sizeof (gfloat));
52 | 
53 |     os_update();
54 |     memcpy(core_freq_min, core_freq, cores * sizeof (gfloat));
55 |     memcpy(core_freq_max, core_freq, cores * sizeof (gfloat));
56 | 
57 |     return TRUE;
58 | }
59 | 
60 | void os_update(void) {
61 |     guint i;
62 | 
63 |     for (i = 0; i < cores; i++) {
64 |         core_freq[i] = get_frequency(i);
65 |         if (core_freq[i] < core_freq_min[i])
66 |             core_freq_min[i] = core_freq[i];
67 |         if (core_freq[i] > core_freq_max[i])
68 |             core_freq_max[i] = core_freq[i];
69 |     }
70 | }
71 | 
72 | void os_clear_minmax(void) {
73 |     guint i;
74 | 
75 |     for (i = 0; i < cores; i++) {
76 |         core_freq_min[i] = core_freq[i];
77 |         core_freq_max[i] = core_freq[i];
78 |     }
79 | }
80 | 
81 | GSList* os_get_sensors(void) {
82 |     GSList *list = NULL;
83 |     SensorInit *data;
84 |     guint i;
85 | 
86 |     for (i = 0; i < cores; i++) {
87 |         data = sensor_init_new();
88 |         data->label = g_strdup_printf("Core %d Frequency", display_coreid ? cpu_dev_ids[i].coreid: i);
89 |         data->hint = g_strdup_printf("Current frequency of the CPU as determined by the governor and cpufreq core.\n Source: %s", frq_files[i]);
90 |         data->value = &(core_freq[i]);
91 |         data->min = &(core_freq_min[i]);
92 |         data->max = &(core_freq_max[i]);
93 |         data->printf_format = OS_FREQ_PRINTF_FORMAT;
94 |         list = g_slist_append(list, data);
95 |     }
96 | 
97 |     return list;
98 | }
99 | 
--------------------------------------------------------------------------------
/src/ss/zenpower.c:
--------------------------------------------------------------------------------
  1 | #include 
  2 | #include 
  3 | #include 
  4 | #include "zenmonitor.h"
  5 | #include "zenpower.h"
  6 | 
  7 | GSList *zp_sensors = NULL;
  8 | static int nodes = 0;
  9 | 
 10 | typedef struct
 11 | {
 12 |     const gchar *label;
 13 |     const gchar *hint;
 14 |     const gchar *file;
 15 |     const gchar *printf_format;
 16 |     const double adjust_ratio;
 17 | } HwmonSensorType;
 18 | 
 19 | typedef struct
 20 | {
 21 |     float current_value;
 22 |     float min;
 23 |     float max;
 24 |     HwmonSensorType *type;
 25 |     gchar *hwmon_dir;
 26 |     int node;
 27 | } HwmonSensor;
 28 | 
 29 | static HwmonSensorType hwmon_stype[] = {
 30 |   {"CPU Temperature (tCtl)",    "Reported CPU Temperature",                  "temp1_input",  " %6.2f°C", 1000.0},
 31 |   {"CPU Temperature (tDie)",    "Reported CPU Temperature - offset",         "temp2_input",  " %6.2f°C", 1000.0},
 32 |   {"CCD1 Temperature",          "Core Complex Die 1 Temperature",            "temp3_input",  " %6.2f°C", 1000.0},
 33 |   {"CCD2 Temperature",          "Core Complex Die 2 Temperature",            "temp4_input",  " %6.2f°C", 1000.0},
 34 |   {"CCD3 Temperature",          "Core Complex Die 3 Temperature",            "temp5_input",  " %6.2f°C", 1000.0},
 35 |   {"CCD4 Temperature",          "Core Complex Die 4 Temperature",            "temp6_input",  " %6.2f°C", 1000.0},
 36 |   {"CCD5 Temperature",          "Core Complex Die 5 Temperature",            "temp7_input",  " %6.2f°C", 1000.0},
 37 |   {"CCD6 Temperature",          "Core Complex Die 6 Temperature",            "temp8_input",  " %6.2f°C", 1000.0},
 38 |   {"CCD7 Temperature",          "Core Complex Die 7 Temperature",            "temp9_input",  " %6.2f°C", 1000.0},
 39 |   {"CCD8 Temperature",          "Core Complex Die 8 Temperature",            "temp10_input", " %6.2f°C", 1000.0},
 40 |   {"CPU Core Voltage (SVI2)",   "Core Voltage reported by SVI2 telemetry",   "in1_input",    " %8.3f V", 1000.0},
 41 |   {"SOC Voltage (SVI2)",        "SOC Voltage reported by SVI2 telemetry",    "in2_input",    " %8.3f V", 1000.0},
 42 |   {"CPU Core Current (SVI2)",   "Core Current reported by SVI2 telemetry\n"
 43 |                                 "Note: May not be accurate on some systems", "curr1_input",  " %8.3f A", 1000.0},
 44 |   {"SOC Current (SVI2)",        "SOC Current reported by SVI2 telemetry\n"
 45 |                                 "Note: May not be accurate on some systems", "curr2_input",  " %8.3f A", 1000.0},
 46 |   {"CPU Core Power (SVI2)",     "Core Voltage * Current\n"
 47 |                                 "Note: May not be accurate on some systems", "power1_input", " %8.3f W", 1000000.0},
 48 |   {"SOC Power (SVI2)",          "Core Voltage * Current\n"
 49 |                                 "Note: May not be accurate on some systems", "power2_input", " %8.3f W", 1000000.0},
 50 |   {0, NULL}
 51 | };
 52 | 
 53 | static gboolean hwmon_file_exists(const gchar *dir, const gchar *file) {
 54 |     gchar *full_path;
 55 |     gboolean result;
 56 | 
 57 |     full_path = g_strdup_printf("/sys/class/hwmon/%s/%s", dir, file);
 58 |     result = g_file_test(full_path, G_FILE_TEST_EXISTS);
 59 | 
 60 |     g_free(full_path);
 61 |     return result;
 62 | }
 63 | 
 64 | static gboolean read_raw_hwmon_value(const gchar *dir, const gchar *file, gchar **result) {
 65 |     gchar *full_path;
 66 |     gboolean file_result;
 67 | 
 68 |     full_path = g_strdup_printf("/sys/class/hwmon/%s/%s", dir, file);
 69 |     file_result = g_file_get_contents(full_path, result, NULL, NULL);
 70 | 
 71 |     g_free(full_path);
 72 |     return file_result;
 73 | }
 74 | 
 75 | static HwmonSensor *hwmon_sensor_new(HwmonSensorType *type, const gchar *dir, gint node) {
 76 |     HwmonSensor *s;
 77 |     s = g_new0(HwmonSensor, 1);
 78 |     s->min = 999.0;
 79 |     s->type = type;
 80 |     s->hwmon_dir = g_strdup(dir);
 81 |     s->node = node;
 82 |     return s;
 83 | }
 84 | 
 85 | gboolean zenpower_init() {
 86 |     GDir *hwmon;
 87 |     const gchar *entry;
 88 |     gchar *name = NULL;
 89 |     HwmonSensorType *type;
 90 | 
 91 |     hwmon = g_dir_open("/sys/class/hwmon", 0, NULL);
 92 |     if (!hwmon)
 93 |         return FALSE;
 94 | 
 95 |     while ((entry = g_dir_read_name(hwmon))) {
 96 |         read_raw_hwmon_value(entry, "name", &name);
 97 | 
 98 |         if (strcmp(g_strchomp(name), "zenpower") == 0) {
 99 | 
100 |             for (type = hwmon_stype; type->label; type++) {
101 |                 if (hwmon_file_exists(entry, type->file)) {
102 |                     zp_sensors = g_slist_append(zp_sensors, hwmon_sensor_new(type, entry, nodes));
103 |                 }
104 |             }
105 |             nodes++;
106 | 
107 |         }
108 |         g_free(name);
109 |     }
110 | 
111 |     if (zp_sensors == NULL)
112 |         return FALSE;
113 | 
114 |     return TRUE;
115 | }
116 | 
117 | void zenpower_update() {
118 |     gchar *tmp = NULL;
119 |     GSList *node;
120 |     HwmonSensor *sensor;
121 | 
122 |     node = zp_sensors;
123 |     while(node) {
124 |         sensor = (HwmonSensor *)node->data;
125 | 
126 |         if (read_raw_hwmon_value(sensor->hwmon_dir, sensor->type->file, &tmp)){
127 |             sensor->current_value = atof(tmp) / sensor->type->adjust_ratio;
128 | 
129 |             if (sensor->current_value < sensor->min)
130 |                 sensor->min = sensor->current_value;
131 | 
132 |             if (sensor->current_value > sensor->max)
133 |                 sensor->max = sensor->current_value;
134 | 
135 |             g_free(tmp);
136 |         }
137 |         else{
138 |             sensor->current_value = ERROR_VALUE;
139 |         }
140 |         node = node->next;
141 |     }
142 | }
143 | 
144 | void zenpower_clear_minmax() {
145 |     HwmonSensor *sensor;
146 |     GSList *node;
147 |     node = zp_sensors;
148 |     while(node) {
149 |         sensor = (HwmonSensor *)node->data;
150 |         sensor->min = sensor->current_value;
151 |         sensor->max = sensor->current_value;
152 |         node = node->next;
153 |     }
154 | }
155 | 
156 | GSList* zenpower_get_sensors() {
157 |     GSList *list = NULL;
158 |     HwmonSensor *sensor;
159 |     GSList *node;
160 |     SensorInit *data;
161 | 
162 |     node = zp_sensors;
163 |     while(node) {
164 |         sensor = (HwmonSensor *)node->data;
165 | 
166 |         data = sensor_init_new();
167 |         if (nodes > 1){
168 |             data->label = g_strdup_printf("Node %d - %s", sensor->node, sensor->type->label);
169 |         }
170 |         else{
171 |             data->label = g_strdup(sensor->type->label);
172 |         }
173 |         data->hint = g_strdup_printf("%s\nSource: zenpower %s/%s", sensor->type->hint, sensor->hwmon_dir, sensor->type->file);
174 |         data->value = &sensor->current_value;
175 |         data->min = &sensor->min;
176 |         data->max = &sensor->max;
177 |         data->printf_format = sensor->type->printf_format;
178 |         list = g_slist_append(list, data);
179 | 
180 |         node = node->next;
181 |     }
182 | 
183 |     return list;
184 | }
185 | 
--------------------------------------------------------------------------------
/src/sysfs.c:
--------------------------------------------------------------------------------
 1 | #include 
 2 | #include 
 3 | #include 
 4 | #include 
 5 | #include "sysfs.h"
 6 | #include "zenmonitor.h"
 7 | 
 8 | #define CPUD_MAX 512
 9 | struct bitset {
10 |     guint bits[CPUD_MAX/32];
11 | };
12 | 
13 | static int bitset_set(struct bitset *set, int id) {
14 |     if (id < CPUD_MAX) {
15 |         int v = (set->bits[id/32] >> (id & 31)) & 1;
16 | 
17 |         set->bits[id/32] |= 1 << (id & 31);
18 |         return v;
19 |     }
20 |     return 1;
21 | }
22 | 
23 | static int cmp_cpudev(const void *ap, const void *bp) {
24 |     return ((struct cpudev *)ap)->cpuid - ((struct cpudev *)bp)->cpuid;
25 | }
26 | 
27 | struct cpudev* get_cpu_dev_ids(void) {
28 |     struct cpudev *cpu_dev_ids;
29 |     gshort coreid, cpuid, siblingid;
30 |     GDir *dir;
31 |     const gchar *entry;
32 |     gchar *filename, *buffer;
33 |     gchar **cpusiblings;
34 |     gchar **ptr;
35 |     guint cores;
36 |     gboolean found;
37 |     struct bitset seen = { 0 };
38 |     int i;
39 | 
40 |     cores = get_core_count();
41 |     cpu_dev_ids = malloc(cores * sizeof (*cpu_dev_ids));
42 |     for (i=0;i
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include "zenmonitor.h"
  6 | #include "zenpower.h"
  7 | #include "msr.h"
  8 | #include "os.h"
  9 | #include "gui.h"
 10 | 
 11 | #define AMD_STRING "AuthenticAMD"
 12 | #define ZEN_FAMILY 0x17
 13 | 
 14 | // AMD PPR = https://www.amd.com/system/files/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf
 15 | 
 16 | gboolean check_zen() {
 17 |     guint32 eax = 0, ebx = 0, ecx = 0, edx = 0, ext_family;
 18 |     char vendor[13];
 19 | 
 20 |     __get_cpuid(0, &eax, &ebx, &ecx, &edx);
 21 | 
 22 |     memcpy(vendor, &ebx, 4);
 23 |     memcpy(vendor+4, &edx, 4);
 24 |     memcpy(vendor+8, &ecx, 4);
 25 |     vendor[12] = 0;
 26 | 
 27 |     if (strcmp(vendor, AMD_STRING) != 0){
 28 |         return FALSE;
 29 |     }
 30 | 
 31 |     __get_cpuid(1, &eax, &ebx, &ecx, &edx);
 32 | 
 33 |     ext_family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF);
 34 |     if (ext_family != ZEN_FAMILY){
 35 |         return FALSE;
 36 |     }
 37 | 
 38 |     return TRUE;
 39 | }
 40 | 
 41 | gchar *cpu_model() {
 42 |     guint32 eax = 0, ebx = 0, ecx = 0, edx = 0;
 43 |     char model[48];
 44 | 
 45 |     // AMD PPR: page 65-68 - CPUID_Fn80000002_EAX-CPUID_Fn80000004_EDX
 46 |     __get_cpuid(0x80000002, &eax, &ebx, &ecx, &edx);
 47 |     memcpy(model, &eax, 4);
 48 |     memcpy(model+4, &ebx, 4);
 49 |     memcpy(model+8, &ecx, 4);
 50 |     memcpy(model+12, &edx, 4);
 51 | 
 52 |     __get_cpuid(0x80000003, &eax, &ebx, &ecx, &edx);
 53 |     memcpy(model+16, &eax, 4);
 54 |     memcpy(model+20, &ebx, 4);
 55 |     memcpy(model+24, &ecx, 4);
 56 |     memcpy(model+28, &edx, 4);
 57 | 
 58 |     __get_cpuid(0x80000004, &eax, &ebx, &ecx, &edx);
 59 |     memcpy(model+32, &eax, 4);
 60 |     memcpy(model+36, &ebx, 4);
 61 |     memcpy(model+40, &ecx, 4);
 62 |     memcpy(model+44, &edx, 4);
 63 | 
 64 |     model[48] = 0;
 65 |     return g_strdup(g_strchomp(model));
 66 | }
 67 | 
 68 | guint get_core_count() {
 69 |     guint eax = 0, ebx = 0, ecx = 0, edx = 0;
 70 |     guint logical_cpus, threads_per_code;
 71 | 
 72 |     // AMD PPR: page 57 - CPUID_Fn00000001_EBX
 73 |     __get_cpuid(1, &eax, &ebx, &ecx, &edx);
 74 |     logical_cpus = (ebx >> 16) & 0xFF;
 75 | 
 76 |     // AMD PPR: page 82 - CPUID_Fn8000001E_EBX
 77 |     __get_cpuid(0x8000001E, &eax, &ebx, &ecx, &edx);
 78 |     threads_per_code = ((ebx >> 8) & 0xF) + 1;
 79 | 
 80 |     if (threads_per_code == 0)
 81 |         return logical_cpus;
 82 | 
 83 |     return logical_cpus / threads_per_code;
 84 | }
 85 | 
 86 | static SensorSource sensor_sources[] = {
 87 |     {
 88 |         "zenpower",
 89 |         zenpower_init, zenpower_get_sensors, zenpower_update, zenpower_clear_minmax,
 90 |         FALSE, NULL
 91 |     },
 92 |     {
 93 |         "msr",
 94 |         msr_init, msr_get_sensors, msr_update, msr_clear_minmax,
 95 |         FALSE, NULL
 96 |     },
 97 |     {
 98 |         "os",
 99 |         os_init, os_get_sensors, os_update, os_clear_minmax,
100 |         FALSE, NULL
101 |     },
102 |     {
103 |         NULL
104 |     }
105 | };
106 | 
107 | SensorInit *sensor_init_new() {
108 |     return g_new0(SensorInit, 1);
109 | }
110 | 
111 | void sensor_init_free(SensorInit *s) {
112 |     if (s) {
113 |         g_free(s->label);
114 |         g_free(s->hint);
115 |         g_free(s);
116 |     }
117 | }
118 | 
119 | gboolean display_coreid = 0;
120 | 
121 | static GOptionEntry options[] =
122 | {
123 |     { "coreid", 'c', 0, G_OPTION_ARG_NONE, &display_coreid, "Display core_id instead of core index", NULL },
124 |     { NULL }
125 | };
126 | 
127 | int main (int argc, char *argv[])
128 | {
129 |     GError *error = NULL;
130 |     GOptionContext *context;
131 | 
132 |     context = g_option_context_new ("- Zenmonitor display options");
133 |     g_option_context_add_main_entries(context, options, NULL);
134 |     g_option_context_add_group(context, gtk_get_option_group (TRUE));
135 |     if (!g_option_context_parse(context, &argc, &argv, &error)) {
136 |         g_print ("option parsing failed: %s\n", error->message);
137 |         exit (1);
138 |     }
139 | 
140 |     start_gui(sensor_sources);
141 | }
142 | 
--------------------------------------------------------------------------------