system-controller: initial CPU load monitoring implementation.
authorKrisztian Litkey <krisztian.litkey@intel.com>
Tue, 28 Jan 2014 16:07:46 +0000 (18:07 +0200)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Thu, 8 Jan 2015 16:37:12 +0000 (18:37 +0200)
This will need quite a bit of touching still to open up also
the data collection and callback triggering logic for Lua
scripting.

Change-Id: Ifb142885eb9af6678aef9bd546f50e62f144c44c

src/plugins/system-controller/system-monitor/Makefile [new file with mode: 0644]
src/plugins/system-controller/system-monitor/cpu.c [new file with mode: 0644]
src/plugins/system-controller/system-monitor/cpu.h [new file with mode: 0644]
src/plugins/system-controller/system-monitor/system-monitor.c [new file with mode: 0644]

diff --git a/src/plugins/system-controller/system-monitor/Makefile b/src/plugins/system-controller/system-monitor/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/plugins/system-controller/system-monitor/cpu.c b/src/plugins/system-controller/system-monitor/cpu.c
new file mode 100644 (file)
index 0000000..af1bfb1
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/debug.h>
+
+#include "cpu.h"
+
+
+typedef struct {
+    uint64_t user;
+    uint64_t nice;
+    uint64_t system;
+    uint64_t idle;
+    uint64_t iowait;
+    uint64_t irq;
+    uint64_t softirq;
+    uint64_t steal;
+    uint64_t guest;
+    uint64_t guest_nice;
+} cpu_ticks_t;
+
+
+typedef struct {
+    cpu_ticks_t ticks[2];
+    int         curr;
+    int         statfd;
+} cpu_load_t;
+
+
+
+static cpu_load_t load = { .statfd = -1 };
+
+
+int read_ticks(int fd, cpu_ticks_t *t)
+{
+    char buf[256], *p, *n;
+    int  len;
+
+    lseek(fd, 0, SEEK_SET);
+
+    if ((len = read(fd, buf, sizeof(buf) - 1)) < 0)
+        return -1;
+
+    buf[len] = '\0';
+    p = buf;
+
+    if (p[0] != 'c' || p[1] != 'p' || p[2] != 'u' || p[3] != ' ') {
+        errno = EILSEQ;
+        return -1;
+    }
+
+    n = NULL;
+
+    p += 4;
+    t->user = strtoll(p, &n, 10);
+
+    if (*n != ' ')
+        return -1;
+
+    t->nice = strtoll((p = n), &n, 10);
+
+    if (*n != ' ')
+        return -1;
+
+    t->system = strtoll((p = n), &n, 10);
+
+    if (*n != ' ')
+        return -1;
+
+    t->idle = strtoll((p = n), &n, 10);
+
+    if (*n != ' ')
+        return -1;
+
+    t->iowait = strtoll((p = n), &n, 10);
+
+    if (*n != ' ')
+        return -1;
+
+    t->irq = strtoll((p = n), &n, 10);
+
+    if (*n != ' ')
+        return -1;
+
+    t->softirq = strtoll((p = n), &n, 10);
+
+    if (*n == '\n') {
+        t->steal = t->guest = t->guest_nice = 0;
+        return 0;
+    }
+    if (*n != ' ')
+        return -1;
+
+    t->steal = strtoll((p = n), &n, 10);
+
+    if (*n == '\n') {
+        t->guest = t->guest_nice = 0;
+        return 0;
+    }
+    if (*n != ' ')
+        return -1;
+
+    t->guest = strtoll((p = n), &n, 10);
+
+    if (*n == '\n') {
+        t->guest_nice = 0;
+        return 0;
+    }
+    if (*n != ' ')
+        return -1;
+
+    t->guest_nice = strtoll((p = n), &n, 10);
+
+    if (*n == '\n')
+        return 0;
+
+    return -1;
+}
+
+
+static void dump_ticks(char *msg, cpu_ticks_t *t)
+{
+    mrp_debug("%s: user=%lld, nice=%lld, system=%lld", msg,
+              t->user, t->nice, t->system);
+    mrp_debug("%s: idle=%lld, iowait=%lld, irq=%lld", msg,
+              t->idle, t->iowait, t->irq);
+    mrp_debug("%s: softirq=%lld, (%lld, %lld, %lld)", msg,
+              t->softirq, t->steal, t->guest, t->guest_nice);
+}
+
+
+int cpu_get_load(int *loadp, int *idlep, int *iowaitp)
+{
+    cpu_ticks_t *prev, *curr;
+    uint64_t     usr, nic, sys, idl, iow, total;
+
+    if (load.statfd < 0) {
+        load.statfd = open("/proc/stat", O_RDONLY);
+
+        if (load.statfd < 0)
+            return -1;
+
+        if (read_ticks(load.statfd, load.ticks) < 0)
+            return -1;
+
+        errno = EAGAIN;
+        return -1;
+    }
+
+    prev = load.ticks +  !!load.curr;
+    curr = load.ticks + !!!load.curr;
+
+    if (read_ticks(load.statfd, curr) < 0)
+        return -1;
+
+    dump_ticks("prev: ", prev);
+    dump_ticks("curr: ", curr);
+
+#define DIFF(_fld) (curr->_fld - prev->_fld)
+
+    total = (usr = DIFF(user)) + (nic = DIFF(nice)) + (sys = DIFF(system)) +
+        (idl = DIFF(idle)) + (iow = DIFF(iowait)) + DIFF(irq) + DIFF(softirq) +
+        DIFF(steal) + DIFF(guest) + DIFF(guest_nice);
+
+
+#undef DIFF
+
+    if (idlep != NULL)
+        *idlep = (int)floor(0.5 + 100.0 * idl / total);
+
+    if (iowaitp != NULL)
+        *iowaitp = (int)floor(0.5 + 100.0 * iow / total);
+
+    if (loadp != NULL)
+        *loadp = (int)(0.5 + 100.0 * (usr + nic + sys + iow) / total);
+
+    load.curr = !load.curr;
+
+    return 0;
+}
diff --git a/src/plugins/system-controller/system-monitor/cpu.h b/src/plugins/system-controller/system-monitor/cpu.h
new file mode 100644 (file)
index 0000000..0e0d949
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SYSCTL_CPU_H__
+#define __MURPHY_SYSCTL_CPU_H__
+
+/** Get the load, idle, and I/O-wait (in percentage) for the system. */
+int cpu_get_load(int *load, int *idlep, int *iowaitp);
+
+#endif /* __MURPHY_SYSCTL_CPU_H__ */
diff --git a/src/plugins/system-controller/system-monitor/system-monitor.c b/src/plugins/system-controller/system-monitor/system-monitor.c
new file mode 100644 (file)
index 0000000..f93dfdd
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#include "cpu.h"
+
+#define MIN_INTERVAL       1000          /* minimal sampling interval */
+#define DEFAULT_INTERVAL   5000          /* default sampling interval */
+
+typedef struct sysmon_s     sysmon_t;
+typedef struct sysmon_lua_s sysmon_lua_t;
+typedef struct cpu_watch_s  cpu_watch_t;
+typedef struct mem_watch_s  mem_watch_t;
+
+static int sysmon_lua_create(lua_State *L);
+static void sysmon_lua_destroy(void *data);
+static void sysmon_lua_changed(void *data, lua_State *L, int member);
+static ssize_t sysmon_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                   size_t size, lua_State *L, void *data);
+
+static int sysmon_lua_add_cpu_watch(lua_State *L);
+static int sysmon_lua_del_cpu_watch(lua_State *L);
+static int sysmon_lua_add_mem_watch(lua_State *L);
+static int sysmon_lua_del_mem_watch(lua_State *L);
+
+
+/*
+ * Lua system-monitor object
+ */
+
+#define SYSMON_LUA_CLASS MRP_LUA_CLASS(sysmon, lua)
+
+struct sysmon_lua_s {
+    mrp_context_t   *ctx;                /* murphy context */
+    lua_State       *L;                  /* Lua execution context */
+    mrp_list_hook_t  cpu_watches;        /* active CPU watches */
+    mrp_list_hook_t  mem_watches;        /* active memory watches */
+    int              interval;           /* sampling interval */
+    mrp_timer_t     *t;                  /* sampling timer */
+    int              next_id;            /* next watch id */
+};
+
+
+/*
+ * CPU load monitor
+ */
+
+typedef struct {
+    char *name;                          /* limit name */
+    int   limit;                         /* upper limit */
+} cpu_limit_t;
+
+typedef enum {
+    CPU_WATCH_INVALID,
+    CPU_WATCH_LOAD,                      /* track overall CPU load */
+    CPU_WATCH_IDLE,                      /* track CPU idle state */
+    CPU_WATCH_IOWAIT                     /* track CPU I/O wait state */
+} cpu_watch_type_t;
+
+struct cpu_watch_s {
+    mrp_list_hook_t    hook;             /* to list of watches */
+    int                id;               /* watch id */
+    cpu_watch_type_t   type;             /* watch type, what to track */
+    cpu_limit_t       *limits;           /* limits for this watch */
+    int                nlimit;           /* number of limits */
+    cpu_limit_t       *limit;            /* limit index of last sampled value */
+    int                callback;         /* reference to callback */
+    int                data;             /* reference to callback data */
+};
+
+
+/*
+ * Lua system-monitor class
+ */
+
+#define OFFS(m) MRP_OFFSET(sysmon_lua_t, m)
+#define NOTIFY  MRP_LUA_CLASS_NOTIFY
+
+MRP_LUA_METHOD_LIST_TABLE(sysmon_lua_methods,
+    MRP_LUA_METHOD_CONSTRUCTOR(sysmon_lua_create)
+    MRP_LUA_METHOD(add_cpu_watch, sysmon_lua_add_cpu_watch)
+    MRP_LUA_METHOD(del_cpu_watch, sysmon_lua_del_cpu_watch)
+    MRP_LUA_METHOD(add_mem_watch, sysmon_lua_add_mem_watch)
+    MRP_LUA_METHOD(add_mem_watch, sysmon_lua_del_mem_watch));
+
+MRP_LUA_METHOD_LIST_TABLE(sysmon_lua_overrides,
+    MRP_LUA_OVERRIDE_CALL(sysmon_lua_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(sysmon_lua_members,
+    MRP_LUA_CLASS_INTEGER("interval", OFFS(interval), NULL, NULL, NOTIFY));
+
+MRP_LUA_DEFINE_CLASS(sysmon, lua, sysmon_lua_t, sysmon_lua_destroy,
+                     sysmon_lua_methods, sysmon_lua_overrides,
+                     sysmon_lua_members, NULL, sysmon_lua_changed,
+                     sysmon_lua_tostring, NULL, MRP_LUA_CLASS_EXTENSIBLE);
+
+MRP_LUA_CLASS_CHECKER(sysmon_lua_t, sysmon_lua, SYSMON_LUA_CLASS);
+
+typedef enum {
+    SYSMON_MEMBER_INTERVAL,
+} sysmon_member_t;
+
+
+static inline cpu_watch_type_t cpu_watch_type(const char *name)
+{
+    if      (!strcmp(name, "load"))   return CPU_WATCH_LOAD;
+    else if (!strcmp(name, "idle"))   return CPU_WATCH_IDLE;
+    else if (!strcmp(name, "iowait")) return CPU_WATCH_IOWAIT;
+    else                              return CPU_WATCH_INVALID;
+}
+
+
+static inline const char *cpu_watch_name(cpu_watch_type_t type)
+{
+    switch (type) {
+    case CPU_WATCH_LOAD:   return "load";
+    case CPU_WATCH_IDLE:   return "idle";
+    case CPU_WATCH_IOWAIT: return "iowait";
+    default:               return "<invalid>";
+    }
+}
+
+
+static int cmp_limits(const void *ptr1, const void *ptr2)
+{
+    cpu_limit_t *l1 = (cpu_limit_t *)ptr1;
+    cpu_limit_t *l2 = (cpu_limit_t *)ptr2;
+
+    return l1->limit - l2->limit;
+}
+
+
+static cpu_watch_t *cpu_watch_create(sysmon_lua_t *sm, char *ebuf, size_t esize)
+{
+    lua_State   *L = sm->L;
+    cpu_watch_t *w;
+    const char  *kname;
+    int          ktype, limit, i;
+    size_t       klen;
+    bool         has_max;
+
+    if ((w = mrp_allocz(sizeof(*w))) == NULL) {
+        snprintf(ebuf, esize, "faled to allocate CPU watch");
+        return NULL;
+    }
+
+    mrp_list_init(&w->hook);
+    w->callback = LUA_NOREF;
+    w->data     = LUA_NOREF;
+
+    if ((w->type = cpu_watch_type(lua_tostring(L, 2))) == CPU_WATCH_INVALID) {
+        snprintf(ebuf, esize, "invalid CPU watch type %s", lua_tostring(L, 2));
+        goto fail;
+    }
+
+    has_max = false;
+
+    MRP_LUA_FOREACH_ALL(L, i, 3, ktype, kname, klen) {
+        if (ktype != LUA_TSTRING || lua_type(L, -1) != LUA_TNUMBER) {
+            snprintf(ebuf, esize, "invalid CPU watch limit entry #%d", i);
+            goto fail;
+        }
+
+        if ((limit = lua_tointeger(L, -1)) < 0 || limit > 100) {
+            snprintf(ebuf, esize, "CPU watch limit #%d (%d %%) out of range",
+                     i, limit);
+            goto fail;
+        }
+
+        if (mrp_reallocz(w->limits, w->nlimit, w->nlimit + 1) == NULL) {
+            snprintf(ebuf, esize, "failed to extend CPU watch limit table");
+            goto fail;
+        }
+
+        if ((w->limits[i].name = mrp_strdup(kname)) == NULL) {
+            snprintf(ebuf, esize, "failed to extend CPU watch limit table");
+            goto fail;
+        }
+
+        w->limits[i].limit = limit;
+        w->nlimit++;
+
+        if (limit == 100)
+            has_max = true;
+    }
+
+    if (w->nlimit < 1) {
+        snprintf(ebuf, esize, "expecting at least 1 CPU watch limit");
+        goto fail;
+    }
+
+    if (!has_max) {
+        if (mrp_reallocz(w->limits, w->nlimit, w->nlimit + 1) == NULL) {
+            snprintf(ebuf, esize, "failed to add CPU watch limit 100");
+            goto fail;
+        }
+
+        if ((w->limits[w->nlimit].name = mrp_strdup("full")) == NULL) {
+            snprintf(ebuf, esize, "failed to add CPU watch limit 100");
+            goto fail;
+        }
+
+        w->limits[w->nlimit].limit = 100;
+        w->nlimit++;
+    }
+
+    qsort(w->limits, w->nlimit, sizeof(w->limits[0]), cmp_limits);
+    w->limit = w->limits;
+
+    w->id       = sm->next_id++;
+    w->callback = mrp_lua_object_ref_value(sm, L, 4);
+    w->data     = mrp_lua_object_ref_value(sm, L, 5);
+
+    mrp_list_append(&sm->cpu_watches, &w->hook);
+
+    return w;
+
+ fail:
+    for (i = 0; i < w->nlimit; i++)
+        mrp_free(w->limits[i].name);
+    mrp_free(w->limits);
+    mrp_free(w);
+
+    return NULL;
+}
+
+
+static void cpu_watch_destroy(sysmon_lua_t *sm, cpu_watch_t *w)
+{
+    int i;
+
+    mrp_list_delete(&w->hook);
+    mrp_lua_object_unref_value(sm, sm->L, w->callback);
+    mrp_lua_object_unref_value(sm, sm->L, w->data);
+
+    for (i = 0; i < w->nlimit; i++)
+        mrp_free(w->limits[i].name);
+    mrp_free(w->limits);
+
+    mrp_free(w);
+}
+
+
+static cpu_watch_t *cpu_watch_find(sysmon_lua_t *sm, int id)
+{
+    mrp_list_hook_t *p, *n;
+    cpu_watch_t     *w;
+
+    mrp_list_foreach(&sm->cpu_watches, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+
+        if (w->id == id)
+            return w;
+    }
+
+    return NULL;
+}
+
+
+static void cpu_watch_notify(sysmon_lua_t *sm, cpu_watch_t *w, const char *prev,
+                             const char *curr)
+{
+    if (!mrp_lua_object_deref_value(sm, sm->L, w->callback, false)) {
+        mrp_log_error("Failed to dereference Lua CPU watch callback.");
+        return;
+    }
+
+    lua_pushstring(sm->L, cpu_watch_name(w->type));
+    lua_pushstring(sm->L, prev);
+    lua_pushstring(sm->L, curr);
+    mrp_lua_object_deref_value(sm, sm->L, w->data, true);
+
+    if (lua_pcall(sm->L, 4, 0, 0) != 0)
+        mrp_log_error("Failed to notify Lua CPU watch (error: %s).",
+                      lua_tostring(sm->L, -1));
+}
+
+
+static void cpu_watch_check(sysmon_lua_t *sm, cpu_watch_t *w,
+                            int load, int idle, int iowait)
+{
+    cpu_limit_t *l;
+    int          i, value;
+
+    switch (w->type) {
+    default:
+    case CPU_WATCH_LOAD:   value = load;   break;
+    case CPU_WATCH_IDLE:   value = idle;   break;
+    case CPU_WATCH_IOWAIT: value = iowait; break;
+    }
+
+    for (i = 0, l = w->limits; i < w->nlimit; i++, l++) {
+        if (value <= l->limit) {
+            if (w->limit != l) {
+                cpu_watch_notify(sm, w, w->limit->name, l->name);
+                w->limit = l;
+            }
+            break;
+        }
+    }
+}
+
+
+static void sample_cpu_load(mrp_timer_t *t, void *user_data)
+{
+    sysmon_lua_t    *sm = (sysmon_lua_t *)user_data;
+    int              idle, iowait, load;
+    mrp_list_hook_t *p, *n;
+    cpu_watch_t     *w;
+
+    MRP_UNUSED(t);
+
+    if (cpu_get_load(&load, &idle, &iowait) < 0) {
+        if (errno != EAGAIN)
+            mrp_log_error("cpu_get_load failed (%d: %s)", errno,
+                          strerror(errno));
+    }
+    else {
+        mrp_debug("load = %d, idle = %d, iowait = %d", load, idle, iowait);
+
+        mrp_list_foreach(&sm->cpu_watches, p, n) {
+            w = mrp_list_entry(p, typeof(*w), hook);
+            cpu_watch_check(sm, w, load, idle, iowait);
+        }
+    }
+}
+
+
+static void sysmon_lua_changed(void *data, lua_State *L, int member)
+{
+    sysmon_lua_t *sm = (sysmon_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (member) {
+    case SYSMON_MEMBER_INTERVAL:
+        if (sm->interval < MIN_INTERVAL)
+            sm->interval = MIN_INTERVAL;
+
+        if (sm->t != NULL)
+            mrp_mod_timer(sm->t, sm->interval);
+
+        mrp_log_info("system-monitor: sampling interval set to %d msecs.",
+                     sm->interval);
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static sysmon_lua_t *singleton(lua_State *L)
+{
+    static sysmon_lua_t *sm = NULL;
+
+    if (L == NULL) {
+        mrp_debug("clearing system-monitor singleton object");
+        sm = NULL;
+    }
+    else if (sm == NULL) {
+        mrp_debug("creating system-monitor singleton object");
+        sm = (sysmon_lua_t *)mrp_lua_create_object(L, SYSMON_LUA_CLASS,
+                                                   NULL, 0);
+        mrp_list_init(&sm->cpu_watches);
+        mrp_list_init(&sm->mem_watches);
+
+        sm->L        = L;
+        sm->ctx      = mrp_lua_get_murphy_context();
+        sm->next_id  = 1;
+        sm->interval = DEFAULT_INTERVAL;
+    }
+
+    return sm;
+}
+
+
+static int sysmon_lua_create(lua_State *L)
+{
+    sysmon_lua_t *sm = singleton(L);
+
+    mrp_lua_push_object(L, sm);
+
+    return 1;
+}
+
+
+static int sysmon_lua_get(lua_State *L)
+{
+    return sysmon_lua_create(L);
+}
+
+
+static void sysmon_lua_destroy(void *data)
+{
+    sysmon_lua_t    *sm = (sysmon_lua_t *)data;
+    mrp_list_hook_t *p, *n;
+    cpu_watch_t     *w;
+
+    mrp_list_foreach(&sm->cpu_watches, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+        cpu_watch_destroy(sm, w);
+    }
+
+    mrp_del_timer(sm->t);
+    sm->t = NULL;
+
+    singleton(NULL);
+}
+
+
+static int sysmon_lua_add_cpu_watch(lua_State *L)
+{
+    sysmon_lua_t *sm;
+    cpu_watch_t  *w;
+    char          err[256];
+    int           narg;
+
+    if ((narg = lua_gettop(L)) != 5)
+        return luaL_error(L, "expecting 5 arguments, got %d", narg);
+
+    sm = sysmon_lua_check(L, 1);
+
+    luaL_checktype(L, 2, LUA_TSTRING);
+    luaL_checktype(L, 3, LUA_TTABLE);
+    luaL_checktype(L, 4, LUA_TFUNCTION);
+    luaL_checkany(L, 5);
+
+    if ((w = cpu_watch_create(sm, err, sizeof(err))) == NULL)
+        return luaL_error(L, "%s", err);
+
+    if (sm->t == NULL)
+        sm->t = mrp_add_timer(sm->ctx->ml, 2000, sample_cpu_load, sm);
+
+    lua_pushinteger(L, w->id);
+    return 1;
+}
+
+
+static int sysmon_lua_del_cpu_watch(lua_State *L)
+{
+    sysmon_lua_t *sm = sysmon_lua_check(L, 1);
+    int           id = luaL_checkinteger(L, 2);
+    cpu_watch_t  *w  = cpu_watch_find(sm, id);
+
+    if (w != NULL) {
+        cpu_watch_destroy(sm, w);
+
+        if (mrp_list_empty(&sm->cpu_watches) &&
+            mrp_list_empty(&sm->mem_watches)) {
+            mrp_del_timer(sm->t);
+            sm->t = NULL;
+        }
+    }
+
+    return 0;
+}
+
+
+static int sysmon_lua_add_mem_watch(lua_State *L)
+{
+    MRP_UNUSED(L);
+
+    mrp_debug("should add memory usage watch...");
+
+    return 0;
+}
+
+
+static int sysmon_lua_del_mem_watch(lua_State *L)
+{
+    MRP_UNUSED(L);
+
+    mrp_debug("should delete memory usage watch...");
+
+    return 0;
+}
+
+
+static ssize_t sysmon_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                   size_t size, lua_State *L, void *data)
+{
+    sysmon_lua_t *sm = (sysmon_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{%s system-monitor @ %d msecs}",
+                        sm->t ? "active" : "inactive", sm->interval);
+    }
+}
+
+
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, SYSMON_LUA_CLASS,
+                             { "get_system_monitor", sysmon_lua_get });