Initial Commit

This commit is contained in:
2026-04-18 04:42:13 -05:00
commit 3d13336d22
12 changed files with 908 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Editor directories
.ecode/
# Project files
build/
config.ini
messages

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "deps/vector"]
path = deps/vector
url = https://github.com/goldsborough/vector.git

17
CMakeLists.txt Normal file
View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.10)
project(stethoscope
VERSION 1.0
LANGUAGES C)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
endif()
include_directories(${CMAKE_BINARY_DIR} ${PROJECT_SOURCE_DIR})
add_subdirectory(src)
#file(COPY resources DESTINATION ${CMAKE_BINARY_DIR})
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)

35
config.ini.example Normal file
View File

@@ -0,0 +1,35 @@
## STETHOSCOPE EXAMPLE CONFIG
# The name of this Stethoscope instance. This name is used for the page title. (default: "My Stethoscope")
service_name=My Stethoscope
# Port to listen on. This should be reverse proxied by something like httpd or nginx. (default: 7800)
listen=7800
# Path of the webroot server uses to load asset paths like the stylesheet. (default: )
webroot=
# Time in seconds to wait before attempting to reconnect to the endpoint. (default: 5)
backoff=5
# These are example services.
;[service.openssh]
;name=OpenSSH Daemon
;address=tcp://localhost:22
;[service.nginx]
;name=Nginx
;address=tcp://localhost:80,tcp://localhost:443,process://nginx,pidfile:///run/nginx.pid
;backoff=1
;[service.email]
;name=Email
;address=tcp://localhost:25,tcp://localhost:143
;[service.tor]
;name=Tor
;address=tcp://127.0.0.1:9050
;[service.mumble]
;name=mumble
;address=tcp://localhost:64738,udp://localhost:64738

1
deps/vector vendored Submodule

Submodule deps/vector added at 40efe82256

5
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
add_executable(${PROJECT_NAME}
main.c
../deps/vector/vector.c
)
target_link_libraries(${PROJECT_NAME} PUBLIC microhttpd pthread inih mxml)

183
src/main.c Normal file
View File

