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

Last change on this file since 29283 was 29197, checked in by apmon, 7 years ago

[render_expired] Set back date by exactly 20 years for touch

based expiry

With a set back of 20 years rather than to a specific date,
the actual render time gets preserved and can be checked in
e.g. mod_tiles tile status

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