summaryrefslogtreecommitdiff
path: root/src/ot_stats.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_stats.c')
-rw-r--r--src/ot_stats.c866
1 files changed, 866 insertions, 0 deletions
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 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <pthread.h>
6#include <stdatomic.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/types.h>
11#include <sys/uio.h>
12#include <unistd.h>
13#ifdef WANT_SYSLOGS
14#include <syslog.h>
15#endif
16
17/* Opentracker */
18#include "ot_accesslist.h"
19#include "ot_event.h"
20#include "ot_hex.h"
21#include "ot_iovec.h"
22#include "ot_ip.h"
23#include "ot_mutex.h"
24#include "ot_workqueue.h"
25#include "ot_fmt.h"
26#include "ot_stats.h"
27#include "trackerlogic.h"
28
29#ifndef NO_FULLSCRAPE_LOGGING
30#define LOG_TO_STDERR(...) fprintf(stderr, __VA_ARGS__)
31#else
32#define LOG_TO_STDERR(...)
33#endif
34
35/* Forward declaration */
36static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode);
37#define OT_STATS_TMPSIZE 8192
38
39/* Each serving thread keeps its own statistics tally. The list is linked
40 through intrusive next pointers within the workstruct.
41
42 Registration happens once per thread and the adding up once per statistics
43 request, so this lock is never on anybody's hot path. */
44static struct ot_workstruct *g_counter_contexts;
45static ot_counter g_retired[OT_COUNTER_COUNT];
46static pthread_mutex_t g_counter_mutex = PTHREAD_MUTEX_INITIALIZER;
47
48void stats_counters_register(struct ot_workstruct *ws) {
49 pthread_mutex_lock(&g_counter_mutex);
50 ws->counters_next = g_counter_contexts;
51 g_counter_contexts = ws;
52 pthread_mutex_unlock(&g_counter_mutex);
53}
54
55void stats_counters_retire(struct ot_workstruct *ws) {
56 struct ot_workstruct **link;
57 size_t i;
58
59 pthread_mutex_lock(&g_counter_mutex);
60 for (link = &g_counter_contexts; *link; link = &(*link)->counters_next)
61 if (*link == ws) {
62 *link = ws->counters_next;
63 break;
64 }
65
66 /* When retiring a worker thread, collect the counter tally in the global */
67 for (i = 0; i < OT_COUNTER_COUNT; ++i)
68 atomic_fetch_add_explicit(g_retired + i, atomic_load_explicit(ws->counters + i, memory_order_relaxed), memory_order_relaxed);
69 pthread_mutex_unlock(&g_counter_mutex);
70}
71
72/* What every thread has counted so far, added together */
73static void counters_total(unsigned long long *total) {
74 struct ot_workstruct *ws;
75 size_t i;
76
77 pthread_mutex_lock(&g_counter_mutex);
78 for (i = 0; i < OT_COUNTER_COUNT; ++i)
79 total[i] = atomic_load_explicit(g_retired + i, memory_order_relaxed);
80 for (ws = g_counter_contexts; ws; ws = ws->counters_next)
81 for (i = 0; i < OT_COUNTER_COUNT; ++i)
82 total[i] += atomic_load_explicit(ws->counters + i, memory_order_relaxed);
83 pthread_mutex_unlock(&g_counter_mutex);
84}
85
86#define COUNT(index, by) atomic_fetch_add_explicit(ws->counters + (index), (unsigned long long)(by), memory_order_relaxed)
87
88static const char *ot_failed_request_names[] = {
89 "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest",
90 "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"};
91
92static time_t ot_start_time;
93
94#define STATS_NETWORK_NODE_BITWIDTH 4
95#define STATS_NETWORK_NODE_COUNT (1 << STATS_NETWORK_NODE_BITWIDTH)
96
97#define __BYTE(P, D) (((uint8_t *)P)[D / 8])
98#define __MSK (STATS_NETWORK_NODE_COUNT - 1)
99#define __SHFT(D) ((D ^ STATS_NETWORK_NODE_BITWIDTH) & STATS_NETWORK_NODE_BITWIDTH)
100
101#define __LDR(P, D) ((__BYTE((P), (D)) >> __SHFT((D))) & __MSK)
102#define __STR(P, D, V) __BYTE((P), (D)) = (__BYTE((P), (D)) & ~(__MSK << __SHFT((D)))) | ((V) << __SHFT((D)))
103
104#if 0
105// XXX
106#define STATS_NETWORK_NODE_MAXDEPTH (68 - STATS_NETWORK_NODE_BITWIDTH)
107#define STATS_NETWORK_NODE_LIMIT (48 - STATS_NETWORK_NODE_BITWIDTH)
108#endif
109#define STATS_NETWORK_NODE_MAXDEPTH (28 - STATS_NETWORK_NODE_BITWIDTH)
110#define STATS_NETWORK_NODE_LIMIT (24 - STATS_NETWORK_NODE_BITWIDTH)
111
112typedef union stats_network_node stats_network_node;
113union stats_network_node {
114 size_t counters[STATS_NETWORK_NODE_COUNT];
115 stats_network_node *children[STATS_NETWORK_NODE_COUNT];
116};
117
118static int stat_increase_network_count(stats_network_node **pnode, int depth, uintptr_t ip) {
119 int foo = __LDR(ip, depth);
120 stats_network_node *node;
121
122 if (!*pnode) {
123 *pnode = malloc(sizeof(stats_network_node));
124 if (!*pnode)
125 return -1;
126 memset(*pnode, 0, sizeof(stats_network_node));
127 }
128 node = *pnode;
129
130 if (depth < STATS_NETWORK_NODE_MAXDEPTH)
131 return stat_increase_network_count(node->children + foo, depth + STATS_NETWORK_NODE_BITWIDTH, ip);
132
133 node->counters[foo]++;
134 return 0;
135}
136
137static int stats_shift_down_network_count(stats_network_node **node, int depth, int shift) {
138 size_t i, rest = 0;
139
140 if (!*node)
141 return 0;
142
143 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
144 if (depth < STATS_NETWORK_NODE_MAXDEPTH)
145 rest += stats_shift_down_network_count((*node)->children + i, depth + STATS_NETWORK_NODE_BITWIDTH, shift);
146 else
147 rest += (*node)->counters[i] >>= shift;
148
149 if (!rest) {
150 free(*node);
151 *node = NULL;
152 }
153
154 return rest;
155}
156
157static 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,
158 int limit) {
159 size_t i, score = 0;
160
161 if (!node)
162 return 0;
163
164 if (depth < limit) {
165 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
166 if (node->children[i]) {
167 __STR(node_value, depth, i);
168 score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit);
169 }
170 return score;
171 }
172
173 if (depth > limit && depth < STATS_NETWORK_NODE_MAXDEPTH) {
174 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
175 if (node->children[i])
176 score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit);
177 return score;
178 }
179
180 if (depth > limit && depth == STATS_NETWORK_NODE_MAXDEPTH) {
181 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
182 score += node->counters[i];
183 return score;
184 }
185
186 /* if( depth == limit ) */
187 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) {
188 int j = 1;
189 size_t node_score;
190
191 if (depth == STATS_NETWORK_NODE_MAXDEPTH)
192 node_score = node->counters[i];
193 else
194 node_score = stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit);
195
196 score += node_score;
197
198 if (node_score <= scores[0])
199 continue;
200
201 __STR(node_value, depth, i);
202 while (j < network_count && node_score > scores[j])
203 ++j;
204 --j;
205
206 memmove(scores, scores + 1, j * sizeof(*scores));
207 memmove(networks, networks + 1, j * sizeof(*networks));
208 scores[j] = node_score;
209 memcpy(networks + j, node_value, sizeof(*networks));
210 }
211
212 return score;
213}
214
215static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) {
216 ot_ip6 networks[amount];
217 ot_ip6 node_value;
218 size_t scores[amount];
219 size_t i;
220
221 memset(scores, 0, sizeof(scores));
222 memset(networks, 0, sizeof(networks));
223 memset(node_value, 0, sizeof(node_value));
224
225 stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit);
226
227 fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH);
228 for (i = amount; i > 0; --i) {
229 if (scores[i - 1]) {
230 char ip_readable[64];
231
232 ip_readable[ip_format(ip_readable, networks[i - 1])] = 0;
233 fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable);
234 }
235 }
236 fmt_append(reply, reply_end, "\n");
237}
238
239static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) {
240 stats_network_node *slash24s_network_counters_root = NULL;
241 size_t i, bucket, peer_size = OT_PEER_SIZE4;
242
243 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
244 ot_vector *torrents_list = mutex_bucket_lock(bucket);
245 for (i = 0; i < torrents_list->size; ++i) {
246 ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[i]).peer_list4;
247 ot_vector *bucket_list = &peer_list->peers;
248 int num_buckets = 1;
249
250 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
251 num_buckets = bucket_list->size;
252 bucket_list = (ot_vector *)bucket_list->data;
253 }
254
255 while (num_buckets--) {
256 ot_peer *peers = (ot_peer *)bucket_list->data;
257 size_t numpeers = bucket_list->size;
258 while (numpeers--) {
259 if (stat_increase_network_count(&slash24s_network_counters_root, 0, (uintptr_t)(peers)))
260 goto bailout_unlock;
261 peers += peer_size;
262 }
263 ++bucket_list;
264 }
265 }
266 mutex_bucket_unlock(bucket, 0);
267 if (!g_opentracker_running)
268 goto done;
269 }
270
271 /* The tree is built. Now analyze */
272 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH);
273 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT);
274 goto done;
275
276bailout_unlock:
277 mutex_bucket_unlock(bucket, 0);
278done:
279 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1);
280}
281
282#ifdef WANT_SPOT_WOODPECKER
283static stats_network_node *stats_woodpeckers_tree;
284static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER;
285
286static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) {
287 pthread_mutex_lock(&g_woodpeckers_mutex);
288 stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH);
289 pthread_mutex_unlock(&g_woodpeckers_mutex);
290}
291#endif
292
293typedef struct {
294 unsigned long long torrent_count;
295 unsigned long long peer_count;
296 unsigned long long seed_count;
297} torrent_stats;
298
299static int torrent_statter(ot_torrent *torrent, uintptr_t data) {
300 torrent_stats *stats = (torrent_stats *)data;
301 stats->torrent_count++;
302 stats->peer_count += torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
303 stats->seed_count += torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
304 return 0;
305}
306
307/* Converter function from memory to human readable hex strings */
308typedef struct {
309 size_t val;
310 ot_hash hash;
311} ot_record;
312
313static inline void stats_insert_into_highscore(size_t val, ot_record* list, size_t amount, ot_hash* hash) {
314 size_t i;
315 for (i = 0; (i < amount) && (list[i].val > val); ++i)
316 {}
317 /* if we score better than a record in the list, we need to make room in the high
318 score array by moving lower scoring entries up */
319 if (i < amount) {
320 memmove(list + i + 1, list + i, (amount - i - 1) * sizeof(ot_record));
321
322 memcpy(&list[i].hash, hash, sizeof(ot_hash));
323 list[i].val = val;
324 }
325}
326
327
328/* Fetches stats from tracker */
329void stats_top_txt(char **reply, const char *reply_end, size_t amount) {
330 size_t j, idx, bucket;
331 ot_record top100s[100], top100c[100], top100l[100];
332 char hex_out[42];
333
334 if (amount > 100)
335 amount = 100;
336
337 memset(top100s, 0, sizeof(top100s));
338 memset(top100c, 0, sizeof(top100c));
339 memset(top100l, 0, sizeof(top100l));
340
341 /* Iterate over complete torrent list */
342 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
343 ot_vector *torrents_list = mutex_bucket_lock(bucket);
344 for (j = 0; j < torrents_list->size; ++j) {
345 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j;
346 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
347 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
348 size_t leech_count = peer_count - seed_count;
349
350 stats_insert_into_highscore(peer_count, top100c, amount, &torrent->hash);
351 stats_insert_into_highscore(seed_count, top100s, amount, &torrent->hash);
352 stats_insert_into_highscore(leech_count, top100l, amount, &torrent->hash);
353 }
354 mutex_bucket_unlock(bucket, 0);
355 if (!g_opentracker_running)
356 return;
357 }
358
359 fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount);
360 for (idx = 0; idx < amount; ++idx)
361 if (top100c[idx].val)
362 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash));
363 fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount);
364 for (idx = 0; idx < amount; ++idx)
365 if (top100s[idx].val)
366 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash));
367 fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount);
368 for (idx = 0; idx < amount; ++idx)
369 if (top100l[idx].val)
370 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash));
371}
372
373static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); }
374
375static void stats_connections_mrtg(char **reply, const char *reply_end) {
376 ot_time t = time(NULL) - ot_start_time;
377 unsigned long long total[OT_COUNTER_COUNT];
378
379 counters_total(total);
380
381 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.",
382 total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS],
383 total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600),
384 events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t),
385 events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t));
386}
387
388static void stats_udpconnections_mrtg(char **reply, const char *reply_end) {
389 ot_time t = time(NULL) - ot_start_time;
390 unsigned long long total[OT_COUNTER_COUNT];
391
392 counters_total(total);
393
394 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],
395 total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600),
396 events_per_time(total[OT_COUNTER_UDP_CONNECTIONS], t),
397 events_per_time(total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t));
398}
399
400static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) {
401 time_t t = time(NULL) - ot_start_time;
402 unsigned long long total[OT_COUNTER_COUNT];
403
404 counters_total(total);
405
406 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],
407 total[OT_COUNTER_TCP_ANNOUNCES], (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS], t),
408 events_per_time(total[OT_COUNTER_TCP_ANNOUNCES], t));
409}
410
411static void stats_scrape_mrtg(char **reply, const char *reply_end) {
412 time_t t = time(NULL) - ot_start_time;
413 unsigned long long total[OT_COUNTER_COUNT];
414
415 counters_total(total);
416
417 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)",
418 total[OT_COUNTER_TCP_SCRAPES], total[OT_COUNTER_UDP_SCRAPES], (int)t, (int)(t / 3600),
419 events_per_time((total[OT_COUNTER_TCP_SCRAPES] + total[OT_COUNTER_UDP_SCRAPES]), t));
420}
421
422static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) {
423 ot_time t = time(NULL) - ot_start_time;
424 unsigned long long total[OT_COUNTER_COUNT];
425
426 counters_total(total);
427
428 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.",
429 total[OT_COUNTER_FULLSCRAPES] * 1000, total[OT_COUNTER_FULLSCRAPE_BYTES], (int)t, (int)(t / 3600),
430 events_per_time(total[OT_COUNTER_FULLSCRAPES], t),
431 events_per_time(total[OT_COUNTER_FULLSCRAPE_BYTES], t));
432}
433
434static void stats_peers_mrtg(char **reply, const char *reply_end) {
435 torrent_stats stats = {0, 0, 0};
436
437 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
438
439 fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count,
440 stats.torrent_count);
441}
442
443static void stats_torrents_mrtg(char **reply, const char *reply_end) {
444 size_t torrent_count = mutex_get_torrent_count();
445
446 fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count);
447}
448
449static void stats_httperrors_txt(char **reply, const char *reply_end) {
450 unsigned long long total[OT_COUNTER_COUNT];
451
452 counters_total(total);
453
454 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",
455 total[OT_COUNTER_FAILED + 0], total[OT_COUNTER_FAILED + 1], total[OT_COUNTER_FAILED + 2], total[OT_COUNTER_FAILED + 3],
456 total[OT_COUNTER_FAILED + 4], total[OT_COUNTER_FAILED + 5], total[OT_COUNTER_FAILED + 6]);
457}
458
459static void stats_return_renew_bucket(char **reply, const char *reply_end) {
460 unsigned long long total[OT_COUNTER_COUNT];
461
462 counters_total(total);
463
464 int i;
465
466 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
467 fmt_append(reply, reply_end, "%02i %llu\n", i, total[OT_COUNTER_RENEWED + i]);
468}
469
470static void stats_return_sync_mrtg(char **reply, const char *reply_end) {
471 ot_time t = time(NULL) - ot_start_time;
472 unsigned long long total[OT_COUNTER_COUNT];
473
474 counters_total(total);
475
476 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,
477 (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t),
478 events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t));
479}
480
481static void stats_return_completed_mrtg(char **reply, const char *reply_end) {
482 ot_time t = time(NULL) - ot_start_time;
483 unsigned long long total[OT_COUNTER_COUNT];
484
485 counters_total(total);
486
487
488 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),
489 events_per_time(total[OT_COUNTER_COMPLETED], t / 3600));
490}
491
492#ifdef WANT_LOG_NUMWANT
493extern unsigned long long numwants[201];
494static void stats_return_numwants(char **reply, const char *reply_end) {
495 int i;
496 for (i = 0; i <= 200; ++i)
497 fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]);
498}
499#endif
500
501static void stats_return_everything(char **reply, const char *reply_end) {
502 unsigned long long total[OT_COUNTER_COUNT];
503
504 counters_total(total);
505
506 torrent_stats stats = {0, 0, 0};
507 int i;
508
509 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
510
511 fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
512 fmt_append(reply, reply_end, "<stats>\n");
513 fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time));
514 fmt_append(reply, reply_end, " <torrents>\n");
515 fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count());
516 fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count);
517 fmt_append(reply, reply_end, " </torrents>\n");
518 fmt_append(reply, reply_end, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count);
519 fmt_append(reply, reply_end, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count);
520 fmt_append(reply, reply_end, " <completed>\n <count>%llu</count>\n </completed>\n", total[OT_COUNTER_COMPLETED]);
521 fmt_append(reply, reply_end, " <connections>\n");
522 fmt_append(reply, reply_end, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n",
523 total[OT_COUNTER_TCP_CONNECTIONS], total[OT_COUNTER_TCP_ANNOUNCES], total[OT_COUNTER_TCP_SCRAPES]);
524 fmt_append(reply, reply_end, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n",
525 total[OT_COUNTER_UDP_CONNECTIONS], total[OT_COUNTER_UDP_CONNECTS], total[OT_COUNTER_UDP_ANNOUNCES], total[OT_COUNTER_UDP_SCRAPES],
526 total[OT_COUNTER_UDP_CONNID_MISSMATCH]);
527 fmt_append(reply, reply_end, " <livesync>\n <count>%llu</count>\n </livesync>\n", total[OT_COUNTER_SYNCS]);
528 fmt_append(reply, reply_end, " </connections>\n");
529 fmt_append(reply, reply_end, " <debug>\n");
530 fmt_append(reply, reply_end, " <renew>\n");
531 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
532 fmt_append(reply, reply_end, " <count interval=\"%02i\">%llu</count>\n", i, total[OT_COUNTER_RENEWED + i]);
533 fmt_append(reply, reply_end, " </renew>\n");
534 fmt_append(reply, reply_end, " <http_error>\n");
535 for (i = 0; i < CODE_HTTPERROR_COUNT; ++i)
536 fmt_append(reply, reply_end, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], total[OT_COUNTER_FAILED + i]);
537 fmt_append(reply, reply_end, " </http_error>\n");
538 fmt_append(reply, reply_end, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", total[OT_COUNTER_STALLS]);
539 fmt_append(reply, reply_end, " </debug>\n");
540 fmt_append(reply, reply_end, "</stats>");
541}
542
543/* The names as the Makefile spells them, so that what a binary reports maps
544 straight back onto the FEATURES lines it was built from */
545static const char *const build_features[] = {
546#ifdef WANT_V4_ONLY
547 "WANT_V4_ONLY",
548#endif
549#ifdef WANT_ACCESSLIST_BLACK
550 "WANT_ACCESSLIST_BLACK",
551#endif
552#ifdef WANT_ACCESSLIST_WHITE
553 "WANT_ACCESSLIST_WHITE",
554#endif
555#ifdef WANT_DYNAMIC_ACCESSLIST
556 "WANT_DYNAMIC_ACCESSLIST",
557#endif
558#ifdef WANT_SYNC_LIVE
559 "WANT_SYNC_LIVE",
560#endif
561#ifdef WANT_IP_FROM_QUERY_STRING
562 "WANT_IP_FROM_QUERY_STRING",
563#endif
564#ifdef WANT_IP_FROM_PROXY
565 "WANT_IP_FROM_PROXY",
566#endif
567#ifdef WANT_COMPRESSION_GZIP
568 "WANT_COMPRESSION_GZIP",
569#endif
570#ifdef WANT_COMPRESSION_GZIP_ALWAYS
571 "WANT_COMPRESSION_GZIP_ALWAYS",
572#endif
573#ifdef WANT_COMPRESSION_ZSTD
574 "WANT_COMPRESSION_ZSTD",
575#endif
576#ifdef WANT_COMPRESSION_ZSTD_ALWAYS
577 "WANT_COMPRESSION_ZSTD_ALWAYS",
578#endif
579#ifdef WANT_FULLSCRAPE
580 "WANT_FULLSCRAPE",
581#endif
582#ifdef WANT_MODEST_FULLSCRAPES
583 "WANT_MODEST_FULLSCRAPES",
584#endif
585#ifdef WANT_KEEPALIVE
586 "WANT_KEEPALIVE",
587#endif
588#ifdef WANT_RESTRICT_STATS
589 "WANT_RESTRICT_STATS",
590#endif
591#ifdef WANT_LOG_NUMWANT
592 "WANT_LOG_NUMWANT",
593#endif
594#ifdef WANT_SPOT_WOODPECKER
595 "WANT_SPOT_WOODPECKER",
596#endif
597#ifdef WANT_SYSLOGS
598 "WANT_SYSLOGS",
599#endif
600#ifdef WANT_DEV_RANDOM
601 "WANT_DEV_RANDOM",
602#endif
603#ifdef WANT_INSECURE_RANDOM_FALLBACK
604 "WANT_INSECURE_RANDOM_FALLBACK",
605#endif
606#ifdef _DEBUG
607 "_DEBUG",
608#endif
609#ifdef _DEBUG_HTTPERROR
610 "_DEBUG_HTTPERROR",
611#endif
612#ifdef _DEBUG_RANDOMTORRENTS
613 "_DEBUG_RANDOMTORRENTS",
614#endif
615 NULL};
616
617void stats_return_tracker_features(char **reply, const char *reply_end) {
618 size_t i;
619
620 fmt_append(reply, reply_end, "events: %s\n", event_mechanism());
621 fmt_append(reply, reply_end, "features:");
622 for (i = 0; build_features[i]; ++i)
623 fmt_append(reply, reply_end, " %s", build_features[i]);
624 if (!build_features[0])
625 fmt_append(reply, reply_end, " none");
626 fmt_append(reply, reply_end, "\n");
627}
628
629void stats_return_tracker_version(char **reply, const char *reply_end) {
630 fmt_append(reply, reply_end, "opentracker %s", OT_VERSION);
631 if (*GIT_VERSION && strcmp(GIT_VERSION, OT_TAG))
632 fmt_append(reply, reply_end, " (%s)", GIT_VERSION);
633 fmt_append(reply, reply_end, "\n");
634 if (*OT_TAG)
635 fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/tag/?h=%s\n", OT_TAG);
636}
637
638void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) {
639 (void)format;
640 switch (mode & TASK_TASK_MASK) {
641 case TASK_STATS_CONNS:
642 stats_connections_mrtg(reply, reply_end);
643 break;
644 case TASK_STATS_SCRAPE:
645 stats_scrape_mrtg(reply, reply_end);
646 break;
647 case TASK_STATS_UDP:
648 stats_udpconnections_mrtg(reply, reply_end);
649 break;
650 case TASK_STATS_TCP:
651 stats_tcpconnections_mrtg(reply, reply_end);
652 break;
653 case TASK_STATS_FULLSCRAPE:
654 stats_fullscrapes_mrtg(reply, reply_end);
655 break;
656 case TASK_STATS_COMPLETED:
657 stats_return_completed_mrtg(reply, reply_end);
658 break;
659 case TASK_STATS_HTTPERRORS:
660 stats_httperrors_txt(reply, reply_end);
661 break;
662 case TASK_STATS_RENEW:
663 stats_return_renew_bucket(reply, reply_end);
664 break;
665 case TASK_STATS_SYNCS:
666 stats_return_sync_mrtg(reply, reply_end);
667 break;
668#ifdef WANT_LOG_NUMWANT
669 case TASK_STATS_NUMWANTS:
670 stats_return_numwants(reply, reply_end);
671 break;
672#endif
673 default:
674 break;
675 }
676}
677
678static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) {
679 char *r;
680 const char *r_end;
681
682 *iovec_entries = 0;
683 *iovector = NULL;
684 if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE)))
685 return;
686 r_end = r + OT_STATS_TMPSIZE;
687
688 switch (mode & TASK_TASK_MASK) {
689 case TASK_STATS_TORRENTS:
690 stats_torrents_mrtg(&r, r_end);
691 break;
692 case TASK_STATS_PEERS:
693 stats_peers_mrtg(&r, r_end);
694 break;
695 case TASK_STATS_SLASH24S:
696 stats_slash24s_txt(&r, r_end, 128);
697 break;
698 case TASK_STATS_TOP10:
699 stats_top_txt(&r, r_end, 10);
700 break;
701 case TASK_STATS_TOP100:
702 r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE);
703 if (!r)
704 return;
705 r_end = r + 4 * OT_STATS_TMPSIZE;
706 stats_top_txt(&r, r_end, 100);
707 break;
708 case TASK_STATS_EVERYTHING:
709 r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT);
710 if (!r)
711 return;
712 r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT;
713 stats_return_everything(&r, r_end);
714 break;
715#ifdef WANT_SPOT_WOODPECKER
716 case TASK_STATS_WOODPECKERS:
717 stats_return_woodpeckers(&r, r_end, 128);
718 break;
719#endif
720 default:
721 iovec_free(iovec_entries, iovector);
722 return;
723 }
724 iovec_trim_last(iovec_entries, iovector, r);
725}
726
727void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) {
728 if (!ws)
729 return;
730
731 switch (event) {
732 case EVENT_ACCEPT:
733 if (proto == FLAG_TCP)
734 COUNT(OT_COUNTER_TCP_CONNECTIONS, 1);
735 else
736 COUNT(OT_COUNTER_UDP_CONNECTIONS, 1);
737 break;
738 case EVENT_ANNOUNCE:
739 if (proto == FLAG_TCP)
740 COUNT(OT_COUNTER_TCP_ANNOUNCES, 1);
741 else
742 COUNT(OT_COUNTER_UDP_ANNOUNCES, 1);
743 break;
744 case EVENT_CONNECT:
745 if (proto == FLAG_TCP)
746 COUNT(OT_COUNTER_TCP_CONNECTS, 1);
747 else
748 COUNT(OT_COUNTER_UDP_CONNECTS, 1);
749 break;
750 case EVENT_COMPLETED:
751#ifdef WANT_SYSLOGS
752 {
753 char timestring[64];
754 char hash_hex[42], peerid_hex[42], ip_readable[64];
755 struct tm time_now;
756 time_t ttt;
757
758 time(&ttt);
759 localtime_r(&ttt, &time_now);
760 strftime(timestring, sizeof(timestring), "%FT%T%z", &time_now);
761
762 hex_from_hash(hash_hex, *ws->hash);
763 if (ws->peer_id)
764 hex_from_hash(peerid_hex, (uint8_t *)ws->peer_id);
765 else {
766 *peerid_hex = 0;
767 }
768
769 ip_readable[ip_format(ip_readable, (char *)&ws->peer)] = 0;
770 syslog(LOG_INFO, "time=%s event=completed info_hash=%s peer_id=%s ip=%s", timestring, hash_hex, peerid_hex, ip_readable);
771 }
772#endif
773 COUNT(OT_COUNTER_COMPLETED, 1);
774 break;
775 case EVENT_SCRAPE:
776 if (proto == FLAG_TCP)
777 COUNT(OT_COUNTER_TCP_SCRAPES, 1);
778 else
779 COUNT(OT_COUNTER_UDP_SCRAPES, 1);
780 break;
781 case EVENT_FULLSCRAPE:
782 COUNT(OT_COUNTER_FULLSCRAPES, 1);
783 COUNT(OT_COUNTER_FULLSCRAPE_BYTES, event_data);
784 break;
785 case EVENT_FULLSCRAPE_REQUEST: {
786 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */
787 char _debug[512];
788 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60);
789 off += ip_format(_debug + off, *ip);
790 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n");
791 (void)write(2, _debug, off);
792 COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1);
793 } break;
794 case EVENT_FULLSCRAPE_REQUEST_GZIP: {
795 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */
796 char _debug[512];
797 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60);
798 off += ip_format(_debug + off, *ip);
799 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n");
800 (void)write(2, _debug, off);
801 COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1);
802 } break;
803 case EVENT_FAILED:
804 COUNT(OT_COUNTER_FAILED + event_data, 1);
805 break;
806 case EVENT_RENEW:
807 COUNT(OT_COUNTER_RENEWED + event_data, 1);
808 break;
809 case EVENT_SYNC:
810 COUNT(OT_COUNTER_SYNCS, event_data);
811 break;
812 case EVENT_BUCKET_LOCKED:
813 COUNT(OT_COUNTER_STALLS, 1);
814 break;
815#ifdef WANT_SPOT_WOODPECKER
816 case EVENT_WOODPECKER:
817 pthread_mutex_lock(&g_woodpeckers_mutex);
818 stat_increase_network_count(&stats_woodpeckers_tree, 0, event_data);
819 pthread_mutex_unlock(&g_woodpeckers_mutex);
820 break;
821#endif
822 case EVENT_CONNID_MISSMATCH:
823 COUNT(OT_COUNTER_UDP_CONNID_MISSMATCH, 1);
824 default:
825 break;
826 }
827}
828
829void stats_cleanup(void) {
830#ifdef WANT_SPOT_WOODPECKER
831 pthread_mutex_lock(&g_woodpeckers_mutex);
832 stats_shift_down_network_count(&stats_woodpeckers_tree, 0, 1);
833 pthread_mutex_unlock(&g_woodpeckers_mutex);
834#endif
835}
836
837static void *stats_worker(void *args) {
838 size_t iovec_entries;
839 struct iovec *iovector;
840
841 (void)args;
842
843 while (g_opentracker_running) {
844 ot_tasktype tasktype = TASK_STATS;
845 ot_taskid taskid = workqueue_poptask(&tasktype);
846 if (!taskid)
847 break;
848 stats_make(&iovec_entries, &iovector, tasktype);
849 if (workqueue_pushresult(taskid, iovec_entries, iovector))
850 iovec_free(&iovec_entries, &iovector);
851 }
852 return NULL;
853}
854
855void stats_deliver(int64_t sock, int tasktype) { workqueue_pushtask(sock, tasktype); }
856
857static pthread_t thread_id;
858void stats_init(void) {
859 ot_start_time = time_now_seconds();
860 pthread_create(&thread_id, NULL, stats_worker, NULL);
861}
862
863void stats_deinit(void) {
864 workqueue_wakeall();
865 pthread_join(thread_id, NULL);
866}