From 52919f767cade4ad60ce92028c26e5652dd952c0 Mon Sep 17 00:00:00 2001 From: Florian Obser Date: Mon, 7 May 2018 14:54:39 +0200 Subject: [PATCH] Initial commit --- Makefile | 21 +++ httpd.conf | 8 + kmunin_exporter.c | 458 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 487 insertions(+) create mode 100644 Makefile create mode 100644 httpd.conf create mode 100644 kmunin_exporter.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..914e7db --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +BINDIR= /var/www/metrics + +PROG= kmunin_exporter +SRCS= kmunin_exporter.c + +LDSTATIC= ${STATIC} +NOMAN= YES + +#DEBUG= -g -DDEBUG=3 -O0 + +CFLAGS+= -Wall +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -I/usr/local/include +LDADD+= -L/usr/local/lib -lkcgihtml -lkcgi -lz +LDADD+= -levent +DPADD+= ${LIBEVENT} + +.include diff --git a/httpd.conf b/httpd.conf new file mode 100644 index 0000000..fc06076 --- /dev/null +++ b/httpd.conf @@ -0,0 +1,8 @@ +# example httpd.conf snipped +server "kmuninexporter" { + listen on localhost port 4848 + location "/metrics/*" { + fastcgi + root "/" + } +} diff --git a/kmunin_exporter.c b/kmunin_exporter.c new file mode 100644 index 0000000..8e227d4 --- /dev/null +++ b/kmunin_exporter.c @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2018 Florian Obser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define WAIT_CONNECT 1 +#define WAIT_CONNECT_SUCCEED 2 +#define WAIT_BANNER 3 +#define WRITE_LIST 4 +#define WAIT_LIST 5 +#define FETCH_ELEMENT 6 +#define WAIT_FETCHED_DATA 7 + +#define BANNER_PREFIX "# munin node at " + +struct munin { + SLIST_ENTRY(munin) entry; + struct event ev; + struct kreq *r; + struct sockaddr_storage ss; + int s; + int state; + char hbuf[NI_MAXHOST]; + char *data; + ssize_t write_size; + char *hostname; + char *list; + char *current_metric; +}; + +SLIST_HEAD(munin_head, munin); + +struct munin_head munins; + +void write_handler(int, short, void *); +void read_handler(int, short, void *); +void export(struct munin *); + +void khttp_log(struct kreq *, char *, ...); + +void +khttp_log(struct kreq *r, char *fmt, ...) +{ + va_list ap; + int ret; + char *str; + + va_start(ap, fmt); + ret = vasprintf(&str, fmt, ap); + va_end(ap); + + if (ret >= 0) { + khttp_puts(r, "# "); + khttp_puts(r, str); + khttp_putc(r, '\n'); + } + free(str); +} + +void +export(struct munin *munin) +{ + + char *d, *line, *k, *v, *str; + + d = munin->data; + + while((line = strsep(&d, "\n")) != NULL) { + if (*line == '\0') + continue; /* two adjacent delimiters */ + if (*line == '#') { + asprintf(&str, "# skipping %s metric comment: %s\n", + munin->current_metric, line); + khttp_puts(munin->r, str); + free(str); + continue; + } + + if (line[0] == '.' && line[1] == '\0') + break; /* munin EOF marker */ + + if ((v = strchr(line, ' ')) == NULL) { + asprintf(&str, "# skipping malformed line: %s\n", line); + khttp_puts(munin->r, str); + free(str); + continue; + } else + *v++ = '\0'; + + if ((k = strstr(line, ".value")) != NULL) + *k = '\0'; + + k = line; + + asprintf(&str, + "# TYPE %s_%s counter\n%s_%s{hostname=\"%s\"} %s\n", + munin->current_metric, k, + munin->current_metric, k, + munin->hostname, v); + khttp_puts(munin->r, str); + free(str); + } + +} + +void +read_handler(int fd, short events, void *arg) +{ + struct munin *munin, *i, *t; + ssize_t n; + char buf[4096], *tmp, *str; + + munin = (struct munin*)arg; + + n = read(fd, buf, sizeof(buf) - 1); + switch (n) { + case -1: + switch (errno) { + case EINTR: + case EAGAIN: + return; + default: + asprintf(&str, "# read: %s\n", strerror(errno)); + khttp_puts(munin->r, str); + free(str); + goto fail; + } + break; + case 0: + asprintf(&str, "# read: closed connection\n"); + khttp_puts(munin->r, str); + free(str); + goto fail; + default: + break; + } + + buf[n] = '\0'; + + if (munin->data == NULL) + munin->data = strdup(buf); + else { + tmp = munin->data; + asprintf(&munin->data, "%s%s", munin->data, buf); + free(tmp); + } + + switch (munin->state) { + case WAIT_CONNECT_SUCCEED: + munin->state = WAIT_BANNER; + /* connection succeeded, canceling others */ + SLIST_FOREACH_SAFE(i, &munins, entry, t) { + if (i != munin) { + event_del(&i->ev); + close(i->s); + SLIST_REMOVE(&munins, i, munin, entry); + free(i); + } + } + + /* fall through */ + case WAIT_BANNER: + if ((tmp = strchr(munin->data, '\n')) != NULL) { + *tmp = '\0'; + if (strncmp(BANNER_PREFIX, munin->data, + sizeof(BANNER_PREFIX) - 1) == 0) { + munin->hostname = strdup(munin->data + + (sizeof(BANNER_PREFIX) - 1)); + asprintf(&str, "#hostname: %s\n", + munin->hostname); + khttp_puts(munin->r, str); + free(str); + } + free(munin->data); + munin->data = strdup("list\n"); + munin->write_size = sizeof("list\n") - 1; + munin->state = WRITE_LIST; + event_del(&munin->ev); + event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, + write_handler, munin); + event_add(&munin->ev, NULL); + + } + break; + case WAIT_LIST: + /* XXX ignore comment lines */ + if ((tmp = strchr(munin->data, '\n')) != NULL) { + *tmp = '\0'; + munin->list = strdup(munin->data); + munin->state = FETCH_ELEMENT; + event_del(&munin->ev); + event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, + write_handler, munin); + event_add(&munin->ev, NULL); + } + break; + case WAIT_FETCHED_DATA: + if ((munin->data[0] == '.' && munin->data[1] == '\n') + || strstr(munin->data, "\n.\n") != NULL) { + export(munin); + free(munin->data); + free(munin->current_metric); + munin->state = FETCH_ELEMENT; + event_del(&munin->ev); + event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, + write_handler, munin); + event_add(&munin->ev, NULL); + } + break; + default: + asprintf(&str, "# %s: unknown state: %d\n", __func__, + munin->state); + khttp_puts(munin->r, str); + free(str); + exit(1); + break; + } + return; +fail: + event_del(&munin->ev); + close(fd); + SLIST_REMOVE(&munins, munin, munin, entry); + free(munin); +} + +void +write_handler(int fd, short events, void *arg) +{ + struct munin *munin; + int error = 0; + socklen_t len = sizeof(error); + char *tmp, *str; + + munin = (struct munin*)arg; + + switch (munin->state) { + case WAIT_CONNECT: + if (getsockopt(munin->s, SOL_SOCKET, SO_ERROR, &error, &len) + < 0) { + asprintf(&str, "# getsockopt: %s\n", strerror(errno)); + khttp_puts(munin->r, str); + free(str); + goto fail; + } + if (error != 0) { + errno = error; + asprintf(&str, "# connect: %s\n", strerror(errno)); + khttp_puts(munin->r, str); + free(str); + goto fail; + } + event_del(&munin->ev); + + munin->state = WAIT_CONNECT_SUCCEED; + event_set(&munin->ev, munin->s, EV_READ | EV_PERSIST, + read_handler, munin); + event_add(&munin->ev, NULL); + break; + case WRITE_LIST: + if (write(fd, munin->data, munin->write_size) != + munin->write_size) { + asprintf(&str, "# write: %s\n", strerror(errno)); + khttp_puts(munin->r, str); + free(str); + goto fail; + } + free(munin->data); + munin->data = NULL; + munin->write_size = -1; + event_del(&munin->ev); + + munin->state = WAIT_LIST; + event_set(&munin->ev, munin->s, EV_READ | EV_PERSIST, + read_handler, munin); + event_add(&munin->ev, NULL); + break; + case FETCH_ELEMENT: + tmp = strsep(&munin->list, " \t"); + if (tmp == NULL) { + free(munin->list); + goto fail; // XXX this is success, we are done! + } else if (tmp == '\0') + break; /* two adjacent delimiters */ + + munin->current_metric = strdup(tmp); + munin->write_size = asprintf(&munin->data, "fetch %s\n", + munin->current_metric); + + if (write(fd, munin->data, munin->write_size) != + munin->write_size) { + asprintf(&str, "# write: %s\n", strerror(errno)); + khttp_puts(munin->r, str); + free(str); + goto fail; + } + + free(munin->data); + munin->data = NULL; + munin->write_size = -1; + event_del(&munin->ev); + + munin->state = WAIT_FETCHED_DATA; + event_set(&munin->ev, munin->s, EV_READ | EV_PERSIST, + read_handler, munin); + event_add(&munin->ev, NULL); + break; + + default: + asprintf(&str, "# %s: unknown state: %d\n", __func__, + munin->state); + khttp_puts(munin->r, str); + free(str); + exit(1); + break; + } + return; +fail: + event_del(&munin->ev); + close(fd); + SLIST_REMOVE(&munins, munin, munin, entry); + free(munin); +} + +int +main(int argc, char **argv) +{ + struct kreq r; + + struct addrinfo hints, *res, *res0; + struct munin *munin; + int error; + + char *host = NULL; + + if (pledge("stdio dns inet proc", "") == -1) + err(1, "pledge"); + + if (KCGI_OK != khttp_parse(&r, NULL, 0, NULL, 0, 0)) + return(EXIT_FAILURE); + + if (pledge("stdio dns inet", "") == -1) + err(1, "pledge"); + + if (r.fullpath != NULL) { + if (r.fullpath[0] == '/') + host = r.fullpath + 1; + else + host = r.fullpath; + } else { + khttp_head(&r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_500]); + khttp_head(&r, kresps[KRESP_CONTENT_TYPE], "%s", "text/plain"); + khttp_body(&r); + + khttp_puts(&r, "# missing host\n"); + khttp_free(&r); + return (EXIT_SUCCESS); + } + + SLIST_INIT(&munins); + event_init(); + + khttp_head(&r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_head(&r, kresps[KRESP_CONTENT_TYPE], "%s", "text/plain"); + khttp_body(&r); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo(host, "4949", &hints, &res0); + if (error) { + khttp_log(&r, "getaddrinfo: %s", gai_strerror(error)); + goto out; + } + + for (res = res0; res; res = res->ai_next) { + if ((munin = calloc(1, sizeof(*munin))) == NULL) { + khttp_log(&r, "malloc: %s", strerror(errno)); + goto out; + } + error = getnameinfo(res->ai_addr, res->ai_addr->sa_len, + munin->hbuf, sizeof(munin->hbuf), NULL, 0, NI_NUMERICHOST | + NI_NUMERICSERV); + if (error) { + khttp_log(&r, "could not get numeric hostname: %s", + gai_strerror(error)); + goto out; + } + + memcpy(&munin->ss, res->ai_addr, res->ai_addrlen); + + if ((munin->s = socket(res->ai_family, res->ai_socktype | + SOCK_NONBLOCK, res->ai_protocol)) == -1) { + khttp_log(&r, "socket: %s", strerror(errno)); + goto out; + } + munin->r = &r; + munin->state = WAIT_CONNECT; + event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, + write_handler, munin); + event_add(&munin->ev, NULL); + SLIST_INSERT_HEAD(&munins, munin, entry); + } + freeaddrinfo(res0); + + if (pledge("stdio inet", "") == -1) + err(1, "pledge"); + + SLIST_FOREACH(munin, &munins, entry) { + if (connect(munin->s, (struct sockaddr *)&munin->ss, + munin->ss.ss_len) == -1) + if (errno != EINPROGRESS) { + khttp_log(&r, "connect: %s", strerror(errno)); + goto out; + } + } + + if (pledge("stdio", "") == -1) + err(1, "pledge"); + + event_dispatch(); + + khttp_puts(&r, "# OK\n"); +out: + khttp_free(&r); + + return (EXIT_SUCCESS); +}