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