[gnome-builder] rg: add realtime scrolling graph implementation
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] rg: add realtime scrolling graph implementation
- Date: Mon, 1 Jun 2015 23:47:36 +0000 (UTC)
commit f111bb7928c84b3accd52cf6020481e5aa28bd91
Author: Christian Hergert <christian hergert me>
Date: Mon Jun 1 16:47:20 2015 -0700
rg: add realtime scrolling graph implementation
Lightly based on the ideas from writing Uber Graph years ago. However, this
is much more simplistic in that we don't yet do the ring buffer for the
backing pixmap. It makes it much simpler to wrap my head around in the
process (while costing a bit more CPU). Last I checked (a few years ago)
the pixmap saved an extra 5% CPU depending on the number of data points
as well size of the graph. At some point, we will probably add this.
The CPU value parser is rather crappy. sscanf() is generally pertty slow,
so if you feel like optimizing this, go for it. It would be nice to have
it abstracted to support FreeBSD/OpenBSD/etc as well.
We still need to add scales and other necessary components around the
graph to allow them to be interacted with. But it's time to get this
in the primary repository for review.
I assume long term we'll make these visible when we execute the program.
One of the best things I ever did when developing previous software was
ensure I watched CPU/Memory/Network graphs while testing and debugging
code. I'd like to see that here as well.
To begin with, we'll probably make an additional side pane that can be
enabled/disabled with an easter egg for now to play with various graphs.
To have a go at the graph and test how smooth it can render on your system,
use the test application in tests/
./test-cpu-graph -m 1 -s 60
The above command will perform 1 sample per second with a visible range of
60 seconds on the graph. The Y axis is from 100.0% to 0.0%.
configure.ac | 2 +
contrib/rg/Makefile.am | 37 +++
contrib/rg/rg-column-private.h | 49 ++++
contrib/rg/rg-column.c | 305 ++++++++++++++++++++++
contrib/rg/rg-column.h | 43 +++
contrib/rg/rg-cpu-table.c | 272 +++++++++++++++++++
contrib/rg/rg-cpu-table.h | 34 +++
contrib/rg/rg-graph.c | 399 ++++++++++++++++++++++++++++
contrib/rg/rg-graph.h | 48 ++++
contrib/rg/rg-line-renderer.c | 324 +++++++++++++++++++++++
contrib/rg/rg-line-renderer.h | 41 +++
contrib/rg/rg-renderer.c | 58 ++++
contrib/rg/rg-renderer.h | 57 ++++
contrib/rg/rg-ring.c | 206 +++++++++++++++
contrib/rg/rg-ring.h | 80 ++++++
contrib/rg/rg-table.c | 560 ++++++++++++++++++++++++++++++++++++++++
contrib/rg/rg-table.h | 73 ++++++
tests/Makefile.am | 22 ++-
tests/test-cpu-graph.c | 102 ++++++++
19 files changed, 2711 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1adb4b6..db20c56 100644
--- a/configure.ac
+++ b/configure.ac
@@ -161,6 +161,7 @@ PKG_CHECK_MODULES(LIBIDE, [gio-2.0 >= glib_required_version
gjs-1.0 >= gjs_required_version
gjs-internals-1.0 >= gjs_required_version
pygobject-3.0 >= pygobject_required_version])
+PKG_CHECK_MODULES(RG, [gtk+-3.0 >= gtk_required_version])
PKG_CHECK_MODULES(SEARCH, [glib-2.0 >= glib_required_version])
PKG_CHECK_MODULES(GEDIT, [glib-2.0 >= glib_required_version
gtk+-3.0 >= gtk_required_version])
@@ -366,6 +367,7 @@ AC_CONFIG_FILES([
contrib/gedit/Makefile
contrib/libeditorconfig/Makefile
contrib/nautilus/Makefile
+ contrib/rg/Makefile
contrib/search/Makefile
contrib/xml/Makefile
diff --git a/contrib/rg/Makefile.am b/contrib/rg/Makefile.am
new file mode 100644
index 0000000..75b01fb
--- /dev/null
+++ b/contrib/rg/Makefile.am
@@ -0,0 +1,37 @@
+noinst_LTLIBRARIES = librg.la
+
+librg_la_SOURCES = \
+ rg-column.c \
+ rg-column.h \
+ rg-column-private.h \
+ rg-cpu-table.c \
+ rg-cpu-table.h \
+ rg-graph.c \
+ rg-graph.h \
+ rg-line-renderer.c \
+ rg-line-renderer.h \
+ rg-renderer.c \
+ rg-renderer.h \
+ rg-ring.c \
+ rg-ring.h \
+ rg-table.c \
+ rg-table.h \
+ $(NULL)
+
+librg_la_CFLAGS = \
+ -I$(top_srcdir)/contrib/egg \
+ $(DEBUG_CFLAGS) \
+ $(OPTIMIZE_CFLAGS) \
+ $(RG_CFLAGS) \
+ $(NULL)
+
+librg_la_LIBADD = \
+ $(RG_LIBS) \
+ $(top_builddir)/contrib/egg/libegg.la \
+ $(NULL)
+
+librg_la_LDFLAGS = \
+ $(OPTIMIZE_LDFLAGS) \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/rg/rg-column-private.h b/contrib/rg/rg-column-private.h
new file mode 100644
index 0000000..03f4d34
--- /dev/null
+++ b/contrib/rg/rg-column-private.h
@@ -0,0 +1,49 @@
+/* rg-column-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_COLUMN_PRIVATE_H
+#define RG_COLUMN_PRIVATE_H
+
+#include <glib-object.h>
+
+#include "rg-column.h"
+
+G_BEGIN_DECLS
+
+void _rg_column_get_value (RgColumn *self,
+ guint index,
+ GValue *value);
+void _rg_column_collect (RgColumn *self,
+ guint index,
+ va_list args);
+void _rg_column_lcopy (RgColumn *self,
+ guint index,
+ va_list args);
+void _rg_column_get (RgColumn *column,
+ guint index,
+ ...);
+void _rg_column_set (RgColumn *column,
+ guint index,
+ ...);
+guint _rg_column_push (RgColumn *column);
+void _rg_column_set_n_rows (RgColumn *column,
+ guint n_rows);
+
+G_END_DECLS
+
+#endif /* RG_COLUMN_PRIVATE_H */
diff --git a/contrib/rg/rg-column.c b/contrib/rg/rg-column.c
new file mode 100644
index 0000000..795906c
--- /dev/null
+++ b/contrib/rg/rg-column.c
@@ -0,0 +1,305 @@
+/* rg-column.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+
+#include "rg-column.h"
+#include "rg-column-private.h"
+#include "rg-ring.h"
+
+struct _RgColumn
+{
+ GObject parent_instance;
+ gchar *name;
+ RgRing *values;
+ GType value_type;
+};
+
+G_DEFINE_TYPE (RgColumn, rg_column, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_VALUE_TYPE,
+ LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+RgColumn *
+rg_column_new (const gchar *name,
+ GType value_type)
+{
+ return g_object_new (RG_TYPE_COLUMN,
+ "name", name,
+ "value-type", value_type,
+ NULL);
+}
+
+const gchar *
+rg_column_get_name (RgColumn *self)
+{
+ g_return_val_if_fail (RG_IS_COLUMN (self), NULL);
+
+ return self->name;
+}
+
+void
+rg_column_set_name (RgColumn *self,
+ const gchar *name)
+{
+ g_return_if_fail (RG_IS_COLUMN (self));
+
+ if (g_strcmp0 (name, self->name) != 0)
+ {
+ g_free (self->name);
+ self->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_NAME]);
+ }
+}
+
+static void
+rg_column_copy_value (gpointer data,
+ gpointer user_data)
+{
+ const GValue *src_value = data;
+ RgRing *ring = user_data;
+ GValue copy = G_VALUE_INIT;
+
+ if (G_IS_VALUE (src_value))
+ {
+ g_value_init (©, G_VALUE_TYPE (src_value));
+ g_value_copy (src_value, ©);
+ }
+
+ rg_ring_append_val (ring, copy);
+}
+
+void
+_rg_column_set_n_rows (RgColumn *self,
+ guint n_rows)
+{
+ RgRing *ring;
+
+ g_return_if_fail (RG_IS_COLUMN (self));
+ g_return_if_fail (n_rows > 0);
+
+ ring = rg_ring_sized_new (sizeof (GValue), n_rows, NULL);
+ rg_ring_foreach (self->values, rg_column_copy_value, ring);
+ g_clear_pointer (&self->values, rg_ring_unref);
+ self->values = ring;
+}
+
+guint
+_rg_column_push (RgColumn *self)
+{
+ GValue value = G_VALUE_INIT;
+ guint ret;
+
+ g_return_val_if_fail (RG_IS_COLUMN (self), 0);
+
+ g_value_init (&value, self->value_type);
+ ret = rg_ring_append_val (self->values, value);
+
+ return ret;
+}
+
+void
+_rg_column_get_value (RgColumn *self,
+ guint index,
+ GValue *value)
+{
+ const GValue *src_value;
+
+ g_return_if_fail (RG_IS_COLUMN (self));
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (index < self->values->len);
+
+ src_value = &((GValue *)(gpointer)self->values->data)[index];
+
+ g_value_init (value, self->value_type);
+ if (G_IS_VALUE (src_value))
+ g_value_copy (src_value, value);
+}
+
+void
+_rg_column_collect (RgColumn *self,
+ guint index,
+ va_list args)
+{
+ GValue *value;
+ gchar *errmsg = NULL;
+
+ g_return_if_fail (RG_IS_COLUMN (self));
+ g_return_if_fail (index < self->values->len);
+
+ value = &((GValue *)(gpointer)self->values->data)[index];
+
+ G_VALUE_COLLECT (value, args, 0, &errmsg);
+
+ if (G_UNLIKELY (errmsg != NULL))
+ {
+ g_critical ("%s", errmsg);
+ g_free (errmsg);
+ }
+}
+
+void
+_rg_column_set (RgColumn *self,
+ guint index,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (RG_IS_COLUMN (self));
+ g_return_if_fail (index < self->values->len);
+
+ va_start (args, index);
+ _rg_column_collect (self, index, args);
+ va_end (args);
+}
+
+void
+_rg_column_get (RgColumn *self,
+ guint index,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (RG_IS_COLUMN (self));
+ g_return_if_fail (index < self->values->len);
+
+ va_start (args, index);
+ _rg_column_lcopy (self, index, args);
+ va_end (args);
+}
+
+void
+_rg_column_lcopy (RgColumn *self,
+ guint index,
+ va_list args)
+{
+ const GValue *value;
+ gchar *errmsg = NULL;
+
+ g_return_if_fail (RG_IS_COLUMN (self));
+ g_return_if_fail (index < self->values->len);
+
+ value = &((GValue *)(gpointer)self->values->data)[index];
+
+ if (!G_IS_VALUE (value))
+ return;
+
+ G_VALUE_LCOPY (value, args, 0, &errmsg);
+
+ if (G_UNLIKELY (errmsg != NULL))
+ {
+ g_critical ("%s", errmsg);
+ g_free (errmsg);
+ }
+}
+
+static void
+rg_column_finalize (GObject *object)
+{
+ RgColumn *self = (RgColumn *)object;
+
+ g_clear_pointer (&self->name, g_free);
+ g_clear_pointer (&self->values, rg_ring_unref);
+
+ G_OBJECT_CLASS (rg_column_parent_class)->finalize (object);
+}
+
+static void
+rg_column_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RgColumn *self = RG_COLUMN (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, rg_column_get_name (self));
+ break;
+
+ case PROP_VALUE_TYPE:
+ g_value_set_gtype (value, self->value_type);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_column_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RgColumn *self = RG_COLUMN (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ rg_column_set_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_VALUE_TYPE:
+ self->value_type = g_value_get_gtype (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_column_class_init (RgColumnClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = rg_column_finalize;
+ object_class->get_property = rg_column_get_property;
+ object_class->set_property = rg_column_set_property;
+
+ gParamSpecs [PROP_NAME] =
+ g_param_spec_string ("name",
+ _("Name"),
+ _("The name of the column"),
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_VALUE_TYPE] =
+ g_param_spec_gtype ("value-type",
+ _("Value Type"),
+ _("Value Type"),
+ G_TYPE_NONE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+}
+
+static void
+rg_column_init (RgColumn *self)
+{
+ self->values = rg_ring_sized_new (sizeof (GValue), 60, (GDestroyNotify)g_value_unset);
+}
diff --git a/contrib/rg/rg-column.h b/contrib/rg/rg-column.h
new file mode 100644
index 0000000..65d581a
--- /dev/null
+++ b/contrib/rg/rg-column.h
@@ -0,0 +1,43 @@
+/* rg-column.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_COLUMN_H
+#define RG_COLUMN_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define RG_TYPE_COLUMN (rg_column_get_type())
+
+G_DECLARE_FINAL_TYPE (RgColumn, rg_column, RG, COLUMN, GObject)
+
+struct _RgColumnClass
+{
+ GObjectClass parent;
+};
+
+RgColumn *rg_column_new (const gchar *name,
+ GType value_type);
+const gchar *rg_column_get_name (RgColumn *self);
+void rg_column_set_name (RgColumn *self,
+ const gchar *name);
+
+G_END_DECLS
+
+#endif /* RG_COLUMN_H */
diff --git a/contrib/rg/rg-cpu-table.c b/contrib/rg/rg-cpu-table.c
new file mode 100644
index 0000000..13134c1
--- /dev/null
+++ b/contrib/rg/rg-cpu-table.c
@@ -0,0 +1,272 @@
+/* rg-cpu-table.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __linux__
+# error "This file only supports Linux."
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include "rg-cpu-table.h"
+
+typedef struct
+{
+ gdouble total;
+ gdouble freq;
+ glong last_user;
+ glong last_idle;
+ glong last_system;
+ glong last_nice;
+ glong last_iowait;
+ glong last_irq;
+ glong last_softirq;
+ glong last_steal;
+ glong last_guest;
+ glong last_guest_nice;
+} CpuInfo;
+
+struct _RgCpuTable
+{
+ RgTable parent_instance;
+
+ GArray *cpu_info;
+ guint n_cpu;
+
+ guint poll_source;
+ guint poll_interval_msec;
+};
+
+G_DEFINE_TYPE (RgCpuTable, rg_cpu_table, RG_TYPE_TABLE)
+
+#ifdef __linux__
+static void
+rg_cpu_table_poll (RgCpuTable *self)
+{
+ gchar cpu[64] = { 0 };
+ glong user;
+ glong sys;
+ glong nice;
+ glong idle;
+ glong iowait;
+ glong irq;
+ glong softirq;
+ glong steal;
+ glong guest;
+ glong guest_nice;
+ glong user_calc;
+ glong system_calc;
+ glong nice_calc;
+ glong idle_calc;
+ glong iowait_calc;
+ glong irq_calc;
+ glong softirq_calc;
+ glong steal_calc;
+ glong guest_calc;
+ glong guest_nice_calc;
+ gchar *buf = NULL;
+ glong total;
+ gchar *line;
+ gint ret;
+ gint id;
+ gint i;
+
+ if (g_file_get_contents("/proc/stat", &buf, NULL, NULL))
+ {
+ line = buf;
+ for (i = 0; buf[i]; i++)
+ {
+ if (buf[i] == '\n') {
+ buf[i] = '\0';
+ if (g_str_has_prefix(line, "cpu"))
+ {
+ if (isdigit(line[3]))
+ {
+ CpuInfo *cpu_info;
+
+ user = nice = sys = idle = id = 0;
+ ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
+ cpu, &user, &nice, &sys, &idle,
+ &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
+ if (ret != 11)
+ goto next;
+
+ ret = sscanf(cpu, "cpu%d", &id);
+
+ if (ret != 1 || id < 0 || id >= self->n_cpu)
+ goto next;
+
+ cpu_info = &g_array_index (self->cpu_info, CpuInfo, id);
+
+ user_calc = user - cpu_info->last_user;
+ nice_calc = nice - cpu_info->last_nice;
+ system_calc = sys - cpu_info->last_system;
+ idle_calc = idle - cpu_info->last_idle;
+ iowait_calc = iowait - cpu_info->last_iowait;
+ irq_calc = irq - cpu_info->last_irq;
+ softirq_calc = softirq - cpu_info->last_softirq;
+ steal_calc = steal - cpu_info->last_steal;
+ guest_calc = guest - cpu_info->last_guest;
+ guest_nice_calc = guest_nice - cpu_info->last_guest_nice;
+
+ total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc +
softirq_calc + steal_calc + guest_calc + guest_nice_calc;
+ cpu_info->total = ((total - idle_calc) / (gdouble)total) * 100.0;
+
+ cpu_info->last_user = user;
+ cpu_info->last_nice = nice;
+ cpu_info->last_idle = idle;
+ cpu_info->last_system = sys;
+ cpu_info->last_iowait = iowait;
+ cpu_info->last_irq = irq;
+ cpu_info->last_softirq = softirq;
+ cpu_info->last_steal = steal;
+ cpu_info->last_guest = guest;
+ cpu_info->last_guest_nice = guest_nice;
+ }
+ } else {
+ /* CPU info comes first. Skip further lines. */
+ break;
+ }
+
+ next:
+ line = &buf[i + 1];
+ }
+ }
+ }
+
+ g_free (buf);
+}
+#else
+static void
+rg_cpu_table_poll (RgCpuTable *self)
+{
+ /*
+ * TODO: calculate cpu info for FreeBSD/OpenBSD/etc.
+ *
+ * While we are at it, we should make the Linux code above non-shitty.
+ */
+}
+#endif
+
+static gboolean
+rg_cpu_table_poll_cb (gpointer user_data)
+{
+ RgCpuTable *self = user_data;
+ RgTableIter iter;
+ guint i;
+
+ rg_cpu_table_poll (self);
+
+ rg_table_push (RG_TABLE (self), &iter, g_get_monotonic_time ());
+
+ for (i = 0; i < self->cpu_info->len; i++)
+ {
+ CpuInfo *cpu_info;
+
+ cpu_info = &g_array_index (self->cpu_info, CpuInfo, i);
+ rg_table_iter_set (&iter, i, cpu_info->total, -1);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+rg_cpu_table_constructed (GObject *object)
+{
+ RgCpuTable *self = (RgCpuTable *)object;
+ gint64 timespan;
+ guint max_samples;
+ guint i;
+
+ G_OBJECT_CLASS (rg_cpu_table_parent_class)->constructed (object);
+
+ max_samples = rg_table_get_max_samples (RG_TABLE (self));
+ timespan = rg_table_get_timespan (RG_TABLE (self));
+
+ self->poll_interval_msec = (gdouble)timespan / (gdouble)(max_samples - 1) / 1000L;
+
+ if (self->poll_interval_msec == 0)
+ {
+ g_critical ("Implausible timespan/max_samples combination for graph.");
+ self->poll_interval_msec = 1000;
+ }
+
+ self->n_cpu = g_get_num_processors ();
+
+ for (i = 0; i < self->n_cpu; i++)
+ {
+ CpuInfo cpu_info = { 0 };
+ RgColumn *column;
+ gchar *name;
+
+ name = g_strdup_printf ("CPU %d", i + 1);
+ column = rg_column_new (name, G_TYPE_DOUBLE);
+
+ rg_table_add_column (RG_TABLE (self), column);
+ g_array_append_val (self->cpu_info, cpu_info);
+
+ g_object_unref (column);
+ g_free (name);
+ }
+
+ rg_cpu_table_poll (self);
+
+ self->poll_source = g_timeout_add (self->poll_interval_msec, rg_cpu_table_poll_cb, self);
+}
+
+static void
+rg_cpu_table_finalize (GObject *object)
+{
+ RgCpuTable *self = (RgCpuTable *)object;
+
+ if (self->poll_source != 0)
+ {
+ g_source_remove (self->poll_source);
+ self->poll_source = 0;
+ }
+
+ g_clear_pointer (&self->cpu_info, g_array_unref);
+
+ G_OBJECT_CLASS (rg_cpu_table_parent_class)->finalize (object);
+}
+
+static void
+rg_cpu_table_class_init (RgCpuTableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = rg_cpu_table_constructed;
+ object_class->finalize = rg_cpu_table_finalize;
+}
+
+static void
+rg_cpu_table_init (RgCpuTable *self)
+{
+ self->cpu_info = g_array_new (FALSE, FALSE, sizeof (CpuInfo));
+
+ g_object_set (self,
+ "value-min", 0.0,
+ "value-max", 100.0,
+ NULL);
+}
+
+RgTable *
+rg_cpu_table_new (void)
+{
+ return g_object_new (RG_TYPE_CPU_TABLE, NULL);
+}
diff --git a/contrib/rg/rg-cpu-table.h b/contrib/rg/rg-cpu-table.h
new file mode 100644
index 0000000..b723d7f
--- /dev/null
+++ b/contrib/rg/rg-cpu-table.h
@@ -0,0 +1,34 @@
+/* rg-cpu-table.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_CPU_TABLE_H
+#define RG_CPU_TABLE_H
+
+#include "rg-table.h"
+
+G_BEGIN_DECLS
+
+#define RG_TYPE_CPU_TABLE (rg_cpu_table_get_type())
+
+G_DECLARE_FINAL_TYPE (RgCpuTable, rg_cpu_table, RG, CPU_TABLE, RgTable)
+
+RgTable *rg_cpu_table_new (void);
+
+G_END_DECLS
+
+#endif /* RG_CPU_TABLE_H */
diff --git a/contrib/rg/rg-graph.c b/contrib/rg/rg-graph.c
new file mode 100644
index 0000000..6578f48
--- /dev/null
+++ b/contrib/rg/rg-graph.c
@@ -0,0 +1,399 @@
+/* rg-graph.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "egg-signal-group.h"
+
+#include "rg-graph.h"
+
+typedef struct
+{
+ RgTable *table;
+ EggSignalGroup *table_signals;
+ GPtrArray *renderers;
+ cairo_surface_t *surface;
+ guint tick_handler;
+ gint x_offset;
+ guint surface_dirty : 1;
+} RgGraphPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (RgGraph, rg_graph, GTK_TYPE_DRAWING_AREA)
+
+enum {
+ PROP_0,
+ PROP_TABLE,
+ LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+GtkWidget *
+rg_graph_new (void)
+{
+ return g_object_new (RG_TYPE_GRAPH, NULL);
+}
+
+static void
+rg_graph_clear_surface (RgGraph *self)
+{
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ g_assert (RG_IS_GRAPH (self));
+
+ priv->surface_dirty = TRUE;
+}
+
+/**
+ * rg_graph_get_table:
+ *
+ * Gets the #RgGraph:table property.
+ *
+ * Returns: (transfer none) (nullable): An #RgTable or %NULL.
+ */
+RgTable *
+rg_graph_get_table (RgGraph *self)
+{
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ g_return_val_if_fail (RG_IS_GRAPH (self), NULL);
+
+ return priv->table;
+}
+
+void
+rg_graph_set_table (RgGraph *self,
+ RgTable *table)
+{
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ g_return_if_fail (RG_IS_GRAPH (self));
+ g_return_if_fail (!table || RG_IS_TABLE (table));
+
+ if (g_set_object (&priv->table, table))
+ {
+ egg_signal_group_set_target (priv->table_signals, table);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_TABLE]);
+ }
+}
+
+void
+rg_graph_add_renderer (RgGraph *self,
+ RgRenderer *renderer)
+{
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ g_return_if_fail (RG_IS_GRAPH (self));
+ g_return_if_fail (RG_IS_RENDERER (renderer));
+
+ g_ptr_array_add (priv->renderers, g_object_ref (renderer));
+ rg_graph_clear_surface (self);
+}
+
+static gboolean
+rg_graph_tick_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ RgGraph *self = (RgGraph *)widget;
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+ GtkAllocation alloc;
+ gint64 frame_time;
+ gint64 end_time;
+ gint64 timespan;
+
+ g_assert (RG_IS_GRAPH (self));
+
+ if ((priv->surface == NULL) || (priv->table == NULL))
+ {
+ if (priv->tick_handler != 0)
+ {
+ gtk_widget_remove_tick_callback (widget, priv->tick_handler);
+ priv->tick_handler = 0;
+ }
+
+ return G_SOURCE_REMOVE;
+ }
+
+ gtk_widget_get_allocation (widget, &alloc);
+
+ frame_time = gdk_frame_clock_get_frame_time (frame_clock);
+ end_time = rg_table_get_end_time (priv->table);
+ timespan = rg_table_get_timespan (priv->table);
+
+ priv->x_offset = -((frame_time - end_time) / (gdouble)timespan * alloc.width);
+
+ gtk_widget_queue_draw (widget);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+rg_graph_ensure_surface (RgGraph *self)
+{
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+ GtkAllocation alloc;
+ RgTableIter iter;
+ gint64 begin_time;
+ gint64 end_time;
+ gdouble y_begin;
+ gdouble y_end;
+ cairo_t *cr;
+ gsize i;
+
+ g_assert (RG_IS_GRAPH (self));
+
+ gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+ if (priv->surface == NULL)
+ {
+ priv->surface_dirty = TRUE;
+ priv->surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (self)),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ alloc.width,
+ alloc.height);
+ }
+
+ if (priv->table == NULL)
+ return;
+
+ if (priv->surface_dirty)
+ {
+ priv->surface_dirty = FALSE;
+
+ cr = cairo_create (priv->surface);
+
+ cairo_save (cr);
+ gdk_cairo_rectangle (cr, &alloc);
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ g_object_get (priv->table,
+ "value-min", &y_begin,
+ "value-max", &y_end,
+ NULL);
+
+ rg_table_get_iter_last (priv->table, &iter);
+ end_time = rg_table_iter_get_timestamp (&iter);
+ begin_time = end_time - rg_table_get_timespan (priv->table);
+
+ for (i = 0; i < priv->renderers->len; i++)
+ {
+ RgRenderer *renderer;
+
+ renderer = g_ptr_array_index (priv->renderers, i);
+
+ cairo_save (cr);
+ rg_renderer_render (renderer, priv->table, begin_time, end_time, y_begin, y_end, cr, &alloc);
+ cairo_restore (cr);
+ }
+
+ cairo_destroy (cr);
+
+ priv->x_offset = 0;
+ }
+
+ if (priv->tick_handler == 0)
+ priv->tick_handler = gtk_widget_add_tick_callback (GTK_WIDGET (self),
+ rg_graph_tick_cb,
+ self,
+ NULL);
+}
+
+static gboolean
+rg_graph_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ RgGraph *self = (RgGraph *)widget;
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+ GtkStyleContext *style_context;
+ GtkAllocation alloc;
+
+ g_assert (RG_IS_GRAPH (self));
+
+ gtk_widget_get_allocation (widget, &alloc);
+
+ style_context = gtk_widget_get_style_context (widget);
+
+ rg_graph_ensure_surface (self);
+
+ gtk_style_context_save (style_context);
+ gtk_style_context_add_class (style_context, "view");
+ gtk_render_background (style_context, cr, alloc.x, alloc.y, alloc.width, alloc.height);
+ gtk_style_context_restore (style_context);
+
+ cairo_save (cr);
+ cairo_set_source_surface (cr, priv->surface, priv->x_offset, 0);
+ gdk_cairo_rectangle (cr, &alloc);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+rg_graph_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc)
+{
+ RgGraph *self = (RgGraph *)widget;
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ g_assert (RG_IS_GRAPH (self));
+ g_assert (alloc != NULL);
+
+ g_clear_pointer (&priv->surface, cairo_surface_destroy);
+
+ GTK_WIDGET_CLASS (rg_graph_parent_class)->size_allocate (widget, alloc);
+}
+
+static void
+rg_graph__table_changed (RgGraph *self,
+ RgTable *table)
+{
+ g_assert (RG_IS_GRAPH (self));
+ g_assert (RG_IS_TABLE (table));
+
+ rg_graph_clear_surface (self);
+}
+
+static void
+rg_graph_destroy (GtkWidget *widget)
+{
+ RgGraph *self = (RgGraph *)widget;
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ if (priv->tick_handler != 0)
+ {
+ gtk_widget_remove_tick_callback (widget, priv->tick_handler);
+ priv->tick_handler = 0;
+ }
+
+ GTK_WIDGET_CLASS (rg_graph_parent_class)->destroy (widget);
+}
+
+static void
+rg_graph_finalize (GObject *object)
+{
+ RgGraph *self = (RgGraph *)object;
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ g_clear_object (&priv->table);
+ g_clear_object (&priv->table_signals);
+ g_clear_pointer (&priv->surface, cairo_surface_destroy);
+ g_clear_pointer (&priv->renderers, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (rg_graph_parent_class)->finalize (object);
+}
+
+static void
+rg_graph_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RgGraph *self = RG_GRAPH (object);
+
+ switch (prop_id)
+ {
+ case PROP_TABLE:
+ g_value_set_object (value, rg_graph_get_table (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_graph_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RgGraph *self = RG_GRAPH (object);
+
+ switch (prop_id)
+ {
+ case PROP_TABLE:
+ rg_graph_set_table (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_graph_class_init (RgGraphClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = rg_graph_finalize;
+ object_class->get_property = rg_graph_get_property;
+ object_class->set_property = rg_graph_set_property;
+
+ widget_class->destroy = rg_graph_destroy;
+ widget_class->draw = rg_graph_draw;
+ widget_class->size_allocate = rg_graph_size_allocate;
+
+ gParamSpecs [PROP_TABLE] =
+ g_param_spec_object ("table",
+ _("Table"),
+ _("The data table for the graph."),
+ RG_TYPE_TABLE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+}
+
+static void
+rg_graph_init (RgGraph *self)
+{
+ RgGraphPrivate *priv = rg_graph_get_instance_private (self);
+
+ priv->renderers = g_ptr_array_new_with_free_func (g_object_unref);
+
+ priv->table_signals = egg_signal_group_new (RG_TYPE_TABLE);
+
+ egg_signal_group_connect_object (priv->table_signals,
+ "notify::value-max",
+ G_CALLBACK (gtk_widget_queue_resize),
+ self,
+ G_CONNECT_SWAPPED);
+
+ egg_signal_group_connect_object (priv->table_signals,
+ "notify::value-min",
+ G_CALLBACK (gtk_widget_queue_resize),
+ self,
+ G_CONNECT_SWAPPED);
+
+ egg_signal_group_connect_object (priv->table_signals,
+ "notify::timespan",
+ G_CALLBACK (gtk_widget_queue_resize),
+ self,
+ G_CONNECT_SWAPPED);
+
+ egg_signal_group_connect_object (priv->table_signals,
+ "changed",
+ G_CALLBACK (rg_graph__table_changed),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/contrib/rg/rg-graph.h b/contrib/rg/rg-graph.h
new file mode 100644
index 0000000..b8884a6
--- /dev/null
+++ b/contrib/rg/rg-graph.h
@@ -0,0 +1,48 @@
+/* rg-graph.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_GRAPH_H
+#define RG_GRAPH_H
+
+#include <gtk/gtk.h>
+
+#include "rg-table.h"
+#include "rg-renderer.h"
+
+G_BEGIN_DECLS
+
+#define RG_TYPE_GRAPH (rg_graph_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (RgGraph, rg_graph, RG, GRAPH, GtkDrawingArea)
+
+struct _RgGraphClass
+{
+ GtkDrawingAreaClass parent_class;
+ gpointer padding[8];
+};
+
+GtkWidget *rg_graph_new (void);
+void rg_graph_set_table (RgGraph *self,
+ RgTable *table);
+RgTable *rg_graph_get_table (RgGraph *self);
+void rg_graph_add_renderer (RgGraph *self,
+ RgRenderer *renderer);
+
+G_END_DECLS
+
+#endif /* RG_GRAPH_H */
diff --git a/contrib/rg/rg-line-renderer.c b/contrib/rg/rg-line-renderer.c
new file mode 100644
index 0000000..fed57df
--- /dev/null
+++ b/contrib/rg/rg-line-renderer.c
@@ -0,0 +1,324 @@
+/* rg-line-renderer.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <math.h>
+
+#include "rg-line-renderer.h"
+
+struct _RgLineRenderer
+{
+ GObject parent_instance;
+
+ GdkRGBA stroke_color;
+ gdouble line_width;
+ guint column;
+};
+
+static void rg_line_renderer_init_renderer (RgRendererInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (RgLineRenderer, rg_line_renderer, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (RG_TYPE_RENDERER,
+ rg_line_renderer_init_renderer))
+
+enum {
+ PROP_0,
+ PROP_COLUMN,
+ PROP_LINE_WIDTH,
+ PROP_STROKE_COLOR,
+ PROP_STROKE_COLOR_RGBA,
+ LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+RgLineRenderer *
+rg_line_renderer_new (void)
+{
+ return g_object_new (RG_TYPE_LINE_RENDERER, NULL);
+}
+
+static gdouble
+calc_x (RgTableIter *iter,
+ gint64 begin,
+ gint64 end,
+ guint width)
+{
+ gint64 timestamp;
+
+ timestamp = rg_table_iter_get_timestamp (iter);
+
+ g_assert_cmpint (timestamp, !=, 0);
+
+ return ((timestamp - begin) / (gdouble)(end - begin) * width);
+}
+
+static gdouble
+calc_y (RgTableIter *iter,
+ gdouble range_begin,
+ gdouble range_end,
+ guint height,
+ guint column)
+{
+ GValue value = G_VALUE_INIT;
+ gdouble y;
+
+ rg_table_iter_get_value (iter, column, &value);
+
+ switch (G_VALUE_TYPE (&value))
+ {
+ case G_TYPE_DOUBLE:
+ y = g_value_get_double (&value);
+ break;
+
+ case G_TYPE_UINT:
+ y = g_value_get_uint (&value);
+ break;
+
+ case G_TYPE_UINT64:
+ y = g_value_get_uint64 (&value);
+ break;
+
+ case G_TYPE_INT:
+ y = g_value_get_int (&value);
+ break;
+
+ case G_TYPE_INT64:
+ y = g_value_get_int64 (&value);
+ break;
+
+ default:
+ y = 0.0;
+ break;
+ }
+
+ y -= range_begin;
+ y /= (range_end - range_begin);
+ y = height - (y * height);
+
+ return y;
+}
+
+static void
+rg_line_renderer_render (RgRenderer *renderer,
+ RgTable *table,
+ gint64 x_begin,
+ gint64 x_end,
+ gdouble y_begin,
+ gdouble y_end,
+ cairo_t *cr,
+ const cairo_rectangle_int_t *area)
+{
+ RgLineRenderer *self = (RgLineRenderer *)renderer;
+ RgTableIter iter;
+
+ g_assert (RG_IS_LINE_RENDERER (self));
+
+ cairo_save (cr);
+
+ if (rg_table_get_iter_first (table, &iter))
+ {
+ guint max_samples;
+ gdouble chunk;
+ gdouble last_x;
+ gdouble last_y;
+
+ max_samples = rg_table_get_max_samples (table);
+
+ chunk = area->width / (gdouble)(max_samples - 1) / 2.0;
+
+ last_x = calc_x (&iter, x_begin, x_end, area->width);
+ last_y = calc_y (&iter, y_begin, y_end, area->height, self->column);
+
+ cairo_move_to (cr, last_x, last_y);
+
+ while (rg_table_iter_next (&iter))
+ {
+ gdouble x;
+ gdouble y;
+
+ x = calc_x (&iter, x_begin, x_end, area->width);
+ y = calc_y (&iter, y_begin, y_end, area->height, self->column);
+
+ cairo_curve_to (cr,
+ last_x + chunk,
+ last_y,
+ last_x + chunk,
+ y,
+ x,
+ y);
+
+ last_x = x;
+ last_y = y;
+ }
+ }
+
+ cairo_set_line_width (cr, self->line_width);
+ gdk_cairo_set_source_rgba (cr, &self->stroke_color);
+ cairo_stroke (cr);
+
+ cairo_restore (cr);
+}
+
+static void
+rg_line_renderer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RgLineRenderer *self = RG_LINE_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_COLUMN:
+ g_value_set_uint (value, self->column);
+ break;
+
+ case PROP_LINE_WIDTH:
+ g_value_set_double (value, self->line_width);
+ break;
+
+ case PROP_STROKE_COLOR:
+ g_value_take_string (value, gdk_rgba_to_string (&self->stroke_color));
+ break;
+
+ case PROP_STROKE_COLOR_RGBA:
+ g_value_set_boxed (value, &self->stroke_color);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_line_renderer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RgLineRenderer *self = RG_LINE_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_COLUMN:
+ self->column = g_value_get_uint (value);
+ break;
+
+ case PROP_LINE_WIDTH:
+ self->line_width = g_value_get_double (value);
+ break;
+
+ case PROP_STROKE_COLOR:
+ rg_line_renderer_set_stroke_color (self, g_value_get_string (value));
+ break;
+
+ case PROP_STROKE_COLOR_RGBA:
+ rg_line_renderer_set_stroke_color_rgba (self, g_value_get_boxed (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_line_renderer_class_init (RgLineRendererClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = rg_line_renderer_get_property;
+ object_class->set_property = rg_line_renderer_set_property;
+
+ gParamSpecs [PROP_COLUMN] =
+ g_param_spec_uint ("column",
+ _("Column"),
+ _("Column"),
+ 0, G_MAXUINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_LINE_WIDTH] =
+ g_param_spec_double ("line-width",
+ _("Line Width"),
+ _("Line Width"),
+ 0.0, G_MAXDOUBLE,
+ 1.0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_STROKE_COLOR] =
+ g_param_spec_string ("stroke-color",
+ _("Stroke Color"),
+ _("Stroke Color"),
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_STROKE_COLOR_RGBA] =
+ g_param_spec_boxed ("stroke-color-rgba",
+ _("Stroke Color Rgba"),
+ _("Stroke Color Rgba"),
+ GDK_TYPE_RGBA,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+}
+
+static void
+rg_line_renderer_init (RgLineRenderer *self)
+{
+ self->line_width = 1.0;
+}
+
+static void
+rg_line_renderer_init_renderer (RgRendererInterface *iface)
+{
+ iface->render = rg_line_renderer_render;
+}
+
+void
+rg_line_renderer_set_stroke_color_rgba (RgLineRenderer *self,
+ const GdkRGBA *rgba)
+{
+ const GdkRGBA black = { 0, 0, 0, 1.0 };
+
+ g_return_if_fail (RG_IS_LINE_RENDERER (self));
+
+ if (rgba == NULL)
+ rgba = &black;
+
+ if (!gdk_rgba_equal (rgba, &self->stroke_color))
+ {
+ self->stroke_color = *rgba;
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_STROKE_COLOR_RGBA]);
+ }
+}
+
+void
+rg_line_renderer_set_stroke_color (RgLineRenderer *self,
+ const gchar *stroke_color)
+{
+ GdkRGBA rgba;
+
+ g_return_if_fail (RG_IS_LINE_RENDERER (self));
+
+ if (stroke_color == NULL)
+ stroke_color = "#000000";
+
+ if (gdk_rgba_parse (&rgba, stroke_color))
+ rg_line_renderer_set_stroke_color_rgba (self, &rgba);
+}
diff --git a/contrib/rg/rg-line-renderer.h b/contrib/rg/rg-line-renderer.h
new file mode 100644
index 0000000..68cf7fd
--- /dev/null
+++ b/contrib/rg/rg-line-renderer.h
@@ -0,0 +1,41 @@
+/* rg-line-renderer.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_LINE_RENDERER_H
+#define RG_LINE_RENDERER_H
+
+#include <gdk/gdk.h>
+
+#include "rg-renderer.h"
+
+G_BEGIN_DECLS
+
+#define RG_TYPE_LINE_RENDERER (rg_line_renderer_get_type())
+
+G_DECLARE_FINAL_TYPE (RgLineRenderer, rg_line_renderer, RG, LINE_RENDERER, GObject)
+
+RgLineRenderer *rg_line_renderer_new (void);
+void rg_line_renderer_set_stroke_color (RgLineRenderer *self,
+ const gchar *stroke_color);
+void rg_line_renderer_set_stroke_color_rgba (RgLineRenderer *self,
+ const GdkRGBA *stroke_color_rgba);
+const GdkRGBA *rg_line_renderer_get_stroke_color_rgba (RgLineRenderer *self);
+
+G_END_DECLS
+
+#endif /* RG_LINE_RENDERER_H */
diff --git a/contrib/rg/rg-renderer.c b/contrib/rg/rg-renderer.c
new file mode 100644
index 0000000..7b5e97a
--- /dev/null
+++ b/contrib/rg/rg-renderer.c
@@ -0,0 +1,58 @@
+/* rg-renderer.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cairo/cairo.h>
+
+#include "rg-renderer.h"
+
+G_DEFINE_INTERFACE (RgRenderer, rg_renderer, G_TYPE_OBJECT)
+
+static void
+dummy_render (RgRenderer *renderer,
+ RgTable *table,
+ gint64 x_begin,
+ gint64 x_end,
+ gdouble y_begin,
+ gdouble y_end,
+ cairo_t *cr,
+ const cairo_rectangle_int_t *area)
+{
+}
+
+static void
+rg_renderer_default_init (RgRendererInterface *iface)
+{
+ iface->render = dummy_render;
+}
+
+void
+rg_renderer_render (RgRenderer *self,
+ RgTable *table,
+ gint64 x_begin,
+ gint64 x_end,
+ gdouble y_begin,
+ gdouble y_end,
+ cairo_t *cr,
+ const cairo_rectangle_int_t *area)
+{
+ g_return_if_fail (RG_IS_RENDERER (self));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (area != NULL);
+
+ RG_RENDERER_GET_IFACE (self)->render (self, table, x_begin, x_end, y_begin, y_end, cr, area);
+}
diff --git a/contrib/rg/rg-renderer.h b/contrib/rg/rg-renderer.h
new file mode 100644
index 0000000..91d9c49
--- /dev/null
+++ b/contrib/rg/rg-renderer.h
@@ -0,0 +1,57 @@
+/* rg-renderer.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_RENDERER_H
+#define RG_RENDERER_H
+
+#include <glib-object.h>
+
+#include "rg-table.h"
+
+G_BEGIN_DECLS
+
+#define RG_TYPE_RENDERER (rg_renderer_get_type ())
+
+G_DECLARE_INTERFACE (RgRenderer, rg_renderer, RG, RENDERER, GObject)
+
+struct _RgRendererInterface
+{
+ GTypeInterface parent;
+
+ void (*render) (RgRenderer *self,
+ RgTable *table,
+ gint64 x_begin,
+ gint64 x_end,
+ gdouble y_begin,
+ gdouble y_end,
+ cairo_t *cr,
+ const cairo_rectangle_int_t *area);
+};
+
+void rg_renderer_render (RgRenderer *self,
+ RgTable *table,
+ gint64 x_begin,
+ gint64 x_end,
+ gdouble y_begin,
+ gdouble y_end,
+ cairo_t *cr,
+ const cairo_rectangle_int_t *area);
+
+G_END_DECLS
+
+#endif /* RG_RENDERER_H */
diff --git a/contrib/rg/rg-ring.c b/contrib/rg/rg-ring.c
new file mode 100644
index 0000000..16a3a7d
--- /dev/null
+++ b/contrib/rg/rg-ring.c
@@ -0,0 +1,206 @@
+/* rg-ring.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris dronelabs com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "rg-ring"
+
+#include <string.h>
+
+#include "rg-ring.h"
+
+#define get_element(r,i) ((r)->data + ((r)->elt_size * i))
+
+typedef struct
+{
+ /*< public >*/
+ guint8 *data; /* Pointer to real data. */
+ guint len; /* Length of data allocation. */
+ guint pos; /* Position in ring. */
+
+ /*< private >*/
+ guint elt_size; /* Size of each element. */
+ gboolean looped; /* Have we wrapped around at least once. */
+ GDestroyNotify destroy; /* Destroy element callback. */
+ volatile gint ref_count; /* Hidden Reference count. */
+} RgRingImpl;
+
+G_DEFINE_BOXED_TYPE (RgRing, rg_ring, rg_ring_ref, rg_ring_unref)
+
+/**
+ * rg_ring_sized_new:
+ * @element_size: (in): The size per element.
+ * @reserved_size: (in): The number of elements to allocate.
+ * @element_destroy: (in): Notification called when removing an element.
+ *
+ * Creates a new instance of #RgRing with the given number of elements.
+ *
+ * Returns: A new #RgRing.
+ */
+RgRing*
+rg_ring_sized_new (guint element_size,
+ guint reserved_size,
+ GDestroyNotify element_destroy)
+{
+ RgRingImpl *ring_impl;
+
+ ring_impl = g_slice_new0 (RgRingImpl);
+ ring_impl->elt_size = element_size;
+ ring_impl->len = reserved_size;
+ ring_impl->data = g_malloc0_n (reserved_size, element_size);
+ ring_impl->destroy = element_destroy;
+ ring_impl->ref_count = 1;
+
+ return (RgRing *)ring_impl;
+}
+
+/**
+ * rg_ring_append_vals:
+ * @ring: (in): A #RgRing.
+ * @data: (in): A pointer to the array of values.
+ * @len: (in): The number of values.
+ *
+ * Appends @len values located at @data.
+ *
+ * Returns: the index of the first item.
+ */
+guint
+rg_ring_append_vals (RgRing *ring,
+ gconstpointer data,
+ guint len)
+{
+ RgRingImpl *ring_impl = (RgRingImpl *)ring;
+ gpointer idx;
+ gint ret = -1;
+ gint x;
+ gint i;
+
+ g_return_if_fail (ring_impl != NULL);
+ g_return_if_fail (len <= ring->len);
+ g_return_if_fail (len > 0);
+
+ for (i = 0; i < len; i++)
+ {
+ x = ring->pos - i;
+ x = (x >= 0) ? x : ring->len + x;
+ idx = ring->data + (ring_impl->elt_size * x);
+ if (ring_impl->destroy && (ring_impl->looped == TRUE))
+ ring_impl->destroy (idx);
+ if (ret == -1)
+ ret = x;
+ memcpy (idx, data, ring_impl->elt_size);
+ ring->pos++;
+ if (ring->pos >= ring->len)
+ ring_impl->looped = TRUE;
+ ring->pos %= ring->len;
+ data = ((guint8 *)data) + ring_impl->elt_size;
+ }
+
+ return (guint)ret;
+}
+
+/**
+ * rg_ring_foreach:
+ * @ring: (in): A #RgRing.
+ * @func: (in): A #GFunc to call for each element.
+ * @user_data: (in): user data for @func.
+ *
+ * Calls @func for every item in the #RgRing starting from the most recently
+ * inserted element to the least recently inserted.
+ */
+void
+rg_ring_foreach (RgRing *ring,
+ GFunc func,
+ gpointer user_data)
+{
+ RgRingImpl *ring_impl = (RgRingImpl *)ring;
+ gint i;
+
+ g_return_if_fail (ring_impl != NULL);
+ g_return_if_fail (func != NULL);
+
+ if (!ring_impl->looped)
+ {
+ for (i = 0; i < ring_impl->pos; i++)
+ func (get_element (ring_impl, i), user_data);
+ return;
+ }
+
+ for (i = ring_impl->pos; i < ring_impl->len; i++)
+ func (get_element (ring_impl, i), user_data);
+
+ for (i = 0; i < ring_impl->pos; i++)
+ func (get_element (ring_impl, i), user_data);
+}
+
+/**
+ * rg_ring_destroy:
+ * @ring: (in): A #RgRing.
+ *
+ * Cleans up after a #RgRing that is no longer in use.
+ */
+void
+rg_ring_destroy (RgRing *ring)
+{
+ RgRingImpl *ring_impl = (RgRingImpl *)ring;
+
+ g_return_if_fail (ring != NULL);
+ g_return_if_fail (ring_impl->ref_count == 0);
+
+ g_free (ring_impl->data);
+
+ g_slice_free (RgRingImpl, ring_impl);
+}
+
+/**
+ * rg_ring_ref:
+ * @ring: (in): A #RgRing.
+ *
+ * Atomically increments the reference count of @ring by one.
+ *
+ * Returns: The @ring pointer.
+ */
+RgRing *
+rg_ring_ref (RgRing *ring)
+{
+ RgRingImpl *ring_impl = (RgRingImpl *)ring;
+
+ g_return_val_if_fail (ring != NULL, NULL);
+ g_return_val_if_fail (ring_impl->ref_count > 0, NULL);
+
+ g_atomic_int_inc (&ring_impl->ref_count);
+
+ return ring;
+}
+
+/**
+ * rg_ring_unref:
+ * @ring: (in): A #RgRing.
+ *
+ * Atomically decrements the reference count of @ring by one. When the
+ * reference count reaches zero, the structure is freed.
+ */
+void
+rg_ring_unref (RgRing *ring)
+{
+ RgRingImpl *ring_impl = (RgRingImpl *)ring;
+
+ g_return_if_fail (ring != NULL);
+ g_return_if_fail (ring_impl->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&ring_impl->ref_count))
+ rg_ring_destroy (ring);
+}
diff --git a/contrib/rg/rg-ring.h b/contrib/rg/rg-ring.h
new file mode 100644
index 0000000..024fa0f
--- /dev/null
+++ b/contrib/rg/rg-ring.h
@@ -0,0 +1,80 @@
+/* rg-ring.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris dronelabs com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RG_RING_H__
+#define __RG_RING_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/**
+ * rg_ring_append_val:
+ * @ring: A #RgRing.
+ * @val: A value to append to the #RgRing.
+ *
+ * Appends a value to the ring buffer. @val must be a variable as it is
+ * referenced to.
+ *
+ * Returns: None.
+ */
+#define rg_ring_append_val(ring, val) rg_ring_append_vals(ring, &(val), 1)
+
+/**
+ * rg_ring_get_index:
+ * @ring: A #RgRing.
+ * @type: The type to extract.
+ * @i: The index within the #RgRing relative to the current position.
+ *
+ * Retrieves the value at the given index from the #RgRing. The value
+ * is cast to @type. You may retrieve a pointer to the value within the
+ * array by using &.
+ *
+ * [[
+ * gdouble *v = &rg_ring_get_index(ring, gdouble, 0);
+ * gdouble v = rg_ring_get_index(ring, gdouble, 0);
+ * ]]
+ *
+ * Returns: The value at the given index.
+ */
+#define rg_ring_get_index(ring, type, i) \
+ ((((type*)(ring)->data))[((i) + (ring)->pos) % (ring)->len])
+
+typedef struct
+{
+ guint8 *data;
+ guint len;
+ guint pos;
+} RgRing;
+
+GType rg_ring_get_type (void);
+RgRing *rg_ring_sized_new (guint element_size,
+ guint reserved_size,
+ GDestroyNotify element_destroy);
+guint rg_ring_append_vals (RgRing *ring,
+ gconstpointer data,
+ guint len);
+void rg_ring_foreach (RgRing *ring,
+ GFunc func,
+ gpointer user_data);
+RgRing *rg_ring_ref (RgRing *ring);
+void rg_ring_unref (RgRing *ring);
+
+G_END_DECLS
+
+#endif /* __RG_RING_H__ */
diff --git a/contrib/rg/rg-table.c b/contrib/rg/rg-table.c
new file mode 100644
index 0000000..b99314e
--- /dev/null
+++ b/contrib/rg/rg-table.c
@@ -0,0 +1,560 @@
+/* rg-table.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "rg-column-private.h"
+#include "rg-table.h"
+
+typedef struct
+{
+ GPtrArray *columns;
+ RgColumn *timestamps;
+
+ guint last_index;
+
+ guint max_samples;
+ GTimeSpan timespan;
+ gdouble value_max;
+ gdouble value_min;
+} RgTablePrivate;
+
+typedef struct
+{
+ RgTable *table;
+ gint64 timestamp;
+ guint index;
+} RgTableIterImpl;
+
+enum {
+ PROP_0,
+ PROP_MAX_SAMPLES,
+ PROP_TIMESPAN,
+ PROP_VALUE_MAX,
+ PROP_VALUE_MIN,
+ LAST_PROP
+};
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (RgTable, rg_table, G_TYPE_OBJECT)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+gint64
+rg_table_get_timespan (RgTable *self)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_return_val_if_fail (RG_IS_TABLE (self), 0);
+
+ return priv->timespan;
+}
+
+void
+rg_table_set_timespan (RgTable *self,
+ GTimeSpan timespan)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_return_if_fail (RG_IS_TABLE (self));
+
+ if (timespan != priv->timespan)
+ {
+ priv->timespan = timespan;
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_TIMESPAN]);
+ }
+}
+
+static void
+rg_table_set_value_max (RgTable *self,
+ gdouble value_max)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_return_if_fail (RG_IS_TABLE (self));
+
+ if (priv->value_max != value_max)
+ {
+ priv->value_max = value_max;
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_VALUE_MAX]);
+ }
+}
+
+static void
+rg_table_set_value_min (RgTable *self,
+ gdouble value_min)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_return_if_fail (RG_IS_TABLE (self));
+
+ if (priv->value_min != value_min)
+ {
+ priv->value_min = value_min;
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_VALUE_MIN]);
+ }
+}
+
+RgTable *
+rg_table_new (void)
+{
+ return g_object_new (RG_TYPE_TABLE, NULL);
+}
+
+guint
+rg_table_add_column (RgTable *self,
+ RgColumn *column)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_return_if_fail (RG_IS_TABLE (self));
+ g_return_if_fail (RG_IS_COLUMN (column));
+
+ _rg_column_set_n_rows (column, priv->max_samples);
+
+ g_ptr_array_add (priv->columns, g_object_ref (column));
+
+ return priv->columns->len - 1;
+}
+
+guint
+rg_table_get_max_samples (RgTable *self)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_return_val_if_fail (RG_IS_TABLE (self), 0);
+
+ return priv->max_samples;
+}
+
+void
+rg_table_set_max_samples (RgTable *self,
+ guint max_samples)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+ gsize i;
+
+ g_return_if_fail (RG_IS_TABLE (self));
+ g_return_if_fail (max_samples > 0);
+
+ if (max_samples == priv->max_samples)
+ return;
+
+ for (i = 0; i < priv->columns->len; i++)
+ {
+ RgColumn *column;
+
+ column = g_ptr_array_index (priv->columns, i);
+ _rg_column_set_n_rows (column, max_samples);
+ }
+
+ _rg_column_set_n_rows (priv->timestamps, max_samples);
+
+ priv->max_samples = max_samples;
+
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_MAX_SAMPLES]);
+}
+
+void
+rg_table_push (RgTable *self,
+ RgTableIter *iter,
+ gint64 timestamp)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+ guint pos;
+ gsize i;
+
+ g_return_if_fail (RG_IS_TABLE (self));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (timestamp > 0);
+
+ for (i = 0; i < priv->columns->len; i++)
+ {
+ RgColumn *column;
+
+ column = g_ptr_array_index (priv->columns, i);
+ _rg_column_push (column);
+ }
+
+ pos = _rg_column_push (priv->timestamps);
+ _rg_column_set (priv->timestamps, pos, timestamp);
+
+ impl->table = self;
+ impl->timestamp = timestamp;
+ impl->index = pos;
+
+ priv->last_index = pos;
+
+ g_signal_emit (self, gSignals [CHANGED], 0);
+}
+
+gboolean
+rg_table_get_iter_last (RgTable *self,
+ RgTableIter *iter)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+
+ g_return_if_fail (RG_IS_TABLE (self));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (impl != NULL);
+
+ impl->table = self;
+ impl->index = priv->last_index;
+ impl->timestamp = 0;
+
+ _rg_column_get (priv->timestamps, impl->index, &impl->timestamp);
+
+ return (impl->timestamp != 0);
+}
+
+gint64
+rg_table_get_end_time (RgTable *self)
+{
+ RgTableIter iter;
+
+ g_return_val_if_fail (RG_IS_TABLE (self), 0);
+
+ if (rg_table_get_iter_last (self, &iter))
+ return rg_table_iter_get_timestamp (&iter);
+
+ return g_get_monotonic_time ();
+}
+
+gboolean
+rg_table_get_iter_first (RgTable *self,
+ RgTableIter *iter)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+
+ g_return_if_fail (RG_IS_TABLE (self));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (impl != NULL);
+
+ impl->table = self;
+ impl->index = (priv->last_index + 1) % priv->max_samples;
+ impl->timestamp = 0;
+
+ _rg_column_get (priv->timestamps, impl->index, &impl->timestamp);
+
+ /*
+ * Maybe this is our first time around the ring, and we can just
+ * assume the 0 index is the real first entry.
+ */
+ if (impl->timestamp == 0)
+ {
+ impl->index = 0;
+ _rg_column_get (priv->timestamps, impl->index, &impl->timestamp);
+ }
+
+ return (impl->timestamp != 0);
+}
+
+gboolean
+rg_table_iter_next (RgTableIter *iter)
+{
+ RgTablePrivate *priv;
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (impl != NULL, FALSE);
+ g_return_val_if_fail (RG_IS_TABLE (impl->table), FALSE);
+
+ priv = rg_table_get_instance_private (impl->table);
+
+ if (impl->index == priv->last_index)
+ {
+ impl->table = NULL;
+ impl->index = 0;
+ impl->timestamp = 0;
+ return FALSE;
+ }
+
+ do
+ {
+ impl->index = (impl->index + 1) % priv->max_samples;
+
+ impl->timestamp = 0;
+ _rg_column_get (priv->timestamps, impl->index, &impl->timestamp);
+
+ if (impl->timestamp > 0)
+ break;
+ }
+ while (impl->index < priv->last_index);
+
+ return (impl->timestamp > 0);
+}
+
+gint64
+rg_table_iter_get_timestamp (RgTableIter *iter)
+{
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+
+ g_return_val_if_fail (iter != NULL, 0);
+
+ return impl->timestamp;
+}
+
+void
+rg_table_iter_set (RgTableIter *iter,
+ gint first_column,
+ ...)
+{
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+ RgTablePrivate *priv;
+ gint column_id = first_column;
+ va_list args;
+
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (impl != NULL);
+ g_return_if_fail (RG_IS_TABLE (impl->table));
+
+ priv = rg_table_get_instance_private (impl->table);
+
+ va_start (args, first_column);
+
+ while (column_id >= 0)
+ {
+ RgColumn *column;
+
+ if (column_id >= priv->columns->len)
+ {
+ g_critical ("No such column %d", column_id);
+ return;
+ }
+
+ column = g_ptr_array_index (priv->columns, column_id);
+
+ _rg_column_collect (column, impl->index, args);
+
+ column_id = va_arg (args, gint);
+ }
+
+ if (column_id != -1)
+ g_critical ("Invalid column sentinal: %d", column_id);
+
+ va_end (args);
+}
+
+void
+rg_table_iter_get (RgTableIter *iter,
+ gint first_column,
+ ...)
+{
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+ RgTablePrivate *priv;
+ gint column_id = first_column;
+ va_list args;
+
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (impl != NULL);
+ g_return_if_fail (RG_IS_TABLE (impl->table));
+
+ priv = rg_table_get_instance_private (impl->table);
+
+ va_start (args, first_column);
+
+ while (column_id >= 0)
+ {
+ RgColumn *column;
+
+ if (column_id >= priv->columns->len)
+ {
+ g_critical ("No such column %d", column_id);
+ return;
+ }
+
+ column = g_ptr_array_index (priv->columns, column_id);
+
+ _rg_column_lcopy (column, impl->index, args);
+
+ column_id = va_arg (args, gint);
+ }
+
+ if (column_id != -1)
+ g_critical ("Invalid column sentinal: %d", column_id);
+
+ va_end (args);
+}
+
+void
+rg_table_iter_get_value (RgTableIter *iter,
+ guint column,
+ GValue *value)
+{
+ RgTableIterImpl *impl = (RgTableIterImpl *)iter;
+ RgTablePrivate *priv;
+ RgColumn *col;
+
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (impl != NULL);
+ g_return_if_fail (RG_IS_TABLE (impl->table));
+ priv = rg_table_get_instance_private (impl->table);
+ g_return_if_fail (column < priv->columns->len);
+
+ col = g_ptr_array_index (priv->columns, column);
+ _rg_column_get_value (col, impl->index, value);
+}
+
+static void
+rg_table_finalize (GObject *object)
+{
+ RgTable *self = (RgTable *)object;
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ g_clear_pointer (&priv->columns, g_object_unref);
+
+ G_OBJECT_CLASS (rg_table_parent_class)->finalize (object);
+}
+
+static void
+rg_table_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RgTable *self = (RgTable *)object;
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_TIMESPAN:
+ g_value_set_int64 (value, priv->timespan);
+ break;
+
+ case PROP_MAX_SAMPLES:
+ g_value_set_uint (value, priv->max_samples);
+ break;
+
+ case PROP_VALUE_MAX:
+ g_value_set_double (value, priv->value_max);
+ break;
+
+ case PROP_VALUE_MIN:
+ g_value_set_double (value, priv->value_min);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_table_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RgTable *self = (RgTable *)object;
+
+ switch (prop_id)
+ {
+ case PROP_MAX_SAMPLES:
+ rg_table_set_max_samples (self, g_value_get_uint (value));
+ break;
+
+ case PROP_TIMESPAN:
+ rg_table_set_timespan (self, g_value_get_int64 (value));
+ break;
+
+ case PROP_VALUE_MAX:
+ rg_table_set_value_max (self, g_value_get_double (value));
+ break;
+
+ case PROP_VALUE_MIN:
+ rg_table_set_value_min (self, g_value_get_double (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+rg_table_class_init (RgTableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = rg_table_finalize;
+ object_class->get_property = rg_table_get_property;
+ object_class->set_property = rg_table_set_property;
+
+ gParamSpecs [PROP_MAX_SAMPLES] =
+ g_param_spec_uint ("max-samples",
+ _("Max Samples"),
+ _("Max Samples"),
+ 1, G_MAXUINT,
+ 120,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_TIMESPAN] =
+ g_param_spec_int64 ("timespan",
+ _("Timespan"),
+ _("Timespan to visualize, in microseconds."),
+ 1, G_MAXINT64,
+ G_USEC_PER_SEC * 60L,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_VALUE_MAX] =
+ g_param_spec_double ("value-max",
+ _("Value Max"),
+ _("Value Max"),
+ -G_MINDOUBLE, G_MAXDOUBLE,
+ 100.0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gParamSpecs [PROP_VALUE_MIN] =
+ g_param_spec_double ("value-min",
+ _("Value Min"),
+ _("Value Min"),
+ -G_MINDOUBLE, G_MAXDOUBLE,
+ 100.0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+ gSignals [CHANGED] = g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+rg_table_init (RgTable *self)
+{
+ RgTablePrivate *priv = rg_table_get_instance_private (self);
+
+ priv->max_samples = 60;
+ priv->value_min = 0.0;
+ priv->value_max = 100.0;
+
+ priv->columns = g_ptr_array_new_with_free_func (g_object_unref);
+
+ priv->timestamps = rg_column_new (NULL, G_TYPE_INT64);
+ _rg_column_set_n_rows (priv->timestamps, priv->max_samples);
+}
diff --git a/contrib/rg/rg-table.h b/contrib/rg/rg-table.h
new file mode 100644
index 0000000..fb3ebaf
--- /dev/null
+++ b/contrib/rg/rg-table.h
@@ -0,0 +1,73 @@
+/* rg-table.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RG_TABLE_H
+#define RG_TABLE_H
+
+#include <glib-object.h>
+
+#include "rg-column.h"
+
+G_BEGIN_DECLS
+
+#define RG_TYPE_TABLE (rg_table_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (RgTable, rg_table, RG, TABLE, GObject)
+
+struct _RgTableClass
+{
+ GObjectClass parent;
+};
+
+typedef struct
+{
+ gpointer data[8];
+} RgTableIter;
+
+RgTable *rg_table_new (void);
+guint rg_table_add_column (RgTable *self,
+ RgColumn *column);
+GTimeSpan rg_table_get_timespan (RgTable *self);
+void rg_table_set_timespan (RgTable *self,
+ GTimeSpan timespan);
+gint64 rg_table_get_end_time (RgTable *self);
+guint rg_table_get_max_samples (RgTable *self);
+void rg_table_set_max_samples (RgTable *self,
+ guint n_rows);
+void rg_table_push (RgTable *self,
+ RgTableIter *iter,
+ gint64 timestamp);
+gboolean rg_table_get_iter_first (RgTable *self,
+ RgTableIter *iter);
+gboolean rg_table_get_iter_last (RgTable *self,
+ RgTableIter *iter);
+gboolean rg_table_iter_next (RgTableIter *iter);
+void rg_table_iter_get (RgTableIter *iter,
+ gint first_column,
+ ...);
+void rg_table_iter_get_value (RgTableIter *iter,
+ guint column,
+ GValue *value);
+gint64 rg_table_iter_get_timestamp (RgTableIter *iter);
+void rg_table_iter_set (RgTableIter *iter,
+ gint first_column,
+ ...);
+
+G_END_DECLS
+
+#endif /* RG_TABLE_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1856aa4..fc749fa 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -11,6 +11,19 @@ egg_libs = \
$(top_builddir)/contrib/egg/libegg.la \
$(NULL)
+rg_cflags = \
+ $(DEBUG_CFLAGS) \
+ $(RG_CFLAGS) \
+ -I$(top_srcdir)/contrib/egg \
+ -I$(top_srcdir)/contrib/rg \
+ $(NULL)
+
+rg_libs = \
+ $(top_builddir)/contrib/egg/libegg.la \
+ $(top_builddir)/contrib/rg/librg.la \
+ $(RG_LIBS) \
+ $(NULL)
+
tests_cflags = \
$(egg_cflags) \
-I$(top_srcdir)/libide \
@@ -22,6 +35,7 @@ tests_libs = \
$(top_builddir)/libide/libide-1.0.la \
$(NULL)
+misc_programs =
TESTS_ENVIRONMENT= \
GI_TYPELIB_PATH="$(top_srcdir)/libide" \
@@ -104,12 +118,18 @@ test_vim_LDADD = \
$(NULL)
-misc_programs = test-ide-source-view
+misc_programs += test-ide-source-view
test_ide_source_view_SOURCES = test-ide-source-view.c
test_ide_source_view_CFLAGS = $(tests_cflags)
test_ide_source_view_LDADD = $(tests_libs)
+misc_programs += test-cpu-graph
+test_cpu_graph_SOURCES = test-cpu-graph.c
+test_cpu_graph_CFLAGS = $(rg_cflags)
+test_cpu_graph_LDADD = $(rg_libs)
+
+
TESTS += test-ide-ctags
test_ide_ctags_SOURCES = test-ide-ctags.c
test_ide_ctags_CFLAGS = $(tests_cflags)
diff --git a/tests/test-cpu-graph.c b/tests/test-cpu-graph.c
new file mode 100644
index 0000000..3b8a7a3
--- /dev/null
+++ b/tests/test-cpu-graph.c
@@ -0,0 +1,102 @@
+#include "rg-graph.h"
+#include "rg-cpu-table.h"
+#include "rg-line-renderer.h"
+
+#include <stdlib.h>
+
+#define CSS_DATA \
+ "RgGraph {\n" \
+ " background-color: #f6f7f8;\n" \
+ " background-size: 8px 8px;\n" \
+ " background-image:repeating-linear-gradient(0deg, #f0f1f2, #f0f1f2 1px, transparent 1px, transparent
8px),repeating-linear-gradient(-90deg, #f0f1f2, #f0f1f2 1px, transparent 1px, transparent 8px);\n" \
+ "}"
+
+static gchar *colors[] = {
+ "#73d216",
+ "#f57900",
+ "#3465a4",
+ "#ef2929",
+ "#75507b",
+ "#ce5c00",
+ "#c17d11",
+ "#ce5c00",
+};
+
+int
+main (int argc,
+ char *argv[])
+{
+ guint samples = 2;
+ guint seconds = 30;
+ const GOptionEntry entries[] = {
+ { "samples", 'm', 0, G_OPTION_ARG_INT, &samples, "Number of samples per second", "2" },
+ { "seconds", 's', 0, G_OPTION_ARG_INT, &seconds, "Number of seconds to display", "60" },
+ { NULL }
+ };
+ gint64 timespan;
+ guint max_samples;
+ GOptionContext *context;
+ GtkWindow *window;
+ RgGraph *graph;
+ RgTable *table;
+ RgRenderer *renderer;
+ GtkCssProvider *provider;
+ GError *error = NULL;
+ gsize i;
+
+ context = g_option_context_new ("- a simple cpu graph");
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_printerr ("%s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ g_print ("%d samples per second over %d seconds.\n",
+ samples, seconds);
+
+ timespan = (gint64)seconds * G_USEC_PER_SEC;
+ max_samples = seconds * samples;
+
+ table = g_object_new (RG_TYPE_CPU_TABLE,
+ "timespan", timespan,
+ "max-samples", max_samples,
+ NULL);
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider, CSS_DATA, -1, NULL);
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ g_object_unref (provider);
+
+ window = g_object_new (GTK_TYPE_WINDOW,
+ "default-width", 600,
+ "default-height", 325,
+ "title", "CPU Graph",
+ NULL);
+
+ graph = g_object_new (RG_TYPE_GRAPH,
+ "visible", TRUE,
+ "table", table,
+ NULL);
+
+ for (i = 0; i < g_get_num_processors (); i++)
+ {
+ renderer = g_object_new (RG_TYPE_LINE_RENDERER,
+ "column", i,
+ "stroke-color", colors[i % G_N_ELEMENTS (colors)],
+ "line-width", 1.0,
+ NULL);
+ rg_graph_add_renderer (graph, renderer);
+ g_object_unref (renderer);
+ }
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (graph));
+
+ g_signal_connect (window, "delete-event", gtk_main_quit, NULL);
+ gtk_window_present (window);
+ gtk_main ();
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]