Adds a new output which can be used to display common memory-statistics,
like top would do it.

The current implementation supports only linux and reads /proc/meminfo.

Signed-of-by: Benjamin Block <[email protected]>
---
 i3status.c            |   14 +++
 include/i3status.h    |    1 +
 man/i3status.man      |   27 ++++++
 src/print_mem_usage.c |  210 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 252 insertions(+), 0 deletions(-)
 create mode 100644 src/print_mem_usage.c

diff --git a/i3status.c b/i3status.c
index dd7d1e6..f86d0e6 100644
--- a/i3status.c
+++ b/i3status.c
@@ -234,6 +234,13 @@ int main(int argc, char *argv[]) {
                 CFG_END()
         };
 
+        cfg_opt_t mem_usage_opts[] = {
+                CFG_STR("format", "%usage", CFGF_NONE),
+                CFG_INT("include_buffers", 0, CFGF_NONE),
+                CFG_INT("include_caches", 0, CFGF_NONE),
+                CFG_END()
+        };
+
         cfg_opt_t temp_opts[] = {
                 CFG_STR("format", "%degrees C", CFGF_NONE),
                 CFG_STR("path", NULL, CFGF_NONE),
@@ -268,6 +275,7 @@ int main(int argc, char *argv[]) {
                 CFG_SEC("ddate", ddate_opts, CFGF_NONE),
                 CFG_SEC("load", load_opts, CFGF_NONE),
                 CFG_SEC("cpu_usage", usage_opts, CFGF_NONE),
+                CFG_SEC("mem_usage", mem_usage_opts, CFGF_TITLE | CFGF_MULTI),
                 CFG_END()
         };
 
@@ -410,6 +418,12 @@ int main(int argc, char *argv[]) {
 
                         CASE_SEC("cpu_usage")
                                 print_cpu_usage(cfg_getstr(sec, "format"));
+
+                        CASE_SEC_TITLE("mem_usage")
+                                print_mem_usage(title,
+                                                cfg_getstr(sec, "format"),
+                                                cfg_getint(sec, 
"include_buffers"),
+                                                cfg_getint(sec, 
"include_caches"));
                 }
                 if (output_format == O_I3BAR)
                         printf("],");
diff --git a/include/i3status.h b/include/i3status.h
index e40003c..6afccdd 100644
--- a/include/i3status.h
+++ b/include/i3status.h
@@ -73,6 +73,7 @@ void print_wireless_info(const char *interface, const char 
*format_up, const cha
 void print_run_watch(const char *title, const char *pidfile, const char 
*format);
 void print_cpu_temperature_info(int zone, const char *path, const char 
*format);
 void print_cpu_usage(const char *format);
+void print_mem_usage(const char *title, const char *format, int 
include_buffers, int include_caches);
 void print_eth_info(const char *interface, const char *format_up, const char 
*format_down);
 void print_load();
 void print_volume(const char *fmt, const char *device, const char *mixer, int 
mixer_idx);
diff --git a/man/i3status.man b/man/i3status.man
index fb1bbf4..b14eaa6 100644
--- a/man/i3status.man
+++ b/man/i3status.man
@@ -136,6 +136,8 @@ managers like dwm, wmii and xmonad though it will work with 
any windowmanger
 xmobar::
 xmobar is a minimalistic, text based, status bar. It was designed to work
 with the xmonad Window Manager.
+i3bar::
+the native status bar of i3.
 none::
 Does not use any color codes. Separates values by the pipe symbol. This should
 be used with i3bar and can be used for custom scripts.
@@ -220,6 +222,31 @@ Gets the percentual CPU usage from +/proc/stat+.
 
 *Example format*: +%usage+
 
+=== Memory Usage
+
+Gets several memory-parameters from +/proc/meminfo+.
+
+*Example order*: +memory_usage phy_free+
+
+*Example config*:
+-------------------------------------------------------------
+memory_usage top {
+       format = "Memory: %phytotal total, %phyused used, %phyfree
+                 free; Swap: %swaptotal total, %swapused used,
+                 %swapfree free"
+       include_buffers = 0
+       include_caches = 0
+}
+-------------------------------------------------------------
+
+-------------------------------------------------------------
+memory_usage phy_free {
+       format = "M: %phyfree"
+       include_buffers = 0
+       include_caches = 0
+}
+-------------------------------------------------------------
+
 === Load
 
 Gets the system load (number of processes waiting for CPU time in the last
diff --git a/src/print_mem_usage.c b/src/print_mem_usage.c
new file mode 100644
index 0000000..fed9f67
--- /dev/null
+++ b/src/print_mem_usage.c
@@ -0,0 +1,210 @@
+// vim:sw=8:sts=8:ts=8:expandtab
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef __FreeBSD__
+/* don't have any freebsd-systems .. */
+#endif
+
+#include "i3status.h"
+
+enum mem_usage_idx {
+        MEM_USAGE_MIN   = 0,
+        MEM_USAGE_TOTAL = 0,
+        MEM_USAGE_FREE   = 1,
+        MEM_USAGE_USED  = 2,
+        MEM_USAGE_MAX   = 3
+};
+
+/*
+ * shall provide a common way to express the amount of memory used and 
available
+ *
+ * the values are expected to be kByte-numbers
+ */
+struct mem_usage {
+        unsigned long phy[3];
+        unsigned long swap[3];
+};
+
+/* os-specific implementations provided at the bottom */
+int get_mem_usage(struct mem_usage * usage, int include_buffers,
+                int include_caches);
+
+void
+print_mem_usage(const char *title, const char *format, int include_buffers,
+                int include_caches)
+{
+        const char *walk;
+        struct mem_usage usage;
+
+        if (output_format == O_I3BAR)
+                printf("{\"name\":\"mem_usage\", \"full_text\":\"");
+
+        if(get_mem_usage(&usage, include_buffers, include_caches))
+                goto err_out;
+
+/* matches the str against (walk + 1) */
+#define matchField(str) (strncmp(walk + 1, (str), sizeof((str)) - 1) == 0)
+
+        for (walk = format; *walk != '\0'; walk++) {
+                if (*walk != '%') {
+                        putchar(*walk);
+                        continue;
+                }
+
+                /* dispaly in MegaBytes */
+                if (matchField("phytotal")) {
+                        printf("%lumB", usage.phy[MEM_USAGE_TOTAL] >> 10);
+                        walk += sizeof("phytotal") - 1;
+                } else if (matchField("phyfree")) {
+                        printf("%lumB", usage.phy[MEM_USAGE_FREE] >> 10);
+                        walk += sizeof("phyfree") - 1;
+                } else if (matchField("phyused")) {
+                        printf("%lumB", usage.phy[MEM_USAGE_USED] >> 10);
+                        walk += sizeof("phyused") - 1;
+                } else if (matchField("swaptotal")) {
+                        printf("%lumB", usage.swap[MEM_USAGE_TOTAL] >> 10);
+                        walk += sizeof("swaptotal") - 1;
+                } else if (matchField("swapfree")) {
+                        printf("%lumB", usage.swap[MEM_USAGE_FREE] >> 10);
+                        walk += sizeof("swapfree") - 1;
+                } else if (matchField("swapused")) {
+                        printf("%lumB", usage.swap[MEM_USAGE_USED] >> 10);
+                        walk += sizeof("swapused") - 1;
+                }
+        }
+
+        if (output_format == O_I3BAR)
+                printf("\"}");
+
+#undef  matchField
+
+        return;
+err_out:
+        (void) fputs("Cannot read mem-usage\n", stderr);
+}
+
+#if defined(LINUX)
+int
+to_ulong(char *c, unsigned long *ul)
+{
+        *ul = strtoul(c, NULL, 10);
+        if (errno == ERANGE || errno == EINVAL)
+                return 1;
+        return 0;
+}
+
+int
+get_mem_usage(struct mem_usage * usage, int include_buffers,
+                int include_caches)
+{
+        /*
+         * Documentation: $LINUX_HOME/Documentation/filesystems/proc.txt
+         * Implementation: $LINUX_HOME/fs/proc/meminfo.c
+         */
+        char * file = "/proc/meminfo";
+        /*
+         * we actually need 6 lines:
+         * - MemTotal
+         * - MemFree
+         * - Buffers
+         * - Cached
+         * - SwapTotal
+         * - SwapFree
+         *
+         * SwapFree is the last of the necessary ones. So, if you
+         * look into the implementation, there are currently around
+         * 20 lines, each with 28 Bytes. Therefore we need to read
+         * at least 560 Bytes.
+         */
+        char buffer[1024]; /* should be plenty */
+        int read = 0; /* how many lines were found */
+
+        char *fn, *fv, *fl; /* the 3 line-tokens */
+        unsigned long tmp, caches, buffers;
+
+        if (!slurp(file, buffer, sizeof(buffer)))
+                goto err_out;
+
+        memset(usage, 0, sizeof(*usage));
+
+        /* working with strings is so much fun in C */
+
+/* matches the str against fn */
+#define matchField(str) (strncmp(fn, (str), sizeof((str)) - 1) == 0)
+/* try to get a value out of fv */
+#define getValue(val) (to_ulong(fv, &(val)) == 0)
+
+        /* tokenize one line */
+        fn = strtok(buffer, " "); /* pointer to the field-name (and ':') */
+        fv = strtok(NULL, " "); /* pointer to the field-value */
+        fl = strtok(NULL, "\n"); /* should contain 'kB' */
+        while(fn && fv && fl) {
+                /*
+                 * try to find a important line and if one is found
+                 * try to convert the field-value-string into a usable
+                 */
+                if (matchField("MemTotal") && getValue(tmp)) {
+                        usage->phy[MEM_USAGE_TOTAL] = tmp;
+                        read++;
+                } else if (matchField("MemFree") && getValue(tmp)) {
+                        usage->phy[MEM_USAGE_FREE] = tmp;
+                        read++;
+                } else if (matchField("Buffers") && getValue(tmp)) {
+                        buffers = tmp;
+                        read++;
+                } else if (matchField("Cached") && getValue(tmp)) {
+                        caches = tmp;
+                        read++;
+                } else if (matchField("SwapTotal") && getValue(tmp)) {
+                        usage->swap[MEM_USAGE_TOTAL] = tmp;
+                        read++;
+                } else if (matchField("SwapFree") && getValue(tmp)) {
+                        usage->swap[MEM_USAGE_FREE] = tmp;
+                        read++;
+                }
+
+                if(read >= 6)
+                        break;
+
+                /* try to tokenize an other line */
+                fn = strtok(NULL, " ");
+                fv = strtok(NULL, " ");
+                fl = strtok(NULL, "\n");
+        }
+
+#undef getValue
+#undef matchField
+
+        if(read < 6)
+                goto err_out;
+
+        usage->phy[MEM_USAGE_USED] = usage->phy[MEM_USAGE_TOTAL] -
+                usage->phy[MEM_USAGE_FREE];
+        usage->swap[MEM_USAGE_USED] = usage->swap[MEM_USAGE_TOTAL] -
+                usage->swap[MEM_USAGE_FREE];
+
+        if (include_buffers == 0) {
+                usage->phy[MEM_USAGE_FREE] += buffers;
+                usage->phy[MEM_USAGE_USED] -= buffers;
+        }
+
+        if (include_caches == 0) {
+                usage->phy[MEM_USAGE_FREE] += caches;
+                usage->phy[MEM_USAGE_USED] -= caches;
+        }
+
+        return 0;
+err_out:
+        return 1;
+}
+#else
+int
+get_mem_usage(struct mem_usage * usage)
+{
+        return 1;
+}
+#endif
-- 
1.7.3.4

Reply via email to