source: subversion/applications/utils/mod_tile/render_expired.c @ 21645

Last change on this file since 21645 was 21645, checked in by ldp, 9 years ago

Do not spawn render threads when we're not actually going to rerender anything.

File size: 18.0 KB
Line 
1/**
2 * A modified version of render_list.c that does the following:
3 *
4 * - read list of expired tiles from stdin
5 * - calculate a list of all meta tiles between minZoom and maxZoom
6 *   affected by that expiry
7 * - for all expired meta tiles that are actually present on disk,
8 *   issue a re-render request to renderd
9 *
10 * If you run a tile server that servers z0-z17, it makes sense to run
11 * osm2pgsql with "-e14-14" (i.e. three zoom levels lower than your max)
12 * and then run this script with maxZoom=17. Due to z17 metatiles being
13 * exactly the area of a z14 tile, your expiry list just becomes unnecessarily
14 * large if you use -e17-17 (although this would lead to the same tiles
15 * being re-rendered).
16 *
17 * Be careful about minZoom; if you set it to 0, the tile x=0,y=0,z=0 will
18 * be expired every time the script is run. Having minZoom somewhere between
19 * z8 and z12 probably makes sense, and then use another, time-based mechanism
20 * to expire tiles.
21 *
22 * NOTE: format on stdin is one tile per line, formatted "z/x/y". This is different
23 * from render_list which wants "x y z". "z/x/y" is the format written by osm2pgsql.
24 *
25 * See also
26 * https://subversion.nexusuk.org/trac/browser/openpistemap/trunk/scripts/expire_tiles.py
27 * for a database-backed expiry solution, or
28 * http://trac.openstreetmap.org/browser/applications/utils/export/tile_expiry
29 * for a solution that works outside of osm2pgsql.
30 *
31 * This program made by Frederik Ramm <frederik@remote.org>. My ideas and
32 * contributions are in the public domain but being based on GPL'ed code
33 * this program inherits the GPL.
34 */
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <unistd.h>
39#include <sys/types.h>
40#include <sys/socket.h>
41#include <sys/stat.h>
42#include <sys/time.h>
43#include <sys/un.h>
44#include <getopt.h>
45#include <time.h>
46#include <utime.h>
47#include <string.h>
48#include <limits.h>
49#include <utime.h>
50
51#include <pthread.h>
52
53#include "protocol.h"
54#include "render_config.h"
55#include "dir_utils.h"
56
57// macros handling our tile marking arrays (these are essentially bit arrays
58// that have one bit for each tile on the repsective zoom level; since we only
59// need them for meta tile levels, even if someone were to render level 20,
60// we'd still only use 4^17 bits = 2 GB RAM (plus a little for the lower zoom
61// levels) - this saves us the hassle of working with a tree structure.
62
63#define TILE_REQUESTED(z,x,y) \
64   (tile_requested[z][((x)*twopow[z]+(y))/(8*sizeof(int))]>>(((x)*twopow[z]+(y))%(8*sizeof(int))))&0x01
65#define SET_TILE_REQUESTED(z,x,y) \
66   tile_requested[z][((x)*twopow[z]+(y))/(8*sizeof(int))] |= (0x01 << (((x)*twopow[z]+(y))%(8*sizeof(int))));
67
68
69#ifndef METATILE
70#warning("render_expired not implemented for non-metatile mode. Feel free to submit fix")
71int main(int argc, char **argv)
72{
73    fprintf(stderr, "render_expired not implemented for non-metatile mode. Feel free to submit fix!\n");
74    return -1;
75}
76#else
77
78// tile marking arrays
79unsigned int **tile_requested;
80
81// "two raised to the power of [...]" - don't trust pow() to be efficient
82// for base 2
83unsigned int twopow[18];
84
85static int minZoom = 0;
86static int maxZoom = 18;
87static int verbose = 0;
88int work_complete;
89
90void display_rate(struct timeval start, struct timeval end, int num) 
91{
92    int d_s, d_us;
93    float sec;
94
95    d_s  = end.tv_sec  - start.tv_sec;
96    d_us = end.tv_usec - start.tv_usec;
97
98    sec = d_s + d_us / 1000000.0;
99
100    printf("Rendered %d tiles in %.2f seconds (%.2f tiles/s)\n", num, sec, num / sec);
101    fflush(NULL);
102}
103
104int process_loop(int fd, const char *mapname, int x, int y, int z)
105{
106    struct protocol cmd, rsp;
107    //struct pollfd fds[1];
108    int ret = 0;
109
110    bzero(&cmd, sizeof(cmd));
111
112    cmd.ver = 2;
113    cmd.cmd = cmdRenderBulk;
114    cmd.z = z;
115    cmd.x = x;
116    cmd.y = y;
117    strcpy(cmd.xmlname, mapname);
118
119    //printf("Sending request\n");
120    ret = send(fd, &cmd, sizeof(cmd), 0);
121    if (ret != sizeof(cmd)) {
122        perror("send error");
123    }
124
125    //printf("Waiting for response\n");
126    bzero(&rsp, sizeof(rsp));
127    ret = recv(fd, &rsp, sizeof(rsp), 0);
128    if (ret != sizeof(rsp)) 
129    {
130        perror("recv error");
131        return 0;
132    }
133    //printf("Got response\n");
134
135    if (rsp.cmd != cmdDone) 
136    {
137        printf("rendering failed, pausing\n");
138        sleep(10);
139    }
140
141    if (!ret)
142        perror("Socket send error");
143    return ret;
144}
145
146void process(int fd, const char *name)
147{
148    char xmlconfig[XMLCONFIG_MAX];
149    int x, y, z;
150
151    if (path_to_xyz(name, xmlconfig, &x, &y, &z))
152        return;
153
154    printf("Requesting xml(%s) x(%d) y(%d) z(%d)\n", xmlconfig, x, y, z);
155    process_loop(fd, xmlconfig, x, y, z);
156}
157
158#define QMAX 32
159
160pthread_mutex_t qLock;
161pthread_cond_t qCondNotEmpty;
162pthread_cond_t qCondNotFull;
163
164unsigned int qLen;
165struct qItem {
166    char *path;
167    struct qItem *next;
168};
169
170struct qItem *qHead, *qTail;
171
172char *fetch(void)
173{
174    // Fetch path to render from queue or NULL on work completion
175    // Must free() pointer after use
176    char *path;
177
178    pthread_mutex_lock(&qLock);
179
180    while (qLen == 0) {
181        if (work_complete) {
182            pthread_mutex_unlock(&qLock);
183            return NULL;
184        }
185        pthread_cond_wait(&qCondNotEmpty, &qLock);
186    }
187
188    // Fetch item from queue
189    if (!qHead) {
190        fprintf(stderr, "Queue failure, null qHead with %d items in list\n", qLen);
191        exit(1);
192    }
193    path = qHead->path;
194
195    if (qHead == qTail) {
196        free(qHead);
197        qHead = NULL;
198        qTail = NULL;
199        qLen = 0;
200    } else {
201        struct qItem *e = qHead;
202        qHead = qHead->next;
203        free(e);
204        qLen--;
205        pthread_cond_signal(&qCondNotFull);
206    }
207
208    pthread_mutex_unlock(&qLock);
209    return path;
210}
211
212void enqueue(const char *path)
213{
214    // Add this path in the local render queue
215    struct qItem *e = malloc(sizeof(struct qItem));
216
217    e->path = strdup(path);
218    e->next = NULL;
219
220    if (!e->path) {
221        fprintf(stderr, "Malloc failure\n");
222        exit(1);
223    }
224
225    pthread_mutex_lock(&qLock);
226
227    while (qLen == QMAX) 
228    {
229        pthread_cond_wait(&qCondNotFull, &qLock);
230    }
231
232    // Append item to end of queue
233    if (qTail)
234        qTail->next = e;
235    else
236        qHead = e;
237    qTail = e;
238    qLen++;
239    pthread_cond_signal(&qCondNotEmpty);
240
241    pthread_mutex_unlock(&qLock);
242}
243
244
245void *thread_main(void *arg)
246{
247    const char *spath = (const char *)arg;
248    int fd;
249    struct sockaddr_un addr;
250    char *tile;
251
252    fd = socket(PF_UNIX, SOCK_STREAM, 0);
253    if (fd < 0) {
254        fprintf(stderr, "failed to create unix socket\n");
255        exit(2);
256    }
257
258    bzero(&addr, sizeof(addr));
259    addr.sun_family = AF_UNIX;
260    strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
261
262    if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
263        fprintf(stderr, "socket connect failed for: %s\n", spath);
264        close(fd);
265        return NULL;
266    }
267
268    while(1) {
269        if (!(tile = fetch())) break;
270        process(fd, tile);
271        free(tile);
272    }
273
274    close(fd);
275
276    return NULL;
277}
278
279
280pthread_t *workers;
281
282void spawn_workers(int num, const char *spath)
283{
284    int i;
285
286    // Setup request queue
287    pthread_mutex_init(&qLock, NULL);
288    pthread_cond_init(&qCondNotEmpty, NULL);
289    pthread_cond_init(&qCondNotFull, NULL);
290
291    printf("Starting %d rendering threads\n", num);
292    workers = calloc(sizeof(pthread_t), num);
293    if (!workers) {
294        perror("Error allocating worker memory");
295        exit(1);
296    }
297    for(i=0; i<num; i++) {
298        if (pthread_create(&workers[i], NULL, thread_main, (void *)spath)) {
299            perror("Thread creation failed");
300            exit(1);
301        }
302    }
303}
304
305void finish_workers(int num)
306{
307    int i;
308
309    printf("Waiting for rendering threads to finish\n");
310    pthread_mutex_lock(&qLock);
311    work_complete = 1;
312    pthread_mutex_unlock(&qLock);
313    pthread_cond_broadcast(&qCondNotEmpty);
314
315    for(i=0; i<num; i++)
316        pthread_join(workers[i], NULL);
317    free(workers);
318    workers = NULL;
319}
320
321
322int main(int argc, char **argv)
323{
324    char *spath = RENDER_SOCKET;
325    char *mapname = XMLCONFIG_DEFAULT;
326    char *tile_dir = HASH_PATH;
327    int x, y, z;
328    char name[PATH_MAX];
329    struct timeval start, end;
330    int num_render = 0, num_all = 0, num_read = 0, num_ignore = 0, num_unlink = 0, num_touch = 0;
331    int c;
332    int all=0;
333    int numThreads = 1;
334    int deleteFrom = -1;
335    int touchFrom = -1;
336    int doRender = 0;
337    int i;
338
339    // Initialize touchFrom timestamp
340    struct utimbuf touchTime;
341    touchTime.actime = 946681200;
342    touchTime.modtime = 946681200; // Jan 1 00:00 2000
343
344    // excess_zoomlevels is how many zoom levels at the large end
345    // we can ignore because their tiles will share one meta tile.
346    // with the default METATILE==8 this is 3.
347    int excess_zoomlevels = 0;
348    int mt = METATILE;
349    while (mt > 1)
350    {
351        excess_zoomlevels++;
352        mt >>= 1; 
353    }
354
355    // initialise arrays for tile markings
356
357    tile_requested = (unsigned int **) malloc((maxZoom - excess_zoomlevels + 1)*sizeof(unsigned int *));
358
359    for (i=0; i<=maxZoom - excess_zoomlevels; i++)
360    {
361        // initialize twopow array
362        twopow[i] = (i==0) ? 1 : twopow[i-1]*2;
363        unsigned int fourpow=twopow[i]*twopow[i];
364        tile_requested[i] = (unsigned int *) malloc((fourpow / METATILE) + 1);
365        memset(tile_requested[i], 0, (fourpow / METATILE) + 1);
366    }
367
368    while (1) 
369    {
370        int option_index = 0;
371        static struct option long_options[] = 
372        {
373            {"min-zoom", 1, 0, 'z'},
374            {"max-zoom", 1, 0, 'Z'},
375            {"socket", 1, 0, 's'},
376            {"num-threads", 1, 0, 'n'},
377            {"delete-from", 1, 0, 'd'},
378            {"touch-from", 1, 0, 'T'},
379            {"tile-dir", 1, 0, 't'},
380            {"map", 1, 0, 'm'},
381            {"verbose", 0, 0, 'v'},
382            {"help", 0, 0, 'h'},
383            {0, 0, 0, 0}
384        };
385
386        c = getopt_long(argc, argv, "hvz:Z:s:m:t:n:", long_options, &option_index);
387
388        if (c == -1)
389            break;
390
391        switch (c) {
392            case 'a':   /* -a, --all */
393                all=1;
394                break;
395            case 's':   /* -s, --socket */
396                spath = strdup(optarg);
397                break;
398            case 't':   /* -t, --tile-dir */
399                tile_dir=strdup(optarg);
400                break;
401            case 'm':   /* -m, --map */
402                mapname=strdup(optarg);
403                break;
404            case 'n':   /* -n, --num-threads */
405                numThreads=atoi(optarg);
406                if (numThreads <= 0) {
407                    fprintf(stderr, "Invalid number of threads, must be at least 1\n");
408                    return 1;
409                }
410                break;
411            case 'd':   /* -d, --delete-from */
412                deleteFrom=atoi(optarg);
413                if (deleteFrom < 0 || deleteFrom > 18) 
414                {
415                    fprintf(stderr, "Invalid 'delete-from' zoom, must be between 0 and 18\n");
416                    return 1;
417                }
418                break;
419            case 'T':   /* -T, --touch-from */
420                touchFrom=atoi(optarg);
421                if (touchFrom < 0 || touchFrom > 18) 
422                {
423                    fprintf(stderr, "Invalid 'touch-from' zoom, must be between 0 and 18\n");
424                    return 1;
425                }
426                break;
427            case 'z':   /* -z, --min-zoom */
428                minZoom=atoi(optarg);
429                if (minZoom < 0 || minZoom > 18) {
430                    fprintf(stderr, "Invalid minimum zoom selected, must be between 0 and 18\n");
431                    return 1;
432                }
433                break;
434            case 'Z':   /* -Z, --max-zoom */
435                maxZoom=atoi(optarg);
436                if (maxZoom < 0 || maxZoom > 18) {
437                    fprintf(stderr, "Invalid maximum zoom selected, must be between 0 and 18\n");
438                    return 1;
439                }
440                break;
441            case 'v':   /* -v, --verbose */
442                verbose=1;
443                break;
444            case 'h':   /* -h, --help */
445                fprintf(stderr, "Usage: render_expired [OPTION] ...\n");
446                fprintf(stderr, "  -m, --map=MAP        render tiles in this map (defaults to '" XMLCONFIG_DEFAULT "')\n");
447                fprintf(stderr, "  -s, --socket=SOCKET  unix domain socket name for contacting renderd\n");
448                fprintf(stderr, "  -n, --num-threads=N the number of parallel request threads (default 1)\n");
449                fprintf(stderr, "  -t, --tile-dir       tile cache directory (defaults to '" HASH_PATH "')\n");
450                fprintf(stderr, "  -z, --min-zoom=ZOOM  filter input to only render tiles greater or equal to this zoom level (default is 0)\n");
451                fprintf(stderr, "  -Z, --max-zoom=ZOOM  filter input to only render tiles less than or equal to this zoom level (default is 18)\n");
452                fprintf(stderr, "  -d, --delete-from=ZOOM  when expiring tiles of ZOOM or higher, delete them instead of re-rendering (default is off)\n");
453                fprintf(stderr, "  -T, --touch-from=ZOOM   when expiring tiles of ZOOM or higher, touch them instead of re-rendering (default is off)\n");
454                fprintf(stderr, "Send a list of tiles to be rendered from STDIN in the format:\n");
455                fprintf(stderr, "  z/x/y\n");
456                fprintf(stderr, "e.g.\n");
457                fprintf(stderr, "  1/0/1\n");
458                fprintf(stderr, "  1/1/1\n");
459                fprintf(stderr, "  1/0/0\n");
460                fprintf(stderr, "  1/1/0\n");
461                fprintf(stderr, "The above would cause all 4 tiles at zoom 1 to be rendered\n");
462                return -1;
463            default:
464                fprintf(stderr, "unhandled char '%c'\n", c);
465                break;
466        }
467    }
468
469    if (maxZoom < minZoom) {
470        fprintf(stderr, "Invalid zoom range, max zoom must be greater or equal to minimum zoom\n");
471        return 1;
472    }
473
474    if (minZoom < excess_zoomlevels) minZoom = excess_zoomlevels;
475
476    fprintf(stderr, "Rendering client\n");
477
478    gettimeofday(&start, NULL);
479
480    if (minZoom < touchFrom || minZoom < deleteFrom) {
481        // No need to spawn render threads, when we're not actually going to rerender tiles
482        spawn_workers(numThreads, spath);
483        doRender = 1;
484    }
485
486    while(!feof(stdin)) 
487    {
488        struct stat s;
489        int n = fscanf(stdin, "%d/%d/%d", &z, &x, &y);
490
491        printf("read: x=%d y=%d z=%d\n", x, y, z);
492        if (n != 3) {
493            // Discard input line
494            char tmp[1024];
495            char *r = fgets(tmp, sizeof(tmp), stdin);
496            if (!r)
497                continue;
498            // fprintf(stderr, "bad line %d: %s", num_all, tmp);
499            continue;
500        }
501
502        while (z > maxZoom)
503        {
504            x>>=1; y>>=1; z--;
505        }
506        while (z < maxZoom)
507        {
508            x<<=1; y<<=1; z++;
509        }
510        //printf("loop: x=%d y=%d z=%d up to z=%d\n", x, y, z, minZoom);
511        num_read++;
512
513        for (; z>= minZoom; z--, x>>=1, y>>=1)
514        {
515            printf("process: x=%d y=%d z=%d\n", x, y, z);
516
517            // don't do anything if this tile was already requested.
518            // renderd does keep a list internally to avoid enqueing the same tile
519            // twice but in case it has already rendered the tile we don't want to
520            // cause extra work.
521            if (TILE_REQUESTED(z - excess_zoomlevels,x>>excess_zoomlevels,y>>excess_zoomlevels)) 
522            { 
523                printf("already requested\n"); 
524                break; 
525            }
526
527            // mark tile as requested. (do this even if, below, the tile is not
528            // actually requested due to not being present on disk, to avoid
529            // unnecessary later stat'ing).
530            SET_TILE_REQUESTED(z - excess_zoomlevels,x>>excess_zoomlevels,y>>excess_zoomlevels); 
531
532            // commented out - seems to cause problems in MT environment,
533            // trying to write to already-closed file
534            //check_load();
535
536            num_all++;
537            xyz_to_meta(name, sizeof(name), tile_dir, mapname, x, y, z);
538
539            if (stat(name, &s) == 0) // 0 is success
540            {
541                // tile exists on disk; render it
542                if (deleteFrom != -1 && z >= deleteFrom)
543                {
544                    printf("unlink: %s\n", name);
545                    unlink(name);
546                    num_unlink++;
547                }
548                else if (touchFrom != -1 && z >= touchFrom)
549                {
550                    printf("touch: %s\n", name);
551                    utime(name, &touchTime);
552                    num_touch++;
553                }
554                else if (doRender)
555                {
556                    printf("render: %s\n", name);
557                    enqueue(name);
558                    num_render++;
559                }
560                /*
561                if (!(num_render % 10))
562                {
563                    gettimeofday(&end, NULL);
564                    printf("\n");
565                    printf("Meta tiles rendered: ");
566                    display_rate(start, end, num_render);
567                    printf("Total tiles rendered: ");
568                    display_rate(start, end, num_render * METATILE * METATILE);
569                    printf("Total tiles in input: %d\n", num_read);
570                    printf("Total tiles expanded from input: %d\n", num_all);
571                    printf("Total tiles ignored (not on disk): %d\n", num_ignore);
572                }
573                */
574            }
575            else
576            {
577                printf("not on disk: %s\n", name);
578                num_ignore++;
579            }
580        }
581    }
582
583    if (doRender) {
584        finish_workers(numThreads);
585    }
586
587    gettimeofday(&end, NULL);
588    printf("\nTotal for all tiles rendered\n");
589    printf("Meta tiles rendered: ");
590    display_rate(start, end, num_render);
591    printf("Total tiles rendered: ");
592    display_rate(start, end, num_render * METATILE * METATILE);
593    printf("Total tiles in input: %d\n", num_read);
594    printf("Total tiles expanded from input: %d\n", num_all);
595    printf("Total meta tiles deleted: %d\n", num_unlink);
596    printf("Total meta tiles touched: %d\n", num_touch);
597    printf("Total tiles ignored (not on disk): %d\n", num_ignore);
598
599    return 0;
600}
601#endif
Note: See TracBrowser for help on using the repository browser.