1 #include <config.h>
2 
3 #include <gdkmm/pixbuf.h>
4 
5 #include <stdio.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8 #include <signal.h>
9 #include <dirent.h>
10 #include <string.h>
11 #include <time.h>
12 
13 #include <glib/gi18n.h>
14 
15 #include <glibtop.h>
16 #include <glibtop/cpu.h>
17 #include <glibtop/mem.h>
18 #include <glibtop/swap.h>
19 #include <glibtop/netload.h>
20 #include <glibtop/netlist.h>
21 #include <math.h>
22 
23 #include <algorithm>
24 
25 #include "procman.h"
26 #include "load-graph.h"
27 #include "util.h"
28 #include "gsm_color_button.h"
29 
clear_background()30 void LoadGraph::clear_background()
31 {
32     if (background) {
33         cairo_pattern_destroy (background);
34         this->background = NULL;
35     }
36 }
37 
38 
num_bars() const39 unsigned LoadGraph::num_bars() const
40 {
41     unsigned n;
42 
43     // keep 100 % num_bars == 0
44     switch (static_cast<int>(this->draw_height / (fontsize + 14)))
45     {
46     case 0:
47     case 1:
48         n = 1;
49         break;
50     case 2:
51     case 3:
52         n = 2;
53         break;
54     case 4:
55         n = 4;
56         break;
57     default:
58         n = 5;
59     }
60 
61     return n;
62 }
63 
64 
65 
66 #define FRAME_WIDTH 4
draw_background(LoadGraph * graph)67 static void draw_background(LoadGraph *graph) {
68     GtkAllocation allocation;
69     cairo_t *cr;
70     guint i;
71     unsigned num_bars;
72     g_autofree gchar *caption;
73     PangoLayout* layout;
74     PangoFontDescription* font_desc;
75     PangoRectangle extents;
76     cairo_surface_t *surface;
77     GdkRGBA fg, bg;
78 
79     num_bars = graph->num_bars();
80     graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
81     graph->real_draw_height = graph->graph_dely * num_bars;
82     graph->graph_delx = (graph->draw_width - 2.0 - graph->rmargin - graph->indent) / (LoadGraph::NUM_POINTS - 3);
83     graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH ;
84 
85     gtk_widget_get_allocation (graph->disp, &allocation);
86     surface = gdk_window_create_similar_surface (gtk_widget_get_window (graph->disp), CAIRO_CONTENT_COLOR_ALPHA, allocation.width, allocation.height);
87     cr = cairo_create (surface);
88 
89     GtkStyleContext *context = gtk_widget_get_style_context (ProcData::get_instance()->notebook);
90     gtk_style_context_save (context);
91     gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
92     gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg);
93     gtk_style_context_get_color (context, gtk_style_context_get_state (context), &fg);
94     gtk_style_context_restore (context);
95 
96     // set the background color
97     gdk_cairo_set_source_rgba (cr, &bg);
98     cairo_paint (cr);
99 
100     layout = pango_cairo_create_layout (cr);
101     gtk_style_context_save (context);
102     gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
103     gtk_style_context_get (context,
104                            gtk_style_context_get_state (context),
105                            GTK_STYLE_PROPERTY_FONT,
106                            &font_desc, NULL);
107     gtk_style_context_restore (context);
108     pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE);
109     pango_layout_set_font_description (layout, font_desc);
110     pango_font_description_free (font_desc);
111 
112     /* draw frame */
113     cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);
114 
115     /* Draw background rectangle */
116     cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
117     cairo_rectangle (cr, graph->rmargin + graph->indent, 0,
118                      graph->draw_width - graph->rmargin - graph->indent, graph->real_draw_height);
119     cairo_fill(cr);
120 
121     cairo_set_line_width (cr, 1.0);
122     cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0);
123 
124     bool network_in_bits = ProcData::get_instance()->config.network_in_bits;
125     for (i = 0; i <= num_bars; ++i) {
126         double y;
127 
128         if (i == 0)
129             y = 0.5 + graph->fontsize / 2.0;
130         else if (i == num_bars)
131             y = i * graph->graph_dely + 0.5;
132         else
133             y = i * graph->graph_dely + graph->fontsize / 2.0;
134 
135         gdk_cairo_set_source_rgba (cr, &fg);
136         if (graph->type == LOAD_GRAPH_NET) {
137             // operation orders matters so it's 0 if i == num_bars
138             guint64 rate = graph->net.max - (i * graph->net.max / num_bars);
139             caption = g_format_size_full (network_in_bits ? rate*8 : rate, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
140             pango_layout_set_text (layout, caption, -1);
141             pango_layout_get_extents (layout, NULL, &extents);
142             cairo_move_to (cr, graph->indent - 1.0 * extents.width / PANGO_SCALE + 20, y - 1.0 * extents.height / PANGO_SCALE / 2);
143             pango_cairo_show_layout (cr, layout);
144         } else {
145             // operation orders matters so it's 0 if i == num_bars
146             caption = g_strdup_printf("%d%%", 100 - i * (100 / num_bars));
147             pango_layout_set_text (layout, caption, -1);
148             pango_layout_get_extents (layout, NULL, &extents);
149             cairo_move_to (cr, graph->indent - 1.0 * extents.width / PANGO_SCALE + 20, y - 1.0 * extents.height / PANGO_SCALE / 2);
150             pango_cairo_show_layout (cr, layout);
151         }
152 
153         if (i==0 || i==num_bars)
154           cairo_set_source_rgba (cr, 0.70, 0.71, 0.70, 1.0);
155         else
156           cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0);
157         cairo_move_to (cr, graph->rmargin + graph->indent - 3, i * graph->graph_dely + 0.5);
158         cairo_line_to (cr, graph->draw_width - 0.5, i * graph->graph_dely + 0.5);
159         cairo_stroke (cr);
160     }
161 
162     const unsigned total_seconds = graph->speed * (LoadGraph::NUM_POINTS - 2) / 1000;
163 
164     for (unsigned int i = 0; i < 7; i++) {
165         double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6;
166         if (i==0 || i==6)
167           cairo_set_source_rgba (cr, 0.70, 0.71, 0.70, 1.0);
168         else
169           cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0);
170         cairo_move_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, 0.5);
171         cairo_line_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, graph->real_draw_height + 4.5);
172         cairo_stroke(cr);
173         unsigned seconds = total_seconds - i * total_seconds / 6;
174         const char* format;
175         if (i == 0)
176             format = dngettext(GETTEXT_PACKAGE, "%u second", "%u seconds", seconds);
177         else
178             format = "%u";
179         caption = g_strdup_printf(format, seconds);
180         pango_layout_set_text (layout, caption, -1);
181         pango_layout_get_extents (layout, NULL, &extents);
182         cairo_move_to (cr, ((ceil(x) + 0.5) + graph->rmargin + graph->indent) - (1.0 * extents.width / PANGO_SCALE/2), graph->draw_height - 1.0 * extents.height / PANGO_SCALE);
183         gdk_cairo_set_source_rgba (cr, &fg);
184         pango_cairo_show_layout (cr, layout);
185     }
186     g_object_unref(layout);
187     cairo_stroke (cr);
188     cairo_destroy (cr);
189     graph->background = cairo_pattern_create_for_surface (surface);
190     cairo_surface_destroy (surface);
191 }
192 
193 /* Redraws the backing buffer for the load graph and updates the window */
194 void
load_graph_queue_draw(LoadGraph * graph)195 load_graph_queue_draw (LoadGraph *graph)
196 {
197     /* repaint */
198     gtk_widget_queue_draw (graph->disp);
199 }
200 
201 static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)
202 
203 static gboolean
load_graph_configure(GtkWidget * widget,GdkEventConfigure * event,gpointer data_ptr)204 load_graph_configure (GtkWidget *widget,
205                       GdkEventConfigure *event,
206                       gpointer data_ptr)
207 {
208     GtkAllocation allocation;
209     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
210 
211     gtk_widget_get_allocation (widget, &allocation);
212     graph->draw_width = allocation.width - 2 * FRAME_WIDTH;
213     graph->draw_height = allocation.height - 2 * FRAME_WIDTH;
214 
215     graph->clear_background();
216 
217     load_graph_queue_draw (graph);
218 
219     return TRUE;
220 }
221 
load_graph_draw(GtkWidget * widget,cairo_t * context,gpointer data_ptr)222 static gboolean load_graph_draw (GtkWidget *widget, cairo_t *context, gpointer data_ptr)
223 {
224     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
225     GdkWindow *window;
226 
227     guint i, j;
228     gdouble sample_width, x_offset;
229 
230     window = gtk_widget_get_window (graph->disp);
231 
232     /* Number of pixels wide for one graph point */
233     sample_width = (float)(graph->draw_width - graph->rmargin - graph->indent) / (float)LoadGraph::NUM_POINTS;
234     /* General offset */
235     x_offset = graph->draw_width - graph->rmargin + (sample_width*2);
236 
237     /* Subframe offset */
238     x_offset += graph->rmargin - ((sample_width / graph->frames_per_unit) * graph->render_counter);
239 
240     /* draw the graph */
241     cairo_t* cr;
242 
243     cr = gdk_cairo_create (window);
244 
245     if (graph->background == NULL) {
246         draw_background(graph);
247     }
248     cairo_set_source (cr, graph->background);
249     cairo_paint (cr);
250 
251     cairo_set_line_width (cr, 1);
252     cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
253     cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
254     cairo_rectangle (cr, graph->rmargin + graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1,
255              graph->draw_width - graph->rmargin - graph->indent - 1, graph->real_draw_height + FRAME_WIDTH - 1);
256     cairo_clip(cr);
257 
258     for (j = 0; j < graph->n; ++j) {
259         cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height);
260         gdk_cairo_set_source_rgba (cr, &(graph->colors [j]));
261 
262         for (i = 1; i < LoadGraph::NUM_POINTS; ++i) {
263             if (graph->data[i][j] == -1.0f)
264                 continue;
265             cairo_curve_to (cr,
266                        x_offset - ((i - 0.5f) * graph->graph_delx),
267                        (1.0f - graph->data[i-1][j]) * graph->real_draw_height + 3.5f,
268                        x_offset - ((i - 0.5f) * graph->graph_delx),
269                        (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f,
270                        x_offset - (i * graph->graph_delx),
271                        (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f);
272         }
273         cairo_stroke (cr);
274 
275     }
276 
277     cairo_destroy (cr);
278 
279     return TRUE;
280 }
281 
282 static void
get_load(LoadGraph * graph)283 get_load (LoadGraph *graph)
284 {
285     guint i;
286     glibtop_cpu cpu;
287 
288     glibtop_get_cpu (&cpu);
289 
290 #undef NOW
291 #undef LAST
292 #define NOW  (graph->cpu.times[graph->cpu.now])
293 #define LAST (graph->cpu.times[graph->cpu.now ^ 1])
294 
295     if (graph->n == 1) {
296         NOW[0][CPU_TOTAL] = cpu.total;
297         NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys;
298     } else {
299         for (i = 0; i < graph->n; i++) {
300             NOW[i][CPU_TOTAL] = cpu.xcpu_total[i];
301             NOW[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i]
302                 + cpu.xcpu_sys[i];
303         }
304     }
305 
306     // on the first call, LAST is 0
307     // which means data is set to the average load since boot
308     // that value has no meaning, we just want all the
309     // graphs to be aligned, so the CPU graph needs to start
310     // immediately
311 
312     for (i = 0; i < graph->n; i++) {
313         float load;
314         float total, used;
315         gchar *text;
316 
317         total = NOW[i][CPU_TOTAL] - LAST[i][CPU_TOTAL];
318         used  = NOW[i][CPU_USED]  - LAST[i][CPU_USED];
319 
320         load = used / MAX(total, 1.0f);
321         graph->data[0][i] = load;
322 
323         /* Update label */
324         text = g_strdup_printf("%.1f%%", load * 100.0f);
325         gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text);
326         g_free(text);
327     }
328 
329     graph->cpu.now ^= 1;
330 
331 #undef NOW
332 #undef LAST
333 }
334 
335 
336 namespace
337 {
338 
set_memory_label_and_picker(GtkLabel * label,GSMColorButton * picker,guint64 used,guint64 total,double percent)339     void set_memory_label_and_picker(GtkLabel* label, GSMColorButton* picker,
340                                    guint64 used, guint64 total, double percent)
341     {
342         char* used_text;
343         char* total_text;
344         char* text;
345 
346         used_text = g_format_size_full(used, G_FORMAT_SIZE_IEC_UNITS);
347         total_text = g_format_size_full(total, G_FORMAT_SIZE_IEC_UNITS);
348         if (total == 0) {
349             text = g_strdup(_("not available"));
350         } else {
351             // xgettext: 540MiB (53 %) of 1.0 GiB
352             text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text);
353         }
354         gtk_label_set_text(label, text);
355         g_free(used_text);
356         g_free(total_text);
357         g_free(text);
358 
359         if (picker)
360             gsm_color_button_set_fraction(picker, percent);
361     }
362 }
363 
364 static void
get_memory(LoadGraph * graph)365 get_memory (LoadGraph *graph)
366 {
367     float mempercent, swappercent;
368 
369     glibtop_mem mem;
370     glibtop_swap swap;
371 
372     glibtop_get_mem (&mem);
373     glibtop_get_swap (&swap);
374 
375     /* There's no swap on LiveCD : 0.0f is better than NaN :) */
376     swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f);
377     mempercent  = (float)mem.user  / (float)mem.total;
378 
379     set_memory_label_and_picker(GTK_LABEL(graph->labels.memory),
380                                 GSM_COLOR_BUTTON(graph->mem_color_picker),
381                                 mem.user, mem.total, mempercent);
382 
383     set_memory_label_and_picker(GTK_LABEL(graph->labels.swap),
384                                 GSM_COLOR_BUTTON(graph->swap_color_picker),
385                                 swap.used, swap.total, swappercent);
386 
387     graph->data[0][0] = mempercent;
388     graph->data[0][1] = swappercent;
389 }
390 
391 /* Nice Numbers for Graph Labels after Paul Heckbert
392    nicenum: find a "nice" number approximately equal to x.
393    Round the number if round=1, take ceiling if round=0    */
394 
395 static double
nicenum(double x,int round)396 nicenum (double x, int round)
397 {
398     int expv;				/* exponent of x */
399     double f;				/* fractional part of x */
400     double nf;				/* nice, rounded fraction */
401 
402     expv = floor( log10(x) );
403     f = x/pow( 10.0, expv );		/* between 1 and 10 */
404     if (round) {
405         if ( f < 1.5 ) nf = 1.0;
406         else if ( f < 3.0 ) nf = 2.0;
407         else if ( f < 7.0 ) nf = 5.0;
408         else nf = 10.0;
409     } else {
410         if ( f <= 1.0 ) nf = 1.0;
411         else if ( f <= 2.0 ) nf = 2.0;
412         else if ( f <= 5.0 ) nf = 5.0;
413         else nf = 10.0;
414     }
415     return nf * pow(10.0, expv);
416 }
417 
418 static void
net_scale(LoadGraph * graph,guint64 din,guint64 dout)419 net_scale (LoadGraph *graph, guint64 din, guint64 dout)
420 {
421     graph->data[0][0] = 1.0f * din / graph->net.max;
422     graph->data[0][1] = 1.0f * dout / graph->net.max;
423 
424     guint64 dmax = std::max(din, dout);
425     graph->net.values[graph->net.cur] = dmax;
426     graph->net.cur = (graph->net.cur + 1) % LoadGraph::NUM_POINTS;
427 
428     guint64 new_max;
429     // both way, new_max is the greatest value
430     if (dmax >= graph->net.max)
431         new_max = dmax;
432     else
433         new_max = *std::max_element(&graph->net.values[0],
434                                     &graph->net.values[LoadGraph::NUM_POINTS]);
435 
436     //
437     // Round network maximum
438     //
439 
440     const guint64 bak_max(new_max);
441 
442     if (ProcData::get_instance()->config.network_in_bits) {
443         // nice number is for the ticks
444         unsigned ticks = graph->num_bars();
445 
446         // gets messy at low values due to division by 8
447         guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) );
448 
449         // our tick size leads to max
450         double d = nicenum(bit_max/ticks, 0);
451         bit_max = ticks * d;
452         new_max = bit_max / 8;
453 
454         procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f"
455                       ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT,
456                       bak_max*8, ticks, d, bit_max, new_max );
457     } else {
458         // round up to get some extra space
459         // yes, it can overflow
460         new_max = 1.1 * new_max;
461         // make sure max is not 0 to avoid / 0
462         // default to 1 KiB
463         new_max = std::max(new_max, G_GUINT64_CONSTANT(1024));
464 
465         // decompose new_max = coef10 * 2**(base10 * 10)
466         // where coef10 and base10 are integers and coef10 < 2**10
467         //
468         // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10)
469         //      where base10 = 1, coef10 = 101, pow2 = 16
470 
471 	guint64 pow2 = std::floor(log(new_max) / log(2));
472         guint64 base10 = pow2 / 10.0;
473         guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10)));
474         g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10))));
475 
476         // then decompose coef10 = x * 10**factor10
477         // where factor10 is integer and x < 10
478         // so we new_max has only 1 significant digit
479 
480         guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10)));
481         coef10 = std::ceil(coef10 / double(factor10)) * factor10;
482 
483         // then make coef10 divisible by num_bars
484         if (coef10 % graph->num_bars() != 0)
485             coef10 = coef10 + (graph->num_bars() - coef10 % graph->num_bars());
486         g_assert(coef10 % graph->num_bars() == 0);
487         new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10));
488         procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT
489                       "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT,
490                       bak_max, new_max, pow2, coef10);
491     }
492 
493     if (bak_max > new_max) {
494         procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT
495                       " new=%" G_GUINT64_FORMAT,
496                       bak_max, new_max);
497         new_max = bak_max;
498     }
499 
500     // if max is the same or has decreased but not so much, don't
501     // do anything to avoid rescaling
502     if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max)
503         return;
504 
505     const double scale = 1.0f * graph->net.max / new_max;
506 
507     for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) {
508         if (graph->data[i][0] >= 0.0f) {
509             graph->data[i][0] *= scale;
510             graph->data[i][1] *= scale;
511         }
512     }
513 
514     procman_debug("rescale dmax = %" G_GUINT64_FORMAT
515                   " max = %" G_GUINT64_FORMAT
516                   " new_max = %" G_GUINT64_FORMAT,
517                   dmax, graph->net.max, new_max);
518 
519     graph->net.max = new_max;
520 
521     // force the graph background to be redrawn now that scale has changed
522     graph->clear_background();
523 }
524 
525 static void
get_net(LoadGraph * graph)526 get_net (LoadGraph *graph)
527 {
528     glibtop_netlist netlist;
529     char **ifnames;
530     guint32 i;
531     guint64 in = 0, out = 0;
532 #if GLIB_CHECK_VERSION(2,61,2)
533     gint64 time;
534 #else
535     GTimeVal time;
536 #endif
537     guint64 din, dout;
538 
539     ifnames = glibtop_get_netlist(&netlist);
540 
541     for (i = 0; i < netlist.number; ++i)
542     {
543         glibtop_netload netload;
544         glibtop_get_netload (&netload, ifnames[i]);
545 
546         if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK))
547             continue;
548 
549         /* Skip interfaces without any IPv4/IPv6 address (or
550          those with only a LINK ipv6 addr) However we need to
551          be able to exclude these while still keeping the
552          value so when they get online (with NetworkManager
553          for example) we don't get a suddent peak.  Once we're
554          able to get this, ignoring down interfaces will be
555          possible too.  */
556         if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6)
557              and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK)
558             and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS)))
559             continue;
560 
561         /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP)
562            to avoid spikes when they are brought up */
563 
564         in  += netload.bytes_in;
565         out += netload.bytes_out;
566     }
567 
568     g_strfreev(ifnames);
569 
570 #if GLIB_CHECK_VERSION(2,61,2)
571     time = g_get_real_time ();
572 
573     if (in >= graph->net.last_in && out >= graph->net.last_out &&
574         graph->net.time != 0) {
575         float dtime = (double) (time - graph->net.time) / G_USEC_PER_SEC;
576 #else
577     g_get_current_time (&time);
578     if (in >= graph->net.last_in && out >= graph->net.last_out &&
579         graph->net.time.tv_sec != 0) {
580         float dtime;
581         dtime = time.tv_sec - graph->net.time.tv_sec +
582             (double) (time.tv_usec - graph->net.time.tv_usec) / G_USEC_PER_SEC;
583 #endif
584         din   = static_cast<guint64>((in  - graph->net.last_in)  / dtime);
585         dout  = static_cast<guint64>((out - graph->net.last_out) / dtime);
586     } else {
587         /* Don't calc anything if new data is less than old (interface
588            removed, counters reset, ...) or if it is the first time */
589         din  = 0;
590         dout = 0;
591     }
592 
593     graph->net.last_in  = in;
594     graph->net.last_out = out;
595     graph->net.time     = time;
596 
597     net_scale(graph, din, dout);
598 
599     bool network_in_bits = ProcData::get_instance()->config.network_in_bits;
600     g_autofree gchar *str=NULL, *formatted_str=NULL;
601 
602     str = g_format_size_full (network_in_bits ? din*8 : din, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
603     formatted_str = g_strdup_printf(_("%s/s"), str);
604     gtk_label_set_text (GTK_LABEL (graph->labels.net_in), formatted_str);
605 
606     str = g_format_size_full (network_in_bits ? in*8 : in, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
607     gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), str);
608 
609     str = g_format_size_full (network_in_bits ? dout*8 : dout, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
610     formatted_str = g_strdup_printf(_("%s/s"), str);
611     gtk_label_set_text (GTK_LABEL (graph->labels.net_out), formatted_str);
612 
613     str = g_format_size_full (network_in_bits ? out*8 : out, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
614     gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), str);
615 }
616 
617 
618 /* Updates the load graph when the timeout expires */
619 static gboolean
620 load_graph_update (gpointer user_data)
621 {
622     LoadGraph * const graph = static_cast<LoadGraph*>(user_data);
623 
624     if (graph->render_counter == graph->frames_per_unit - 1) {
625         std::rotate(&graph->data[0], &graph->data[LoadGraph::NUM_POINTS - 1], &graph->data[LoadGraph::NUM_POINTS]);
626 
627         switch (graph->type) {
628         case LOAD_GRAPH_CPU:
629             get_load(graph);
630             break;
631         case LOAD_GRAPH_MEM:
632             get_memory(graph);
633             break;
634         case LOAD_GRAPH_NET:
635             get_net(graph);
636             break;
637         default:
638             g_assert_not_reached();
639         }
640     }
641 
642     if (graph->draw)
643         load_graph_queue_draw (graph);
644 
645     graph->render_counter++;
646 
647     if (graph->render_counter >= graph->frames_per_unit)
648         graph->render_counter = 0;
649 
650     return TRUE;
651 }
652 
653 
654 
655 LoadGraph::~LoadGraph()
656 {
657   load_graph_stop(this);
658 
659   if (timer_index)
660     g_source_remove(timer_index);
661 
662   clear_background();
663 }
664 
665 
666 
667 static gboolean
668 load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
669 {
670     LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
671 
672     delete graph;
673 
674     return FALSE;
675 }
676 
677 
678 LoadGraph::LoadGraph(guint type)
679   : fontsize(8.0),
680     rmargin(3.5 * fontsize),
681     indent(24.0),
682     n(0),
683     type(type),
684     speed(0),
685     draw_width(0),
686     draw_height(0),
687     render_counter(0),
688     frames_per_unit(10), // this will be changed but needs initialising
689     graph_dely(0),
690     real_draw_height(0),
691     graph_delx(0.0),
692     graph_buffer_offset(0),
693     main_widget(NULL),
694     disp(NULL),
695     background(NULL),
696     timer_index(0),
697     draw(FALSE),
698     mem_color_picker(NULL),
699     swap_color_picker(NULL)
700 {
701     LoadGraph * const graph = this;
702 
703     // FIXME:
704     // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS);
705     // knock FRAMES down to 5 until cairo gets faster
706 
707     switch (type) {
708     case LOAD_GRAPH_CPU:
709         memset(&cpu, 0, sizeof cpu);
710         n = ProcData::get_instance()->config.num_cpus;
711 
712         for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i)
713             labels.cpu[i] = gtk_label_new(NULL);
714 
715         break;
716 
717     case LOAD_GRAPH_MEM:
718         n = 2;
719         labels.memory = gtk_label_new(NULL);
720         labels.swap = gtk_label_new(NULL);
721         break;
722 
723     case LOAD_GRAPH_NET:
724         memset(&net, 0, sizeof net);
725         n = 2;
726         net.max = 1;
727         labels.net_in = gtk_label_new(NULL);
728         labels.net_in_total = gtk_label_new(NULL);
729         labels.net_out = gtk_label_new(NULL);
730         labels.net_out_total = gtk_label_new(NULL);
731         break;
732     }
733 
734     speed  = ProcData::get_instance()->config.graph_update_interval;
735 
736     colors.resize(n);
737 
738     switch (type) {
739     case LOAD_GRAPH_CPU:
740         memcpy(&colors[0], ProcData::get_instance()->config.cpu_color,
741                n * sizeof colors[0]);
742         break;
743     case LOAD_GRAPH_MEM:
744         colors[0] = ProcData::get_instance()->config.mem_color;
745         colors[1] = ProcData::get_instance()->config.swap_color;
746         mem_color_picker = gsm_color_button_new (&colors[0],
747                                                  GSMCP_TYPE_PIE);
748         swap_color_picker = gsm_color_button_new (&colors[1],
749                                                   GSMCP_TYPE_PIE);
750         break;
751     case LOAD_GRAPH_NET:
752         colors[0] = ProcData::get_instance()->config.net_in_color;
753         colors[1] = ProcData::get_instance()->config.net_out_color;
754         break;
755     }
756 
757     timer_index = 0;
758     render_counter = (frames_per_unit - 1);
759     draw = FALSE;
760 
761     main_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
762     gtk_widget_set_size_request(main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT);
763     gtk_widget_show (main_widget);
764 
765     disp = gtk_drawing_area_new ();
766     gtk_widget_show (disp);
767     g_signal_connect (G_OBJECT (disp), "draw", G_CALLBACK (load_graph_draw), graph);
768     g_signal_connect (G_OBJECT(disp), "configure_event",
769                       G_CALLBACK (load_graph_configure), graph);
770     g_signal_connect (G_OBJECT(disp), "destroy",
771                       G_CALLBACK (load_graph_destroy), graph);
772 
773     gtk_widget_set_events (disp, GDK_EXPOSURE_MASK);
774 
775     gtk_box_pack_start (GTK_BOX (main_widget), disp, TRUE, TRUE, 0);
776 
777 
778     /* Allocate data in a contiguous block */
779     data_block = std::vector<float>(n * LoadGraph::NUM_POINTS, -1.0f);
780 
781     for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i)
782         data[i] = &data_block[0] + i * n;
783 
784     gtk_widget_show_all (main_widget);
785 }
786 
787 void
788 load_graph_start (LoadGraph *graph)
789 {
790     if(!graph->timer_index) {
791 
792         load_graph_update(graph);
793 
794         graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit,
795                                         load_graph_update,
796                                         graph);
797     }
798 
799     graph->draw = TRUE;
800 }
801 
802 void
803 load_graph_stop (LoadGraph *graph)
804 {
805     /* don't draw anymore, but continue to poll */
806     graph->draw = FALSE;
807 }
808 
809 void
810 load_graph_change_speed (LoadGraph *graph,
811              guint new_speed)
812 {
813     if (graph->speed == new_speed)
814         return;
815 
816     graph->speed = new_speed;
817 
818     if(graph->timer_index) {
819         g_source_remove (graph->timer_index);
820         graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit,
821                                         load_graph_update,
822                                         graph);
823     }
824 
825     graph->clear_background();
826 }
827 
828 
829 LoadGraphLabels*
830 load_graph_get_labels (LoadGraph *graph)
831 {
832     return &graph->labels;
833 }
834 
835 GtkWidget*
836 load_graph_get_widget (LoadGraph *graph)
837 {
838     return graph->main_widget;
839 }
840 
841 GtkWidget*
842 load_graph_get_mem_color_picker(LoadGraph *graph)
843 {
844     return graph->mem_color_picker;
845 }
846 
847 GtkWidget*
848 load_graph_get_swap_color_picker(LoadGraph *graph)
849 {
850     return graph->swap_color_picker;
851 }
852