@@ -0,0 +1,183 @@
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <pthread.h>
#include <ini.h>
#include "main.h"
#define CONFIG_SERVICE_PREFIX "service."
static void signal_handler(int signo) {
if (signo == SIGINT || signo == SIGKILL) {
running = 0;
} else if (signo == SIGUSR1) {
printf("USR1 signal detected! Not doing anything for now.\n");
}
}
// Only used to construct basic variables of a service before initialization
struct PreService {
char* id;
char* name;
char* text;
char* endpoints;
uint32_t backoff;
};
static int config_handler(void* user, const char* section, const char* name, const char* value) {
if (strcmp(section, "") == 0) {
if (strcmp(name, "service_name") == 0) {
daemon_name = malloc(strlen(value)+1);
strcpy(daemon_name, value);
} else if (strcmp(name, "webroot") == 0) {
webroot = malloc(strlen(value)+1);
strcpy(webroot, value);
} else if (strcmp(name, "listen") == 0) {
listen_port = atoi(value);
} else if (strcmp(name, "backoff") == 0) {
global_backoff = atoi(value);
} else {
printf("Unknown configuration entry \'%s\'\n", name);
}
} else if (strncmp(CONFIG_SERVICE_PREFIX, section, strlen(CONFIG_SERVICE_PREFIX)) == 0) {
const char* id = section+strlen(CONFIG_SERVICE_PREFIX);
Vector* tmp_services = (Vector*)user;
struct PreService* current_service = NULL;
Iterator it = vector_begin(tmp_services);
Iterator end = vector_end(tmp_services);
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct PreService* current = *(struct PreService**)iterator_get(&it);
if (strcmp(id, current->id) == 0) {
current_service = current;
break;
}
}
if (current_service == NULL) {
current_service = malloc(4096);
current_service->id = malloc(strlen(id)+1);
strcpy(current_service->id, id);
current_service->backoff = 0;
vector_push_back(tmp_services, &current_service);
}
if (strcmp(name, "name") == 0) {
current_service->name = malloc(strlen(value)+1);
strcpy(current_service->name, value);
} else if (strcmp(name, "text") == 0) {
current_service->text = malloc(strlen(value)+1);
strcpy(current_service->text, value);
} else if (strcmp(name, "address") == 0) {
current_service->endpoints = malloc(strlen(value)+1);
strcpy(current_service->endpoints, value);
} else if (strcmp(name, "backoff") == 0) {
current_service->backoff = atoi(value);
} else {
printf("Unknown configuration entry \'%s\' in section \'%s\'\n", name, id);
}
} else {
printf("Unknown configuration section \'%s\'\n", section);
}
fflush(stdout);
return 1;
}
static void init_signals() {
struct sigaction sa = {};
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGKILL, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
}
int main(int argc, char** argv) {
printf("Starting Stethoscope...\n");
fflush(stdout);
init_signals();
vector_setup(&status_timeline, 100, sizeof(struct StatusPeroid*));
char* config_path;
for (int c = 1; c < argc; c++) {
if (strcmp(argv[c], "-c") == 0) {
if (argc > c) {
config_path = argv[c+1];
c++;
break;
}
}
goto broken_args;
}
if (argc <= 1) {
broken_args:
fprintf(stderr, "error: invalid number of arguments\nexample: %s -c <file>\n", argv[0]);
fflush(stderr);
return 3;
}
// Load configuration file
Vector tmp_services;
vector_setup(&tmp_services, 255, sizeof(struct PreService*));
printf("Loading configuration file: %s\n", config_path);
int err = ini_parse(config_path, config_handler, &tmp_services);
if (err < 0) {
fprintf(stderr, "Failed to load configuration file: %i\n", err);
return 1;
}
// initialize Services from temp vector
vector_setup(&services, 255, sizeof(struct Service*));
Iterator it = vector_begin(&tmp_services);
Iterator end = vector_end(&tmp_services);
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct PreService* preservice = *(struct PreService**)iterator_get(&it);
printf("Initializing service \"%s\"\n", preservice->name);
fflush(stdout);
struct Service* service = malloc(8192);
service->id = preservice->id;
service->name = preservice->name;
service->text = preservice->text;
service->last_state = DOWN;
service->backoff = preservice->backoff == 0 ? global_backoff : preservice->backoff;
char* endpoints = malloc(strlen(preservice->endpoints)+1);
strcpy(endpoints, preservice->endpoints);
char* token = strtok(endpoints, ",");
vector_setup(&service->endpoints, 255, sizeof(struct Endpoint*));
while (token != NULL) {
add_endpoint(service, token);
fflush(stdout);
token = strtok(NULL, ",");
}
vector_push_back(&services, &service);
load_service(&service);
}
vector_resize(&services, services.size);
vector_clear(&tmp_services);
vector_destroy(&tmp_services);
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD, listen_port, NULL, NULL, &http_response, NULL, MHD_OPTION_END);
if (daemon == NULL) {
fprintf(stderr, "Failed to start HTTP daemon\n");
return 2;
}
running = 1;
while (daemon != NULL && running == 1) {
if (report_queued > 0) {
report_queued = 0;
report();
} else sleep(global_backoff / 2);
}
printf("\nShutting down services...\n");
fflush(stdout);
MHD_stop_daemon(daemon);
return 0;
}

213
src/main.h Normal file
View File

