system-monitor: reworked and split out to a separate plugin.
authorKrisztian Litkey <kli@iki.fi>
Fri, 14 Feb 2014 21:08:33 +0000 (23:08 +0200)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Thu, 8 Jan 2015 16:37:13 +0000 (18:37 +0200)
Reworked system-monitor, allowing a bit more flexibility and
scriptability. Both the CPU and memory usage monitors now
allow overriding the default data collector functions from
Lua. Also both now support a configurable exponentially
weighted moving average estimator for smoothing out occasional
spikes over a configurable time window.

Change-Id: I7fb2a2ab1189769c61f2531038a4b7c8f17960b6

15 files changed:
configure.ac
src/Makefile.am
src/plugins/system-monitor/Makefile [new file with mode: 0644]
src/plugins/system-monitor/cpu-sampler.c [new file with mode: 0644]
src/plugins/system-monitor/cpu-sampler.h [new file with mode: 0644]
src/plugins/system-monitor/cpu-watch.c [new file with mode: 0644]
src/plugins/system-monitor/cpu-watch.h [new file with mode: 0644]
src/plugins/system-monitor/estimator.h [new file with mode: 0644]
src/plugins/system-monitor/mem-sampler.c [new file with mode: 0644]
src/plugins/system-monitor/mem-sampler.h [new file with mode: 0644]
src/plugins/system-monitor/mem-watch.c [new file with mode: 0644]
src/plugins/system-monitor/mem-watch.h [new file with mode: 0644]
src/plugins/system-monitor/plugin-system-monitor.c [new file with mode: 0644]
src/plugins/system-monitor/system-monitor.c [new file with mode: 0644]
src/plugins/system-monitor/system-monitor.h [new file with mode: 0644]

index a8b1e08..dbff4e2 100644 (file)
@@ -525,6 +525,22 @@ AC_SUBST(WAYLAND_CLIENT_LIBS)
 AC_SUBST(LIBXML2_CFLAGS)
 AC_SUBST(LIBXML2_LIBS)
 
