1 /* cpu.cc
2 * Part of xfce4-cpugraph-plugin
3 *
4 * Copyright (c) Alexander Nordfelth <alex.nordfelth@telia.com>
5 * Copyright (c) gatopeich <gatoguan-os@yahoo.com>
6 * Copyright (c) 2007-2008 Angelo Arrifano <miknix@gmail.com>
7 * Copyright (c) 2007-2008 Lidiriel <lidiriel@coriolys.org>
8 * Copyright (c) 2010 Florian Rivoal <frivoal@gmail.com>
9 * Copyright (c) 2021 Jan Ziak <0xe2.0x9a.0x9b@xfce.org>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Library General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 */
25
26 /* The fixes file has to be included before any other #include directives */
27 #include "xfce4++/util/fixes.h"
28
29 #include "cpu.h"
30 #include "settings.h"
31 #include "mode.h"
32 #include "plugin.h"
33 #include "properties.h"
34
35 #include <libxfce4ui/libxfce4ui.h>
36 #include <math.h>
37 #include "xfce4++/util.h"
38
39 using xfce4::PluginSize;
40 using xfce4::Propagation;
41 using xfce4::TooltipTime;
42
43 /* vim: !sort -k3 */
44 static void about_cb ();
45 static Propagation command_cb (GdkEventButton *event, const Ptr<CPUGraph> &base);
46 static void create_bars (const Ptr<CPUGraph> &base, GtkOrientation orientation);
47 static Ptr<CPUGraph> create_gui (XfcePanelPlugin *plugin);
48 static void delete_bars (const Ptr<CPUGraph> &base);
49 static Propagation draw_area_cb (cairo_t *cr, const Ptr<CPUGraph> &base);
50 static Propagation draw_bars_cb (cairo_t *cr, const Ptr<CPUGraph> &base);
51 static void mode_cb (XfcePanelPlugin *plugin, const Ptr<CPUGraph> &base);
52 static guint nb_bars (const Ptr<const CPUGraph> &base);
53 static void set_bars_size (const Ptr<CPUGraph> &base);
54 static void shutdown (const Ptr<CPUGraph> &base);
55 static PluginSize size_cb (XfcePanelPlugin *plugin, guint size, const Ptr<CPUGraph> &base);
56 static TooltipTime tooltip_cb (GtkTooltip *tooltip, const Ptr<CPUGraph> &base);
57 static void update_tooltip (const Ptr<CPUGraph> &base);
58
59 void
cpugraph_construct(XfcePanelPlugin * plugin)60 cpugraph_construct (XfcePanelPlugin *plugin)
61 {
62 xfce_textdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");
63
64 Ptr<CPUGraph> base = create_gui (plugin);
65
66 read_settings (plugin, base);
67
68 xfce_panel_plugin_menu_show_about (plugin);
69 xfce_panel_plugin_menu_show_configure (plugin);
70
71 xfce4::connect_about (plugin, [base](XfcePanelPlugin *p) { about_cb(); });
72 xfce4::connect_free_data (plugin, [base](XfcePanelPlugin *p) { shutdown(base); });
73 xfce4::connect_save (plugin, [base](XfcePanelPlugin *p) { write_settings(p, base); });
74 xfce4::connect_configure_plugin(plugin, [base](XfcePanelPlugin *p) { create_options(p, base); });
75 xfce4::connect_mode_changed (plugin, [base](XfcePanelPlugin *p, XfcePanelPluginMode mode) { mode_cb(p, base); });
76 xfce4::connect_size_changed (plugin, [base](XfcePanelPlugin *p, guint size) { return size_cb(p, size, base); });
77 }
78
79 static guint
init_cpu_data(std::vector<CpuData> & data)80 init_cpu_data (std::vector<CpuData> &data)
81 {
82 guint cpuNr = detect_cpu_number ();
83 if (cpuNr != 0)
84 data.resize(cpuNr+1);
85 return cpuNr;
86 }
87
88 static Ptr<CPUGraph>
create_gui(XfcePanelPlugin * plugin)89 create_gui (XfcePanelPlugin *plugin)
90 {
91 GtkWidget *frame, *ebox;
92 GtkOrientation orientation;
93 auto base = xfce4::make<CPUGraph>();
94
95 orientation = xfce_panel_plugin_get_orientation (plugin);
96 if ((base->nr_cores = init_cpu_data (base->cpu_data)) == 0)
97 fprintf (stderr,"Cannot init cpu data !\n");
98
99 /* Read CPU data twice in order to initialize
100 * cpu_data[].previous_used and cpu_data[].previous_total
101 * with the current HWMs. HWM = High Water Mark. */
102 read_cpu_data (base->cpu_data);
103 read_cpu_data (base->cpu_data);
104
105 base->topology = read_topology ();
106
107 base->plugin = plugin;
108
109 base->ebox = ebox = gtk_event_box_new ();
110 gtk_event_box_set_visible_window (GTK_EVENT_BOX (ebox), FALSE);
111 gtk_event_box_set_above_child (GTK_EVENT_BOX (ebox), TRUE);
112 gtk_container_add (GTK_CONTAINER (plugin), ebox);
113 xfce_panel_plugin_add_action_widget (plugin, ebox);
114 xfce4::connect_button_press (ebox, [base](GtkWidget*, GdkEventButton *event) -> Propagation {
115 return command_cb (event, base);
116 });
117
118 base->box = gtk_box_new (orientation, 0);
119 gtk_container_add (GTK_CONTAINER (ebox), base->box);
120 gtk_widget_set_has_tooltip (base->box, TRUE);
121 xfce4::connect_query_tooltip (base->box, [base](GtkWidget *widget, gint x, gint y, bool keyboard, GtkTooltip *tooltip) {
122 return tooltip_cb (tooltip, base);
123 });
124
125 base->frame_widget = frame = gtk_frame_new (NULL);
126 gtk_box_pack_end (GTK_BOX (base->box), frame, TRUE, TRUE, 2);
127
128 base->draw_area = gtk_drawing_area_new ();
129 gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (base->draw_area));
130 xfce4::connect_after_draw (base->draw_area, [base](cairo_t *cr) { return draw_area_cb (cr, base); });
131
132 base->has_bars = false;
133 base->has_barcolor = false;
134 base->bars.orientation = orientation;
135 base->highlight_smt = HIGHLIGHT_SMT_BY_DEFAULT;
136 base->per_core_spacing = PER_CORE_SPACING_DEFAULT;
137
138 mode_cb (plugin, base);
139 gtk_widget_show_all (ebox);
140
141 base->tooltip_text = gtk_label_new (NULL);
142 g_object_ref (base->tooltip_text);
143
144 return base;
145 }
146
147 static void
about_cb()148 about_cb ()
149 {
150 /* List of authors (in alphabetical order) */
151 const gchar *auth[] = {
152 "Agustin Ferrin Pozuelo <gatoguan-os@yahoo.com>",
153 "Alexander Nordfelth <alex.nordfelth@telia.com>",
154 "Angelo Miguel Arrifano <miknix@gmail.com>",
155 "Florian Rivoal <frivoal@gmail.com>",
156 "Jan Ziak <0xe2.0x9a.0x9b@xfce.org>",
157 "Ludovic Mercier <lidiriel@coriolys.org>",
158 "Peter Tribble <peter.tribble@gmail.com>",
159 NULL
160 };
161
162 gtk_show_about_dialog (NULL,
163 "logo-icon-name", "org.xfce.panel.cpugraph",
164 "license", xfce_get_license_text (XFCE_LICENSE_TEXT_GPL),
165 "version", PACKAGE_VERSION,
166 "program-name", PACKAGE_NAME,
167 "comments", _("Graphical representation of the CPU load"),
168 "website", "https://docs.xfce.org/panel-plugins/xfce4-cpugraph-plugin",
169 "copyright", _("Copyright (c) 2003-2021\n"),
170 "authors", auth, NULL);
171 }
172
173 static void
ebox_revalidate(const Ptr<CPUGraph> & base)174 ebox_revalidate (const Ptr<CPUGraph> &base)
175 {
176 gtk_event_box_set_above_child (GTK_EVENT_BOX (base->ebox), FALSE);
177 gtk_event_box_set_above_child (GTK_EVENT_BOX (base->ebox), TRUE);
178 }
179
180 static guint
nb_bars(const Ptr<const CPUGraph> & base)181 nb_bars (const Ptr<const CPUGraph> &base)
182 {
183 return base->tracked_core == 0 ? base->nr_cores : 1;
184 }
185
186 static void
create_bars(const Ptr<CPUGraph> & base,GtkOrientation orientation)187 create_bars (const Ptr<CPUGraph> &base, GtkOrientation orientation)
188 {
189 base->bars.frame = gtk_frame_new (NULL);
190 base->bars.draw_area = gtk_drawing_area_new ();
191 base->bars.orientation = orientation;
192 CPUGraph::set_frame (base, base->has_frame);
193 gtk_container_add (GTK_CONTAINER (base->bars.frame), base->bars.draw_area);
194 gtk_box_pack_end (GTK_BOX (base->box), base->bars.frame, TRUE, TRUE, 0);
195 xfce4::connect_after_draw (base->bars.draw_area, [base](cairo_t *cr) { return draw_bars_cb(cr, base); });
196 gtk_widget_show_all (base->bars.frame);
197 ebox_revalidate (base);
198 }
199
~CPUGraph()200 CPUGraph::~CPUGraph()
201 {
202 g_info ("%s", __PRETTY_FUNCTION__);
203 for (auto hist_data : history.data)
204 g_free (hist_data);
205 }
206
207 static void
shutdown(const Ptr<CPUGraph> & base)208 shutdown (const Ptr<CPUGraph> &base)
209 {
210 delete_bars (base);
211 gtk_widget_destroy (base->ebox);
212 base->ebox = NULL;
213 g_object_unref (base->tooltip_text);
214 base->tooltip_text = NULL;
215 if (base->timeout_id)
216 {
217 g_source_remove (base->timeout_id);
218 base->timeout_id = 0;
219 }
220 }
221
222 static void
queue_draw(const Ptr<CPUGraph> & base)223 queue_draw (const Ptr<CPUGraph> &base)
224 {
225 if (base->mode != MODE_DISABLED)
226 gtk_widget_queue_draw (base->draw_area);
227 if (base->bars.draw_area)
228 gtk_widget_queue_draw (base->bars.draw_area);
229 }
230
231 static void
delete_bars(const Ptr<CPUGraph> & base)232 delete_bars (const Ptr<CPUGraph> &base)
233 {
234 if (base->bars.frame)
235 {
236 gtk_widget_destroy (base->bars.frame);
237 base->bars.frame = NULL;
238 base->bars.draw_area = NULL;
239 }
240 }
241
242 static void
resize_history(const Ptr<CPUGraph> & base,gssize history_size)243 resize_history (const Ptr<CPUGraph> &base, gssize history_size)
244 {
245 const guint fastest = get_update_interval_ms (RATE_FASTEST);
246 const guint slowest = get_update_interval_ms (RATE_SLOWEST);
247 const gssize old_cap_pow2 = base->history.cap_pow2;
248
249 gssize cap_pow2 = 1;
250 while (cap_pow2 < MAX_SIZE * slowest / fastest)
251 cap_pow2 <<= 1;
252 while (cap_pow2 < history_size * slowest / fastest)
253 cap_pow2 <<= 1;
254
255 if (cap_pow2 != old_cap_pow2)
256 {
257 const std::vector<CpuLoad*> old_data = std::move(base->history.data);
258 const gssize old_mask = base->history.mask();
259 const gssize old_offset = base->history.offset;
260
261 base->history.cap_pow2 = cap_pow2;
262 base->history.data.resize(base->nr_cores + 1);
263 base->history.offset = 0;
264 for (guint core = 0; core < base->nr_cores + 1; core++)
265 {
266 base->history.data[core] = (CpuLoad*) g_malloc0 (cap_pow2 * sizeof (CpuLoad));
267 if (!old_data.empty())
268 {
269 for (gssize i = 0; i < old_cap_pow2 && i < cap_pow2; i++)
270 base->history.data[core][i] = old_data[core][(old_offset + i) & old_mask];
271 g_free (old_data[core]);
272 }
273 }
274
275 xfce4::trim_memory ();
276 }
277
278 base->history.size = history_size;
279 }
280
281 static PluginSize
size_cb(XfcePanelPlugin * plugin,guint plugin_size,const Ptr<CPUGraph> & base)282 size_cb (XfcePanelPlugin *plugin, guint plugin_size, const Ptr<CPUGraph> &base)
283 {
284 gint frame_h, frame_v, size;
285 gssize history;
286 GtkOrientation orientation;
287 guint border_width;
288 const gint shadow_width = base->has_frame ? 2*1 : 0;
289
290 size = base->size;
291 if (base->per_core && base->nr_cores >= 2)
292 {
293 size *= base->nr_cores;
294 size += (base->nr_cores - 1) * base->per_core_spacing;
295 }
296
297 orientation = xfce_panel_plugin_get_orientation (plugin);
298
299 if (orientation == GTK_ORIENTATION_HORIZONTAL)
300 {
301 frame_h = size + shadow_width;
302 frame_v = plugin_size;
303 history = base->size;
304 }
305 else
306 {
307 frame_h = plugin_size;
308 frame_v = size + shadow_width;
309 history = plugin_size;
310 }
311
312 /* Expand history size for the non-linear time-scale mode.
313 * 128 * pow(1.04, 128) = 19385.5175366781
314 * 163 * pow(1.04, 163) = 97414.11965601446
315 */
316 history = ceil (history * pow(NONLINEAR_MODE_BASE, history));
317 if (G_UNLIKELY (history < 0 || history > MAX_HISTORY_SIZE))
318 history = MAX_HISTORY_SIZE;
319
320 if (history > base->history.cap_pow2)
321 resize_history (base, history);
322 else
323 base->history.size = history;
324
325 gtk_widget_set_size_request (GTK_WIDGET (base->frame_widget), frame_h, frame_v);
326 if (base->has_bars) {
327 base->bars.orientation = orientation;
328 set_bars_size (base);
329 }
330
331 if (base->has_border)
332 border_width = (xfce_panel_plugin_get_size (base->plugin) > 26 ? 2 : 1);
333 else
334 border_width = 0;
335 gtk_container_set_border_width (GTK_CONTAINER (base->box), border_width);
336
337 base->set_border (base, base->has_border);
338
339 return xfce4::RECTANGLE;
340 }
341
342 static void
set_bars_size(const Ptr<CPUGraph> & base)343 set_bars_size (const Ptr<CPUGraph> &base)
344 {
345 gint h, v;
346 gint shadow_width;
347
348 shadow_width = base->has_frame ? 2*1 : 0;
349
350 if (base->bars.orientation == GTK_ORIENTATION_HORIZONTAL)
351 {
352 h = 6 * nb_bars (base) - 2 + shadow_width;
353 v = -1;
354 }
355 else
356 {
357 h = -1;
358 v = 6 * nb_bars (base) - 2 + shadow_width;
359 }
360
361 gtk_widget_set_size_request (base->bars.frame, h, v);
362 }
363
364 static void
mode_cb(XfcePanelPlugin * plugin,const Ptr<CPUGraph> & base)365 mode_cb (XfcePanelPlugin *plugin, const Ptr<CPUGraph> &base)
366 {
367 gtk_orientable_set_orientation (GTK_ORIENTABLE (base->box), xfce_panel_plugin_get_orientation (plugin));
368
369 size_cb (plugin, xfce_panel_plugin_get_size (base->plugin), base);
370 }
371
372 static void
detect_smt_issues(const Ptr<CPUGraph> & base)373 detect_smt_issues (const Ptr<CPUGraph> &base)
374 {
375 const bool debug = false;
376 gfloat actual_load[base->nr_cores];
377 bool movement[base->nr_cores];
378 bool suboptimal[base->nr_cores];
379
380 for (guint i = 0; i < base->nr_cores; i++)
381 {
382 actual_load[i] = base->cpu_data[i+1].load;
383 suboptimal[i] = false;
384 movement[i] = false;
385 if (debug)
386 g_info ("actual_load[%u] = %g", i, actual_load[i]);
387 }
388
389 if (base->topology && base->topology->smt)
390 {
391 /* Use <Topology> instead of <const Topology>.
392 * The non-const version results in less efficient C++ code,
393 * but it is less prone to generate an exception or a crash
394 * than the const version due to an unforseen programming bug. */
395 const Ptr0<Topology> topo = base->topology;
396
397 gfloat optimal_load[base->nr_cores];
398 gfloat actual_num_instr_executed[base->nr_cores];
399 gfloat optimal_num_instr_executed[base->nr_cores];
400 bool smt_incident = false;
401
402 /* Initialize CPU load arrays.
403 * The array optimal_load[] will be updated
404 * if a suboptimal SMT thread placement is detected. */
405 for (guint i = 0; i < base->nr_cores; i++)
406 {
407 const gfloat load = actual_load[i];
408 optimal_load[i] = load;
409 actual_num_instr_executed[i] = load;
410 optimal_num_instr_executed[i] = load;
411 }
412
413 const gfloat THRESHOLD = 1.0 + 0.1; /* A lower bound (this core) */
414 const gfloat THRESHOLD_OTHER = 1.0 - 0.1; /* An upper bound (some other core) */
415
416 for (guint i = 0; i < base->nr_cores; i++)
417 {
418 if (G_LIKELY (i < topo->num_logical_cpus))
419 {
420 const gint core = topo->logical_cpu_2_core[i];
421 if (G_LIKELY (core != -1) && topo->cores[core].logical_cpus.size() >= 2)
422 {
423 /* _Approximate_ slowdown if two threads
424 * are executed on the same physical core
425 * instead of being executed on separate cores.
426 * This number has been determined by measuring
427 * the slowdown of running two instances of
428 * "stress-ng --cpu=1" on a Ryzen 3700X CPU. */
429 const gfloat SMT_SLOWDOWN = 0.25f;
430
431 retry:
432 gfloat combined_usage = 0;
433 for (guint cpu : topo->cores[core].logical_cpus)
434 {
435 if (G_LIKELY (cpu < base->nr_cores))
436 combined_usage += optimal_load[cpu];
437 }
438 if (combined_usage > THRESHOLD)
439 {
440 /* Attempt to find a free CPU *core* different from `core`
441 * that might have had executed the workload
442 * without resorting to SMT/hyperthreading */
443 for (const auto &core_iterator : topo->cores)
444 {
445 guint other_core = core_iterator.first;
446 if (other_core != (guint) core)
447 {
448 gfloat combined_usage_other = 0.0;
449 for (guint other_cpu : topo->cores[other_core].logical_cpus)
450 {
451 if (G_LIKELY (other_cpu < base->nr_cores))
452 combined_usage_other += optimal_load[other_cpu];
453 }
454 if (combined_usage_other < THRESHOLD_OTHER)
455 {
456 /* The thread might have been executed on 'other_core',
457 * instead of on 'core', where it might have enjoyed
458 * a much higher IPC (instructions per clock) ratio */
459
460 smt_incident = true;
461 for (guint cpu : topo->cores[core].logical_cpus)
462 {
463 if (G_LIKELY (cpu < base->nr_cores))
464 suboptimal[cpu] = true;
465 }
466
467 /*
468 * 1.001 and 0.999 are used instead of 1.0 to make sure that:
469 * - the algorithm always terminates
470 * - the algorithm terminates quickly
471 * - it skips unimportant differences such as 1e-5
472 */
473
474 if (G_LIKELY (combined_usage > 1.001f))
475 {
476 /* Move as much of excess load to the other core as possible */
477 const gfloat excess_load = combined_usage - 1.0f;
478 gint other_cpu_min = -1;
479 for (guint other_cpu : topo->cores[other_core].logical_cpus)
480 {
481 if (G_LIKELY (other_cpu < base->nr_cores))
482 if (optimal_load[other_cpu] < 0.999f)
483 if (other_cpu_min == -1 || optimal_load[other_cpu_min] > optimal_load[other_cpu])
484 other_cpu_min = other_cpu;
485 }
486 if (G_LIKELY (other_cpu_min != -1))
487 {
488 gfloat load_to_move;
489
490 load_to_move = excess_load;
491 if (load_to_move > 1.0f - optimal_load[other_cpu_min])
492 load_to_move = 1.0f - optimal_load[other_cpu_min];
493
494 if (debug)
495 g_info ("load_to_move = %g", load_to_move);
496
497 optimal_load[other_cpu_min] += load_to_move;
498
499 /* The move negates the SMT slowdown for the work moved onto the underutilized target CPU core */
500 movement[other_cpu_min] = true;
501 optimal_num_instr_executed[other_cpu_min] += (1.0f + SMT_SLOWDOWN) * load_to_move;
502
503 /* Decrease combined_usage by load_to_move */
504 for (guint j = topo->cores[core].logical_cpus.size(); load_to_move > 0 && j != 0;)
505 {
506 guint cpu = topo->cores[core].logical_cpus[--j];
507 if (G_LIKELY (cpu < base->nr_cores))
508 {
509 if (optimal_load[cpu] >= load_to_move)
510 {
511 const gfloat diff = load_to_move;
512
513 optimal_load[cpu] -= diff;
514 load_to_move = 0;
515
516 /* The move negates the SMT slowdown for the work remaining on the original CPU core */
517 optimal_num_instr_executed[cpu] -= 1.0f * diff; /* Moved work */
518 optimal_num_instr_executed[cpu] += SMT_SLOWDOWN * diff; /* Remaining work (speedup) */
519 movement[cpu] = true;
520 }
521 else
522 {
523 const gfloat diff = optimal_load[cpu];
524
525 optimal_load[cpu] = 0;
526 load_to_move -= diff;
527
528 /* The move negates the SMT slowdown for the work remaining on the original CPU core */
529 optimal_num_instr_executed[cpu] -= 1.0f * diff; /* Moved work */
530 optimal_num_instr_executed[cpu] += SMT_SLOWDOWN * diff; /* Remaining work (speedup) */
531 movement[cpu] = true;
532 }
533 }
534 }
535
536 /* At this point: load_to_move should be zero or very close to zero */
537 g_warn_if_fail (load_to_move < 0.001f);
538
539 goto retry;
540 }
541 }
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549
550 /* Update instruction counters */
551 for (guint i = 0; i < base->nr_cores; i++)
552 {
553 base->stats.num_instructions_executed.total.actual += actual_num_instr_executed[i];
554 base->stats.num_instructions_executed.total.optimal += optimal_num_instr_executed[i];
555 }
556
557 /* Suboptimal SMT scheduling cases are actually quite rare (at least in Linux):
558 * - They are impossible to happen if the CPU is under full load
559 * - They are rare if the CPU is running one single-threaded task
560 * - They tend to occur especially when the CPU is running about nr_cores/2 threads
561 */
562 if (G_UNLIKELY (smt_incident))
563 {
564 base->stats.num_smt_incidents++;
565
566 /* Update instruction counters */
567 for (guint i = 0; i < base->nr_cores; i++)
568 {
569 if (movement[i] || suboptimal[i])
570 {
571 base->stats.num_instructions_executed.during_smt_incidents.actual += actual_num_instr_executed[i];
572 base->stats.num_instructions_executed.during_smt_incidents.optimal += optimal_num_instr_executed[i];
573 }
574 }
575 }
576
577 /* At this point, the values in suboptimal[] are based on values in optimal_load.
578 * This can falsely mark a CPU as suboptimal if the algoritm moved some work to the CPU from other CPUs.
579 * Fix false positives in suboptimal[] based on values in actual_load.
580 *
581 * It is uncertain whether this correction should be performed before or after instruction counter updates.
582 */
583 for (const auto &core_iterator : topo->cores)
584 {
585 const Topology::CpuCore &core = core_iterator.second;
586
587 bool positive = false;
588 for (guint cpu : core.logical_cpus)
589 if (G_LIKELY (cpu < base->nr_cores))
590 positive |= suboptimal[cpu];
591
592 if (positive)
593 {
594 gfloat actual_combined_usage = 0;
595 for (guint cpu : core_iterator.second.logical_cpus)
596 actual_combined_usage += actual_load[cpu];
597
598 bool false_positive = !(actual_combined_usage > THRESHOLD);
599 if (false_positive)
600 for (guint cpu : core.logical_cpus)
601 if (G_LIKELY (cpu < base->nr_cores))
602 suboptimal[cpu] = false;
603 }
604 }
605 }
606
607 for (guint i = 0; i < base->nr_cores; i++)
608 base->cpu_data[i+1].smt_highlight = suboptimal[i];
609 }
610
611 static bool
update_cb(const Ptr<CPUGraph> & base)612 update_cb (const Ptr<CPUGraph> &base)
613 {
614 if (!read_cpu_data (base->cpu_data))
615 return TRUE;
616
617 detect_smt_issues (base);
618
619 if (!base->history.data.empty())
620 {
621 const gint64 timestamp = g_get_real_time ();
622
623 /* Prepend the current CPU load to the history */
624 base->history.offset = (base->history.offset - 1) & base->history.mask();
625 for (guint core = 0; core < base->nr_cores + 1; core++)
626 {
627 CpuLoad load;
628 load.timestamp = timestamp;
629 load.value = base->cpu_data[core].load;
630 base->history.data[core][base->history.offset] = load;
631 }
632 }
633
634 queue_draw (base);
635 update_tooltip (base);
636
637 return TRUE;
638 }
639
640 static void
update_tooltip(const Ptr<CPUGraph> & base)641 update_tooltip (const Ptr<CPUGraph> &base)
642 {
643 auto tooltip = xfce4::sprintf (_("Usage: %u%%"), (guint) roundf (base->cpu_data[0].load * 100));
644 if (gtk_label_get_text (GTK_LABEL (base->tooltip_text)) != tooltip)
645 gtk_label_set_text (GTK_LABEL (base->tooltip_text), tooltip.c_str());
646 }
647
648 static TooltipTime
tooltip_cb(GtkTooltip * tooltip,const Ptr<CPUGraph> & base)649 tooltip_cb (GtkTooltip *tooltip, const Ptr<CPUGraph> &base)
650 {
651 gtk_tooltip_set_custom (tooltip, base->tooltip_text);
652 return xfce4::NOW;
653 }
654
655 static Propagation
draw_area_cb(cairo_t * cr,const Ptr<CPUGraph> & base)656 draw_area_cb (cairo_t *cr, const Ptr<CPUGraph> &base)
657 {
658 GtkAllocation alloc;
659 gint w, h;
660 void (*draw) (const Ptr<CPUGraph> &base, cairo_t *cr, gint w, gint h, guint core) = NULL;
661
662 gtk_widget_get_allocation (base->draw_area, &alloc);
663 w = alloc.width;
664 h = alloc.height;
665
666 switch (base->mode)
667 {
668 case MODE_DISABLED:
669 break;
670 case MODE_NORMAL:
671 draw = draw_graph_normal;
672 break;
673 case MODE_LED:
674 draw = draw_graph_LED;
675 break;
676 case MODE_NO_HISTORY:
677 draw = draw_graph_no_history;
678 break;
679 case MODE_GRID:
680 draw = draw_graph_grid;
681 break;
682 }
683
684 if (draw)
685 {
686 if (!base->per_core || base->nr_cores == 1)
687 {
688 guint core;
689
690 if (!base->colors[BG_COLOR].isTransparent())
691 {
692 xfce4::cairo_set_source (cr, base->colors[BG_COLOR]);
693 cairo_rectangle (cr, 0, 0, w, h);
694 cairo_fill (cr);
695 }
696
697 core = base->tracked_core;
698 if (G_UNLIKELY (core > base->nr_cores + 1))
699 core = 0;
700 draw (base, cr, w, h, core);
701 }
702 else
703 {
704 bool horizontal;
705 gint w1, h1;
706
707 horizontal = (xfce_panel_plugin_get_orientation (base->plugin) == GTK_ORIENTATION_HORIZONTAL);
708 if (horizontal)
709 {
710 w1 = base->size;
711 h1 = h;
712 }
713 else
714 {
715 w1 = w;
716 h1 = base->size;
717 }
718
719 for (guint core = 0; core < base->nr_cores; core++)
720 {
721 cairo_save (cr);
722 {
723 cairo_rectangle_t translation = {};
724 *(horizontal ? &translation.x : &translation.y) = core * (base->size + base->per_core_spacing);
725 cairo_translate (cr, translation.x, translation.y);
726
727 if (!base->colors[BG_COLOR].isTransparent())
728 {
729 xfce4::cairo_set_source (cr, base->colors[BG_COLOR]);
730 cairo_rectangle (cr, 0, 0, w1, h1);
731 cairo_fill (cr);
732 }
733
734 cairo_rectangle (cr, 0, 0, w1, h1);
735 cairo_clip (cr);
736 draw (base, cr, w1, h1, core+1);
737 }
738 cairo_restore (cr);
739 }
740 }
741 }
742 return xfce4::PROPAGATE;
743 }
744
745 static Propagation
draw_bars_cb(cairo_t * cr,const Ptr<CPUGraph> & base)746 draw_bars_cb (cairo_t *cr, const Ptr<CPUGraph> &base)
747 {
748 GtkAllocation alloc;
749 gfloat size;
750 const bool horizontal = (base->bars.orientation == GTK_ORIENTATION_HORIZONTAL);
751
752 gtk_widget_get_allocation (base->bars.draw_area, &alloc);
753
754 if (!base->colors[BG_COLOR].isTransparent())
755 {
756 xfce4::cairo_set_source (cr, base->colors[BG_COLOR]);
757 cairo_rectangle (cr, 0, 0, alloc.width, alloc.height);
758 cairo_fill (cr);
759 }
760
761 size = (horizontal ? alloc.height : alloc.width);
762 if (base->tracked_core != 0 || base->nr_cores == 1)
763 {
764 gfloat usage = base->cpu_data[0].load;
765 if (usage < base->load_threshold)
766 usage = 0;
767 usage *= size;
768
769 xfce4::cairo_set_source (cr, base->colors[BARS_COLOR]);
770 if (horizontal)
771 cairo_rectangle (cr, 0, size-usage, 4, usage);
772 else
773 cairo_rectangle (cr, 0, 0, usage, 4);
774 cairo_fill (cr);
775 }
776 else
777 {
778 const xfce4::RGBA *active_color = NULL;
779 bool fill = false;
780 for (guint i = 0; i < base->nr_cores; i++)
781 {
782 const bool highlight = base->highlight_smt && base->cpu_data[i+1].smt_highlight;
783
784 gfloat usage = base->cpu_data[i+1].load;
785 if (usage < base->load_threshold)
786 usage = 0;
787 usage *= size;
788
789 /* Suboptimally placed threads on SMT CPUs are optionally painted using a different color. */
790 const xfce4::RGBA *color = &base->colors[highlight ? SMT_ISSUES_COLOR : BARS_COLOR];
791 if (active_color != color)
792 {
793 if (fill)
794 {
795 cairo_fill (cr);
796 fill = false;
797 }
798 xfce4::cairo_set_source (cr, *color);
799 active_color = color;
800 }
801
802 if (horizontal)
803 cairo_rectangle (cr, 6*i, size-usage, 4, usage);
804 else
805 cairo_rectangle (cr, 0, 6*i, usage, 4);
806 fill = true;
807 }
808 if (fill)
809 cairo_fill (cr);
810 }
811 return xfce4::PROPAGATE;
812 }
813
814 static const gchar*
default_command(bool * in_terminal,bool * startup_notification)815 default_command (bool *in_terminal, bool *startup_notification)
816 {
817 gchar *s = g_find_program_in_path ("xfce4-taskmanager");
818 if (s)
819 {
820 g_free (s);
821 *in_terminal = false;
822 *startup_notification = true;
823 return "xfce4-taskmanager";
824 }
825 else
826 {
827 *in_terminal = true;
828 *startup_notification = false;
829
830 s = g_find_program_in_path ("htop");
831 if (s)
832 {
833 g_free (s);
834 return "htop";
835 }
836 else
837 {
838 return "top";
839 }
840 }
841 }
842
843 static Propagation
command_cb(GdkEventButton * event,const Ptr<CPUGraph> & base)844 command_cb (GdkEventButton *event, const Ptr<CPUGraph> &base)
845 {
846 if (event->button == 1)
847 {
848 std::string command;
849 bool in_terminal, startup_notification;
850
851 if (!base->command.empty())
852 {
853 command = base->command;
854 in_terminal = base->command_in_terminal;
855 startup_notification = base->command_startup_notification;
856 }
857 else
858 {
859 command = default_command (&in_terminal, &startup_notification);
860 }
861
862 xfce_spawn_command_line_on_screen (gdk_screen_get_default (),
863 command.c_str(), in_terminal,
864 startup_notification, NULL);
865 }
866 return xfce4::STOP;
867 }
868
869 /**
870 * get_update_interval_ms:
871 *
872 * Returns: update interval in milliseconds.
873 */
874 guint
get_update_interval_ms(CPUGraphUpdateRate rate)875 get_update_interval_ms (CPUGraphUpdateRate rate)
876 {
877 switch (rate)
878 {
879 case RATE_FASTEST:
880 return 250;
881 case RATE_FAST:
882 return 500;
883 case RATE_NORMAL:
884 return 750;
885 case RATE_SLOW:
886 return 1000;
887 case RATE_SLOWEST:
888 return 3000;
889 default:
890 return 750;
891 }
892 }
893
894 void
set_startup_notification(const Ptr<CPUGraph> & base,bool startup_notification)895 CPUGraph::set_startup_notification (const Ptr<CPUGraph> &base, bool startup_notification)
896 {
897 base->command_startup_notification = startup_notification;
898 }
899
900 void
set_in_terminal(const Ptr<CPUGraph> & base,bool in_terminal)901 CPUGraph::set_in_terminal (const Ptr<CPUGraph> &base, bool in_terminal)
902 {
903 base->command_in_terminal = in_terminal;
904 }
905
906 void
set_command(const Ptr<CPUGraph> & base,const std::string & command)907 CPUGraph::set_command (const Ptr<CPUGraph> &base, const std::string &command)
908 {
909 base->command = xfce4::trim (command);
910 }
911
912 void
set_bars(const Ptr<CPUGraph> & base,bool has_bars)913 CPUGraph::set_bars (const Ptr<CPUGraph> &base, bool has_bars)
914 {
915 if (base->has_bars != has_bars)
916 {
917 base->has_bars = has_bars;
918 if (base->has_bars)
919 {
920 create_bars (base, xfce_panel_plugin_get_orientation (base->plugin));
921 set_bars_size (base);
922 }
923 else
924 delete_bars (base);
925 }
926 }
927
928 void
set_border(const Ptr<CPUGraph> & base,bool has_border)929 CPUGraph::set_border (const Ptr<CPUGraph> &base, bool has_border)
930 {
931 if (base->has_border != has_border)
932 {
933 base->has_border = has_border;
934 size_cb (base->plugin, xfce_panel_plugin_get_size (base->plugin), base);
935 }
936 }
937
938 void
set_frame(const Ptr<CPUGraph> & base,bool has_frame)939 CPUGraph::set_frame (const Ptr<CPUGraph> &base, bool has_frame)
940 {
941 base->has_frame = has_frame;
942 gtk_frame_set_shadow_type (GTK_FRAME (base->frame_widget), has_frame ? GTK_SHADOW_IN : GTK_SHADOW_NONE);
943 if (base->bars.frame)
944 gtk_frame_set_shadow_type (GTK_FRAME (base->bars.frame), has_frame ? GTK_SHADOW_IN : GTK_SHADOW_NONE);
945 size_cb (base->plugin, xfce_panel_plugin_get_size (base->plugin), base);
946 }
947
948 void
set_nonlinear_time(const Ptr<CPUGraph> & base,bool non_linear)949 CPUGraph::set_nonlinear_time (const Ptr<CPUGraph> &base, bool non_linear)
950 {
951 if (base->non_linear != non_linear)
952 {
953 base->non_linear = non_linear;
954 queue_draw (base);
955 }
956 }
957
958 void
set_per_core(const Ptr<CPUGraph> & base,bool per_core)959 CPUGraph::set_per_core (const Ptr<CPUGraph> &base, bool per_core)
960 {
961 if (base->per_core != per_core)
962 {
963 base->per_core = per_core;
964 size_cb (base->plugin, xfce_panel_plugin_get_size (base->plugin), base);
965 }
966 }
967
968 void
set_per_core_spacing(const Ptr<CPUGraph> & base,guint spacing)969 CPUGraph::set_per_core_spacing (const Ptr<CPUGraph> &base, guint spacing)
970 {
971 /* Use <=, instead of <, supresses a compiler warning */
972 if (G_UNLIKELY (spacing <= PER_CORE_SPACING_MIN))
973 spacing = PER_CORE_SPACING_MIN;
974 if (G_UNLIKELY (spacing > PER_CORE_SPACING_MAX))
975 spacing = PER_CORE_SPACING_MAX;
976
977 if (base->per_core_spacing != spacing)
978 {
979 base->per_core_spacing = spacing;
980 size_cb (base->plugin, xfce_panel_plugin_get_size (base->plugin), base);
981 }
982 }
983
984 void
set_smt(const Ptr<CPUGraph> & base,bool highlight_smt)985 CPUGraph::set_smt (const Ptr<CPUGraph> &base, bool highlight_smt)
986 {
987 base->highlight_smt = highlight_smt;
988 }
989
990 void
set_update_rate(const Ptr<CPUGraph> & base,CPUGraphUpdateRate rate)991 CPUGraph::set_update_rate (const Ptr<CPUGraph> &base, CPUGraphUpdateRate rate)
992 {
993 bool change = (base->update_interval != rate);
994 bool init = (base->timeout_id == 0);
995
996 if (change || init)
997 {
998 guint interval = get_update_interval_ms (rate);
999
1000 base->update_interval = rate;
1001 if (base->timeout_id)
1002 g_source_remove (base->timeout_id);
1003 base->timeout_id = xfce4::timeout_add (interval, [base]() -> bool { return update_cb(base); });
1004
1005 if (change && !init)
1006 queue_draw (base);
1007 }
1008 }
1009
1010 void
set_size(const Ptr<CPUGraph> & base,guint size)1011 CPUGraph::set_size (const Ptr<CPUGraph> &base, guint size)
1012 {
1013 if (G_UNLIKELY (size < MIN_SIZE))
1014 size = MIN_SIZE;
1015 if (G_UNLIKELY (size > MAX_SIZE))
1016 size = MAX_SIZE;
1017
1018 base->size = size;
1019 size_cb (base->plugin, xfce_panel_plugin_get_size (base->plugin), base);
1020 }
1021
1022 void
set_color_mode(const Ptr<CPUGraph> & base,guint color_mode)1023 CPUGraph::set_color_mode (const Ptr<CPUGraph> &base, guint color_mode)
1024 {
1025 if (base->color_mode != color_mode)
1026 {
1027 base->color_mode = color_mode;
1028 queue_draw (base);
1029 }
1030 }
1031
1032 void
set_mode(const Ptr<CPUGraph> & base,CPUGraphMode mode)1033 CPUGraph::set_mode (const Ptr<CPUGraph> &base, CPUGraphMode mode)
1034 {
1035 base->mode = mode;
1036 if (mode == MODE_DISABLED)
1037 {
1038 gtk_widget_hide (base->frame_widget);
1039 }
1040 else
1041 {
1042 gtk_widget_show (base->frame_widget);
1043 ebox_revalidate (base);
1044 }
1045 }
1046
1047 void
set_color(const Ptr<CPUGraph> & base,CPUGraphColorNumber number,const xfce4::RGBA & color)1048 CPUGraph::set_color (const Ptr<CPUGraph> &base, CPUGraphColorNumber number, const xfce4::RGBA &color)
1049 {
1050 if (!base->colors[number].equals(color))
1051 {
1052 base->colors[number] = color;
1053 queue_draw (base);
1054 }
1055 }
1056
1057 void
set_tracked_core(const Ptr<CPUGraph> & base,guint core)1058 CPUGraph::set_tracked_core (const Ptr<CPUGraph> &base, guint core)
1059 {
1060 if (G_UNLIKELY (core > base->nr_cores + 1))
1061 core = 0;
1062
1063 if (base->tracked_core != core)
1064 {
1065 const bool had_bars = base->has_bars;
1066 if (had_bars)
1067 set_bars (base, false);
1068 base->tracked_core = core;
1069 if (had_bars)
1070 set_bars (base, true);
1071 }
1072 }
1073
1074 void
set_load_threshold(const Ptr<CPUGraph> & base,gfloat threshold)1075 CPUGraph::set_load_threshold (const Ptr<CPUGraph> &base, gfloat threshold)
1076 {
1077 if (threshold < 0)
1078 threshold = 0;
1079 if (threshold > MAX_LOAD_THRESHOLD)
1080 threshold = MAX_LOAD_THRESHOLD;
1081 base->load_threshold = threshold;
1082 }
1083