File Coverage

blib/lib/CHI/Stats.pm
Criterion Covered Total %
statement 18 102 17.6
branch 0 22 0.0
condition 0 10 0.0
subroutine 6 16 37.5
pod 4 8 50.0
total 28 158 17.7


line stmt bran cond sub pod time code
1             package CHI::Stats;
2             $CHI::Stats::VERSION = '0.59';
3 1     1   387 use CHI::Util qw(json_encode json_decode);
  1         2  
  1         60  
4 1     1   4 use List::Util qw(sum);
  1         1  
  1         75  
5 1     1   4273 use Log::Any qw($log);
  1         1473  
  1         4  
6 1     1   2184 use Moo;
  1         10818  
  1         5  
7 1     1   1199 use strict;
  1         2  
  1         43  
8 1     1   6 use warnings;
  1         4  
  1         1015  
9              
10             has 'chi_root_class' => ( is => 'ro' );
11             has 'data' => ( is => 'ro', default => sub { {} } );
12             has 'enabled' => ( is => 'rwp', default => sub { 0 } );
13             has 'start_time' => ( is => 'ro', default => sub { time } );
14              
15 0     0 1   sub enable { $_[0]->_set_enabled(1) }
16 0     0 1   sub disable { $_[0]->_set_enabled(0) }
17              
18             sub flush {
19 0     0 1   my ($self) = @_;
20              
21 0           my $data = $self->data;
22 0           foreach my $label ( sort keys %$data ) {
23 0           my $label_stats = $data->{$label};
24 0           foreach my $namespace ( sort keys(%$label_stats) ) {
25 0           my $namespace_stats = $label_stats->{$namespace};
26 0 0         if (%$namespace_stats) {
27 0           $self->log_namespace_stats( $label, $namespace,
28             $namespace_stats );
29             }
30             }
31             }
32 0           $self->clear();
33             }
34              
35             sub log_namespace_stats {
36 0     0 0   my ( $self, $label, $namespace, $namespace_stats ) = @_;
37              
38 0           my %data = (
39             label => $label,
40             end_time => time(),
41             namespace => $namespace,
42             root_class => $self->chi_root_class,
43             %$namespace_stats
44             );
45 0 0         %data =
46 0           map { /_ms$/ ? ( $_, int( $data{$_} ) ) : ( $_, $data{$_} ) }
47             keys(%data);
48 0           $log->infof( 'CHI stats: %s', json_encode( \%data ) );
49             }
50              
51             sub format_time {
52 0     0 0   my ($time) = @_;
53              
54 0           my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
55             localtime($time);
56 0           return sprintf(
57             "%04d%02d%02d:%02d:%02d:%02d",
58             $year + 1900,
59             $mon + 1, $mday, $hour, $min, $sec
60             );
61             }
62              
63             sub stats_for_driver {
64 0     0 0   my ( $self, $cache ) = @_;
65              
66 0   0       my $stats =
67             ( $self->data->{ $cache->label }->{ $cache->namespace } ||= {} );
68 0   0       $stats->{start_time} ||= time;
69 0           return $stats;
70             }
71              
72             sub parse_stats_logs {
73 0     0 1   my $self = shift;
74 0           my ( %results_hash, @results, %numeric_fields_seen );
75 0           foreach my $log_file (@_) {
76 0           my $logfh;
77 0 0         if ( ref($log_file) ) {
78 0           $logfh = $log_file;
79             }
80             else {
81 0 0         open( $logfh, '<', $log_file ) or die "cannot open $log_file: $!";
82 0           $log->infof( "processing '%s'", $log_file );
83             }
84 0           while ( my $line = <$logfh> ) {
85 0           chomp($line);
86 0 0         if ( my ($json) = ( $line =~ /CHI stats: (\{.*\})$/ ) ) {
87 0           my %hash = %{ json_decode($json) };
  0            
88 0           my $root_class = delete( $hash{root_class} );
89 0           my $namespace = delete( $hash{namespace} );
90 0           my $label = delete( $hash{label} );
91 0   0       my $results_set =
92             ( $results_hash{$root_class}->{$label}->{$namespace} ||= {} );
93 0 0         if ( !%$results_set ) {
94 0           $results_set->{root_class} = $root_class;
95 0           $results_set->{namespace} = $namespace;
96 0           $results_set->{label} = $label;
97 0           push( @results, $results_set );
98             }
99 0           while ( my ( $key, $value ) = each(%hash) ) {
100 0 0         next if $key =~ /_time$/;
101 0           $results_set->{$key} += $value;
102 0           $numeric_fields_seen{$key}++;
103             }
104             }
105             }
106             }
107 0           my @numeric_fields = sort( keys(%numeric_fields_seen) );
108              
109             my $sum = sub {
110 0     0     my ( $rs, $name, @fields ) = @_;
111 0 0         if ( grep { $rs->{$_} } @fields ) {
  0            
112 0 0         $rs->{$name} = sum( map { $rs->{$_} || 0 } @fields );
  0            
113             }
114 0           };
115 0           foreach my $rs (@results) {
116 0           $sum->( $rs, 'misses', 'absent_misses', 'expired_misses' );
117 0           $sum->( $rs, 'gets', 'hits', 'misses' );
118             }
119              
120 0           my %totals = map { ( $_, 'TOTALS' ) } qw(root_class namespace label);
  0            
121 0           foreach my $field (@numeric_fields) {
122 0 0         $totals{$field} = sum( map { $_->{$field} || 0 } @results );
  0            
123             }
124 0           push( @results, \%totals );
125              
126             my $divide = sub {
127 0     0     my ( $rs, $name, $top, $bottom ) = @_;
128 0 0 0       if ( $rs->{$top} && $rs->{$bottom} ) {
129 0           $rs->{$name} = ( $rs->{$top} / $rs->{$bottom} );
130             }
131 0           };
132              
133 0           foreach my $rs (@results) {
134 0           $divide->( $rs, 'avg_compute_time_ms', 'compute_time_ms', 'computes' );
135 0           $divide->( $rs, 'avg_get_time_ms', 'get_time_ms', 'gets' );
136 0           $divide->( $rs, 'avg_set_time_ms', 'set_time_ms', 'sets' );
137 0           $divide->( $rs, 'avg_set_key_size', 'set_key_size', 'sets' );
138 0           $divide->( $rs, 'avg_set_value_size', 'set_value_size', 'sets' );
139 0           $divide->( $rs, 'hit_rate', 'hits', 'gets' );
140             }
141 0           return \@results;
142             }
143              
144             sub clear {
145 0     0 0   my ($self) = @_;
146              
147 0           my $data = $self->data;
148 0           foreach my $key ( keys %{$data} ) {
  0            
149 0           %{ $data->{$key} } = ();
  0            
150             }
151 0           $self->{start_time} = time;
152             }
153              
154             1;
155              
156             __END__