summaryrefslogtreecommitdiff
path: root/src/ot_fullscrape.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_fullscrape.c')
-rw-r--r--src/ot_fullscrape.c455
1 files changed, 455 insertions, 0 deletions
diff --git a/src/ot_fullscrape.c b/src/ot_fullscrape.c
new file mode 100644
index 0000000..9afad92
--- /dev/null
+++ b/src/ot_fullscrape.c
@@ -0,0 +1,455 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifdef WANT_FULLSCRAPE
5
6/* System */
7#include <arpa/inet.h>
8#include <pthread.h>
9#include <stdio.h>
10#include <string.h>
11#ifdef WANT_COMPRESSION_GZIP
12#include <zlib.h>
13#endif
14#ifdef WANT_COMPRESSION_ZSTD
15#include <zstd.h>
16#endif
17
18/* Opentracker */
19#include "ot_fmt.h"
20#include "ot_hex.h"
21#include "ot_fullscrape.h"
22#include "ot_iovec.h"
23#include "ot_mutex.h"
24#include "ot_workqueue.h"
25#include "ot_urlencoded.h"
26#include "trackerlogic.h"
27
28/* Fetch full scrape info for all torrents
29 Full scrapes usually are huge and one does not want to
30 allocate more memory. So lets get them in 512k units
31*/
32#define OT_SCRAPE_CHUNK_SIZE (1024 * 1024)
33
34/* "d8:completei%zde10:downloadedi%zde10:incompletei%zdee" */
35#define OT_SCRAPE_MAXENTRYLEN 256
36
37/* Forward declaration */
38static void fullscrape_make(int taskid, ot_tasktype mode);
39#ifdef WANT_COMPRESSION_GZIP
40static void fullscrape_make_gzip(int taskid, ot_tasktype mode);
41#endif
42#ifdef WANT_COMPRESSION_ZSTD
43static void fullscrape_make_zstd(int taskid, ot_tasktype mode);
44#endif
45
46/* Converter function from memory to human readable hex strings
47 XXX - Duplicated from ot_stats. Needs fix. */
48/* This is the entry point into this worker thread
49 It grabs tasks from mutex_tasklist and delivers results back
50*/
51static void *fullscrape_worker(void *args) {
52 (void)args;
53
54 while (g_opentracker_running) {
55 ot_tasktype tasktype = TASK_FULLSCRAPE;
56 ot_taskid taskid = workqueue_poptask(&tasktype);
57 if (!taskid)
58 break;
59#ifdef WANT_COMPRESSION_ZSTD
60 if (tasktype & TASK_FLAG_ZSTD)
61 fullscrape_make_zstd(taskid, tasktype);
62 else
63#endif
64#ifdef WANT_COMPRESSION_GZIP
65 if (tasktype & TASK_FLAG_GZIP)
66 fullscrape_make_gzip(taskid, tasktype);
67 else
68#endif
69 fullscrape_make(taskid, tasktype);
70 workqueue_pushchunked(taskid, NULL);
71 }
72 return NULL;
73}
74
75static pthread_t thread_id;
76void fullscrape_init(void) {
77 pthread_create( &thread_id, NULL, fullscrape_worker, NULL );
78}
79
80void fullscrape_deinit(void) {
81 workqueue_wakeall();
82 pthread_join( thread_id, NULL );
83}
84
85void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) {
86 workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype );
87}
88
89static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) {
90 /* Callers guarantee this much room: the chunk writer keeps its low
91 watermark that far from the end, the compressors hand over a buffer of
92 exactly that size */
93 char *r = *cursor;
94 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN;
95 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
96 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
97 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
98
99 switch (mode & TASK_TASK_MASK) {
100 case TASK_FULLSCRAPE:
101 default:
102 /* push hash as bencoded string */
103 *r++ = '2';
104 *r++ = '0';
105 *r++ = ':';
106 memcpy(r, hash, sizeof(ot_hash));
107 r += sizeof(ot_hash);
108 /* push rest of the scrape string */
109 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count);
110
111 break;
112 case TASK_FULLSCRAPE_TPB_ASCII:
113 hex_from_hash(r, *hash);
114 r += 2 * sizeof(ot_hash);
115 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
116 break;
117 case TASK_FULLSCRAPE_TPB_ASCII_PLUS:
118 hex_from_hash(r, *hash);
119 r += 2 * sizeof(ot_hash);
120 fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count);
121 break;
122 case TASK_FULLSCRAPE_TPB_BINARY:
123 memcpy(r, *hash, sizeof(ot_hash));
124 r += sizeof(ot_hash);
125 *(uint32_t *)(r + 0) = htonl((uint32_t)seed_count);
126 *(uint32_t *)(r + 4) = htonl((uint32_t)(peer_count - seed_count));
127 r += 8;
128 break;
129 case TASK_FULLSCRAPE_TPB_URLENCODED:
130 r += urlencoded_format(r, *hash, sizeof(ot_hash));
131 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
132 break;
133 case TASK_FULLSCRAPE_TRACKERSTATE:
134 hex_from_hash(r, *hash);
135 r += 2 * sizeof(ot_hash);
136 fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count);
137 break;
138 }
139 *cursor = r;
140}
141
142static void fullscrape_make(int taskid, ot_tasktype mode) {
143 size_t bucket;
144 char *r, *re;
145 struct iovec iovector = {NULL, 0};
146
147 /* Setup return vector... */
148 r = iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
149 if (!r)
150 return;
151
152 /* re points to low watermark */
153 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
154
155 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
156 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd");
157
158 /* For each bucket... */
159 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
160 /* Get exclusive access to that bucket */
161 ot_vector *torrents_list = mutex_bucket_lock(bucket);
162 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
163 size_t i;
164
165 /* For each torrent in this bucket.. */
166 for (i = 0; i < torrents_list->size; ++i) {
167 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
168
169 if (r > re) {
170 iovector.iov_len = r - (char *)iovector.iov_base;
171
172 if (workqueue_pushchunked(taskid, &iovector)) {
173 free(iovector.iov_base);
174 mutex_bucket_unlock(bucket, 0);
175 return;
176 }
177 /* Allocate a fresh output buffer */
178 r = iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
179 if (!r) {
180 mutex_bucket_unlock(bucket, 0);
181 return;
182 }
183
184 /* re points to low watermark */
185 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
186 }
187 }
188
189 /* All torrents done: release lock on current bucket */
190 mutex_bucket_unlock(bucket, 0);
191
192 /* Parent thread died? */
193 if (!g_opentracker_running)
194 return;
195 }
196
197 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
198 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee");
199
200 /* Send rest of data */
201 iovector.iov_len = r - (char *)iovector.iov_base;
202 if (workqueue_pushchunked(taskid, &iovector))
203 free(iovector.iov_base);
204}
205
206#ifdef WANT_COMPRESSION_GZIP
207
208static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
209 size_t bucket;
210 char *r;
211 struct iovec iovector = {NULL, 0};
212 int zres;
213 z_stream strm;
214 /* Setup return vector... */
215 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
216 if (!iovector.iov_base)
217 return;
218
219 memset(&strm, 0, sizeof(strm));
220 strm.next_out = (uint8_t *)iovector.iov_base;
221 strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
222 if (deflateInit2(&strm, 7, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY) != Z_OK)
223 fprintf(stderr, "not ok.\n");
224
225 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
226 strm.next_in = (uint8_t *)"d5:filesd";
227 strm.avail_in = strlen("d5:filesd");
228 zres = deflate(&strm, Z_NO_FLUSH);
229 }
230
231 /* For each bucket... */
232 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
233 /* Get exclusive access to that bucket */
234 ot_vector *torrents_list = mutex_bucket_lock(bucket);
235 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
236 size_t i;
237
238 /* For each torrent in this bucket.. */
239 for (i = 0; i < torrents_list->size; ++i) {
240 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
241 r = compress_buffer;
242 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
243 strm.next_in = (uint8_t *)compress_buffer;
244 strm.avail_in = r - compress_buffer;
245 zres = deflate(&strm, Z_NO_FLUSH);
246 if ((zres < Z_OK) && (zres != Z_BUF_ERROR))
247 fprintf(stderr, "deflate() failed while in fullscrape_make().\n");
248
249 /* Check if there still is enough buffer left */
250 while (!strm.avail_out) {
251 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
252
253 if (workqueue_pushchunked(taskid, &iovector)) {
254 free(iovector.iov_base);
255 mutex_bucket_unlock(bucket, 0);
256 return;
257 }
258 /* Allocate a fresh output buffer */
259 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
260 if (!iovector.iov_base) {
261 fprintf(stderr, "Out of memory trying to claim ouput buffer\n");
262 deflateEnd(&strm);
263 mutex_bucket_unlock(bucket, 0);
264 return;
265 }
266 strm.next_out = (uint8_t *)iovector.iov_base;
267 strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
268 zres = deflate(&strm, Z_NO_FLUSH);
269 if ((zres < Z_OK) && (zres != Z_BUF_ERROR))
270 fprintf(stderr, "deflate() failed while in fullscrape_make().\n");
271 }
272 }
273
274 /* All torrents done: release lock on current bucket */
275 mutex_bucket_unlock(bucket, 0);
276
277 /* Parent thread died? */
278 if (!g_opentracker_running) {
279 deflateEnd(&strm);
280 return;
281 }
282 }
283
284 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
285 strm.next_in = (uint8_t *)"ee";
286 strm.avail_in = strlen("ee");
287 }
288
289 if (deflate(&strm, Z_FINISH) < Z_OK)
290 fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n");
291
292 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
293 if (workqueue_pushchunked(taskid, &iovector)) {
294 free(iovector.iov_base);
295 deflateEnd(&strm);
296 return;
297 }
298
299 /* Check if there's a last batch of data in the zlib buffer */
300 if (!strm.avail_out) {
301 /* Allocate a fresh output buffer */
302 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
303
304 if (!iovector.iov_base) {
305 fprintf(stderr, "Problem with iovec_next_chunk\n");
306 deflateEnd(&strm);
307 return;
308 }
309 strm.next_out = iovector.iov_base;
310 strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
311 if (deflate(&strm, Z_FINISH) < Z_OK)
312 fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n");
313
314 /* Only pass the new buffer if there actually was some data left in the buffer */
315 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
316 if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector))
317 free(iovector.iov_base);
318 }
319
320 deflateEnd(&strm);
321}
322/* WANT_COMPRESSION_GZIP */
323#endif
324
325#ifdef WANT_COMPRESSION_ZSTD
326
327static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
328 int bucket;
329 char *r;
330 struct iovec iovector = {NULL, 0};
331 ZSTD_CCtx *zstream = ZSTD_createCCtx();
332 ZSTD_inBuffer inbuf;
333 ZSTD_outBuffer outbuf;
334 size_t more_bytes;
335
336 if (!zstream)
337 return;
338
339 /* Setup return vector... */
340 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
341 if (!iovector.iov_base) {
342 ZSTD_freeCCtx(zstream);
343 return;
344 }
345
346 /* Working with a compression level 6 is half as fast as level 3, but
347 seems to be the last reasonable bump that's worth extra cpu */
348 ZSTD_CCtx_setParameter(zstream, ZSTD_c_compressionLevel, 6);
349
350 outbuf.dst = iovector.iov_base;
351 outbuf.size = OT_SCRAPE_CHUNK_SIZE;
352 outbuf.pos = 0;
353
354 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
355 inbuf.src = (const void *)"d5:filesd";
356 inbuf.size = strlen("d5:filesd");
357 inbuf.pos = 0;
358 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue);
359 }
360
361 /* For each bucket... */
362 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
363 /* Get exclusive access to that bucket */
364 ot_vector *torrents_list = mutex_bucket_lock(bucket);
365 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
366 size_t i;
367
368 /* For each torrent in this bucket.. */
369 for (i = 0; i < torrents_list->size; ++i) {
370 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
371 r = compress_buffer;
372 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
373 inbuf.src = compress_buffer;
374 inbuf.size = r - compress_buffer;
375 inbuf.pos = 0;
376 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue);
377
378 /* Check if there still is enough buffer left */
379 while (outbuf.pos + OT_SCRAPE_MAXENTRYLEN > outbuf.size) {
380 iovector.iov_len = outbuf.size;
381
382 if (workqueue_pushchunked(taskid, &iovector)) {
383 free(iovector.iov_base);
384 ZSTD_freeCCtx(zstream);
385 return mutex_bucket_unlock(bucket, 0);
386 }
387 /* Allocate a fresh output buffer */
388 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
389 if (!iovector.iov_base) {
390 fprintf(stderr, "Out of memory trying to claim ouput buffer\n");
391 ZSTD_freeCCtx(zstream);
392 return mutex_bucket_unlock(bucket, 0);
393 }
394
395 outbuf.dst = iovector.iov_base;
396 outbuf.size = OT_SCRAPE_CHUNK_SIZE;
397 outbuf.pos = 0;
398
399 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue);
400 }
401 }
402
403 /* All torrents done: release lock on current bucket */
404 mutex_bucket_unlock(bucket, 0);
405
406 /* Parent thread died? */
407 if (!g_opentracker_running)
408 return;
409 }
410
411 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
412 inbuf.src = (const void *)"ee";
413 inbuf.size = strlen("ee");
414 inbuf.pos = 0;
415 }
416
417 more_bytes = ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end);
418
419 iovector.iov_len = outbuf.pos;
420 if (workqueue_pushchunked(taskid, &iovector)) {
421 free(iovector.iov_base);
422 ZSTD_freeCCtx(zstream);
423 return;
424 }
425
426 /* Check if there's a last batch of data in the zlib buffer */
427 if (more_bytes) {
428 /* Allocate a fresh output buffer */
429 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
430
431 if (!iovector.iov_base) {
432 fprintf(stderr, "Problem with iovec_next_chunk\n");
433 ZSTD_freeCCtx(zstream);
434 return;
435 }
436
437 outbuf.dst = iovector.iov_base;
438 outbuf.size = OT_SCRAPE_CHUNK_SIZE;
439 outbuf.pos = 0;
440
441 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end);
442
443 /* Only pass the new buffer if there actually was some data left in the buffer */
444 iovector.iov_len = outbuf.pos;
445 if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector))
446 free(iovector.iov_base);
447 }
448
449 ZSTD_freeCCtx(zstream);
450}
451/* WANT_COMPRESSION_ZSTD */
452#endif
453
454/* WANT_FULLSCRAPE */
455#endif