shoreline/main.c
Tobias Schramm c152c25e3b statistics: Switch to unsigned long long for value storage
This ensures formatting can always be habdled through %llu
2020-01-06 11:57:12 +01:00

472 lines
13 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <getopt.h>
#include <netdb.h>
#include <time.h>
#include "framebuffer.h"
#ifdef FEATURE_SDL
#include "sdl.h"
#endif
#include "network.h"
#include "llist.h"
#include "util.h"
#include "frontend.h"
#include "workqueue.h"
#ifdef FEATURE_TTF
#include "textrender.h"
#endif
#ifdef FEATURE_STATISTICS
#include "statistics.h"
#endif
#define PORT_DEFAULT "1234"
#define LISTEN_DEFAULT "::"
#define RATE_DEFAULT 60
#define WIDTH_DEFAULT 1024
#define HEIGHT_DEFAULT 768
#define RINGBUFFER_DEFAULT 65536
#define LISTEN_THREADS_DEFAULT 10
#define MAX_STAT_LENGTH 265
#define MAX_FRONTENDS 16
#define REPO_URL "https://github.com/TobleMiner/shoreline"
static char* default_description = REPO_URL;
static bool do_exit = false;
extern struct frontend_id frontends[];
void show_frontends() {
fprintf(stderr, "Available frontends:\n");
struct frontend_id* front = frontends;
for(; front->def != NULL; front++) {
const struct frontend_arg* options = front->def->args;
fprintf(stderr, "\t%s: %s ", front->id, front->def->name);
if(options) {
fprintf(stderr, "(Options:");
while(strlen(options->name)) {
fprintf(stderr, " %s", options->name);
options++;
}
fprintf(stderr, ")");
}
fprintf(stderr, "\n");
}
}
void doshutdown(int sig)
{
printf("Shutting down\n");
do_exit = true;
}
void show_usage(char* binary) {
fprintf(stderr, "Usage: %s [-p <port>] [-b <bind address>] [-w <width>] [-h <height>] [-r <screen update rate>] "\
"[-s <ring buffer size>] [-l <number of listening threads>] [-f <frontend>] [-t <fontfile>] [-d <description>] [-?]\n", binary);
fprintf(stderr, "Options:\n");
fprintf(stderr, " -p <port> Port to listen on (default %s)\n", PORT_DEFAULT);
fprintf(stderr, " -b <address> Address to listen on (default %s)\n", LISTEN_DEFAULT);
fprintf(stderr, " -w <width> Width of drawing surface (default %u)\n", WIDTH_DEFAULT);
fprintf(stderr, " -h <height> Height of drawing surface (default %u)\n", HEIGHT_DEFAULT);
fprintf(stderr, " -r <update rate> Screen update rate in HZ (default %u)\n", RATE_DEFAULT);
fprintf(stderr, " -s <ring size> Size of network ring buffer in bytes (default %u)\n", RINGBUFFER_DEFAULT);
fprintf(stderr, " -l <listen threads> Number of threads used to listen for incoming connections (default %u)\n", LISTEN_THREADS_DEFAULT);
fprintf(stderr, " -f <frontend,[option=value,...]> Frontend to use as a display. May be specified multiple times. "\
"Use -f ? to list available frontends and options\n");
fprintf(stderr, " -t <fontfile> Enable fancy text rendering using TTF, OTF or CFF font from <fontfile>\n");
fprintf(stderr, " -d <description> Set description text to be displayed in upper left corner (default %s)\n", REPO_URL);
fprintf(stderr, " -? Show this help\n");
}
struct resize_wq_priv {
struct fb* fb;
struct fb_size size;
};
int resize_wq_cb(void* priv) {
struct resize_wq_priv* resize_priv = priv;
return fb_resize(resize_priv->fb, resize_priv->size.width, resize_priv->size.height);
}
int resize_wq_err(int err, void* priv) {
doshutdown(SIGTERM);
return err;
}
void resize_wq_cleanup(int err, void* priv) {
free(priv);
}
#ifdef FEATURE_SDL
int resize_cb(struct sdl* sdl, unsigned int width, unsigned int height) {
struct llist* fb_list = sdl->cb_private;
struct llist_entry* cursor;
struct fb* fb;
int err = 0;
llist_for_each(fb_list, cursor) {
struct resize_wq_priv* resize_priv = malloc(sizeof(struct resize_wq_priv));
if(!resize_priv) {
err = -ENOMEM;
goto fail;
}
resize_priv->size.width = width;
resize_priv->size.height = height;
fb = llist_entry_get_value(cursor, struct fb, list);
resize_priv->fb = fb;
// TODO: Add error callback
if((err = workqueue_enqueue(fb->numa_node, resize_priv, resize_wq_cb, resize_wq_err, resize_wq_cleanup))) {
free(resize_priv);
goto fail;
}
}
fail:
return err;
}
#endif
#ifdef FEATURE_STATISTICS
struct statistics stats = { 0 };
#endif
#ifdef FEATURE_TTF
struct textrender* txtrndr = NULL;
#endif
char* description = REPO_URL;
void draw_overlays(struct fb* fb) {
#ifdef FEATURE_STATISTICS
char stat_line[MAX_STAT_LENGTH];
snprintf(stat_line, sizeof(stat_line), "Traffic: %.3f %sB / %.3f %sPixels "
"Throughput: %.3f %sb/s / %.3f %sPixels/s FPS: %d frames/s %llu connections",
statistics_traffic_get_scaled(&stats), statistics_traffic_get_unit(&stats),
statistics_pixels_get_scaled(&stats), statistics_pixels_get_unit(&stats),
statistics_throughput_get_scaled(&stats), statistics_throughput_get_unit(&stats),
statistics_pps_get_scaled(&stats), statistics_pps_get_unit(&stats),
statistics_get_frames_per_second(&stats), stats.num_connections);
#endif
#ifdef FEATURE_TTF
if(txtrndr) {
textrender_draw_string(txtrndr, fb, 100, fb->size.height / 20, description, 16);
#ifdef FEATURE_STATISTICS
textrender_draw_string(txtrndr, fb, 100, fb->size.height - fb->size.height / 10, stat_line, 16);
#endif
}
#endif
}
int main(int argc, char** argv) {
int err, opt;
struct fb* fb;
struct llist fb_list;
struct sockaddr_storage* inaddr;
struct addrinfo* addr_list;
struct net* net;
struct llist fronts;
struct llist_entry* cursor, *next;
struct frontend* front;
#ifdef FEATURE_SDL
struct sdl_param sdl_param;
#endif
size_t addr_len;
#ifdef FEATURE_STATISTICS
char stat_line[MAX_STAT_LENGTH];
#endif
unsigned int frontend_cnt = 0;
char* frontend_names[MAX_FRONTENDS];
bool handle_signals = true;
char* port = PORT_DEFAULT;
char* listen_address = LISTEN_DEFAULT;
int width = WIDTH_DEFAULT;
int height = HEIGHT_DEFAULT;
int screen_update_rate = RATE_DEFAULT;
int ringbuffer_size = RINGBUFFER_DEFAULT;
int listen_threads = LISTEN_THREADS_DEFAULT;
struct timespec before, after;
long long time_delta;
while((opt = getopt(argc, argv, "p:b:w:h:r:s:l:f:t:d:?")) != -1) {
switch(opt) {
case('p'):
port = optarg;
break;
case('b'):
listen_address = optarg;
break;
case('w'):
width = atoi(optarg);
if(width <= 0) {
fprintf(stderr, "Width must be > 0\n");
err = -EINVAL;
goto fail;
}
break;
case('h'):
height = atoi(optarg);
if(height <= 0) {
fprintf(stderr, "Height must be > 0\n");
err = -EINVAL;
goto fail;
}
break;
case('r'):
screen_update_rate = atoi(optarg);
if(screen_update_rate <= 0) {
fprintf(stderr, "Screen update rate must be > 0\n");
err = -EINVAL;
goto fail;
}
break;
case('s'):
ringbuffer_size = atoi(optarg);
if(ringbuffer_size < 2) {
fprintf(stderr, "Ring buffer size must be >= 2\n");
err = -EINVAL;
goto fail;
}
break;
case('l'):
listen_threads = atoi(optarg);
if(listen_threads <= 0) {
fprintf(stderr, "Number of listening threads must be > 0\n");
err = -EINVAL;
goto fail;
}
break;
case('f'):
if(frontend_cnt >= MAX_FRONTENDS) {
fprintf(stderr, "Maximum number of frontends reached.\n");
err = -EINVAL;
goto fail;
}
frontend_names[frontend_cnt] = strdup(optarg);
if(!frontend_names[frontend_cnt]) {
fprintf(stderr, "Can not copy frontend spec. Out of memory\n");
err = -ENOMEM;
goto fail;
}
frontend_cnt++;
break;
case('t'):
#ifdef FEATURE_TTF
if((err = textrender_alloc(&txtrndr, optarg))) {
fprintf(stderr, "Failed to allocate freetype text renderer\n");
goto fail;
}
#else
fprintf(stderr, "Shoreline was compiled without font rendering support!\n");
err = -EINVAL;
goto fail;
#endif
break;
case('d'):
description = strdup(optarg);
if(!description) {
fprintf(stderr, "Failed to allocate memory for description string\n");
err = -ENOMEM;
goto fail;
}
break;
default:
show_usage(argv[0]);
err = -EINVAL;
goto fail;
}
}
if(frontend_cnt == 0) {
printf("WARNING: No frontends specified, continuing without any frontends\n");
}
if((err = workqueue_init())) {
fprintf(stderr, "Failed to initialize workqueues: %d => %s\n", err, strerror(-err));
goto fail;
}
if((err = fb_alloc(&fb, width, height))) {
fprintf(stderr, "Failed to allocate framebuffer: %d => %s\n", err, strerror(-err));
goto fail;
}
llist_init(&fb_list);
#ifdef FEATURE_SDL
sdl_param.cb_private = &fb_list;
sdl_param.resize_cb = resize_cb;
#endif
llist_init(&fronts);
while(frontend_cnt > 0 && frontend_cnt--) {
char* frontid = frontend_names[frontend_cnt];
char* options = frontend_spec_extract_name(frontid);
struct frontend_def* frontdef = frontend_get_def(frontid);
if(!frontdef) {
fprintf(stderr, "Unknown frontend '%s'\n", frontid);
show_frontends();
goto fail_fronts_free_name;
}
handle_signals = handle_signals && !frontdef->handles_signals;
#ifdef FEATURE_SDL
if((err = frontend_alloc(frontdef, &front, fb, &sdl_param))) {
#else
if((err = frontend_alloc(frontdef, &front, fb, NULL))) {
#endif
fprintf(stderr, "Failed to allocate frontend '%s'\n", frontdef->name);
goto fail_fronts_free_name;
}
front->def = frontdef;
llist_append(&fronts, &front->list);
if(frontend_can_configure(front) && options) {
if((err = frontend_configure(front, options))) {
fprintf(stderr, "Failed to configure frontend '%s'\n", frontdef->name);
goto fail_fronts_free_name;
}
}
if(frontend_can_start(front)) {
if((err = frontend_start(front))) {
fprintf(stderr, "Failed to start frontend '%s'\n", frontdef->name);
goto fail_fronts_free_name;
}
}
free(frontid);
}
if((err = net_alloc(&net, fb, &fb_list, &fb->size, ringbuffer_size))) {
fprintf(stderr, "Failed to initialize network: %d => %s\n", err, strerror(-err));
goto fail_fronts;
}
if(handle_signals) {
if(signal(SIGINT, doshutdown)) {
fprintf(stderr, "Failed to bind signal\n");
err = -EINVAL;
goto fail_net;
}
if(signal(SIGPIPE, SIG_IGN)) {
fprintf(stderr, "Failed to bind signal\n");
err = -EINVAL;
goto fail_net;
}
}
if((err = -getaddrinfo(listen_address, port, NULL, &addr_list))) {
fprintf(stderr, "Failed to resolve listen address '%s', %d => %s\n", listen_address, err, gai_strerror(-err));
goto fail_net;
}
inaddr = (struct sockaddr_storage*)addr_list->ai_addr;
addr_len = addr_list->ai_addrlen;
if((err = net_listen(net, listen_threads, inaddr, addr_len))) {
fprintf(stderr, "Failed to start listening: %d => %s\n", err, strerror(-err));
goto fail_addrinfo;
}
nice(-20);
while(!do_exit) {
clock_gettime(CLOCK_MONOTONIC, &before);
llist_lock(&fb_list);
fb_coalesce(fb, &fb_list);
llist_unlock(&fb_list);
#ifdef FEATURE_STATISTICS
statistics_update(&stats, net);
snprintf(stat_line, sizeof(stat_line), "Traffic: %.3f %sB / %.3f %sPixels "
"Throughput: %.3f %sb/s / %.3f %sPixels/s FPS: %d frames/s %llu connections",
statistics_traffic_get_scaled(&stats), statistics_traffic_get_unit(&stats),
statistics_pixels_get_scaled(&stats), statistics_pixels_get_unit(&stats),
statistics_throughput_get_scaled(&stats), statistics_throughput_get_unit(&stats),
statistics_pps_get_scaled(&stats), statistics_pps_get_unit(&stats),
statistics_get_frames_per_second(&stats), stats.num_connections);
#endif
draw_overlays(fb);
#ifdef FEATURE_TTF
if(txtrndr) {
textrender_draw_string(txtrndr, fb, 100, fb->size.height / 20, description, 16);
#ifdef FEATURE_STATISTICS
textrender_draw_string(txtrndr, fb, 100, fb->size.height - fb->size.height / 10, stat_line, 16);
#endif
}
#endif
llist_for_each(&fronts, cursor) {
front = llist_entry_get_value(cursor, struct frontend, list);
#ifdef FEATURE_TTF
if(!txtrndr) {
#endif
if(frontend_can_draw_string(front)) {
frontend_draw_string(front, 0, 0, description);
#ifdef FEATURE_STATISTICS
frontend_draw_string(front, 0, fb->size.height - fb->size.height / 10, stat_line);
#endif
}
#ifdef FEATURE_TTF
}
#endif
if((err = frontend_update(front))) {
fprintf(stderr, "Failed to update frontend '%s', %d => %s, bailing out\n", front->def->name, err, strerror(-err));
doshutdown(SIGINT);
break;
}
}
#ifdef FEATURE_STATISTICS
stats.num_frames++;
#endif
clock_gettime(CLOCK_MONOTONIC, &after);
time_delta = get_timespec_diff(&after, &before);
time_delta = 1000000000UL / screen_update_rate - time_delta;
if(time_delta > 0) {
usleep(time_delta / 1000UL);
}
}
net_shutdown(net);
fb_free_all(&fb_list);
fail_addrinfo:
freeaddrinfo(addr_list);
fail_net:
net_free(net);
fail_fronts:
llist_for_each_safe(&fronts, cursor, next) {
front = llist_entry_get_value(cursor, struct frontend, list);
printf("Shutting down frontend '%s'\n", front->def->name);
frontend_free(front);
}
//fail_fb:
fb_free(fb);
fail:
while(frontend_cnt > 0 && frontend_cnt--) {
free(frontend_names[frontend_cnt]);
}
#ifdef FEATURE_TTF
if(txtrndr) {
textrender_free(txtrndr);
}
#endif
if(description && description != default_description) {
free(description);
}
workqueue_deinit();
return err;
fail_fronts_free_name:
frontend_cnt++;
goto fail_fronts;
}