#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "procman.h" #include "load-graph.h" #include "util.h" #include "gsm_color_button.h" void LoadGraph::clear_background() { if (this->background) { #if GTK_CHECK_VERSION(3,0,0) cairo_surface_destroy (this->background); #else g_object_unref(this->background); #endif this->background = NULL; } } unsigned LoadGraph::num_bars() const { unsigned n; // keep 100 % num_bars == 0 switch (static_cast(this->draw_height / (this->fontsize + 14))) { case 0: case 1: n = 1; break; case 2: case 3: n = 2; break; case 4: n = 4; break; default: n = 5; } return n; } #define FRAME_WIDTH 4 static void draw_background(LoadGraph *graph) { GtkAllocation allocation; double dash[2] = { 1.0, 2.0 }; cairo_t *cr; guint i; unsigned num_bars; char *caption; cairo_text_extents_t extents; num_bars = graph->num_bars(); graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */ graph->real_draw_height = graph->graph_dely * num_bars; graph->graph_delx = (graph->draw_width - 2.0 - graph->rmargin - graph->indent) / (LoadGraph::NUM_POINTS - 3); graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH ; gtk_widget_get_allocation (graph->disp, &allocation); #if GTK_CHECK_VERSION(3,0,0) graph->background = gdk_window_create_similar_surface (gtk_widget_get_window (graph->disp), CAIRO_CONTENT_COLOR_ALPHA, allocation.width, allocation.height); cr = cairo_create (graph->background); #else graph->background = gdk_pixmap_new (GDK_DRAWABLE (gtk_widget_get_window (graph->disp)), allocation.width, allocation.height, -1); cr = gdk_cairo_create (graph->background); #endif // set the background colour GtkStyle *style = gtk_widget_get_style (ProcData::get_instance()->notebook); gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); cairo_paint (cr); /* draw frame */ cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH); /* Draw background rectangle */ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); cairo_rectangle (cr, graph->rmargin + graph->indent, 0, graph->draw_width - graph->rmargin - graph->indent, graph->real_draw_height); cairo_fill(cr); cairo_set_line_width (cr, 1.0); cairo_set_dash (cr, dash, 2, 0); cairo_set_font_size (cr, graph->fontsize); for (i = 0; i <= num_bars; ++i) { double y; if (i == 0) y = 0.5 + graph->fontsize / 2.0; else if (i == num_bars) y = i * graph->graph_dely + 0.5; else y = i * graph->graph_dely + graph->fontsize / 2.0; gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); if (graph->type == LOAD_GRAPH_NET) { // operation orders matters so it's 0 if i == num_bars unsigned rate = graph->net.max - (i * graph->net.max / num_bars); const std::string caption(procman::format_network_rate(rate, graph->net.max)); cairo_text_extents (cr, caption.c_str(), &extents); cairo_move_to (cr, graph->indent - extents.width + 20, y); cairo_show_text (cr, caption.c_str()); } else { // operation orders matters so it's 0 if i == num_bars caption = g_strdup_printf("%d %%", 100 - i * (100 / num_bars)); cairo_text_extents (cr, caption, &extents); cairo_move_to (cr, graph->indent - extents.width + 20, y); cairo_show_text (cr, caption); g_free (caption); } cairo_set_source_rgba (cr, 0, 0, 0, 0.75); cairo_move_to (cr, graph->rmargin + graph->indent - 3, i * graph->graph_dely + 0.5); cairo_line_to (cr, graph->draw_width - 0.5, i * graph->graph_dely + 0.5); } cairo_stroke (cr); cairo_set_dash (cr, dash, 2, 1.5); const unsigned total_seconds = graph->speed * (LoadGraph::NUM_POINTS - 2) / 1000; for (unsigned int i = 0; i < 7; i++) { double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6; cairo_set_source_rgba (cr, 0, 0, 0, 0.75); cairo_move_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, 0.5); cairo_line_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, graph->real_draw_height + 4.5); cairo_stroke(cr); unsigned seconds = total_seconds - i * total_seconds / 6; const char* format; if (i == 0) format = dngettext(GETTEXT_PACKAGE, "%u second", "%u seconds", seconds); else format = "%u"; caption = g_strdup_printf(format, seconds); cairo_text_extents (cr, caption, &extents); cairo_move_to (cr, ((ceil(x) + 0.5) + graph->rmargin + graph->indent) - (extents.width/2), graph->draw_height); gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); cairo_show_text (cr, caption); g_free (caption); } cairo_stroke (cr); cairo_destroy (cr); } /* Redraws the backing buffer for the load graph and updates the window */ void load_graph_queue_draw (LoadGraph *graph) { /* repaint */ gtk_widget_queue_draw (graph->disp); } static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;) static gboolean load_graph_configure (GtkWidget *widget, GdkEventConfigure *event, gpointer data_ptr) { GtkAllocation allocation; LoadGraph * const graph = static_cast(data_ptr); gtk_widget_get_allocation (widget, &allocation); graph->draw_width = allocation.width - 2 * FRAME_WIDTH; graph->draw_height = allocation.height - 2 * FRAME_WIDTH; graph->clear_background(); load_graph_queue_draw (graph); return TRUE; } #if GTK_CHECK_VERSION(3,0,0) static gboolean load_graph_draw (GtkWidget *widget, cairo_t *context, gpointer data_ptr) #else static gboolean load_graph_expose (GtkWidget *widget, GdkEventExpose *event, gpointer data_ptr) #endif { LoadGraph * const graph = static_cast(data_ptr); GtkAllocation allocation; GdkWindow *window; guint i, j; gdouble sample_width, x_offset; window = gtk_widget_get_window (graph->disp); gtk_widget_get_allocation (graph->disp, &allocation); if (graph->background == NULL) { draw_background(graph); #if GTK_CHECK_VERSION(3,0,0) cairo_pattern_t *pattern = cairo_pattern_create_for_surface (graph->background); gdk_window_set_background_pattern (window, pattern); cairo_pattern_destroy (pattern); #else gdk_window_set_back_pixmap (window, graph->background, FALSE); #endif } /* Number of pixels wide for one graph point */ sample_width = (float)(graph->draw_width - graph->rmargin - graph->indent) / (float)LoadGraph::NUM_POINTS; /* General offset */ x_offset = graph->draw_width - graph->rmargin + (sample_width*2); /* Subframe offset */ x_offset += graph->rmargin - ((sample_width / graph->frames_per_unit) * graph->render_counter); /* draw the graph */ cairo_t* cr; cr = gdk_cairo_create (window); cairo_set_line_width (cr, 1); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_rectangle (cr, graph->rmargin + graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1, graph->draw_width - graph->rmargin - graph->indent - 1, graph->real_draw_height + FRAME_WIDTH - 1); cairo_clip(cr); for (j = 0; j < graph->n; ++j) { cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height); gdk_cairo_set_source_color (cr, &(graph->colors [j])); for (i = 1; i < LoadGraph::NUM_POINTS; ++i) { if (graph->data[i][j] == -1.0f) continue; cairo_curve_to (cr, x_offset - ((i - 0.5f) * graph->graph_delx), (1.0f - graph->data[i-1][j]) * graph->real_draw_height + 3.5f, x_offset - ((i - 0.5f) * graph->graph_delx), (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f, x_offset - (i * graph->graph_delx), (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f); } cairo_stroke (cr); } cairo_destroy (cr); return TRUE; } static void get_load (LoadGraph *graph) { guint i; glibtop_cpu cpu; glibtop_get_cpu (&cpu); #undef NOW #undef LAST #define NOW (graph->cpu.times[graph->cpu.now]) #define LAST (graph->cpu.times[graph->cpu.now ^ 1]) if (graph->n == 1) { NOW[0][CPU_TOTAL] = cpu.total; NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys; } else { for (i = 0; i < graph->n; i++) { NOW[i][CPU_TOTAL] = cpu.xcpu_total[i]; NOW[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i] + cpu.xcpu_sys[i]; } } // on the first call, LAST is 0 // which means data is set to the average load since boot // that value has no meaning, we just want all the // graphs to be aligned, so the CPU graph needs to start // immediately for (i = 0; i < graph->n; i++) { float load; float total, used; gchar *text; total = NOW[i][CPU_TOTAL] - LAST[i][CPU_TOTAL]; used = NOW[i][CPU_USED] - LAST[i][CPU_USED]; load = used / MAX(total, 1.0f); graph->data[0][i] = load; /* Update label */ text = g_strdup_printf("%.1f%%", load * 100.0f); gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text); g_free(text); } graph->cpu.now ^= 1; #undef NOW #undef LAST } namespace { void set_memory_label_and_picker(GtkLabel* label, GSMColorButton* picker, guint64 used, guint64 total, double percent) { char* used_text; char* total_text; char* text; used_text = procman::format_size(used); total_text = procman::format_size(total); // xgettext: 540MiB (53 %) of 1.0 GiB text = g_strdup_printf(_("%s (%.1f %%) of %s"), used_text, 100.0 * percent, total_text); gtk_label_set_text(label, text); g_free(used_text); g_free(total_text); g_free(text); if (picker) gsm_color_button_set_fraction(picker, percent); } } static void get_memory (LoadGraph *graph) { float mempercent, swappercent; glibtop_mem mem; glibtop_swap swap; glibtop_get_mem (&mem); glibtop_get_swap (&swap); /* There's no swap on LiveCD : 0.0f is better than NaN :) */ swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f); mempercent = (float)mem.user / (float)mem.total; set_memory_label_and_picker(GTK_LABEL(graph->labels.memory), GSM_COLOR_BUTTON(graph->mem_color_picker), mem.user, mem.total, mempercent); set_memory_label_and_picker(GTK_LABEL(graph->labels.swap), GSM_COLOR_BUTTON(graph->swap_color_picker), swap.used, swap.total, swappercent); graph->data[0][0] = mempercent; graph->data[0][1] = swappercent; } static void net_scale (LoadGraph *graph, guint64 din, guint64 dout) { graph->data[0][0] = 1.0f * din / graph->net.max; graph->data[0][1] = 1.0f * dout / graph->net.max; guint64 dmax = std::max(din, dout); graph->net.values[graph->net.cur] = dmax; graph->net.cur = (graph->net.cur + 1) % LoadGraph::NUM_POINTS; guint64 new_max; // both way, new_max is the greatest value if (dmax >= graph->net.max) new_max = dmax; else new_max = *std::max_element(&graph->net.values[0], &graph->net.values[LoadGraph::NUM_POINTS]); // // Round network maximum // const guint64 bak_max(new_max); if (ProcData::get_instance()->config.network_in_bits) { // TODO: fix logic to give a nice scale with bits // round up to get some extra space // yes, it can overflow new_max = 1.1 * new_max; // make sure max is not 0 to avoid / 0 // default to 125 bytes == 1kbit new_max = std::max(new_max, G_GUINT64_CONSTANT(125)); } else { // round up to get some extra space // yes, it can overflow new_max = 1.1 * new_max; // make sure max is not 0 to avoid / 0 // default to 1 KiB new_max = std::max(new_max, G_GUINT64_CONSTANT(1024)); // decompose new_max = coef10 * 2**(base10 * 10) // where coef10 and base10 are integers and coef10 < 2**10 // // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10) // where base10 = 1, coef10 = 101, pow2 = 16 guint64 pow2 = std::floor(log2(new_max)); guint64 base10 = pow2 / 10.0; guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10))); g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10)))); // then decompose coef10 = x * 10**factor10 // where factor10 is integer and x < 10 // so we new_max has only 1 significant digit guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10))); coef10 = std::ceil(coef10 / double(factor10)) * factor10; // then make coef10 divisible by num_bars if (coef10 % graph->num_bars() != 0) coef10 = coef10 + (graph->num_bars() - coef10 % graph->num_bars()); g_assert(coef10 % graph->num_bars() == 0); new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10)); procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT, bak_max, new_max, pow2, coef10); } if (bak_max > new_max) { procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT " new=%" G_GUINT64_FORMAT, bak_max, new_max); new_max = bak_max; } // if max is the same or has decreased but not so much, don't // do anything to avoid rescaling if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max) return; const double scale = 1.0f * graph->net.max / new_max; for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) { if (graph->data[i][0] >= 0.0f) { graph->data[i][0] *= scale; graph->data[i][1] *= scale; } } procman_debug("rescale dmax = %" G_GUINT64_FORMAT " max = %" G_GUINT64_FORMAT " new_max = %" G_GUINT64_FORMAT, dmax, graph->net.max, new_max); graph->net.max = new_max; // force the graph background to be redrawn now that scale has changed graph->clear_background(); } static void get_net (LoadGraph *graph) { glibtop_netlist netlist; char **ifnames; guint32 i; guint64 in = 0, out = 0; GTimeVal time; guint64 din, dout; ifnames = glibtop_get_netlist(&netlist); for (i = 0; i < netlist.number; ++i) { glibtop_netload netload; glibtop_get_netload (&netload, ifnames[i]); if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK)) continue; /* Skip interfaces without any IPv4/IPv6 address (or those with only a LINK ipv6 addr) However we need to be able to exclude these while still keeping the value so when they get online (with NetworkManager for example) we don't get a suddent peak. Once we're able to get this, ignoring down interfaces will be possible too. */ if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6) and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK) and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS))) continue; /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP) to avoid spikes when they are brought up */ in += netload.bytes_in; out += netload.bytes_out; } g_strfreev(ifnames); g_get_current_time (&time); if (in >= graph->net.last_in && out >= graph->net.last_out && graph->net.time.tv_sec != 0) { float dtime; dtime = time.tv_sec - graph->net.time.tv_sec + (double) (time.tv_usec - graph->net.time.tv_usec) / G_USEC_PER_SEC; din = static_cast((in - graph->net.last_in) / dtime); dout = static_cast((out - graph->net.last_out) / dtime); } else { /* Don't calc anything if new data is less than old (interface removed, counters reset, ...) or if it is the first time */ din = 0; dout = 0; } graph->net.last_in = in; graph->net.last_out = out; graph->net.time = time; net_scale(graph, din, dout); gtk_label_set_text (GTK_LABEL (graph->labels.net_in), procman::format_network_rate(din).c_str()); gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), procman::format_network(in).c_str()); gtk_label_set_text (GTK_LABEL (graph->labels.net_out), procman::format_network_rate(dout).c_str()); gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), procman::format_network(out).c_str()); } /* Updates the load graph when the timeout expires */ static gboolean load_graph_update (gpointer user_data) { LoadGraph * const graph = static_cast(user_data); if (graph->render_counter == graph->frames_per_unit - 1) { std::rotate(&graph->data[0], &graph->data[LoadGraph::NUM_POINTS - 1], &graph->data[LoadGraph::NUM_POINTS]); switch (graph->type) { case LOAD_GRAPH_CPU: get_load(graph); break; case LOAD_GRAPH_MEM: get_memory(graph); break; case LOAD_GRAPH_NET: get_net(graph); break; default: g_assert_not_reached(); } } if (graph->draw) load_graph_queue_draw (graph); graph->render_counter++; if (graph->render_counter >= graph->frames_per_unit) graph->render_counter = 0; return TRUE; } LoadGraph::~LoadGraph() { load_graph_stop(this); if (this->timer_index) g_source_remove(this->timer_index); this->clear_background(); } static gboolean load_graph_destroy (GtkWidget *widget, gpointer data_ptr) { LoadGraph * const graph = static_cast(data_ptr); delete graph; return FALSE; } LoadGraph::LoadGraph(guint type) : fontsize(8.0), rmargin(3.5 * fontsize), indent(24.0), n(0), type(type), speed(0), draw_width(0), draw_height(0), render_counter(0), frames_per_unit(10), // this will be changed but needs initialising graph_dely(0), real_draw_height(0), graph_delx(0.0), graph_buffer_offset(0), main_widget(NULL), disp(NULL), background(NULL), timer_index(0), draw(FALSE), mem_color_picker(NULL), swap_color_picker(NULL) { LoadGraph * const graph = this; // FIXME: // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS); // knock FRAMES down to 5 until cairo gets faster switch (type) { case LOAD_GRAPH_CPU: memset(&this->cpu, 0, sizeof graph->cpu); graph->n = ProcData::get_instance()->config.num_cpus; for(guint i = 0; i < G_N_ELEMENTS(graph->labels.cpu); ++i) graph->labels.cpu[i] = gtk_label_new(NULL); break; case LOAD_GRAPH_MEM: graph->n = 2; graph->labels.memory = gtk_label_new(NULL); graph->labels.swap = gtk_label_new(NULL); break; case LOAD_GRAPH_NET: memset(&this->net, 0, sizeof graph->net); graph->n = 2; graph->net.max = 1; graph->labels.net_in = gtk_label_new(NULL); graph->labels.net_in_total = gtk_label_new(NULL); graph->labels.net_out = gtk_label_new(NULL); graph->labels.net_out_total = gtk_label_new(NULL); break; } graph->speed = ProcData::get_instance()->config.graph_update_interval; graph->colors.resize(graph->n); switch (type) { case LOAD_GRAPH_CPU: memcpy(&graph->colors[0], ProcData::get_instance()->config.cpu_color, graph->n * sizeof graph->colors[0]); break; case LOAD_GRAPH_MEM: graph->colors[0] = ProcData::get_instance()->config.mem_color; graph->colors[1] = ProcData::get_instance()->config.swap_color; graph->mem_color_picker = gsm_color_button_new (&graph->colors[0], GSMCP_TYPE_PIE); graph->swap_color_picker = gsm_color_button_new (&graph->colors[1], GSMCP_TYPE_PIE); break; case LOAD_GRAPH_NET: graph->colors[0] = ProcData::get_instance()->config.net_in_color; graph->colors[1] = ProcData::get_instance()->config.net_out_color; break; } graph->timer_index = 0; graph->render_counter = (graph->frames_per_unit - 1); graph->draw = FALSE; graph->main_widget = gtk_vbox_new (FALSE, FALSE); gtk_widget_set_size_request(graph->main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT); gtk_widget_show (graph->main_widget); graph->disp = gtk_drawing_area_new (); gtk_widget_show (graph->disp); #if GTK_CHECK_VERSION(3,0,0) g_signal_connect (G_OBJECT (graph->disp), "draw", G_CALLBACK (load_graph_draw), graph); #else g_signal_connect (G_OBJECT (graph->disp), "expose_event", G_CALLBACK (load_graph_expose), graph); #endif g_signal_connect (G_OBJECT(graph->disp), "configure_event", G_CALLBACK (load_graph_configure), graph); g_signal_connect (G_OBJECT(graph->disp), "destroy", G_CALLBACK (load_graph_destroy), graph); gtk_widget_set_events (graph->disp, GDK_EXPOSURE_MASK); gtk_box_pack_start (GTK_BOX (graph->main_widget), graph->disp, TRUE, TRUE, 0); /* Allocate data in a contiguous block */ graph->data_block = std::vector(graph->n * LoadGraph::NUM_POINTS, -1.0f); for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i) graph->data[i] = &graph->data_block[0] + i * graph->n; gtk_widget_show_all (graph->main_widget); } void load_graph_start (LoadGraph *graph) { if(!graph->timer_index) { load_graph_update(graph); graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit, load_graph_update, graph); } graph->draw = TRUE; } void load_graph_stop (LoadGraph *graph) { /* don't draw anymore, but continue to poll */ graph->draw = FALSE; } void load_graph_change_speed (LoadGraph *graph, guint new_speed) { if (graph->speed == new_speed) return; graph->speed = new_speed; g_assert(graph->timer_index); if(graph->timer_index) { g_source_remove (graph->timer_index); graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit, load_graph_update, graph); } graph->clear_background(); } LoadGraphLabels* load_graph_get_labels (LoadGraph *graph) { return &graph->labels; } GtkWidget* load_graph_get_widget (LoadGraph *graph) { return graph->main_widget; } GtkWidget* load_graph_get_mem_color_picker(LoadGraph *graph) { return graph->mem_color_picker; } GtkWidget* load_graph_get_swap_color_picker(LoadGraph *graph) { return graph->swap_color_picker; }