Add support for cpu and memory cgroups in process view

Add two new columns - CPU Cgroup and Memory Cgroup in the
process view. If the kernel doesn't support cgroups, or
if system-monitor is not running on linux, the cgroup columns
are hidden.

Signed-off-by: Jason Baron <jbaron@redhat.com>
---
 src/Makefile.am                                   |    1 +
 src/cgroups.cpp                                   |   76 +++++++++++++++++++++
 src/cgroups.h                                     |   14 ++++
 src/org.gnome.gnome-system-monitor.gschema.xml.in |   28 ++++++++
 src/procdialogs.cpp                               |    7 ++
 src/procman.h                                     |    2 +
 src/proctable.cpp                                 |   28 +++++++-
 src/proctable.h                                   |    2 +
 8 files changed, 156 insertions(+), 2 deletions(-)
 create mode 100644 src/cgroups.cpp
 create mode 100644 src/cgroups.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 56b6b83..cb8bdaa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,6 +25,7 @@ gnome_system_monitor_SOURCES = \
 	defaulttable.h \
 	disks.cpp disks.h \
 	selinux.h selinux.cpp \
+	cgroups.h cgroups.cpp \
 	procman_gnomesu.h procman_gnomesu.cpp \
 	procman_gksu.h procman_gksu.cpp \
 	sysinfo.cpp sysinfo.h \
diff --git a/src/cgroups.cpp b/src/cgroups.cpp
new file mode 100644
index 0000000..fecb435
--- /dev/null
+++ b/src/cgroups.cpp
@@ -0,0 +1,76 @@
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "procman.h"
+#include "util.h"
+
+gboolean
+cgroups_enabled(void)
+{
+    static gboolean initialized = FALSE;
+    static gboolean has_cgroups;
+
+    if (!initialized) {
+        initialized = TRUE;
+        has_cgroups = g_file_test("/proc/cgroups", G_FILE_TEST_EXISTS);
+    }
+
+    return has_cgroups;
+}
+
+void
+update_cgroup_name(gchar **cgroup_name, gchar *new_cgroup_name)
+{
+    if (!new_cgroup_name) {
+        if (*cgroup_name)
+            g_free(*cgroup_name);
+        *cgroup_name = g_strdup(_("N/A")); 
+        return;
+    }
+
+    if (not (*cgroup_name) or std::strcmp(*cgroup_name, new_cgroup_name) != 0) {
+            if (*cgroup_name)
+                g_free(*cgroup_name);
+            *cgroup_name = g_strdup(new_cgroup_name);
+    }
+}
+
+void
+get_process_cgroup_info(ProcInfo *info)
+{
+    gchar *path;
+    GFile *file;
+    GFileInputStream *input_stream;
+    GDataInputStream *data_stream;
+    char *line;
+    gchar *cgroup;
+
+    if (!cgroups_enabled())
+        return;
+
+    /* read out of /proc/pid/cgroup */
+    path = g_strdup_printf("/proc/%d/cgroup", info->pid);
+    file = g_file_new_for_path(path);
+    if(!(input_stream = g_file_read(file, NULL, NULL))) {
+        update_cgroup_name(&info->cpu_cgroup_name, NULL);
+        update_cgroup_name(&info->memory_cgroup_name, NULL);
+        goto out;
+    }
+    data_stream = g_data_input_stream_new(G_INPUT_STREAM(input_stream));
+
+    while ((line = g_data_input_stream_read_line(data_stream, NULL, NULL, NULL))) {
+        if ((cgroup = g_strstr_len(line, -1, "cpu:"))) {
+            update_cgroup_name(&info->cpu_cgroup_name, cgroup + 4);
+        } else if ((cgroup = g_strstr_len(line, -1, "memory:"))) {
+            update_cgroup_name(&info->memory_cgroup_name, cgroup + 7);
+        }
+        g_free(line);
+    }
+    g_object_unref(data_stream);
+    g_object_unref(input_stream);
+out:
+    g_object_unref(file);
+    g_free(path);
+}
diff --git a/src/cgroups.h b/src/cgroups.h
new file mode 100644
index 0000000..8c42f6f
--- /dev/null
+++ b/src/cgroups.h
@@ -0,0 +1,14 @@
+#ifndef PROCMAN_CGROUP_H_20111103
+#define PROCMAN_CGROUP_H_20111103
+
+#include <glib.h>
+
+#include "procman.h"
+
+void
+get_process_cgroup_info (ProcInfo *info);
+
+gboolean
+cgroups_enabled (void);
+
+#endif /* PROCMAN_CGROUP_H_20111103 */
diff --git a/src/org.gnome.gnome-system-monitor.gschema.xml.in b/src/org.gnome.gnome-system-monitor.gschema.xml.in
index dfa9e40..13d2713 100644
--- a/src/org.gnome.gnome-system-monitor.gschema.xml.in
+++ b/src/org.gnome.gnome-system-monitor.gschema.xml.in
@@ -418,6 +418,34 @@
 	  <_summary>Show process 'Waiting Channel' column on startup
 	  </_summary>
 	</key>