@@ -0,0 +1,213 @@
#ifndef H_MAIN
#define H_MAIN
#include <linux/limits.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <microhttpd.h>
#include "../deps/vector/vector.h"
#include "mxml.h"
#include "util.h"
#include "services.c"
#define PAGE_INDEX 0
#define PAGE_ASSET 1
#define PAGE_ERROR 2
uint8_t running = 0;
char* daemon_name = "Stethoscope";
uint16_t listen_port = 7800;
char* webroot = "share/";
uint32_t global_backoff = 5;
char* cached_index;
int get_asset(char** buffer, const char* path, size_t* size) {
*size = 0;
if (strlen(webroot) + strlen(path) + 2 > PATH_MAX)
return 403;
char full_path[PATH_MAX];
snprintf(full_path, sizeof(full_path), "%s%s", webroot, path);
char resolved_path[PATH_MAX];
if (realpath(full_path, resolved_path) == NULL)
return 403;
char resolved_base[PATH_MAX];
if (realpath(webroot, resolved_base) == NULL)
return 403;
size_t resolved_base_len = strlen(resolved_base);
if (strncmp(resolved_path, resolved_base, resolved_base_len) != 0)
return 400;
if (resolved_path[resolved_base_len] != '\0' && resolved_path[resolved_base_len] != '/')
return 400;
FILE* asset = fopen(resolved_path, "rb");
if (!asset) return 404;
fseek(asset, 0, SEEK_END);
*size = ftell(asset);
rewind(asset);
*buffer = (char*)malloc(*size + 1);
if (!*buffer) {
fclose(asset);
*size = 0;
return 500;
}
size_t bytes_read = fread(*buffer, 1, *size, asset);
fclose(asset);
if (bytes_read != *size) {
free(*buffer);
*size = 0;
return 500;
}
buffer[*size] = 0;
return 200;
};
struct XMLServiceRow {
struct Service* service;
mxml_node_t* row;
};
static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, const char* uri, const char* method, const char* version, const char* upload_Data, size_t* upload_data_size, void** req_cls) {
const union MHD_ConnectionInfo *info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
const char *client_ip = {};
uint16_t client_port = 0;
if (info != NULL) {
const struct sockaddr *addr = info->client_addr;
char ip_str[INET6_ADDRSTRLEN];
if (addr->sa_family == AF_INET) {
const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
client_ip = inet_ntop(AF_INET, &addr4->sin_addr, ip_str, sizeof(ip_str));
client_port = ntohs(addr4->sin_port);
} else if (addr->sa_family == AF_INET6) {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
client_ip = inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, sizeof(ip_str));
client_port = ntohs(addr6->sin6_port);
}
} else client_ip = "?.?.?.?";
uint16_t response_code = 0;
char* page;
size_t page_size = 0;
if (strcmp(method, "GET") == 0) {
if (strcmp(uri, "/") != 0)
response_code = get_asset(&page, uri, &page_size);
} else response_code = 400;
if (response_code != 200) {
mxml_node_t* doc = mxmlNewXML("1.0");
mxml_node_t* html = mxmlNewElement(doc, "html");
mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml");
mxmlElementSetAttr(html, "xml:lang", "en");
mxmlElementSetAttr(html, "lang", "en");
mxml_node_t* head = mxmlNewElement(html, "head");
mxml_node_t *title = mxmlNewElement(head, "title");
mxmlNewText(title, 0, daemon_name);
mxml_node_t* meta_charset = mxmlNewElement(head, "meta");
mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type");
mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8");
mxml_node_t* meta_viewport = mxmlNewElement(head, "meta");
mxmlElementSetAttr(meta_viewport, "name", "viewport");
mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0");
mxml_node_t* stylesheet = mxmlNewElement(head, "link");
mxmlElementSetAttr(stylesheet, "rel", "stylesheet");
mxmlElementSetAttr(stylesheet, "href", "style.css");
mxml_node_t* body = mxmlNewElement(html, "body");
mxml_node_t* center = mxmlNewElement(body, "center");
if (strcmp(uri, "/") == 0 && response_code == 0) {
mxml_node_t* header = mxmlNewElement(center, "h1");
mxmlNewText(header, 0, daemon_name);
int row_index = 0;
struct XMLServiceRow table_rows[services.size];
Iterator it = vector_begin(&services);
Iterator end = vector_end(&services);
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct Service* service = *(struct Service**)iterator_get(&it);
mxml_node_t* table = mxmlNewElement(center, "table");
mxmlElementSetAttr(table, "width", "100%");
mxml_node_t* header_row = mxmlNewElement(table, "tr");
mxml_node_t* header_cell = mxmlNewElement(table, "th");
mxmlElementSetAttr(header_cell, "align", "left");
mxmlNewText(header_cell, 0, service->name);
mxml_node_t* status_row = mxmlNewElement(table, "tr");
table_rows[row_index] = (struct XMLServiceRow){ service, status_row };
row_index++;
}
Iterator pit = vector_begin(&status_timeline);
Iterator pend = vector_end(&status_timeline);
for (; !iterator_equals(&pit, &pend); iterator_increment(&pit)) {
struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&pit);
Iterator sit = vector_begin(&peroid->server_statuses);
Iterator send = vector_end(&peroid->server_statuses);
for (; !iterator_equals(&sit, &send); iterator_increment(&sit)) {
struct ServerStatus* srvstat = *(struct ServerStatus**)iterator_get(&sit);
struct XMLServiceRow row_struct;
for (int x = 0; x < services.size; x++)
if (table_rows[x].service == srvstat->service) {
row_struct = table_rows[x];
break;
}
mxml_node_t* cell = mxmlNewElement(row_struct.row, "td");
mxmlElementSetAttr(cell, "_status", state_text(srvstat->state));
char* ping_str = (char*)malloc(16);
snprintf(ping_str, 16, "%f ms", srvstat->ping);
mxmlElementSetAttr(cell, "_ping", ping_str);
}
}
response_code = 200;
} else {
mxml_node_t* error_header = mxmlNewElement(center, "h1");
char* err_str = (char*)malloc(5);
snprintf(err_str, 5, "%i", response_code);
mxmlNewText(error_header, 0, err_str);
}
page = mxmlSaveAllocString(doc, MXML_NO_CALLBACK);
page_size = strlen(page);
}
printf("[%lu] %s %s \"%s\" %s:%u %zu %zu %i\n", time(NULL), method, uri, version, client_ip, client_port, *upload_data_size, page_size, response_code);
fflush(stdout);
struct MHD_Response* response = MHD_create_response_from_buffer_static(page_size, page);
enum MHD_Result ret = MHD_queue_response(conn, response_code, response);
MHD_destroy_response(response);
return ret;
};
#endif

