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

Last change on this file since 19984 was 19984, checked in by apmon, 10 years ago

Add a "touch-from" option to render_expired

In this mode it changes the timestamp of a metatile into the past to mark it as dirty

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