From: Julien Ammous Date: Fri, 12 Aug 2016 20:10:59 +0000 (+0200) Subject: Add a Lua plugin for Collectd X-Git-Tag: collectd-5.6.0~33^2~12 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=023092323ca617c8ff95b1efd4a055c29b1aaa3b;p=collectd.git Add a Lua plugin for Collectd --- diff --git a/AUTHORS b/AUTHORS index f5f6db0e..28220e79 100644 --- a/AUTHORS +++ b/AUTHORS @@ -163,6 +163,9 @@ Jérôme Renard Jiri Tyr - fhcount plugin. +Julien Ammous + - Lua plugin. + Kevin Bowling - write_tsdb plugin for http://opentsdb.net/ diff --git a/configure.ac b/configure.ac index 8dda7104..4f5f0783 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/src/Makefile.am b/src/Makefile.am index 12c7730c..9a8e346d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/collectd.conf.in b/src/collectd.conf.in index e06465bf..7a3818dc 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -135,6 +135,7 @@ #@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 @@ -634,6 +635,12 @@ # ReportBySerial false # +# +# BasePath "@prefix@/share/@PACKAGE_NAME@/lua" +# Script "script1.lua" +# Script "script2.lua" +# + # # Interface "wlan0" # IgnoreSelected false diff --git a/src/daemon/plugin.h b/src/daemon/plugin.h index 49edba25..de42c068 100644 --- a/src/daemon/plugin.h +++ b/src/daemon/plugin.h @@ -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 index 00000000..bbfcb2f8 --- /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 + * Ruben Kerkhof + **/ + +/* 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 +#include +#include + +#include + +#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 */ + +/* + * + * BasePath "/" + * Script "script1.lua" + * Script "script2.lua" + * + */ +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 index 00000000..f7466551 --- /dev/null +++ b/src/utils_lua.c @@ -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 + **/ + +/* 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 index 00000000..b5941904 --- /dev/null +++ b/src/utils_lua.h @@ -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 + **/ + +#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 + +/* + * 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 : */