347
src/services.c Normal file
View File

@@ -0,0 +1,347 @@
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include "services.h"
#ifndef _GNU_SOURCE
#define POLLRDHUP 0x2000
#endif
uint8_t report_queued = 0;
uint64_t queued_time = 0;
Vector services;
Vector status_timeline; // of StatusPeroid*
void* run_endpoint(void* endpoint) {
struct Endpoint* self = (struct Endpoint*)endpoint;
struct timespec start, end;
while (self->enabled) {
int success = 0;
if (strcmp(self->scheme, "tcp") == 0 || strcmp(self->scheme, "udp") == 0) {
if (self->sockfd < 0) {
struct addrinfo hints = {0}, *result, *rp;
hints.ai_family = AF_UNSPEC;
if (strcmp(self->scheme, "udp") == 0) {
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
} else {
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
}
char port_str[6];
snprintf(port_str, sizeof(port_str), "%d", self->port);
int ret = getaddrinfo(self->target, port_str, &hints, &result);
if (ret != 0) {
fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(ret));
queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
sleep(self->backoff);
continue;
}
self->sockfd = -1;
for (rp = result; rp != NULL; rp = rp->ai_next) {
int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) continue;
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (strcmp(self->scheme, "tcp") == 0) {
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
int keepidle = 0;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
int keepintvl = self->backoff;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
int keepcnt = 1;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
}
struct timespec connect_start;
clock_gettime(CLOCK_REALTIME_ALARM, &connect_start);
ret = connect(fd, rp->ai_addr, rp->ai_addrlen);
if (strcmp(self->scheme, "tcp") == 0) {
if (errno == EINPROGRESS) {
struct pollfd pfd = { .fd = fd, .events = POLLOUT };
int pollret = poll(&pfd, 1, 5000);
if (pollret > 0 && (pfd.revents & POLLOUT)) {
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) != 0 && error != 0) {
close(fd);
continue;
}
}
}
} else {
char* dummy = 0;
ssize_t sent = send(fd, &dummy, 1, MSG_DONTWAIT | MSG_NOSIGNAL);
if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
close(fd);
continue;
}
usleep(10000);
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) != 0 || error != 0) {
close(fd);
continue;
}
struct pollfd pfd = { .fd = fd, .events = POLLIN | POLLERR };
if (poll(&pfd, 1, (self->backoff > 0 ? self->backoff * 1000 : 10000)) > 0 && (pfd.revents & (POLLERR | POLLIN))) {
if (recv(fd, NULL, sizeof(NULL), MSG_DONTWAIT) < 0) {
self->last_errno = errno;
if (self->last_errno == ECONNREFUSED || self->last_errno == EHOSTUNREACH || self->last_errno == ENETUNREACH) {
close(fd);
continue;
}
}
}
}
struct timespec now;
clock_gettime(CLOCK_REALTIME_ALARM, &now);
double ping = (now.tv_sec - connect_start.tv_sec) * 1000.0 + (now.tv_nsec - connect_start.tv_nsec) / 1000000.0;
self->sockfd = fd;
self->last_errno = 0;
queue_report((struct EndpointStatus){ self, UP, ping });
}
freeaddrinfo(result);
if (self->sockfd < 0) {
if (self->last_errno != errno) {
self->last_errno = errno;
fprintf(stderr, "%s://%s:%u connect failed: %i\n", self->scheme, self->target, self->port, errno);
}
queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
sleep(self->backoff);
continue;
}
}
struct pollfd pfd = { .fd = self->sockfd, .events = POLLERR | POLLHUP | POLLRDHUP };
int pollret = poll(&pfd, 1, (self->backoff > 0 ? self->backoff * 1000 : 10000));
if (pollret < 0) {
close(self->sockfd);
self->sockfd = -1;
continue;
}
if (pfd.revents & (POLLERR | POLLHUP | POLLRDHUP)) {
close(self->sockfd);
self->sockfd = -1;
//queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
continue;
}
if (strcmp(self->scheme, "udp") == 0) {
if (send(self->sockfd, NULL, sizeof(NULL), 0) < 0) {
close(self->sockfd);
self->sockfd = -1;
continue;
}
}
if (pollret == 0) {
time_t now = time(NULL);
if (now - self->last_check >= self->backoff) {
self->last_check = now;
queue_report((struct EndpointStatus){ self, UP, self->last_ping });
}
continue;
}
queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
}
}
if (self->sockfd >= 0) {
close(self->sockfd);
self->sockfd = -1;
}
return NULL;
}
void queue_report(struct EndpointStatus status) {
status.endpoint->last_ping = status.ping;
if (status.endpoint->last_state == status.state) return;
status.endpoint->last_state = status.state;
uint64_t time = microtime();
printf("[%lu] State of %s://%s:%u is %s\n", (time / 1000000), status.endpoint->scheme, status.endpoint->target, status.endpoint->port, state_text(status.state));
fflush(stdout);
queued_time = time;
report_queued = 1;
}
void load_service(struct Service** self) {
Iterator it = vector_begin(&(*self)->endpoints);
Iterator end = vector_end(&(*self)->endpoints);
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct Endpoint* endpoint = *(struct Endpoint**)iterator_get(&it);
pthread_create(&endpoint->thread, NULL, &run_endpoint, (void *)endpoint);
}
}
uint8_t add_endpoint(struct Service* self, const char* uri) {
if (!uri || !*uri) return 255;
struct Endpoint* endpoint = (struct Endpoint*)malloc(2048);
endpoint->service = self;
endpoint->enabled = true;
endpoint->sockfd = -1;
endpoint->backoff = self->backoff;
const char* p = uri;
const char* colon = strchr(p, ':');
if (!colon) {
fprintf(stderr, "Target URI is malformed: %s\n", p);
return 1;
}
size_t scheme_len = colon - p;
endpoint->scheme = (char*)malloc(scheme_len+1);
if (endpoint->scheme) {
strncpy(endpoint->scheme, p, scheme_len);
endpoint->scheme[scheme_len] = 0;
for (int i = 0; i < scheme_len; i++)
endpoint->scheme[i] = (char)tolower(endpoint->scheme[i]);
}
p = colon + 1;
if (!(*p == '/' && *(p+1) == '/')) {
fprintf(stderr, "Target URI is malformed: %s\n", p);
return 1;
}
p += 2;
const char* addr_start = p;
const char* addr_end = NULL;
if (*p == '[') {
++p;
addr_start = p;
addr_end = strchr(p, ']');
if (addr_end)
p = addr_end + 1;
else {
fprintf(stderr, "Target URI is malformed: Unable to find end of IPv6 bracket in \"%s\"\n", addr_start);
return 1;
}
}
if (!addr_end) {
while (*p && *p != ':' && *p != '?' && *p != '#') ++p;
addr_end = p;
}
size_t addr_len = addr_end - addr_start;
endpoint->target = (char*)malloc(addr_len+1);
if (endpoint->target) {
strncpy(endpoint->target, addr_start, addr_len);
endpoint->target[addr_len] = 0;
}
endpoint->port = 0;
if (*p == ':') {
++p;
endpoint->port = (int)strtol(p, NULL, 10);
}
printf("[%s] Added endpoint to service: %s, %s, %i\n", self->id, endpoint->scheme, endpoint->target, endpoint->port);
vector_push_back(&self->endpoints, &endpoint);
return 0;
}
enum State get_state(struct Service** self) {
Iterator it = vector_begin(&(*self)->endpoints);
Iterator end = vector_end(&(*self)->endpoints);
int up_count = 0; int total_count = (*self)->endpoints.size;
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct Endpoint* endpoint = *(struct Endpoint**)iterator_get(&it);
if (endpoint->last_state == UP)
up_count++;
}
if (up_count == total_count) return UP;
else if (up_count == 0) return DOWN;
else return PARTIAL;
}
void report() {
if (status_timeline.size + 1 >= status_timeline.capacity)
vector_pop_front(&status_timeline); // remove (what should be) oldest entry
struct StatusPeroid* peroid = malloc(512);
peroid->time = queued_time;
vector_setup(&peroid->server_statuses, services.size, sizeof(struct ServerStatus*));
Iterator it = vector_begin(&services);
Iterator end = vector_end(&services);
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct Service* service = *(struct Service**)iterator_get(&it);
struct ServerStatus* server_status = update_server_status(&service);
vector_push_back(&peroid->server_statuses, &server_status);
}
vector_push_back(&status_timeline, &peroid);
}
struct ServerStatus* update_server_status(struct Service** self) {
struct Service* service = *self;
double pings[service->endpoints.size] = {};
struct ServerStatus* server_status = malloc(512);
server_status->service = service;
enum State service_state = DOWN;
vector_setup(&server_status->endpoint_statuses, service->endpoints.size, sizeof(struct EndpointStatus));
int index = 0;
Iterator it = vector_begin(&service->endpoints);
Iterator end = vector_end(&service->endpoints);
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
struct Endpoint* endpoint = *(struct Endpoint**)iterator_get(&it);
struct EndpointStatus estatus;
estatus.endpoint = endpoint;
estatus.state = endpoint->last_state;
pings[index] = estatus.ping = endpoint->last_ping;
vector_push_back(&server_status->endpoint_statuses, &estatus);
if (index == 0) service_state = endpoint->last_state;
else if ((endpoint->last_state == DOWN && service_state == UP) || (endpoint->last_state == UP && service_state == DOWN))
service_state = PARTIAL;
index++;
}
size_t pings_len = index;
double ping_sum = 0;
for (int i = 0; i < sizeof(pings)/sizeof(double); i++)
if (pings[i] >= 0) {
ping_sum+=pings[i];
pings_len--;
}
double avg_ping = ping_sum / pings_len;
server_status->state = service_state;
server_status->ping = avg_ping;
return server_status;
}
const char* state_text(enum State state) {
if (state == UP)
return "UP";
else if (state == PARTIAL)
return "PARTIAL";
else return "DOWN";
}

