File Coverage

blib/lib/Perlbal.pm
Criterion Covered Total %
statement 317 796 39.8
branch 66 332 19.8
condition 16 81 19.7
subroutine 69 109 63.3
pod 0 58 0.0
total 468 1376 34.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #
3             # Copyright 2004, Danga Interactive, Inc.
4             # Copyright 2005-2007, Six Apart, Ltd.
5             #
6              
7             =head1 NAME
8              
9             Perlbal - Reverse-proxy load balancer and webserver
10              
11             =head1 SEE ALSO
12              
13             L
14              
15             =head1 CONTRIBUTING
16              
17             Got a patch? Or a bug report? Instructions on how to contribute
18             are located here:
19              
20             L
21              
22             Thanks!
23              
24             =head1 COPYRIGHT AND LICENSE
25              
26             Copyright 2004, Danga Interactive, Inc.
27             Copyright 2005-2010, Six Apart, Ltd.
28              
29             You can use and redistribute Perlbal under the same terms as Perl itself.
30              
31             =cut
32              
33             package Perlbal;
34              
35             BEGIN {
36             # keep track of anonymous subs' origins:
37 23     23   63318 $^P |= 0x200;
38             }
39              
40 22     22   15465 my $has_gladiator = eval "use Devel::Gladiator; 1;";
  0         0  
  0         0  
41 22     22   9397 my $has_cycle = eval "use Devel::Cycle; 1;";
  0         0  
  0         0  
42 22     22   53412 my $has_devel_peek = eval "use Devel::Peek; 1;";
  22         284916  
  22         176  
43              
44 23     22   381 use vars qw($VERSION);
  23         165  
  22         5265  
45             $VERSION = '1.80';
46              
47 22   50 22   294 use constant DEBUG => $ENV{PERLBAL_DEBUG} || 0;
  22         139  
  22         23970  
48 22   50 22   185 use constant DEBUG_OBJ => $ENV{PERLBAL_DEBUG_OBJ} || 0;
  22         99  
  22         1875  
49 22   50 22   159 use constant TRACK_STATES => $ENV{PERLBAL_TRACK_STATES} || 0; # if on, track states for "state changes" command
  22         77  
  22         2101  
50              
51 22     22   139 use strict;
  22         83  
  22         3108  
52 22     22   991 use warnings;
  22         62  
  22         10497  
53 22     22   151 no warnings qw(deprecated);
  22         55  
  22         2592  
54              
55 22     22   2160 use IO::Socket;
  22         56949  
  22         936  
56 22     22   2011630 use IO::Handle;
  22         60  
  22         3520  
57 22     22   35448 use IO::File;
  22         1140239  
  22         8637  
58              
59             $Perlbal::SYSLOG_AVAILABLE = eval { require Sys::Syslog; 1; };
60             $Perlbal::BSD_RESOURCE_AVAILABLE = eval { require BSD::Resource; 1; };
61              
62             # incremented every second by a timer:
63             $Perlbal::tick_time = time();
64              
65             # Set to 1 when we open syslog, and 0 when we close it
66             $Perlbal::syslog_open = 0;
67              
68 22     22   42425 use Getopt::Long;
  22         3747884  
  22         213  
69 22     22   18194 use Carp qw(cluck croak);
  22         55  
  22         1785  
70 22     22   139 use Errno qw(EBADF);
  22         54  
  22         5525  
71 22     22   2106 use POSIX qw(SIG_BLOCK SIG_UNBLOCK SIGINT sigprocmask);
  22         51813  
  22         653  
72              
73             our(%TrackVar);
74             sub track_var {
75 352     352 0 611 my ($name, $ref) = @_;
76 352         1426 $TrackVar{$name} = $ref;
77             }
78              
79 22     22   38094 use Perlbal::AIO;
  22         124  
  22         805  
80 22     22   19937 use Perlbal::HTTPHeaders;
  22         85  
  22         853  
81 22     22   26619 use Perlbal::Service;
  22         95  
  22         1177  
82 22     22   363 use Perlbal::Socket;
  22         53  
  22         642  
83 22     22   18963 use Perlbal::TCPListener;
  22         86  
  22         864  
84 22     22   17763 use Perlbal::UploadListener;
  22         274  
  22         2283  
85 22     22   19300 use Perlbal::ClientManage;
  22         74  
  22         752  
86 22     22   169 use Perlbal::ClientHTTPBase;
  22         45  
  22         586  
87 22     22   123 use Perlbal::ClientProxy;
  22         43  
  22         603  
88 22     22   18909 use Perlbal::ClientHTTP;
  22         77  
  22         888  
89 22     22   227 use Perlbal::BackendHTTP;
  22         49  
  22         466  
90 22     22   17235 use Perlbal::ReproxyManager;
  22         84  
  22         772  
91 22     22   16310 use Perlbal::Pool;
  22         79  
  22         710  
92 22     22   21244 use Perlbal::ManageCommand;
  22         64  
  22         699  
93 22     22   15850 use Perlbal::CommandContext;
  22         71  
  22         657  
94 22     22   150 use Perlbal::Util;
  22         43  
  22         5803  
95              
96             $SIG{'PIPE'} = "IGNORE"; # handled manually
97              
98             our(%hooks); # hookname => subref
99             our(%service); # servicename -> Perlbal::Service
100             our(%pool); # poolname => Perlbal::Pool
101             our(%plugins); # plugin => 1 (shows loaded plugins)
102             our($last_error);
103             our $service_autonumber = 1; # used to generate names for anonymous services created with Perlbal->create_service()
104             our $vivify_pools = 1; # if on, allow automatic creation of pools
105             our $foreground = 1; # default to foreground
106             our $track_obj = 0; # default to not track creation locations
107             our $reqs = 0; # total number of requests we've done
108             our $starttime = time(); # time we started
109             our $pidfile = ''; # full path, default to not writing pidfile
110             # used by pidfile (only makes sense before run started)
111             # don't rely on this variable, it might change.
112             our $run_started = 0;
113             our ($lastutime, $laststime, $lastreqs) = (0, 0, 0); # for deltas
114              
115             our %PluginCase = (); # lowercase plugin name -> as file is named
116              
117             # setup XS status data structures
118             our %XSModules; # ( 'headers' => 'Perlbal::XS::HTTPHeaders' )
119              
120             # now include XS files
121 22     22   12369 eval "use Perlbal::XS::HTTPHeaders;"; # if we have it, load it
  0         0  
  0         0  
