-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathtirex-batch
executable file
·460 lines (347 loc) · 13.7 KB
/
tirex-batch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
#!/usr/bin/perl
#-----------------------------------------------------------------------------
#
# Tirex Tile Rendering System
#
# tirex-batch
#
#-----------------------------------------------------------------------------
# See end of this file for documentation.
#-----------------------------------------------------------------------------
#
# Copyright (C) 2010 Frederik Ramm <frederik.ramm@geofabrik.de> and
# Jochen Topf <jochen.topf@geofabrik.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; If not, see <http://www.gnu.org/licenses/>.
#
#-----------------------------------------------------------------------------
use strict;
use warnings;
use File::stat;
use Getopt::Long qw( :config gnu_getopt );
use IO::Socket;
use JSON;
use Pod::Usage qw();
use Socket;
use Time::HiRes;
use Tirex;
use Tirex::Status;
use Tirex::Renderer;
use Tirex::Map;
#-----------------------------------------------------------------------------
# Reading command line and config
#-----------------------------------------------------------------------------
my %opts = ();
GetOptions( \%opts, 'help|h', 'debug|d', 'config|c=s', 'quit|q', 'num|n=i', 'prio|p=i', 'expire|e=s', 'filter|f=s', 'remove', 'count-only' ) or exit(2);
if ($opts{'help'})
{
Pod::Usage::pod2usage(
-verbose => 1,
-msg => "tirex-batch - send rendering requests to tirex master\n",
-exitval => 0
);
}
$Tirex::DEBUG = 1 if ($opts{'debug'});
my $prio = $opts{'prio'} || 99; # default batch prio is 99
my $num = $opts{'num'} || 999_999_999; # huge queue size as default max size
my @filters;
@filters = split(qr{\s*;\s*}, $opts{'filter'}) if (defined $opts{'filter'});
foreach my $filter (@filters)
{
if ($filter !~ qr{^(exists|not-exists|older\(([^)]+)\)|newer\(([^)]+)\)|multi\(2,[01]\))$})
{
print STDERR "unknown filter: $filter\n";
exit(2);
}
}
if ($Tirex::DEBUG)
{
print STDERR "Using prio: $prio\n";
print STDERR "Using expire: $opts{'expire'}\n" if (exists $opts{'expire'});
print STDERR "Using filters: ", join('; ', @filters) ,"\n";
}
my $config_dir = $opts{'config'} || $Tirex::TIREX_CONFIGDIR;
my $config_file = $config_dir . '/' . $Tirex::TIREX_CONFIGFILENAME;
Tirex::Config::init($config_file);
Tirex::Renderer->read_config_dir($config_dir);
#-----------------------------------------------------------------------------
my $master_socket_name = Tirex::Config::get('socket_dir', $Tirex::SOCKET_DIR) . '/master.sock';
my $socket = IO::Socket::UNIX->new(
Type => SOCK_DGRAM,
# Local => '',
Peer => $master_socket_name,
) or die("Cannot open connection to master: $!\n");
my $status;
if ($opts{'num'})
{
$status = eval { Tirex::Status->new(); };
die("Can't connect to shared memory. Is the tirex-master running?\n") if ($@);
}
if ($opts{'quit'} && !$opts{'num'})
{
die("--quit can only be used in conjunction with --num");
}
#-----------------------------------------------------------------------------
my $mx = Tirex::Config::get('metatile_columns') || $Tirex::METATILE_COLUMNS;
my $my = Tirex::Config::get('metatile_rows') || $Tirex::METATILE_ROWS;
#-----------------------------------------------------------------------------
my $count = 0;
# if there are still command line args, use those as init string
if (scalar(@ARGV) > 0)
{
$count += handle_init(join(' ', @ARGV));
}
# else read init strings from STDIN
else
{
while (<STDIN>)
{
chomp;
$count += handle_init($_);
}
}
print "metatiles: $count\n" if ($opts{'count-only'});
exit(0);
#-----------------------------------------------------------------------------
# get queue size of given priority from master
#-----------------------------------------------------------------------------
sub queue_size
{
my $prio = shift;
if (defined $status)
{
my $s = $status->read();
if (defined $s)
{
my $queues = JSON::from_json($s)->{'queue'}->{'prioqueues'};
foreach my $q (@$queues)
{
return $q->{'size'} if ($q->{'prio'} == $prio);
}
return 0;
}
else
{
die("can't read status\n");
}
}
else
{
die("can't get status\n");
}
}
#-----------------------------------------------------------------------------
# handle one init string, ie. decode and send job requests to server
#-----------------------------------------------------------------------------
sub handle_init
{
my $init = shift;
my $count_metatiles = 0;
# if this looks like a metatile path name, decode it
if ($init =~ qr{^\.?/([^/]+)/(.*)$})
{
my $metatile = Tirex::Metatile->new_from_filename_and_map($2, $1);
$init = $metatile->to_s();
}
my $range = eval { Tirex::Metatiles::Range->new(init => $init); };
if ($@)
{
print STDERR "Error parsing init string: $@";
exit(2);
}
print STDERR "Range: ", $range->to_s(), "\n" if ($Tirex::DEBUG);
while (1)
{
my $queue_size = 0;
if (!$opts{'count-only'} && $opts{'num'})
{
# wait for queue to have some space
while (($queue_size = queue_size($prio)) >= $opts{'num'})
{
if ($opts{'quit'})
{
print STDERR " queue size $queue_size >= max queue size $opts{'num'}; terminating (--quit set)\n" if ($Tirex::DEBUG);
exit(0);
}
print STDERR " queue size $queue_size >= max queue size $opts{'num'}. waiting...\n" if ($Tirex::DEBUG);
sleep(1);
}
print STDERR " queue size $queue_size, can send up to ", $opts{'num'}-$queue_size ," jobs\n" if ($Tirex::DEBUG);
}
# send as many jobs as will fit into queue
METATILE:
while ($queue_size <= $num)
{
my $metatile = $range->next();
# if there are no more jobs, we are done
return $count_metatiles unless (defined $metatile);
print STDERR "Considering ", $metatile->to_s(), "\n" if ($Tirex::DEBUG);
foreach my $filter (@filters)
{
if ($filter eq 'exists') { next METATILE unless ($metatile->exists()); }
elsif ($filter eq 'not-exists') { next METATILE if ($metatile->exists()); }
elsif ($filter =~ qr{^older\(([0-9]+)\)$}) { next METATILE unless ($metatile->older($1)); } # seconds since epoch
elsif ($filter =~ qr{^older\(([^)]+)\)$}) { next METATILE unless ($metatile->older(get_mtime($1))); } # filename
elsif ($filter =~ qr{^newer\(([0-9]+)\)$}) { next METATILE unless ($metatile->newer($1)); } # seconds since epoch
elsif ($filter =~ qr{^newer\(([^)]+)\)$}) { next METATILE unless ($metatile->newer(get_mtime($1))); } # filename
elsif ($filter =~ qr{^multi\(([0-9]+),([0-9]+)\)$}) { next METATILE if (($metatile->get_x()/$mx + $metatile->get_y()/$my) % $1 != $2); }
}
$count_metatiles++;
if (!$opts{'count-only'})
{
$queue_size++;
my %jobparams = ( metatile => $metatile, prio => $prio );
if (defined $opts{'expire'})
{
if ($opts{'expire'} =~ /^\+/)
{
$jobparams{'expire'} = time() + $opts{'expire'};
}
else
{
$jobparams{'expire'} = $opts{'expire'};
}
}
my $job = Tirex::Job->new(%jobparams);
my $request = $job->to_msg( id => undef, type => $opts{'remove'} ? 'metatile_remove_request' : 'metatile_enqueue_request' );
print STDERR " sending: ", $request->to_s(), "\n" if ($Tirex::DEBUG);
my $ret = $request->send($socket);
if (! defined $ret)
{
print STDERR "Can't send request. Is the master server running?\n";
exit(1);
}
Time::HiRes::usleep(1000); # don't send more than 1000 requests/s to not overwhelm the UDP receive buffer or the master
}
}
sleep(1) unless ($opts{'count-only'});
}
}
sub get_mtime
{
my $filename = shift;
my $st = File::stat::stat($filename);
if (! $st) {
print "Can't stat $filename: $!\n";
exit(2);
}
return $st->mtime;
}
__END__
=head1 NAME
tirex-batch - send rendering requests to tirex master
=head1 SYNOPSIS
tirex-batch [OPTIONS] [INIT]
=head1 OPTIONS
=over 8
=item B<-h>, B<--help>
Display help message.
=item B<-d>, B<--debug>
Run in debug mode. You'll see the actual messages sent and received.
=item B<-c>, B<--config=DIR>
Use the config directory DIR instead of /etc/tirex.
=item B<-n>, B<--num=NUM>
Try to keep the number of jobs in the queue below this number (Only checked
once per second). Disable with NUM=0.
=item B<-q>, B<--quit>
Quit if the number of jobs in the queue is higher than the number given
with -n. Without -q, tirex-batch would wait, and continue to fill the queue
once it has gone below the threshold.
=item B<-p>, B<--prio=PRIO>
Priority for requests.
=item B<-e>, B<--expire=TIME>
Expire time (seconds since epoch) for jobs. If it starts with '+', number of
seconds added to current time.
=item B<-f>, B<--filter>
Add filters to metatile selection, see section FILTERS.
=item B<--remove>
Send remove request instead of rendering request. Jobs will be removed from
the queue.
=item B<--count-only>
Only count how many metatiles would be rendered, do not actually send the
requests. This will take the filters into account, so it will possibly
check the disk for thousands of files!
=back
=head1 DESCRIPTION
INIT is a string describing a range of tiles. If no INIT string is given on the
command line, tirex-batch reads init strings from STDIN, one per line.
Several different ways of describing the tiles are allowed:
Single metatile:
map=foo x=4 y=7 z=15 (coordinates will be rounded to metatile numbers)
Multiple metatiles:
map=foo x=0-32 y=16-32 z=15
Using longitude and latitude ranges (8 to 9 degrees longitude, 48 to 49 degrees latitude)
map=foo lon=8,9 lat=48,49 z=15-17
Using a bounding box (8 to 9 degrees longitude, 48 to 49 degrees latitude)
map=foo bbox=8,48,9,49 z=15-17
Multiple maps are allowed, too:
map=foo,bar
You can use a z range (z=10-15). This does not work together with x
and y ranges, but works with lon/lat ranges.
Ranges of x, y, and z numbers are written as "MIN,MAX" or "MIN-MAX".
Ranges of lon and lat are written as "MIN,MAX" (lon and lat can be
negative, so range with "-" is problematic).
You can also just give a pathname of a metatile file as INIT string. It
has to start with './' or '/'. The first directory component must be
the name of the map.
=head1 FILTERS
FILTER is a ;-separated list of filter options. Metatiles not matching
the filter are skipped.
Filter Options:
=over 8
=item B<exists>
Matches if the meta tile exists on disk.
=item B<not-exists>
Matches if the meta tile does not exist on disk.
=item B<older(time)>
Matches if the meta tile's last modification time is before the given Unix time
stamp (seconds since the epoch, 1970-01-01 00:00:00). Also matches if the meta
tile does not exist on disk. If you want to match only files older than the
given date which do actually exist, add the I<exists> filter.
=item B<older(filename)>
Instead of the time in seconds since the epoch you can also enter a filename
here. The mtime (last modified) of this file will be used. tirex-batch will
exit with return code 2 if the file does not exist.
=item B<newer(time)>
Matches if the meta tile's last modification time is after the given Unix time
stamp (seconds since the epoch, 1970-01-01 00:00:00). Also matches if the meta
tile does not exist on disk. If you want to match only files newer than the
given date which do actually exist, add the I<exists> filter.
=item B<newer(filename)>
Instead of the time in seconds since the epoch you can also enter a filename
here. The mtime (last modified) of this file will be used. tirex-batch will
exit with return code 2 if the file does not exist.
=item B<multi(count,num)>
A magic filter that divides all meta tiles up in I<count> classes, and
matches only if the current meta tile is in class I<num> of these. Hence
the allowed range for I<num> is always 0..I<count>-1. Currently only
I<count>=2 is supported. This filter can be used to distribute rendering
requests among different tile servers (which may or may not then use
F<tirex-syncd> to share resulting tiles).
=back
=head1 FILES
=over 8
=item F</etc/tirex/tirex.conf>
The configuration file.
=back
=head1 DIAGNOSTICS
Returns 0 on success, 1 if there was a problem sending the request and 2 if there was
a problem parsing the command line or init string.
=head1 SEE ALSO
L<http://wiki.openstreetmap.org/wiki/Tirex>
=head1 AUTHORS
Frederik Ramm <frederik.ramm@geofabrik.de>, Jochen Topf
<jochen.topf@geofabrik.de> and possibly others.
=cut
#-- THE END ----------------------------------------------------------------------------