80
src/services.h Normal file
View File

@@ -0,0 +1,80 @@
#ifndef H_SERVICES
#define H_SERVICES
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <string.h>
#include <pthread.h>
#include "../deps/vector/vector.h"
#include "util.h"
enum State { DOWN, PARTIAL, UP };
struct StatusPeroid;
struct ServerStatus;
struct EndpointStatus;
struct Endpoint;
struct Service;
void report();
void* run_endpoint(void* endpoint);
void queue_report(struct EndpointStatus status);
void load_service(struct Service** self);
uint8_t add_endpoint(struct Service* self, const char* uri);
enum State get_state(struct Service** self);
struct ServerStatus* update_server_status(struct Service** self);
const char* state_text(enum State state);
struct StatusPeroid {
unsigned long time;
Vector server_statuses; // of ServerStatus*
};
struct ServerStatus {
struct Service* service;
Vector endpoint_statuses; // of EndpointStatus*
enum State state;
double ping;
};
struct EndpointStatus {
struct Endpoint* endpoint;
enum State state;
double ping;
};
struct Endpoint {
struct Service* service;
uint8_t enabled;
uint8_t running;
char* scheme;
char* target;
uint16_t port;
uint32_t backoff;
time_t last_check;
enum State last_state;
double last_ping;
Vector status_history;
pthread_t thread;
int sockfd;
int last_errno;
};
struct Service {
char* id;
char* name;
char* text;
Vector endpoints;
enum State last_state;
double average_ping;
uint32_t backoff;
};
#endif

14
src/util.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef H_UTIL
#define H_UTIL
#include <stddef.h>
#include <stdint.h>
#include <sys/select.h>
#include <sys/time.h>
static uint64_t microtime() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
};
#endif

3
www/style.css Normal file
View File

@@ -0,0 +1,3 @@
td[_status=UP] { background-color: green; }
td[_status=PARTIAL] { background-color: yellow; }
td[_status=DOWN] { background-color: red; }