1 /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 #include <config.h>
3 
4 #include <math.h>
5 
6 #include <glib/gi18n.h>
7 
8 #include <glibtop.h>
9 #include <glibtop/cpu.h>
10 #include <glibtop/mem.h>
11 #include <glibtop/swap.h>
12 #include <glibtop/netload.h>
13 #include <glibtop/netlist.h>
14 
15 #include "application.h"
16 #include "load-graph.h"
17 #include "util.h"
18 #include "legacy/gsm_color_button.h"
19 
20 gchar* format_duration(unsigned seconds);
21 
clear_background()22 void LoadGraph::clear_background()
23 {
24     if (background) {
25         cairo_surface_destroy (background);
26         background = NULL;
27     }
28 }
29 
is_logarithmic_scale() const30 bool LoadGraph::is_logarithmic_scale() const
31 {
32     // logarithmic scale is used only for memory graph
33     return this->type == LOAD_GRAPH_MEM && GsmApplication::get()->config.logarithmic_scale;
34 }
35 
num_bars() const36 unsigned LoadGraph::num_bars() const
37 {
38     unsigned n;
39 
40     // keep 100 % num_bars == 0
41     switch (static_cast<int>(draw_height / (fontsize + 14)))
42     {
43         case 0:
44         case 1:
45             n = 1;
46             break;
47         case 2:
48         case 3:
49             n = 2;
50             break;
51         case 4:
52             n = 4;
53             break;
54         case 5:
55             n = 5;
56             if (this->is_logarithmic_scale())
57                 n = 4;
58             break;
59         default:
60             n = 5;
61             if (this->is_logarithmic_scale())
62                 n = 6;
63     }
64 
65     return n;
66 }
67 
68 /*
69  Returns Y scale caption based on give index of the label.
70  Takes into account whether the scale should be logarithmic for memory graph.
71  */
get_caption(guint index)72 char* LoadGraph::get_caption(guint index)
73 {
74     char *caption;
75     unsigned num_bars = this->num_bars();
76     guint64 max_value;
77     if (this->type == LOAD_GRAPH_NET)
78         max_value = this->net.max;
79     else
80         max_value = 100;
81 
82     // operation orders matters so it's 0 if index == num_bars
83     float caption_percentage = (float)max_value - index * (float)max_value / num_bars;
84 
85     if (this->is_logarithmic_scale()) {
86         float caption_value = caption_percentage == 0 ? 0 : pow(100, caption_percentage / max_value);
87         // Translators: loadgraphs y axis percentage labels: 0 %, 50%, 100%
88         caption = g_strdup_printf(_("%.0f %%"), caption_value);
89     } else if (this->type == LOAD_GRAPH_NET) {
90         const std::string captionstr(procman::format_network_rate((guint64)caption_percentage));
91         caption = g_strdup(captionstr.c_str());
92     } else {
93         // Translators: loadgraphs y axis percentage labels: 0 %, 50%, 100%
94         caption = g_strdup_printf(_("%.0f %%"), caption_percentage);
95     }
96 
97     return caption;
98 }
99 
100 /*
101  Translates y partial position to logarithmic position if set to logarithmic scale.
102 */
translate_to_log_partial_if_needed(float position_partial)103 float LoadGraph::translate_to_log_partial_if_needed(float position_partial)
104 {
105     if (this->is_logarithmic_scale())
106         position_partial = position_partial == 0 ? 0 : log10(position_partial * 100) / 2;
107 
108     return position_partial;
109 }
110 
format_duration(unsigned seconds)111 gchar* format_duration(unsigned seconds) {
112     gchar* caption = NULL;
113 
114     unsigned minutes = seconds / 60;
115     unsigned hours = seconds / 3600;
116 
117     if (hours != 0) {
118         if (minutes % 60 == 0) {
119              // If minutes mod 60 is 0 set it to 0, to prevent it from showing full hours in
120              // minutes in addition to hours.
121              minutes = 0;
122         } else {
123              // Round minutes as seconds wont get shown if neither hours nor minutes are 0.
124              minutes = int(rint(seconds / 60.0)) % 60;
125              if (minutes == 0) {
126                   // Increase hours if rounding minutes results in 0, because that would be
127                   // what it would be rounded to.
128                   hours++;
129                   // Set seconds to hours * 3600 to prevent seconds from being drawn.
130                   seconds = hours * 3600;
131              }
132         }
133 
134     }
135 
136     gchar* captionH = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u hr", "%u hrs", hours), hours);
137     gchar* captionM = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u min", "%u mins", minutes),
138                                      minutes);
139     gchar* captionS = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u sec", "%u secs", seconds % 60),
140                                      seconds % 60);
141 
142     caption = g_strjoin (" ", hours > 0 ? captionH : "",
143                               minutes > 0 ? captionM : "",
144                               seconds % 60 > 0 ? captionS : "",
145                          NULL);
146     g_free (captionH);
147     g_free (captionM);
148     g_free (captionS);
149 
150     return caption;
151 }
152 
153 const int FRAME_WIDTH = 4;
draw_background(LoadGraph * graph)154 static void draw_background(LoadGraph *graph) {
155     GtkAllocation allocation;
156     cairo_t *cr;
157     guint i;
158     double label_x_offset_modifier, label_y_offset_modifier;
159     unsigned num_bars;
160     gchar *caption;
161     PangoLayout* layout;
162     PangoAttrList *attrs = NULL;
163     PangoFontDescription* font_desc;
164     PangoRectangle extents;
165     cairo_surface_t *surface;
166     GdkRGBA fg;
167     GdkRGBA fg_grid;
168     double const border_alpha = 0.7;
169     double const grid_alpha = border_alpha / 2.0;
170 
171     num_bars = graph->num_bars();
172     graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
173     graph->real_draw_height = graph->graph_dely * num_bars;
174     graph->graph_delx = (graph->draw_width - 2.0 - graph->indent) / (graph->num_points - 3);
175     graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH;
176 
177     gtk_widget_get_allocation (GTK_WIDGET (graph->disp), &allocation);
178     surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (graph->disp)),
179                                                            CAIRO_CONTENT_COLOR_ALPHA,
180                                                            allocation.width,
181                                                            allocation.height);
182     cr = cairo_create (surface);
183 
184     GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (GsmApplication::get()->stack));
185 
186     gtk_style_context_get_color (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), &fg);
187 
188     cairo_paint_with_alpha (cr, 0.0);
189     layout = pango_cairo_create_layout (cr);
190 
191     attrs = make_tnum_attr_list ();
192     pango_layout_set_attributes (layout, attrs);
193     g_clear_pointer (&attrs, pango_attr_list_unref);
194 
195     gtk_style_context_get (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
196     pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE);
197     pango_layout_set_font_description (layout, font_desc);
198     pango_font_description_free (font_desc);
199 
200     /* draw frame */
201     cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);
202 
203     /* Draw background rectangle */
204     /* When a user uses a dark theme, the hard-coded
205      * white background in GSM is a lone white on the
206      * display, which makes the user unhappy. To fix
207      * this, here we offer the user a chance to set
208      * his favorite background color. */
209     gtk_style_context_save (context);
210 
211     /* Here we specify the name of the class. Now in
212      * the theme's CSS we can specify the own colors
213      * for this class. */
214     gtk_style_context_add_class (context, "loadgraph");
215 
216     /* And in case the user does not care, we add
217      * classes that usually have a white background. */
218     gtk_style_context_add_class (context, GTK_STYLE_CLASS_PAPER);
219     gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
220 
221     /* And, as a bonus, the user can choose the color of the grid. */
222     gtk_style_context_get_color (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), &fg_grid);
223 
224     /* Why not use the new features of the
225      * GTK instead of cairo_rectangle ?! :) */
226     gtk_render_background (context, cr, graph->indent, 0.0,
227             graph->draw_width - graph->rmargin - graph->indent,
228             graph->real_draw_height);
229 
230     gtk_style_context_restore (context);
231 
232     cairo_set_line_width (cr, 1.0);
233 
234     for (i = 0; i <= num_bars; ++i) {
235         double y;
236 
237         if (i == 0)
238             y = 0.5 + graph->fontsize / 2.0;
239         else if (i == num_bars)
240             y = i * graph->graph_dely + 0.5;
241         else
242             y = i * graph->graph_dely + graph->fontsize / 2.0;
243 
244         gdk_cairo_set_source_rgba (cr, &fg);
245         caption = graph->get_caption(i);
246         pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
247         pango_layout_set_text (layout, caption, -1);
248         pango_layout_get_extents (layout, NULL, &extents);
249         label_y_offset_modifier = i == 0 ? 0.5
250                                 : i == num_bars
251                                     ? 1.0
252                                     : 0.85;
253         cairo_move_to (cr, graph->draw_width - graph->indent - 23,
254                        y - label_y_offset_modifier * extents.height / PANGO_SCALE);
255         pango_cairo_show_layout (cr, layout);
256         g_free(caption);
257 
258         if (i==0 || i==num_bars)
259             fg_grid.alpha = border_alpha;
260         else
261             fg_grid.alpha = grid_alpha;
262 
263         gdk_cairo_set_source_rgba (cr, &fg_grid);
264         cairo_move_to (cr, graph->indent, i * graph->graph_dely + 0.5);
265         cairo_line_to (cr, graph->draw_width - graph->rmargin + 0.5 + 4, i * graph->graph_dely + 0.5);
266         cairo_stroke (cr);
267     }
268 
269 
270     const unsigned total_seconds = graph->speed * (graph->num_points - 2) / 1000 * graph->frames_per_unit;
271 
272     for (unsigned int i = 0; i < 7; i++) {
273         double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6;
274 
275         if (i==0 || i==6)
276             fg_grid.alpha = border_alpha;
277         else
278             fg_grid.alpha = grid_alpha;
279 
280         gdk_cairo_set_source_rgba (cr, &fg_grid);
281         cairo_move_to (cr, (ceil(x) + 0.5) + graph->indent, 0.5);
282         cairo_line_to (cr, (ceil(x) + 0.5) + graph->indent, graph->real_draw_height + 4.5);
283         cairo_stroke(cr);
284 
285         caption = format_duration(total_seconds - i * total_seconds / 6);
286 
287         pango_layout_set_text (layout, caption, -1);
288         pango_layout_get_extents (layout, NULL, &extents);
289         label_x_offset_modifier = i == 0 ? 0
290                                          : i == 6
291                                             ? 1.0
292                                             : 0.5;
293         cairo_move_to (cr,
294                        (ceil(x) + 0.5 + graph->indent) - label_x_offset_modifier * extents.width / PANGO_SCALE + 1.0,
295                        graph->draw_height - 1.0 * extents.height / PANGO_SCALE);
296         gdk_cairo_set_source_rgba (cr, &fg);
297         pango_cairo_show_layout (cr, layout);
298         g_free (caption);
299     }
300     g_object_unref(layout);
301     cairo_stroke (cr);
302     cairo_destroy (cr);
303     graph->background = surface;
304 }
305 
306 /* Redraws the backing buffer for the load graph and updates the window */
307 void
load_graph_queue_draw(LoadGraph * graph)308 load_graph_queue_draw (LoadGraph *graph)
309 {
310     /* repaint */
311     gtk_widget_queue_draw (GTK_WIDGET (graph->disp));
312 }
313 
314 void load_graph_update_data (LoadGraph *graph);
315 static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)
316 
317 static void
load_graph_rescale(LoadGraph * graph)318 load_graph_rescale (LoadGraph *graph) {
319     ///org/gnome/desktop/interface/text-scaling-factor
320     graph->fontsize = 8 * graph->font_settings->get_double ("text-scaling-factor");
321     graph->clear_background();
322 
323     load_graph_queue_draw (graph);
324 }
325 
326 static gboolean
load_graph_configure(GtkWidget * widget,GdkEventConfigure * event,gpointer data_ptr)327 load_graph_configure (GtkWidget *widget,
328                       GdkEventConfigure *event,
329                       gpointer data_ptr)
330 {
331     GtkAllocation allocation;
332     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
333 
334     load_graph_rescale (graph);
335 
336     gtk_widget_get_allocation (widget, &allocation);
337     graph->draw_width = allocation.width - 2 * FRAME_WIDTH;
338     graph->draw_height = allocation.height - 2 * FRAME_WIDTH;
339 
340     graph->clear_background();
341 
342     load_graph_queue_draw (graph);
343 
344     return TRUE;
345 }
346 
force_refresh(LoadGraph * const graph)347 static void force_refresh (LoadGraph * const graph)
348 {
349     graph->clear_background();
350     load_graph_queue_draw (graph);
351 }
352 
353 static void
load_graph_style_updated(GtkWidget * widget,gpointer data_ptr)354 load_graph_style_updated (GtkWidget *widget,
355                           gpointer data_ptr)
356 {
357     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
358     force_refresh (graph);
359 }
360 
361 static gboolean
load_graph_state_changed(GtkWidget * widget,GtkStateFlags * flags,gpointer data_ptr)362 load_graph_state_changed (GtkWidget *widget,
363                       GtkStateFlags *flags,
364                       gpointer data_ptr)
365 {
366     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
367     force_refresh (graph);
368     graph->draw = gtk_widget_is_visible (widget);
369     return TRUE;
370 }
371 
372 static gboolean
load_graph_draw(GtkWidget * widget,cairo_t * cr,gpointer data_ptr)373 load_graph_draw (GtkWidget *widget,
374                  cairo_t * cr,
375                  gpointer data_ptr)
376 {
377     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
378 
379     guint i;
380     gint j;
381     gdouble sample_width, x_offset;
382 
383     /* Number of pixels wide for one sample point */
384     sample_width = (double)(graph->draw_width - graph->rmargin - graph->indent) / (double)graph->num_points;
385     /* Lines start at the right edge of the drawing,
386      * a bit outside the clip rectangle. */
387     x_offset = graph->draw_width - graph->rmargin + sample_width + 2;
388     /* Adjustment for smooth movement between samples */
389     x_offset -= sample_width * graph->render_counter / (double)graph->frames_per_unit;
390 
391     /* draw the graph */
392 
393     if (graph->background == NULL) {
394         draw_background(graph);
395     }
396     cairo_set_source_surface (cr, graph->background, 0, 0);
397     cairo_paint (cr);
398 
399     cairo_set_line_width (cr, 1);
400     cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
401     cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
402     cairo_rectangle (cr, graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1,
403                      graph->draw_width - graph->rmargin - graph->indent - 1,
404                      graph->real_draw_height + FRAME_WIDTH - 1);
405     cairo_clip(cr);
406 
407     bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
408     bool drawSmooth = GsmApplication::get()->config.draw_smooth;
409     for (j = graph->n-1; j >= 0; j--) {
410         gdk_cairo_set_source_rgba (cr, &(graph->colors [j]));
411         // Start drawing on the right at the correct height.
412         cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height + 3);
413         // then draw the path of the line.
414         // Loop starts at 1 because the curve accesses the 0th data point.
415         for (i = 1; i < graph->num_points; ++i) {
416             if (graph->data[i][j] == -1.0f)
417                 continue;
418             if (drawSmooth) {
419                 cairo_curve_to (cr,
420                               x_offset - ((i - 0.5f) * graph->graph_delx),
421                               (1.0 - graph->data[i-1][j]) * graph->real_draw_height + 3,
422                               x_offset - ((i - 0.5f) * graph->graph_delx),
423                               (1.0 - graph->data[i][j]) * graph->real_draw_height + 3,
424                               x_offset - (i * graph->graph_delx),
425                               (1.0 - graph->data[i][j]) * graph->real_draw_height + 3);
426             } else {
427                 cairo_line_to (cr, x_offset - (i * graph->graph_delx),
428                               (1.0 - graph->data[i][j]) * graph->real_draw_height + 3);
429             }
430 
431         }
432         if (drawStacked) {
433             // Draw the remaining outline of the area:
434             // Left bottom corner
435             cairo_rel_line_to (cr, 0, graph->real_draw_height + 3);
436             // Right bottom corner. It's drawn far outside the visible area
437             // to avoid a weird bug where it's not filling the area it should completely.
438             cairo_rel_line_to (cr, x_offset * 2, 0);
439 
440             //cairo_stroke_preserve(cr);
441             cairo_close_path(cr);
442             cairo_fill(cr);
443         } else {
444             cairo_stroke (cr);
445         }
446     }
447 
448     return TRUE;
449 }
450 
451 void
load_graph_reset(LoadGraph * graph)452 load_graph_reset (LoadGraph *graph)
453 {
454     std::fill(graph->data_block.begin(), graph->data_block.end(), -1.0);
455 }
456 
457 static void
get_load(LoadGraph * graph)458 get_load (LoadGraph *graph)
459 {
460     guint i;
461     glibtop_cpu cpu;
462 
463     glibtop_get_cpu (&cpu);
464 
465     auto NOW  = [&]() -> guint64 (&)[GLIBTOP_NCPU][N_CPU_STATES] { return graph->cpu.times[graph->cpu.now]; };
466     auto LAST = [&]() -> guint64 (&)[GLIBTOP_NCPU][N_CPU_STATES] { return graph->cpu.times[graph->cpu.now ^ 1]; };
467 
468     if (graph->n == 1) {
469         NOW()[0][CPU_TOTAL] = cpu.total;
470         NOW()[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys;
471     } else {
472         for (i = 0; i < graph->n; i++) {
473             NOW()[i][CPU_TOTAL] = cpu.xcpu_total[i];
474             NOW()[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i]
475                 + cpu.xcpu_sys[i];
476         }
477     }
478 
479     // on the first call, LAST is 0
480     // which means data is set to the average load since boot
481     // that value has no meaning, we just want all the
482     // graphs to be aligned, so the CPU graph needs to start
483     // immediately
484     bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
485 
486     for (i = 0; i < graph->n; i++) {
487         float load;
488         float total, used;
489         gchar *text;
490 
491         total = NOW()[i][CPU_TOTAL] - LAST()[i][CPU_TOTAL];
492         used  = NOW()[i][CPU_USED]  - LAST()[i][CPU_USED];
493 
494         load = used / MAX(total, 1.0f);
495         graph->data[0][i] = load;
496         if (drawStacked) {
497             graph->data[0][i] /= graph->n;
498             if (i > 0) {
499                 graph->data[0][i] += graph->data[0][i-1];
500             }
501         }
502 
503         /* Update label */
504         // Translators: CPU usage percentage label: 95.7%
505         text = g_strdup_printf(_("%.1f%%"), load * 100.0f);
506         gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text);
507         g_free(text);
508     }
509 
510     graph->cpu.now ^= 1;
511 }
512 
513 
514 namespace
515 {
516 
set_memory_label_and_picker(GtkLabel * label,GsmColorButton * picker,guint64 used,guint64 cached,guint64 total,double percent)517     void set_memory_label_and_picker(GtkLabel* label, GsmColorButton* picker,
518                                      guint64 used, guint64 cached, guint64 total, double percent)
519     {
520         char* used_text;
521         char* cached_text;
522         char* cached_label;
523         char* total_text;
524         char* text;
525 
526         used_text = format_byte_size(used, GsmApplication::get()->config.resources_memory_in_iec);
527         cached_text = format_byte_size(cached, GsmApplication::get()->config.resources_memory_in_iec);
528         total_text = format_byte_size(total, GsmApplication::get()->config.resources_memory_in_iec);
529         if (total == 0) {
530             text = g_strdup(_("not available"));
531         } else {
532             // xgettext: "540MiB (53 %) of 1.0 GiB" or "540MB (53 %) of 1.0 GB"
533             text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text);
534 
535             if (cached != 0) {
536                 // xgettext: Used cache string, e.g.: "Cache 2.4GiB" or "Cache 2.4GB"
537                 cached_label = g_strdup_printf(_("Cache %s"), cached_text);
538                 text = g_strdup_printf("%s\n%s", text, cached_label);
539                 g_free (cached_label);
540             }
541         }
542         gtk_label_set_text(label, text);
543         g_free(used_text);
544         g_free(cached_text);
545         g_free(total_text);
546         g_free(text);
547 
548         if (picker)
549             gsm_color_button_set_fraction(picker, percent);
550     }
551 }
552 
553 static void
get_memory(LoadGraph * graph)554 get_memory (LoadGraph *graph)
555 {
556     float mempercent, swappercent;
557 
558     glibtop_mem mem;
559     glibtop_swap swap;
560 
561     glibtop_get_mem (&mem);
562     glibtop_get_swap (&swap);
563 
564     /* There's no swap on LiveCD : 0.0f is better than NaN :) */
565     swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f);
566     mempercent  = (float)mem.user  / (float)mem.total;
567     set_memory_label_and_picker(GTK_LABEL(graph->labels.memory),
568                                 GSM_COLOR_BUTTON(graph->mem_color_picker),
569                                 mem.user, mem.cached, mem.total, mempercent);
570 
571     set_memory_label_and_picker(GTK_LABEL(graph->labels.swap),
572                                 GSM_COLOR_BUTTON(graph->swap_color_picker),
573                                 swap.used, 0, swap.total, swappercent);
574 
575     gtk_widget_set_sensitive (GTK_WIDGET (graph->swap_color_picker), swap.total > 0);
576 
577     graph->data[0][0] = graph->translate_to_log_partial_if_needed(mempercent);
578     graph->data[0][1] = swap.total>0 ? graph->translate_to_log_partial_if_needed(swappercent) : -1.0;
579 }
580 
581 /* Nice Numbers for Graph Labels after Paul Heckbert
582    nicenum: find a "nice" number approximately equal to x.
583    Round the number if round=1, take ceiling if round=0    */
584 
585 static double
nicenum(double x,int round)586 nicenum (double x, int round)
587 {
588     int expv;                /* exponent of x */
589     double f;                /* fractional part of x */
590     double nf;                /* nice, rounded fraction */
591 
592     expv = floor(log10(x));
593     f = x/pow(10.0, expv);        /* between 1 and 10 */
594     if (round) {
595         if (f < 1.5)
596             nf = 1.0;
597         else if (f < 3.0)
598             nf = 2.0;
599         else if (f < 7.0)
600             nf = 5.0;
601         else
602             nf = 10.0;
603     } else {
604         if (f <= 1.0)
605             nf = 1.0;
606         else if (f <= 2.0)
607             nf = 2.0;
608         else if (f <= 5.0)
609             nf = 5.0;
610         else
611             nf = 10.0;
612     }
613     return nf * pow(10.0, expv);
614 }
615 
616 static void
net_scale(LoadGraph * graph,guint64 din,guint64 dout)617 net_scale (LoadGraph *graph, guint64 din, guint64 dout)
618 {
619     graph->data[0][0] = 1.0f * din / graph->net.max;
620     graph->data[0][1] = 1.0f * dout / graph->net.max;
621 
622     guint64 dmax = std::max(din, dout);
623     if (graph->latest == 0) {
624         graph->net.values[graph->num_points - 1] = dmax;
625     } else {
626         graph->net.values[graph->latest - 1] = dmax;
627     }
628 
629     guint64 new_max;
630     // both way, new_max is the greatest value
631     if (dmax >= graph->net.max)
632         new_max = dmax;
633     else
634         new_max = *std::max_element(&graph->net.values[0],
635                                     &graph->net.values[graph->num_points - 1]);
636 
637     //
638     // Round network maximum
639     //
640 
641     const guint64 bak_max(new_max);
642 
643     if (GsmApplication::get()->config.network_in_bits) {
644         // nice number is for the ticks
645         unsigned ticks = graph->num_bars();
646 
647         // gets messy at low values due to division by 8
648         guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) );
649 
650         // our tick size leads to max
651         double d = nicenum(bit_max/ticks, 0);
652         bit_max = ticks * d;
653         new_max = bit_max / 8;
654 
655         procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f"
656                       ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT,
657                       bak_max*8, ticks, d, bit_max, new_max );
658     } else {
659         // round up to get some extra space
660         // yes, it can overflow
661         new_max = 1.1 * new_max;
662         // make sure max is not 0 to avoid / 0
663         // default to 1 KiB
664         new_max = std::max(new_max, G_GUINT64_CONSTANT(1024));
665 
666         // decompose new_max = coef10 * 2**(base10 * 10)
667         // where coef10 and base10 are integers and coef10 < 2**10
668         //
669         // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10)
670         //      where base10 = 1, coef10 = 101, pow2 = 16
671 
672         guint64 pow2 = std::floor(log(new_max) / log (2));
673         guint64 base10 = pow2 / 10.0;
674         guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10)));
675         g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10))));
676 
677         // then decompose coef10 = x * 10**factor10
678         // where factor10 is integer and x < 10
679         // so we new_max has only 1 significant digit
680 
681         guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10)));
682         coef10 = std::ceil(coef10 / double(factor10)) * factor10;
683 
684         new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10));
685         procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT
686                       "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT,
687                       bak_max, new_max, pow2, coef10);
688     }
689 
690     if (bak_max > new_max) {
691         procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT
692                       " new=%" G_GUINT64_FORMAT,
693                       bak_max, new_max);
694         new_max = bak_max;
695     }
696 
697     // if max is the same or has decreased but not so much, don't
698     // do anything to avoid rescaling
699     if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max)
700         return;
701 
702     const double scale = 1.0f * graph->net.max / new_max;
703 
704     for (size_t i = 0; i < graph->num_points; i++) {
705         if (graph->data[i][0] >= 0.0f) {
706             graph->data[i][0] *= scale;
707             graph->data[i][1] *= scale;
708         }
709     }
710 
711     procman_debug("rescale dmax = %" G_GUINT64_FORMAT
712                   " max = %" G_GUINT64_FORMAT
713                   " new_max = %" G_GUINT64_FORMAT,
714                   dmax, graph->net.max, new_max);
715 
716     graph->net.max = new_max;
717 
718     // force the graph background to be redrawn now that scale has changed
719     graph->clear_background();
720 }
721 
722 static void
get_net(LoadGraph * graph)723 get_net (LoadGraph *graph)
724 {
725     glibtop_netlist netlist;
726     char **ifnames;
727     guint32 i;
728     guint64 in = 0, out = 0;
729     guint64 time;
730     guint64 din, dout;
731     gboolean first = true;
732     ifnames = glibtop_get_netlist(&netlist);
733 
734     for (i = 0; i < netlist.number; ++i)
735     {
736         glibtop_netload netload;
737         glibtop_get_netload (&netload, ifnames[i]);
738 
739         if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK))
740             continue;
741 
742         /* Skip interfaces without any IPv4/IPv6 address (or
743            those with only a LINK ipv6 addr) However we need to
744            be able to exclude these while still keeping the
745            value so when they get online (with NetworkManager
746            for example) we don't get a suddent peak.  Once we're
747            able to get this, ignoring down interfaces will be
748            possible too.  */
749         if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6)
750                  and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK)
751             and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS)))
752             continue;
753 
754         /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP)
755            to avoid spikes when they are brought up */
756 
757         in  += netload.bytes_in;
758         out += netload.bytes_out;
759     }
760 
761     g_strfreev(ifnames);
762 
763     time = g_get_monotonic_time ();
764 
765     if (in >= graph->net.last_in && out >= graph->net.last_out && graph->net.time != 0) {
766         float dtime;
767         dtime = ((double) (time - graph->net.time)) / G_USEC_PER_SEC;
768         din   = static_cast<guint64>((in  - graph->net.last_in)  / dtime);
769         dout  = static_cast<guint64>((out - graph->net.last_out) / dtime);
770     } else {
771         /* Don't calc anything if new data is less than old (interface
772            removed, counters reset, ...) or if it is the first time */
773         din  = 0;
774         dout = 0;
775     }
776 
777     first = first && (graph->net.time==0);
778     graph->net.last_in  = in;
779     graph->net.last_out = out;
780     graph->net.time     = time;
781 
782     if (!first)
783         net_scale(graph, din, dout);
784 
785     gtk_label_set_text (GTK_LABEL (graph->labels.net_in), procman::format_network_rate(din).c_str());
786     gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), procman::format_network(in).c_str());
787 
788     gtk_label_set_text (GTK_LABEL (graph->labels.net_out), procman::format_network_rate(dout).c_str());
789     gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), procman::format_network(out).c_str());
790 }
791 
792 
793 
794 void
load_graph_update_data(LoadGraph * graph)795 load_graph_update_data (LoadGraph *graph)
796 {
797     // Rotate data one element down.
798     std::rotate(graph->data.begin(),
799                 graph->data.end() - 1,
800                 graph->data.end());
801 
802     // Update rotation counter.
803     graph->latest = (graph->latest + 1) % graph->num_points;
804 
805     // Replace the 0th element
806     switch (graph->type) {
807         case LOAD_GRAPH_CPU:
808             get_load(graph);
809             break;
810         case LOAD_GRAPH_MEM:
811             get_memory(graph);
812             break;
813         case LOAD_GRAPH_NET:
814             get_net(graph);
815             break;
816         default:
817             g_assert_not_reached();
818     }
819 }
820 
821 
822 
823 /* Updates the load graph when the timeout expires */
824 static gboolean
load_graph_update(gpointer user_data)825 load_graph_update (gpointer user_data)
826 {
827     LoadGraph * const graph = static_cast<LoadGraph*>(user_data);
828 
829     if (graph->render_counter == graph->frames_per_unit - 1)
830         load_graph_update_data(graph);
831 
832     if (graph->draw)
833         load_graph_queue_draw (graph);
834 
835     graph->render_counter++;
836 
837     if (graph->render_counter >= graph->frames_per_unit)
838         graph->render_counter = 0;
839 
840     return TRUE;
841 }
842 
843 
844 
~LoadGraph()845 LoadGraph::~LoadGraph()
846 {
847     load_graph_stop(this);
848 
849     if (timer_index)
850         g_source_remove(timer_index);
851 
852     clear_background();
853 }
854 
855 
856 
857 static gboolean
load_graph_destroy(GtkWidget * widget,gpointer data_ptr)858 load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
859 {
860     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
861 
862     delete graph;
863 
864     return FALSE;
865 }
866 
867 
LoadGraph(guint type)868 LoadGraph::LoadGraph(guint type)
869     : fontsize(8.0),
870       rmargin(6 * fontsize),
871       indent(18.0),
872       n(0),
873       type(type),
874       speed(0),
875       num_points(0),
876       latest(0),
877       draw_width(0),
878       draw_height(0),
879       render_counter(0),
880       frames_per_unit(10), // this will be changed but needs initialising
881       graph_dely(0),
882       real_draw_height(0),
883       graph_delx(0.0),
884       graph_buffer_offset(0),
885       colors(),
886       data_block(),
887       data(),
888       main_widget(NULL),
889       disp(NULL),
890       background(NULL),
891       timer_index(0),
892       draw(FALSE),
893       labels(),
894       mem_color_picker(NULL),
895       swap_color_picker(NULL),
896       font_settings(Gio::Settings::create (FONT_SETTINGS_SCHEMA)),
897       cpu(),
898       net()
899 {
900     LoadGraph * const graph = this;
901     font_settings->signal_changed(FONT_SETTING_SCALING).connect([this](const Glib::ustring&) { load_graph_rescale (this); } );
902     // FIXME:
903     // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS);
904     // knock FRAMES down to 5 until cairo gets faster
905 
906     switch (type) {
907         case LOAD_GRAPH_CPU:
908             cpu = CPU {};
909             n = GsmApplication::get()->config.num_cpus;
910 
911             for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i)
912                 labels.cpu[i] = make_tnum_label ();
913 
914             break;
915 
916         case LOAD_GRAPH_MEM:
917             n = 2;
918             labels.memory = make_tnum_label ();
919             gtk_widget_set_valign (GTK_WIDGET (labels.memory), GTK_ALIGN_CENTER);
920             gtk_widget_set_halign (GTK_WIDGET (labels.memory), GTK_ALIGN_START);
921             gtk_widget_show (GTK_WIDGET (labels.memory));
922             labels.swap = make_tnum_label ();
923             gtk_widget_set_valign (GTK_WIDGET (labels.swap), GTK_ALIGN_CENTER);
924             gtk_widget_set_halign (GTK_WIDGET (labels.swap), GTK_ALIGN_START);
925             gtk_widget_show (GTK_WIDGET (labels.swap));
926             break;
927 
928         case LOAD_GRAPH_NET:
929             net = NET {};
930             n = 2;
931             net.max = 1;
932             labels.net_in = make_tnum_label ();
933             gtk_label_set_width_chars(labels.net_in, 10);
934             gtk_widget_set_valign (GTK_WIDGET (labels.net_in), GTK_ALIGN_CENTER);
935             gtk_widget_set_halign (GTK_WIDGET (labels.net_in), GTK_ALIGN_END);
936             gtk_widget_show (GTK_WIDGET (labels.net_in));
937 
938             labels.net_in_total = make_tnum_label ();
939             gtk_widget_set_valign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_CENTER);
940             gtk_widget_set_halign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_END);
941             gtk_label_set_width_chars(labels.net_in_total, 10);
942             gtk_widget_show (GTK_WIDGET (labels.net_in_total));
943 
944             labels.net_out = make_tnum_label ();
945             gtk_widget_set_valign (GTK_WIDGET (labels.net_out), GTK_ALIGN_CENTER);
946             gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
947             gtk_label_set_width_chars(labels.net_out, 10);
948             gtk_widget_show (GTK_WIDGET (labels.net_out));
949 
950             labels.net_out_total = make_tnum_label ();
951             gtk_widget_set_valign (GTK_WIDGET (labels.net_out_total), GTK_ALIGN_CENTER);
952             gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
953             gtk_label_set_width_chars(labels.net_out_total, 10);
954             gtk_widget_show (GTK_WIDGET (labels.net_out_total));
955 
956             break;
957     }
958 
959     speed = GsmApplication::get()->config.graph_update_interval;
960 
961     num_points = GsmApplication::get()->config.graph_data_points + 2;
962 
963     colors.resize(n);
964 
965     switch (type) {
966         case LOAD_GRAPH_CPU:
967             memcpy(&colors[0], GsmApplication::get()->config.cpu_color,
968                    n * sizeof colors[0]);
969             break;
970         case LOAD_GRAPH_MEM:
971             colors[0] = GsmApplication::get()->config.mem_color;
972             colors[1] = GsmApplication::get()->config.swap_color;
973             mem_color_picker = gsm_color_button_new (&colors[0],
974                                                         GSMCP_TYPE_PIE);
975             swap_color_picker = gsm_color_button_new (&colors[1],
976                                                          GSMCP_TYPE_PIE);
977             break;
978         case LOAD_GRAPH_NET:
979             net.values = std::vector<unsigned>(num_points);
980             colors[0] = GsmApplication::get()->config.net_in_color;
981             colors[1] = GsmApplication::get()->config.net_out_color;
982             break;
983     }
984 
985     timer_index = 0;
986     render_counter = (frames_per_unit - 1);
987     draw = FALSE;
988 
989     main_widget = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
990     gtk_widget_set_size_request(GTK_WIDGET (main_widget), -1, LoadGraph::GRAPH_MIN_HEIGHT);
991     gtk_widget_show (GTK_WIDGET (main_widget));
992 
993     disp = GTK_DRAWING_AREA (gtk_drawing_area_new ());
994     gtk_widget_show (GTK_WIDGET (disp));
995     g_signal_connect (G_OBJECT (disp), "draw",
996                       G_CALLBACK (load_graph_draw), graph);
997     g_signal_connect (G_OBJECT(disp), "configure_event",
998                       G_CALLBACK (load_graph_configure), graph);
999     g_signal_connect (G_OBJECT(disp), "destroy",
1000                       G_CALLBACK (load_graph_destroy), graph);
1001     g_signal_connect (G_OBJECT(disp), "state-flags-changed",
1002                       G_CALLBACK (load_graph_state_changed), graph);
1003     g_signal_connect (G_OBJECT(disp), "style-updated",
1004                       G_CALLBACK (load_graph_style_updated), graph);
1005 
1006     gtk_widget_set_events (GTK_WIDGET (disp), GDK_EXPOSURE_MASK);
1007 
1008     gtk_box_pack_start (main_widget, GTK_WIDGET (disp), TRUE, TRUE, 0);
1009 
1010     data = std::vector<double*>(num_points);
1011     /* Allocate data in a contiguous block */
1012     data_block = std::vector<double>(n * num_points, -1.0);
1013 
1014     for (guint i = 0; i < num_points; ++i)
1015         data[i] = &data_block[0] + i * n;
1016 
1017     gtk_widget_show_all (GTK_WIDGET (main_widget));
1018 }
1019 
1020 void
load_graph_start(LoadGraph * graph)1021 load_graph_start (LoadGraph *graph)
1022 {
1023     if (!graph->timer_index) {
1024         // Update the data two times so the graph
1025         // doesn't wait one cycle to start drawing.
1026         load_graph_update_data(graph);
1027         load_graph_update(graph);
1028 
1029         graph->timer_index = g_timeout_add (graph->speed,
1030                                             load_graph_update,
1031                                             graph);
1032     }
1033 
1034     graph->draw = TRUE;
1035 }
1036 
1037 void
load_graph_stop(LoadGraph * graph)1038 load_graph_stop (LoadGraph *graph)
1039 {
1040     /* don't draw anymore, but continue to poll */
1041     graph->draw = FALSE;
1042 }
1043 
1044 void
load_graph_change_speed(LoadGraph * graph,guint new_speed)1045 load_graph_change_speed (LoadGraph *graph,
1046                          guint new_speed)
1047 {
1048     if (graph->speed == new_speed)
1049         return;
1050 
1051     graph->speed = new_speed;
1052 
1053     if (graph->timer_index) {
1054         g_source_remove (graph->timer_index);
1055         graph->timer_index = g_timeout_add (graph->speed,
1056                                             load_graph_update,
1057                                             graph);
1058     }
1059 
1060     graph->clear_background();
1061 }
1062 
1063 void
load_graph_change_num_points(LoadGraph * graph,guint new_num_points)1064 load_graph_change_num_points(LoadGraph *graph,
1065                              guint new_num_points)
1066 {
1067     // Don't do anything if the value didn't change.
1068     if (graph->num_points == new_num_points)
1069         return;
1070 
1071     // Sort the values in the data_block vector in the order they were accessed in by the pointers in data.
1072     std::rotate(graph->data_block.begin(),
1073                 graph->data_block.begin() + (graph->num_points - graph->latest) * graph->n,
1074                 graph->data_block.end());
1075 
1076     // Reset rotation counter.
1077     graph->latest = 0;
1078 
1079     // Resize the vectors to the new amount of data points.
1080     // Fill the new values with -1.
1081     graph->data.resize(new_num_points);
1082     graph->data_block.resize(graph->n * new_num_points, -1.0);
1083     if (graph->type == LOAD_GRAPH_NET) {
1084         graph->net.values.resize(new_num_points);
1085     }
1086 
1087     // Replace the pointers in data, to match the new data_block values.
1088     for (guint i = 0; i < new_num_points; ++i) {
1089         graph->data[i] = &graph->data_block[0] + i * graph->n;
1090     }
1091 
1092     // Set the actual number of data points to be used by the graph.
1093     graph->num_points = new_num_points;
1094 
1095     // Force the scale to be redrawn.
1096     graph->clear_background();
1097 }
1098 
1099 
1100 LoadGraphLabels*
load_graph_get_labels(LoadGraph * graph)1101 load_graph_get_labels (LoadGraph *graph)
1102 {
1103     return &graph->labels;
1104 }
1105 
1106 GtkBox*
load_graph_get_widget(LoadGraph * graph)1107 load_graph_get_widget (LoadGraph *graph)
1108 {
1109     return graph->main_widget;
1110 }
1111 
1112 GsmColorButton*
load_graph_get_mem_color_picker(LoadGraph * graph)1113 load_graph_get_mem_color_picker(LoadGraph *graph)
1114 {
1115     return graph->mem_color_picker;
1116 }
1117 
1118 GsmColorButton*
load_graph_get_swap_color_picker(LoadGraph * graph)1119 load_graph_get_swap_color_picker(LoadGraph *graph)
1120 {
1121     return graph->swap_color_picker;
1122 }
1123