+
+	<key name="col-17-width" type="i">
+	  <default>48
+	  </default>
+	  <_summary>Width of process 'CPU Cgroup' column
+	  </_summary>
+	</key>
+
+	<key name="col-17-visible" type="b">
+	  <default>false
+	  </default>
+	  <_summary>Show process 'CPU Cgroup' column on startup
+	  </_summary>
+	</key>
+
+	<key name="col-18-width" type="i">
+	  <default>48
+	  </default>
+	  <_summary>Width of process 'Memory Cgroup' column
+	  </_summary>
+	</key>
+
+	<key name="col-18-visible" type="b">
+	  <default>false
+	  </default>
+	  <_summary>Show process 'Memory Cgroup' column on startup
+	  </_summary>
+	</key>
   </schema>
 
   <schema id="org.gnome.gnome-system-monitor.disktreenew" path="/org/gnome/gnome-system-monitor/disktreenew/">
diff --git a/src/procdialogs.cpp b/src/procdialogs.cpp
index ace8b4c..ec4486c 100644
--- a/src/procdialogs.cpp
+++ b/src/procdialogs.cpp
@@ -34,6 +34,7 @@
 #include "settings-keys.h"
 #include "procman_gnomesu.h"
 #include "procman_gksu.h"
+#include "cgroups.h"
 
 static GtkWidget *renice_dialog = NULL;
 static GtkWidget *prefs_dialog = NULL;
@@ -465,11 +466,17 @@ create_field_page(GtkWidget *tree, const char* text)
         GtkTreeIter iter;
         const gchar *title;
         gboolean visible;
+        gint column_id;
 
         title = gtk_tree_view_column_get_title (column);
         if (!title)
             title = _("Icon");
 
+        column_id = gtk_tree_view_column_get_sort_column_id(column);
+        if (((column_id == COL_CPU_CGROUP) || (column_id == COL_MEMORY_CGROUP)) && 
+                (!cgroups_enabled()))
+            continue;  
+
         visible = gtk_tree_view_column_get_visible (column);
 
         gtk_list_store_append (model, &iter);
diff --git a/src/procman.h b/src/procman.h
index b0e0143..7fbb5b2 100644
--- a/src/procman.h
+++ b/src/procman.h
@@ -166,6 +166,8 @@ class ProcInfo
     gchar           *arguments;
 
     gchar           *security_context;
+    gchar           *cpu_cgroup_name;
+    gchar           *memory_cgroup_name;
 
     const guint     pid;
     guint           ppid;
diff --git a/src/proctable.cpp b/src/proctable.cpp
index 2429373..5b0d614 100644
--- a/src/proctable.cpp
+++ b/src/proctable.cpp
@@ -52,6 +52,7 @@
 #include "util.h"
 #include "interface.h"
 #include "selinux.h"
+#include "cgroups.h"
 
 ProcInfo::UserMap ProcInfo::users;
 ProcInfo::List ProcInfo::all;
@@ -247,6 +248,8 @@ proctable_new (ProcData * const procdata)
         N_("Memory"),
         /* xgettext: wchan, see ps(1) or top(1) */
         N_("Waiting Channel"),
