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

Last change on this file since 28516 was 28516, checked in by Dirk Stoecker, 7 years ago

finally remove hardcoded tile path and replace it by config option

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