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