summaryrefslogtreecommitdiff
path: root/daemon/poll-engine.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/poll-engine.c')
-rw-r--r--daemon/poll-engine.c454
1 files changed, 454 insertions, 0 deletions
diff --git a/daemon/poll-engine.c b/daemon/poll-engine.c
new file mode 100644
index 0000000..6130a89
--- /dev/null
+++ b/daemon/poll-engine.c
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2008, Stefan Walter
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the
+ * above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ * * The names of contributors to this software may not be
+ * used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ *
+ * CONTRIBUTORS
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#include "usuals.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <err.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+
+#include "log.h"
+#include "rrdbotd.h"
+#include "server-mainloop.h"
+#include "snmp-engine.h"
+
+/* -----------------------------------------------------------------------------
+ * PACKET HANDLING
+ */
+
+static void
+complete_request (rb_item *item, int code)
+{
+ int host;
+
+ ASSERT (item);
+
+ if (item->request)
+ snmp_engine_cancel (item->request);
+ item->request = 0;
+
+ /* If we have multiple host names then try the next host */
+ if (code != SNMP_ERR_NOERROR) {
+ host = (item->hostindex + 1) % item->n_hostnames;
+ if (host != item->hostindex) {
+ log_debug ("request failed, trying new host: %s", item->hostnames[host]);
+ item->hostindex = host;
+ }
+ }
+}
+
+static void
+cancel_request (rb_item *item, const char *reason)
+{
+ ASSERT (item);
+ ASSERT (reason);
+ ASSERT (item->request);
+
+ log_debug ("value for field '%s': %s", item->field, reason);
+ item->vtype = VALUE_UNSET;
+
+ complete_request (item, -1);
+}
+
+static void
+force_poll (rb_poller *poll, mstime when, const char *reason)
+{
+ rb_item *item;
+ int forced = 0;
+
+ ASSERT (poll);
+ ASSERT (reason);
+
+ /* Now see if the entire request is done */
+ for (item = poll->items; item; item = item->next) {
+ if (item->request) {
+ cancel_request (item, reason);
+ forced = 1;
+ }
+ ASSERT (!item->request);
+ }
+
+ if (forced) {
+
+ /*
+ * We note the failure has having taken place halfway between
+ * the request and the current time.
+ */
+ poll->last_polled = poll->last_request + ((when - poll->last_request) / 2);
+
+ /* And send off our collection of values */
+ rb_rrd_update (poll);
+ }
+}
+
+static void
+finish_poll (rb_poller *poll, mstime when)
+{
+ rb_item *item;
+
+ ASSERT (poll);
+
+ /* Now see if the entire request is done */
+ for (item = poll->items; item; item = item->next) {
+ if (item->request)
+ return;
+ }
+
+ /* Update the book-keeping */
+ poll->last_polled = when;
+
+ /* And send off our collection of values */
+ rb_rrd_update (poll);
+}
+
+static void
+field_response (int request, int code, struct snmp_value *value, void *arg)
+{
+ rb_item *item = arg;
+ const char *msg = NULL;
+
+ ASSERT (item->request == request);
+
+ /* Mark this item as done */
+ item->request = 0;
+
+ /* Errors result in us writing U */
+ if (code != SNMP_ERR_NOERROR) {
+ item->vtype = VALUE_UNSET;
+
+ /* Parse the value from server */
+ } else {
+ switch(value->syntax)
+ {
+ case SNMP_SYNTAX_NULL:
+ item->vtype = VALUE_UNSET;
+ break;
+ case SNMP_SYNTAX_INTEGER:
+ item->v.i_value = value->v.integer;
+ item->vtype = VALUE_REAL;
+ break;
+ case SNMP_SYNTAX_COUNTER:
+ case SNMP_SYNTAX_GAUGE:
+ case SNMP_SYNTAX_TIMETICKS:
+ item->v.i_value = value->v.uint32;
+ item->vtype = VALUE_REAL;
+ break;
+ case SNMP_SYNTAX_COUNTER64:
+ item->v.i_value = value->v.counter64;
+ item->vtype = VALUE_REAL;
+ break;
+ case SNMP_SYNTAX_OCTETSTRING:
+ case SNMP_SYNTAX_OID:
+ case SNMP_SYNTAX_IPADDRESS:
+ msg = "snmp server returned non numeric value for field: %s";
+ break;
+ case SNMP_SYNTAX_NOSUCHOBJECT:
+ case SNMP_SYNTAX_NOSUCHINSTANCE:
+ case SNMP_SYNTAX_ENDOFMIBVIEW:
+ msg = "field not available on snmp server: %s";
+ break;
+ default:
+ msg = "snmp server returned invalid or unsupported value for field: %s";
+ break;
+ };
+
+ if (msg)
+ log_warnx (msg, item->field);
+ else if (item->vtype == VALUE_REAL)
+ log_debug ("got value for field '%s': %lld",
+ item->field, item->v.i_value);
+ else if (item->vtype == VALUE_FLOAT)
+ log_debug ("got value for field '%s': %.4lf",
+ item->field, item->v.f_value);
+ else
+ log_debug ("got value for field '%s': U",
+ item->field);
+ }
+
+ complete_request (item, code);
+
+ /* If the entire poll is done, then complete it */
+ finish_poll (item->poller, server_get_time ());
+}
+
+static void
+field_request (rb_item *item)
+{
+ int req;
+
+ ASSERT (item);
+ ASSERT (!item->request);
+
+ item->vtype = VALUE_UNSET;
+
+ req = snmp_engine_request (item->hostnames[item->hostindex], item->community,
+ item->version, item->poller->interval, item->poller->timeout,
+ SNMP_PDU_GET, &item->field_oid, field_response, item);
+ item->request = req;
+}
+
+/* Forward declaration */
+static void query_request (rb_item *item, int first);
+
+static void
+query_response (int request, int code, struct snmp_value *value, void *arg)
+{
+ rb_item *item = arg;
+ struct asn_oid oid;
+ int matched, req, found;
+
+ ASSERT (request == item->request);
+ ASSERT (item->has_query);
+
+ /*
+ * This was the number we last appended.
+ */
+ ASSERT (item->query_value >= 0);
+ item->request = 0;
+
+ /* Problems communicating with the server? */
+ if (code != SNMP_ERR_NOERROR && code != SNMP_ERR_NOSUCHNAME) {
+ complete_request (item, code);
+ return;
+ }
+
+ found = 0;
+ matched = 0;
+
+ if (code == SNMP_ERR_NOERROR) {
+ ASSERT (value);
+
+ /* These all signify 'not found' in our book */
+ switch (value->syntax) {
+ case SNMP_SYNTAX_NOSUCHOBJECT:
+ case SNMP_SYNTAX_NOSUCHINSTANCE:
+ case SNMP_SYNTAX_ENDOFMIBVIEW:
+ found = 0;
+ matched = 0;
+ break;
+
+ /* See if we have a match */
+ default:
+ if (item->query_match)
+ matched = snmp_engine_match (value, item->query_match);
+
+ /* When query match is null, anything matches */
+ else
+ matched = 1;
+
+ found = 1;
+ break;
+ };
+ }
+
+ /*
+ * When we had found this before, but then can no longer find it, we
+ * start search again from the base.
+ */
+ if (!matched && item->query_last != 0) {
+ item->query_last = 0;
+ query_request (item, 1);
+
+ /*
+ * When we find no value at zero, then we skip ahead and see if
+ * perhaps its a one based table
+ */
+ } else if (!found && item->query_value == 0) {
+ item->query_last = 0;
+ query_request (item, 0);
+
+ /*
+ * Any other time we don't find a value, its game over for us,
+ * we didn't find a match and are out of values.
+ */
+ } else if (!found) {
+ item->query_last = 0;
+ log_warn ("couldn't find match for query value: %s", item->query_match);
+ complete_request (item, SNMP_ERR_NOSUCHNAME);
+
+
+ /*
+ * Found a value but didn't match, so try next one.
+ */
+ } else if (!matched) {
+ item->query_last = 0;
+ query_request (item, 0);
+
+ /*
+ * When we have a match send off a new request, built from the original
+ * oid and the last numeric part of the query oid.
+ */
+ } else {
+
+ /* Build up the OID */
+ oid = item->field_oid;
+ ASSERT (oid.len < ASN_MAXOIDLEN);
+ oid.subs[oid.len] = item->query_value;
+ ++oid.len;
+
+ item->query_last = item->query_value;
+ item->vtype = VALUE_UNSET;
+
+ req = snmp_engine_request (item->hostnames[item->hostindex], item->community,
+ item->version, item->poller->interval, item->poller->timeout,
+ SNMP_PDU_GET, &oid, field_response, item);
+
+ item->request = req;
+ }
+}
+
+static void
+query_request (rb_item *item, int first)
+{
+ struct asn_oid oid;
+ int req;
+
+ ASSERT (item);
+ ASSERT (!item->request);
+ ASSERT (item->has_query);
+
+ item->vtype = VALUE_UNSET;
+
+ /*
+ * Build up an appropriate oid.
+ *
+ * We first try any oid that worked last time, and see if
+ * it still has the same value, to avoid doing the brute
+ * force search each time needlessly.
+ */
+
+ /* The first time the request has been called */
+ if (first)
+ item->query_value = item->query_last;
+
+ /* Try the next one in turn */
+ else
+ item->query_value = item->query_value + 1;
+
+ /* Build up the OID */
+ oid = item->query_oid;
+ ASSERT (oid.len < ASN_MAXOIDLEN);
+ oid.subs[oid.len] = item->query_value;
+ ++oid.len;
+
+ /* Make the request */
+ req = snmp_engine_request (item->hostnames[item->hostindex], item->community,
+ item->version, item->poller->interval, item->poller->timeout,
+ SNMP_PDU_GET, &oid, query_response, item);
+
+ /* Mark item as active by this request */
+ item->request = req;
+}
+
+static int
+poller_timer (mstime when, void *arg)
+{
+ rb_poller *poll = (rb_poller*)arg;
+ rb_item *item;
+
+ /*
+ * If the previous poll has not completed, then we count it
+ * as a timeout.
+ */
+ force_poll (poll, when, "timed out");
+
+ /* Mark this poller as starting requests now */
+ poll->last_request = when;
+
+ /*
+ * Send off the next query. This needs to be done after
+ * all the timeouts above, as the above could write to RRD.
+ */
+ for (item = poll->items; item; item = item->next) {
+ if (item->has_query)
+ query_request (item, 1);
+ else
+ field_request (item);
+ }
+
+ snmp_engine_flush ();
+
+ return 1;
+}
+
+static int
+prep_timer (mstime when, void* arg)
+{
+ /*
+ * We don't prepare all timers at exactly the same time
+ * but we sort of randomly start the various timers. We're
+ * going to be hitting these over and over again, so there's
+ * lots of benefits to spreading them out randomly over a
+ * few seconds.
+ */
+
+ rb_poller* poll;
+ int next;
+
+ /* All done? */
+ if(!arg)
+ return 0;
+
+ poll = (rb_poller*)arg;
+ if (server_timer (poll->interval, poller_timer, poll) == -1)
+ log_error ("couldn't setup poller timer");
+
+ /* Setup the next poller anywhere between 0 and 750 ms */
+ next = rand () % 750;
+ server_oneshot (next, prep_timer, poll->next);
+ return 0;
+}
+
+
+void
+rb_poll_engine_init (void)
+{
+ /* Start the preparation timers for setting up randomly */
+ if (server_oneshot (100, prep_timer, g_state.polls) == -1)
+ err(1, "couldn't setup timer");
+}
+
+void
+rb_poll_engine_uninit (void)
+{
+
+}