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

Last change on this file since 19103 was 19103, checked in by frederik, 10 years ago

+render_expired... ANOTHER way to handle tile expiry.

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