├── 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 |
--------------------------------------------------------------------------------