| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ########################################################################### | 
| 2 |  |  |  |  |  |  | # basic Perlbal statistics gatherer | 
| 3 |  |  |  |  |  |  | ########################################################################### | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | package Perlbal::Plugin::Stats; | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 1 |  |  | 1 |  | 2473 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 41 |  | 
| 8 | 1 |  |  | 1 |  | 7 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 35 |  | 
| 9 | 1 |  |  | 1 |  | 6 | no  warnings qw(deprecated); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 1 |  |  | 1 |  | 8 | use Time::HiRes qw(gettimeofday tv_interval); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 59 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | # setup our package variables | 
| 14 |  |  |  |  |  |  | our %statobjs; # { svc_name => [ service, statobj ], svc_name => [ service, statobj ], ... } | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | # define all stats keys here | 
| 17 |  |  |  |  |  |  | our @statkeys = qw( files_sent      files_reproxied | 
| 18 |  |  |  |  |  |  | web_requests    proxy_requests | 
| 19 |  |  |  |  |  |  | proxy_requests_highpri          ); | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | # called when we're being added to a service | 
| 22 |  |  |  |  |  |  | sub register { | 
| 23 | 0 |  |  | 0 | 0 |  | my ($class, $svc) = @_; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | # create a stats object | 
| 26 | 0 |  |  |  |  |  | my $sobj = Perlbal::Plugin::Stats::Storage->new(); | 
| 27 | 0 |  |  |  |  |  | $statobjs{$svc->{name}} = [ $svc, $sobj ]; | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | # simple events we count are done here.  when the hook on the left side is called, | 
| 30 |  |  |  |  |  |  | # we simply increment the count of the stat on the right side. | 
| 31 | 0 |  |  |  |  |  | my %simple = qw( | 
| 32 |  |  |  |  |  |  | start_send_file         files_sent | 
| 33 |  |  |  |  |  |  | start_file_reproxy      files_reproxied | 
| 34 |  |  |  |  |  |  | start_web_request       web_requests | 
| 35 |  |  |  |  |  |  | ); | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | # create hooks for %simple things | 
| 38 | 0 |  |  |  |  |  | while (my ($hook, $stat) = each %simple) { | 
| 39 | 0 |  |  |  |  |  | eval "\$svc->register_hook('Stats', '$hook', sub { \$sobj->{'$stat'}++; return 0; });"; | 
| 40 | 0 | 0 |  |  |  |  | return undef if $@; | 
| 41 |  |  |  |  |  |  | } | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | # more complicated statistics | 
| 44 |  |  |  |  |  |  | $svc->register_hook('Stats', 'backend_client_assigned', sub { | 
| 45 | 0 |  |  | 0 |  |  | my Perlbal::BackendHTTP $be = shift; | 
| 46 | 0 |  |  |  |  |  | my Perlbal::ClientProxy $cp = $be->{client}; | 
| 47 | 0 |  |  |  |  |  | $sobj->{pending}->{"$cp"} = [ gettimeofday() ]; | 
| 48 | 0 | 0 |  |  |  |  | ($cp->{high_priority} ? $sobj->{proxy_requests_highpri} : $sobj->{proxy_requests})++; | 
| 49 | 0 |  |  |  |  |  | return 0; | 
| 50 | 0 |  |  |  |  |  | }); | 
| 51 |  |  |  |  |  |  | $svc->register_hook('Stats', 'backend_response_received', sub { | 
| 52 | 0 |  |  | 0 |  |  | my Perlbal::BackendHTTP $be = shift; | 
| 53 | 0 |  |  |  |  |  | my Perlbal::ClientProxy $obj = $be->{client}; | 
| 54 | 0 |  |  |  |  |  | my $ot = delete $sobj->{pending}->{"$obj"}; | 
| 55 | 0 | 0 |  |  |  |  | return 0 unless defined $ot; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | # now construct data to put in recent | 
| 58 | 0 | 0 |  |  |  |  | if (defined $obj->{req_headers}) { | 
| 59 | 0 |  | 0 |  |  |  | my $uri = 'http://' . ($obj->{req_headers}->header('Host') || 'unknown') . $obj->{req_headers}->request_uri; | 
| 60 | 0 |  |  |  |  |  | push @{$sobj->{recent}}, sprintf('%-6.4f %s', tv_interval($ot), $uri); | 
|  | 0 |  |  |  |  |  |  | 
| 61 | 0 | 0 |  |  |  |  | shift(@{$sobj->{recent}}) if scalar(@{$sobj->{recent}}) > 100; # if > 100 items, lose one | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | } | 
| 63 | 0 |  |  |  |  |  | return 0; | 
| 64 | 0 |  |  |  |  |  | }); | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 0 |  |  |  |  |  | return 1; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | # called when we're no longer active on a service | 
| 70 |  |  |  |  |  |  | sub unregister { | 
| 71 | 0 |  |  | 0 | 0 |  | my ($class, $svc) = @_; | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | # clean up time | 
| 74 | 0 |  |  |  |  |  | $svc->unregister_hooks('Stats'); | 
| 75 | 0 |  |  |  |  |  | delete $statobjs{$svc->{name}}; | 
| 76 | 0 |  |  |  |  |  | return 1; | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | # called when we are loaded | 
| 80 |  |  |  |  |  |  | sub load { | 
| 81 |  |  |  |  |  |  | # setup a management command to dump statistics | 
| 82 |  |  |  |  |  |  | Perlbal::register_global_hook("manage_command.stats", sub { | 
| 83 | 0 |  |  | 0 |  |  | my @res; | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | # create temporary object for stats storage | 
| 86 | 0 |  |  |  |  |  | my $gsobj = Perlbal::Plugin::Stats::Storage->new(); | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | # dump per service | 
| 89 | 0 |  |  |  |  |  | foreach my $svc (keys %statobjs) { | 
| 90 | 0 |  |  |  |  |  | my $sobj = $statobjs{$svc}->[1]; | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | # for now, simply dump the numbers we have | 
| 93 | 0 |  |  |  |  |  | foreach my $key (sort @statkeys) { | 
| 94 | 0 |  |  |  |  |  | push @res, sprintf("%-15s %-25s %12d", $svc, $key, $sobj->{$key}); | 
| 95 | 0 |  |  |  |  |  | $gsobj->{$key} += $sobj->{$key}; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | # global stats | 
| 100 | 0 |  |  |  |  |  | foreach my $key (sort @statkeys) { | 
| 101 | 0 |  |  |  |  |  | push @res, sprintf("%-15s %-25s %12d", 'total', $key, $gsobj->{$key}); | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 0 |  |  |  |  |  | push @res, "."; | 
| 105 | 0 |  |  |  |  |  | return \@res; | 
| 106 | 0 |  |  | 0 | 0 |  | }); | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | # recent requests and how long they took | 
| 109 |  |  |  |  |  |  | Perlbal::register_global_hook("manage_command.recent", sub { | 
| 110 | 0 |  |  | 0 |  |  | my @res; | 
| 111 | 0 |  |  |  |  |  | foreach my $svc (keys %statobjs) { | 
| 112 | 0 |  |  |  |  |  | my $sobj = $statobjs{$svc}->[1]; | 
| 113 | 0 |  |  |  |  |  | push @res, "$svc $_" | 
| 114 | 0 |  |  |  |  |  | foreach @{$sobj->{recent}}; | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 0 |  |  |  |  |  | push @res, "."; | 
| 118 | 0 |  |  |  |  |  | return \@res; | 
| 119 | 0 |  |  |  |  |  | }); | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 0 |  |  |  |  |  | return 1; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | # called for a global unload | 
| 125 |  |  |  |  |  |  | sub unload { | 
| 126 |  |  |  |  |  |  | # unregister our global hooks | 
| 127 | 0 |  |  | 0 | 0 |  | Perlbal::unregister_global_hook('manage_command.stats'); | 
| 128 | 0 |  |  |  |  |  | Perlbal::unregister_global_hook('manage_command.recent'); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | # take out all service stuff | 
| 131 | 0 |  |  |  |  |  | foreach my $statref (values %statobjs) { | 
| 132 | 0 |  |  |  |  |  | $statref->[0]->unregister_hooks('Stats'); | 
| 133 |  |  |  |  |  |  | } | 
| 134 | 0 |  |  |  |  |  | %statobjs = (); | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 |  |  |  |  |  | return 1; | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | # statistics storage object | 
| 140 |  |  |  |  |  |  | package Perlbal::Plugin::Stats::Storage; | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | use fields ( | 
| 143 | 1 |  |  |  |  | 27 | 'files_sent',         # files sent from disk (includes reproxies and regular web requests) | 
| 144 |  |  |  |  |  |  | 'files_reproxied',    # files we've sent via reproxying (told to by backend) | 
| 145 |  |  |  |  |  |  | 'web_requests',       # requests we sent ourselves (no reproxy, no backend) | 
| 146 |  |  |  |  |  |  | 'proxy_requests',     # regular requests that went to a backend to be served | 
| 147 |  |  |  |  |  |  | 'proxy_requests_highpri', # same as above, except high priority | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | 'pending',            # hashref; { "obj" => time_start } | 
| 150 |  |  |  |  |  |  | 'recent',             # arrayref; strings of recent URIs and times | 
| 151 | 1 |  |  | 1 |  | 1668 | ); | 
|  | 1 |  |  |  |  | 8 |  | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | sub new { | 
| 154 | 0 |  |  | 0 |  |  | my Perlbal::Plugin::Stats::Storage $self = shift; | 
| 155 | 0 | 0 |  |  |  |  | $self = fields::new($self) unless ref $self; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | # 0 initialize everything here | 
| 158 | 0 |  |  |  |  |  | $self->{$_} = 0 foreach @Perlbal::Plugin::Stats::statkeys; | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | # other setup | 
| 161 | 0 |  |  |  |  |  | $self->{pending} = {}; | 
| 162 | 0 |  |  |  |  |  | $self->{recent} = []; | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 0 |  |  |  |  |  | return $self; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | 1; |