#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) { g_object_unref(this->background); 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 void draw_background(LoadGraph *g) { 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 = g->num_bars(); g->graph_dely = (g->draw_height - 15) / num_bars; /* round to int to avoid AA blur */ g->real_draw_height = g->graph_dely * num_bars; g->graph_delx = (g->draw_width - 2.0 - g->rmargin - g->indent) / (LoadGraph::NUM_POINTS - 3); g->graph_buffer_offset = (int) (1.5 * g->graph_delx) + FRAME_WIDTH ; gtk_widget_get_allocation (g->disp, &allocation); g->background = gdk_pixmap_new (GDK_DRAWABLE (gtk_widget_get_window (g->disp)), allocation.width, allocation.height, -1); cr = gdk_cairo_create (g->background); // 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, g->rmargin + g->indent, 0, g->draw_width - g->rmargin - g->indent, g->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, g->fontsize); for (i = 0; i <= num_bars; ++i) { double y; if (i == 0) y = 0.5 + g->fontsize / 2.0; else if (i == num_bars) y = i * g->graph_dely + 0.5; else y = i * g->graph_dely + g->fontsize / 2.0; gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); if (g->type == LOAD_GRAPH_NET) { // operation orders matters so it's 0 if i == num_bars unsigned rate = g->net.max - (i * g->net.max / num_bars); const std::string caption(procman::format_network_rate(rate, g->net.max)); cairo_text_extents (cr, caption.c_str(), &extents); cairo_move_to (cr, g->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, g->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, g->rmargin + g->indent - 3, i * g->graph_dely + 0.5); cairo_line_to (cr, g->draw_width - 0.5, i * g->graph_dely + 0.5); } cairo_stroke (cr); cairo_set_dash (cr, dash, 2, 1.5); const unsigned total_seconds = g->speed * (LoadGraph::NUM_POINTS - 2) / 1000; for (unsigned int i = 0; i < 7; i++) { double x = (i) * (g->draw_width - g->rmargin - g->indent) / 6; cairo_set_source_rgba (cr, 0, 0, 0, 0.75); cairo_move_to (cr, (ceil(x) + 0.5) + g->rmargin + g->indent, 0.5); cairo_line_to (cr, (ceil(x) + 0.5) + g->rmargin + g->indent, g->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) + g->rmargin + g->indent) - (extents.width/2), g->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_draw (LoadGraph *g) { /* repaint */ gtk_widget_queue_draw (g->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 g = static_cast(data_ptr); gtk_widget_get_allocation (widget, &allocation); g->draw_width = allocation.width - 2 * FRAME_WIDTH; g->draw_height = allocation.height - 2 * FRAME_WIDTH; g->clear_background(); load_graph_draw (g); return TRUE; } static gboolean load_graph_expose (GtkWidget *widget, GdkEventExpose *event, gpointer data_ptr) { LoadGraph * const g = static_cast(data_ptr); GtkAllocation allocation; GdkWindow *window; guint i, j; gdouble sample_width, x_offset; window = gtk_widget_get_window (g->disp); gtk_widget_get_allocation (g->disp, &allocation); if (g->background == NULL) { draw_background(g); gdk_window_set_back_pixmap (window, g->background, FALSE); } /* Number of pixels wide for one graph point */ sample_width = (float)(g->draw_width - g->rmargin - g->indent) / (float)LoadGraph::NUM_POINTS; /* General offset */ x_offset = g->draw_width - g->rmargin + (sample_width*2); /* Subframe offset */ x_offset += g->rmargin - ((sample_width / g->frames_per_unit) * g->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, g->rmargin + g->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1, g->draw_width - g->rmargin - g->indent - 1, g->real_draw_height + FRAME_WIDTH - 1); cairo_clip(cr); for (j = 0; j < g->n; ++j) { cairo_move_to (cr, x_offset, (1.0f - g->data[0][j]) * g->real_draw_height); gdk_cairo_set_source_color (cr, &(g->colors [j])); for (i = 1; i < LoadGraph::NUM_POINTS; ++i) { if (g->data[i][j] == -1.0f) continue; cairo_curve_to (cr, x_offset - ((i - 0.5f) * g->graph_delx), (1.0f - g->data[i-1][j]) * g->real_draw_height + 3.5f, x_offset - ((i - 0.5f) * g->graph_delx), (1.0f - g->data[i][j]) * g->real_draw_height + 3.5f, x_offset - (i * g->graph_delx), (1.0f - g->data[i][j]) * g->real_draw_height + 3.5f); } cairo_stroke (cr); } cairo_destroy (cr); return TRUE; } static void get_load (LoadGraph *g) { guint i; glibtop_cpu cpu; glibtop_get_cpu (&cpu); #undef NOW #undef LAST #define NOW (g->cpu.times[g->cpu.now]) #define LAST (g->cpu.times[g->cpu.now ^ 1]) if (g->n == 1) { NOW[0][CPU_TOTAL] = cpu.total; NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys; } else { for (i = 0; i < g->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 < g->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); g->data[0][i] = load; /* Update label */ text = g_strdup_printf("%.1f%%", load * 100.0f); gtk_label_set_text(GTK_LABEL(g->labels.cpu[i]), text); g_free(text); } g->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 *g) { 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(g->labels.memory), GSM_COLOR_BUTTON(g->mem_color_picker), mem.user, mem.total, mempercent); set_memory_label_and_picker(GTK_LABEL(g->labels.swap), GSM_COLOR_BUTTON(g->swap_color_picker), swap.used, swap.total, swappercent); g->data[0][0] = mempercent; g->data[0][1] = swappercent; } static void net_scale (LoadGraph *g, unsigned din, unsigned dout) { g->data[0][0] = 1.0f * din / g->net.max; g->data[0][1] = 1.0f * dout / g->net.max; unsigned dmax = std::max(din, dout); g->net.values[g->net.cur] = dmax; g->net.cur = (g->net.cur + 1) % LoadGraph::NUM_POINTS; unsigned new_max; // both way, new_max is the greatest value if (dmax >= g->net.max) new_max = dmax; else new_max = *std::max_element(&g->net.values[0], &g->net.values[LoadGraph::NUM_POINTS]); // // Round network maximum // const unsigned 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, 125U); } 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, 1024U); // 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 unsigned pow2 = std::floor(log2(new_max)); unsigned base10 = pow2 / 10; unsigned coef10 = std::ceil(new_max / double(1UL << (base10 * 10))); g_assert(new_max <= (coef10 * (1UL << (base10 * 10)))); // then decompose coef10 = x * 10**factor10 // where factor10 is integer and x < 10 // so we new_max has only 1 significant digit unsigned 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 % g->num_bars() != 0) coef10 = coef10 + (g->num_bars() - coef10 % g->num_bars()); g_assert(coef10 % g->num_bars() == 0); new_max = coef10 * (1UL << (base10 * 10)); procman_debug("bak %u new_max %u pow2 %u coef10 %u", bak_max, new_max, pow2, coef10); } if (bak_max > new_max) { procman_debug("overflow detected: bak=%u new=%u", 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 * g->net.max) < new_max && new_max <= g->net.max) return; const float scale = 1.0f * g->net.max / new_max; for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) { if (g->data[i][0] >= 0.0f) { g->data[i][0] *= scale; g->data[i][1] *= scale; } } procman_debug("rescale dmax = %u max = %u new_max = %u", dmax, g->net.max, new_max); g->net.max = new_max; // force the graph background to be redrawn now that scale has changed g->clear_background(); } static void get_net (LoadGraph *g) { glibtop_netlist netlist; char **ifnames; guint32 i; guint64 in = 0, out = 0; GTimeVal time; unsigned 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 >= g->net.last_in && out >= g->net.last_out && g->net.time.tv_sec != 0) { float dtime; dtime = time.tv_sec - g->net.time.tv_sec + (float) (time.tv_usec - g->net.time.tv_usec) / G_USEC_PER_SEC; din = static_cast((in - g->net.last_in) / dtime); dout = static_cast((out - g->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; } g->net.last_in = in; g->net.last_out = out; g->net.time = time; net_scale(g, din, dout); gtk_label_set_text (GTK_LABEL (g->labels.net_in), procman::format_network_rate(din).c_str()); gtk_label_set_text (GTK_LABEL (g->labels.net_in_total), procman::format_network(in).c_str()); gtk_label_set_text (GTK_LABEL (g->labels.net_out), procman::format_network_rate(dout).c_str()); gtk_label_set_text (GTK_LABEL (g->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 g = static_cast(user_data); if (g->render_counter == g->frames_per_unit - 1) { std::rotate(&g->data[0], &g->data[LoadGraph::NUM_POINTS - 1], &g->data[LoadGraph::NUM_POINTS]); switch (g->type) { case LOAD_GRAPH_CPU: get_load(g); break; case LOAD_GRAPH_MEM: get_memory(g); break; case LOAD_GRAPH_NET: get_net(g); break; default: g_assert_not_reached(); } } if (g->draw) load_graph_draw (g); g->render_counter++; if (g->render_counter >= g->frames_per_unit) g->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 g = static_cast(data_ptr); delete g; return FALSE; } LoadGraph::LoadGraph(guint type) : fontsize(0.0), rmargin(0.0), indent(0.0), n(0), type(0), speed(0), draw_width(0), draw_height(0), render_counter(0), frames_per_unit(0), 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 g = this; // FIXME: // on configure, g->frames_per_unit = g->draw_width/(LoadGraph::NUM_POINTS); // knock FRAMES down to 5 until cairo gets faster g->frames_per_unit = 10; // this will be changed but needs initialising g->fontsize = 8.0; g->rmargin = 3.5 * g->fontsize; g->indent = 24.0; g->type = type; switch (type) { case LOAD_GRAPH_CPU: memset(&this->cpu, 0, sizeof g->cpu); g->n = ProcData::get_instance()->config.num_cpus; for(guint i = 0; i < G_N_ELEMENTS(g->labels.cpu); ++i) g->labels.cpu[i] = gtk_label_new(NULL); break; case LOAD_GRAPH_MEM: g->n = 2; g->labels.memory = gtk_label_new(NULL); g->labels.swap = gtk_label_new(NULL); break; case LOAD_GRAPH_NET: memset(&this->net, 0, sizeof g->net); g->n = 2; g->net.max = 1; g->labels.net_in = gtk_label_new(NULL); g->labels.net_in_total = gtk_label_new(NULL); g->labels.net_out = gtk_label_new(NULL); g->labels.net_out_total = gtk_label_new(NULL); break; } g->speed = ProcData::get_instance()->config.graph_update_interval; g->colors.resize(g->n); switch (type) { case LOAD_GRAPH_CPU: memcpy(&g->colors[0], ProcData::get_instance()->config.cpu_color, g->n * sizeof g->colors[0]); break; case LOAD_GRAPH_MEM: g->colors[0] = ProcData::get_instance()->config.mem_color; g->colors[1] = ProcData::get_instance()->config.swap_color; g->mem_color_picker = gsm_color_button_new (&g->colors[0], GSMCP_TYPE_PIE); g->swap_color_picker = gsm_color_button_new (&g->colors[1], GSMCP_TYPE_PIE); break; case LOAD_GRAPH_NET: g->colors[0] = ProcData::get_instance()->config.net_in_color; g->colors[1] = ProcData::get_instance()->config.net_out_color; break; } g->timer_index = 0; g->render_counter = (g->frames_per_unit - 1); g->draw = FALSE; g->main_widget = gtk_vbox_new (FALSE, FALSE); gtk_widget_set_size_request(g->main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT); gtk_widget_show (g->main_widget); g->disp = gtk_drawing_area_new (); gtk_widget_show (g->disp); g_signal_connect (G_OBJECT (g->disp), "expose_event", G_CALLBACK (load_graph_expose), g); g_signal_connect (G_OBJECT(g->disp), "configure_event", G_CALLBACK (load_graph_configure), g); g_signal_connect (G_OBJECT(g->disp), "destroy", G_CALLBACK (load_graph_destroy), g); gtk_widget_set_events (g->disp, GDK_EXPOSURE_MASK); gtk_box_pack_start (GTK_BOX (g->main_widget), g->disp, TRUE, TRUE, 0); /* Allocate data in a contiguous block */ g->data_block = std::vector(g->n * LoadGraph::NUM_POINTS, -1.0f); for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i) g->data[i] = &g->data_block[0] + i * g->n; gtk_widget_show_all (g->main_widget); } void load_graph_start (LoadGraph *g) { if(!g->timer_index) { load_graph_update(g); g->timer_index = g_timeout_add (g->speed / g->frames_per_unit, load_graph_update, g); } g->draw = TRUE; } void load_graph_stop (LoadGraph *g) { /* don't draw anymore, but continue to poll */ g->draw = FALSE; } void load_graph_change_speed (LoadGraph *g, guint new_speed) { if (g->speed == new_speed) return; g->speed = new_speed; g_assert(g->timer_index); if(g->timer_index) { g_source_remove (g->timer_index); g->timer_index = g_timeout_add (g->speed / g->frames_per_unit, load_graph_update, g); } g->clear_background(); } LoadGraphLabels* load_graph_get_labels (LoadGraph *g) { return &g->labels; } GtkWidget* load_graph_get_widget (LoadGraph *g) { return g->main_widget; } GtkWidget* load_graph_get_mem_color_picker(LoadGraph *g) { return g->mem_color_picker; } GtkWidget* load_graph_get_swap_color_picker(LoadGraph *g) { return g->swap_color_picker; }