diff options
Diffstat (limited to 'src/load-graph.cpp')
-rw-r--r-- | src/load-graph.cpp | 787 |
1 files changed, 787 insertions, 0 deletions
diff --git a/src/load-graph.cpp b/src/load-graph.cpp new file mode 100644 index 0000000..79700d4 --- /dev/null +++ b/src/load-graph.cpp @@ -0,0 +1,787 @@ +#include <config.h> + +#include <gdkmm/pixbuf.h> + +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <signal.h> +#include <dirent.h> +#include <string.h> +#include <time.h> +#include <gdk/gdkx.h> + +#include <glib/gi18n.h> + +#include <glibtop.h> +#include <glibtop/cpu.h> +#include <glibtop/mem.h> +#include <glibtop/swap.h> +#include <glibtop/netload.h> +#include <glibtop/netlist.h> +#include <math.h> + +#include <algorithm> + +#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<int>(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<LoadGraph*>(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(); + + if (g->gc == NULL) { + g->gc = gdk_gc_new (GDK_DRAWABLE (gtk_widget_get_window (widget))); + } + + load_graph_draw (g); + + return TRUE; +} + +static gboolean +load_graph_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data_ptr) +{ + LoadGraph * const g = static_cast<LoadGraph*>(data_ptr); + GtkAllocation allocation; + GdkWindow *window; + + guint i, j; + gdouble sample_width, x_offset; + + if (g->background == NULL) { + draw_background(g); + } + + window = gtk_widget_get_window (g->disp); + gtk_widget_get_allocation (g->disp, &allocation); + gdk_draw_drawable (window, + g->gc, + g->background, + 0, 0, 0, 0, + allocation.width, + allocation.height); + + /* 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<unsigned>((in - g->net.last_in) / dtime); + dout = static_cast<unsigned>((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<LoadGraph*>(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<LoadGraph*>(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), + gc(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<float>(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; +} |