about summary refs log tree commit diff
path: root/scrape.c
diff options
context:
space:
mode:
Diffstat (limited to 'scrape.c')
-rw-r--r--scrape.c152
1 files changed, 152 insertions, 0 deletions
diff --git a/scrape.c b/scrape.c
new file mode 100644
index 0000000..33e22fc
--- /dev/null
+++ b/scrape.c
@@ -0,0 +1,152 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "scrape.h"
+#include "util.h"
+
+#define BUF_INITIAL 1024
+#define BUF_MAX 65536
+
+#define MAX_LISTEN_SOCKETS 4
+#define MAX_BACKLOG 16
+
+struct scrape_req {
+  int socket;
+  cbuf *buf;
+};
+
+bool scrape_serve(const char *port, scrape_handler *handler, void *handler_ctx) {
+  struct scrape_req req;
+
+  struct pollfd fds[MAX_LISTEN_SOCKETS];
+  nfds_t nfds = 0;
+
+  int ret;
+
+  {
+    struct addrinfo hints = {
+      .ai_family = AF_UNSPEC,
+      .ai_socktype = SOCK_STREAM,
+      .ai_protocol = 0,
+      .ai_flags = AI_PASSIVE | AI_ADDRCONFIG,
+    };
+    struct addrinfo *addrs;
+
+    ret = getaddrinfo(0, port, &hints, &addrs);
+    if (ret != 0) {
+      fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
+      return false;
+    }
+
+    for (struct addrinfo *a = addrs; a && nfds < MAX_LISTEN_SOCKETS; a = a->ai_next) {
+      int s = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
+      if (s == -1) {
+        perror("socket");
+        continue;
+      }
+
+      setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof (int));
+#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) && defined(AF_INET6)
+      if (a->ai_family == AF_INET6)
+        setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (int[]){1}, sizeof (int));
+#endif
+
+      ret = bind(s, a->ai_addr, a->ai_addrlen);
+      if (ret == -1) {
+        perror("bind");
+        close(s);
+        continue;
+      }
+
+      ret = listen(s, MAX_BACKLOG);
+      if (ret == -1) {
+        perror("listen");
+        close(s);
+        continue;
+      }
+
+      fds[nfds].fd = s;
+      fds[nfds].events = POLLIN;
+      nfds++;
+    }
+  }
+
+  if (nfds == 0) {
+    fprintf(stderr, "failed to bind any sockets");
+    return false;
+  }
+
+  req.buf = cbuf_alloc(BUF_INITIAL, BUF_MAX);
+  if (!req.buf) {
+    perror("cbuf_alloc");
+    for (nfds_t i = 0; i < nfds; i++)
+      close(fds[i].fd);
+    return false;
+  }
+
+  while (1) {
+    ret = poll(fds, nfds, -1);
+    if (ret == -1) {
+      perror("poll");
+      break;
+    }
+
+    for (nfds_t i = 0; i < nfds; i++) {
+      if (fds[i].revents == 0)
+        continue;
+      if (fds[i].revents != POLLIN) {
+        fprintf(stderr, "poll .revents = %d\n", fds[i].revents);
+        goto break_loop;
+      }
+
+      req.socket = accept(fds[i].fd, 0, 0);
+      if (req.socket == -1) {
+        perror("accept");
+        continue;
+      }
+
+      // TODO: HTTP stuff
+      handler(&req, handler_ctx);
+      close(req.socket);
+    }
+  }
+break_loop:
+
+  for (nfds_t i = 0; i < nfds; i++)
+    close(fds[i].fd);
+
+  return false;
+}
+
+void scrape_write(scrape_req *req, const char *metric, const char *(*labels)[2], double value) {
+  cbuf_reset(req->buf);
+
+  cbuf_puts(req->buf, metric);
+
+  if (labels && (*labels)[0]) {
+    cbuf_putc(req->buf, '{');
+    for (const char *(*l)[2] = labels; (*l)[0]; l++) {
+      if (l != labels)
+        cbuf_putc(req->buf, ',');
+      cbuf_putf(req->buf, "%s=\"%s\"", (*l)[0], (*l)[1]);
+    }
+    cbuf_putc(req->buf, '}');
+  }
+
+  cbuf_putf(req->buf, " %.16g\n", value);
+
+  size_t buf_len;
+  const char *buf = cbuf_get(req->buf, &buf_len);
+  write_all(req->socket, buf, buf_len);
+}
+
+void scrape_write_raw(scrape_req *req, const void *buf, size_t len) {
+  write_all(req->socket, buf, len);
+}