###################################################################### # Service class ###################################################################### package Perlbal::Service; use strict; use warnings; use Net::Netmask; use Perlbal::BackendHTTP; use fields ( 'name', 'enabled', # bool 'role', # currently 'reverse_proxy' or 'management' 'listen', # scalar: "$ip:$port" 'pool', # Perlbal::Pool that we're using to allocate nodes if we're in proxy mode 'docroot', # document root for webserver role 'dirindexing', # bool: direcotry indexing? (for webserver role) not async. 'index_files', # arrayref of filenames to try for index files 'listener', 'waiting_clients', # arrayref of clients waiting for backendhttp conns 'waiting_clients_highpri', # arrayref of high-priority clients waiting for backendhttp conns 'waiting_client_count', # number of clients waiting for backendds 'waiting_client_map' , # map of clientproxy fd -> 1 (if they're waiting for a conn) 'pending_connects', # hashref of "ip:port" -> $time (only one pending connect to backend at a time) 'pending_connect_count', # number of outstanding backend connects 'high_priority_cookie', # cookie name to check if client can 'cut in line' and get backends faster 'high_priority_cookie_contents', # aforementioned cookie value must contain this substring 'connect_ahead', # scalar: number of spare backends to connect to in advance all the time 'backend_persist_cache', # scalar: max number of persistent backends to hold onto while no clients 'bored_backends', # arrayref of backends we've already connected to, but haven't got clients 'persist_client', # bool: persistent connections for clients 'persist_backend', # bool: persistent connections for backends 'verify_backend', # bool: get attention of backend before giving it clients (using OPTIONS) 'max_backend_uses', # max requests to send per kept-alive backend (default 0 = unlimited) 'hooks', # hashref: hookname => [ [ plugin, ref ], [ plugin, ref ], ... ] 'plugins', # hashref: name => 1 'plugin_order', # arrayref: name, name, name... 'plugin_setters', # hashref: { plugin_name => { key_name => coderef } } 'extra_config', # hashref: extra config options; name => values 'enable_put', # bool: whether PUT is supported 'max_put_size', # int: max size in bytes of a put file 'min_put_directory', # int: number of directories required to exist at beginning of URIs in put 'enable_delete', # bool: whether DELETE is supported 'buffer_size', # int: specifies how much data a ClientProxy object should buffer from a backend 'buffer_size_reproxy_url', # int: same as above but for backends that are reproxying for us 'spawn_lock', # bool: if true, we're currently in spawn_backends 'queue_relief_size', # int; number of outstanding standard priority # connections to activate pressure relief at 'queue_relief_chance', # int:0-100; % chance to take a standard priority # request when we're in pressure relief mode 'trusted_upstreams', # Net::Netmask object containing netmasks for trusted upstreams 'always_trusted', # bool; if true, always trust upstreams 'extra_headers', # { insert => [ [ header, value ], ... ], remove => [ header, header, ... ], # set => [ [ header, value ], ... ] }; used in header management interface 'generation', # int; generation count so we can slough off backends from old pools 'backend_no_spawn', # { "ip:port" => 1 }; if on, spawn_backends will ignore this ip:port combo 'buffer_backend_connect', # 0 for of, else, number of bytes to buffer before we ask for a backend ); sub new { my Perlbal::Service $self = shift; $self = fields::new($self) unless ref $self; my ($name) = @_; $self->{name} = $name; $self->{enabled} = 0; $self->{listen} = ""; $self->{persist_client} = 0; $self->{persist_backend} = 0; $self->{verify_backend} = 0; $self->{max_backend_uses} = 0; $self->{backend_persist_cache} = 2; $self->{generation} = 0; $self->{backend_no_spawn} = {}; $self->{buffer_backend_connect} = 0; $self->{hooks} = {}; $self->{plugins} = {}; $self->{plugin_order} = []; $self->{enable_put} = 0; $self->{max_put_size} = 0; # 0 means no max size $self->{min_put_directory} = 0; $self->{enable_delete} = 0; # disable pressure relief by default $self->{queue_relief_size} = 0; $self->{queue_relief_chance} = 0; # set some default maximum buffer sizes $self->{buffer_size} = 256_000; $self->{buffer_size_reproxy_url} = 51_200; # track pending connects to backend $self->{pending_connects} = {}; $self->{pending_connect_count} = 0; $self->{bored_backends} = []; $self->{connect_ahead} = 0; # waiting clients $self->{waiting_clients} = []; $self->{waiting_clients_highpri} = []; $self->{waiting_client_count} = 0; # directory handling $self->{dirindexing} = 0; $self->{index_files} = [ 'index.html' ]; # don't have an object for this yet $self->{trusted_upstreams} = undef; $self->{always_trusted} = 0; # bare data structure for extra header info $self->{extra_headers} = { remove => [], insert => [] }; return $self; } # run the hooks in a list one by one until one hook returns 1. returns # 1 or 0 depending on if any hooks handled the request. sub run_hook { my Perlbal::Service $self = shift; my $hook = shift; if (defined (my $ref = $self->{hooks}->{$hook})) { # call all the hooks until one returns true foreach my $hookref (@$ref) { my $rval = $hookref->[1]->(@_); return 1 if defined $rval && $rval; } } return 0; } # run a bunch of hooks in this service, always returns undef. sub run_hooks { my Perlbal::Service $self = shift; my $hook = shift; if (defined (my $ref = $self->{hooks}->{$hook})) { # call all the hooks $_->[1]->(@_) foreach @$ref; } return undef; } # define a hook for this service sub register_hook { my Perlbal::Service $self = shift; my ($pclass, $hook, $ref) = @_; push @{$self->{hooks}->{$hook} ||= []}, [ $pclass, $ref ]; return 1; } # remove hooks we have defined sub unregister_hook { my Perlbal::Service $self = shift; my ($pclass, $hook) = @_; if (defined (my $refs = $self->{hooks}->{$hook})) { my @new; foreach my $ref (@$refs) { # fill @new with hooks that DON'T match push @new, $ref unless $ref->[0] eq $pclass; } $self->{hooks}->{$hook} = \@new; return 1; } return undef; } # remove all hooks of a certain class sub unregister_hooks { my Perlbal::Service $self = shift; foreach my $hook (keys %{$self->{hooks}}) { # call unregister_hook with this hook name $self->unregister_hook($_[0], $hook); } } # register a value setter for plugin configuration sub register_setter { my Perlbal::Service $self = shift; my ($pclass, $key, $coderef) = @_; return unless $pclass && $key && $coderef; $self->{plugin_setters}->{lc $pclass}->{lc $key} = $coderef; } # remove a setter sub unregister_setter { my Perlbal::Service $self = shift; my ($pclass, $key) = @_; return unless $pclass && $key; delete $self->{plugin_setters}->{lc $pclass}->{lc $key}; } # remove a bunch of setters sub unregister_setters { my Perlbal::Service $self = shift; my $pclass = shift; return unless $pclass; delete $self->{plugin_setters}->{lc $pclass}; } # take a backend we've created and mark it as pending if we do not # have another pending backend connection in this slot sub add_pending_connect { my Perlbal::Service $self = shift; my Perlbal::BackendHTTP $be = shift; # error if we already have a pending connection for this ipport if (defined $self->{pending_connects}{$be->{ipport}}) { Perlbal::log('warning', "Warning: attempting to spawn backend connection that already existed."); # now dump a backtrace so we know how we got here my $depth = 0; while (my ($package, $filename, $line, $subroutine) = caller($depth++)) { Perlbal::log('warning', " -- [$filename:$line] $package::$subroutine"); } # we're done now, just return return; } # set this connection up in the pending connection list $self->{pending_connects}{$be->{ipport}} = $be; $self->{pending_connect_count}++; } # remove a backend connection from the pending connect list if and only # if it is the actual connection contained in the list; prevent double # decrementing on accident sub clear_pending_connect { my Perlbal::Service $self = shift; my Perlbal::BackendHTTP $be = shift; if (defined $self->{pending_connects}{$be->{ipport}} && defined $be && $self->{pending_connects}{$be->{ipport}} == $be) { $self->{pending_connects}{$be->{ipport}} = undef; $self->{pending_connect_count}--; } } # called by BackendHTTP when it's closed by any means sub note_backend_close { my Perlbal::Service $self = shift; my Perlbal::BackendHTTP $be = shift; $self->clear_pending_connect($be); $self->spawn_backends; } # called by ClientProxy when it dies. sub note_client_close { my Perlbal::Service $self; my Perlbal::ClientProxy $cp; ($self, $cp) = @_; if (delete $self->{waiting_client_map}{$cp->{fd}}) { $self->{waiting_client_count}--; } } sub mark_node_used { my Perlbal::Service $self = $_[0]; $self->{pool}->mark_node_used($_[1]) if $self->{pool}; } sub get_client { my Perlbal::Service $self = shift; my $ret = sub { my Perlbal::ClientProxy $cp = shift; $self->{waiting_client_count}--; delete $self->{waiting_client_map}{$cp->{fd}}; # before we return, start another round of connections $self->spawn_backends; return $cp; }; # determine if we should jump straight to the high priority queue or # act as pressure relief on the standard queue my $hp_first = 1; if (($self->{queue_relief_size} > 0) && (scalar(@{$self->{waiting_clients}}) >= $self->{queue_relief_size})) { # if we're below the chance level, take a standard queue item $hp_first = 0 if rand(100) < $self->{queue_relief_chance}; } # find a high-priority client, or a regular one my Perlbal::ClientProxy $cp; while ($hp_first && ($cp = shift @{$self->{waiting_clients_highpri}})) { if (Perlbal::DEBUG >= 2) { my $backlog = scalar @{$self->{waiting_clients}}; print "Got from fast queue, in front of $backlog others\n"; } return $ret->($cp) if ! $cp->{closed}; } while ($cp = shift @{$self->{waiting_clients}}) { if (Perlbal::DEBUG >= 2) { print "Backend requesting client, got normal = $cp->{fd}.\n" unless $cp->{closed}; } return $ret->($cp) if ! $cp->{closed}; } return undef; } # given a backend, verify it's generation sub verify_generation { my Perlbal::Service $self = $_[0]; my Perlbal::BackendHTTP $be = $_[1]; # fast cases: generation count matches, so we just return an 'okay!' flag return 1 if $self->{generation} == $be->generation; # if our current pool knows about this ip:port, then we can still use it if (defined $self->{pool}->node_used($be->ipport)) { # so we know this is good, in the future we just want to hit the fast case # and continue, so let's update the generation $be->generation($self->{generation}); return 1; } # if we get here, the backend should be closed $be->close('invalid_generation'); return 0; } # called by backend connection after it becomes writable sub register_boredom { my Perlbal::Service $self; my Perlbal::BackendHTTP $be; ($self, $be) = @_; # note that this backend is no longer pending a connect, # if we thought it was before. but not if it's a persistent # connection asking to be re-used. unless ($be->{use_count}) { $self->clear_pending_connect($be); } # it is possible that this backend is part of a different pool that we're # no longer using... if that's the case, we want to close it return unless $self->verify_generation($be); # now try to fetch a client for it my Perlbal::ClientProxy $cp = $self->get_client; if ($cp) { if ($be->assign_client($cp)) { return; } else { # don't want to lose client, so we (unfortunately) # stick it at the end of the waiting queue. # fortunately, assign_client shouldn't ever fail. $self->request_backend_connection($cp); } } # don't hang onto more bored, persistent connections than # has been configured for connect-ahead if ($be->{use_count}) { my $current_bored = scalar @{$self->{bored_backends}}; if ($current_bored >= $self->{backend_persist_cache}) { $be->close('too_many_bored'); return; } } # put backends which are known to be bound to processes # and not to TCP stacks at the beginning where they'll # be used first if ($be->{has_attention}) { unshift @{$self->{bored_backends}}, $be; } else { push @{$self->{bored_backends}}, $be; } } sub note_bad_backend_connect { my Perlbal::Service $self = shift; my Perlbal::BackendHTTP $be = shift; my $retry_time = shift(); # clear this pending connection $self->clear_pending_connect($be); # mark this host as dead for a while if we need to if (defined $retry_time && $retry_time > 0) { # we don't want other spawn_backends calls to retry $self->{backend_no_spawn}->{$be->{ipport}} = 1; # and now we set a callback to ensure we're kicked at the right time Perlbal::Socket::register_callback($retry_time, sub { delete $self->{backend_no_spawn}->{$be->{ipport}}; $self->spawn_backends; }); } # FIXME: do something interesting (tell load balancer about dead host, # and fire up a new connection, if warranted) # makes a new connection, if needed $self->spawn_backends; } sub request_backend_connection { my Perlbal::Service $self; my Perlbal::ClientProxy $cp; ($self, $cp) = @_; my $hi_pri = 0; # by default, low priority # is there a defined high-priority cookie? if (my $cname = $self->{high_priority_cookie}) { # decide what priority class this request is in my $hd = $cp->{req_headers}; my %cookie; foreach (split(/;\s+/, $hd->header("Cookie") || '')) { next unless ($_ =~ /(.*)=(.*)/); $cookie{_durl($1)} = _durl($2); } my $hicookie = $cookie{$cname} || ""; $hi_pri = index($hicookie, $self->{high_priority_cookie_contents}) != -1; } # now, call hook to see if this should be high priority $hi_pri = $self->run_hook('make_high_priority', $cp) unless $hi_pri; # only if it's not already $cp->{high_priority} = 1 if $hi_pri; # before we even consider spawning backends, let's see if we have # some bored (pre-connected) backends that'd take this client my Perlbal::BackendHTTP $be; my $now = time; while ($be = shift @{$self->{bored_backends}}) { next if $be->{closed}; # now make sure that it's still in our pool, and if not, close it next unless $self->verify_generation($be); # don't use connect-ahead connections when we haven't # verified we have their attention if (! $be->{has_attention} && $be->{create_time} < $now - 5) { $be->close("too_old_bored"); next; } # don't use keep-alive connections if we know the server's # just about to kill the connection for being idle if ($be->{disconnect_at} && $now + 2 > $be->{disconnect_at}) { $be->close("too_close_disconnect"); next; } # give the backend this client if ($be->assign_client($cp)) { # and make some extra bored backends, if configured as such $self->spawn_backends; return; } } if ($hi_pri) { push @{$self->{waiting_clients_highpri}}, $cp; } else { push @{$self->{waiting_clients}}, $cp; } $self->{waiting_client_count}++; $self->{waiting_client_map}{$cp->{fd}} = 1; $self->spawn_backends; } # sees if it should spawn one or more backend connections sub spawn_backends { my Perlbal::Service $self = shift; # to spawn we must have a pool return unless $self->{pool}; # check our lock and set it if we can return if $self->{spawn_lock}; $self->{spawn_lock} = 1; # sanity checks on our bookkeeping if ($self->{pending_connect_count} < 0) { Perlbal::log('crit', "Bogus: service $self->{name} has pending connect ". "count of $self->{pending_connect_count}?! Resetting."); $self->{pending_connect_count} = scalar map { $_ && ! $_->{closed} } values %{$self->{pending_connects}}; } # keep track of the sum of existing_bored + bored_created my $backends_created = scalar(@{$self->{bored_backends}}) + $self->{pending_connect_count}; my $backends_needed = $self->{waiting_client_count} + $self->{connect_ahead}; my $to_create = $backends_needed - $backends_created; # can't create more than this, assuming one pending connect per node my $max_creatable = $self->{pool}->node_count - $self->{pending_connect_count}; $to_create = $max_creatable if $to_create > $max_creatable; # cap number of attempted connects at once $to_create = 10 if $to_create > 10; my $now = time; while ($to_create > 0) { $to_create--; my ($ip, $port) = $self->{pool}->get_backend_endpoint; unless ($ip) { Perlbal::log('crit', "No backend IP for service $self->{name}"); # FIXME: register desperate flag, so load-balancer module can callback when it has a node $self->{spawn_lock} = 0; return; } # handle retry timeouts so we don't spin next if $self->{backend_no_spawn}->{"$ip:$port"}; # if it's pending, verify the pending one is still valid if (my Perlbal::BackendHTTP $be = $self->{pending_connects}{"$ip:$port"}) { my $age = $now - $be->{create_time}; if ($age >= 5 && $be->{state} eq "connecting") { $be->close('connect_timeout'); } elsif ($age >= 60 && $be->{state} eq "verifying_backend") { # after 60 seconds of attempting to verify, we're probably already dead $be->close('verify_timeout'); } elsif (! $be->{closed}) { next; } } # now actually spawn a backend and add it to our pending list if (my $be = Perlbal::BackendHTTP->new($self, $ip, $port, { pool => $self->{pool}, generation => $self->{generation} })) { $self->add_pending_connect($be); } } # clear our spawn lock $self->{spawn_lock} = 0; } # getter only sub role { my Perlbal::Service $self = shift; return $self->{role}; } # manage some header stuff sub header_management { my Perlbal::Service $self = shift; my ($mode, $key, $val, $out) = @_; my $err = sub { $out->("ERROR: $_[0]"); return 0; }; return $err->("no header provided") unless $key; return $err->("no value provided") unless $val || $mode eq 'remove'; if ($mode eq 'insert') { push @{$self->{extra_headers}->{insert}}, [ $key, $val ]; } elsif ($mode eq 'remove') { push @{$self->{extra_headers}->{remove}}, $key; } else { return $err->("invalid mode '$mode'"); } return 1; } sub munge_headers { my Perlbal::Service $self = $_[0]; my Perlbal::HTTPHeaders $hdrs = $_[1]; # handle removals first foreach my $hdr (@{$self->{extra_headers}->{remove}}) { $hdrs->header($hdr, undef); } # and now insertions foreach my $hdr (@{$self->{extra_headers}->{insert}}) { $hdrs->header($hdr->[0], $hdr->[1]); } } # Service sub set { my Perlbal::Service $self = shift; my ($key, $val, $out, $verbose) = @_; my $err = sub { $out->("ERROR: $_[0]"); return 0; }; my $ok = sub { $out->("OK") if $verbose; return 1; }; my $set = sub { $self->{$key} = $val; return $ok->(); }; my $pool_set = sub { # if we don't have a pool, automatically create one named $NAME_pool unless ($self->{pool}) { # die if necessary die "ERROR: Attempt to vivify pool $self->{name}_pool but one or more pools\n" . " have already been created manually. Please set $key on a\n" . " previously created pool.\n" unless $Perlbal::vivify_pools; # create the pool and ensure that vivify stays on Perlbal::run_manage_command("CREATE POOL $self->{name}_pool", $out); Perlbal::run_manage_command("SET $self->{name}.pool = $self->{name}_pool"); $Perlbal::vivify_pools = 1; } # now we actually do the set warn "WARNING: '$key' set on service $self->{name} on auto-vivified pool.\n" . " This behavior is obsolete. This value should be set on a\n" . " pool object and not on a service.\n" if $Perlbal::vivify_pools; return $err->("No pool defined for service") unless $self->{pool}; return $self->{pool}->set($key, $val, $out, $verbose); }; if ($key eq "role") { return $err->("Unknown service role") unless $val eq "reverse_proxy" || $val eq "management" || $val eq "web_server"; return $set->(); } if ($key eq "listen") { return $err->("Invalid host:port") unless $val =~ m!^\d+\.\d+\.\d+\.\d+:\d+$!; # close/reopen listening socket if ($val ne $self->{listen} && $self->{enabled}) { $self->disable(undef, "force"); $self->{listen} = $val; $self->enable(undef); } return $set->(); } my $bool = sub { my $val = shift; return 1 if $val =~ /^1|true|on|yes$/i; return 0 if $val =~ /^0|false|off|no$/i; return undef; }; if ($key eq 'trusted_upstream_proxies') { if ($self->{trusted_upstreams} = Net::Netmask->new2($val)) { # set, all good return $ok->(); } else { return $err->("Error defining trusted upstream proxies: " . Net::Netmask::errstr()); } } if ($key eq 'always_trusted') { $val = $bool->($val); return $err->("Expecting boolean value for option '$key'") unless defined $val; return $set->(); } if ($key eq 'enable_put' || $key eq 'enable_delete') { return $err->("This can only be used on web_server service") unless $self->{role} eq 'web_server'; $val = $bool->($val); return $err->("Expecting boolean value for option '$key'.") unless defined $val; return $set->(); } if ($key eq "persist_client" || $key eq "persist_backend" || $key eq "verify_backend") { $val = $bool->($val); return $err->("Expecting boolean value for option '$key'") unless defined $val; return $set->(); } # this is now handled by Perlbal::Pool, so we pass this set command on # through in case people try to use it on us like the old method. return $pool_set->() if $key eq 'balance_method' || $key eq 'nodefile' || $key =~ /^sendstats\./; if ($key eq "balance_method") { return $err->("Can only set balance method on a reverse_proxy service") unless $self->{role} eq "reverse_proxy"; } if ($key eq "high_priority_cookie" || $key eq "high_priority_cookie_contents") { return $set->(); } if ($key eq "connect_ahead") { return $err->("Expected integer value") unless $val =~ /^\d+$/; $set->(); $self->spawn_backends if $self->{enabled}; return $ok->(); } if ($key eq "max_backend_uses" || $key eq "backend_persist_cache" || $key eq "max_put_size" || $key eq "min_put_directory" || $key eq "buffer_size" || $key eq "buffer_size_reproxy_url" || $key eq "queue_relief_size" || $key eq "buffer_backend_connect") { return $err->("Expected integer value") unless $val =~ /^\d+$/; return $set->(); } if ($key eq "queue_relief_chance") { return $err->("Expected integer value") unless $val =~ /^\d+$/; return $err->("Expected integer value between 0 and 100 inclusive") unless $val >= 0 && $val <= 100; return $set->(); } if ($key eq "docroot") { return $err->("Can only set docroot on a web_server service") unless $self->{role} eq "web_server"; $val =~ s!/$!!; return $err->("Directory not found") unless $val && -d $val; return $set->(); } if ($key eq "dirindexing") { return $err->("Can only set dirindexing on a web_server service") unless $self->{role} eq "web_server"; return $err->("Expected value 0 or 1") unless $val eq '0' || $val eq '1'; return $set->(); } if ($key eq "index_files") { return $err->("Can only set index_files on a web_server service") unless $self->{role} eq "web_server"; my @list = split(/[\s,]+/, $val); $self->{index_files} = \@list; return $ok->(); } if ($key eq 'plugins') { # unload existing plugins foreach my $plugin (keys %{$self->{plugins}}) { eval "Perlbal::Plugin::$plugin->unregister(\$self);"; return $err->($@) if $@; } # clear out loaded plugins and hooks $self->{hooks} = {}; $self->{plugins} = {}; $self->{plugin_order} = []; # load some plugins foreach my $plugin (split /[\s,]+/, $val) { next if $plugin eq 'none'; # since we lowercase our input, uppercase the first character here my $fn = uc($1) . lc($2) if $plugin =~ /^(.)(.*)$/; next if $self->{plugins}->{$fn}; unless ($Perlbal::plugins{$fn}) { $err->("Plugin $fn not loaded; not registered for $self->{name}."); next; } # now register it eval "Perlbal::Plugin::$fn->register(\$self);"; $self->{plugins}->{$fn} = 1; push @{$self->{plugin_order}}, $fn; return $err->($@) if $@; } return $ok->(); } if ($key =~ /^extra\.(.+)$/) { # set some extra configuration data data $self->{extra_config}->{$1} = $val; return $ok->(); } if ($key eq 'pool') { my $pl = Perlbal->pool($val); return $err->("Pool '$val' not found") unless $pl; $self->{pool}->decrement_use_count if $self->{pool}; $self->{pool} = $pl; $self->{pool}->increment_use_count; $self->{generation}++; return $ok->(); } # see if it happens to be a plugin set command? if ($key =~ /^(.+)\.(.+)$/) { if (my $coderef = $self->{plugin_setters}->{$1}->{$2}) { return $coderef->($out, $2, $val); } } return $err->("Unknown attribute '$key'"); } # Service sub enable { my Perlbal::Service $self; my $out; ($self, $out) = @_; if ($self->{enabled}) { $out && $out->("ERROR: service $self->{name} is already enabled"); return 0; } # create listening socket my $tl = Perlbal::TCPListener->new($self->{listen}, $self); unless ($tl) { $out && $out->("ERROR: Can't start service '$self->{name}' on $self->{listen}: $Perlbal::last_error"); return 0; } $self->{listener} = $tl; $self->{enabled} = 1; return 1; } # Service sub disable { my Perlbal::Service $self; my ($out, $force); ($self, $out, $force) = @_; if (! $self->{enabled}) { $out && $out->("ERROR: service $self->{name} is already disabled"); return 0; } if ($self->{role} eq "management" && ! $force) { $out && $out->("ERROR: can't disable management service"); return 0; } # find listening socket my $tl = $self->{listener}; $tl->close; $self->{listener} = undef; $self->{enabled} = 0; return 1; } sub stats_info { my Perlbal::Service $self = shift; my $out = shift; my $now = time; $out->("SERVICE $self->{name}"); $out->(" listening: $self->{listen}"); $out->(" role: $self->{role}"); if ($self->{role} eq "reverse_proxy" || $self->{role} eq "web_server") { $out->(" pend clients: $self->{waiting_client_count}"); $out->(" pend backend: $self->{pending_connect_count}"); foreach my $ipport (sort keys %{$self->{pending_connects}}) { my $be = $self->{pending_connects}{$ipport}; next unless $be; my $age = $now - $be->{create_time}; $out->(" $ipport - " . ($be->{closed} ? "(closed)" : $be->{state}) . " - ${age}s"); } } if ($self->{role} eq "reverse_proxy") { my $bored_count = scalar @{$self->{bored_backends}}; $out->(" connect-ahead: $bored_count/$self->{connect_ahead}"); if ($self->{pool}) { $out->(" pool: " . $self->{pool}->name); $out->("balance method: " . $self->{pool}->balance_method); $out->(" nodes:"); foreach my $n (@{ $self->{pool}->nodes }) { my $hostport = "$n->[0]:$n->[1]"; $out->(sprintf(" %-21s %7d", $hostport, $self->{pool}->node_used($hostport) || 0)); } } } elsif ($self->{role} eq "web_server") { $out->(" docroot: $self->{docroot}"); } } # simple passthroughs to the run_hook mechanism. part of the reportto interface. sub backend_response_received { return $_[0]->run_hook('backend_response_received', $_[1]); } sub _durl { my ($a) = @_; $a =~ tr/+/ /; $a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; return $a; } 1; # Local Variables: # mode: perl # c-basic-indent: 4 # indent-tabs-mode: nil # End: