root/trunk/lib/Perlbal.pm

Revision 829, 39.0 kB (checked in by hachi, 5 weeks ago)

Reimplement MANAGE_load.

this fixes some plugin loading problems, like hidden error messages and things.

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