snmp_agent: Implement SNMP Agent plugin.
authorKorynkevych, RomanX <romanx.korynkevych@intel.com>
Wed, 14 Dec 2016 10:37:05 +0000 (10:37 +0000)
committerRoman Korynkevych <romanx.korynkevych@intel.com>
Tue, 3 Jan 2017 14:55:32 +0000 (14:55 +0000)
The SNMP Agent plugin is an AgentX subagent that receives and handles
queries from SNMP master agent and returns the data collected by read
plugins. The SNMP Agent plugin handles requests only for OIDs specified
in configuration file. To handle SNMP queries the plugin gets data from
collectd and translates requested values from collectd's internal
format to SNMP format. This plugin is a generic plugin and cannot work
without configuration. For more details on AgentX subagent see
<http://www.net-snmp.org/tutorial/tutorial-5/toolkit/demon/>

Change-Id: I0e82131685f2138e1af0a9283b06f85dd2028878
Signed-off-by: Korynkevych, RomanX <romanx.korynkevych@intel.com>
Makefile.am
README
configure.ac
src/collectd.conf.in
src/collectd.conf.pod
src/daemon/utils_cache.c
src/daemon/utils_cache.h
src/snmp_agent.c [new file with mode: 0644]

index 3460cf6..d60a78b 100644 (file)
@@ -1456,6 +1456,14 @@ snmp_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMP_LDFLAGS)
 snmp_la_LIBADD = $(BUILD_WITH_LIBNETSNMP_LIBS)
 endif
 
+if BUILD_PLUGIN_SNMP_AGENT
+pkglib_LTLIBRARIES += snmp_agent.la
+snmp_agent_la_SOURCES = snmp_agent.c snmp_agent.h
+snmp_agent_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBNETSNMP_CPPFLAGS)
+snmp_agent_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMP_LDFLAGS)
+snmp_agent_la_LIBADD = $(BUILD_WITH_LIBNETSNMP_LIBS)
+endif
+
 if BUILD_PLUGIN_STATSD
 pkglib_LTLIBRARIES += statsd.la
 statsd_la_SOURCES = src/statsd.c
diff --git a/README b/README
index fb3aa27..c497b71 100644 (file)
--- a/README
+++ b/README
@@ -490,6 +490,13 @@ Features
       updates to the files and write a bunch of updates at once, which lessens
       system load a lot.
 
+    - snmp_agent
+      Receives and handles queries from SNMP master agent and returns the data
+      collected by read plugins. Handles requests only for OIDs specified in
+      configuration file. To handle SNMP queries the plugin gets data from
+      collectd and translates requested values from collectd's internal format
+      to SNMP format.
+
     - unixsock
       One can query the values from the unixsock plugin whenever they're
       needed. Please read collectd-unixsock(5) for a description on how that's
@@ -817,7 +824,11 @@ Prerequisites
     This library is part of the “Manage ONTAP SDK” published by NetApp.
 
   * libnetsnmp (optional)
-    For the `snmp' plugin.
+    For the `snmp' and 'snmp_agent' plugins.
+    <http://www.net-snmp.org/>
+
+  * libnetsnmpagent (optional)
+    Required for the 'snmp_agent' plugin.
     <http://www.net-snmp.org/>
 
   * libnotify (optional)
index c0fe54e..efa54c5 100644 (file)
@@ -3643,6 +3643,7 @@ AC_SUBST([LIBNETAPP_LIBS])
 # }}}
 
 # --with-libnetsnmp {{{
+with_libnetsnmpagent="no"
 AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
 [
        if test "x$withval" = "xno"
@@ -3655,6 +3656,7 @@ AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Pat
                with_libnetsnmp_cppflags="-I$withval/include"
                with_libnetsnmp_ldflags="-I$withval/lib"
                with_libnetsnmp="yes"
+               with_libnetsnmpagent="yes"
        fi; fi
 ],
 [with_libnetsnmp="yes"])
@@ -3667,6 +3669,10 @@ then
       [with_libnetsnmp="yes"],
       [with_libnetsnmp="no (net-snmp/net-snmp-config.h not found)"]
     )
+    AC_CHECK_HEADERS(net-snmp/agent/agent_module_config.h,
+      [],
+      [with_libnetsnmpagent="no (net-snmp/agent/agent_module_config.h not found)"]
+    )
 
        CPPFLAGS="$SAVE_CPPFLAGS"
 fi
@@ -3680,6 +3686,11 @@ then
                [with_libnetsnmp="no (libnetsnmp not found)"],
                [$with_snmp_libs])
 
+       AC_CHECK_LIB(netsnmpagent, init_agent,
+               [with_libnetsnmpagent="yes"],
+               [with_libnetsnmpagent="no (libnetsnmpagent not found)"],
+               [$with_snmp_libs])
+
        LDFLAGS="$SAVE_LDFLAGS"
 fi
 if test "x$with_libnetsnmp" = "xyes"
@@ -3688,6 +3699,10 @@ then
        BUILD_WITH_LIBNETSNMP_LDFLAGS="$with_libnetsnmp_ldflags"
        BUILD_WITH_LIBNETSNMP_LIBS="-lnetsnmp"
 fi
+if test "x$with_libnetsnmpagent" = "xyes"
+then
+       BUILD_WITH_LIBNETSNMP_LIBS+=" -lnetsnmpagent"
+fi
 AC_SUBST(BUILD_WITH_LIBNETSNMP_CPPFLAGS)
 AC_SUBST(BUILD_WITH_LIBNETSNMP_LDFLAGS)
 AC_SUBST(BUILD_WITH_LIBNETSNMP_LIBS)
@@ -5899,6 +5914,7 @@ plugin_processes="no"
 plugin_protocols="no"
 plugin_python="no"
 plugin_serial="no"
+plugin_snmp_agent="no"
 plugin_smart="no"
 plugin_swap="no"
 plugin_tape="no"
@@ -6191,6 +6207,10 @@ if test "x$with_kvm_getswapinfo" = "xyes"; then
   plugin_swap="yes"
 fi
 
+if test "x$with_libnetsnmp" = "xyes" && test "x$with_libnetsnmpagent" = "xyes"; then
+  plugin_snmp_agent="yes"
+fi
+
 if test "x$have_swapctl" = "xyes" && test "x$c_cv_have_swapctl_two_args" = "xyes"; then
   plugin_swap="yes"
 fi