122              
123             # activate modules as necessary
124             if ($ENV{PERLBAL_XS_HEADERS} && $XSModules{headers}) {
125             Perlbal::XS::HTTPHeaders::enable();
126             }
127              
128             # unactivate field::new
129             if ($ENV{PERLBAL_REMOVE_FIELDS}) {
130 22     22   15242 use Perlbal::Fields;
  22         97  
  22         43363  
131             Perlbal::Fields->remove();
132             }
133              
134              
135             # setup a USR1 signal handler that tells us to dump some basic statistics
136             # of how we're doing to the syslog
137             $SIG{'USR1'} = sub {
138             my $dumper = sub { Perlbal::log('info', $_[0]); };
139             foreach my $svc (values %service) {
140             run_manage_command("show service $svc->{name}", $dumper);
141             }
142             run_manage_command('states', $dumper);
143             run_manage_command('queues', $dumper);
144             };
145              
146             sub error {
147 0     0 0 0 $last_error = shift;
148 0         0 return 0;
149             }
150              
151             # Object instance counts, for debugging and leak detection
152             our(%ObjCount); # classname -> instances
153             our(%ObjTotal); # classname -> instances
154             our(%ObjTrack); # "$objref" -> creation location
155             sub objctor {
156 924     924 0 4360 if (DEBUG_OBJ) {
157             my $ref = ref $_[0];
158             $ref .= "-$_[1]" if $_[1];
159             $ObjCount{$ref}++;
160             $ObjTotal{$ref}++;
161              
162             # now, if we're tracing leaks, note this object's creation location
163             if ($track_obj) {
164             my $i = 1;
165             my @list;
166             while (my $sub = (caller($i++))[3]) {
167             push @list, $sub;
168             }
169             $ObjTrack{"$_[0]"} = [ time, join(', ', @list) ];
170             }
171             }
172             }
173             sub objdtor {
174 983     983 0 22581 if (DEBUG_OBJ) {
175             my $ref = ref $_[0];
176             $ref .= "-$_[1]" if $_[1];
177             $ObjCount{$ref}--;
178              
179             # remove tracking for this object
180             if ($track_obj) {
181             delete $ObjTrack{"$_[0]"};
182             }
183             }
184             }
185              
186             sub register_global_hook {
187 5     5 0 20 $hooks{$_[0]} = $_[1];
188 5         14 return 1;
189             }
190              
191             sub unregister_global_hook {
192 0     0 0 0 delete $hooks{$_[0]};
193 0         0 return 1;
194             }
195              
196             sub run_global_hook {
197 33     33 0 80 my $hookname = shift;
198 33         93 my $ref = $hooks{$hookname};
199 33 100       205 return $ref->(@_) if defined $ref; # @_ is $mc (a Perlbal::ManageCommand)
200 17         42 return undef;
201             }
202              
203             sub service_names {
204 0     0 0 0 return sort keys %service;
205             }
206              
207             # class method: given a service name, returns a service object
208             sub service {
209 98     98 0 251 my $class = shift;
210 98         888 return $service{$_[0]};
211             }
212              
213             sub create_service {
214 43     43 0 93 my $class = shift;
215 43         100 my $name = shift;
216              
217 43 50       142 unless (defined($name)) {
218 0         0 $name = "____auto_".($service_autonumber++);
219             }
220              
221 43 50       151 croak("service '$name' already exists") if $service{$name};
222 43 50       130 croak("pool '$name' already exists") if $pool{$name};
223              
224             # Create the new service and return it
225 43         464 return $service{$name} = Perlbal::Service->new($name);
226             }
227              
228             sub pool {
229 9     9 0 20 my $class = shift;
230 9         47 return $pool{$_[0]};
231             }
232              
233             # given some plugin name, return its correct case
234             sub plugin_case {
235 6     6 0 20 my $pname = lc shift;
236 6   33     50 return $PluginCase{$pname} || $pname;
237             }
238              
239             # run a block of commands. returns true if they all passed
240             sub run_manage_commands {
241 18     18 0 56 my ($cmd_block, $out, $ctx) = @_;
242              
243 18   33     347 $ctx ||= Perlbal::CommandContext->new;
244 18         172 foreach my $cmd (split(/\n/, $cmd_block)) {
245 341 100       837 return 0 unless Perlbal::run_manage_command($cmd, $out, $ctx);
246             }
247 17         208 return 1;
248             }
249              
250             # allows ${ip:eth0} in config. currently the only supported expansion
251             sub _expand_config_var {
252 0     0   0 my $cmd = shift;
253 0 0       0 $cmd =~ /^(\w+):(.+)/
254             or die "Unknown config variable: $cmd\n";
255 0         0 my ($type, $val) = ($1, $2);
256 0 0       0 if ($type eq "ip") {
257 0 0       0 die "Bogus-looking iface name" unless $val =~ /^\w+$/;
258 0         0 my $conf = `/sbin/ifconfig $val`;
259 0 0       0 $conf =~ /inet addr:(\S+)/
260             or die "Can't find IP of interface '$val'";
261 0         0 return $1;
262             }
263 0         0 die "Unknown config variable type: $type\n";
264             }
265              
266             # returns 1 if command succeeded, 0 otherwise
267             sub run_manage_command {
268 482     482 0 1510 my ($cmd, $out, $ctx) = @_; # $out is output stream closure
269              
270 482         965 $cmd =~ s/\#.*//;
271 482         1527 $cmd =~ s/^\s+//;
272 482         1521 $cmd =~ s/\s+$//;
273 482         2943 $cmd =~ s/\s+/ /g;
274              
275             # expand variables
276 482         842 my $orig = $cmd; # save original case for some commands
277              
278 482         841 $cmd =~ s/\$\{(.+?)\}/_expand_config_var($1)/eg;
  0         0  
279 482         778 $cmd =~ s/\$(\w+)/$ENV{$1}/g;
280              
281 482         2357 $cmd =~ s/^([^=]+)/lc $1/e; # lowercase everything up to an =
  413         1735  
282 482 100       2165 return 1 unless $cmd =~ /^\S/;
283              
284 413   50 0   1003 $out ||= sub {};
  0         0  
285 413   33     1017 $ctx ||= Perlbal::CommandContext->new;
286              
287             my $err = sub {
288 10     10   63 $out->("ERROR: $_[0]");
289 10         11014 return 0;
290 413         2050 };
291             my $ok = sub {
292 361 100   361   1362 $out->("OK") if $ctx->verbose;
293 361         4017 return 1;
294 413         2009 };
295              
296 413 50       1757 return $err->("invalid command") unless $cmd =~ /^(\w+)/;
297 413         822 my $basecmd = $1;
298              
299 413         2700 my $mc = Perlbal::ManageCommand->new($basecmd, $cmd, $out, $ok, $err, $orig, $ctx);
300              
301             # for testing auto crashing and recovery:
302 413 50       1185 if ($basecmd eq "crash") { die "Intentional crash." };
  0         0  
303              
304 22     22   182 no strict 'refs';
  22         50  
  22         226812  
305 413         581 my $handler;
306 413 100 100     3175 if ($Perlbal::{"MANAGE_$basecmd"} && ($handler = *{"MANAGE_$basecmd"}{CODE})) {
  397         4602  
307 397         657 my $rv = eval { $handler->($mc); };
  397         1264  
308 380 50       970 return $mc->err($@) if $@;
309 380         6408 return $rv;
310             }
311              
312             # if no handler found, look for plugins
313              
314             # call any hooks if they've been defined
315 16         32 my $rval = eval { run_global_hook("manage_command.$basecmd", $mc); };
  16         72  
316 16 100       188 return $mc->err($@) if $@;
317 13 50       40 if (defined $rval) {
318             # commands may return boolean, or arrayref to mass-print
319 13 50       145 if (ref $rval eq "ARRAY") {
320 0         0 $mc->out($_) foreach @$rval;
321 0         0 return 1;
322             }
323 13         545 return $rval;
324             }
325              
326 0         0 return $mc->err("unknown command: $basecmd");
327             }
328              
329             sub arena_ref_counts {
330 0     0 0 0 my $all = Devel::Gladiator::walk_arena();
331 0         0 my %ct;
332              
333             my %run_cycle;
334 0         0 foreach my $it (@$all) {
335 0         0 $ct{ref $it}++;
336 0 0       0 if (ref $it eq "CODE") {
337 0         0 my $name = Devel::Peek::CvGV($it);
338 0 0       0 $ct{$name}++ if $name =~ /ANON/;
339             }
340             }
341 0         0 $all = undef;
342 0         0 return \%ct;
343             }
344              
345             my %last_gladiator;
346             sub MANAGE_gladiator {
347 0     0 0 0 my $mc = shift->no_opts;
348 0 0 0     0 unless ($has_gladiator && $has_devel_peek) {
349 0         0 $mc->end;
350 0         0 return;
351             }
352              
353 0         0 my $ct = arena_ref_counts();
354 0         0 my $ret;
355 0         0 $ret .= "ARENA COUNTS:\n";
356 0         0 foreach my $k (sort {$ct->{$b} <=> $ct->{$a}} keys %$ct) {
  0         0  
357 0   0     0 my $delta = $ct->{$k} - ($last_gladiator{$k} || 0);
358 0         0 $last_gladiator{$k} = $ct->{$k};
359 0 0       0 next unless $ct->{$k} > 1;
360 0         0 $ret .= sprintf(" %4d %-4d $k\n", $ct->{$k}, $delta);
361             }
362              
363 0         0 $mc->out($ret);
364 0         0 $mc->end;
365             }
366              
367             sub MANAGE_varsize {
368 0     0 0 0 my $mc = shift->no_opts;
369              
370 0         0 my $emit;
371             $emit = sub {
372 0     0   0 my ($v, $depth, $name) = @_;
373 0   0     0 $name ||= "";
374              
375 0         0 my $show;
376 0 0       0 if (ref $v eq "ARRAY") {
    0          
377 0 0       0 return unless @$v;
378 0         0 $show = "[] " . scalar @$v;
379             }
380             elsif (ref $v eq "HASH") {
381 0 0       0 return unless %$v;
382 0         0 $show = "{} " . scalar keys %$v;
383             }
384             else {
385 0         0 $show = " = $v";
386             }
387 0         0 my $pre = " " x $depth;
388 0         0 $mc->out("$pre$name $show");
389              
390 0 0       0 if (ref $v eq "HASH") {
391 0         0 foreach my $k (sort keys %$v) {
392 0         0 $emit->($v->{$k}, $depth+1, "{$k}");
393             }
394             }
395 0         0 };
396              
397 0         0 foreach my $k (sort keys %TrackVar) {
398 0 0       0 my $v = $TrackVar{$k} or next;
399 0         0 $emit->($v, 0, $k);
400             }
401              
402 0         0 $mc->end;
403             }
404              
405             sub MANAGE_obj {
406 0     0 0 0 my $mc = shift->no_opts;
407              
408 0         0 foreach (sort keys %ObjCount) {
409 0         0 $mc->out("$_ = $ObjCount{$_} (tot=$ObjTotal{$_})");
410             }
411 0         0 $mc->end;
412             }
413              
414             sub MANAGE_verbose {
415 0     0 0 0 my $mc = shift->parse(qr/^verbose (on|off)$/,
416             "usage: VERBOSE {on|off}");
417 0         0 my $onoff = $mc->arg(1);
418 0 0       0 $mc->{ctx}->verbose(lc $onoff eq 'on' ? 1 : 0);
419 0         0 return $mc->ok;
420             }
421              
422             sub MANAGE_shutdown {
423 17     17 0 222 my $mc = shift->parse(qr/^shutdown(\s?graceful)?\s?(\d+)?$/);
424              
425             # immediate shutdown
426 17 50       150 exit(0) unless $mc->arg(1);
427              
428             # set connect ahead to 0 for all services so they don't spawn extra backends
429 0         0 foreach my $svc (values %service) {
430 0         0 $svc->{connect_ahead} = 0;
431             }
432              
433             # tell all sockets we're doing a graceful stop
434 0         0 my $sf = Perlbal::Socket->get_sock_ref;
435 0         0 foreach my $k (keys %$sf) {
436 0         0 my Perlbal::Socket $v = $sf->{$k};
437 0 0       0 $v->die_gracefully if $v->can("die_gracefully");
438             }
439              
440             # register a post loop callback that will end the event loop when we only have
441             # a single socket left, the AIO socket
442             Perlbal::Socket->SetPostLoopCallback(sub {
443 0     0   0 my ($descmap, $otherfds) = @_;
444              
445             # Ghetto: duplicate the code we already had for our postloopcallback
446 0         0 Perlbal::Socket::run_callbacks();
447              
448             # see what we have here; make sure we have no Clients and no unbored Backends
449 0         0 foreach my $sock (values %$descmap) {
450 0         0 my $ref = ref $sock;
451 0 0 0     0 return 1 if $ref =~ /^Perlbal::Client/ && $ref ne 'Perlbal::ClientManage';
452 0 0 0     0 return 1 if $sock->isa('Perlbal::BackendHTTP') && $sock->{state} ne 'bored';
453             }
454 0         0 return 0; # end the event loop and thus we exit perlbal
455 0         0 });
456              
457             # If requested, register a callback to kill the perlbal process after a specified number of seconds
458 0 0       0 if (my $timeout = $mc->arg(2)) {
459 0     0   0 Perlbal::Socket::register_callback($timeout, sub { exit(0); });
  0         0  
460             }
461              
462             # so they know something happened
463 0         0 return $mc->ok;
464             }
465              
466             sub MANAGE_mime {
467 0     0 0 0 my $mc = shift->parse(qr/^mime(?:\s+(\w+)(?:\s+(\w+))?(?:\s+(\S+))?)?$/);
468 0         0 my ($cmd, $arg1, $arg2) = ($mc->arg(1), $mc->arg(2), $mc->arg(3));
469              
470 0 0 0     0 if (!$cmd || $cmd eq 'list') {
    0          
    0          
471 0         0 foreach my $key (sort keys %$Perlbal::ClientHTTPBase::MimeType) {
472 0         0 $mc->out("$key $Perlbal::ClientHTTPBase::MimeType->{$key}");
473             }
474 0         0 $mc->end;
475             } elsif ($cmd eq 'set') {
476 0 0 0     0 if (!$arg1 || !$arg2) {
477 0         0 return $mc->err("Usage: set ");
478             }
479              
480 0         0 $Perlbal::ClientHTTPBase::MimeType->{$arg1} = $arg2;
481 0         0 return $mc->out("$arg1 set to $arg2.");
482             } elsif ($cmd eq 'remove') {
483 0 0       0 if (delete $Perlbal::ClientHTTPBase::MimeType->{$arg1}) {
484 0         0 return $mc->out("$arg1 removed.");
485             } else {
486 0         0 return $mc->err("$arg1 not a defined extension.");
487             }
488             } else {
489 0         0 return $mc->err("Usage: list, remove , set ");
490             }
491             }
492              
493             sub MANAGE_xs {
494 0     0 0 0 my $mc = shift->parse(qr/^xs(?:\s+(\w+)\s+(\w+))?$/);
495 0         0 my ($cmd, $module) = ($mc->arg(1), $mc->arg(2));
496              
497 0 0       0 if ($cmd) {
498             # command? verify
499 0 0       0 return $mc->err('Known XS modules: ' . join(', ', sort keys %XSModules) . '.')
500             unless $XSModules{$module};
501              
502             # okay, so now enable or disable this module
503 0 0       0 if ($cmd eq 'enable') {
    0          
504 0         0 my $res = eval "return $XSModules{$module}::enable();";
505 0 0       0 return $mc->err("Unable to enable module.")
506             unless $res;
507 0         0 return $mc->ok;
508             } elsif ($cmd eq 'disable') {
509 0         0 my $res = eval "return $XSModules{$module}::disable();";
510 0 0       0 return $mc->err("Unable to disable module.")
511             unless $res;
512 0         0 return $mc->out("Module disabled.");
513             } else {
514 0         0 return $mc->err('Usage: xs [ ]');
515             }
516             } else {
517             # no commands, so just check status
518 0         0 $mc->out('XS module status:', '');
519 0         0 foreach my $module (sort keys %XSModules) {
520 0         0 my $class = $XSModules{$module};
521 0         0 my $enabled = eval "return \$${class}::Enabled;";
522 0 0       0 my $status = defined $enabled ? ($enabled ? "installed, enabled" :
    0          
523             "installed, disabled") : "not installed";
524 0         0 $mc->out(" $module: $status");
525             }
526 0 0       0 $mc->out(' No modules available.') unless %XSModules;
527 0         0 $mc->out('');
528 0         0 $mc->out("To enable a module: xs enable ");
529 0         0 $mc->out("To disable a module: xs disable ");
530             }
531 0         0 $mc->end;
532             }
533              
534             sub MANAGE_fd {
535 0     0 0 0 my $mc = shift->no_opts;
536 0 0       0 return $mc->err('This command is not available unless BSD::Resource is installed') unless $Perlbal::BSD_RESOURCE_AVAILABLE;
537              
538             # called in list context on purpose, but we want the hard limit
539 0         0 my (undef, $max) = BSD::Resource::getrlimit(BSD::Resource::RLIMIT_NOFILE());
540 0         0 my $ct = 0;
541              
542             # first try procfs if one exists, as that's faster than iterating
543 0 0       0 if (opendir(DIR, "/proc/self/fd")) {
544 0         0 my @dirs = readdir(DIR);
545 0         0 $ct = scalar(@dirs) - 2; # don't count . and ..
546 0         0 closedir(DIR);
547             } else {
548             # isatty() is cheap enough to do on everything
549 0         0 foreach (0..$max) {
550 0         0 my $res = POSIX::isatty($_);
551 0 0 0     0 $ct++ if $res || ($! != EBADF);
552             }
553             }
554 0         0 $mc->out("max $max");
555 0         0 $mc->out("cur $ct");
556 0         0 $mc->end;
557             }
558              
559             sub MANAGE_proc {
560 17     17 0 201 my $mc = shift->no_opts;
561              
562 17         143 $mc->out('time: ' . time());
563 17         187 $mc->out('pid: ' . $$);
564              
565              
566 17 50       100 if ($Perlbal::BSD_RESOURCE_AVAILABLE) {
567 17         193 my $ru = BSD::Resource::getrusage();
568 17         26544 my ($ut, $st) = ($ru->utime, $ru->stime);
569 17         58647 my ($udelta, $sdelta) = ($ut - $lastutime, $st - $laststime);
570 17         393 $mc->out("utime: $ut (+$udelta)");
571 17         292 $mc->out("stime: $st (+$sdelta)");
572 17         240 ($lastutime, $laststime, $lastreqs) = ($ut, $st, $reqs);
573             }
574              
575 17         53 my $rdelta = $reqs - $lastreqs;
576 17         136 $mc->out("reqs: $reqs (+$rdelta)");
577 17         59 $lastreqs = $reqs;
578              
579 17         111 $mc->end;
580             }
581              
582             sub MANAGE_nodes {
583 6     6 0 75 my $mc = shift->parse(qr/^nodes?(?:\s+(\d+.\d+.\d+.\d+)(?::(\d+))?)?$/);
584              
585 6   50     44 my ($ip, $port) = ($mc->arg(1), $mc->arg(2) || 80);
586 6 50       27 my $spec_ipport = $ip ? "$ip:$port" : undef;
587 6         19 my $ref = \%Perlbal::BackendHTTP::NodeStats;
588              
589             my $dump = sub {
590 12     12   28 my $ipport = shift;
591 12         20 foreach my $key (keys %{$ref->{$ipport}}) {
  12         86  
592 85 100       260 if (ref $ref->{$ipport}->{$key} eq 'ARRAY') {
593 12         23 my %temp;
594 12         19 $temp{$_}++ foreach @{$ref->{$ipport}->{$key}};
  12         245  
595 12         52 foreach my $tkey (keys %temp) {
596 12         77 $mc->out("$ipport $key $tkey $temp{$tkey}");
597             }
598             } else {
599 73         372 $mc->out("$ipport $key $ref->{$ipport}->{$key}");
600             }
601             }
602 6         45 };
603              
604             # dump a node, or all nodes
605 6 50       20 if ($spec_ipport) {
606 0         0 $dump->($spec_ipport);
607             } else {
608 6         28 foreach my $ipport (keys %$ref) {
609 12         33 $dump->($ipport);
610             }
611             }
612              
613 6         33 $mc->end;
614             }
615              
616             # singular also works for the nodes command
617             *MANAGE_node = \&MANAGE_nodes;
618              
619             sub MANAGE_prof {
620 0     0 0 0 my $mc = shift->parse(qr/^prof\w*\s+(on|off|data)$/);
621 0         0 my $which = $mc->arg(1);
622              
623 0 0       0 if ($which eq 'on') {
624 0 0       0 if (Danga::Socket->EnableProfiling) {
625 0         0 return $mc->ok;
626             } else {
627 0         0 return $mc->err('Unable to enable profiling. Please ensure you have the BSD::Resource module installed.');
628             }
629             }
630              
631 0 0       0 if ($which eq 'off') {
632 0         0 Danga::Socket->DisableProfiling;
633 0         0 return $mc->ok;
634             }
635              
636 0 0       0 if ($which eq 'data') {
637 0         0 my $href = Danga::Socket->ProfilingData;
638 0         0 foreach my $key (sort keys %$href) {
639 0         0 my ($utime, $stime, $calls) = @{$href->{$key}};
  0         0  
640 0         0 $mc->out(sprintf("%s %0.5f %0.5f %d %0.7f %0.7f",
641             $key, $utime, $stime, $calls, $utime / $calls, $stime / $calls));
642             }
643 0         0 $mc->end;
644             }
645             }
646              
647             sub MANAGE_uptime {
648 0     0 0 0 my $mc = shift->no_opts;
649              
650 0         0 $mc->out("starttime $starttime");
651 0         0 $mc->out("uptime " . (time() - $starttime));
652 0         0 $mc->out("version $Perlbal::VERSION");
653 0         0 $mc->end;
654             }
655              
656             *MANAGE_version = \&MANAGE_uptime;
657              
658             sub MANAGE_track {
659 0     0 0 0 my $mc = shift->no_opts;
660              
661 0         0 my $now = time();
662 0         0 my @list;
663 0         0 foreach (keys %ObjTrack) {
664 0         0 my $age = $now - $ObjTrack{$_}->[0];
665 0         0 push @list, [ $age, "${age}s $_: $ObjTrack{$_}->[1]" ];
666             }
667              
668             # now output based on sorted age
669 0         0 foreach (sort { $a->[0] <=> $b->[0] } @list) {
  0         0  
670 0         0 $mc->out($_->[1]);
671             }
672 0         0 $mc->end;
673             }
674              
675             sub MANAGE_socks {
676 0     0 0 0 my $mc = shift->parse(qr/^socks(?: (\w+))?$/);
677 0   0     0 my $mode = $mc->arg(1) || "all";
678              
679 0         0 my $sf = Perlbal::Socket->get_sock_ref;
680              
681 0 0       0 if ($mode eq "summary") {
    0          
682 0         0 my %count;
683 0         0 my $write_buf = 0;
684 0         0 my $open_files = 0;
685 0         0 while (my $k = each %$sf) {
686 0         0 my Perlbal::Socket $v = $sf->{$k};
687 0         0 $count{ref $v}++;
688 0         0 $write_buf += $v->{write_buf_size};
689 0 0       0 if ($v->isa("Perlbal::ClientHTTPBase")) {
690 0         0 my Perlbal::ClientHTTPBase $cv = $v;
691 0 0       0 $open_files++ if $cv->{'reproxy_fh'};
692             }
693             }
694              
695 0         0 foreach (sort keys %count) {
696 0         0 $mc->out(sprintf("%5d $_", $count{$_}));
697             }
698 0         0 $mc->out();
699 0         0 $mc->out(sprintf("Aggregate write buffer: %.1fk", $write_buf / 1024));
700 0         0 $mc->out(sprintf(" Open files: %d", $open_files));
701             } elsif ($mode eq "all") {
702 0         0 my $now = time;
703 0         0 $mc->out(sprintf("%5s %6s", "fd", "age"));
704 0         0 foreach (sort { $a <=> $b } keys %$sf) {
  0         0  
705 0         0 my $sock = $sf->{$_};
706 0         0 my $age;
707 0         0 eval {
708 0         0 $age = $now - $sock->{create_time};
709             };
710 0   0     0 $age ||= 0;
711 0         0 $mc->out(sprintf("%5d %5ds %s", $_, $age, $sock->as_string));
712             }
713             }
714 0         0 $mc->end;
715             }
716              
717             sub MANAGE_backends {
718 0     0 0 0 my $mc = shift->no_opts;
719              
720 0         0 my $sf = Perlbal::Socket->get_sock_ref;
721 0         0 my %nodes; # { "Backend" => int count }
722 0         0 foreach my $sock (values %$sf) {
723 0 0       0 if ($sock->isa("Perlbal::BackendHTTP")) {
724 0         0 my Perlbal::BackendHTTP $cv = $sock;
725 0         0 $nodes{"$cv->{ipport}"}++;
726             }
727             }
728              
729             # now print out text
730 0         0 foreach my $node (sort keys %nodes) {
731 0         0 $mc->out("$node " . $nodes{$node});
732             }
733              
734 0         0 $mc->end;
735             }
736              
737             sub MANAGE_noverify {
738 0     0 0 0 my $mc = shift->no_opts;
739              
740             # shows the amount of time left for each node marked as noverify
741 0         0 my $now = time;
742 0         0 foreach my $ipport (keys %Perlbal::BackendHTTP::NoVerify) {
743 0         0 my $until = $Perlbal::BackendHTTP::NoVerify{$ipport} - $now;
744 0         0 $mc->out("$ipport $until");
745             }
746 0         0 $mc->end;
747             }
748              
749             sub MANAGE_pending {
750 0     0 0 0 my $mc = shift->no_opts;
751              
752             # shows pending backend connections by service, node, and age
753 0         0 my %pend; # { "service" => { "ip:port" => age } }
754 0         0 my $now = time;
755              
756 0         0 foreach my $svc (values %service) {
757 0         0 foreach my $ipport (keys %{$svc->{pending_connects}}) {
  0         0  
758 0         0 my Perlbal::BackendHTTP $be = $svc->{pending_connects}->{$ipport};
759 0 0       0 next unless defined $be;
760 0         0 $pend{$svc->{name}}->{$ipport} = $now - $be->{create_time};
761             }
762             }
763              
764 0         0 foreach my $name (sort keys %pend) {
765 0         0 foreach my $ipport (sort keys %{$pend{$name}}) {
  0         0  
766 0         0 $mc->out("$name $ipport $pend{$name}{$ipport}");
767             }
768             }
769 0         0 $mc->end;
770             }
771              
772             sub MANAGE_states {
773 0     0 0 0 my $mc = shift->parse(qr/^states(?:\s+(.+))?$/);
774              
775 0         0 my $svc;
776 0 0       0 if (defined $mc->arg(1)) {
777 0         0 $svc = $service{$mc->arg(1)};
778 0 0       0 return $mc->err("Service not found.")
779             unless defined $svc;
780             }
781              
782 0         0 my $sf = Perlbal::Socket->get_sock_ref;
783              
784 0         0 my %states; # { "Class" => { "State" => int count; } }
785 0         0 foreach my $sock (values %$sf) {
786 0 0       0 next unless $sock->can('state');
787 0         0 my $state = $sock->state;
788 0 0       0 next unless defined $state;
789 0 0       0 if (defined $svc) {
790 0 0 0     0 next unless $sock->isa('Perlbal::ClientProxy') ||
      0        
791             $sock->isa('Perlbal::BackendHTTP') ||
792             $sock->isa('Perlbal::ClientHTTP');
793 0 0       0 next unless $sock->{service} == $svc;
794             }
795 0         0 $states{ref $sock}->{$state}++;
796             }
797              
798             # now print out text
799 0         0 foreach my $class (sort keys %states) {
800 0         0 foreach my $state (sort keys %{$states{$class}}) {
  0         0  
801 0         0 $mc->out("$class $state " . $states{$class}->{$state});
802             }
803             }
804 0         0 $mc->end;
805             }
806              
807             sub MANAGE_queues {
808 0     0 0 0 my $mc = shift->no_opts;
809 0         0 my $now = time;
810              
811 0         0 foreach my $svc (values %service) {
812 0 0       0 next unless $svc->{role} eq 'reverse_proxy';
813              
814 0         0 my %queues = (
815             normal => 'waiting_clients',
816             highpri => 'waiting_clients_highpri',
817             lowpri => 'waiting_clients_lowpri',
818             );
819              
820 0         0 while (my ($queue_name, $clients_key) = each %queues) {
821 0         0 my $age = 0;
822 0         0 my $count = @{$svc->{$clients_key}};
  0         0  
823 0         0 my Perlbal::ClientProxy $oldest = $svc->{$clients_key}->[0];
824 0 0       0 $age = $now - $oldest->{last_request_time} if defined $oldest;
825 0         0 $mc->out("$svc->{name}-$queue_name.age $age");
826 0         0 $mc->out("$svc->{name}-$queue_name.count $count");
827             }
828             }
829 0         0 $mc->end;
830             }
831              
832             sub MANAGE_state {
833 0     0 0 0 my $mc = shift->parse(qr/^state changes$/);
834 0         0 my $hr = Perlbal::Socket->get_statechange_ref;
835 0         0 my %final; # { "state" => count }
836 0         0 while (my ($obj, $arref) = each %$hr) {
837 0         0 $mc->out("$obj: " . join(', ', @$arref));
838 0         0 $final{$arref->[-1]}++;
839             }
840 0         0 foreach my $k (sort keys %final) {
841 0         0 $mc->out("$k $final{$k}");
842             }
843 0         0 $mc->end;
844             }
845              
846             sub MANAGE_leaks {
847 0     0 0 0 my $mc = shift->parse(qr/^leaks(?:\s+(.+))?$/);
848 0 0       0 return $mc->err("command disabled without \$ENV{PERLBAL_DEBUG} set")
849             unless $ENV{PERLBAL_DEBUG};
850              
851 0         0 my $what = $mc->arg(1);
852              
853             # iterates over active objects. if you specify an argument, it is treated as code
854             # with $_ being the reference to the object.
855             # shows objects that we think might have been leaked
856 0         0 my $ref = Perlbal::Socket::get_created_objects_ref;
857 0         0 foreach (@$ref) {
858 0 0       0 next unless $_; # might be undef!
859 0 0       0 if ($what) {
860 0         0 my $rv = eval "$what";
861 0 0       0 return $mc->err("$@") if $@;
862 0 0       0 next unless defined $rv;
863 0         0 $mc->out($rv);
864             } else {
865 0         0 $mc->out($_->as_string);
866             }
867             }
868 0         0 $mc->end;
869             }
870              
871             sub MANAGE_show {
872 1     1 0 3 my $mc = shift;
873              
874 1 50       6 if ($mc->cmd =~ /^show service (\w+)$/) {
875 0         0 my $sname = $1;
876 0         0 my Perlbal::Service $svc = $service{$sname};
877 0 0       0 return $mc->err("Unknown service") unless $svc;
878 0         0 $svc->stats_info($mc->out);
879 0         0 return $mc->end;
880             }
881              
882 1 50       5 if ($mc->cmd =~ /^show pool(?:\s+(\w+))?$/) {
883 0         0 my $pool = $1;
884 0 0       0 if ($pool) {
885 0         0 my $pl = $pool{$pool};
886 0 0       0 return $mc->err("pool '$pool' does not exist") unless $pl;
887              
888 0         0 foreach my $node (@{ $pl->nodes }) {
  0         0  
889 0         0 my $ipport = "$node->[0]:$node->[1]";
890 0         0 $mc->out($ipport . " " . $pl->node_used($ipport));
891             }
892             } else {
893 0         0 foreach my $name (sort keys %pool) {
894 0         0 my Perlbal::Pool $pl = $pool{$name};
895 0         0 $mc->out("$name nodes $pl->{node_count}");
896 0         0 $mc->out("$name services $pl->{use_count}");
897             }
898             }
899 0         0 return $mc->end;
900             }
901              
902 1 50       5 if ($mc->cmd =~ /^show service$/) {
903 1         19 foreach my $name (sort keys %service) {
904 4         9 my $svc = $service{$name};
905 4   50     14 my $listen = $svc->{listen} || "not_listening";
906 4 50       25 $mc->out("$name $listen " . ($svc->{enabled} ? "ENABLED" : "DISABLED"));
907             }
908 1         7 return $mc->end;
909             }
910              
911 0         0 return $mc->parse_error;
912             }
913              
914             sub MANAGE_server {
915 14     14 0 142 my $mc = shift->parse(qr/^server (\S+) ?= ?(.+)$/);
916 14         94 my ($key, $val) = ($mc->arg(1), $mc->arg(2));
917              
918 14 50       78 if ($key =~ /^max_reproxy_connections(?:\((.+)\))?/) {
919 0 0       0 return $mc->err("Expected numeric parameter") unless $val =~ /^-?\d+$/;
920 0         0 my $hostip = $1;
921 0 0       0 if (defined $hostip) {
922 0         0 $Perlbal::ReproxyManager::ReproxyMax{$hostip} = $val+0;
923             } else {
924 0         0 $Perlbal::ReproxyManager::ReproxyGlobalMax = $val+0;
925             }
926 0         0 return $mc->ok;
927             }
928              
929 14 50       48 if ($key eq "max_connections") {
930 0 0       0 return $mc->err('This command is not available unless BSD::Resource is installed') unless $Perlbal::BSD_RESOURCE_AVAILABLE;
931 0 0       0 return $mc->err("Expected numeric parameter") unless $val =~ /^-?\d+$/;
932 0         0 my $rv = BSD::Resource::setrlimit(BSD::Resource::RLIMIT_NOFILE(), $val, $val);
933 0 0 0     0 unless (defined $rv && $rv) {
934 0 0       0 if ($> == 0) {
935 0         0 $mc->err("Unable to set limit.");
936             } else {
937 0         0 $mc->err("Need to be root to increase max connections.");
938             }
939             }
940 0         0 return $mc->ok;
941             }
942              
943 14 50       61 if ($key eq "nice_level") {
944 0 0       0 return $mc->err("Expected numeric parameter") unless $val =~ /^-?\d+$/;
945 0         0 my $rv = POSIX::nice($val);
946 0 0       0 $mc->err("Unable to renice: $!")
947             unless defined $rv;
948 0         0 return $mc->ok;
949             }
950              
951 14 50       71 if ($key eq "aio_mode") {
952 14 50       86 return $mc->err("Unknown AIO mode") unless $val =~ /^none|linux|ioaio$/;
953 14 50       59 return $mc->err("Linux::AIO no longer supported") if $val eq "linux";
954 14 100 66     168 return $mc->err("IO::AIO not available") if $val eq "ioaio" && ! $Perlbal::OPTMOD_IO_AIO;
955 10         35 $Perlbal::AIO_MODE = $val;
956 10         51 return $mc->ok;
957             }
958              
959 0 0       0 if ($key eq "aio_threads") {
960 0 0       0 return $mc->err("Expected numeric parameter") unless $val =~ /^-?\d+$/;
961 0 0       0 IO::AIO::min_parallel($val)
962             if $Perlbal::OPTMOD_IO_AIO;
963 0         0 return $mc->ok;
964             }
965              
966 0 0       0 if ($key eq "track_obj") {
967 0 0 0     0 return $mc->err("Expected 1 or 0") unless $val eq '1' || $val eq '0';
968 0         0 $track_obj = $val + 0;
969 0 0       0 %ObjTrack = () if $val; # if we're turning it on, clear it out
970 0         0 return $mc->ok;
971             }
972              
973 0 0       0 if ($key eq "pidfile") {
974 0 0       0 return $mc->err("pidfile must be configured at startup, before Perlbal::run is called") if $run_started;
975 0 0       0 return $mc->err("Expected full pathname to pidfile") unless $val;
976 0         0 $pidfile = $val;
977 0         0 return $mc->ok;
978             }
979              
980 0 0       0 if ($key eq "crash_backtrace") {
981 0 0 0     0 return $mc->err("Expected 1 or 0") unless $val eq '1' || $val eq '0';
982 0 0       0 if ($val) {
983 0     0   0 $SIG{__DIE__} = sub { Carp::confess(@_) };
  0         0  
984             } else {
985 0         0 $SIG{__DIE__} = undef;
986             }
987 0         0 return $mc->ok;
988             }
989              
990 0         0 return $mc->err("unknown server option '$val'");
991             }
992              
993             sub MANAGE_dumpconfig {
994 0     0 0 0 my $mc = shift;
995              
996 0         0 while (my ($name, $pool) = each %pool) {
997 0         0 $mc->out("CREATE POOL $name");
998              
999 0 0       0 if ($pool->can("dumpconfig")) {
1000 0         0 foreach my $line ($pool->dumpconfig) {
1001 0         0 $mc->out(" $line");
1002             }
1003             } else {
1004 0         0 my $class = ref($pool);
1005 0         0 $mc->out(" # Pool class '$class' is unable to dump config.");
1006             }
1007             } continue {
1008 0         0 $mc->out("");
1009             }
1010              
1011 0         0 while (my ($name, $service) = each %service) {
1012 0         0 $mc->out("CREATE SERVICE $name");
1013              
1014 0 0       0 if ($service->can("dumpconfig")) {
1015 0         0 foreach my $line ($service->dumpconfig) {
1016 0         0 $mc->out(" $line");
1017             }
1018             } else {
1019 0         0 my $class = ref($service);
1020 0         0 $mc->out(" # Service class '$class' is unable to dump config.");
1021             }
1022              
1023 0 0       0 my $state = $service->{enabled} ? "ENABLE" : "DISABLE";
1024 0         0 $mc->out("$state $name");
1025             } continue {
1026 0         0 $mc->out("");
1027             }
1028              
1029 0         0 return $mc->ok
1030             }
1031              
1032             sub MANAGE_reproxy_state {
1033 0     0 0 0 my $mc = shift;
1034 0         0 Perlbal::ReproxyManager::dump_state($mc->out);
1035 0         0 return 1;
1036             }
1037              
1038             sub MANAGE_create {
1039 52     52 0 443 my $mc = shift->parse(qr/^create (service|pool) (\w+)$/,
1040             "usage: CREATE {service|pool} ");
1041 52         279 my ($what, $name) = $mc->args;
1042              
1043 52 100       259 if ($what eq "service") {
1044 43 50       162 return $mc->err("service '$name' already exists") if $service{$name};
1045 43 50       167 return $mc->err("pool '$name' already exists") if $pool{$name};
1046 43         295 Perlbal->create_service($name);
1047 43         141 $mc->{ctx}{last_created} = $name;
1048 43         215 return $mc->ok;
1049             }
1050              
1051 9 50       67 if ($what eq "pool") {
1052 9 50       42 return $mc->err("pool '$name' already exists") if $pool{$name};
1053 9 50       844 return $mc->err("service '$name' already exists") if $service{$name};
1054 9         27 $vivify_pools = 0;
1055 9         96 $pool{$name} = Perlbal::Pool->new($name);
1056 9         44 $mc->{ctx}{last_created} = $name;
1057 9         59 return $mc->ok;
1058             }
1059             }
1060              
1061             sub MANAGE_use {
1062 0     0 0 0 my $mc = shift->parse(qr/^use (\w+)$/,
1063             "usage: USE ");
1064 0         0 my ($name) = $mc->args;
1065 0 0 0     0 return $mc->err("Non-existent pool or service '$name'") unless $pool{$name} || $service{$name};
1066              
1067 0         0 $mc->{ctx}{last_created} = $name;
1068 0         0 return $mc->ok;
1069             }
1070              
1071             sub MANAGE_pool {
1072 14     14 0 133 my $mc = shift->parse(qr/^pool (\w+) (\w+) (\d+.\d+.\d+.\d+)(?::(\d+))?$/);
1073 14         93 my ($cmd, $name, $ip, $port) = $mc->args;
1074 14   50     71 $port ||= 80;
1075              
1076 14         64 my $good_cmd = qr/^(?:add|remove)$/;
1077              
1078             # "add" and "remove" can be in either order
1079 14 50       143 ($cmd, $name) = ($name, $cmd) if $name =~ /$good_cmd/;
1080 14 50       112 return $mc->err("Invalid command: must be 'add' or 'remove'")
1081             unless $cmd =~ /$good_cmd/;
1082              
1083 14         47 my $pl = $pool{$name};
1084 14 50       61 return $mc->err("Pool '$name' not found") unless $pl;
1085 14         107 $pl->$cmd($ip, $port);
1086 14         71 return $mc->ok;
1087             }
1088              
1089             sub MANAGE_default {
1090 0     0 0 0 my $mc = shift->parse(qr/^default (\w+) ?= ?(.+)$/,
1091             "usage: DEFAULT = ");
1092              
1093 0         0 my ($key, $val) = $mc->args;
1094 0         0 return Perlbal::Service::set_defaults($mc, $key => $val);
1095             }
1096              
1097             sub MANAGE_set {
1098 226     226 0 2627 my $mc = shift->parse(qr/^set (?:(\w+)[\. ])?([\w\.]+) ?= ?(.+)$/,
1099             "usage: SET [] = ");
1100 226         1033 my ($name, $key, $val) = $mc->args;
1101 226 50 66     1059 unless ($name ||= $mc->{ctx}{last_created}) {
1102 0         0 return $mc->err("omitted service/pool name not implied from context");
1103             }
1104              
1105 226 50       815 if (my Perlbal::Service $svc = $service{$name}) {
    0          
1106 226         2208 return $svc->set($key, $val, $mc);
1107             } elsif (my Perlbal::Pool $pl = $pool{$name}) {
1108 0         0 return $pl->set($key, $val, $mc);
1109             }
1110 0         0 return $mc->err("service/pool '$name' does not exist");
1111             }
1112              
1113              
1114             sub MANAGE_header {
1115 2     2 0 18 my $mc = shift->parse(qr/^header\s+(\w+)\s+(insert|remove)\s+(.+?)(?:\s*:\s*(.+))?$/i,
1116             "Usage: HEADER {INSERT|REMOVE}
[: ]");
1117              
1118 2         11 my ($svc_name, $action, $header, $val) = $mc->args;
1119 2         7 my $svc = $service{$svc_name};
1120 2 50       7 return $mc->err("service '$svc_name' does not exist") unless $svc;
1121 2         9 return $svc->header_management($action, $header, $val, $mc);
1122             }
1123              
1124             sub MANAGE_enable {
1125 42     42 0 301 my $mc = shift->parse(qr/^(disable|enable) (\w+)$/,
1126             "Usage: {ENABLE|DISABLE} ");
1127 42         199 my ($verb, $name) = $mc->args;
1128 42         193 my $svc = $service{$name};
1129 42 50       384 return $mc->err("service '$name' does not exist") unless $svc;
1130 42         269 return $svc->$verb($mc);
1131             }
1132             *MANAGE_disable = \&MANAGE_enable;
1133              
1134             sub MANAGE_unload {
1135 0     0 0 0 my $mc = shift->parse(qr/^unload (\w+)$/);
1136 0         0 my ($fn) = $mc->args;
1137 0         0 $fn = $PluginCase{lc $fn};
1138 0         0 my $rv = eval "Perlbal::Plugin::$fn->unload; 1;";
1139 0         0 $plugins{$fn} = 0;
1140 0         0 return $mc->ok;
1141             }
1142              
1143              
1144             sub MANAGE_load {
1145 6     6 0 59 my $mc = shift->parse(qr/^load \w+$/);
1146              
1147 6         20 my $fn;
1148 6 50       31 $fn = $1 if $mc->orig =~ /^load (\w+)$/i;
1149              
1150 6         13 my $last_case;
1151             my $last_class;
1152              
1153 0         0 my $good_error;
1154              
1155             # TODO case protection
1156              
1157 6         30 foreach my $name ($fn, lc $fn, ucfirst lc $fn) {
1158 6         23 $last_case = $name;
1159 6         22 my $class = $last_class = "Perlbal::Plugin::$name";
1160 6         30 my $file = $class . ".pm";
1161 6         39 $file =~ s!::!/!g;
1162              
1163 6     5   821 my $rv = eval "use $class; $class->can('load');";
  5         129858  
  5         18  
  5         177  
1164              
1165 6 50       79 if ($rv) {
1166 6         43 $good_error = undef;
1167 6         24 last;
1168             }
1169              
1170             # If we don't have a good error yet, start with this one.
1171 0 0       0 $good_error = $@ unless defined $good_error;
1172              
1173             # If the file existed perl will place an entry in %INC (though it will be undef due to compilation error)
1174 0 0 0     0 if ($@ and exists $INC{$file}) {
1175 0         0 $good_error = $@;
1176 0         0 last;
1177             }
1178             }
1179              
1180 6 50       27 unless (defined $good_error) {
1181 6         354 my $rv = eval "$last_class->load; 1;";
1182              
1183 6 50       39 if ($rv) {
1184 6         30 $PluginCase{lc $fn} = $last_case;
1185 6         19 $plugins{$last_case} = $last_class;
1186 6         69 return $mc->ok;
1187             }
1188              
1189 0         0 $good_error = $@;
1190             }
1191              
1192 0         0 return $mc->err($good_error);
1193             }
1194              
1195             sub MANAGE_reload {
1196 0     0 0 0 my $mc = shift->parse(qr/^reload (\w+)$/);
1197 0         0 my ($fn) = $mc->args;
1198              
1199 0 0       0 my $class = $PluginCase{lc $fn} or
1200             return $mc->err("Unknown/unloaded plugin '$fn'");
1201 0         0 $class = "Perlbal::Plugin::$class";
1202              
1203 0 0       0 eval "$class->can_reload" or
1204             return $mc->err("Plugin $class doesn't support reloading");
1205              
1206 0 0       0 if ($class->can("pre_reload_unload")) {
1207 0 0       0 eval "$class->pre_reload_unload; 1" or
1208             return $mc->err("Error running $class->pre_reload_unload: $@");
1209             }
1210              
1211 0 0       0 eval "$class->unload; 1;" or
1212             return $mc->err("Failed to unload $class: $@");
1213              
1214 0         0 my $file = $class . ".pm";
1215 0         0 $file =~ s!::!/!g;
1216              
1217 0 0       0 delete $INC{$file} or
1218             die $mc->err("Didn't find $file in %INC");
1219              
1220 22     22   310 no warnings 'redefine';
  22         96  
  22         39328  
1221 0 0       0 eval "use $class; $class->load; 1;" or
1222             return $mc->err("Failed to reload: $@");
1223              
1224 0         0 return $mc->ok;
1225             }
1226              
1227             sub MANAGE_plugins {
1228 0     0 0 0 my $mc = shift->no_opts;
1229 0         0 foreach my $svc (values %service) {
1230 0 0       0 next unless @{$svc->{plugin_order}};
  0         0  
1231 0         0 $mc->out(join(' ', $svc->{name}, @{$svc->{plugin_order}}));
  0         0  
1232             }
1233 0         0 $mc->end;
1234             }
1235              
1236             sub MANAGE_help {
1237 0     0 0 0 my $mc = shift->no_opts;
1238 0 0       0 my @commands = sort map { m/^MANAGE_(\S+)$/ ? $1 : () }
  0         0  
1239             keys %Perlbal::;
1240 0         0 foreach my $command (@commands) {
1241 0         0 $mc->out("$command");
1242             }
1243 0         0 $mc->end;
1244             }
1245              
1246             sub MANAGE_aio {
1247 0     0 0 0 my $mc = shift->no_opts;
1248 0         0 my $stats = Perlbal::AIO::get_aio_stats();
1249 0         0 foreach my $c (sort keys %$stats) {
1250 0         0 my $r = $stats->{$c};
1251 0         0 foreach my $k (keys %$r) {
1252 0         0 $mc->out("$c $k $r->{$k}");
1253             }
1254             }
1255 0         0 $mc->end;
1256             }
1257              
1258             sub load_config {
1259 4     4 0 10 my ($file, $writer) = @_;
1260 4 100       259 open (my $fh, $file) or die "Error opening config file ($file): $!\n";
1261 3         27 my $ctx = Perlbal::CommandContext->new;
1262 3         14 $ctx->verbose(0);
1263 3         59 while (my $line = <$fh>) {
1264 24 50       67 return 0 unless run_manage_command($line, $writer, $ctx);
1265             }
1266 3         41 close($fh);
1267 3         39 return 1;
1268             }
1269              
1270             sub daemonize {
1271 0     0 0 0 my($pid, $sess_id, $i);
1272              
1273             # note that we're not in the foreground (for logging purposes)
1274 0         0 $foreground = 0;
1275              
1276             # required before fork: (as of old Linux::AIO 1.1, still true?)
1277 0 0       0 IO::AIO::max_parallel(0)
1278             if $Perlbal::OPTMOD_IO_AIO;
1279              
1280 0         0 my $sigset = POSIX::SigSet->new(SIGINT);
1281 0 0       0 sigprocmask(SIG_BLOCK, $sigset)
1282             or die "Can't block sigint for fork: $!";
1283              
1284             ## Fork and exit parent
1285 0 0       0 if ($pid = fork) { exit 0; }
  0         0  
1286              
1287 0 0       0 sigprocmask(SIG_UNBLOCK, $sigset)
1288             or die "Can't unblock sigint after fork: $!";
1289              
1290             ## Detach ourselves from the terminal
1291 0 0       0 croak "Cannot detach from controlling terminal"
1292             unless $sess_id = POSIX::setsid();
1293              
1294             # Handler for INT needs to be restored.
1295 0         0 $SIG{INT} = 'DEFAULT';
1296              
1297             ## Change working directory
1298 0         0 chdir "/";
1299              
1300             ## Clear file creation mask
1301 0         0 umask 0;
1302              
1303             ## Close open file descriptors
1304 0         0 close(STDIN);
1305 0         0 close(STDOUT);
1306 0         0 close(STDERR);
1307              
1308             ## Reopen stderr, stdout, stdin to /dev/null
1309 0         0 open(STDIN, "+>/dev/null");
1310 0         0 open(STDOUT, "+>&STDIN");
1311 0         0 open(STDERR, "+>&STDIN");
1312             }
1313              
1314             # For other apps using Danga::Socket that want to embed Perlbal, this can be called
1315             # directly to start it up. You can call this as many times as you like; it'll
1316             # only actually do what it does the first time it's called.
1317             sub initialize {
1318 17 50   17 0 91 unless ($run_started) {
1319 17         38 $run_started = 1;
1320              
1321             # number of AIO threads. the number of outstanding requests isn't
1322             # affected by this
1323 17 50       76 IO::AIO::min_parallel(3) if $Perlbal::OPTMOD_IO_AIO;
1324              
1325             # register IO::AIO pipe which gets written to from threads
1326             # doing blocking IO
1327 17 50       64 if ($Perlbal::OPTMOD_IO_AIO) {
1328 0         0 Perlbal::Socket->AddOtherFds(IO::AIO::poll_fileno() =>
1329             \&IO::AIO::poll_cb);
1330             }
1331              
1332             # The fact that this only runs the first time someone calls initialize()
1333             # means that some things which depend on it might be unreliable when
1334             # used in an embedded perlbal if there is a race for multiple components
1335             # to call initialize().
1336 17         75 run_global_hook("pre_event_loop");
1337             }
1338             }
1339              
1340             # This is the function to call if you want Perlbal to be in charge of the event loop.
1341             # It won't return until Perlbal is somehow told to exit.
1342             sub run {
1343              
1344             # setup for logging
1345 17 50   17 0 172 Sys::Syslog::openlog('perlbal', 'pid', 'daemon') if $Perlbal::SYSLOG_AVAILABLE;
1346 17         590 $Perlbal::syslog_open = 1;
1347 17         90 Perlbal::log('info', 'beginning run');
1348 17         41 my $pidfile_written = 0;
1349 17 50       89 $pidfile_written = _write_pidfile( $pidfile ) if $pidfile;
1350              
1351 17         398 Perlbal::initialize();
1352              
1353 17         772 Danga::Socket->SetLoopTimeout(1000);
1354             Danga::Socket->SetPostLoopCallback(sub {
1355 830     830   11766956 $Perlbal::tick_time = time();
1356 830         4780 Perlbal::Socket::run_callbacks();
1357 830         4069 return 1;
1358 17         232 });
1359              
1360             # begin the overall loop to try to capture if Perlbal dies at some point
1361             # so we can have a log of it
1362 17         164 eval {
1363             # wait for activity
1364 17         177 Perlbal::Socket->EventLoop();
1365             };
1366              
1367 0         0 my $clean_exit = 1;
1368              
1369             # closing messages
1370 0 0       0 if ($@) {
1371 0         0 Perlbal::log('crit', "crash log: $_") foreach split(/\r?\n/, $@);
1372 0         0 $clean_exit = 0;
1373             }
1374              
1375             # Note: This will only actually remove the pidfile on 'shutdown graceful'
1376             # A more reliable approach might be to have a pidfile object which fires
1377             # removal on DESTROY.
1378 0 0       0 _remove_pidfile( $pidfile ) if $pidfile_written;
1379              
1380 0         0 Perlbal::log('info', 'ending run');
1381 0         0 $Perlbal::syslog_open = 0;
1382 0 0       0 Sys::Syslog::closelog() if $Perlbal::SYSLOG_AVAILABLE;
1383              
1384 0         0 return $clean_exit;
1385             }
1386              
1387             sub log {
1388             # simple logging functionality
1389 17 50   17 0 68 if ($foreground) {
1390             # syslog acts like printf so we have to use printf and append a \n
1391 17         34 shift; # ignore the first parameter (info, warn, crit, etc)
1392 17         41 my $message = shift;
1393 17 50       88 if (@_) {
1394 0         0 printf("$message\n", @_);
1395             } else {
1396 17         4875 print("$message\n");
1397             }
1398             } else {
1399             # just pass the parameters to syslog
1400 0 0       0 Sys::Syslog::syslog(@_) if $Perlbal::syslog_open;
1401             }
1402             }
1403              
1404              
1405             sub _write_pidfile {
1406 0     0   0 my $file = shift;
1407              
1408 0         0 my $fh;
1409 0 0       0 unless (open($fh, ">$file")) {
1410 0         0 Perlbal::log('info', "couldn't create pidfile '$file': $!" );
1411 0         0 return 0;
1412             }
1413 0 0 0     0 unless ((print $fh "$$\n") && close($fh)) {
1414 0         0 Perlbal::log('info', "couldn't write into pidfile '$file': $!" );
1415 0         0 _remove_pidfile($file);
1416 0         0 return 0;
1417             }
1418 0         0 return 1;
1419             }
1420              
1421              
1422             sub _remove_pidfile {
1423 0     0   0 my $file = shift;
1424            
1425 0         0 unlink $file;
1426 0         0 return 1;
1427             }
1428              
1429              
1430             # Local Variables:
1431             # mode: perl
1432             # c-basic-indent: 4
1433             # indent-tabs-mode: nil
1434             # End:
1435              
1436             1;