├── 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 | ![screenshot](screenshot.png) 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 | --------------------------------------------------------------------------------