Add a Lua plugin for Collectd
authorJulien Ammous <j.ammous at gmail.com>
Fri, 12 Aug 2016 20:10:59 +0000 (22:10 +0200)
committerRuben Kerkhof <ruben@rubenkerkhof.com>
Fri, 12 Aug 2016 20:27:11 +0000 (22:27 +0200)
AUTHORS
configure.ac
src/Makefile.am
src/collectd.conf.in
src/daemon/plugin.h
src/lua.c [new file with mode: 0644]
src/utils_lua.c [new file with mode: 0644]
src/utils_lua.h [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index f5f6db0..28220e7 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -163,6 +163,9 @@ Jérôme Renard <jerome.renard at gmail.com>
 Jiri Tyr <jiri.tyr at gmail.com>
  - fhcount plugin.
 
+Julien Ammous <j.ammous at gmail.com>
+ - Lua plugin.
+
 Kevin Bowling <kbowling at llnw.com>
  - write_tsdb plugin for http://opentsdb.net/
 
index 8dda710..4f5f078 100644 (file)
@@ -2750,6 +2750,67 @@ fi
 AM_CONDITIONAL(BUILD_WITH_LIBLDAP, test "x$with_libldap" = "xyes")
 # }}}
 
+# --with-liblua {{{
+with_liblua_cppflags=""
+with_liblua_ldflags=""
+with_liblua_libs=""
+with_liblua="yes"
+
+AC_ARG_VAR([LIBLUA_PKG_CONFIG_NAME], [Name of liblua used by pkg-config])
+if test "x$LIBLUA_PKG_CONFIG_NAME" = "x"
+then
+       LIBLUA_PKG_CONFIG_NAME="lua"
+fi
+
+if test "x$with_liblua" = "xyes"
+then
+       $PKG_CONFIG --exists $LIBLUA_PKG_CONFIG_NAME 2>/dev/null
+       lua_config_status=$?
+
+       if test 0 -ne $lua_config_status
+       then
+               with_liblua="no"
+       fi
+fi
+
+if test "x$with_liblua" = "xyes"
+then
+       with_liblua_cppflags=`$PKG_CONFIG --cflags-only-I $LIBLUA_PKG_CONFIG_NAME` || with_liblua="no"
+       with_liblua_ldflags=`$PKG_CONFIG --libs-only-L $LIBLUA_PKG_CONFIG_NAME` || with_liblua="no"
+       with_liblua_libs=`$PKG_CONFIG --libs-only-l $LIBLUA_PKG_CONFIG_NAME` || with_liblua="no"
+fi
+if test "x$with_liblua" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_liblua_cppflags"
+
+       AC_CHECK_HEADERS(lua.h lauxlib.h lualib.h, [], [with_liblua="no (header not found)"], [])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_liblua" = "xyes"
+then
+       SAVE_LDFLAGS="$LDFLAGS"
+       SAVE_LIBS="$LIBS"
+       LDFLAGS="$SAVE_LDFLAGS $with_liblua_ldflags"
+       LIBS="$LIBS $with_liblua_libs"
+
+       AC_CHECK_FUNC(lua_settop, [with_liblua="yes"], [with_liblua="no (symbol 'lua_settop' not found)"])
+
+       LDFLAGS="$SAVE_LDFLAGS"
+       LIBS="$SAVE_LIBS"
+fi
+if test "x$with_liblua" = "xyes"
+then
+    BUILD_WITH_LIBLUA_CPPFLAGS="$with_liblua_cppflags"
+    BUILD_WITH_LIBLUA_LDFLAGS="$with_liblua_ldflags"
+    BUILD_WITH_LIBLUA_LIBS="$with_liblua_libs"
+fi
+AC_SUBST(BUILD_WITH_LIBLUA_CPPFLAGS)
+AC_SUBST(BUILD_WITH_LIBLUA_LDFLAGS)
+AC_SUBST(BUILD_WITH_LIBLUA_LIBS)
+# }}}
+
 # --with-liblvm2app {{{
 with_liblvm2app_cppflags=""
 with_liblvm2app_ldflags=""
@@ -6096,6 +6157,7 @@ AC_PLUGIN([load],                [$plugin_load],            [System load])
 AC_PLUGIN([log_logstash],        [$plugin_log_logstash],    [Logstash json_event compatible logging])
 AC_PLUGIN([logfile],             [yes],                     [File logging plugin])
 AC_PLUGIN([lpar],                [$with_perfstat],          [AIX logical partitions statistics])
+AC_PLUGIN([lua],                 [$with_liblua],            [Lua plugin])
 AC_PLUGIN([lvm],                 [$with_liblvm2app],        [LVM statistics])
 AC_PLUGIN([madwifi],             [$have_linux_wireless_h],  [Madwifi wireless statistics])
 AC_PLUGIN([match_empty_counter], [yes],                     [The empty counter match])
@@ -6413,6 +6475,7 @@ AC_MSG_RESULT([    libjvm  . . . . . . . $with_java])
 AC_MSG_RESULT([    libkstat  . . . . . . $with_kstat])
 AC_MSG_RESULT([    libkvm  . . . . . . . $with_libkvm])
 AC_MSG_RESULT([    libldap . . . . . . . $with_libldap])
+AC_MSG_RESULT([    liblua  . . . . . . . $with_liblua])
 AC_MSG_RESULT([    liblvm2app  . . . . . $with_liblvm2app])
 AC_MSG_RESULT([    libmemcached  . . . . $with_libmemcached])
 AC_MSG_RESULT([    libmnl  . . . . . . . $with_libmnl])
@@ -6512,6 +6575,7 @@ AC_MSG_RESULT([    load  . . . . . . . . $enable_load])
 AC_MSG_RESULT([    logfile . . . . . . . $enable_logfile])
 AC_MSG_RESULT([    log_logstash  . . . . $enable_log_logstash])
 AC_MSG_RESULT([    lpar  . . . . . . . . $enable_lpar])
+AC_MSG_RESULT([    lua . . . . . . . . . $enable_lua])
 AC_MSG_RESULT([    lvm . . . . . . . . . $enable_lvm])
 AC_MSG_RESULT([    madwifi . . . . . . . $enable_madwifi])
 AC_MSG_RESULT([    match_empty_counter . $enable_match_empty_counter])
index 12c7730..9a8e346 100644 (file)
@@ -170,6 +170,7 @@ apache_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBCURL_CFLAGS)
 apache_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS)
 endif
 
