summaryrefslogtreecommitdiffstats
path: root/src/load-graph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/load-graph.cpp')
-rw-r--r--src/load-graph.cpp1119
1 files changed, 1119 insertions, 0 deletions
diff --git a/src/load-graph.cpp b/src/load-graph.cpp
new file mode 100644
index 0000000..377623f
--- /dev/null
+++ b/src/load-graph.cpp
@@ -0,0 +1,1119 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <math.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 "application.h"
+#include "load-graph.h"
+#include "util.h"
+#include "legacy/gsm_color_button.h"
+
+gchar* format_duration(unsigned seconds);
+
+void LoadGraph::clear_background()
+{
+ if (background) {
+ cairo_surface_destroy (background);
+ background = NULL;
+ }
+}
+
+bool LoadGraph::is_logarithmic_scale() const
+{
+ // logarithmic scale is used only for memory graph
+ return this->type == LOAD_GRAPH_MEM && GsmApplication::get()->config.logarithmic_scale;
+}
+
+unsigned LoadGraph::num_bars() const
+{
+ unsigned n;
+
+ // keep 100 % num_bars == 0
+ switch (static_cast<int>(draw_height / (fontsize + 14)))
+ {
+ case 0:
+ case 1:
+ n = 1;
+ break;
+ case 2:
+ case 3:
+ n = 2;
+ break;
+ case 4:
+ n = 4;
+ break;
+ case 5:
+ n = 5;
+ if (this->is_logarithmic_scale())
+ n = 4;
+ break;
+ default:
+ n = 5;
+ if (this->is_logarithmic_scale())
+ n = 6;
+ }
+
+ return n;
+}
+
+/*
+ Returns Y scale caption based on give index of the label.
+ Takes into account whether the scale should be logarithmic for memory graph.
+ */
+char* LoadGraph::get_caption(guint index)
+{
+ char *caption;
+ unsigned num_bars = this->num_bars();
+ guint64 max_value;
+ if (this->type == LOAD_GRAPH_NET)
+ max_value = this->net.max;
+ else
+ max_value = 100;
+
+ // operation orders matters so it's 0 if index == num_bars
+ float caption_percentage = (float)max_value - index * (float)max_value / num_bars;
+
+ if (this->is_logarithmic_scale()) {
+ float caption_value = caption_percentage == 0 ? 0 : pow(100, caption_percentage / max_value);
+ // Translators: loadgraphs y axis percentage labels: 0 %, 50%, 100%
+ caption = g_strdup_printf(_("%.0f %%"), caption_value);
+ } else if (this->type == LOAD_GRAPH_NET) {
+ const std::string captionstr(procman::format_network_rate((guint64)caption_percentage));
+ caption = g_strdup(captionstr.c_str());
+ } else {
+ // Translators: loadgraphs y axis percentage labels: 0 %, 50%, 100%
+ caption = g_strdup_printf(_("%.0f %%"), caption_percentage);
+ }
+
+ return caption;
+}
+
+/*
+ Translates y partial position to logarithmic position if set to logarithmic scale.
+*/
+float LoadGraph::translate_to_log_partial_if_needed(float position_partial)
+{
+ if (this->is_logarithmic_scale())
+ position_partial = position_partial == 0 ? 0 : log10(position_partial * 100) / 2;
+
+ return position_partial;
+}
+
+gchar* format_duration(unsigned seconds) {
+ gchar* caption = NULL;
+
+ unsigned minutes = seconds / 60;
+ unsigned hours = seconds / 3600;
+
+ if (hours != 0) {
+ if (minutes % 60 == 0) {
+ // If minutes mod 60 is 0 set it to 0, to prevent it from showing full hours in
+ // minutes in addition to hours.
+ minutes = 0;
+ } else {
+ // Round minutes as seconds wont get shown if neither hours nor minutes are 0.
+ minutes = int(rint(seconds / 60.0)) % 60;
+ if (minutes == 0) {
+ // Increase hours if rounding minutes results in 0, because that would be
+ // what it would be rounded to.
+ hours++;
+ // Set seconds to hours * 3600 to prevent seconds from being drawn.
+ seconds = hours * 3600;
+ }
+ }
+
+ }
+
+ gchar* captionH = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u hr", "%u hrs", hours), hours);
+ gchar* captionM = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u min", "%u mins", minutes),
+ minutes);
+ gchar* captionS = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u sec", "%u secs", seconds % 60),
+ seconds % 60);
+
+ caption = g_strjoin (" ", hours > 0 ? captionH : "",
+ minutes > 0 ? captionM : "",
+ seconds % 60 > 0 ? captionS : "",
+ NULL);
+ g_free (captionH);
+ g_free (captionM);
+ g_free (captionS);
+
+ return caption;
+}
+
+const int FRAME_WIDTH = 4;
+static void draw_background(LoadGraph *graph) {
+ GtkAllocation allocation;
+ cairo_t *cr;
+ guint i;
+ double label_x_offset_modifier, label_y_offset_modifier;
+ unsigned num_bars;
+ gchar *caption;
+ PangoLayout* layout;
+ PangoAttrList *attrs = NULL;
+ PangoFontDescription* font_desc;
+ PangoRectangle extents;
+ cairo_surface_t *surface;
+ GdkRGBA fg;
+ GdkRGBA fg_grid;
+ double const border_alpha = 0.7;
+ double const grid_alpha = border_alpha / 2.0;
+
+ 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->indent) / (graph->num_points - 3);
+ graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH;
+
+ gtk_widget_get_allocation (GTK_WIDGET (graph->disp), &allocation);
+ surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (graph->disp)),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ allocation.width,
+ allocation.height);
+ cr = cairo_create (surface);
+
+ GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (GsmApplication::get()->stack));
+
+ gtk_style_context_get_color (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), &fg);
+
+ cairo_paint_with_alpha (cr, 0.0);
+ layout = pango_cairo_create_layout (cr);
+
+ attrs = make_tnum_attr_list ();
+ pango_layout_set_attributes (layout, attrs);
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+
+ gtk_style_context_get (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
+ pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE);
+ pango_layout_set_font_description (layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ /* draw frame */
+ cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);
+
+ /* Draw background rectangle */
+ /* When a user uses a dark theme, the hard-coded
+ * white background in GSM is a lone white on the
+ * display, which makes the user unhappy. To fix
+ * this, here we offer the user a chance to set
+ * his favorite background color. */
+ gtk_style_context_save (context);
+
+ /* Here we specify the name of the class. Now in
+ * the theme's CSS we can specify the own colors
+ * for this class. */
+ gtk_style_context_add_class (context, "loadgraph");
+
+ /* And in case the user does not care, we add
+ * classes that usually have a white background. */
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_PAPER);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
+
+ /* And, as a bonus, the user can choose the color of the grid. */
+ gtk_style_context_get_color (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), &fg_grid);
+
+ /* Why not use the new features of the
+ * GTK instead of cairo_rectangle ?! :) */
+ gtk_render_background (context, cr, graph->indent, 0.0,
+ graph->draw_width - graph->rmargin - graph->indent,
+ graph->real_draw_height);
+
+ gtk_style_context_restore (context);
+
+ cairo_set_line_width (cr, 1.0);
+
+ 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_rgba (cr, &fg);
+ caption = graph->get_caption(i);
+ pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
+ pango_layout_set_text (layout, caption, -1);
+ pango_layout_get_extents (layout, NULL, &extents);
+ label_y_offset_modifier = i == 0 ? 0.5
+ : i == num_bars
+ ? 1.0
+ : 0.85;
+ cairo_move_to (cr, graph->draw_width - graph->indent - 23,
+ y - label_y_offset_modifier * extents.height / PANGO_SCALE);
+ pango_cairo_show_layout (cr, layout);
+ g_free(caption);
+
+ if (i==0 || i==num_bars)
+ fg_grid.alpha = border_alpha;
+ else
+ fg_grid.alpha = grid_alpha;
+
+ gdk_cairo_set_source_rgba (cr, &fg_grid);
+ cairo_move_to (cr, graph->indent, i * graph->graph_dely + 0.5);
+ cairo_line_to (cr, graph->draw_width - graph->rmargin + 0.5 + 4, i * graph->graph_dely + 0.5);
+ cairo_stroke (cr);
+ }
+
+
+ const unsigned total_seconds = graph->speed * (graph->num_points - 2) / 1000 * graph->frames_per_unit;
+
+ for (unsigned int i = 0; i < 7; i++) {
+ double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6;
+
+ if (i==0 || i==6)
+ fg_grid.alpha = border_alpha;
+ else
+ fg_grid.alpha = grid_alpha;
+
+ gdk_cairo_set_source_rgba (cr, &fg_grid);
+ cairo_move_to (cr, (ceil(x) + 0.5) + graph->indent, 0.5);
+ cairo_line_to (cr, (ceil(x) + 0.5) + graph->indent, graph->real_draw_height + 4.5);
+ cairo_stroke(cr);
+
+ caption = format_duration(total_seconds - i * total_seconds / 6);
+
+ pango_layout_set_text (layout, caption, -1);
+ pango_layout_get_extents (layout, NULL, &extents);
+ label_x_offset_modifier = i == 0 ? 0
+ : i == 6
+ ? 1.0
+ : 0.5;
+ cairo_move_to (cr,
+ (ceil(x) + 0.5 + graph->indent) - label_x_offset_modifier * extents.width / PANGO_SCALE + 1.0,
+ graph->draw_height - 1.0 * extents.height / PANGO_SCALE);
+ gdk_cairo_set_source_rgba (cr, &fg);
+ pango_cairo_show_layout (cr, layout);
+ g_free (caption);
+ }
+ g_object_unref(layout);
+ cairo_stroke (cr);
+ cairo_destroy (cr);
+ graph->background = surface;
+}
+
+/* Redraws the backing buffer for the load graph and updates the window */
+void
+load_graph_queue_draw (LoadGraph *graph)
+{
+ /* repaint */
+ gtk_widget_queue_draw (GTK_WIDGET (graph->disp));
+}
+
+void load_graph_update_data (LoadGraph *graph);
+static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)
+
+static void
+load_graph_rescale (LoadGraph *graph) {
+ ///org/gnome/desktop/interface/text-scaling-factor
+ graph->fontsize = 8 * graph->font_settings->get_double ("text-scaling-factor");
+ graph->clear_background();
+
+ load_graph_queue_draw (graph);
+}
+
+static gboolean
+load_graph_configure (GtkWidget *widget,
+ GdkEventConfigure *event,
+ gpointer data_ptr)
+{
+ GtkAllocation allocation;
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+
+ load_graph_rescale (graph);
+
+ 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;
+}
+
+static void force_refresh (LoadGraph * const graph)
+{
+ graph->clear_background();
+ load_graph_queue_draw (graph);
+}
+
+static void
+load_graph_style_updated (GtkWidget *widget,
+ gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+ force_refresh (graph);
+}
+
+static gboolean
+load_graph_state_changed (GtkWidget *widget,
+ GtkStateFlags *flags,
+ gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+ force_refresh (graph);
+ graph->draw = gtk_widget_is_visible (widget);
+ return TRUE;
+}
+
+static gboolean
+load_graph_draw (GtkWidget *widget,
+ cairo_t * cr,
+ gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+
+ guint i;
+ gint j;
+ gdouble sample_width, x_offset;
+
+ /* Number of pixels wide for one sample point */
+ sample_width = (double)(graph->draw_width - graph->rmargin - graph->indent) / (double)graph->num_points;
+ /* Lines start at the right edge of the drawing,
+ * a bit outside the clip rectangle. */
+ x_offset = graph->draw_width - graph->rmargin + sample_width + 2;
+ /* Adjustment for smooth movement between samples */
+ x_offset -= sample_width * graph->render_counter / (double)graph->frames_per_unit;
+
+ /* draw the graph */
+
+ if (graph->background == NULL) {
+ draw_background(graph);
+ }
+ cairo_set_source_surface (cr, graph->background, 0, 0);
+ cairo_paint (cr);
+
+ 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->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);
+
+ bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
+ bool drawSmooth = GsmApplication::get()->config.draw_smooth;
+ for (j = graph->n-1; j >= 0; j--) {
+ gdk_cairo_set_source_rgba (cr, &(graph->colors [j]));
+ // Start drawing on the right at the correct height.
+ cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height + 3);
+ // then draw the path of the line.
+ // Loop starts at 1 because the curve accesses the 0th data point.
+ for (i = 1; i < graph->num_points; ++i) {
+ if (graph->data[i][j] == -1.0f)
+ continue;
+ if (drawSmooth) {
+ cairo_curve_to (cr,
+ x_offset - ((i - 0.5f) * graph->graph_delx),
+ (1.0 - graph->data[i-1][j]) * graph->real_draw_height + 3,
+ x_offset - ((i - 0.5f) * graph->graph_delx),
+ (1.0 - graph->data[i][j]) * graph->real_draw_height + 3,
+ x_offset - (i * graph->graph_delx),
+ (1.0 - graph->data[i][j]) * graph->real_draw_height + 3);
+ } else {
+ cairo_line_to (cr, x_offset - (i * graph->graph_delx),
+ (1.0 - graph->data[i][j]) * graph->real_draw_height + 3);
+ }
+
+ }
+ if (drawStacked) {
+ // Draw the remaining outline of the area:
+ // Left bottom corner
+ cairo_rel_line_to (cr, 0, graph->real_draw_height + 3);
+ // Right bottom corner. It's drawn far outside the visible area
+ // to avoid a weird bug where it's not filling the area it should completely.
+ cairo_rel_line_to (cr, x_offset * 2, 0);
+
+ //cairo_stroke_preserve(cr);
+ cairo_close_path(cr);
+ cairo_fill(cr);
+ } else {
+ cairo_stroke (cr);
+ }
+ }
+
+ return TRUE;
+}
+
+void
+load_graph_reset (LoadGraph *graph)
+{
+ std::fill(graph->data_block.begin(), graph->data_block.end(), -1.0);
+}
+
+static void
+get_load (LoadGraph *graph)
+{
+ guint i;
+ glibtop_cpu cpu;
+
+ glibtop_get_cpu (&cpu);
+
+ auto NOW = [&]() -> guint64 (&)[GLIBTOP_NCPU][N_CPU_STATES] { return graph->cpu.times[graph->cpu.now]; };
+ auto LAST = [&]() -> guint64 (&)[GLIBTOP_NCPU][N_CPU_STATES] { return 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
+ bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
+
+ 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;
+ if (drawStacked) {
+ graph->data[0][i] /= graph->n;
+ if (i > 0) {
+ graph->data[0][i] += graph->data[0][i-1];
+ }
+ }
+
+ /* Update label */
+ // Translators: CPU usage percentage label: 95.7%
+ 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;
+}
+
+
+namespace
+{
+
+ void set_memory_label_and_picker(GtkLabel* label, GsmColorButton* picker,
+ guint64 used, guint64 cached, guint64 total, double percent)
+ {
+ char* used_text;
+ char* cached_text;
+ char* cached_label;
+ char* total_text;
+ char* text;
+
+ used_text = format_byte_size(used, GsmApplication::get()->config.resources_memory_in_iec);
+ cached_text = format_byte_size(cached, GsmApplication::get()->config.resources_memory_in_iec);
+ total_text = format_byte_size(total, GsmApplication::get()->config.resources_memory_in_iec);
+ if (total == 0) {
+ text = g_strdup(_("not available"));
+ } else {
+ // xgettext: "540MiB (53 %) of 1.0 GiB" or "540MB (53 %) of 1.0 GB"
+ text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text);
+
+ if (cached != 0) {
+ // xgettext: Used cache string, e.g.: "Cache 2.4GiB" or "Cache 2.4GB"
+ cached_label = g_strdup_printf(_("Cache %s"), cached_text);
+ text = g_strdup_printf("%s\n%s", text, cached_label);
+ g_free (cached_label);
+ }
+ }
+ gtk_label_set_text(label, text);
+ g_free(used_text);
+ g_free(cached_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.cached, mem.total, mempercent);
+
+ set_memory_label_and_picker(GTK_LABEL(graph->labels.swap),
+ GSM_COLOR_BUTTON(graph->swap_color_picker),
+ swap.used, 0, swap.total, swappercent);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (graph->swap_color_picker), swap.total > 0);
+
+ graph->data[0][0] = graph->translate_to_log_partial_if_needed(mempercent);
+ graph->data[0][1] = swap.total>0 ? graph->translate_to_log_partial_if_needed(swappercent) : -1.0;
+}
+
+/* Nice Numbers for Graph Labels after Paul Heckbert
+ nicenum: find a "nice" number approximately equal to x.
+ Round the number if round=1, take ceiling if round=0 */
+
+static double
+nicenum (double x, int round)
+{
+ int expv; /* exponent of x */
+ double f; /* fractional part of x */
+ double nf; /* nice, rounded fraction */
+
+ expv = floor(log10(x));
+ f = x/pow(10.0, expv); /* between 1 and 10 */
+ if (round) {
+ if (f < 1.5)
+ nf = 1.0;
+ else if (f < 3.0)
+ nf = 2.0;
+ else if (f < 7.0)
+ nf = 5.0;
+ else
+ nf = 10.0;
+ } else {
+ if (f <= 1.0)
+ nf = 1.0;
+ else if (f <= 2.0)
+ nf = 2.0;
+ else if (f <= 5.0)
+ nf = 5.0;
+ else
+ nf = 10.0;
+ }
+ return nf * pow(10.0, expv);
+}
+
+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);
+ if (graph->latest == 0) {
+ graph->net.values[graph->num_points - 1] = dmax;
+ } else {
+ graph->net.values[graph->latest - 1] = dmax;
+ }
+
+ 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[graph->num_points - 1]);
+
+ //
+ // Round network maximum
+ //
+
+ const guint64 bak_max(new_max);
+
+ if (GsmApplication::get()->config.network_in_bits) {
+ // nice number is for the ticks
+ unsigned ticks = graph->num_bars();
+
+ // gets messy at low values due to division by 8
+ guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) );
+
+ // our tick size leads to max
+ double d = nicenum(bit_max/ticks, 0);
+ bit_max = ticks * d;
+ new_max = bit_max / 8;
+
+ procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f"
+ ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT,
+ bak_max*8, ticks, d, bit_max, new_max );
+ } 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;
+
+ 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 < graph->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;
+ guint64 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 sudden 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);
+
+ time = g_get_monotonic_time ();
+
+ if (in >= graph->net.last_in && out >= graph->net.last_out && graph->net.time != 0) {
+ float dtime;
+ dtime = ((double) (time - graph->net.time)) / G_USEC_PER_SEC;
+ din = static_cast<guint64>((in - graph->net.last_in) / dtime);
+ dout = static_cast<guint64>((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());
+}
+
+
+
+void
+load_graph_update_data (LoadGraph *graph)
+{
+ // Rotate data one element down.
+ std::rotate(graph->data.begin(),
+ graph->data.end() - 1,
+ graph->data.end());
+
+ // Update rotation counter.
+ graph->latest = (graph->latest + 1) % graph->num_points;
+
+ // Replace the 0th element
+ 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();
+ }
+}
+
+
+
+/* Updates the load graph when the timeout expires */
+static gboolean
+load_graph_update (gpointer user_data)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(user_data);
+
+ if (graph->render_counter == graph->frames_per_unit - 1)
+ load_graph_update_data(graph);
+
+ 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 (timer_index)
+ g_source_remove(timer_index);
+
+ clear_background();
+}
+
+
+
+static gboolean
+load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+
+ delete graph;
+
+ return FALSE;
+}
+
+
+LoadGraph::LoadGraph(guint type)
+ : fontsize(8.0),
+ rmargin(6 * fontsize),
+ indent(18.0),
+ n(0),
+ type(type),
+ speed(0),
+ num_points(0),
+ latest(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),
+ colors(),
+ data_block(),
+ data(),
+ main_widget(NULL),
+ disp(NULL),
+ background(NULL),
+ timer_index(0),
+ draw(FALSE),
+ labels(),
+ mem_color_picker(NULL),
+ swap_color_picker(NULL),
+ font_settings(Gio::Settings::create (FONT_SETTINGS_SCHEMA)),
+ cpu(),
+ net()
+{
+ LoadGraph * const graph = this;
+ font_settings->signal_changed(FONT_SETTING_SCALING).connect([this](const Glib::ustring&) { load_graph_rescale (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:
+ cpu = CPU {};
+ n = GsmApplication::get()->config.num_cpus;
+
+ for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i)
+ labels.cpu[i] = make_tnum_label ();
+
+ break;
+
+ case LOAD_GRAPH_MEM:
+ n = 2;
+ labels.memory = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.memory), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.memory), GTK_ALIGN_START);
+ gtk_widget_show (GTK_WIDGET (labels.memory));
+ labels.swap = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.swap), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.swap), GTK_ALIGN_START);
+ gtk_widget_show (GTK_WIDGET (labels.swap));
+ break;
+
+ case LOAD_GRAPH_NET:
+ net = NET {};
+ n = 2;
+ net.max = 1;
+ labels.net_in = make_tnum_label ();
+ gtk_label_set_width_chars(labels.net_in, 10);
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_in), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_in), GTK_ALIGN_END);
+ gtk_widget_show (GTK_WIDGET (labels.net_in));
+
+ labels.net_in_total = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_END);
+ gtk_label_set_width_chars(labels.net_in_total, 10);
+ gtk_widget_show (GTK_WIDGET (labels.net_in_total));
+
+ labels.net_out = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_out), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
+ gtk_label_set_width_chars(labels.net_out, 10);
+ gtk_widget_show (GTK_WIDGET (labels.net_out));
+
+ labels.net_out_total = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_out_total), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
+ gtk_label_set_width_chars(labels.net_out_total, 10);
+ gtk_widget_show (GTK_WIDGET (labels.net_out_total));
+
+ break;
+ }
+
+ speed = GsmApplication::get()->config.graph_update_interval;
+
+ num_points = GsmApplication::get()->config.graph_data_points + 2;
+
+ colors.resize(n);
+
+ switch (type) {
+ case LOAD_GRAPH_CPU:
+ memcpy(&colors[0], GsmApplication::get()->config.cpu_color,
+ n * sizeof colors[0]);
+ break;
+ case LOAD_GRAPH_MEM:
+ colors[0] = GsmApplication::get()->config.mem_color;
+ colors[1] = GsmApplication::get()->config.swap_color;
+ mem_color_picker = gsm_color_button_new (&colors[0],
+ GSMCP_TYPE_PIE);
+ swap_color_picker = gsm_color_button_new (&colors[1],
+ GSMCP_TYPE_PIE);
+ break;
+ case LOAD_GRAPH_NET:
+ net.values = std::vector<unsigned>(num_points);
+ colors[0] = GsmApplication::get()->config.net_in_color;
+ colors[1] = GsmApplication::get()->config.net_out_color;
+ break;
+ }
+
+ timer_index = 0;
+ render_counter = (frames_per_unit - 1);
+ draw = FALSE;
+
+ main_widget = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
+ gtk_widget_set_size_request(GTK_WIDGET (main_widget), -1, LoadGraph::GRAPH_MIN_HEIGHT);
+ gtk_widget_show (GTK_WIDGET (main_widget));
+
+ disp = GTK_DRAWING_AREA (gtk_drawing_area_new ());
+ gtk_widget_show (GTK_WIDGET (disp));
+ g_signal_connect (G_OBJECT (disp), "draw",
+ G_CALLBACK (load_graph_draw), graph);
+ g_signal_connect (G_OBJECT(disp), "configure_event",
+ G_CALLBACK (load_graph_configure), graph);
+ g_signal_connect (G_OBJECT(disp), "destroy",
+ G_CALLBACK (load_graph_destroy), graph);
+ g_signal_connect (G_OBJECT(disp), "state-flags-changed",
+ G_CALLBACK (load_graph_state_changed), graph);
+ g_signal_connect (G_OBJECT(disp), "style-updated",
+ G_CALLBACK (load_graph_style_updated), graph);
+
+ gtk_widget_set_events (GTK_WIDGET (disp), GDK_EXPOSURE_MASK);
+
+ gtk_box_pack_start (main_widget, GTK_WIDGET (disp), TRUE, TRUE, 0);
+
+ data = std::vector<double*>(num_points);
+ /* Allocate data in a contiguous block */
+ data_block = std::vector<double>(n * num_points, -1.0);
+
+ for (guint i = 0; i < num_points; ++i)
+ data[i] = &data_block[0] + i * n;
+
+ gtk_widget_show_all (GTK_WIDGET (main_widget));
+}
+
+void
+load_graph_start (LoadGraph *graph)
+{
+ if (!graph->timer_index) {
+ // Update the data two times so the graph
+ // doesn't wait one cycle to start drawing.
+ load_graph_update_data(graph);
+ load_graph_update(graph);
+
+ graph->timer_index = g_timeout_add (graph->speed,
+ 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;
+
+ if (graph->timer_index) {
+ g_source_remove (graph->timer_index);
+ graph->timer_index = g_timeout_add (graph->speed,
+ load_graph_update,
+ graph);
+ }
+
+ graph->clear_background();
+}
+
+void
+load_graph_change_num_points(LoadGraph *graph,
+ guint new_num_points)
+{
+ // Don't do anything if the value didn't change.
+ if (graph->num_points == new_num_points)
+ return;
+
+ // Sort the values in the data_block vector in the order they were accessed in by the pointers in data.
+ std::rotate(graph->data_block.begin(),
+ graph->data_block.begin() + (graph->num_points - graph->latest) * graph->n,
+ graph->data_block.end());
+
+ // Reset rotation counter.
+ graph->latest = 0;
+
+ // Resize the vectors to the new amount of data points.
+ // Fill the new values with -1.
+ graph->data.resize(new_num_points);
+ graph->data_block.resize(graph->n * new_num_points, -1.0);
+ if (graph->type == LOAD_GRAPH_NET) {
+ graph->net.values.resize(new_num_points);
+ }
+
+ // Replace the pointers in data, to match the new data_block values.
+ for (guint i = 0; i < new_num_points; ++i) {
+ graph->data[i] = &graph->data_block[0] + i * graph->n;
+ }
+
+ // Set the actual number of data points to be used by the graph.
+ graph->num_points = new_num_points;
+
+ // Force the scale to be redrawn.
+ graph->clear_background();
+}
+
+
+LoadGraphLabels*
+load_graph_get_labels (LoadGraph *graph)
+{
+ return &graph->labels;
+}
+
+GtkBox*
+load_graph_get_widget (LoadGraph *graph)
+{
+ return graph->main_widget;
+}
+
+GsmColorButton*
+load_graph_get_mem_color_picker(LoadGraph *graph)
+{
+ return graph->mem_color_picker;
+}
+
+GsmColorButton*
+load_graph_get_swap_color_picker(LoadGraph *graph)
+{
+ return graph->swap_color_picker;
+}