+        N_("CPU Cgroup"),
+        N_("Memory Cgroup"),
         NULL,
         "POINTER"
     };
@@ -276,6 +279,8 @@ proctable_new (ProcData * const procdata)
                                 G_TYPE_STRING,      /* Arguments    */
                                 G_TYPE_ULONG,       /* Memory       */
                                 G_TYPE_STRING,      /* wchan        */
+                                G_TYPE_STRING,      /* Cpu Cgroup   */
+                                G_TYPE_STRING,      /* Memory Cgroup*/
                                 GDK_TYPE_PIXBUF,    /* Icon         */
                                 G_TYPE_POINTER,     /* ProcInfo     */
                                 G_TYPE_STRING       /* Sexy tooltip */
@@ -319,7 +324,7 @@ proctable_new (ProcData * const procdata)
     gtk_tree_view_set_expander_column (GTK_TREE_VIEW (proctree), column);
 
 
-    for (i = COL_USER; i <= COL_WCHAN; i++) {
+    for (i = COL_USER; i <= COL_MEMORY_CGROUP; i++) {
 
         GtkCellRenderer *cell;
         GtkTreeViewColumn *col;
@@ -424,6 +429,16 @@ proctable_new (ProcData * const procdata)
         gtk_tree_view_column_set_visible (column, FALSE);
     }
 
+    if (!cgroups_enabled()) {
+        GtkTreeViewColumn *column;
+
+        column = my_gtk_tree_view_get_column_with_sort_column_id(GTK_TREE_VIEW(proctree), COL_CPU_CGROUP);
+        gtk_tree_view_column_set_visible(column, FALSE);
+        column = my_gtk_tree_view_get_column_with_sort_column_id(GTK_TREE_VIEW(proctree), COL_MEMORY_CGROUP);
+        gtk_tree_view_column_set_visible(column, FALSE);
+    }
+
+
     g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree))),
                       "changed",
                       G_CALLBACK (cb_row_selected), procdata);
@@ -445,6 +460,8 @@ ProcInfo::~ProcInfo()
     g_free(this->tooltip);
     g_free(this->arguments);
     g_free(this->security_context);
+    g_free(this->cpu_cgroup_name);
+    g_free(this->memory_cgroup_name);
 }
 
 
@@ -530,7 +547,6 @@ static void get_process_memory_writable(ProcInfo *info)
     g_free(maps);
 }
 
-
 static void
 get_process_memory_info(ProcInfo *info)
 {
@@ -579,6 +595,8 @@ update_info_mutable_cols(ProcInfo *info)
     tree_store_update(model, &info->node, COL_NICE, info->nice);
     tree_store_update(model, &info->node, COL_MEM, info->mem);
     tree_store_update(model, &info->node, COL_WCHAN, info->wchan);
+    tree_store_update(model, &info->node, COL_CPU_CGROUP, info->cpu_cgroup_name);
+    tree_store_update(model, &info->node, COL_MEMORY_CGROUP, info->memory_cgroup_name);
 }
 
 
@@ -695,6 +713,9 @@ update_info (ProcData *procdata, ProcInfo *info)
     ProcInfo::cpu_times[info->pid] = info->cpu_time = proctime.rtime;
     info->nice = procuid.nice;
     info->ppid = procuid.ppid;
+
+    /* get cgroup data */
+    get_process_cgroup_info(info);
 }
 
 
@@ -739,6 +760,9 @@ ProcInfo::ProcInfo(pid_t pid)
     info->start_time = proctime.start_time;
 
     get_process_selinux_context (info);
+    info->cpu_cgroup_name = NULL;
+    info->memory_cgroup_name = NULL;
+    get_process_cgroup_info(info);
 }
 
 
diff --git a/src/proctable.h b/src/proctable.h
index 62cb529..fc2c3c8 100644
--- a/src/proctable.h
+++ b/src/proctable.h
@@ -43,6 +43,8 @@ enum
     COL_ARGS,
     COL_MEM,
     COL_WCHAN,
+    COL_CPU_CGROUP,
+    COL_MEMORY_CGROUP,
     COL_PIXBUF,
     COL_POINTER,
     COL_TOOLTIP,
-- 
1.7.6.4