+
 if BUILD_PLUGIN_APCUPS
 pkglib_LTLIBRARIES += apcups.la
 apcups_la_SOURCES = apcups.c
@@ -574,6 +575,15 @@ lpar_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 lpar_la_LIBADD = -lperfstat
 endif
 
+if BUILD_PLUGIN_LUA
+pkglib_LTLIBRARIES += lua.la
+lua_la_SOURCES = lua.c \
+                utils_lua.c utils_lua.h
+lua_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBLUA_CPPFLAGS)
+lua_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBLUA_LDFLAGS)
+lua_la_LIBADD = $(BUILD_WITH_LIBLUA_LIBS)
+endif
+
 if BUILD_PLUGIN_LVM
 pkglib_LTLIBRARIES += lvm.la
 lvm_la_SOURCES = lvm.c
index e06465b..7a3818d 100644 (file)
 #@BUILD_PLUGIN_JAVA_TRUE@LoadPlugin java
 @BUILD_PLUGIN_LOAD_TRUE@@BUILD_PLUGIN_LOAD_TRUE@LoadPlugin load
 #@BUILD_PLUGIN_LPAR_TRUE@LoadPlugin lpar
+#@BUILD_PLUGIN_LUA_TRUE@LoadPlugin lua
 #@BUILD_PLUGIN_LVM_TRUE@LoadPlugin lvm
 #@BUILD_PLUGIN_MADWIFI_TRUE@LoadPlugin madwifi
 #@BUILD_PLUGIN_MBMON_TRUE@LoadPlugin mbmon
 #      ReportBySerial false
 #</Plugin>
 
+#<Plugin lua>
+#      BasePath "@prefix@/share/@PACKAGE_NAME@/lua"
+#      Script "script1.lua"
+#      Script "script2.lua"
+#</Plugin>
+
 #<Plugin madwifi>
 #      Interface "wlan0"
 #      IgnoreSelected false
