From ad541c67761b8aa6da1566802c347cac4018d0c2 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Mon, 2 Nov 2026 20:50:24 +0100 Subject: Move the build sources into src/ --- src/ot_stats.c | 866 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 866 insertions(+) create mode 100644 src/ot_stats.c (limited to 'src/ot_stats.c') diff --git a/src/ot_stats.c b/src/ot_stats.c new file mode 100644 index 0000000..2d6335b --- /dev/null +++ b/src/ot_stats.c @@ -0,0 +1,866 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. */ + +/* System */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WANT_SYSLOGS +#include +#endif + +/* Opentracker */ +#include "ot_accesslist.h" +#include "ot_event.h" +#include "ot_hex.h" +#include "ot_iovec.h" +#include "ot_ip.h" +#include "ot_mutex.h" +#include "ot_workqueue.h" +#include "ot_fmt.h" +#include "ot_stats.h" +#include "trackerlogic.h" + +#ifndef NO_FULLSCRAPE_LOGGING +#define LOG_TO_STDERR(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG_TO_STDERR(...) +#endif + +/* Forward declaration */ +static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode); +#define OT_STATS_TMPSIZE 8192 + +/* Each serving thread keeps its own statistics tally. The list is linked + through intrusive next pointers within the workstruct. + + Registration happens once per thread and the adding up once per statistics + request, so this lock is never on anybody's hot path. */ +static struct ot_workstruct *g_counter_contexts; +static ot_counter g_retired[OT_COUNTER_COUNT]; +static pthread_mutex_t g_counter_mutex = PTHREAD_MUTEX_INITIALIZER; + +void stats_counters_register(struct ot_workstruct *ws) { + pthread_mutex_lock(&g_counter_mutex); + ws->counters_next = g_counter_contexts; + g_counter_contexts = ws; + pthread_mutex_unlock(&g_counter_mutex); +} + +void stats_counters_retire(struct ot_workstruct *ws) { + struct ot_workstruct **link; + size_t i; + + pthread_mutex_lock(&g_counter_mutex); + for (link = &g_counter_contexts; *link; link = &(*link)->counters_next) + if (*link == ws) { + *link = ws->counters_next; + break; + } + + /* When retiring a worker thread, collect the counter tally in the global */ + for (i = 0; i < OT_COUNTER_COUNT; ++i) + atomic_fetch_add_explicit(g_retired + i, atomic_load_explicit(ws->counters + i, memory_order_relaxed), memory_order_relaxed); + pthread_mutex_unlock(&g_counter_mutex); +} + +/* What every thread has counted so far, added together */ +static void counters_total(unsigned long long *total) { + struct ot_workstruct *ws; + size_t i; + + pthread_mutex_lock(&g_counter_mutex); + for (i = 0; i < OT_COUNTER_COUNT; ++i) + total[i] = atomic_load_explicit(g_retired + i, memory_order_relaxed); + for (ws = g_counter_contexts; ws; ws = ws->counters_next) + for (i = 0; i < OT_COUNTER_COUNT; ++i) + total[i] += atomic_load_explicit(ws->counters + i, memory_order_relaxed); + pthread_mutex_unlock(&g_counter_mutex); +} + +#define COUNT(index, by) atomic_fetch_add_explicit(ws->counters + (index), (unsigned long long)(by), memory_order_relaxed) + +static const char *ot_failed_request_names[] = { + "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", + "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"}; + +static time_t ot_start_time; + +#define STATS_NETWORK_NODE_BITWIDTH 4 +#define STATS_NETWORK_NODE_COUNT (1 << STATS_NETWORK_NODE_BITWIDTH) + +#define __BYTE(P, D) (((uint8_t *)P)[D / 8]) +#define __MSK (STATS_NETWORK_NODE_COUNT - 1) +#define __SHFT(D) ((D ^ STATS_NETWORK_NODE_BITWIDTH) & STATS_NETWORK_NODE_BITWIDTH) + +#define __LDR(P, D) ((__BYTE((P), (D)) >> __SHFT((D))) & __MSK) +#define __STR(P, D, V) __BYTE((P), (D)) = (__BYTE((P), (D)) & ~(__MSK << __SHFT((D)))) | ((V) << __SHFT((D))) + +#if 0 +// XXX +#define STATS_NETWORK_NODE_MAXDEPTH (68 - STATS_NETWORK_NODE_BITWIDTH) +#define STATS_NETWORK_NODE_LIMIT (48 - STATS_NETWORK_NODE_BITWIDTH) +#endif +#define STATS_NETWORK_NODE_MAXDEPTH (28 - STATS_NETWORK_NODE_BITWIDTH) +#define STATS_NETWORK_NODE_LIMIT (24 - STATS_NETWORK_NODE_BITWIDTH) + +typedef union stats_network_node stats_network_node; +union stats_network_node { + size_t counters[STATS_NETWORK_NODE_COUNT]; + stats_network_node *children[STATS_NETWORK_NODE_COUNT]; +}; + +static int stat_increase_network_count(stats_network_node **pnode, int depth, uintptr_t ip) { + int foo = __LDR(ip, depth); + stats_network_node *node; + + if (!*pnode) { + *pnode = malloc(sizeof(stats_network_node)); + if (!*pnode) + return -1; + memset(*pnode, 0, sizeof(stats_network_node)); + } + node = *pnode; + + if (depth < STATS_NETWORK_NODE_MAXDEPTH) + return stat_increase_network_count(node->children + foo, depth + STATS_NETWORK_NODE_BITWIDTH, ip); + + node->counters[foo]++; + return 0; +} + +static int stats_shift_down_network_count(stats_network_node **node, int depth, int shift) { + size_t i, rest = 0; + + if (!*node) + return 0; + + for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) + if (depth < STATS_NETWORK_NODE_MAXDEPTH) + rest += stats_shift_down_network_count((*node)->children + i, depth + STATS_NETWORK_NODE_BITWIDTH, shift); + else + rest += (*node)->counters[i] >>= shift; + + if (!rest) { + free(*node); + *node = NULL; + } + + return rest; +} + +static size_t stats_get_highscore_networks(stats_network_node *node, int depth, ot_ip6 node_value, size_t *scores, ot_ip6 *networks, int network_count, + int limit) { + size_t i, score = 0; + + if (!node) + return 0; + + if (depth < limit) { + for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) + if (node->children[i]) { + __STR(node_value, depth, i); + score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit); + } + return score; + } + + if (depth > limit && depth < STATS_NETWORK_NODE_MAXDEPTH) { + for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) + if (node->children[i]) + score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit); + return score; + } + + if (depth > limit && depth == STATS_NETWORK_NODE_MAXDEPTH) { + for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) + score += node->counters[i]; + return score; + } + + /* if( depth == limit ) */ + for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) { + int j = 1; + size_t node_score; + + if (depth == STATS_NETWORK_NODE_MAXDEPTH) + node_score = node->counters[i]; + else + node_score = stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit); + + score += node_score; + + if (node_score <= scores[0]) + continue; + + __STR(node_value, depth, i); + while (j < network_count && node_score > scores[j]) + ++j; + --j; + + memmove(scores, scores + 1, j * sizeof(*scores)); + memmove(networks, networks + 1, j * sizeof(*networks)); + scores[j] = node_score; + memcpy(networks + j, node_value, sizeof(*networks)); + } + + return score; +} + +static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) { + ot_ip6 networks[amount]; + ot_ip6 node_value; + size_t scores[amount]; + size_t i; + + memset(scores, 0, sizeof(scores)); + memset(networks, 0, sizeof(networks)); + memset(node_value, 0, sizeof(node_value)); + + stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); + + fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); + for (i = amount; i > 0; --i) { + if (scores[i - 1]) { + char ip_readable[64]; + + ip_readable[ip_format(ip_readable, networks[i - 1])] = 0; + fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable); + } + } + fmt_append(reply, reply_end, "\n"); +} + +static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) { + stats_network_node *slash24s_network_counters_root = NULL; + size_t i, bucket, peer_size = OT_PEER_SIZE4; + + for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { + ot_vector *torrents_list = mutex_bucket_lock(bucket); + for (i = 0; i < torrents_list->size; ++i) { + ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[i]).peer_list4; + ot_vector *bucket_list = &peer_list->peers; + int num_buckets = 1; + + if (OT_PEERLIST_HASBUCKETS(peer_list)) { + num_buckets = bucket_list->size; + bucket_list = (ot_vector *)bucket_list->data; + } + + while (num_buckets--) { + ot_peer *peers = (ot_peer *)bucket_list->data; + size_t numpeers = bucket_list->size; + while (numpeers--) { + if (stat_increase_network_count(&slash24s_network_counters_root, 0, (uintptr_t)(peers))) + goto bailout_unlock; + peers += peer_size; + } + ++bucket_list; + } + } + mutex_bucket_unlock(bucket, 0); + if (!g_opentracker_running) + goto done; + } + + /* The tree is built. Now analyze */ + stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); + stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); + goto done; + +bailout_unlock: + mutex_bucket_unlock(bucket, 0); +done: + stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); +} + +#ifdef WANT_SPOT_WOODPECKER +static stats_network_node *stats_woodpeckers_tree; +static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) { + pthread_mutex_lock(&g_woodpeckers_mutex); + stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); + pthread_mutex_unlock(&g_woodpeckers_mutex); +} +#endif + +typedef struct { + unsigned long long torrent_count; + unsigned long long peer_count; + unsigned long long seed_count; +} torrent_stats; + +static int torrent_statter(ot_torrent *torrent, uintptr_t data) { + torrent_stats *stats = (torrent_stats *)data; + stats->torrent_count++; + stats->peer_count += torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; + stats->seed_count += torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; + return 0; +} + +/* Converter function from memory to human readable hex strings */ +typedef struct { + size_t val; + ot_hash hash; +} ot_record; + +static inline void stats_insert_into_highscore(size_t val, ot_record* list, size_t amount, ot_hash* hash) { + size_t i; + for (i = 0; (i < amount) && (list[i].val > val); ++i) + {} + /* if we score better than a record in the list, we need to make room in the high + score array by moving lower scoring entries up */ + if (i < amount) { + memmove(list + i + 1, list + i, (amount - i - 1) * sizeof(ot_record)); + + memcpy(&list[i].hash, hash, sizeof(ot_hash)); + list[i].val = val; + } +} + + +/* Fetches stats from tracker */ +void stats_top_txt(char **reply, const char *reply_end, size_t amount) { + size_t j, idx, bucket; + ot_record top100s[100], top100c[100], top100l[100]; + char hex_out[42]; + + if (amount > 100) + amount = 100; + + memset(top100s, 0, sizeof(top100s)); + memset(top100c, 0, sizeof(top100c)); + memset(top100l, 0, sizeof(top100l)); + + /* Iterate over complete torrent list */ + for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { + ot_vector *torrents_list = mutex_bucket_lock(bucket); + for (j = 0; j < torrents_list->size; ++j) { + ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; + size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; + size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; + size_t leech_count = peer_count - seed_count; + + stats_insert_into_highscore(peer_count, top100c, amount, &torrent->hash); + stats_insert_into_highscore(seed_count, top100s, amount, &torrent->hash); + stats_insert_into_highscore(leech_count, top100l, amount, &torrent->hash); + } + mutex_bucket_unlock(bucket, 0); + if (!g_opentracker_running) + return; + } + + fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount); + for (idx = 0; idx < amount; ++idx) + if (top100c[idx].val) + fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); + fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount); + for (idx = 0; idx < amount; ++idx) + if (top100s[idx].val) + fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); + fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount); + for (idx = 0; idx < amount; ++idx) + if (top100l[idx].val) + fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); +} + +static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } + +static void stats_connections_mrtg(char **reply, const char *reply_end) { + ot_time t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", + total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], + total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600), + events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t), + events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t)); +} + +static void stats_udpconnections_mrtg(char **reply, const char *reply_end) { + ot_time t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", total[OT_COUNTER_UDP_CONNECTIONS], + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600), + events_per_time(total[OT_COUNTER_UDP_CONNECTIONS], t), + events_per_time(total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t)); +} + +static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) { + time_t t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", total[OT_COUNTER_TCP_CONNECTIONS], + total[OT_COUNTER_TCP_ANNOUNCES], (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS], t), + events_per_time(total[OT_COUNTER_TCP_ANNOUNCES], t)); +} + +static void stats_scrape_mrtg(char **reply, const char *reply_end) { + time_t t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", + total[OT_COUNTER_TCP_SCRAPES], total[OT_COUNTER_UDP_SCRAPES], (int)t, (int)(t / 3600), + events_per_time((total[OT_COUNTER_TCP_SCRAPES] + total[OT_COUNTER_UDP_SCRAPES]), t)); +} + +static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) { + ot_time t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", + total[OT_COUNTER_FULLSCRAPES] * 1000, total[OT_COUNTER_FULLSCRAPE_BYTES], (int)t, (int)(t / 3600), + events_per_time(total[OT_COUNTER_FULLSCRAPES], t), + events_per_time(total[OT_COUNTER_FULLSCRAPE_BYTES], t)); +} + +static void stats_peers_mrtg(char **reply, const char *reply_end) { + torrent_stats stats = {0, 0, 0}; + + iterate_all_torrents(torrent_statter, (uintptr_t)&stats); + + fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, + stats.torrent_count); +} + +static void stats_torrents_mrtg(char **reply, const char *reply_end) { + size_t torrent_count = mutex_get_torrent_count(); + + fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); +} + +static void stats_httperrors_txt(char **reply, const char *reply_end) { + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n", + total[OT_COUNTER_FAILED + 0], total[OT_COUNTER_FAILED + 1], total[OT_COUNTER_FAILED + 2], total[OT_COUNTER_FAILED + 3], + total[OT_COUNTER_FAILED + 4], total[OT_COUNTER_FAILED + 5], total[OT_COUNTER_FAILED + 6]); +} + +static void stats_return_renew_bucket(char **reply, const char *reply_end) { + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + int i; + + for (i = 0; i < OT_PEER_TIMEOUT; ++i) + fmt_append(reply, reply_end, "%02i %llu\n", i, total[OT_COUNTER_RENEWED + i]); +} + +static void stats_return_sync_mrtg(char **reply, const char *reply_end) { + ot_time t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", total[OT_COUNTER_SYNCS], 0LL, + (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t), + events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t)); +} + +static void stats_return_completed_mrtg(char **reply, const char *reply_end) { + ot_time t = time(NULL) - ot_start_time; + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", total[OT_COUNTER_COMPLETED], 0LL, (int)t, (int)(t / 3600), + events_per_time(total[OT_COUNTER_COMPLETED], t / 3600)); +} + +#ifdef WANT_LOG_NUMWANT +extern unsigned long long numwants[201]; +static void stats_return_numwants(char **reply, const char *reply_end) { + int i; + for (i = 0; i <= 200; ++i) + fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]); +} +#endif + +static void stats_return_everything(char **reply, const char *reply_end) { + unsigned long long total[OT_COUNTER_COUNT]; + + counters_total(total); + + torrent_stats stats = {0, 0, 0}; + int i; + + iterate_all_torrents(torrent_statter, (uintptr_t)&stats); + + fmt_append(reply, reply_end, "\n"); + fmt_append(reply, reply_end, "\n"); + fmt_append(reply, reply_end, " %llu\n", (unsigned long long)(time(NULL) - ot_start_time)); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " %zd\n", mutex_get_torrent_count()); + fmt_append(reply, reply_end, " %llu\n", stats.torrent_count); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n %llu\n \n", stats.peer_count); + fmt_append(reply, reply_end, " \n %llu\n \n", stats.seed_count); + fmt_append(reply, reply_end, " \n %llu\n \n", total[OT_COUNTER_COMPLETED]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n %llu\n %llu\n %llu\n \n", + total[OT_COUNTER_TCP_CONNECTIONS], total[OT_COUNTER_TCP_ANNOUNCES], total[OT_COUNTER_TCP_SCRAPES]); + fmt_append(reply, reply_end, " \n %llu\n %llu\n %llu\n %llu\n %llu\n \n", + total[OT_COUNTER_UDP_CONNECTIONS], total[OT_COUNTER_UDP_CONNECTS], total[OT_COUNTER_UDP_ANNOUNCES], total[OT_COUNTER_UDP_SCRAPES], + total[OT_COUNTER_UDP_CONNID_MISSMATCH]); + fmt_append(reply, reply_end, " \n %llu\n \n", total[OT_COUNTER_SYNCS]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n"); + for (i = 0; i < OT_PEER_TIMEOUT; ++i) + fmt_append(reply, reply_end, " %llu\n", i, total[OT_COUNTER_RENEWED + i]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n"); + for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) + fmt_append(reply, reply_end, " %llu\n", ot_failed_request_names[i], total[OT_COUNTER_FAILED + i]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n %llu\n \n", total[OT_COUNTER_STALLS]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, ""); +} + +/* The names as the Makefile spells them, so that what a binary reports maps + straight back onto the FEATURES lines it was built from */ +static const char *const build_features[] = { +#ifdef WANT_V4_ONLY + "WANT_V4_ONLY", +#endif +#ifdef WANT_ACCESSLIST_BLACK + "WANT_ACCESSLIST_BLACK", +#endif +#ifdef WANT_ACCESSLIST_WHITE + "WANT_ACCESSLIST_WHITE", +#endif +#ifdef WANT_DYNAMIC_ACCESSLIST + "WANT_DYNAMIC_ACCESSLIST", +#endif +#ifdef WANT_SYNC_LIVE + "WANT_SYNC_LIVE", +#endif +#ifdef WANT_IP_FROM_QUERY_STRING + "WANT_IP_FROM_QUERY_STRING", +#endif +#ifdef WANT_IP_FROM_PROXY + "WANT_IP_FROM_PROXY", +#endif +#ifdef WANT_COMPRESSION_GZIP + "WANT_COMPRESSION_GZIP", +#endif +#ifdef WANT_COMPRESSION_GZIP_ALWAYS + "WANT_COMPRESSION_GZIP_ALWAYS", +#endif +#ifdef WANT_COMPRESSION_ZSTD + "WANT_COMPRESSION_ZSTD", +#endif +#ifdef WANT_COMPRESSION_ZSTD_ALWAYS + "WANT_COMPRESSION_ZSTD_ALWAYS", +#endif +#ifdef WANT_FULLSCRAPE + "WANT_FULLSCRAPE", +#endif +#ifdef WANT_MODEST_FULLSCRAPES + "WANT_MODEST_FULLSCRAPES", +#endif +#ifdef WANT_KEEPALIVE + "WANT_KEEPALIVE", +#endif +#ifdef WANT_RESTRICT_STATS + "WANT_RESTRICT_STATS", +#endif +#ifdef WANT_LOG_NUMWANT + "WANT_LOG_NUMWANT", +#endif +#ifdef WANT_SPOT_WOODPECKER + "WANT_SPOT_WOODPECKER", +#endif +#ifdef WANT_SYSLOGS + "WANT_SYSLOGS", +#endif +#ifdef WANT_DEV_RANDOM + "WANT_DEV_RANDOM", +#endif +#ifdef WANT_INSECURE_RANDOM_FALLBACK + "WANT_INSECURE_RANDOM_FALLBACK", +#endif +#ifdef _DEBUG + "_DEBUG", +#endif +#ifdef _DEBUG_HTTPERROR + "_DEBUG_HTTPERROR", +#endif +#ifdef _DEBUG_RANDOMTORRENTS + "_DEBUG_RANDOMTORRENTS", +#endif + NULL}; + +void stats_return_tracker_features(char **reply, const char *reply_end) { + size_t i; + + fmt_append(reply, reply_end, "events: %s\n", event_mechanism()); + fmt_append(reply, reply_end, "features:"); + for (i = 0; build_features[i]; ++i) + fmt_append(reply, reply_end, " %s", build_features[i]); + if (!build_features[0]) + fmt_append(reply, reply_end, " none"); + fmt_append(reply, reply_end, "\n"); +} + +void stats_return_tracker_version(char **reply, const char *reply_end) { + fmt_append(reply, reply_end, "opentracker %s", OT_VERSION); + if (*GIT_VERSION && strcmp(GIT_VERSION, OT_TAG)) + fmt_append(reply, reply_end, " (%s)", GIT_VERSION); + fmt_append(reply, reply_end, "\n"); + if (*OT_TAG) + fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/tag/?h=%s\n", OT_TAG); +} + +void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) { + (void)format; + switch (mode & TASK_TASK_MASK) { + case TASK_STATS_CONNS: + stats_connections_mrtg(reply, reply_end); + break; + case TASK_STATS_SCRAPE: + stats_scrape_mrtg(reply, reply_end); + break; + case TASK_STATS_UDP: + stats_udpconnections_mrtg(reply, reply_end); + break; + case TASK_STATS_TCP: + stats_tcpconnections_mrtg(reply, reply_end); + break; + case TASK_STATS_FULLSCRAPE: + stats_fullscrapes_mrtg(reply, reply_end); + break; + case TASK_STATS_COMPLETED: + stats_return_completed_mrtg(reply, reply_end); + break; + case TASK_STATS_HTTPERRORS: + stats_httperrors_txt(reply, reply_end); + break; + case TASK_STATS_RENEW: + stats_return_renew_bucket(reply, reply_end); + break; + case TASK_STATS_SYNCS: + stats_return_sync_mrtg(reply, reply_end); + break; +#ifdef WANT_LOG_NUMWANT + case TASK_STATS_NUMWANTS: + stats_return_numwants(reply, reply_end); + break; +#endif + default: + break; + } +} + +static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { + char *r; + const char *r_end; + + *iovec_entries = 0; + *iovector = NULL; + if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE))) + return; + r_end = r + OT_STATS_TMPSIZE; + + switch (mode & TASK_TASK_MASK) { + case TASK_STATS_TORRENTS: + stats_torrents_mrtg(&r, r_end); + break; + case TASK_STATS_PEERS: + stats_peers_mrtg(&r, r_end); + break; + case TASK_STATS_SLASH24S: + stats_slash24s_txt(&r, r_end, 128); + break; + case TASK_STATS_TOP10: + stats_top_txt(&r, r_end, 10); + break; + case TASK_STATS_TOP100: + r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); + if (!r) + return; + r_end = r + 4 * OT_STATS_TMPSIZE; + stats_top_txt(&r, r_end, 100); + break; + case TASK_STATS_EVERYTHING: + r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); + if (!r) + return; + r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT; + stats_return_everything(&r, r_end); + break; +#ifdef WANT_SPOT_WOODPECKER + case TASK_STATS_WOODPECKERS: + stats_return_woodpeckers(&r, r_end, 128); + break; +#endif + default: + iovec_free(iovec_entries, iovector); + return; + } + iovec_trim_last(iovec_entries, iovector, r); +} + +void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) { + if (!ws) + return; + + switch (event) { + case EVENT_ACCEPT: + if (proto == FLAG_TCP) + COUNT(OT_COUNTER_TCP_CONNECTIONS, 1); + else + COUNT(OT_COUNTER_UDP_CONNECTIONS, 1); + break; + case EVENT_ANNOUNCE: + if (proto == FLAG_TCP) + COUNT(OT_COUNTER_TCP_ANNOUNCES, 1); + else + COUNT(OT_COUNTER_UDP_ANNOUNCES, 1); + break; + case EVENT_CONNECT: + if (proto == FLAG_TCP) + COUNT(OT_COUNTER_TCP_CONNECTS, 1); + else + COUNT(OT_COUNTER_UDP_CONNECTS, 1); + break; + case EVENT_COMPLETED: +#ifdef WANT_SYSLOGS + { + char timestring[64]; + char hash_hex[42], peerid_hex[42], ip_readable[64]; + struct tm time_now; + time_t ttt; + + time(&ttt); + localtime_r(&ttt, &time_now); + strftime(timestring, sizeof(timestring), "%FT%T%z", &time_now); + + hex_from_hash(hash_hex, *ws->hash); + if (ws->peer_id) + hex_from_hash(peerid_hex, (uint8_t *)ws->peer_id); + else { + *peerid_hex = 0; + } + + ip_readable[ip_format(ip_readable, (char *)&ws->peer)] = 0; + syslog(LOG_INFO, "time=%s event=completed info_hash=%s peer_id=%s ip=%s", timestring, hash_hex, peerid_hex, ip_readable); + } +#endif + COUNT(OT_COUNTER_COMPLETED, 1); + break; + case EVENT_SCRAPE: + if (proto == FLAG_TCP) + COUNT(OT_COUNTER_TCP_SCRAPES, 1); + else + COUNT(OT_COUNTER_UDP_SCRAPES, 1); + break; + case EVENT_FULLSCRAPE: + COUNT(OT_COUNTER_FULLSCRAPES, 1); + COUNT(OT_COUNTER_FULLSCRAPE_BYTES, event_data); + break; + case EVENT_FULLSCRAPE_REQUEST: { + ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */ + char _debug[512]; + int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60); + off += ip_format(_debug + off, *ip); + off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n"); + (void)write(2, _debug, off); + COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1); + } break; + case EVENT_FULLSCRAPE_REQUEST_GZIP: { + ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */ + char _debug[512]; + int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60); + off += ip_format(_debug + off, *ip); + off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n"); + (void)write(2, _debug, off); + COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1); + } break; + case EVENT_FAILED: + COUNT(OT_COUNTER_FAILED + event_data, 1); + break; + case EVENT_RENEW: + COUNT(OT_COUNTER_RENEWED + event_data, 1); + break; + case EVENT_SYNC: + COUNT(OT_COUNTER_SYNCS, event_data); + break; + case EVENT_BUCKET_LOCKED: + COUNT(OT_COUNTER_STALLS, 1); + break; +#ifdef WANT_SPOT_WOODPECKER + case EVENT_WOODPECKER: + pthread_mutex_lock(&g_woodpeckers_mutex); + stat_increase_network_count(&stats_woodpeckers_tree, 0, event_data); + pthread_mutex_unlock(&g_woodpeckers_mutex); + break; +#endif + case EVENT_CONNID_MISSMATCH: + COUNT(OT_COUNTER_UDP_CONNID_MISSMATCH, 1); + default: + break; + } +} + +void stats_cleanup(void) { +#ifdef WANT_SPOT_WOODPECKER + pthread_mutex_lock(&g_woodpeckers_mutex); + stats_shift_down_network_count(&stats_woodpeckers_tree, 0, 1); + pthread_mutex_unlock(&g_woodpeckers_mutex); +#endif +} + +static void *stats_worker(void *args) { + size_t iovec_entries; + struct iovec *iovector; + + (void)args; + + while (g_opentracker_running) { + ot_tasktype tasktype = TASK_STATS; + ot_taskid taskid = workqueue_poptask(&tasktype); + if (!taskid) + break; + stats_make(&iovec_entries, &iovector, tasktype); + if (workqueue_pushresult(taskid, iovec_entries, iovector)) + iovec_free(&iovec_entries, &iovector); + } + return NULL; +} + +void stats_deliver(int64_t sock, int tasktype) { workqueue_pushtask(sock, tasktype); } + +static pthread_t thread_id; +void stats_init(void) { + ot_start_time = time_now_seconds(); + pthread_create(&thread_id, NULL, stats_worker, NULL); +} + +void stats_deinit(void) { + workqueue_wakeall(); + pthread_join(thread_id, NULL); +} -- cgit v1.3