@@ -6350,6 +6370,7 @@ AC_PLUGIN([serial],              [$plugin_serial],          [serial port traffic
 AC_PLUGIN([sigrok],              [$with_libsigrok],         [sigrok acquisition sources])
 AC_PLUGIN([smart],               [$plugin_smart],           [SMART statistics])
 AC_PLUGIN([snmp],                [$with_libnetsnmp],        [SNMP querying plugin])
+AC_PLUGIN([snmp_agent],          [$plugin_snmp_agent],      [SNMP agent plugin])
 AC_PLUGIN([statsd],              [yes],                     [StatsD plugin])
 AC_PLUGIN([swap],                [$plugin_swap],            [Swap usage statistics])
 AC_PLUGIN([syslog],              [$have_syslog],            [Syslog logging plugin])
@@ -6774,6 +6795,7 @@ AC_MSG_RESULT([    serial  . . . . . . . $enable_serial])
 AC_MSG_RESULT([    sigrok  . . . . . . . $enable_sigrok])
 AC_MSG_RESULT([    smart . . . . . . . . $enable_smart])
 AC_MSG_RESULT([    snmp  . . . . . . . . $enable_snmp])
+AC_MSG_RESULT([    snmp_agent  . . . . . $enable_snmp_agent])
 AC_MSG_RESULT([    statsd  . . . . . . . $enable_statsd])
 AC_MSG_RESULT([    swap  . . . . . . . . $enable_swap])
 AC_MSG_RESULT([    syslog  . . . . . . . $enable_syslog])
index 1cc86af..3a25b12 100644 (file)
 #@BUILD_PLUGIN_SIGROK_TRUE@LoadPlugin sigrok
 #@BUILD_PLUGIN_SMART_TRUE@LoadPlugin smart
 #@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
+#@BUILD_PLUGIN_SNMP_AGENT_TRUE@LoadPlugin snmp_agent
 #@BUILD_PLUGIN_STATSD_TRUE@LoadPlugin statsd
 #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
 #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
 #   </Host>
 #</Plugin>
 
+#<Plugin snmp_agent>
+#  <Data "memAvailReal">
+#    Plugin "memory"
+#    Type "memory"
+#    TypeInstance "free"
+#    OIDs "1.3.6.1.4.1.2021.4.6.0"
+#  </Data>
+#  <Table "ifTable">
+#    IndexOID "IF-MIB::ifIndex"
+#    SizeOID "IF-MIB::ifNumber"
+#    <Data "ifDescr">
+#      Instance true
+#      Plugin "interface"
+#      OIDs "IF-MIB::ifDescr"
+#    </Data>
+#    <Data "ifOctets">
+#      Plugin "interface"
+#      Type "if_octets"
+#      TypeInstance ""
+#      OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+#    </Data>
+#  </Table>
+#</Plugin>
+
 #<Plugin statsd>
 #  Host "::"
 #  Port "8125"
index 51fe68d..03ca2ce 100644 (file)
@@ -6930,6 +6930,115 @@ Since the configuration of the C<snmp plugin> is a little more complicated than
 other plugins, its documentation has been moved to an own manpage,
 L<collectd-snmp(5)>. Please see there for details.
 
+=head2 Plugin C<snmp_agent>
+
+The I<snmp_agent> plugin is an AgentX subagent that receives and handles queries
+from SNMP master agent and returns the data collected by read plugins.
+The I<snmp_agent> plugin handles requests only for OIDs specified in
+configuration file. To handle SNMP queries the plugin gets data from collectd
+and translates requested values from collectd's internal format to SNMP format.
+This plugin is a generic plugin and cannot work without configuration.
+For more details on AgentX subagent see
+<http://www.net-snmp.org/tutorial/tutorial-5/toolkit/demon/>
+
+B<Synopsis:>
+
+  <Plugin snmp_agent>
+    <Data "memAvailReal">
+      Plugin "memory"
+      Type "memory"
+      TypeInstance "free"
+      OIDs "1.3.6.1.4.1.2021.4.6.0"
+    </Data>
+    <Table "ifTable">
+      IndexOID "IF-MIB::ifIndex"
+      SizeOID "IF-MIB::ifNumber"
+      <Data "ifDescr">
+        Instance true
+        Plugin "interface"
+        OIDs "IF-MIB::ifDescr"
+      </Data>
+      <Data "ifOctets">
+        Plugin "interface"
+        Type "if_octets"
+        TypeInstance ""
+        OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+      </Data>
+    </Table>
+  </Plugin>
+
+There are two types of blocks that can be contained in the
+C<E<lt>PluginE<nbsp> snmp_agentE<gt>> block: B<Data> and B<Table>:
+
+=head3 The B<Data> block
+
+The B<Data> block defines a list OIDs that are to be handled. This block can
+define scalar or table OIDs. If B<Data> block is defined inside of B<Table>
+block it reperesents table OIDs.
+The following options can be set:
+
+=over 4
+
+=item B<Instance> I<true|false>
+
+When B<Instance> is set to B<true>, the value for requested OID is copied from
+plugin instance field of corresponding collectd value. If B<Data> block defines
+scalar data type B<Instance> has no effect and can be omitted.
+
+=item B<Plugin> I<String>
+
+Read plugin name whose collected data will be mapped to specified OIDs.
+
+=item B<Type> I<String>
+
+Collectd's type that is to be used for specified OID, e.E<nbsp>g. "if_octets"
+for example. The types are read from the B<TypesDB> (see L<collectd.conf(5)>).
+
+=item B<TypeInstance> I<String>
+
+Collectd's type-instance that is to be used for specified OID.
+
+=item B<OIDs> I<OID> [I<OID> ...]
+
+Configures the OIDs to be handled by I<snmp_agent> plugin. Values for these OIDs
+are taken from collectd data type specified by B<Plugin>, B<Type>,
+B<TypeInstance> fields of this B<Data> block. Number of the OIDs configured
+should correspond to number of values in specified B<Type>.
+For example two OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets" can be mapped to
+"rx" and "tx" values of "if_octets" type.
+
+=item B<Scale> I<Value>
+
+The values taken from collectd are multiplied by I<Value>. The field is optional
+and the default is B<1.0>.
+
+=item B<Shift> I<Value>
+
+I<Value> is added to values from collectd after they have been multiplied by
+B<Scale> value. The field is optional and the default value is B<0.0>.
+
+=back
+
+=head3 The B<Table> block
+
+The B<Table> block defines a collection of B<Data> blocks that belong to one
+snmp table. In addition to multiple B<Data> blocks the following options can be
+set:
+
+=over 4
+
+=item B<IndexOID> I<OID>
+
+OID that is handled by the plugin and is mapped to numerical index value that is
+generated by the plugin for each table record.
+
+=item B<SizeOID> I<OID>
+
+OID that is handled by the plugin. Returned value is the number of records in
+the table. The field is optional.
+
+=back
+
 =head2 Plugin C<statsd>
 
 The I<statsd plugin> listens to a UDP socket, reads "events" in the statsd
index 0caf22c..17118e0 100644 (file)
@@ -477,6 +477,75 @@ gauge_t *uc_get_rate(const data_set_t *ds, const value_list_t *vl) {
   return (ret);
 } /* gauge_t *uc_get_rate */
 
+int uc_get_value_by_name(const char *name, value_t **ret_values,
+                         size_t *ret_values_num) {
+  value_t *ret = NULL;
+  size_t ret_num = 0;
+  cache_entry_t *ce = NULL;
+  int status = 0;
+
+  pthread_mutex_lock(&cache_lock);
+
+  if (c_avl_get(cache_tree, name, (void *) &ce) == 0) {
+    assert(ce != NULL);
+
+    /* remove missing values from getval */
+    if (ce->state == STATE_MISSING) {
+      status = -1;
+    } else {
+      ret_num = ce->values_num;
+      ret = malloc(ret_num * sizeof(*ret));
+      if (ret == NULL) {
+        ERROR("utils_cache: uc_get_value_by_name: malloc failed.");
+        status = -1;
+      } else {
+        memcpy(ret, ce->values_raw, ret_num * sizeof(value_t));
+      }
+    }
+  }
+  else {
+    DEBUG("utils_cache: uc_get_value_by_name: No such value: %s", name);
+    status = -1;
+  }
+
+  pthread_mutex_unlock(&cache_lock);
+
+  if (status == 0) {
+    *ret_values = ret;
+    *ret_values_num = ret_num;
+  }
+
+  return (status);
+} /* int uc_get_value_by_name */
+
+value_t *uc_get_value(const data_set_t *ds, const value_list_t *vl) {
+  char name[6 * DATA_MAX_NAME_LEN];
+  value_t *ret = NULL;
+  size_t ret_num = 0;
+  int status;
+
+  if (FORMAT_VL(name, sizeof(name), vl) != 0) {
+    ERROR("utils_cache: uc_get_value: FORMAT_VL failed.");
+    return (NULL);
+  }
+
+  status = uc_get_value_by_name(name, &ret, &ret_num);
+  if (status != 0)
+    return (NULL);
+
+  /* This is important - the caller has no other way of knowing how many
+   * values are returned. */
+  if (ret_num != (size_t) ds->ds_num) {
+    ERROR("utils_cache: uc_get_value: ds[%s] has %zu values, "
+          "but uc_get_value_by_name returned %zu.", ds->type, ds->ds_num,
+          ret_num);
+    sfree(ret);
+    return (NULL);
+  }
+
+  return (ret);
+} /* value_t *uc_get_value */
+
 size_t uc_get_size(void) {
   size_t size_arrays = 0;
 
index 24c58ff..8ba7133 100644 (file)
@@ -42,6 +42,8 @@ int uc_update(const data_set_t *ds, const value_list_t *vl);
 int uc_get_rate_by_name(const char *name, gauge_t **ret_values,
                         size_t *ret_values_num);
 gauge_t *uc_get_rate(const data_set_t *ds, const value_list_t *vl);
+int uc_get_value_by_name(const char *name, value_t **ret_values, size_t *ret_values_num);
+value_t *uc_get_value(const data_set_t *ds, const value_list_t *vl);
 
 size_t uc_get_size(void);
 int uc_get_names(char ***ret_names, cdtime_t **ret_times, size_t *ret_number);
diff --git a/src/snmp_agent.c b/src/snmp_agent.c
new file mode 100644 (file)
index 0000000..4b963d0
--- /dev/null
@@ -0,0 +1,1509 @@
+/**
+ * collectd - src/snmp_agent.c
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ *
+ * 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:
+ *   Roman Korynkevych <romanx.korynkevych@intel.com>
+ *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_avltree.h"
+#include "utils_llist.h"
+#include "utils_cache.h"
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+#define PLUGIN_NAME "snmp_agent"
+#define ERR_BUF_SIZE 1024
+#define TYPE_STRING -1
+
+struct oid_s {
+  oid oid[MAX_OID_LEN];
+  size_t oid_len;
+  u_char type;
+};
+typedef struct oid_s oid_t;
+
+struct table_definition_s {
+  char *name;
+  oid_t index_oid;
+  oid_t size_oid;
+  llist_t *columns;
+  c_avl_tree_t *instance_index;
+  c_avl_tree_t *index_instance;
+};
+typedef struct table_definition_s table_definition_t;
+
+struct data_definition_s {
+  char *name;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+  table_definition_t *table;
+  _Bool is_instance;
+  oid_t *oids;
+  size_t oids_len;
+  double scale;
+  double shift;
+};
+typedef struct data_definition_s data_definition_t;
+
+struct snmp_agent_ctx_s {
+  pthread_t thread;
+  pthread_mutex_t lock;
+  pthread_mutex_t agentx_lock;
+  struct tree *tp;
+
+  llist_t *tables;
+  llist_t *scalars;
+};
+typedef struct snmp_agent_ctx_s snmp_agent_ctx_t;
+
+snmp_agent_ctx_t *g_agent = NULL;
+
+#define CHECK_DD_TYPE(_dd, _p, _pi, _t, _ti)                                   \
+  (_dd->plugin ? !strcmp(_dd->plugin, _p) : 0) &&                              \
+      (_dd->plugin_instance ? !strcmp(_dd->plugin_instance, _pi) : 1) &&       \
+      (_dd->type ? !strcmp(_dd->type, _t) : 1) &&                              \
+      (_dd->type_instance ? !strcmp(_dd->type_instance, _ti) : 1)
+
+static void *snmp_agent_thread_run(void *arg);
+static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler);
+static int snmp_agent_set_vardata(void *dst_buf, size_t *dst_buf_len,
+                                  u_char asn_type, double scale, double shift,
+                                  const void *value, size_t len, int type);
+static int snmp_agent_unregister_oid_index(oid_t *oid, int index);
+
+static u_char snmp_agent_get_asn_type(oid *oid, size_t oid_len) {
+  struct tree *node = get_tree(oid, oid_len, g_agent->tp);
+
+  return (node != NULL ? mib_to_asn_type(node->type) : 0);
+}
+
+static char *snmp_agent_get_oid_name(oid *oid, size_t oid_len) {
+  struct tree *node = get_tree(oid, oid_len, g_agent->tp);
+
+  return (node != NULL ? node->label : NULL);
+}
+
+static int snmp_agent_oid_to_string(char *buf, size_t buf_size,
+                                    oid_t const *o) {
+  char oid_str[MAX_OID_LEN][16];
+  char *oid_str_ptr[MAX_OID_LEN];
+
+  for (size_t i = 0; i < o->oid_len; i++) {
+    ssnprintf(oid_str[i], sizeof(oid_str[i]), "%lu", (unsigned long)o->oid[i]);
+    oid_str_ptr[i] = oid_str[i];
+  }
+
+  return (strjoin(buf, buf_size, oid_str_ptr, o->oid_len, "."));
+}
+
+static void snmp_agent_dump_data(void) {
+#if COLLECT_DEBUG
+  char temp[DATA_MAX_NAME_LEN];
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    DEBUG(PLUGIN_NAME ": Table:");
+    DEBUG(PLUGIN_NAME ":   Name: %s", td->name);
+    if (td->index_oid.oid_len != 0) {
+      snmp_agent_oid_to_string(temp, sizeof(temp), &td->index_oid);
+      DEBUG(PLUGIN_NAME ":   IndexOID: %s", temp);
+    }
+    if (td->size_oid.oid_len != 0) {
+      snmp_agent_oid_to_string(temp, sizeof(temp), &td->size_oid);
+      DEBUG(PLUGIN_NAME ":   SizeOID: %s", temp);
+    }
+
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      DEBUG(PLUGIN_NAME ":   Column:");
+      DEBUG(PLUGIN_NAME ":     Name: %s", dd->name);
+      if (dd->plugin)
+        DEBUG(PLUGIN_NAME ":     Plugin: %s", dd->plugin);
+      if (dd->plugin_instance)
+        DEBUG(PLUGIN_NAME ":     PluginInstance: %s", dd->plugin_instance);
+      if (dd->is_instance)
+        DEBUG(PLUGIN_NAME ":     Instance: true");
+      if (dd->type)
+        DEBUG(PLUGIN_NAME ":     Type: %s", dd->type);
+      if (dd->type_instance)
+        DEBUG(PLUGIN_NAME ":     TypeInstance: %s", dd->type_instance);
+      for (int i = 0; i < dd->oids_len; i++) {
+        snmp_agent_oid_to_string(temp, sizeof(temp), &dd->oids[i]);
+        DEBUG(PLUGIN_NAME ":     OID[%d]: %s", i, temp);
+      }
+      DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
+      DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
+    }
+  }
+
+  for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
+    data_definition_t *dd = (data_definition_t *)e->value;
+
+    DEBUG(PLUGIN_NAME ": Scalar:");
+    DEBUG(PLUGIN_NAME ":   Name: %s", dd->name);
+    if (dd->plugin)
+      DEBUG(PLUGIN_NAME ":   Plugin: %s", dd->plugin);
+    if (dd->plugin_instance)
+      DEBUG(PLUGIN_NAME ":   PluginInstance: %s", dd->plugin_instance);
+    if (dd->is_instance)
+      DEBUG(PLUGIN_NAME ":   Instance: true");
+    if (dd->type)
+      DEBUG(PLUGIN_NAME ":   Type: %s", dd->type);
+    if (dd->type_instance)
+      DEBUG(PLUGIN_NAME ":   TypeInstance: %s", dd->type_instance);
+    for (int i = 0; i < dd->oids_len; i++) {
+      snmp_agent_oid_to_string(temp, sizeof(temp), &dd->oids[i]);
+      DEBUG(PLUGIN_NAME ":   OID[%d]: %s", i, temp);
+    }
+    DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
+    DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
+  }
+#endif /* COLLECT_DEBUG */
+}
+
+static int snmp_agent_validate_data(void) {
+
+  snmp_agent_dump_data();
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      if (!dd->plugin) {
+        ERROR(PLUGIN_NAME ": Plugin not defined for '%s'.'%s'", td->name,
+              dd->name);
+        return (-EINVAL);
+      }
+
+      if (dd->plugin_instance) {
+        ERROR(PLUGIN_NAME ": PluginInstance should not be defined for table "
+                          "data type '%s'.'%s'",
+              td->name, dd->name);
+        return (-EINVAL);
+      }
+
+      if (dd->oids_len == 0) {
+        ERROR(PLUGIN_NAME ": No OIDs defined for '%s'.'%s'", td->name,
+              dd->name);
+        return (-EINVAL);
+      }
+
+      if (dd->is_instance) {
+
+        if (dd->type || dd->type_instance) {
+          ERROR(PLUGIN_NAME ": Type and TypeInstance are not valid for "
+                            "instance data '%s'.'%s'",
+                td->name, dd->name);
+          return (-EINVAL);
+        }
+
+        if (dd->oids_len > 1) {
+          ERROR(
+              PLUGIN_NAME
+              ": Only one OID should be specified for instance data '%s'.'%s'",
+              td->name, dd->name);
+          return (-EINVAL);
+        }
+      } else {
+
+        if (!dd->type) {
+          ERROR(PLUGIN_NAME ": Type not defined for data '%s'.'%s'", td->name,
+                dd->name);
+          return (-EINVAL);
+        }
+      }
+    }
+  }
+
+  for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
+    data_definition_t *dd = (data_definition_t *)e->value;
+
+    if (!dd->plugin) {
+      ERROR(PLUGIN_NAME ": Plugin not defined for '%s'", dd->name);
+      return (-EINVAL);
+    }
+
+    if (dd->oids_len == 0) {
+      ERROR(PLUGIN_NAME ": No OIDs defined for '%s'", dd->name);
+      return (-EINVAL);
+    }
+
+    if (dd->is_instance) {
+      ERROR(PLUGIN_NAME
+            ": Instance flag can't be specified for scalar data '%s'",
+            dd->name);
+      return (-EINVAL);
+    }
+
+    if (!dd->type) {
+      ERROR(PLUGIN_NAME ": Type not defined for data '%s'", dd->name);
+      return (-EINVAL);
+    }
+  }
+
+  return (0);
+}
+
+static int snmp_agent_table_row_remove(table_definition_t *td,
+                                       const char *instance) {
+  int *index;
+  char *ins;
+
+  if ((c_avl_get(td->instance_index, instance, (void**) &index) != 0) ||
+      (c_avl_get(td->index_instance, index, (void**) &ins) != 0))
+    return (0);
+
+  pthread_mutex_lock(&g_agent->agentx_lock);
+
+  for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+    data_definition_t *dd = (data_definition_t *)de->value;
+
+    for (int i = 0; i < dd->oids_len; i++)
+      snmp_agent_unregister_oid_index(&dd->oids[i], *index);
+  }
+
+  snmp_agent_unregister_oid_index(&td->index_oid, *index);
+
+  pthread_mutex_unlock(&g_agent->agentx_lock);
+
+  DEBUG(PLUGIN_NAME ": Removed row for '%s' table [%d, %s].", td->name, *index,
+        ins);
+
+  notification_t n = {NOTIF_WARNING, cdtime(), "", "", PLUGIN_NAME, "", "", "",
+                      NULL};
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
+  sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance));
+  ssnprintf(n.message, sizeof(n.message),
+            "Removed data row from table %s instance %s index %d", td->name,
+            ins, *index);
+  plugin_dispatch_notification(&n);
+
+  c_avl_remove(td->index_instance, index, NULL, (void**) &ins);
+  c_avl_remove(td->instance_index, instance, NULL, (void**) &index);
+  sfree(index);
+  sfree(ins);
+
+  return (0);
+}
+
+static int snmp_agent_clear_missing(const value_list_t *vl,
+                                    __attribute__((unused)) user_data_t *ud) {
+  if (vl == NULL)
+    return (-EINVAL);
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      if (!dd->is_instance) {
+        if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
+                          vl->type_instance)) {
+          snmp_agent_table_row_remove(td, vl->plugin_instance);
+          return (0);
+        }
+      }
+    }
+  }
+
+  return (0);
+}
+
+static void snmp_agent_free_data(data_definition_t **dd) {
+
+  if (dd == NULL || *dd == NULL)
+    return;
+
+  /* unregister scalar type OID */
+  if ((*dd)->table == NULL) {
+    for (int i = 0; i < (*dd)->oids_len; i++)
+      unregister_mib((*dd)->oids[i].oid, (*dd)->oids[i].oid_len);
+  } else {
+    /* unregister all table OIDs */
+    int *index;
+    char *value;
+
+    c_avl_iterator_t *iter = c_avl_get_iterator((*dd)->table->index_instance);
+    while (c_avl_iterator_next(iter, (void *)&index, (void *)&value) == 0) {
+      for (int i = 0; i < (*dd)->oids_len; i++)
+        snmp_agent_unregister_oid_index(&(*dd)->oids[i], *index);
+    }
+    c_avl_iterator_destroy(iter);
+  }
+
+  sfree((*dd)->name);
+  sfree((*dd)->plugin);
+  sfree((*dd)->plugin_instance);
+  sfree((*dd)->type);
+  sfree((*dd)->type_instance);
+  sfree((*dd)->oids);
+
+  sfree(*dd);
+
+  return;
+}
+
+static void snmp_agent_free_table(table_definition_t **td) {
+
+  if (td == NULL || *td == NULL)
+    return;
+
+  if ((*td)->size_oid.oid_len)
+    unregister_mib((*td)->size_oid.oid, (*td)->size_oid.oid_len);
+
+  if ((*td)->index_oid.oid_len) {
+    int *index;
+    char *value;
+
+    c_avl_iterator_t *iter = c_avl_get_iterator((*td)->index_instance);
+    while (c_avl_iterator_next(iter, (void *)&index, (void *)&value) == 0)
+      snmp_agent_unregister_oid_index(&(*td)->index_oid, *index);
+
+    c_avl_iterator_destroy(iter);
+  }
+
+  for (llentry_t *de = llist_head((*td)->columns); de != NULL; de = de->next) {
+    data_definition_t *dd = (data_definition_t *)de->value;
+    snmp_agent_free_data(&dd);
+  }
+
+  llist_destroy((*td)->columns);
+
+  void *key = NULL;
+  void *value = NULL;
+
+  c_avl_destroy((*td)->index_instance);
+  (*td)->index_instance = NULL;
+
+  while (c_avl_pick((*td)->instance_index, &key, &value) == 0) {
+    sfree(key);
+    sfree(value);
+  }
+  c_avl_destroy((*td)->instance_index);
+  (*td)->instance_index = NULL;
+
+  sfree((*td)->name);
+  sfree(*td);
+
+  return;
+}
+
+static int
+snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler,
+                             struct netsnmp_handler_registration_s *reginfo,
+                             struct netsnmp_agent_request_info_s *reqinfo,
+                             struct netsnmp_request_info_s *requests) {
+
+  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+    DEBUG(PLUGIN_NAME ": Not supported request mode (%d).", reqinfo->mode);
+    return SNMP_ERR_NOERROR;
+  }
+
+  oid_t oid;
+  char temp[DATA_MAX_NAME_LEN];
+
+  pthread_mutex_lock(&g_agent->lock);
+
+  memcpy(oid.oid, requests->requestvb->name,
+         sizeof(oid.oid[0]) * requests->requestvb->name_length);
+  oid.oid_len = requests->requestvb->name_length;
+
+  snmp_agent_oid_to_string(temp, sizeof(temp), &oid);
+
+  DEBUG(PLUGIN_NAME ": Get request received for table OID '%s'.", temp);
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      for (int i = 0; i < dd->oids_len; i++) {
+
+        int ret = snmp_oid_ncompare(oid.oid, oid.oid_len,
+                                dd->oids[i].oid, dd->oids[i].oid_len,
+                                MIN(oid.oid_len, dd->oids[i].oid_len));
+        if (ret != 0) continue;
+
+        if (td->index_oid.oid_len) {
+          int index = oid.oid[oid.oid_len - 1];
+          char *instance;
+
+          ret = c_avl_get(td->index_instance, &index, (void **)&instance);
+          if (ret != 0) {
+            DEBUG(PLUGIN_NAME ": Nonexisting index '%d' requested.", index);
+            pthread_mutex_unlock(&g_agent->lock);
+            return SNMP_NOSUCHINSTANCE;
+          }
+
+          if (dd->is_instance) {
+
+            requests->requestvb->type = ASN_OCTET_STR;
+            snmp_set_var_typed_value(requests->requestvb,
+                                     requests->requestvb->type, instance,
+                                     strlen((instance)));
+
+            pthread_mutex_unlock(&g_agent->lock);
+
+            return SNMP_ERR_NOERROR;
+          }
+
+          format_name(temp, sizeof(temp), hostname_g, dd->plugin, instance,
+                      dd->type, dd->type_instance);
+          DEBUG(PLUGIN_NAME ": Identifier '%s'", temp);
+
+          value_t *values;
+          size_t values_num;
+          const data_set_t *ds;
+
+          ds = plugin_get_ds(dd->type);
+          if (ds == NULL) {
+            DEBUG(PLUGIN_NAME ": Data set not found for '%s' type.", dd->type);
+            pthread_mutex_unlock(&g_agent->lock);
+            return SNMP_NOSUCHINSTANCE;
+          }
+
+          ret = uc_get_value_by_name(temp, &values, &values_num);
+
+          if (ret != 0) {
+            ERROR(PLUGIN_NAME ": Failed to get value for '%s'.", temp);
+            pthread_mutex_unlock(&g_agent->lock);
+            return SNMP_NOSUCHINSTANCE;
+          }
+
+          assert(ds->ds_num == values_num);
+          assert(i < values_num);
+
+          char data[DATA_MAX_NAME_LEN];
+          size_t data_len = sizeof(data);
+          ret = snmp_agent_set_vardata(data, &data_len, dd->oids[i].type,
+                                       dd->scale, dd->shift, &values[i],
+                                       sizeof(values[i]), ds->ds[i].type);
+
+          sfree(values);
+          values_num = 0;
+
+          if (ret != 0) {
+            ERROR(PLUGIN_NAME ": Failed to convert '%s' value to snmp data.",
+                  temp);
+            pthread_mutex_unlock(&g_agent->lock);
+            return SNMP_NOSUCHINSTANCE;
+          }
+
+          requests->requestvb->type = dd->oids[i].type;
+          snmp_set_var_typed_value(requests->requestvb,
+                                   requests->requestvb->type, data, data_len);
+
+          pthread_mutex_unlock(&g_agent->lock);
+
+          return SNMP_ERR_NOERROR;
+
+        } else {
+          DEBUG(PLUGIN_NAME ": %s:%d NOT IMPLEMENTED.", __FUNCTION__, __LINE__);
+        }
+      }
+    }
+  }
+
+  pthread_mutex_unlock(&g_agent->lock);
+
+  return SNMP_NOSUCHINSTANCE;
+}
+
+static int
+snmp_agent_table_index_oid_handler(struct netsnmp_mib_handler_s *handler,
+                              struct netsnmp_handler_registration_s *reginfo,
+                              struct netsnmp_agent_request_info_s *reqinfo,
+                              struct netsnmp_request_info_s *requests) {
+
+  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+    DEBUG(PLUGIN_NAME ": Not supported request mode (%d).", reqinfo->mode);
+    return SNMP_ERR_NOERROR;
+  }
+
+  oid_t oid;
+
+  pthread_mutex_lock(&g_agent->lock);
+
+  memcpy(oid.oid, requests->requestvb->name,
+         sizeof(oid.oid[0]) * requests->requestvb->name_length);
+  oid.oid_len = requests->requestvb->name_length;
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    if (td->index_oid.oid_len &&
+          (snmp_oid_ncompare(oid.oid, oid.oid_len, td->index_oid.oid,
+                             td->index_oid.oid_len,
+                             MIN(oid.oid_len, td->index_oid.oid_len)) == 0)) {
+
+      DEBUG(PLUGIN_NAME ": Handle '%s' table index OID.", td->name);
+
+      int index = oid.oid[oid.oid_len - 1];
+      char *instance;
+
+      int ret = c_avl_get(td->index_instance, &index, (void **)&instance);
+      if (ret != 0) {
+        /* nonexisting index requested */
+        pthread_mutex_unlock(&g_agent->lock);
+        return SNMP_NOSUCHINSTANCE;
+      }
+
+      requests->requestvb->type = ASN_INTEGER;
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               &index, sizeof(index));
+
+      pthread_mutex_unlock(&g_agent->lock);
+
+      return SNMP_ERR_NOERROR;
+    }
+  }
+
+  pthread_mutex_unlock(&g_agent->lock);
+
+  return SNMP_NOSUCHINSTANCE;
+}
+
+static int
+snmp_agent_table_size_oid_handler(struct netsnmp_mib_handler_s *handler,
+                              struct netsnmp_handler_registration_s *reginfo,
+                              struct netsnmp_agent_request_info_s *reqinfo,
+                              struct netsnmp_request_info_s *requests) {
+
+  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+    DEBUG(PLUGIN_NAME ": Not supported request mode (%d).", reqinfo->mode);
+    return SNMP_ERR_NOERROR;
+  }
+
+  oid_t oid;
+
+  pthread_mutex_lock(&g_agent->lock);
+
+  memcpy(oid.oid, requests->requestvb->name,
+         sizeof(oid.oid[0]) * requests->requestvb->name_length);
+  oid.oid_len = requests->requestvb->name_length;
+
+  DEBUG(PLUGIN_NAME ": Get request received for table size OID.");
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    if (td->size_oid.oid_len &&
+        (snmp_oid_ncompare(oid.oid, oid.oid_len, td->size_oid.oid,
+                           td->size_oid.oid_len,
+                           MIN(oid.oid_len, td->size_oid.oid_len)) == 0)) {
+      DEBUG(PLUGIN_NAME ": Handle '%s' table size OID.", td->name);
+
+      long size = c_avl_size(td->index_instance);
+
+      requests->requestvb->type = td->size_oid.type;
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               &size, sizeof(size));
+
+      pthread_mutex_unlock(&g_agent->lock);
+
+      return SNMP_ERR_NOERROR;
+    }
+  }
+
+  pthread_mutex_unlock(&g_agent->lock);
+
+  return SNMP_NOSUCHINSTANCE;
+}
+
+
+static int
+snmp_agent_scalar_oid_handler(struct netsnmp_mib_handler_s *handler,
+                              struct netsnmp_handler_registration_s *reginfo,
+                              struct netsnmp_agent_request_info_s *reqinfo,
+                              struct netsnmp_request_info_s *requests) {
+
+  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+    DEBUG(PLUGIN_NAME ": Not supported request mode (%d).", reqinfo->mode);
+    return SNMP_ERR_NOERROR;
+  }
+
+  int ret;
+  oid_t oid;
+  char temp[DATA_MAX_NAME_LEN];
+
+  pthread_mutex_lock(&g_agent->lock);
+
+  memcpy(oid.oid, requests->requestvb->name,
+         sizeof(oid.oid[0]) * requests->requestvb->name_length);
+  oid.oid_len = requests->requestvb->name_length;
+
+  snmp_agent_oid_to_string(temp, sizeof(temp), &oid);
+
+  DEBUG(PLUGIN_NAME ": Get request received for scalar OID '%s'.", temp);
+
+  for (llentry_t *de = llist_head(g_agent->scalars); de != NULL; de = de->next) {
+    data_definition_t *dd = (data_definition_t *)de->value;
+
+    for (int i = 0; i < dd->oids_len; i++) {
+
+      ret = snmp_oid_compare(oid.oid, oid.oid_len,
+                             dd->oids[i].oid, dd->oids[i].oid_len);
+      if (ret != 0) continue;
+
+      format_name(temp, sizeof(temp), hostname_g, dd->plugin,
+                  dd->plugin_instance, dd->type, dd->type_instance);
+      DEBUG(PLUGIN_NAME ": Identifier '%s'", temp);
+
+      value_t *values;
+      size_t values_num;
+      const data_set_t *ds;
+
+      ds = plugin_get_ds(dd->type);
+      if (ds == NULL) {
+        DEBUG(PLUGIN_NAME ": Data set not found for '%s' type", dd->type);
+        pthread_mutex_unlock(&g_agent->lock);
+        return SNMP_NOSUCHINSTANCE;
+      }
+
+      ret = uc_get_value_by_name(temp, &values, &values_num);
+
+      if (ret != 0) {
+        ERROR(PLUGIN_NAME ": Failed to get value for '%s'.", temp);
+        pthread_mutex_unlock(&g_agent->lock);
+        return SNMP_NOSUCHINSTANCE;
+      }
+
+      assert(ds->ds_num == values_num);
+      assert(i < values_num);
+
+      char data[DATA_MAX_NAME_LEN];
+      size_t data_len = sizeof(data);
+      ret = snmp_agent_set_vardata(data, &data_len, dd->oids[i].type,
+                                   dd->scale, dd->shift, &values[i],
+                                   sizeof(values[i]), ds->ds[i].type);
+
+      sfree(values);
+      values_num = 0;
+
+      if (ret != 0) {
+        ERROR(PLUGIN_NAME ": Failed to convert '%s' value to snmp data.", temp);
+        pthread_mutex_unlock(&g_agent->lock);
+        return SNMP_NOSUCHINSTANCE;
+      }
+
+      requests->requestvb->type = dd->oids[i].type;
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               data, data_len);
+
+      pthread_mutex_unlock(&g_agent->lock);
+
+      return SNMP_ERR_NOERROR;
+    }
+  }
+
+  pthread_mutex_unlock(&g_agent->lock);
+
+  return SNMP_NOSUCHINSTANCE;
+}
+
+static int snmp_agent_register_table_oids(void) {
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    if (td->size_oid.oid_len != 0) {
+      td->size_oid.type =
+          snmp_agent_get_asn_type(td->size_oid.oid, td->size_oid.oid_len);
+      td->size_oid.oid_len++;
+      int ret = snmp_agent_register_oid(&td->size_oid,
+                                        snmp_agent_table_size_oid_handler);
+      if (ret != 0)
+        return ret;
+    }
+
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      for (int i = 0; i < dd->oids_len; i++) {
+        dd->oids[i].type =
+            snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len);
+      }
+    }
+  }
+
+  return (0);
+}
+
+static int snmp_agent_register_scalar_oids(void) {
+
+  for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
+    data_definition_t *dd = (data_definition_t *)e->value;
+
+    for (int i = 0; i < dd->oids_len; i++) {
+
+      dd->oids[i].type =
+          snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len);
+
+      int ret =
+          snmp_agent_register_oid(&dd->oids[i], snmp_agent_scalar_oid_handler);
+      if (ret != 0)
+        return ret;
+    }
+  }
+
+  return (0);
+}
+
+static int snmp_agent_config_data_oids(data_definition_t *dd,
+                                       oconfig_item_t *ci) {
+  if (ci->values_num < 1) {
+    WARNING(PLUGIN_NAME ": `OIDs' needs at least one argument.");
+    return (-EINVAL);
+  }
+
+  for (int i = 0; i < ci->values_num; i++)
+    if (ci->values[i].type != OCONFIG_TYPE_STRING) {
+      WARNING(PLUGIN_NAME ": `OIDs' needs only string argument.");
+      return (-EINVAL);
+    }
+
+  if (dd->oids != NULL)
+    sfree(dd->oids);
+  dd->oids_len = 0;
+  dd->oids = calloc(ci->values_num, sizeof(*dd->oids));
+  if (dd->oids == NULL)
+    return (-ENOMEM);
+  dd->oids_len = (size_t)ci->values_num;
+
+  for (int i = 0; i < ci->values_num; i++) {
+    dd->oids[i].oid_len = MAX_OID_LEN;
+
+    if (NULL == snmp_parse_oid(ci->values[i].value.string, dd->oids[i].oid,
+                               &dd->oids[i].oid_len)) {
+      ERROR(PLUGIN_NAME ": snmp_parse_oid (%s) failed.",
+            ci->values[i].value.string);
+      sfree(dd->oids);
+      dd->oids_len = 0;
+      return (-1);
+    }
+  }
+
+  return (0);
+}
+
+static int snmp_agent_config_table_size_oid(table_definition_t *td,
+                                            oconfig_item_t *ci) {
+  if (ci->values_num < 1) {
+    WARNING(PLUGIN_NAME ": `TableSizeOID' is empty.");
+    return (-EINVAL);
+  }
+
+  if (ci->values[0].type != OCONFIG_TYPE_STRING) {
+    WARNING(PLUGIN_NAME ": `TableSizeOID' needs only string argument.");
+    return (-EINVAL);
+  }
+
+  td->size_oid.oid_len = MAX_OID_LEN;
+
+  if (NULL == snmp_parse_oid(ci->values[0].value.string, td->size_oid.oid,
+                             &td->size_oid.oid_len)) {
+    ERROR(PLUGIN_NAME ": Failed to parse table size OID (%s)",
+          ci->values[0].value.string);
+    td->size_oid.oid_len = 0;
+    return (-EINVAL);
+  }
+
+  return (0);
+}
+
+static int snmp_agent_config_table_index_oid(table_definition_t *td,
+                                             oconfig_item_t *ci) {
+
+  if (ci->values_num < 1) {
+    WARNING(PLUGIN_NAME ": `IndexOID' is empty.");
+    return (-EINVAL);
+  }
+
+  if (ci->values[0].type != OCONFIG_TYPE_STRING) {
+    WARNING(PLUGIN_NAME ": `IndexOID' needs only string argument.");
+    return (-EINVAL);
+  }
+
+  td->index_oid.oid_len = MAX_OID_LEN;
+
+  if (NULL == snmp_parse_oid(ci->values[0].value.string, td->index_oid.oid,
+                             &td->index_oid.oid_len)) {
+    ERROR(PLUGIN_NAME ": Failed to parse table index OID (%s)",
+          ci->values[0].value.string);
+    td->index_oid.oid_len = 0;
+    return (-EINVAL);
+  }
+
+  return (0);
+}
+
+static int snmp_agent_config_table_data(table_definition_t *td,
+                                        oconfig_item_t *ci) {
+  data_definition_t *dd;
+  int ret = 0;
+
+  assert(ci != NULL);
+
+  dd = calloc(1, sizeof(*dd));
+  if (dd == NULL) {
+    ERROR(PLUGIN_NAME ": Failed to allocate memory for table data definition");
+    return (-ENOMEM);
+  }
+
+  ret = cf_util_get_string(ci, &dd->name);
+  if (ret != 0) {
+    sfree(dd);
+    return (-1);
+  }
+
+  dd->scale = 1.0;
+  dd->shift = 0.0;
+
+  dd->table = td;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp("Instance", option->key) == 0)
+      ret = cf_util_get_boolean(option, &dd->is_instance);
+    else if (strcasecmp("Plugin", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->plugin);
+    else if (strcasecmp("PluginInstance", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->plugin_instance);
+    else if (strcasecmp("Type", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->type);
+    else if (strcasecmp("TypeInstance", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->type_instance);
+    else if (strcasecmp("Shift", option->key) == 0)
+      ret = cf_util_get_double(option, &dd->shift);
+    else if (strcasecmp("Scale", option->key) == 0)
+      ret = cf_util_get_double(option, &dd->scale);
+    else if (strcasecmp("OIDs", option->key) == 0)
+      ret = snmp_agent_config_data_oids(dd, option);
+    else {
+      WARNING(PLUGIN_NAME ": Option `%s' not allowed here.", option->key);
+      ret = -1;
+    }
+
+    if (ret != 0) {
+      snmp_agent_free_data(&dd);
+      return (-1);
+    }
+  }
+
+  llentry_t *entry = llentry_create(dd->name, dd);
+  if (entry == NULL) {
+    snmp_agent_free_data(&dd);
+    return (-ENOMEM);
+  }
+
+  llist_append(td->columns, entry);
+
+  return (0);
+}
+
+static int snmp_agent_config_data(oconfig_item_t *ci) {
+  data_definition_t *dd;
+  int ret = 0;
+
+  assert(ci != NULL);
+
+  dd = calloc(1, sizeof(*dd));
+  if (dd == NULL) {
+    ERROR(PLUGIN_NAME ": Failed to allocate memory for data definition");
+    return (-ENOMEM);
+  }
+
+  ret = cf_util_get_string(ci, &dd->name);
+  if (ret != 0) {
+    free(dd);
+    return (-1);
+  }
+
+  dd->scale = 1.0;
+  dd->shift = 0.0;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp("Instance", option->key) == 0)
+      ret = cf_util_get_boolean(option, &dd->is_instance);
+    else if (strcasecmp("Plugin", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->plugin);
+    else if (strcasecmp("PluginInstance", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->plugin_instance);
+    else if (strcasecmp("Type", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->type);
+    else if (strcasecmp("TypeInstance", option->key) == 0)
+      ret = cf_util_get_string(option, &dd->type_instance);
+    else if (strcasecmp("Shift", option->key) == 0)
+      ret = cf_util_get_double(option, &dd->shift);
+    else if (strcasecmp("Scale", option->key) == 0)
+      ret = cf_util_get_double(option, &dd->scale);
+    else if (strcasecmp("OIDs", option->key) == 0)
+      ret = snmp_agent_config_data_oids(dd, option);
+    else {
+      WARNING(PLUGIN_NAME ": Option `%s' not allowed here.", option->key);
+      ret = -1;
+    }
+
+    if (ret != 0) {
+      snmp_agent_free_data(&dd);
+      return (-1);
+    }
+  }
+
+  llentry_t *entry = llentry_create(dd->name, dd);
+  if (entry == NULL) {
+    snmp_agent_free_data(&dd);
+    return (-ENOMEM);
+  }
+
+  llist_append(g_agent->scalars, entry);
+
+  return (0);
+}
+
+static int num_compare(const int *a, const int *b) {
+  assert((a != NULL) && (b != NULL));
+  if (*a < *b)
+    return (-1);
+  else if (*a > *b)
+    return (1);
+  else
+    return (0);
+}
+
+static int snmp_agent_config_table(oconfig_item_t *ci) {
+  table_definition_t *td;
+  int ret = 0;
+
+  assert(ci != NULL);
+
+  td = calloc(1, sizeof(*td));
+  if (td == NULL) {
+    ERROR(PLUGIN_NAME ": Failed to allocate memory for table definition.");
+    return (-ENOMEM);
+  }
+
+  ret = cf_util_get_string(ci, &td->name);
+  if (ret != 0) {
+    sfree(td);
+    return (-1);
+  }
+
+  td->columns = llist_create();
+  if (td->columns == NULL) {
+    ERROR(PLUGIN_NAME ": Failed to allocate memory for columns list.");
+    snmp_agent_free_table(&td);
+    return (-ENOMEM);
+  }
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp("IndexOID", option->key) == 0)
+      ret = snmp_agent_config_table_index_oid(td, option);
+    else if (strcasecmp("SizeOID", option->key) == 0)
+      ret = snmp_agent_config_table_size_oid(td, option);
+    else if (strcasecmp("Data", option->key) == 0)
+      ret = snmp_agent_config_table_data(td, option);
+    else {
+      WARNING(PLUGIN_NAME ": Option `%s' not allowed here.", option->key);
+      ret = -1;
+    }
+
+    if (ret != 0) {
+      snmp_agent_free_table(&td);
+      return (-ENOMEM);
+    }
+  }
+
+  llentry_t *entry = llentry_create(td->name, td);
+  if (entry == NULL) {
+    snmp_agent_free_table(&td);
+    return (-ENOMEM);
+  }
+
+  td->instance_index =
+      c_avl_create((int (*)(const void *, const void *))strcmp);
+  if (td->instance_index == NULL) {
+    snmp_agent_free_table(&td);
+    return (-ENOMEM);
+  }
+
+  td->index_instance =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  if (td->index_instance == NULL) {
+    snmp_agent_free_table(&td);
+    return (-ENOMEM);
+  }
+
+  llist_append(g_agent->tables, entry);
+
+  return (0);
+}
+
+static int snmp_agent_get_value_from_ds_type(const value_t *val, int type,
+                                             double scale, double shift,
+                                             long *value) {
+  switch (type) {
+  case DS_TYPE_COUNTER:
+    *value = (long)((val->counter * scale) + shift);
+    break;
+  case DS_TYPE_ABSOLUTE:
+    *value = (long)((val->absolute * scale) + shift);
+    break;
+  case DS_TYPE_DERIVE:
+    *value = (long)((val->derive * scale) + shift);
+    break;
+  case DS_TYPE_GAUGE:
+    *value = (long)((val->gauge * scale) + shift);
+    break;
+  case TYPE_STRING:
+    break;
+  default:
+    ERROR(PLUGIN_NAME ": Unknown data source type: %i.", type);
+    return (-EINVAL);
+  }
+
+  return (0);
+}
+
+static int snmp_agent_set_vardata(void *data, size_t *data_len, u_char asn_type,
+                                  double scale, double shift, const void *value,
+                                  size_t len, int type) {
+
+  int ret;
+  netsnmp_vardata var;
+  const value_t *val;
+  long new_value = 0;
+
+  val = value;
+  var.string = (u_char *)data;
+
+  ret = snmp_agent_get_value_from_ds_type(val, type, scale, shift, &new_value);
+  if (ret != 0)
+    return ret;
+
+  switch (asn_type) {
+  case ASN_INTEGER:
+  case ASN_UINTEGER:
+  case ASN_COUNTER:
+  case ASN_TIMETICKS:
+  case ASN_GAUGE:
+    if (*data_len < sizeof(*var.integer))
+      return (-EINVAL);
+    *var.integer = new_value;
+    *data_len = sizeof(*var.integer);
+    break;
+  case ASN_COUNTER64:
+    if (*data_len < sizeof(*var.counter64))
+      return (-EINVAL);
+    var.counter64->high = (u_long)((int64_t)new_value >> 32);
+    var.counter64->low = (u_long)((int64_t)new_value & 0xFFFFFFFF);
+    *data_len = sizeof(*var.counter64);
+    break;
+  case ASN_OCTET_STR:
+    if (type == DS_TYPE_GAUGE) {
+      char buf[DATA_MAX_NAME_LEN];
+      snprintf(buf, sizeof(buf), "%.2f", val->gauge);
+      if (*data_len < strlen(buf))
+        return (-EINVAL);
+      *data_len = strlen(buf);
+      memcpy(var.string, buf, *data_len);
+    } else {
+      ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type.", type,
+            asn_type);
+      return (-EINVAL);
+    }
+    break;
+  default:
+    ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type.", type,
+          asn_type);
+    return (-EINVAL);
+  }
+
+  return (0);
+}
+
+static int snmp_agent_register_oid_index(oid_t *oid, int index,
+                                         Netsnmp_Node_Handler *handler) {
+  oid_t new_oid;
+  memcpy(&new_oid, oid, sizeof(*oid));
+  new_oid.oid[new_oid.oid_len++] = index;
+  return snmp_agent_register_oid(&new_oid, handler);
+}
+
+static int snmp_agent_unregister_oid_index(oid_t *oid, int index) {
+  oid_t new_oid;
+  memcpy(&new_oid, oid, sizeof(*oid));
+  new_oid.oid[new_oid.oid_len++] = index;
+  return unregister_mib(new_oid.oid, new_oid.oid_len);
+}
+
+static int snmp_agent_update_index(table_definition_t *td,
+                                   const char *instance) {
+
+  if (c_avl_get(td->instance_index, instance, NULL) == 0)
+    return (0);
+
+  int ret;
+  int *index;
+  char *ins;
+
+  ins = strdup(instance);
+  if (ins == NULL)
+    return (-ENOMEM);
+
+  index = calloc(1, sizeof(*index));
+  if (index == NULL) {
+    sfree(ins);
+    return (-ENOMEM);
+  }
+
+  *index = c_avl_size(td->instance_index) + 1;
+
+  ret = c_avl_insert(td->instance_index, ins, index);
+  if (ret != 0) {
+    sfree(ins);
+    sfree(index);
+    return ret;
+  }
+
+  ret = c_avl_insert(td->index_instance, index, ins);
+  if (ret < 0) {
+    DEBUG(PLUGIN_NAME ": Failed to update index_instance for '%s' table.",
+          td->name);
+    return ret;
+  }
+
+  DEBUG(PLUGIN_NAME ": Updated index for '%s' table [%d, %s].", td->name,
+        *index, ins);
+
+  /* need to generate index for the table */
+  if (td->index_oid.oid_len) {
+
+    ret = snmp_agent_register_oid_index(&td->index_oid, *index,
+                                        snmp_agent_table_index_oid_handler);
+    if (ret != 0)
+      return ret;
+
+    /* register new oids for all columns */
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      for (int i = 0; i < dd->oids_len; i++) {
+        ret = snmp_agent_register_oid_index(&dd->oids[i], *index,
+                                            snmp_agent_table_oid_handler);
+        if (ret != 0)
+          return ret;
+      }
+    }
+  }
+
+  notification_t n = {NOTIF_OKAY, cdtime(), "", "", PLUGIN_NAME, "", "", "",
+                      NULL};
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
+  sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance));
+  ssnprintf(n.message, sizeof(n.message),
+            "Data row added to table %s instance %s index %d", td->name, ins,
+            *index);
+  plugin_dispatch_notification(&n);
+
+  return (0);
+}
+
+static int snmp_agent_write(value_list_t const *vl) {
+
+  if (vl == NULL)
+    return (-EINVAL);
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
+    table_definition_t *td = (table_definition_t *)te->value;
+
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *dd = (data_definition_t *)de->value;
+
+      if (!dd->is_instance) {
+        if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
+                          vl->type_instance)) {
+          snmp_agent_update_index(td, vl->plugin_instance);
+          return (0);
+        }
+      }
+    }
+  }
+
+  return (0);
+}
+
+static int snmp_agent_collect(const data_set_t *ds, const value_list_t *vl,
+                              user_data_t __attribute__((unused)) * user_data) {
+
+  pthread_mutex_lock(&g_agent->lock);
+
+  snmp_agent_write(vl);
+
+  pthread_mutex_unlock(&g_agent->lock);
+
+  return (0);
+}
+
+static int snmp_agent_preinit(void) {
+  if (g_agent != NULL) {
+    /* already initialized if config callback was called before init callback */
+    return (0);
+  }
+
+  g_agent = calloc(1, sizeof(*g_agent));
+  if (g_agent == NULL) {
+    ERROR(PLUGIN_NAME ": Failed to allocate memory for snmp agent context.");
+    return (-ENOMEM);
+  }
+
+  g_agent->tables = llist_create();
+  g_agent->scalars = llist_create();
+
+  int err;
+  /* make us a agentx client. */
+  err = netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE,
+                               1);
+  if (err != 0) {
+    ERROR(PLUGIN_NAME ": Failed to set agent role (%d)", err);
+    return (-1);
+  }
+
+  /*
+   *  For SNMP debug purposes uses snmp_set_do_debugging(1);
+   */
+
+  /* initialize the agent library */
+  err = init_agent(PLUGIN_NAME);
+  if (err != 0) {
+    ERROR(PLUGIN_NAME ": Failed to initialize the agent library (%d)", err);
+    return (-1);
+  }
+
+  init_snmp(PLUGIN_NAME);
+
+  g_agent->tp = read_all_mibs();
+
+  return (0);
+}
+
+static int snmp_agent_init(void) {
+  int ret;
+
+  ret = snmp_agent_preinit();
+  if (ret != 0)
+    return ret;
+
+  ret = snmp_agent_register_scalar_oids();
+  if (ret != 0)
+    return ret;
+
+  ret = snmp_agent_register_table_oids();
+  if (ret != 0)
+    return ret;
+
+  /* create a second thread to listen for requests from AgentX*/
+  ret = pthread_create(&g_agent->thread, NULL, &snmp_agent_thread_run, NULL);
+  if (ret != 0) {
+    ERROR(PLUGIN_NAME ": Failed to create a separate thread, err %u", ret);
+    return ret;
+  }
+
+  ret = pthread_mutex_init(&g_agent->lock, NULL);
+  if (ret != 0) {
+    ERROR(PLUGIN_NAME ": Failed to initialize mutex, err %u", ret);
+    return ret;
+  }
+
+  ret = pthread_mutex_init(&g_agent->agentx_lock, NULL);
+  if (ret != 0) {
+    ERROR(PLUGIN_NAME ": Failed to initialize AgentX mutex, err %u", ret);
+    return ret;
+  }
+
+  return (0);
+}
+
+static void *snmp_agent_thread_run(void __attribute__((unused)) * arg) {
+  INFO(PLUGIN_NAME ": Thread is up and running");
+
+  for (;;) {
+    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+    pthread_mutex_lock(&g_agent->agentx_lock);
+    agent_check_and_process(0); /* 0 == don't block */
+    pthread_mutex_unlock(&g_agent->agentx_lock);
+
+    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+    usleep(10);
+  }
+
+  pthread_exit(0);
+}
+
+static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler) {
+  netsnmp_handler_registration *reg;
+  char temp[DATA_MAX_NAME_LEN];
+  char *oid_name = snmp_agent_get_oid_name(oid->oid, oid->oid_len - 1);
+
+  snmp_agent_oid_to_string(temp, sizeof(temp), oid);
+
+  if (oid_name == NULL) {
+    WARNING(PLUGIN_NAME
+            ": Skipped registration: OID (%s) is not found in main tree", temp);
+    return (0);
+  }
+
+  reg = netsnmp_create_handler_registration(oid_name, handler, oid->oid,
+                                            oid->oid_len, HANDLER_CAN_RONLY);
+  if (reg == NULL) {
+    ERROR(PLUGIN_NAME ": Failed to create handler registration for OID (%s)",
+          temp);
+    return (-1);
+  }
+
+  pthread_mutex_lock(&g_agent->agentx_lock);
+
+  if (netsnmp_register_instance(reg) != MIB_REGISTERED_OK) {
+    ERROR(PLUGIN_NAME ": Failed to register handler for OID (%s)", temp);
+    pthread_mutex_unlock(&g_agent->agentx_lock);
+    return (-1);
+  }
+
+  pthread_mutex_unlock(&g_agent->agentx_lock);
+
+  DEBUG(PLUGIN_NAME ": Registered handler for OID (%s)", temp);
+
+  return (0);
+}
+
+static int snmp_agent_free_config(void) {
+
+  if (g_agent == NULL)
+    return (-EINVAL);
+
+  for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next)
+    snmp_agent_free_table((table_definition_t **)&te->value);
+  llist_destroy(g_agent->tables);
+
+  for (llentry_t *de = llist_head(g_agent->scalars); de != NULL; de = de->next)
+    snmp_agent_free_data((data_definition_t **)&de->value);
+  llist_destroy(g_agent->scalars);
+
+  return (0);
+}
+
+static int snmp_agent_shutdown(void) {
+  int ret = 0;
+
+  DEBUG(PLUGIN_NAME ": snmp_agent_shutdown.");
+
+  if (g_agent == NULL) {
+    ERROR(PLUGIN_NAME ": snmp_agent_shutdown: plugin not initialized.");
+    return (-EINVAL);
+  }
+
+  if (pthread_cancel(g_agent->thread) != 0)
+    ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to cancel the thread.");
+
+  if (pthread_join(g_agent->thread, NULL) != 0)
+    ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to join the thread.");
+
+  snmp_agent_free_config();
+
+  snmp_shutdown(PLUGIN_NAME);
+
+  pthread_mutex_destroy(&g_agent->lock);
+  pthread_mutex_destroy(&g_agent->agentx_lock);
+
+  sfree(g_agent);
+
+  return ret;
+}
+
+static int snmp_agent_config(oconfig_item_t *ci) {
+
+  int ret = snmp_agent_preinit();
+
+  if (ret != 0) {
+    sfree(g_agent);
+    return (-EINVAL);
+  }
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp("Data", child->key) == 0) {
+      ret = snmp_agent_config_data(child);
+    } else if (strcasecmp("Table", child->key) == 0) {
+      ret = snmp_agent_config_table(child);
+    } else {
+      ERROR(PLUGIN_NAME ": Unknown configuration option `%s'.", child->key);
+      ret = (-EINVAL);
+    }
+
+    if (ret != 0) {
+      ERROR(PLUGIN_NAME ": Failed to parse configuration.");
+      snmp_agent_free_config();
+      snmp_shutdown(PLUGIN_NAME);
+      sfree(g_agent);
+      return (-EINVAL);
+    }
+  }
+
+  ret = snmp_agent_validate_data();
+  if (ret != 0) {
+    ERROR(PLUGIN_NAME ": Invalid configuration provided.");
+    snmp_agent_free_config();
+    snmp_shutdown(PLUGIN_NAME);
+    sfree(g_agent);
+    return (-EINVAL);
+  }
+
+  return (0);
+}
+
+void module_register(void) {
+  plugin_register_init(PLUGIN_NAME, snmp_agent_init);
+  plugin_register_complex_config(PLUGIN_NAME, snmp_agent_config);
+  plugin_register_write(PLUGIN_NAME, snmp_agent_collect, NULL);
+  plugin_register_missing(PLUGIN_NAME, snmp_agent_clear_missing, NULL);
+  plugin_register_shutdown(PLUGIN_NAME, snmp_agent_shutdown);
+}