index 49edba2..de42c06 100644 (file)
@@ -201,7 +201,6 @@ typedef void (*plugin_log_cb) (int severity, const char *message,
 typedef int (*plugin_shutdown_cb) (void);
 typedef int (*plugin_notification_cb) (const notification_t *,
                user_data_t *);
-
 /*
  * NAME
  *  plugin_set_dir
diff --git a/src/lua.c b/src/lua.c
new file mode 100644 (file)
index 0000000..bbfcb2f
--- /dev/null
+++ b/src/lua.c
@@ -0,0 +1,582 @@
+/**
+ * collectd - src/lua.c
+ * Copyright (C) 2010       Julien Ammous
+ * Copyright (C) 2010       Florian Forster
+ * Copyright (C) 2016       Ruben Kerkhof
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *   Julien Ammous
+ *   Florian Forster <octo at collectd.org>
+ *   Ruben Kerkhof <ruben at rubenkerkhof.com>
+ **/
+
+/* <lua5.1/luaconf.h> defines a macro using "sprintf". Although not used here,
+ * GCC will complain about the macro definition. */
+#define DONT_POISON_SPRINTF_YET
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+/* Include the Lua API header files. */
+#include "utils_lua.h"
+#include <lauxlib.h>
+#include <lua.h>
+#include <lualib.h>
+
+#include <pthread.h>
+
+#if COLLECT_DEBUG && __GNUC__
+#undef sprintf
+#pragma GCC poison sprintf
+#endif
+
+typedef struct lua_script_s {
+  char *script_path;
+  lua_State *lua_state;
+  struct lua_script_s *next;
+} lua_script_t;
+
+typedef struct {
+  lua_State *lua_state;
+  const char *lua_function_name;
+  pthread_mutex_t lock;
+  int callback_id;
+} clua_callback_data_t;
+
+typedef struct {
+  const char *name;
+  lua_CFunction func;
+} lua_c_function_t;
+
+static char base_path[PATH_MAX];
+static lua_script_t *scripts;
+
+static int clua_store_callback(lua_State *L, int idx) /* {{{ */
+{
+  /* Copy the function pointer */
+  lua_pushvalue(L, idx);
+
+  return luaL_ref(L, LUA_REGISTRYINDEX);
+} /* }}} int clua_store_callback */
+
+static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
+{
+  lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
+
+  if (!lua_isfunction(L, -1)) {
+    lua_pop(L, 1);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int clua_load_callback */
+
+/* Store the threads in a global variable so they are not cleaned up by the
+ * garbage collector. */
+static int clua_store_thread(lua_State *L, int idx) /* {{{ */
+{
+  if (idx < 0)
+    idx += lua_gettop(L) + 1;
+
+  /* Copy the thread pointer */
+  lua_pushvalue(L, idx); /* +1 = 3 */
+  if (!lua_isthread(L, -1)) {
+    lua_pop(L, 3); /* -3 = 0 */
+    return (-1);
+  }
+
+  luaL_ref(L, LUA_REGISTRYINDEX);
+  lua_pop(L, 1); /* -1 = 0 */
+  return (0);
+} /* }}} int clua_store_thread */
+
+static int clua_read(user_data_t *ud) /* {{{ */
+{
+  clua_callback_data_t *cb = ud->data;
+
+  pthread_mutex_lock(&cb->lock);
+
+  lua_State *L = cb->lua_state;
+
+  int status = clua_load_callback(L, cb->callback_id);
+  if (status != 0) {
+    ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
+          cb->lua_function_name, cb->callback_id);
+    pthread_mutex_unlock(&cb->lock);
+    return (-1);
+  }
+  /* +1 = 1 */
+
+  status = lua_pcall(L, 0, 1, 0);
+  if (status != 0) {
+    const char *errmsg = lua_tostring(L, -1);
+    if (errmsg == NULL)
+      ERROR("Lua plugin: Calling a read callback failed. "
+            "In addition, retrieving the error message failed.");
+    else
+      ERROR("Lua plugin: Calling a read callback failed: %s", errmsg);
+    lua_pop(L, 1);
+    pthread_mutex_unlock(&cb->lock);
+    return (-1);
+  }
+
+  if (!lua_isnumber(L, -1)) {
+    ERROR("Lua plugin: Read function \"%s\" (id %i) did not return a numeric "
+          "status.",
+          cb->lua_function_name, cb->callback_id);
+    status = -1;
+  } else {
+    status = (int)lua_tointeger(L, -1);
+  }
+
+  /* pop return value and function */
+  lua_pop(L, 1); /* -1 = 0 */
+
+  pthread_mutex_unlock(&cb->lock);
+  return (status);
+} /* }}} int clua_read */
+
+static int clua_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
+                      user_data_t *ud) {
+  clua_callback_data_t *cb = ud->data;
+
+  pthread_mutex_lock(&cb->lock);
+
+  lua_State *L = cb->lua_state;
+
+  int status = clua_load_callback(L, cb->callback_id);
+  if (status != 0) {
+    ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
+          cb->lua_function_name, cb->callback_id);
+    pthread_mutex_unlock(&cb->lock);
+    return (-1);
+  }
+  /* +1 = 1 */
+
+  status = luaC_pushvaluelist(L, ds, vl);
+  if (status != 0) {
+    lua_pop(L, 1); /* -1 = 0 */
+    pthread_mutex_unlock(&cb->lock);
+    ERROR("Lua plugin: luaC_pushvaluelist failed.");
+    return (-1);
+  }
+  /* +1 = 2 */
+
+  status = lua_pcall(L, 1, 1, 0); /* -2+1 = 1 */
+  if (status != 0) {
+    const char *errmsg = lua_tostring(L, -1);
+    if (errmsg == NULL)
+      ERROR("Lua plugin: Calling the write callback failed. "
+            "In addition, retrieving the error message failed.");
+    else
+      ERROR("Lua plugin: Calling the write callback failed:\n%s", errmsg);
+    lua_pop(L, 1); /* -1 = 0 */
+    pthread_mutex_unlock(&cb->lock);
+    return (-1);
+  }
+
+  if (!lua_isnumber(L, -1)) {
+    ERROR("Lua plugin: Write function \"%s\" (id %i) did not return a numeric "
+          "value.",
+          cb->lua_function_name, cb->callback_id);
+    status = -1;
+  } else {
+    status = (int)lua_tointeger(L, -1);
+  }
+
+  lua_pop(L, 1); /* -1 = 0 */
+  pthread_mutex_unlock(&cb->lock);
+  return (status);
+} /* }}} int clua_write */
+
+/*
+ * Exported functions
+ */
+
+static int lua_cb_log_debug(lua_State *L) /* {{{ */
+{
+  const char *msg = luaL_checkstring(L, 1);
+  plugin_log(LOG_DEBUG, "%s", msg);
+  return 0;
+} /* }}} int lua_cb_log_debug */
+
+static int lua_cb_log_error(lua_State *L) /* {{{ */
+{
+  const char *msg = luaL_checkstring(L, 1);
+  plugin_log(LOG_ERR, "%s", msg);
+  return 0;
+} /* }}} int lua_cb_log_error */
+
+static int lua_cb_log_info(lua_State *L) /* {{{ */
+{
+  const char *msg = luaL_checkstring(L, 1);
+  plugin_log(LOG_INFO, "%s", msg);
+  return 0;
+} /* }}} int lua_cb_log_info */
+
+static int lua_cb_log_notice(lua_State *L) /* {{{ */
+{
+  const char *msg = luaL_checkstring(L, 1);
+  plugin_log(LOG_NOTICE, "%s", msg);
+  return 0;
+} /* }}} int lua_cb_log_notice */
+
+static int lua_cb_log_warning(lua_State *L) /* {{{ */
+{
+  const char *msg = luaL_checkstring(L, 1);
+  plugin_log(LOG_WARNING, "%s", msg);
+  return 0;
+} /* }}} int lua_cb_log_warning */
+
+static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
+{
+  int nargs = lua_gettop(L);
+
+  if (nargs != 1)
+    return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
+
+  luaL_checktype(L, 1, LUA_TTABLE);
+
+  value_list_t *vl = luaC_tovaluelist(L, -1);
+  if (vl == NULL)
+    return luaL_error(L, "%s", "luaC_tovaluelist failed");
+
+  char identifier[6 * DATA_MAX_NAME_LEN];
+  FORMAT_VL(identifier, sizeof(identifier), vl);
+
+  DEBUG("Lua plugin: collectd.dispatch_values(): Received value list \"%s\", "
+        "time %.3f, interval %.3f.",
+        identifier, CDTIME_T_TO_DOUBLE(vl->time),
+        CDTIME_T_TO_DOUBLE(vl->interval));
+
+  plugin_dispatch_values(vl);
+
+  sfree(vl->values);
+  sfree(vl);
+  return 0;
+} /* }}} lua_cb_dispatch_values */
+
+static int lua_cb_register_read(lua_State *L) /* {{{ */
+{
+  int nargs = lua_gettop(L);
+
+  if (nargs != 1)
+    return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
+
+  luaL_checktype(L, 1, LUA_TFUNCTION);
+
+  char function_name[DATA_MAX_NAME_LEN];
+  ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
+
+  int callback_id = clua_store_callback(L, 1);
+  if (callback_id < 0)
+    return luaL_error(L, "%s", "Storing callback function failed");
+
+  lua_State *thread = lua_newthread(L);
+  if (thread == NULL)
+    return luaL_error(L, "%s", "lua_newthread failed");
+  clua_store_thread(L, -1);
+  lua_pop(L, 1);
+
+  clua_callback_data_t *cb = calloc(1, sizeof(*cb));
+  if (cb == NULL)
+    return luaL_error(L, "%s", "calloc failed");
+
+  cb->lua_state = thread;
+  cb->callback_id = callback_id;
+  cb->lua_function_name = strdup(function_name);
+  pthread_mutex_init(&cb->lock, NULL);
+
+  user_data_t ud = {
+    .data = cb
+  };
+
+  int status = plugin_register_complex_read(/* group = */ "lua",
+                                            /* name      = */ function_name,
+                                            /* callback  = */ clua_read,
+                                            /* interval  = */ 0,
+                                            /* user_data = */ &ud);
+
+  if (status != 0)
+    return luaL_error(L, "%s", "plugin_register_complex_read failed");
+  return 0;
+} /* }}} int lua_cb_register_read */
+
+static int lua_cb_register_write(lua_State *L) /* {{{ */
+{
+  int nargs = lua_gettop(L);
+
+  if (nargs != 1)
+    return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
+
+  luaL_checktype(L, 1, LUA_TFUNCTION);
+
+  char function_name[DATA_MAX_NAME_LEN] = "";
+  ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
+
+  int callback_id = clua_store_callback(L, 1);
+  if (callback_id < 0)
+    return luaL_error(L, "%s", "Storing callback function failed");
+
+  lua_State *thread = lua_newthread(L);
+  if (thread == NULL)
+    return luaL_error(L, "%s", "lua_newthread failed");
+  clua_store_thread(L, -1);
+  lua_pop(L, 1);
+
+  clua_callback_data_t *cb = calloc(1, sizeof(*cb));
+  if (cb == NULL)
+    return luaL_error(L, "%s", "calloc failed");
+
+  cb->lua_state = thread;
+  cb->callback_id = callback_id;
+  cb->lua_function_name = strdup(function_name);
+  pthread_mutex_init(&cb->lock, NULL);
+
+  user_data_t ud = {
+    .data = cb
+  };
+
+  int status = plugin_register_write(/* name = */ function_name,
+                                    /* callback  = */ clua_write,
+                                    /* user_data = */ &ud);
+
+  if (status != 0)
+    return luaL_error(L, "%s", "plugin_register_write failed");
+  return 0;
+} /* }}} int lua_cb_register_write */
+
+static lua_c_function_t lua_c_functions[] = {
+    {"log_debug", lua_cb_log_debug},
+    {"log_error", lua_cb_log_error},
+    {"log_info", lua_cb_log_info},
+    {"log_notice", lua_cb_log_notice},
+    {"log_warning", lua_cb_log_warning},
+    {"dispatch_values", lua_cb_dispatch_values},
+    {"register_read", lua_cb_register_read},
+    {"register_write", lua_cb_register_write}};
+
+static void lua_script_free(lua_script_t *script) /* {{{ */
+{
+  if (script == NULL)
+    return;
+
+  lua_script_t *next = script->next;
+
+  if (script->lua_state != NULL) {
+    lua_close(script->lua_state);
+    script->lua_state = NULL;
+  }
+
+  sfree(script->script_path);
+  sfree(script);
+
+  lua_script_free(next);
+} /* }}} void lua_script_free */
+
+static int lua_script_init(lua_script_t *script) /* {{{ */
+{
+  memset(script, 0, sizeof(*script));
+
+  /* initialize the lua context */
+  script->lua_state = luaL_newstate();
+  if (script->lua_state == NULL) {
+    ERROR("Lua plugin: luaL_newstate() failed.");
+    return (-1);
+  }
+
+  /* Open up all the standard Lua libraries. */
+  luaL_openlibs(script->lua_state);
+
+  /* Register all the functions we implement in C */
+  lua_newtable(script->lua_state);
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(lua_c_functions); i++) {
+    lua_pushcfunction(script->lua_state, lua_c_functions[i].func);
+    lua_setfield(script->lua_state, -2, lua_c_functions[i].name);
+  }
+  lua_setglobal(script->lua_state, "collectd");
+
+  /* Prepend BasePath to package.path */
+  if (base_path[0] != '\0') {
+    lua_getglobal(script->lua_state, "package");
+    lua_getfield(script->lua_state, -1, "path");
+
+    const char *cur_path = lua_tostring(script->lua_state, -1);
+    char *new_path = ssnprintf_alloc("%s/?.lua;%s", base_path, cur_path);
+
+    lua_pop(script->lua_state, 1);
+    lua_pushstring(script->lua_state, new_path);
+
+    free(new_path);
+
+    lua_setfield(script->lua_state, -2, "path");
+    lua_pop(script->lua_state, 1);
+  }
+
+  return (0);
+} /* }}} int lua_script_init */
+
+static int lua_script_load(const char *script_path) /* {{{ */
+{
+  lua_script_t *script = malloc(sizeof(*script));
+  if (script == NULL) {
+    ERROR("Lua plugin: malloc failed.");
+    return (-1);
+  }
+
+  int status = lua_script_init(script);
+  if (status != 0) {
+    lua_script_free(script);
+    return (status);
+  }
+
+  script->script_path = strdup(script_path);
+  if (script->script_path == NULL) {
+    ERROR("Lua plugin: strdup failed.");
+    lua_script_free(script);
+    return (-1);
+  }
+
+  status = luaL_loadfile(script->lua_state, script->script_path);
+  if (status != 0) {
+    ERROR("Lua plugin: luaL_loadfile failed: %s",
+          lua_tostring(script->lua_state, -1));
+    lua_pop(script->lua_state, 1);
+    lua_script_free(script);
+    return (-1);
+  }
+
+  status = lua_pcall(script->lua_state,
+                     /* nargs = */ 0,
+                     /* nresults = */ LUA_MULTRET,
+                     /* errfunc = */ 0);
+  if (status != 0) {
+    const char *errmsg = lua_tostring(script->lua_state, -1);
+
+    if (errmsg == NULL)
+      ERROR("Lua plugin: lua_pcall failed with status %i. "
+            "In addition, no error message could be retrieved from the stack.",
+            status);
+    else
+      ERROR("Lua plugin: Executing script \"%s\" failed:\n%s",
+            script->script_path, errmsg);
+
+    lua_script_free(script);
+    return (-1);
+  }
+
+  /* Append this script to the global list of scripts. */
+  if (scripts) {
+    lua_script_t *last = scripts;
+    while (last->next)
+      last = last->next;
+
+    last->next = script;
+  } else {
+    scripts = script;
+  }
+
+  return (0);
+} /* }}} int lua_script_load */
+
+static int lua_config_base_path(const oconfig_item_t *ci) /* {{{ */
+{
+  int status = cf_util_get_string_buffer(ci, base_path, sizeof(base_path));
+  if (status != 0)
+    return (status);
+
+  size_t len = strlen(base_path);
+  while ((len > 0) && (base_path[len - 1] == '/')) {
+    len--;
+    base_path[len] = '\0';
+  }
+
+  DEBUG("Lua plugin: base_path = \"%s\";", base_path);
+
+  return (0);
+} /* }}} int lua_config_base_path */
+
+static int lua_config_script(const oconfig_item_t *ci) /* {{{ */
+{
+  char rel_path[PATH_MAX];
+
+  int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path));
+  if (status != 0)
+    return (status);
+
+  char abs_path[PATH_MAX];
+
+  if (base_path[0] == '\0')
+    sstrncpy(abs_path, rel_path, sizeof(abs_path));
+  else
+    ssnprintf(abs_path, sizeof(abs_path), "%s/%s", base_path, rel_path);
+
+  DEBUG("Lua plugin: abs_path = \"%s\";", abs_path);
+
+  status = lua_script_load(abs_path);
+  if (status != 0)
+    return (status);
+
+  INFO("Lua plugin: File \"%s\" loaded succesfully", abs_path);
+
+  return 0;
+} /* }}} int lua_config_script */
+
+/*
+ * <Plugin lua>
+ *   BasePath "/"
+ *   Script "script1.lua"
+ *   Script "script2.lua"
+ * </Plugin>
+ */
+static int lua_config(oconfig_item_t *ci) /* {{{ */
+{
+  int status = 0;
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("BasePath", child->key) == 0) {
+      status = lua_config_base_path(child);
+    } else if (strcasecmp("Script", child->key) == 0) {
+      status = lua_config_script(child);
+    } else {
+      ERROR("Lua plugin: Option `%s' is not allowed here.", child->key);
+      status = 1;
+    }
+  }
+
+  return status;
+} /* }}} int lua_config */
+
+static int lua_shutdown(void) /* {{{ */
+{
+  lua_script_free(scripts);
+
+  return (0);
+} /* }}} int lua_shutdown */
+
+void module_register() {
+  plugin_register_complex_config("lua", lua_config);
+  plugin_register_shutdown("lua", lua_shutdown);
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_lua.c b/src/utils_lua.c
new file mode 100644 (file)
index 0000000..f746655
--- /dev/null
@@ -0,0 +1,322 @@
+/**
+ * collectd - src/utils_lua.c
+ * Copyright (C) 2010       Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+/* <lua5.1/luaconf.h> defines a macro using "sprintf". Although not used here,
+ * GCC will complain about the macro definition. */
+#define DONT_POISON_SPRINTF_YET
+
+#include "utils_lua.h"
+#include "common.h"
+
+static int ltoc_values(lua_State *L, /* {{{ */
+                       const data_set_t *ds, value_t *ret_values) {
+  if (!lua_istable(L, -1)) {
+    WARNING("ltoc_values: not a table");
+    return (-1);
+  }
+
+  /* Push initial key */
+  lua_pushnil(L); /* +1 = 1 */
+  size_t i = 0;
+  while (lua_next(L, -2) != 0) /* -1+2 = 2 || -1 = 0 */
+  {
+    if (i >= ds->ds_num) {
+      lua_pop(L, 2); /* -2 = 0 */
+      i++;
+      break;
+    }
+
+    ret_values[i] = luaC_tovalue(L, -1, ds->ds[i].type);
+
+    /* Pop the value */
+    lua_pop(L, 1); /* -1 = 1 */
+    i++;
+  } /* while (lua_next) */
+
+  if (i != ds->ds_num) {
+    WARNING("ltoc_values: invalid size for datasource \"%s\": expected %zu, "
+            "got %zu",
+            ds->type, ds->ds_num, i);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int ltoc_values */
+
+static int ltoc_table_values(lua_State *L, int idx, /* {{{ */
+                             const data_set_t *ds, value_list_t *vl) {
+  /* We're only called from "luaC_tovaluelist", which ensures that "idx" is an
+   * absolute index (i.e. a positive number) */
+  assert(idx > 0);
+
+  lua_getfield(L, idx, "values");
+  if (!lua_istable(L, -1)) {
+    WARNING("utils_lua: ltoc_table_values: The \"values\" member is a %s "
+            "value, not a table.",
+            lua_typename(L, lua_type(L, -1)));
+    lua_pop(L, 1);
+    return (-1);
+  }
+
+  vl->values_len = ds->ds_num;
+  vl->values = calloc(vl->values_len, sizeof(*vl->values));
+  if (vl->values == NULL) {
+    ERROR("utils_lua: calloc failed.");
+    vl->values_len = 0;
+    lua_pop(L, 1);
+    return (-1);
+  }
+
+  int status = ltoc_values(L, ds, vl->values);
+
+  lua_pop(L, 1);
+
+  if (status != 0) {
+    vl->values_len = 0;
+    sfree(vl->values);
+  }
+
+  return (status);
+} /* }}} int ltoc_table_values */
+
+static int luaC_pushvalues(lua_State *L, const data_set_t *ds,
+                           const value_list_t *vl) /* {{{ */
+{
+  assert(vl->values_len == ds->ds_num);
+
+  lua_newtable(L);
+  for (size_t i = 0; i < vl->values_len; i++) {
+    lua_pushinteger(L, (lua_Integer)i + 1);
+    luaC_pushvalue(L, vl->values[i], ds->ds[i].type);
+    lua_settable(L, -3);
+  }
+
+  return (0);
+} /* }}} int luaC_pushvalues */
+
+static int luaC_pushdstypes(lua_State *L, const data_set_t *ds) /* {{{ */
+{
+  lua_newtable(L);
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    lua_pushinteger(L, (lua_Integer)i);
+    lua_pushstring(L, DS_TYPE_TO_STRING(ds->ds[i].type));
+    lua_settable(L, -3);
+  }
+
+  return (0);
+} /* }}} int luaC_pushdstypes */
+
+static int luaC_pushdsnames(lua_State *L, const data_set_t *ds) /* {{{ */
+{
+  lua_newtable(L);
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    lua_pushinteger(L, (lua_Integer)i);
+    lua_pushstring(L, ds->ds[i].name);
+    lua_settable(L, -3);
+  }
+
+  return (0);
+} /* }}} int luaC_pushdsnames */
+
+/*
+ * Public functions
+ */
+cdtime_t luaC_tocdtime(lua_State *L, int idx) /* {{{ */
+{
+  if (!lua_isnumber(L, /* stack pos = */ idx))
+    return (0);
+
+  double d = lua_tonumber(L, idx);
+
+  return (DOUBLE_TO_CDTIME_T(d));
+} /* }}} int ltoc_table_cdtime */
+
+int luaC_tostringbuffer(lua_State *L, int idx, /* {{{ */
+                        char *buffer, size_t buffer_size) {
+  const char *str = lua_tostring(L, idx);
+  if (str == NULL)
+    return (-1);
+
+  sstrncpy(buffer, str, buffer_size);
+  return (0);
+} /* }}} int luaC_tostringbuffer */
+
+value_t luaC_tovalue(lua_State *L, int idx, int ds_type) /* {{{ */
+{
+  value_t v = { 0 };
+
+  if (!lua_isnumber(L, idx))
+    return (v);
+
+  if (ds_type == DS_TYPE_GAUGE)
+    v.gauge = (gauge_t)lua_tonumber(L, /* stack pos = */ -1);
+  else if (ds_type == DS_TYPE_DERIVE)
+    v.derive = (derive_t)lua_tointeger(L, /* stack pos = */ -1);
+  else if (ds_type == DS_TYPE_COUNTER)
+    v.counter = (counter_t)lua_tointeger(L, /* stack pos = */ -1);
+  else if (ds_type == DS_TYPE_ABSOLUTE)
+    v.absolute = (absolute_t)lua_tointeger(L, /* stack pos = */ -1);
+
+  return (v);
+} /* }}} value_t luaC_tovalue */
+
+value_list_t *luaC_tovaluelist(lua_State *L, int idx) /* {{{ */
+{
+#if COLLECT_DEBUG
+  int stack_top_before = lua_gettop(L);
+#endif
+
+  /* Convert relative indexes to absolute indexes, so it doesn't change when we
+   * push / pop stuff. */
+  if (idx < 1)
+    idx += lua_gettop(L) + 1;
+
+  /* Check that idx is in the valid range */
+  if ((idx < 1) || (idx > lua_gettop(L))) {
+    DEBUG("luaC_tovaluelist: idx(%d), top(%d)", idx, stack_top_before);
+    return (NULL);
+  }
+
+  value_list_t *vl = calloc(1, sizeof(*vl));
+  if (vl == NULL) {
+    DEBUG("luaC_tovaluelist: calloc failed");
+    return (NULL);
+  }
+
+  /* Push initial key */
+  lua_pushnil(L);
+  while (lua_next(L, idx) != 0) {
+    const char *key = lua_tostring(L, -2);
+
+    if (key == NULL) {
+      DEBUG("luaC_tovaluelist: Ignoring non-string key.");
+    } else if (strcasecmp("host", key) == 0)
+      luaC_tostringbuffer(L, -1, vl->host, sizeof(vl->host));
+    else if (strcasecmp("plugin", key) == 0)
+      luaC_tostringbuffer(L, -1, vl->plugin, sizeof(vl->plugin));
+    else if (strcasecmp("plugin_instance", key) == 0)
+      luaC_tostringbuffer(L, -1, vl->plugin_instance,
+                          sizeof(vl->plugin_instance));
+    else if (strcasecmp("type", key) == 0)
+      luaC_tostringbuffer(L, -1, vl->type, sizeof(vl->type));
+    else if (strcasecmp("type_instance", key) == 0)
+      luaC_tostringbuffer(L, -1, vl->type_instance,
+                          sizeof(vl->type_instance));
+    else if (strcasecmp("time", key) == 0)
+      vl->time = luaC_tocdtime(L, -1);
+    else if (strcasecmp("interval", key) == 0)
+      vl->interval = luaC_tocdtime(L, -1);
+    else if (strcasecmp("values", key) == 0) {
+      /* This key is not handled here, because we have to assure "type" is read
+       * first. */
+    } else {
+      DEBUG("luaC_tovaluelist: Ignoring unknown key \"%s\".", key);
+    }
+
+    /* Pop the value */
+    lua_pop(L, 1);
+  }
+
+  const data_set_t *ds = plugin_get_ds(vl->type);
+  if (ds == NULL) {
+    INFO("utils_lua: Unable to lookup type \"%s\".", vl->type);
+    sfree(vl);
+    return (NULL);
+  }
+
+  int status = ltoc_table_values(L, idx, ds, vl);
+  if (status != 0) {
+    WARNING("utils_lua: ltoc_table_values failed.");
+    sfree(vl);
+    return (NULL);
+  }
+
+#if COLLECT_DEBUG
+  assert(stack_top_before == lua_gettop(L));
+#endif
+  return (vl);
+} /* }}} value_list_t *luaC_tovaluelist */
+
+int luaC_pushcdtime(lua_State *L, cdtime_t t) /* {{{ */
+{
+  double d = CDTIME_T_TO_DOUBLE(t);
+
+  lua_pushnumber(L, (lua_Number)d);
+  return (0);
+} /* }}} int luaC_pushcdtime */
+
+int luaC_pushvalue(lua_State *L, value_t v, int ds_type) /* {{{ */
+{
+  if (ds_type == DS_TYPE_GAUGE)
+    lua_pushnumber(L, (lua_Number)v.gauge);
+  else if (ds_type == DS_TYPE_DERIVE)
+    lua_pushinteger(L, (lua_Integer)v.derive);
+  else if (ds_type == DS_TYPE_COUNTER)
+    lua_pushinteger(L, (lua_Integer)v.counter);
+  else if (ds_type == DS_TYPE_ABSOLUTE)
+    lua_pushinteger(L, (lua_Integer)v.absolute);
+  else
+    return (-1);
+  return (0);
+} /* }}} int luaC_pushvalue */
+
+int luaC_pushvaluelist(lua_State *L, const data_set_t *ds,
+                       const value_list_t *vl) /* {{{ */
+{
+  lua_newtable(L);
+
+  lua_pushstring(L, vl->host);
+  lua_setfield(L, -2, "host");
+
+  lua_pushstring(L, vl->plugin);
+  lua_setfield(L, -2, "plugin");
+  lua_pushstring(L, vl->plugin_instance);
+  lua_setfield(L, -2, "plugin_instance");
+
+  lua_pushstring(L, vl->type);
+  lua_setfield(L, -2, "type");
+  lua_pushstring(L, vl->type_instance);
+  lua_setfield(L, -2, "type_instance");
+
+  luaC_pushvalues(L, ds, vl);
+  lua_setfield(L, -2, "values");
+
+  luaC_pushdstypes(L, ds);
+  lua_setfield(L, -2, "dstypes");
+
+  luaC_pushdsnames(L, ds);
+  lua_setfield(L, -2, "dsnames");
+
+  luaC_pushcdtime(L, vl->time);
+  lua_setfield(L, -2, "time");
+
+  luaC_pushcdtime(L, vl->interval);
+  lua_setfield(L, -2, "interval");
+
+  return (0);
+} /* }}} int luaC_pushvaluelist */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_lua.h b/src/utils_lua.h
new file mode 100644 (file)
index 0000000..b594190
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * collectd - src/utils_lua.h
+ * Copyright (C) 2010       Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#ifndef UTILS_LUA_H
+#define UTILS_LUA_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+#ifndef DONT_POISON_SPRINTF_YET
+#error "Files including utils_lua.h need to define DONT_POISON_SPRINTF_YET."
+#endif
+#include <lua.h>
+
+/*
+ * access functions (stack -> C)
+ */
+cdtime_t luaC_tocdtime(lua_State *L, int idx);
+int luaC_tostringbuffer(lua_State *L, int idx, char *buffer,
+                        size_t buffer_size);
+value_t luaC_tovalue(lua_State *L, int idx, int ds_type);
+value_list_t *luaC_tovaluelist(lua_State *L, int idx);
+
+/*
+ * push functions (C -> stack)
+ */
+int luaC_pushcdtime(lua_State *L, cdtime_t t);
+int luaC_pushvalue(lua_State *L, value_t v, int ds_type);
+int luaC_pushvaluelist(lua_State *L, const data_set_t *ds,
+                       const value_list_t *vl);
+
+#endif /* UTILS_LUA_H */
+/* vim: set sw=2 sts=2 et fdm=marker : */