+# Check if system-monitor (plugin) support should be enabled.
+AC_ARG_ENABLE(system-monitor,
+              [  --enable-system-monitor enable system-monitor support],
+             [enable_sysmon=$enableval], [enable_sysmon=no])
+
+if test "$enable_sysmon" = "yes"; then
+    AC_MSG_NOTICE([System-monitor support is enabled.])
+    AC_DEFINE([SYSMON_ENABLED], 1, [Enable system-monitor support ?])
+else
+    AC_MSG_NOTICE([System-monitor support is disabled.])
+fi
+
+AM_CONDITIONAL(SYSMON_ENABLED, [test "$enable_sysmon" = "yes"])
+AC_SUBST(SYSMON_ENABLED)
+
+
 # Check if dlog support was enabled.
 AC_ARG_ENABLE(dlog,
               [  --enable-dlog           enable dlog support],
@@ -691,6 +707,7 @@ AM_CONDITIONAL(DISABLED_PLUGIN_TELEPHONY, [check_if_disabled telephony])
 AM_CONDITIONAL(DISABLED_PLUGIN_RESOURCE_ASM,     [check_if_disabled resource-asm])
 
 AM_CONDITIONAL(DISABLED_PLUGIN_SYSTEMCTL,  [test $enable_systemctl != yes])
+AM_CONDITIONAL(DISABLED_PLUGIN_SYSMON,  [test $enable_sysmon != yes])
 
 AM_CONDITIONAL(BUILTIN_PLUGIN_TEST,     [check_if_internal test])
 AM_CONDITIONAL(BUILTIN_PLUGIN_DBUS,     [check_if_internal dbus])
@@ -707,6 +724,7 @@ AM_CONDITIONAL(BUILTIN_PLUGIN_SYSTEMD,  [check_if_internal systemd])
 AM_CONDITIONAL(BUILTIN_PLUGIN_TELEPHONY, [check_if_internal telephony])
 AM_CONDITIONAL(BUILTIN_PLUGIN_RESOURCE_ASM,      [check_if_internal resource-asm])
 AM_CONDITIONAL(BUILTIN_PLUGIN_SYSTEMCTL, [check_if_internal system-controller])
+AM_CONDITIONAL(BUILTIN_PLUGIN_SYSMON, [check_if_internal system-monitor])
 
 # Check for Check (unit test framework).
 PKG_CHECK_MODULES(CHECK,
@@ -861,6 +879,7 @@ echo "Websockets support: $enable_websockets"
 echo "systemd support: $enable_systemd"
 echo "Telephony support: $enable_telephony"
 echo "System controller support: $enable_systemctl"
+echo "System monitor support: $enable_sysmon"
 echo "AIL support: $enable_ail"
 echo "Plugins:"
 echo "  - linked-in:"
index 04c6c85..763633e 100644 (file)
@@ -1617,6 +1617,73 @@ clean-linker-script::
 generate-linker-scripts: linker-script.system-controller
 endif
 
+# system-monitor
+if SYSMON_ENABLED
+SYSMON_PLUGIN_SOURCES =                                                \
+       plugins/system-monitor/plugin-system-monitor.c          \
+       plugins/system-monitor/system-monitor.c                 \
+       plugins/system-monitor/cpu-watch.c                      \
+       plugins/system-monitor/cpu-sampler.c                    \
+       plugins/system-monitor/mem-watch.c                      \
+       plugins/system-monitor/mem-sampler.c
+
+
+SYSMON_PLUGIN_CFLAGS  = -I$(top_builddir)/src/plugins/system-monitor
+SYSTEMCTL_PLUGIN_LIBS = -lm
+
+SYSMON_PLUGIN_LOADER  = linkedin-system-monitor-loader.c
+
+if !DISABLED_PLUGIN_SYSMON
+if BUILTIN_PLUGIN_SYSMON
+LINKEDIN_PLUGINS     += libmurphy-plugin-system-monitor.la
+lib_LTLIBRARIES      += libmurphy-plugin-system-monitor.la
+SYSMON_PLUGIN_CFLAGS += $(BUILTIN_CFLAGS)
+
+
+libmurphy_plugin_system_monitor_ladir =                                        \
+               $(includedir)/murphy/system-monitor
+
+libmurphy_plugin_system_monitor_la_SOURCES =                           \
+               $(SYSMON_PLUGIN_SOURCES)                                \
+               $(SYSMON_PLUGIN_LOADER)
+
+libmurphy_plugin_system_monitor_la_CFLAGS =                            \
+               $(SYSMON_PLUGIN_CFLAGS)                                 \
+               $(AM_CFLAGS)
+
+libmurphy_plugin_system_monitor_la_LDFLAGS =                           \
+               -Wl,-version-script=linker-script.system-monitor        \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_plugin_system_monitor_la_LIBADD =                            \
+               libmurphy-core.la                                       \
+               libmurphy-common.la                                     \
+               $(SYSMON_PLUGIN_LIBS)
+
+libmurphy_plugin_system_monitor_la_DEPENDENCIES =                      \
+               linker-script.system-monitor                            \
+       $(filter %.la, $(libmurphy_plugin_system_monitor_la_LIBADD))
+else
+plugin_system_monitor_la_SOURCES = $(SYSMON_PLUGIN_SOURCES)
+plugin_system_monitor_la_CFLAGS  = $(SYSMON_PLUGIN_CFLAGS)             \
+                                  $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_system_monitor_la_LDFLAGS = -module -avoid-version
+plugin_system_monitor_la_LIBADD  = $(SYSMON_PLUGIN_LIBS)
+plugin_LTLIBRARIES                 += plugin-system-monitor.la
+endif
+endif
+
+# linkedin system-monitor plugin linker script generation
+linker-script.system-monitor: $(SYSMON_PLUGIN_LOADER:%.c=%.h)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -c "$(libmurphy_plugin_system_monitor_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.system-monitor
+
+generate-linker-scripts: linker-script.system-monitor
+endif
+
 # telephony plugin
 TELEPHONY_PLUGIN_SOURCES =  plugins/plugin-telephony.c         \
                             plugins/telephony/telephony.c      \
diff --git a/src/plugins/system-monitor/Makefile b/src/plugins/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-monitor/cpu-sampler.c b/src/plugins/system-monitor/cpu-sampler.c
new file mode 100644 (file)
index 0000000..9bfe85c
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+ * 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/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+
+#include "cpu-sampler.h"
+
+/*
+ * CPU 'ticks' (entry for a single CPU in /proc/stat)
+ */
+
+typedef struct {                         /* time spent in various states */
+    uint64_t user;                       /* in user mode */
+    uint64_t nice;                       /*     - || - with low priority */
+    uint64_t system;                     /* in system mode */
+    uint64_t idle;                       /* in the idle task */
+    uint64_t iowait;                     /* waiting for I/O completion */
+    uint64_t irq;                        /* servicing interrupts */
+    uint64_t softirq;                    /* servicing softirqs */
+    uint64_t steal;                      /* in other guests (if virtualized) */
+    uint64_t guest;                      /* running guests */
+    uint64_t guest_nice;                 /*     - || - with low priority */
+} ticks_t;
+
+
+/*
+ * calculated CPU load
+ */
+
+typedef struct {
+    ticks_t samples[2];                  /* tick sample buffer */
+    int     current;                     /* current write index */
+} cpu_state_t;
+
+
+static int       statfd = -1;            /* fd for /proc/stat */
+static cpu_t    *cpus;                   /* known CPUs */
+static int       ncpu;                   /* number of CPUs */
+static uint32_t  cpu_mask;               /* mask of monitored CPUs */
+static cpu_state_t *states;              /* CPU load/state tracking */
+
+
+static int enumerate_cpus(void)
+{
+    cpu_t *cpu;
+    char   buf[4096], *p, *e;
+    int    i, len;
+
+    if (statfd < 0) {
+        if ((statfd = open("/proc/stat", O_RDONLY)) < 0)
+            return -1;
+
+        fcntl(statfd, F_SETFD, FD_CLOEXEC);
+    }
+
+    if ((len = read(statfd, buf, sizeof(buf) - 1)) <= 0)
+        return -1;
+    else
+        buf[len] = '\0';
+
+    p = buf;
+    while (p[0] == 'c' && p[1] == 'p' && p[2] == 'u') {
+        for (e = p; *e != ' ' && *e; e++)
+            ;
+
+        if (!*e) {
+            errno = EILSEQ;
+            goto fail;
+        }
+
+        if (mrp_reallocz(cpus, ncpu, ncpu + 1) == NULL)
+            goto fail;
+        else
+            cpu = cpus + ncpu;
+
+        if ((cpu->name = mrp_datadup(p, e - p + 1)) == NULL)
+            goto fail;
+
+        cpu->name[e-p] = '\0';
+        cpu->id        = ncpu++;
+
+        mrp_debug("CPU #%d is '%s'", cpu->id, cpu->name);
+
+        if ((p = strchr(p, '\n')) != NULL)
+            p++;
+        else
+            break;
+    }
+
+    if ((states = mrp_allocz(ncpu * sizeof(*states))) == NULL)
+        goto fail;
+
+    for (i = 0; i < ncpu; i++)
+        states[i].current = -1;
+
+    cpu_set_cpu_mask(-1);
+
+    return ncpu;
+
+ fail:
+    if (cpus != NULL) {
+        for (i = 0, cpu = cpus; i < ncpu; cpu++, i++)
+            mrp_free(cpu->name);
+
+        mrp_free(cpus);
+        cpus = NULL;
+        ncpu = 0;
+    }
+
+    return -1;
+}
+
+
+int cpu_get_id(const char *name)
+{
+    cpu_t *cpu;
+    int    i;
+
+    if (cpus == NULL && enumerate_cpus() < 0)
+        return -1;
+
+    for (i = 0, cpu = cpus; i < ncpu; i++, cpu++)
+        if (!strcmp(cpu->name, name))
+            return cpu->id;
+
+    return -1;
+}
+
+
+const char *cpu_get_name(int id)
+{
+    cpu_t *cpu;
+    int    i;
+
+    if (cpus == NULL && enumerate_cpus() < 0)
+        return NULL;
+
+    for (i = 0, cpu = cpus; i < ncpu; i++, cpu++)
+        if (cpu->id == id)
+            return cpu->name;
+
+    return NULL;
+}
+
+
+int cpu_get_cpus(const cpu_t **cpusptr)
+{
+    if (cpus == NULL && enumerate_cpus() < 0)
+        return -1;
+
+    if (cpusptr != NULL)
+        *cpusptr = cpus;
+
+    return ncpu;
+}
+
+
+int cpu_set_cpu_mask(uint32_t mask)
+{
+    if (cpus == NULL && enumerate_cpus() < 0)
+        return -1;
+
+    cpu_mask = (mask & ((1 << ncpu) - 1));
+
+    return 0;
+}
+
+
+static int sample_load(void)
+{
+    char      buf[4096], *p;
+    int       len;
+    uint32_t  cmask, cid;
+    ticks_t  *t;
+
+    lseek(statfd, 0, SEEK_SET);
+
+    if ((len = read(statfd, buf, sizeof(buf) - 1)) < 0)
+        return -1;
+
+    buf[len] = '\0';
+    p = buf;
+
+    cmask = cpu_mask;
+    cid   = 0;
+
+    while (cmask) {
+        if (!(cmask & (1 << cid)))
+            goto next_cpu;
+
+        if (p[0] != 'c' || p[1] != 'p' || p[2] != 'u')
+            goto parse_error;
+
+        p += 3;
+        while ('0' <= *p && *p <= '9')
+            p++;
+
+        if (*p != ' ')
+            goto parse_error;
+        else
+            p++;
+
+        t = states[cid].samples + (states[cid].current & 0x1 ? 0 : 1);
+
+#define PARSE_FIELD(_f, _next) do {                             \
+            t->_f = strtoll(p, &p, 10);                         \
+                                                                \
+            if (*p != _next)                                    \
+                goto parse_error;                               \
+        } while (0)
+
+        PARSE_FIELD(user      , ' ' );
+        PARSE_FIELD(nice      , ' ' );
+        PARSE_FIELD(system    , ' ' );
+        PARSE_FIELD(idle      , ' ' );
+        PARSE_FIELD(iowait    , ' ' );
+        PARSE_FIELD(irq       , ' ' );
+        PARSE_FIELD(softirq   , ' ' );
+        PARSE_FIELD(steal     , ' ' );
+        PARSE_FIELD(guest     , ' ' );
+        PARSE_FIELD(guest_nice, '\n');
+
+#undef PARSE_FIELD
+
+        cmask &= ~(1 << cid);
+
+        if (MRP_UNLIKELY(states[cid].current == -1)) {
+            states[cid].samples[1] = states[cid].samples[0];
+            states[cid].current = 0;
+        }
+        else
+            states[cid].current = !states[cid].current;
+
+    next_cpu:
+        cid++;
+        if (*p == '\n' || (p = strchr(p, '\n')) != NULL)
+            p++;
+        else {
+            if (cmask != 0) {
+                errno = ENODATA;
+                return -1;
+            }
+        }
+    }
+
+    return 0;
+
+ parse_error:
+    errno = EILSEQ;
+    return -1;
+}
+
+
+int cpu_sample_load(void)
+{
+    return sample_load();
+}
+
+
+int cpu_get_sample(int cpu, cpu_sample_t sample)
+{
+    cpu_state_t *state;
+    uint64_t     usr, nic, sys, idl, iow, irq, sirq, stl, gst, gstnc, total;
+
+    if (cpus == NULL && enumerate_cpus() < 0)
+        return -1;
+
+    if (cpu >= ncpu || !(cpu_mask & (1 << cpu))) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    if (sample < 0 || sample > CPU_SAMPLE_GUEST_LOAD) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    state = states + cpu;
+
+#define DIFF(_fld)                              \
+    state->samples[ state->current]._fld -      \
+        state->samples[!state->current]._fld
+    total  = (usr   = DIFF(user));
+    total += (nic   = DIFF(nice));
+    total += (sys   = DIFF(system));
+    total += (idl   = DIFF(idle));
+    total += (iow   = DIFF(iowait));
+    total += (irq   = DIFF(irq));
+    total += (sirq  = DIFF(softirq));
+    total += (stl   = DIFF(steal));
+    total += (gst   = DIFF(guest));
+    total += (gstnc = DIFF(guest_nice));
+#undef DIFF
+
+    if (total == 0) {
+        errno = EAGAIN;
+        return -1;
+    }
+
+    switch (sample) {
+#define PCNT(v) ((int)floor(0.5 + 100.0 * (v) / total))
+    case CPU_SAMPLE_USER:       return PCNT(usr);
+    case CPU_SAMPLE_NICE:       return PCNT(nic);
+    case CPU_SAMPLE_SYSTEM:     return PCNT(sys);
+    case CPU_SAMPLE_IDLE:       return PCNT(idl);
+    case CPU_SAMPLE_IOWAIT:     return PCNT(iow);
+    case CPU_SAMPLE_IRQ:        return PCNT(irq);
+    case CPU_SAMPLE_SOFTIRQ:    return PCNT(sirq);
+    case CPU_SAMPLE_STEAL:      return PCNT(stl);
+    case CPU_SAMPLE_GUEST:      return PCNT(gst);
+    case CPU_SAMPLE_GUEST_NICE: return PCNT(gstnc);
+    case CPU_SAMPLE_LOAD:       return PCNT(usr + nic + sys + iow);
+    case CPU_SAMPLE_INTERRUPT:  return PCNT(irq + sirq);
+    case CPU_SAMPLE_GUEST_LOAD: return PCNT(gst + gstnc);
+    default:                    return -1;
+#undef PCNT
+    }
+}
diff --git a/src/plugins/system-monitor/cpu-sampler.h b/src/plugins/system-monitor/cpu-sampler.h
new file mode 100644 (file)
index 0000000..8cc76e0
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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_SYSTEM_MONITOR_CPU_SAMPLER_H__
+#define __MURPHY_SYSTEM_MONITOR_CPU_SAMPLER_H__
+
+/*
+ * type of CPU load sampling
+ */
+
+typedef enum {
+    CPU_SAMPLE_INVALID = -1,             /* invalid type */
+    /* these correspond directly to entries in /proc/stat */
+    CPU_SAMPLE_USER,                     /* time in user mode */
+    CPU_SAMPLE_NICE,                     /*   - || - with low priority */
+    CPU_SAMPLE_SYSTEM,                   /* time in system mode */
+    CPU_SAMPLE_IDLE,                     /* time in the idle task */
+    CPU_SAMPLE_IOWAIT,                   /* time waiting for I/O completion */
+    CPU_SAMPLE_IRQ,                      /* time serving interrupts */
+    CPU_SAMPLE_SOFTIRQ,                  /* time serving softirqs */
+    CPU_SAMPLE_STEAL,                    /* time in other guests */
+    CPU_SAMPLE_GUEST,                    /* time running guests */
+    CPU_SAMPLE_GUEST_NICE,               /*   -||- with log priority */
+    /* these are calculated from the above */
+    CPU_SAMPLE_LOAD,                     /* user + nice + system + iowait */
+    CPU_SAMPLE_INTERRUPT,                /* irq + softirq */
+    CPU_SAMPLE_GUEST_LOAD,               /* guest + guest_nice */
+    /* these are not coming from /proc/stat at all */
+    CPU_SAMPLE_CGROUP,                   /* time running tasks in a cgroup */
+} cpu_sample_t;
+
+
+/*
+ * a CPU name and identifier
+ */
+
+typedef struct {
+    char *name;                          /* CPU name in /proc/stat */
+    int   id;                            /* our identifier for it */
+} cpu_t;
+
+
+/*
+ * sampled CPU load
+ */
+
+typedef struct {
+    int  user;
+    int  nice;
+    int  system;
+    int  idle;
+    int  iowait;
+    int  irq;
+    int  sofirq;
+    int  steal;
+    int  guest;
+    int  guest_nice;
+    int  load;
+    int  interrupt;
+    int  guest_load;
+#if 0
+    int *cgroup;
+#endif
+} cpu_load_t;
+
+
+/** Get the number, names and identifiers of all CPUs in the system. */
+int cpu_get_cpus(const cpu_t **cpus);
+
+/** Get the identifier for the given CPU name. */
+int cpu_get_id(const char *cpu);
+
+/** Get the CPU name for the given CPU id. */
+const char *cpu_get_name(int id);
+
+/** Set the mask of CPU ids to monitor. */
+int cpu_set_cpu_mask(uint32_t cpu_mask);
+
+/** Take another sample of the (various) CPU load(s) we track. */
+int cpu_sample_load(void);
+
+/** Get the last load for the given CPU and sample type. */
+int cpu_get_sample(int cpu, cpu_sample_t sample);
+
+#endif /* __MURPHY_SYSTEM_MONITOR_CPU_SAMPLER_H__ */
diff --git a/src/plugins/system-monitor/cpu-watch.c b/src/plugins/system-monitor/cpu-watch.c
new file mode 100644 (file)
index 0000000..8981abf
--- /dev/null
@@ -0,0 +1,651 @@
+/*
+ * 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/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#include "cpu-watch.h"
+
+/*
+ * CPU watch object
+ */
+
+#define CPU_WATCH_LUA_CLASS MRP_LUA_CLASS(cpu_watch, lua)
+#define RO                  MRP_LUA_CLASS_READONLY
+#define NOINIT              MRP_LUA_CLASS_NOINIT
+#define NOFLAGS             MRP_LUA_CLASS_NOFLAGS
+#define SETGET              cpu_watch_setmember, cpu_watch_getmember
+#define setmember           cpu_watch_setmember
+#define getmember           cpu_watch_getmember
+
+static int cpu_watch_no_constructor(lua_State *L);
+static void cpu_watch_destroy(void *data);
+static void cpu_watch_changed(void *data, lua_State *L, int member);
+static ssize_t cpu_watch_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                  size_t size, lua_State *L, void *data);
+static int cpu_watch_setmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v);
+static int cpu_watch_getmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v);
+static int cpu_watch_delete(lua_State *L);
+
+MRP_LUA_METHOD_LIST_TABLE(cpu_watch_methods,
+    MRP_LUA_METHOD_CONSTRUCTOR(cpu_watch_no_constructor)
+    MRP_LUA_METHOD(delete, cpu_watch_delete));
+
+MRP_LUA_METHOD_LIST_TABLE(cpu_watch_overrides,
+    MRP_LUA_OVERRIDE_CALL(cpu_watch_no_constructor));
+
+MRP_LUA_MEMBER_LIST_TABLE(cpu_watch_members,
+    MRP_LUA_CLASS_STRING ("cpu"     , 0, setmember, getmember, RO        )
+    MRP_LUA_CLASS_STRING ("sample"  , 0, setmember, getmember, RO        )
+    MRP_LUA_CLASS_ANY    ("limits"  , 0, setmember, getmember, RO        )
+    MRP_LUA_CLASS_INTEGER("polling" , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_INTEGER("window"  , 0, setmember, getmember,    NOFLAGS)
+    MRP_LUA_CLASS_INTEGER("raw"     , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_INTEGER("value"   , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_STRING ("current" , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_STRING ("previous", 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_ANY    ("notify"  , 0, setmember, getmember,    NOFLAGS)
+    MRP_LUA_CLASS_ANY    ("update"  , 0, setmember, getmember,    NOFLAGS));
+
+MRP_LUA_DEFINE_CLASS(cpu_watch, lua, cpu_watch_lua_t, cpu_watch_destroy,
+    cpu_watch_methods, cpu_watch_overrides, cpu_watch_members, NULL,
+    cpu_watch_changed, cpu_watch_tostring , NULL,
+                     MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_PRIVREFS);
+
+MRP_LUA_CLASS_CHECKER(cpu_watch_lua_t, cpu_watch_lua, CPU_WATCH_LUA_CLASS);
+
+typedef enum {
+    CPU_WATCH_MEMBER_CPU,
+    CPU_WATCH_MEMBER_SAMPLE,
+    CPU_WATCH_MEMBER_LIMITS,
+    CPU_WATCH_MEMBER_POLLING,
+    CPU_WATCH_MEMBER_WINDOW,
+    CPU_WATCH_MEMBER_RAW,
+    CPU_WATCH_MEMBER_VALUE,
+    CPU_WATCH_MEMBER_CURRENT,
+    CPU_WATCH_MEMBER_PREVIOUS,
+    CPU_WATCH_MEMBER_NOTIFY,
+    CPU_WATCH_MEMBER_UPDATE
+} cpu_watch_member_t;
+
+static inline int get_cpu_id(const char *name);
+static inline const char *get_cpu_name(int id);
+static inline cpu_sample_t get_sample_id(const char *name);
+static inline const char *get_sample_name(cpu_sample_t id);
+static inline void recalc_smoothing(cpu_watch_lua_t *w);
+static int setup_limits(cpu_watch_lua_t *w, lua_State *L, int limref);
+static void cleanup_limits(cpu_watch_lua_t *w, lua_State *L,
+                           cpu_limit_t *limits, int n, int limref);
+
+
+static int cpu_watch_no_constructor(lua_State *L)
+{
+    return luaL_error(L, "trying create a CPU watch via constructor.");
+}
+
+
+cpu_watch_lua_t *cpu_watch_create(sysmon_lua_t *sm, int polling, lua_State *L)
+{
+    cpu_watch_lua_t *w;
+    char             e[256];
+
+    luaL_checktype(L, 2, LUA_TTABLE);
+
+    w = (cpu_watch_lua_t *)mrp_lua_create_object(L, CPU_WATCH_LUA_CLASS,
+                                                 NULL, 0);
+
+    mrp_list_init(&w->hook);
+    w->sysmon  = sm;
+    w->polling = polling;
+    w->limref  = LUA_NOREF;
+    w->window  = CPU_WATCH_WINDOW;
+
+    if (mrp_lua_init_members(w, L, 2, e, sizeof(e)) != 1) {
+        luaL_error(L, "failed to initialize CPU watch (error: %s)",
+                   *e ? e : "<unknown error>");
+        return NULL;
+    }
+
+    if (w->sample == CPU_SAMPLE_IDLE)
+        w->value.S = 100;
+    else
+        w->value.S = 0;
+
+    recalc_smoothing(w);
+
+    return w;
+}
+
+
+static void cpu_watch_destroy(void *data)
+{
+    MRP_UNUSED(data);
+
+    mrp_debug("CPU watch %p destroyed", data);
+
+    return;
+}
+
+
+static int cpu_watch_delete(lua_State *L)
+{
+    cpu_watch_lua_t *w = cpu_watch_lua_check(L, 1);
+
+    mrp_list_delete(&w->hook);
+    mrp_list_init(&w->hook);
+
+    sysmon_del_cpu_watch(w->sysmon, w);
+
+    cleanup_limits(w, L, w->limits, -1, w->limref);
+    w->limits = NULL;
+    w->limref = LUA_NOREF;
+
+    mrp_funcbridge_unref(L, w->update);
+    mrp_funcbridge_unref(L, w->notify);
+    w->update = NULL;
+    w->notify = NULL;
+
+    return 0;
+}
+
+
+static void cpu_watch_changed(void *data, lua_State *L, int member)
+{
+    MRP_UNUSED(data);
+    MRP_UNUSED(L);
+    MRP_UNUSED(member);
+}
+
+
+static int cpu_watch_setmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v)
+{
+    cpu_watch_lua_t  *w = (cpu_watch_lua_t *)data;
+    mrp_funcbridge_t *f, **fptr;
+
+    switch (member) {
+    case CPU_WATCH_MEMBER_CPU:
+        if ((w->cpu = get_cpu_id(v->str)) >= 0)
+            return 1;
+        else
+            mrp_log_error("Can't create watch for unknown CPU '%s'.", v->str);
+        return 0;
+
+    case CPU_WATCH_MEMBER_SAMPLE:
+        if ((w->sample = get_sample_id(v->str)) >= 0)
+            return 1;
+        else
+            mrp_log_error("Can't sample CPU for unknown type '%s'.", v->str);
+        return 0;
+
+    case CPU_WATCH_MEMBER_WINDOW:
+        cpu_watch_set_window(w, v->s32);
+        return 1;
+
+    case CPU_WATCH_MEMBER_NOTIFY: fptr = &w->notify; goto set_bridge;
+    case CPU_WATCH_MEMBER_UPDATE: fptr = &w->update;
+    set_bridge:
+        if (!mrp_lua_object_deref_value(w, L, v->any, false))
+            return 0;
+        switch (lua_type(L, -1)) {
+        case LUA_TFUNCTION:
+            f = *fptr = mrp_funcbridge_create_luafunc(L, -1);
+            break;
+        default:
+            f = NULL;
+            break;
+        }
+        lua_pop(L, 1);
+        mrp_lua_object_unref_value(w, L, v->any);
+
+        return (f != NULL ? 1 : 0);
+
+    case CPU_WATCH_MEMBER_LIMITS:
+        return setup_limits(w, L, v->any);
+
+    default:
+        mrp_log_error("Trying to set read-only CPU watch member #%d.", member);
+        return 0;
+    }
+}
+
+
+static int cpu_watch_getmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v)
+{
+    cpu_watch_lua_t *w  = (cpu_watch_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (member) {
+    case CPU_WATCH_MEMBER_CPU:
+        v->str = get_cpu_name(w->cpu);
+        return 1;
+
+    case CPU_WATCH_MEMBER_SAMPLE:
+        v->str = get_sample_name(w->sample);
+        return 1;
+
+    case CPU_WATCH_MEMBER_LIMITS:
+        v->any = w->limref;
+        return 1;
+
+    case CPU_WATCH_MEMBER_WINDOW:
+        v->s32 = w->window;
+        return 1;
+
+    case CPU_WATCH_MEMBER_POLLING:
+        v->s32 = w->polling;
+        return 1;
+
+    case CPU_WATCH_MEMBER_VALUE:
+        v->s32 = w->value.S;
+        return 1;
+
+    case CPU_WATCH_MEMBER_RAW:
+        v->s32 = w->value.sample;
+        return 1;
+
+    case CPU_WATCH_MEMBER_CURRENT:
+        v->str = w->curr ? w->curr->label : "<unknown limit>";
+        return 1;
+
+    case CPU_WATCH_MEMBER_PREVIOUS:
+        v->str = w->prev ? w->prev->label : "<unknown limit>";
+        return 1;
+
+    default:
+        v->any = LUA_REFNIL;
+        return 1;
+    }
+}
+
+
+static ssize_t cpu_watch_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                  size_t size, lua_State *L, void *data)
+{
+    cpu_watch_lua_t *w = (cpu_watch_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{CPU watch %s/%s @ %d sec window}",
+                        get_cpu_name(w->cpu), get_sample_name(w->sample),
+                        w->window / 1000);
+    }
+
+}
+
+
+static inline double calculate_alpha(double L, double n)
+{
+    double x, diff, min_x, min_diff;
+
+    min_diff = 1.0;
+    min_x    = 0.1;
+
+    for (x = 0.01; x < 1.0; x += 0.001) {
+        diff = pow(1 - x, n - 1) * x - L;
+        if (fabs(diff) < min_diff) {
+            min_x    = x;
+            min_diff = fabs(diff);
+        }
+    }
+
+    return min_x;
+}
+
+
+static inline void recalc_smoothing(cpu_watch_lua_t *w)
+{
+    double alpha /*= 1 - exp(- (1.0 * w->polling) / (1.0 * w->window))*/;
+    double n;
+
+    if (w->window > w->polling) {
+        n = (1.0 * w->window) / (1.0 * w->polling);
+        alpha = calculate_alpha(0.0005, n);
+    }
+    else
+        alpha = 1;
+
+    ewma_init(&w->value, alpha, w->value.S);
+}
+
+
+void cpu_watch_set_polling(cpu_watch_lua_t *w, int polling)
+{
+    if (w->polling == polling)
+        return;
+
+    w->polling = polling;
+    recalc_smoothing(w);
+}
+
+
+void cpu_watch_set_window(cpu_watch_lua_t *w, int window)
+{
+    if (w->window == window)
+        return;
+
+    w->window = window;
+    recalc_smoothing(w);
+}
+
+
+int cpu_watch_update(cpu_watch_lua_t *w, lua_State *L)
+{
+    int sample = cpu_get_sample(w->cpu, w->sample);
+    int change;
+
+    if (sample < 0)
+        return FALSE;
+
+
+    if (w->update == NULL) {
+        double       value = ewma_add(&w->value, sample);
+        cpu_limit_t *l;
+
+        mrp_debug("%s/%s sample=%d, estimate=%.2f", get_cpu_name(w->cpu),
+                  get_sample_name(w->sample), sample, value);
+
+        change = FALSE;
+        for (l = w->limits; l->label != NULL; l++) {
+            if (value <= l->limit) {
+                if (w->curr != l) {
+                    w->prev = w->curr;
+                    w->curr = l;
+
+                    change = TRUE;
+                }
+
+                break;
+            }
+        }
+    }
+    else {
+        mrp_funcbridge_value_t args[2], rv;
+        char                            rt;
+
+        mrp_debug("%s/%s sample=%d", get_cpu_name(w->cpu),
+                  get_sample_name(w->sample), sample);
+
+        args[0].pointer = w;
+        args[1].integer = sample;
+
+        if (!mrp_funcbridge_call_from_c(L, w->update, "Od", &args[0], &rt,&rv)) {
+            mrp_log_error("Failed to invoke CPU watch update handler (%s).",
+                          rv.string ? rv.string : "<unknown error>");
+            mrp_free((char *)rv.string);
+            change = FALSE;
+        }
+        else
+            change = ((rt == MRP_FUNCBRIDGE_BOOLEAN && rv.boolean) ||
+                      (rt == MRP_FUNCBRIDGE_INTEGER && rv.integer));
+    }
+
+    return change;
+}
+
+
+void cpu_watch_notify(cpu_watch_lua_t *w, lua_State *L)
+{
+    mrp_funcbridge_value_t args[3], rv;
+    char                            rt;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("CPU watch %s/%s: %s -> %s",
+              get_cpu_name(w->cpu), get_sample_name(w->sample),
+              w->prev ? w->prev->label : "<unknown>",
+              w->curr ? w->curr->label : "<unknown>");
+
+    if (w->notify == NULL)
+        return;
+
+    args[0].pointer = w;
+    args[1].string  = w->prev ? w->prev->label : "<unknown>";
+    args[2].string  = w->curr ? w->curr->label : "<unknown>";
+
+    if (!mrp_funcbridge_call_from_c(L, w->notify, "Oss", &args[0], &rt, &rv)) {
+        mrp_log_error("Failed to notify CPU watch %s/%s (%s).",
+                      get_cpu_name(w->cpu), get_sample_name(w->sample),
+                      rv.string ? rv.string : "<unknown error>");
+        mrp_free((char *)rv.string);
+    }
+}
+
+
+static inline int get_cpu_id(const char *name)
+{
+    return cpu_get_id(name);
+}
+
+
+static inline const char *get_cpu_name(int id)
+{
+    const char *name = cpu_get_name(id);
+
+    return name ? name : "<invalid CPU id>";
+}
+
+
+static inline cpu_sample_t get_sample_id(const char *name)
+{
+#define MAP(_name, _id) if (!strcmp(name, _name)) return _id
+    MAP("user"      , CPU_SAMPLE_USER      );
+    MAP("nice"      , CPU_SAMPLE_NICE      );
+    MAP("system"    , CPU_SAMPLE_SYSTEM    );
+    MAP("idle"      , CPU_SAMPLE_IDLE      );
+    MAP("iowait"    , CPU_SAMPLE_IOWAIT    );
+    MAP("irq"       , CPU_SAMPLE_IRQ       );
+    MAP("softirq"   , CPU_SAMPLE_SOFTIRQ   );
+    MAP("steal"     , CPU_SAMPLE_STEAL     );
+    MAP("guest"     , CPU_SAMPLE_GUEST     );
+    MAP("guest_nice", CPU_SAMPLE_GUEST_NICE);
+    MAP("load"      , CPU_SAMPLE_LOAD      );
+    MAP("interrupt" , CPU_SAMPLE_INTERRUPT );
+    MAP("guest_load", CPU_SAMPLE_GUEST_LOAD);
+#undef MAP
+
+    return CPU_SAMPLE_INVALID;
+}
+
+
+static inline const char *get_sample_name(cpu_sample_t id)
+{
+    const char *names[] = {
+#define MAP(_name, _id) [_id] = _name
+        MAP("user"      , CPU_SAMPLE_USER      ),
+        MAP("nice"      , CPU_SAMPLE_NICE      ),
+        MAP("system"    , CPU_SAMPLE_SYSTEM    ),
+        MAP("idle"      , CPU_SAMPLE_IDLE      ),
+        MAP("iowait"    , CPU_SAMPLE_IOWAIT    ),
+        MAP("irq"       , CPU_SAMPLE_IRQ       ),
+        MAP("softirq"   , CPU_SAMPLE_SOFTIRQ   ),
+        MAP("steal"     , CPU_SAMPLE_STEAL     ),
+        MAP("guest"     , CPU_SAMPLE_GUEST     ),
+        MAP("guest_nice", CPU_SAMPLE_GUEST_NICE),
+        MAP("load"      , CPU_SAMPLE_LOAD      ),
+        MAP("interrupt" , CPU_SAMPLE_INTERRUPT ),
+        MAP("guest_load", CPU_SAMPLE_GUEST_LOAD),
+#undef MAP
+    };
+
+    if (CPU_SAMPLE_USER <= id && id <= CPU_SAMPLE_GUEST_LOAD)
+        return names[id];
+    else
+        return "<invalid CPU sample type>";
+}
+
+
+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 int get_limit(lua_State *L, int idx, cpu_limit_t *l)
+{
+    if (lua_type(L, idx) != LUA_TTABLE)
+        return -1;
+
+    lua_getfield(L, idx, "label");
+
+    if (lua_type(L, -1) == LUA_TSTRING)
+        l->label = (char *)lua_tostring(L, -1);
+    else
+        l->label = NULL;
+
+    lua_pop(L, 1);
+
+    if (l->label == NULL)
+        return -1;
+
+    lua_getfield(L, idx, "limit");
+
+    switch (lua_type(L, -1)) {
+    case LUA_TNUMBER: l->limit = lua_tonumber(L, -1); break;
+    case LUA_TNIL:    l->limit = 100;                 break;
+    default:          l->limit = -1;                  break;
+    }
+
+    lua_pop(L, 1);
+
+    if (l->limit >= 0)
+        return 0;
+    else
+        return -1;
+}
+
+
+static int setup_limits(cpu_watch_lua_t *w, lua_State *L, int limref)
+{
+    int          top = lua_gettop(L);
+    cpu_limit_t *limits, l;
+    int          nlimit;
+    const char  *kname;
+    int          ktype, i;
+    size_t       klen;
+
+    if (!mrp_lua_object_deref_value(w, L, limref, false)) {
+        mrp_log_error("Failed to dereference CPU watch limit table.");
+        return 0;
+    }
+
+    limits = NULL;
+    nlimit = 0;
+    MRP_LUA_FOREACH_ALL(L, i, top + 1, ktype, kname, klen) {
+        if (ktype != LUA_TNUMBER) {
+            mrp_log_error("Invalid CPU watch limits (non-numeric index).");
+            goto fail;
+        }
+
+        if (get_limit(L, -1, &l) != 0) {
+            mrp_log_error("Invalid CPU watch limit #%zd.", klen);
+            goto fail;
+        }
+
+        if (mrp_reallocz(limits, nlimit, nlimit + 1) == NULL) {
+            mrp_log_error("Failed to allocate CPU watch limits.");
+            goto fail;
+        }
+
+        limits[nlimit].label = mrp_strdup(l.label);
+        limits[nlimit].limit = l.limit;
+
+        if (limits[nlimit].label == NULL) {
+            mrp_log_error("CPU watch limit with no or invalid label.");
+            goto fail;
+        }
+        else
+            nlimit++;
+    }
+
+    if (mrp_reallocz(limits, nlimit, nlimit + 1) == NULL)
+        goto fail;
+
+    qsort(limits, nlimit, sizeof(limits[0]), cmp_limits);
+
+    cleanup_limits(w, L, w->limits, -1, w->limref);
+    w->limits = limits;
+    w->limref = limref;
+
+    lua_settop(L, top);
+    return 1;
+
+ fail:
+    cleanup_limits(w, L, limits, nlimit, LUA_NOREF);
+    lua_settop(L, top);
+    return 0;
+}
+
+
+static void cleanup_limits(cpu_watch_lua_t *w, lua_State *L,
+                           cpu_limit_t *limits, int n, int limref)
+{
+    cpu_limit_t *l;
+    int          i;
+
+    mrp_lua_object_unref_value(w, L, limref);
+
+    if (limits == NULL)
+        return;
+
+    for (i = 0, l = limits; (n > 0 && i < n) || (n < 0 && l->label); i++, l++)
+        mrp_free(l->label);
+
+    mrp_free(limits);
+}
+
+
+
+/*
+ * Uh... We misuse of the bindings registering macro by passing in
+ * (the empty) { NULL, NULL } for the bindings and use its optional
+ * class registering feature to register our class. Ugly..., we need
+ * to add a similar MURPHY_REGISTER_LUA_CLASSES macro and the necessary
+ * infra for it...
+ */
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, CPU_WATCH_LUA_CLASS, { NULL, NULL });
diff --git a/src/plugins/system-monitor/cpu-watch.h b/src/plugins/system-monitor/cpu-watch.h
new file mode 100644 (file)
index 0000000..ec9cfe1
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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_CPU_WATCH_H__
+#define __MURPHY_CPU_WATCH_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/list.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+
+#include "system-monitor.h"
+#include "cpu-sampler.h"
+#include "estimator.h"
+
+#define CPU_WATCH_WINDOW (60 * 1000)     /* default estimating window */
+
+/*
+ * a CPU watch
+ */
+
+typedef struct {
+    char *label;                         /* label to use in notifications */
+    int   limit;                         /* limit value in percentages */
+} cpu_limit_t;
+
+
+struct cpu_watch_lua_s {
+    mrp_list_hook_t   hook;              /* to list of CPU watches */
+    sysmon_lua_t     *sysmon;            /* system monitor */
+    int               cpu;               /* CPU to watch */
+    cpu_sample_t      sample;            /* sample to watch (what to measure) */
+    cpu_limit_t      *limits;            /* limits to notify about */
+    int               limref;            /* Lua reference to limit table */
+    int               polling;           /* polling interval */
+    int               window;            /* window to estimate over (msecs) */
+    ewma_t            value;             /* estimated value */
+    cpu_limit_t      *curr;              /* currently active limit */
+    cpu_limit_t      *prev;              /* previously active limit */
+    int               watchref;          /* self-ref system monitor */
+    mrp_funcbridge_t *notify;            /* notification callback */
+    mrp_funcbridge_t *update;            /* overridable update method */
+};
+
+/** Create a CPU watch. */
+cpu_watch_lua_t *cpu_watch_create(sysmon_lua_t *sm, int polling, lua_State *L);
+
+/** Let the CPU watch know the polling interval (in milliseconds). */
+void cpu_watch_set_polling(cpu_watch_lua_t *w, int polling);
+
+/** Set the CPU watch estimate window (in milliseconds). */
+void cpu_watch_set_window(cpu_watch_lua_t *w, int window);
+
+/** Update the CPU watch with the latest sampled value. */
+int cpu_watch_update(cpu_watch_lua_t *w, lua_State *L);
+
+/** Invoke the CPU watch state/limit change notification callback. */
+void cpu_watch_notify(cpu_watch_lua_t *w, lua_State *L);
+
+#endif /* __MURPHY_CPU_WATCH_H__ */
diff --git a/src/plugins/system-monitor/estimator.h b/src/plugins/system-monitor/estimator.h
new file mode 100644 (file)
index 0000000..d30d511
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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_ESTIMATOR_H__
+#define __MURPHY_SYSCTL_ESTIMATOR_H__
+
+#include <math.h>
+
+#include <murphy/common/mm.h>
+
+
+/*
+ * exponentially weighted moving average
+ */
+
+typedef struct  {
+    double a;                            /* smoothing factor */
+    double sample;                       /* last sample fed in */
+    double S;                            /* current estimate */
+} ewma_t;
+
+
+/** Initialize the given EWMA estimator. */
+static inline void ewma_init(ewma_t *e, double alpha, double sample)
+{
+    e->S = e->sample = sample;
+    e->a = alpha;
+}
+
+
+/** Allocate and initialize a new EWMA estimator. */
+static inline ewma_t *ewma_create(double alpha, double sample)
+{
+    ewma_t *e;
+
+    if ((e = mrp_allocz(sizeof(*e))) != NULL)
+        ewma_init(e, alpha, sample);
+
+    return e;
+}
+
+
+/** Free an EWMA estimator created by ewma_create. */
+static inline void ewma_free(ewma_t *e)
+{
+    mrp_free(e);
+}
+
+
+/** Reset the given EWMA estimator. */
+#define ewma_reset(e, alpha, sample) ewma_init(e, alpha, sample)
+
+
+/** Push a new sample into the EWMA estimation buffer. */
+static inline double ewma_add(ewma_t *e, double sample)
+{
+    e->sample = sample;
+    e->S = e->a * sample + (1 - e->a) * e->S;
+
+    return e->S;
+}
+
+
+/** Read the current estimate from the EWMA estimation buffer. */
+static inline double ewma_value(ewma_t *e)
+{
+    return e->S;
+}
+
+
+/** Read the last sample fed in. */
+static inline double ewma_sample(ewma_t *e)
+{
+    return e->sample;
+}
+
+
+#endif /* __MURPHY_SYSCTL_ESTIMATOR_H__ */
diff --git a/src/plugins/system-monitor/mem-sampler.c b/src/plugins/system-monitor/mem-sampler.c
new file mode 100644 (file)
index 0000000..9eeef7a
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * 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 <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+
+#include "mem-sampler.h"
+
+static mem_usage_t usage;
+
+/*
+ * mem_usage_t field descriptor
+ */
+typedef struct {
+    off_t       offs;                    /* offset within mem_usage_t */
+    const char *tag;                     /* /proc/meminfo tag */
+    size_t      len;                     /* pre-calculated tag length */
+} field_t;
+
+
+static int read_usage(mem_usage_t *m)
+{
+    /*
+     * fields to extract from /proc/meminfo
+     *
+     * Notes:
+     *    Keep the order of the field definition in sync with
+     *    the order of tags in /proc/meminfo. For efficiency,
+     *    the extracting loop will make at most a single pass
+     *    over the tags without ever backtracking. If you have
+     *    fields out of order, the extractor will simply fail.
+     */
+#define FIELD(_fld, _tag)                                               \
+ { .offs = MRP_OFFSET(mem_usage_t, _fld), .tag = _tag, .len = sizeof(_tag) - 1 }
+    static field_t fields[] = {
+        FIELD(mem_total , "MemTotal" ),
+        FIELD(mem_free  , "MemFree"  ),
+        FIELD(swap_total, "SwapTotal"),
+        FIELD(swap_free , "SwapFree" ),
+        FIELD(dirty     , "Dirty"    ),
+        FIELD(writeback , "Writeback"),
+        { (off_t)-1, NULL, 0 }
+    };
+    static int fd = -1;
+#undef FIELD
+
+    field_t    *f;
+    uint64_t   *v;
+    const char *t, *p;
+    char       *n, buf[4096];
+    int         l;
+
+    if (fd < 0) {
+        if ((fd = open("/proc/meminfo", O_RDONLY)) < 0)
+            return -1;
+
+        fcntl(fd, F_SETFD, FD_CLOEXEC);
+    }
+
+    lseek(fd, 0, SEEK_SET);
+
+    if ((l = read(fd, buf, sizeof(buf) - 1)) < 0)
+        return -1;
+
+    buf[l] = '\0';
+    p = buf;
+
+    for (f = fields; f->tag != NULL; f++) {
+        v = (uint64_t *)((char *)m + f->offs);
+        t = f->tag;
+        l = f->len;
+
+        while (p != NULL) {
+            if (!strncmp(p, t, l) && p[l] == ':') {
+                p  += l + 1;
+                *v  = strtoull(p, &n, 10);
+
+                if (*n == ' ' && n[1] == 'k')
+                    *v *= 1024;
+
+                if ((n = strchr(n, '\n')) != NULL)
+                    p = n + 1;
+                else
+                    p = NULL;
+
+                goto next_field;
+            }
+            else {
+                if ((n = strchr(p, '\n')) != NULL)
+                    p = n + 1;
+                else
+                    p = NULL;
+            }
+        }
+
+        if (p == NULL)
+            return -1;
+
+    next_field:
+        ;
+    }
+
+    return 0;
+}
+
+
+static void dump_usage(char *msg, mem_usage_t *m)
+{
+    mrp_debug("%s: MemTotal=%llu, MemFree=%llu",
+              msg, m->mem_total, m->mem_free);
+    mrp_debug("%s: SwapTotal=%llu, SwapFree=%llu",
+              msg, m->swap_total, m->swap_free);
+    mrp_debug("%s: dirty=%llu, writeback=%llu",
+              msg, m->dirty, m->writeback);
+}
+
+
+int mem_sample_usage(void)
+{
+    if (read_usage(&usage) == 0) {
+        dump_usage("memory usage", &usage);
+        return 0;
+    }
+    else
+        return -1;
+}
+
+
+int mem_get_usage(mem_usage_t *m)
+{
+    if (read_usage(m) == 0) {
+        dump_usage("meminfo", m);
+        return 0;
+    }
+    else
+        return -1;
+}
+
+
+int64_t mem_get_sample(mem_sample_t sample)
+{
+    switch (sample) {
+    case MEM_SAMPLE_MEMFREE:   return usage.mem_free;  break;
+    case MEM_SAMPLE_SWAPFREE:  return usage.swap_free; break;
+    case MEM_SAMPLE_DIRTY:     return usage.dirty;     break;
+    case MEM_SAMPLE_WRITEBACK: return usage.writeback; break;
+    default:                                           return -1;
+    }
+}
+
+
+int64_t mem_get_memory(void)
+{
+    return usage.mem_total;
+}
+
+
+int64_t mem_get_swap(void)
+{
+    return usage.swap_total;
+}
diff --git a/src/plugins/system-monitor/mem-sampler.h b/src/plugins/system-monitor/mem-sampler.h
new file mode 100644 (file)
index 0000000..55833e8
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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_SYSTEM_MONITOR_MEM_SAMPLER_H__
+#define __MURPHY_SYSTEM_MONITOR_MEM_SAMPLER_H__
+
+#include <stdint.h>
+
+/*
+ * type of memory usage sampling
+ */
+
+typedef enum {
+    MEM_SAMPLE_INVALID = -1,             /* invalid type */
+    MEM_SAMPLE_MEMFREE,                  /* free memory */
+    MEM_SAMPLE_SWAPFREE,                 /* free swap */
+    MEM_SAMPLE_DIRTY,                    /* to be written back to disk */
+    MEM_SAMPLE_WRITEBACK,                /* actively being written back */
+} mem_sample_t;
+
+/*
+ * pieces of memory usage information extracted from /proc/meminfo
+ */
+typedef struct {
+    uint64_t mem_total;                  /* MemTotal */
+    uint64_t swap_total;                 /* SwapTotal */
+    uint64_t mem_free;                   /* MemFree */
+    uint64_t swap_free;                  /* SwapFree */
+    uint64_t dirty;                      /* Dirty */
+    uint64_t writeback;                  /* Writeback */
+} mem_usage_t;
+
+
+/** Sample current memory usage. */
+int mem_sample_usage(void);
+
+/** Get memory usage information. */
+int mem_get_usage(mem_usage_t *m);
+
+/** Get memory usage of the given type. */
+int64_t mem_get_sample(mem_sample_t sample);
+
+/** Get the total amount of memory. */
+int64_t mem_get_memory(void);
+
+/** Get the total amount of swap. */
+int64_t mem_get_swap(void);
+
+#endif /* __MURPHY_SYSTEM_MONITOR_MEM_SAMPLER_H__ */
diff --git a/src/plugins/system-monitor/mem-watch.c b/src/plugins/system-monitor/mem-watch.c
new file mode 100644 (file)
index 0000000..5566367
--- /dev/null
@@ -0,0 +1,620 @@
+/*
+ * 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/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#include "mem-watch.h"
+
+/*
+ * memory watch object
+ */
+
+#define MEM_WATCH_LUA_CLASS MRP_LUA_CLASS(mem_watch, lua)
+#define RO                  MRP_LUA_CLASS_READONLY
+#define NOINIT              MRP_LUA_CLASS_NOINIT
+#define NOFLAGS             MRP_LUA_CLASS_NOFLAGS
+#define SETANDGET           mem_watch_setmember, mem_watch_getmember
+#define setmember           mem_watch_setmember
+#define getmember           mem_watch_getmember
+
+static int mem_watch_no_constructor(lua_State *L);
+static void mem_watch_destroy(void *data);
+static void mem_watch_changed(void *data, lua_State *L, int member);
+static ssize_t mem_watch_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                  size_t size, lua_State *L, void *data);
+static int mem_watch_setmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v);
+static int mem_watch_getmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v);
+static int mem_watch_delete(lua_State *L);
+
+MRP_LUA_METHOD_LIST_TABLE(mem_watch_methods,
+    MRP_LUA_METHOD_CONSTRUCTOR(mem_watch_no_constructor)
+    MRP_LUA_METHOD(delete, mem_watch_delete));
+
+MRP_LUA_METHOD_LIST_TABLE(mem_watch_overrides,
+    MRP_LUA_OVERRIDE_CALL(mem_watch_no_constructor));
+
+MRP_LUA_MEMBER_LIST_TABLE(mem_watch_members,
+    MRP_LUA_CLASS_STRING ("sample"  , 0, setmember, getmember, RO        )
+    MRP_LUA_CLASS_ANY    ("limits"  , 0, setmember, getmember, RO        )
+    MRP_LUA_CLASS_INTEGER("polling" , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_INTEGER("window"  , 0, setmember, getmember,    NOFLAGS)
+    MRP_LUA_CLASS_INTEGER("raw"     , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_INTEGER("value"   , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_STRING ("current" , 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_STRING ("previous", 0, setmember, getmember, RO|NOINIT )
+    MRP_LUA_CLASS_ANY    ("notify"  , 0, setmember, getmember,    NOFLAGS)
+    MRP_LUA_CLASS_ANY    ("update"  , 0, setmember, getmember,    NOFLAGS));
+
+MRP_LUA_DEFINE_CLASS(mem_watch, lua, mem_watch_lua_t, mem_watch_destroy,
+    mem_watch_methods, mem_watch_overrides, mem_watch_members, NULL,
+    mem_watch_changed, mem_watch_tostring , NULL,
+                     MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_PRIVREFS);
+
+MRP_LUA_CLASS_CHECKER(mem_watch_lua_t, mem_watch_lua, MEM_WATCH_LUA_CLASS);
+
+typedef enum {
+    MEM_WATCH_MEMBER_SAMPLE,
+    MEM_WATCH_MEMBER_LIMITS,
+    MEM_WATCH_MEMBER_POLLING,
+    MEM_WATCH_MEMBER_WINDOW,
+    MEM_WATCH_MEMBER_RAW,
+    MEM_WATCH_MEMBER_VALUE,
+    MEM_WATCH_MEMBER_CURRENT,
+    MEM_WATCH_MEMBER_PREVIOUS,
+    MEM_WATCH_MEMBER_NOTIFY,
+    MEM_WATCH_MEMBER_UPDATE
+} mem_watch_member_t;
+
+static inline mem_sample_t get_sample_id(const char *name);
+static inline const char *get_sample_name(mem_sample_t id);
+static inline void recalc_smoothing(mem_watch_lua_t *w);
+static int setup_limits(mem_watch_lua_t *w, lua_State *L, int limref);
+static void cleanup_limits(mem_watch_lua_t *w, lua_State *L,
+                           mem_limit_t *limits, int n, int limref);
+
+
+static int mem_watch_no_constructor(lua_State *L)
+{
+    return luaL_error(L, "trying create a memory watch via constructor.");
+}
+
+
+mem_watch_lua_t *mem_watch_create(sysmon_lua_t *sm, int polling, lua_State *L)
+{
+    mem_watch_lua_t *w;
+    char             e[256];
+
+    luaL_checktype(L, 2, LUA_TTABLE);
+
+    w = (mem_watch_lua_t *)mrp_lua_create_object(L, MEM_WATCH_LUA_CLASS,
+                                                 NULL, 0);
+
+    mrp_list_init(&w->hook);
+    w->sysmon  = sm;
+    w->polling = polling;
+    w->limref  = LUA_NOREF;
+    w->window  = MEM_WATCH_WINDOW;
+
+    if (mrp_lua_init_members(w, L, 2, e, sizeof(e)) != 1) {
+        luaL_error(L, "failed to initialize memory watch (error: %s)",
+                   *e ? e : "<unknown error>");
+        return NULL;
+    }
+
+    w->value.S = 0;
+
+    recalc_smoothing(w);
+
+    return w;
+}
+
+
+static void mem_watch_destroy(void *data)
+{
+    MRP_UNUSED(data);
+
+    mrp_debug("memory watch %p destroyed", data);
+
+    return;
+}
+
+
+static int mem_watch_delete(lua_State *L)
+{
+    mem_watch_lua_t *w = mem_watch_lua_check(L, 1);
+
+    mrp_list_delete(&w->hook);
+    mrp_list_init(&w->hook);
+
+    sysmon_del_mem_watch(w->sysmon, w);
+
+    cleanup_limits(w, L, w->limits, -1, w->limref);
+    w->limits = NULL;
+    w->limref = LUA_NOREF;
+
+    mrp_funcbridge_unref(L, w->update);
+    mrp_funcbridge_unref(L, w->notify);
+    w->update = NULL;
+    w->notify = NULL;
+
+    return 0;
+}
+
+
+static void mem_watch_changed(void *data, lua_State *L, int member)
+{
+    MRP_UNUSED(data);
+    MRP_UNUSED(L);
+    MRP_UNUSED(member);
+}
+
+
+static int mem_watch_setmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v)
+{
+    mem_watch_lua_t  *w = (mem_watch_lua_t *)data;
+    mrp_funcbridge_t *f, **fptr;
+
+    switch (member) {
+    case MEM_WATCH_MEMBER_SAMPLE:
+        if ((w->sample = get_sample_id(v->str)) >= 0)
+            return 1;
+        else
+            mrp_log_error("Can't sample memory for unknown type '%s'.", v->str);
+        return 0;
+
+    case MEM_WATCH_MEMBER_WINDOW:
+        mem_watch_set_window(w, v->s32);
+        return 1;
+
+    case MEM_WATCH_MEMBER_NOTIFY: fptr = &w->notify; goto set_bridge;
+    case MEM_WATCH_MEMBER_UPDATE: fptr = &w->update;
+    set_bridge:
+        if (!mrp_lua_object_deref_value(w, L, v->any, false))
+            return 0;
+        switch (lua_type(L, -1)) {
+        case LUA_TFUNCTION:
+            f = *fptr = mrp_funcbridge_create_luafunc(L, -1);
+            break;
+        default:
+            f = NULL;
+            break;
+        }
+        lua_pop(L, 1);
+        mrp_lua_object_unref_value(w, L, v->any);
+
+        return (f != NULL ? 1 : 0);
+
+    case MEM_WATCH_MEMBER_LIMITS:
+        return setup_limits(w, L, v->any);
+
+    default:
+        mrp_log_error("Trying to set read-only memory watch member #%d.",
+                      member);
+        return 0;
+    }
+}
+
+
+static int mem_watch_getmember(void *data, lua_State *L, int member,
+                               mrp_lua_value_t *v)
+{
+    mem_watch_lua_t *w  = (mem_watch_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (member) {
+    case MEM_WATCH_MEMBER_SAMPLE:
+        v->str = get_sample_name(w->sample);
+        return 1;
+
+    case MEM_WATCH_MEMBER_LIMITS:
+        v->any = w->limref;
+        return 1;
+
+    case MEM_WATCH_MEMBER_WINDOW:
+        v->s32 = w->window;
+        return 1;
+
+    case MEM_WATCH_MEMBER_POLLING:
+        v->s32 = w->polling;
+        return 1;
+
+    case MEM_WATCH_MEMBER_VALUE:
+        v->s32 = w->value.S;
+        return 1;
+
+    case MEM_WATCH_MEMBER_RAW:
+        v->s32 = w->value.sample;
+        return 1;
+
+    case MEM_WATCH_MEMBER_CURRENT:
+        v->str = w->curr ? w->curr->label : "<unknown limit>";
+        return 1;
+
+    case MEM_WATCH_MEMBER_PREVIOUS:
+        v->str = w->prev ? w->prev->label : "<unknown limit>";
+        return 1;
+
+    default:
+        v->any = LUA_REFNIL;
+        return 1;
+    }
+}
+
+
+static ssize_t mem_watch_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                  size_t size, lua_State *L, void *data)
+{
+    mem_watch_lua_t *w = (mem_watch_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{memory watch of %s @ %d sec window}",
+                        get_sample_name(w->sample), w->window / 1000);
+    }
+
+}
+
+
+static inline double calculate_alpha(double L, double n)
+{
+    double x, diff, min_x, min_diff;
+
+    min_diff = 1.0;
+    min_x    = 0.1;
+
+    for (x = 0.01; x < 1.0; x += 0.001) {
+        diff = pow(1 - x, n - 1) * x - L;
+        if (fabs(diff) < min_diff) {
+            min_x    = x;
+            min_diff = fabs(diff);
+        }
+    }
+
+    return min_x;
+}
+
+
+static inline void recalc_smoothing(mem_watch_lua_t *w)
+{
+    double alpha /*= 1 - exp(- (1.0 * w->polling) / (1.0 * w->window))*/;
+    double n;
+
+    if (w->window > w->polling) {
+        n = (1.0 * w->window) / (1.0 * w->polling);
+        alpha = calculate_alpha(0.0005, n);
+    }
+    else
+        alpha = 1;
+
+    ewma_init(&w->value, alpha, w->value.S);
+}
+
+
+void mem_watch_set_polling(mem_watch_lua_t *w, int polling)
+{
+    if (w->polling == polling)
+        return;
+
+    w->polling = polling;
+    recalc_smoothing(w);
+}
+
+
+void mem_watch_set_window(mem_watch_lua_t *w, int window)
+{
+    if (w->window == window)
+        return;
+
+    w->window = window;
+    recalc_smoothing(w);
+}
+
+
+int mem_watch_update(mem_watch_lua_t *w, lua_State *L)
+{
+    int64_t sample = mem_get_sample(w->sample);
+    int     change;
+
+    if (sample < 0)
+        return FALSE;
+
+
+    if (w->update == NULL) {
+        double       value = ewma_add(&w->value, sample);
+        mem_limit_t *l;
+
+        mrp_debug("%s sample=%d, estimate=%.2f", get_sample_name(w->sample),
+                  sample, value);
+
+        change = FALSE;
+        for (l = w->limits; l->label != NULL; l++) {
+            if (value <= l->limit) {
+                if (w->curr != l) {
+                    w->prev = w->curr;
+                    w->curr = l;
+
+                    change = TRUE;
+                }
+
+                break;
+            }
+        }
+    }
+    else {
+        mrp_funcbridge_value_t args[2], rv;
+        char                            rt;
+
+        mrp_debug("%s sample=%d", get_sample_name(w->sample), sample);
+
+        args[0].pointer = w;
+        args[1].integer = sample;
+
+        if (!mrp_funcbridge_call_from_c(L, w->update, "Od", &args[0], &rt,&rv)) {
+            mrp_log_error("Failed to invoke memory watch update handler (%s).",
+                          rv.string ? rv.string : "<unknown error>");
+            mrp_free((char *)rv.string);
+            change = FALSE;
+        }
+        else
+            change = ((rt == MRP_FUNCBRIDGE_BOOLEAN && rv.boolean) ||
+                      (rt == MRP_FUNCBRIDGE_INTEGER && rv.integer));
+    }
+
+    return change;
+}
+
+
+void mem_watch_notify(mem_watch_lua_t *w, lua_State *L)
+{
+    mrp_funcbridge_value_t args[3], rv;
+    char                            rt;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("memory watch %s: %s -> %s", get_sample_name(w->sample),
+              w->prev ? w->prev->label : "<unknown>",
+              w->curr ? w->curr->label : "<unknown>");
+
+    if (w->notify == NULL)
+        return;
+
+    args[0].pointer = w;
+    args[1].string  = w->prev ? w->prev->label : "<unknown>";
+    args[2].string  = w->curr ? w->curr->label : "<unknown>";
+
+    if (!mrp_funcbridge_call_from_c(L, w->notify, "Oss", &args[0], &rt, &rv)) {
+        mrp_log_error("Failed to notify memory watch %s (%s).",
+                      get_sample_name(w->sample),
+                      rv.string ? rv.string : "<unknown error>");
+        mrp_free((char *)rv.string);
+    }
+}
+
+
+static inline mem_sample_t get_sample_id(const char *name)
+{
+#define MAP(_name, _id) if (!strcmp(name, _name)) return _id
+    MAP("MemFree"   , MEM_SAMPLE_MEMFREE   );
+    MAP("SwapFree"  , MEM_SAMPLE_SWAPFREE  );
+    MAP("Dirty"     , MEM_SAMPLE_DIRTY     );
+    MAP("Writeback" , MEM_SAMPLE_WRITEBACK );
+#undef MAP
+
+    return MEM_SAMPLE_INVALID;
+}
+
+
+static inline const char *get_sample_name(mem_sample_t id)
+{
+    const char *names[] = {
+#define MAP(_name, _id) [_id] = _name
+        MAP("MemFree"   , MEM_SAMPLE_MEMFREE  ),
+        MAP("SwapFree"  , MEM_SAMPLE_SWAPFREE ),
+        MAP("Dirty"     , MEM_SAMPLE_DIRTY    ),
+        MAP("Writeback" , MEM_SAMPLE_WRITEBACK),
+#undef MAP
+    };
+
+    if (MEM_SAMPLE_MEMFREE <= id && id <= MEM_SAMPLE_WRITEBACK)
+        return names[id];
+    else
+        return "<invalid memory sample type>";
+}
+
+
+static int cmp_limits(const void *ptr1, const void *ptr2)
+{
+    mem_limit_t *l1 = (mem_limit_t *)ptr1;
+    mem_limit_t *l2 = (mem_limit_t *)ptr2;
+
+    return l1->limit - l2->limit;
+}
+
+
+static int get_limit(lua_State *L, int idx, mem_limit_t *l)
+{
+    char *u;
+
+    if (lua_type(L, idx) != LUA_TTABLE)
+        return -1;
+
+    lua_getfield(L, idx, "label");
+
+    if (lua_type(L, -1) == LUA_TSTRING)
+        l->label = (char *)lua_tostring(L, -1);
+    else
+        l->label = NULL;
+
+    lua_pop(L, 1);
+
+    if (l->label == NULL)
+        return -1;
+
+    lua_getfield(L, idx, "limit");
+
+    switch (lua_type(L, -1)) {
+    case LUA_TNUMBER:
+        l->limit = lua_tonumber(L, -1);
+        break;
+    case LUA_TSTRING:
+        l->limit = strtoull(lua_tostring(L, -1), &u, 10);
+        if (u[0]) {
+            if (!u[1]) {
+                switch (u[0]) {
+                case 'k': l->limit *= 1024;           break;
+                case 'M': l->limit *= 1024*1024;      break;
+                case 'G': l->limit *= 1024*1024*1024; break;
+                default:                              goto invalid;
+                }
+            }
+            else
+                goto invalid;
+        }
+        break;
+    default:
+    invalid:
+        l->limit = -1;
+        break;
+    }
+
+    lua_pop(L, 1);
+
+    if (l->limit >= 0)
+        return 0;
+    else
+        return -1;
+}
+
+
+static int setup_limits(mem_watch_lua_t *w, lua_State *L, int limref)
+{
+    int          top = lua_gettop(L);
+    mem_limit_t *limits, l;
+    int          nlimit;
+    const char  *kname;
+    int          ktype, i;
+    size_t       klen;
+
+    if (!mrp_lua_object_deref_value(w, L, limref, false)) {
+        mrp_log_error("Failed to dereference memory watch limit table.");
+        return 0;
+    }
+
+    limits = NULL;
+    nlimit = 0;
+    MRP_LUA_FOREACH_ALL(L, i, top + 1, ktype, kname, klen) {
+        if (ktype != LUA_TNUMBER) {
+            mrp_log_error("Invalid memory watch limits (non-numeric index).");
+            goto fail;
+        }
+
+        if (get_limit(L, -1, &l) != 0) {
+            mrp_log_error("Invalid memory watch limit #%zd.", klen);
+            goto fail;
+        }
+
+        if (mrp_reallocz(limits, nlimit, nlimit + 1) == NULL) {
+            mrp_log_error("Failed to allocate memory watch limits.");
+            goto fail;
+        }
+
+        limits[nlimit].label = mrp_strdup(l.label);
+        limits[nlimit].limit = l.limit;
+
+        if (limits[nlimit].label == NULL) {
+            mrp_log_error("memory watch limit with no or invalid label.");
+            goto fail;
+        }
+        else
+            nlimit++;
+    }
+
+    if (mrp_reallocz(limits, nlimit, nlimit + 1) == NULL)
+        goto fail;
+
+    qsort(limits, nlimit, sizeof(limits[0]), cmp_limits);
+
+    cleanup_limits(w, L, w->limits, -1, w->limref);
+    w->limits = limits;
+    w->limref = limref;
+
+    lua_settop(L, top);
+    return 1;
+
+ fail:
+    cleanup_limits(w, L, limits, nlimit, LUA_NOREF);
+    lua_settop(L, top);
+    return 0;
+}
+
+
+static void cleanup_limits(mem_watch_lua_t *w, lua_State *L,
+                           mem_limit_t *limits, int n, int limref)
+{
+    mem_limit_t *l;
+    int          i;
+
+    mrp_lua_object_unref_value(w, L, limref);
+
+    if (limits == NULL)
+        return;
+
+    for (i = 0, l = limits; (n > 0 && i < n) || (n < 0 && l->label); i++, l++)
+        mrp_free(l->label);
+
+    mrp_free(limits);
+}
+
+
+
+/*
+ * Uh... We misuse of the bindings registering macro by passing in
+ * (the empty) { NULL, NULL } for the bindings and use its optional
+ * class registering feature to register our class. Ugly..., we need
+ * to add a similar MURPHY_REGISTER_LUA_CLASSES macro and the necessary
+ * infra for it...
+ */
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, MEM_WATCH_LUA_CLASS, { NULL, NULL });
diff --git a/src/plugins/system-monitor/mem-watch.h b/src/plugins/system-monitor/mem-watch.h
new file mode 100644 (file)
index 0000000..fba61d2
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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_MEM_WATCH_H__
+#define __MURPHY_MEM_WATCH_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/list.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+
+#include "system-monitor.h"
+#include "mem-sampler.h"
+#include "estimator.h"
+
+#define MEM_WATCH_WINDOW (15 * 1000)     /* default estimating window */
+
+/*
+ * a memory watch
+ */
+
+typedef struct {
+    char     *label;                     /* label to use in notifications */
+    int64_t   limit;                     /* limit value in kilobytes */
+} mem_limit_t;
+
+
+struct mem_watch_lua_s {
+    mrp_list_hook_t   hook;              /* to list of CPU watches */
+    sysmon_lua_t     *sysmon;            /* system monitor */
+    mem_sample_t      sample;            /* sample to watch (what to measure) */
+    mem_limit_t      *limits;            /* limits to notify about */
+    int               limref;            /* Lua reference to limit table */
+    int               polling;           /* polling interval */
+    int               window;            /* window to estimate over (msecs) */
+    ewma_t            value;             /* estimated value */
+    mem_limit_t      *curr;              /* currently active limit */
+    mem_limit_t      *prev;              /* previously active limit */
+    int               watchref;          /* self-ref system monitor */
+    mrp_funcbridge_t *notify;            /* notification callback */
+    mrp_funcbridge_t *update;            /* overridable update method */
+};
+
+/** Create a memory watch. */
+mem_watch_lua_t *mem_watch_create(sysmon_lua_t *sm, int polling, lua_State *L);
+
+/** Let the memory watch know the polling interval (in milliseconds). */
+void mem_watch_set_polling(mem_watch_lua_t *w, int polling);
+
+/** Set the memory watch estimate window (in milliseconds). */
+void mem_watch_set_window(mem_watch_lua_t *w, int window);
+
+/** Update the memory watch with the latest sampled value. */
+int mem_watch_update(mem_watch_lua_t *w, lua_State *L);
+
+/** Invoke the memory watch state/limit change notification callback. */
+void mem_watch_notify(mem_watch_lua_t *w, lua_State *L);
+
+#endif /* __MURPHY_CPU_WATCH_H__ */
diff --git a/src/plugins/system-monitor/plugin-system-monitor.c b/src/plugins/system-monitor/plugin-system-monitor.c
new file mode 100644 (file)
index 0000000..823c56c
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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/plugin.h>
+
+
+/*
+ * There's really nothing done here. All the interesting things
+ * are caused by the murphy Lua infra* registration macros and
+ * get automagically triggered when the plugin is dlopened.
+ */
+
+static int plugin_init(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    return TRUE;
+}
+
+
+static void plugin_exit(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+}
+
+
+#define PLUGIN_DESCRIPTION "Murphy system monitor plugin."
+#define PLUGIN_HELP        "Murphy system monitor plugin."
+#define PLUGIN_AUTHORS     "Krisztian Litkey <kli@iki.fi>"
+#define PLUGIN_VERSION     MRP_VERSION_INT(0, 0, 1)
+
+MURPHY_REGISTER_PLUGIN("system-monitor",
+                       PLUGIN_VERSION, PLUGIN_DESCRIPTION, PLUGIN_AUTHORS,
+                       PLUGIN_HELP, MRP_SINGLETON, plugin_init, plugin_exit,
+                       NULL, 0,
+                       NULL, 0,
+                       NULL, 0,
+                       NULL);
diff --git a/src/plugins/system-monitor/system-monitor.c b/src/plugins/system-monitor/system-monitor.c
new file mode 100644 (file)
index 0000000..9b40f0c
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+ * 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-bindings/murphy.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+
+#include "system-monitor.h"
+#include "cpu-watch.h"
+#include "mem-watch.h"
+
+/*
+ * system monitor context (singleton) object
+ */
+
+#define SYSMON_LUA_CLASS MRP_LUA_CLASS(sysmon, lua)
+#define OFFSET(m)        MRP_OFFSET(sysmon_lua_t, m)
+#define NOTIFY           MRP_LUA_CLASS_NOTIFY
+
+struct sysmon_lua_s {
+    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              polling;            /* polling interval (in msecs) */
+    mrp_context_t   *ctx;                /* murphy context */
+    mrp_timer_t     *t;                  /* polling timer */
+    void            *pending;            /* pending notification */
+};
+
+static int sysmon_create(lua_State *L);
+static void sysmon_destroy(void *data);
+static void sysmon_notify(void *data, lua_State *L, int member);
+static ssize_t sysmon_tostring(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
+                               lua_State *L, void *data);
+
+static int sysmon_add_cpu_watch(lua_State *L);
+static int sysmon_add_mem_watch(lua_State *L);
+
+MRP_LUA_METHOD_LIST_TABLE(sysmon_methods,
+    MRP_LUA_METHOD_CONSTRUCTOR(sysmon_create)
+    MRP_LUA_METHOD(CpuWatch, sysmon_add_cpu_watch)
+    MRP_LUA_METHOD(MemWatch, sysmon_add_mem_watch));
+
+MRP_LUA_METHOD_LIST_TABLE(sysmon_overrides,
+    MRP_LUA_OVERRIDE_CALL(sysmon_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(sysmon_members,
+    MRP_LUA_CLASS_INTEGER("polling", OFFSET(polling), NULL, NULL, NOTIFY));
+
+MRP_LUA_DEFINE_CLASS(sysmon, lua, sysmon_lua_t, sysmon_destroy,
+    sysmon_methods, sysmon_overrides, sysmon_members, NULL,
+    sysmon_notify , sysmon_tostring , NULL,
+                     MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_PRIVREFS);
+
+MRP_LUA_CLASS_CHECKER(sysmon_lua_t, sysmon_lua, SYSMON_LUA_CLASS);
+
+typedef enum {
+    SYSMON_MEMBER_POLLING,               /* polling interval member */
+} sysmon_member_t;
+
+
+static sysmon_lua_t *singleton(lua_State *L)
+{
+    static sysmon_lua_t *sm = NULL;
+
+    if (L != NULL) {
+        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->polling = SYSMON_DEFAULT_POLLING;
+            sm->ctx     = mrp_lua_get_murphy_context();
+        }
+    }
+    else {
+        mrp_debug("clearing system monitor singleton object");
+        sm = NULL;
+    }
+
+    return sm;
+}
+
+
+static int sysmon_create(lua_State *L)
+{
+    return mrp_lua_push_object(L, singleton(L));
+}
+
+
+static int sysmon_get(lua_State *L)
+{
+    return sysmon_create(L);
+}
+
+
+static void sysmon_destroy(void *data)
+{
+    sysmon_lua_t    *sm = (sysmon_lua_t *)data;
+    mrp_list_hook_t *p, *n;
+
+    mrp_del_timer(sm->t);
+    sm->t = NULL;
+
+    mrp_list_foreach(&sm->cpu_watches, p, n) {
+        mrp_list_delete(p);
+    }
+
+    mrp_list_foreach(&sm->mem_watches, p, n) {
+        mrp_list_delete(p);
+    }
+
+    singleton(NULL);
+}
+
+
+static ssize_t sysmon_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->polling);
+    }
+}
+
+
+static void update_polling(sysmon_lua_t *sm)
+{
+    mrp_list_hook_t *p, *n;
+    cpu_watch_lua_t *cw;
+    mem_watch_lua_t *mw;
+
+    mrp_list_foreach(&sm->cpu_watches, p, n) {
+        cw = mrp_list_entry(p, typeof(*cw), hook);
+
+        cpu_watch_set_polling(cw, sm->polling);
+    }
+
+    mrp_list_foreach(&sm->mem_watches, p, n) {
+        mw = mrp_list_entry(p, typeof(*mw), hook);
+
+        mem_watch_set_polling(mw, sm->polling);
+    }
+}
+
+
+static void sysmon_notify(void *data, lua_State *L, int member)
+{
+    sysmon_lua_t *sm = (sysmon_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("system monitor member #%d changed", member);
+
+    switch (member) {
+    case SYSMON_MEMBER_POLLING:
+        if (sm->polling < SYSMON_MINIMUM_POLLING)
+            sm->polling = SYSMON_MINIMUM_POLLING;
+
+        if (sm->t != NULL)
+            mrp_mod_timer(sm->t, sm->polling);
+
+        update_polling(sm);
+
+        mrp_log_info("system monitor polling interval set to %d msecs.",
+                     sm->polling);
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static void sysmon_timer(mrp_timer_t *t, void *user_data)
+{
+    sysmon_lua_t    *sm = (sysmon_lua_t *)user_data;
+    mrp_list_hook_t *p, *n;
+    cpu_watch_lua_t *cw;
+    mem_watch_lua_t *mw;
+
+    MRP_UNUSED(t);
+
+    mrp_debug("sampling CPU load");
+    cpu_sample_load();
+
+    mrp_list_foreach(&sm->cpu_watches, p, n) {
+        cw = mrp_list_entry(p, typeof(*cw), hook);
+
+        sm->pending = n;
+
+        if (cpu_watch_update(cw, sm->L))
+            cpu_watch_notify(cw, sm->L);
+
+        n = sm->pending;
+    }
+
+    mrp_debug("sampling memory usage");
+    mem_sample_usage();
+
+    mrp_list_foreach(&sm->mem_watches, p, n) {
+        mw = mrp_list_entry(p, typeof(*mw), hook);
+
+        sm->pending = n;
+
+        if (mem_watch_update(mw, sm->L))
+            mem_watch_notify(mw, sm->L);
+
+        n = sm->pending;
+    }
+
+}
+
+
+static int sysmon_add_cpu_watch(lua_State *L)
+{
+    sysmon_lua_t    *sm;
+    cpu_watch_lua_t *w;
+
+    sm = sysmon_lua_check(L, 1);
+
+    if ((w = cpu_watch_create(sm, sm->polling, L)) == NULL)
+        return luaL_error(L, "failed to create CPU watch");
+
+    w->watchref = mrp_lua_object_ref_value(sm, L, -1);
+    mrp_list_append(&sm->cpu_watches, &w->hook);
+
+    if (sm->t == NULL)
+        sm->t = mrp_add_timer(sm->ctx->ml, sm->polling, sysmon_timer, sm);
+
+    return 1;
+}
+
+
+static int sysmon_add_mem_watch(lua_State *L)
+{
+    sysmon_lua_t    *sm;
+    mem_watch_lua_t *w;
+
+    sm = sysmon_lua_check(L, 1);
+
+    if ((w = mem_watch_create(sm, sm->polling, L)) == NULL)
+        return luaL_error(L, "failed to create memory watch");
+
+    w->watchref = mrp_lua_object_ref_value(sm, L, -1);
+    mrp_list_append(&sm->mem_watches, &w->hook);
+
+    if (sm->t == NULL)
+        sm->t = mrp_add_timer(sm->ctx->ml, sm->polling, sysmon_timer, sm);
+
+    return 1;
+}
+
+
+int sysmon_del_cpu_watch(sysmon_lua_t *sm, cpu_watch_lua_t *w)
+{
+    if (sm->pending == &w->hook)
+        sm->pending = w->hook.next;
+
+    mrp_lua_object_unref_value(sm, sm->L, w->watchref);
+
+    if (mrp_list_empty(&sm->cpu_watches)) {
+        mrp_del_timer(sm->t);
+        sm->t = NULL;
+    }
+
+    return 0;
+}
+
+
+int sysmon_del_mem_watch(sysmon_lua_t *sm, mem_watch_lua_t *w)
+{
+    if (sm->pending == &w->hook)
+        sm->pending = w->hook.next;
+
+    mrp_lua_object_unref_value(sm, sm->L, w->watchref);
+
+    if (mrp_list_empty(&sm->mem_watches)) {
+        mrp_del_timer(sm->t);
+        sm->t = NULL;
+    }
+
+    return 0;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, SYSMON_LUA_CLASS,
+                             { "get_system_monitor", sysmon_get });
diff --git a/src/plugins/system-monitor/system-monitor.h b/src/plugins/system-monitor/system-monitor.h
new file mode 100644 (file)
index 0000000..0f4ae50
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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_SYSTEM_MONITOR_H__
+#define __MURPHY_SYSTEM_MONITOR_H__
+
+#define SYSMON_MINIMUM_POLLING  1000     /* minimum polling interval */
+#define SYSMON_DEFAULT_POLLING 15000     /* default polling interval */
+
+typedef struct sysmon_lua_s    sysmon_lua_t;
+typedef struct cpu_watch_lua_s cpu_watch_lua_t;
+typedef struct mem_watch_lua_s mem_watch_lua_t;
+
+/** Request deletion of the given CPU watch. */
+int sysmon_del_cpu_watch(sysmon_lua_t *sm, cpu_watch_lua_t *w);
+
+/** Request deletion of the given memory watch. */
+int sysmon_del_mem_watch(sysmon_lua_t *sm, mem_watch_lua_t *w);
+
+#endif /* __MURPHY_SYSTEM_MONITOR_H__ */