File Coverage

blib/lib/Class/ReluctantORM/Monitor/Measuring.pm
Criterion Covered Total %
statement 15 90 16.6
branch 0 20 0.0
condition 0 8 0.0
subroutine 5 24 20.8
pod 15 15 100.0
total 35 157 22.2


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::Monitor::Measuring;
2              
3             =head1 NAME
4              
5             Class::ReluctantORM::Monitor::Measuring - Monitor with Metric support
6              
7             =head1 SYNOPSIS
8              
9             #####
10             # Using a Measuring Monitor
11             #####
12              
13             # Interrogate the monitor
14             print "Last query had " . $mon->last_measured_value() . " foo units\n";
15              
16             # Worst offenders overall
17             foreach my $info ($mon->highwater_marks()) {
18             print "Rank: " . $info->{rank} . "\n";
19             print "Foo Count: " . $info->{measured_value} . "\n";
20             print "Query Object: " . $info->{sql}->pretty_print() . "\n";
21              
22             # next three depend on Origin Tracking being enabled
23             print "Origin File: " . $info->{origin}->{file} . "\n";
24             print "Origin Line: " . $info->{origin}->{line} . "\n";
25             print "Origin Package: " . $info->{origin}->{package} . "\n";
26             }
27              
28             # Can also log, etc - does everything a regular Monitor can do
29              
30             #####
31             # Creating a new Measuring Monitor
32             #####
33              
34             package FooCount;
35             use base 'Class::ReluctantORM::Monitor::Measuring';
36              
37             sub measurement_label { return 'Foo Count (Instantaneous)'; }
38             sub default_events { return @list_of_when; }
39             sub permitted_events { return @list_of_when; }
40              
41             # Gets called whenever a measurement needs to be taken
42             sub take_measurement {
43             my %event_info = @_; # sql_obj, binds, etc
44             return $foo_count;
45             }
46              
47             =head1 DESCRIPTION
48              
49             The Monitor facility allows you to peek inside the Class::ReluctantORM
50             SQL render, execute, and fetch process, and see what is going
51             on. Several monitors are included with Class::ReluctantORM, and it is easy
52             to write your own.
53              
54             The Measuring Monitors have special support to obtain, track, and act on a measured value.
55              
56             See Class::ReluctantORM::Monitor for info about using MOnitors in general. This file only documents the measuring extensions.
57              
58             =cut
59              
60 1     1   6 use strict;
  1         3  
  1         29  
61 1     1   5 use warnings;
  1         2  
  1         23  
62              
63 1     1   5 use base 'Class::ReluctantORM::Monitor';
  1         1  
  1         109  
64 1     1   6 use Class::ReluctantORM::Utilities qw(check_args nz);
  1         3  
  1         52  
65 1     1   5 use Data::Dumper;
  1         2  
  1         1724  
66              
67             our $DEBUG = 0;
68              
69              
70             =head1 CONSTRUCTORS
71              
72             =head2 $mon = SomeMonitor->new(...);
73              
74             See Class::ReluctantORM::Monitor::new().
75              
76             =cut
77              
78             our @WHENS = @Class::ReluctantORM::Monitor::WHENS;
79             our @WHATS = @Class::ReluctantORM::Monitor::WHATS;
80              
81             sub _monitor_check_args_spec {
82 0     0     my $monitor_spec = Class::ReluctantORM::Monitor->_monitor_base_check_args_spec();
83 0           push @{$monitor_spec->{optional}}, qw(
  0            
84             highwater_count
85             log_threshold
86             fatal_threshold
87             );
88 0           return $monitor_spec;
89             }
90              
91             __PACKAGE__->mk_accessors(qw(
92             highwater_marks_ref
93             last_measured_value
94             log_threshold
95             fatal_threshold
96             highwater_count
97             ));
98              
99             sub new {
100 0     0 1   my $class = shift;
101 0           my %args =
102             check_args(
103 0           %{$class->_monitor_check_args_spec()},
104             args => \@_,
105             );
106 0           my $self = $class->_new(%args);
107              
108             # Init Measuring-specifics
109 0   0       $args{highwater_count} ||= 5;
110 0           $self->highwater_count($args{highwater_count});
111 0           $self->fatal_threshold($args{fatal_threshold});
112 0           $self->log_threshold($args{log_threshold});
113              
114             # Init measure to 0
115 0           $self->last_measured_value(0);
116 0           $self->highwater_marks_ref([]);
117              
118             # Check WHENS (was defaulted to all by Monitor->_new)
119 0 0         if (!defined($args{when})) {
120 0           $self->when({ map { $_ => 1 } $self->default_events() });
  0            
121             } else {
122 0           foreach my $when (keys %{$self->when}) {
  0            
123 0 0         unless (grep { $_ eq $when } $self->permitted_events) {
  0            
124 0           Class::ReluctantORM::Exception::Param::BadValue->croak
125             (
126             param => 'when',
127             value => $when,
128             error => "Monitor $class cannot be used on the $when event. Instead, use one of: " . join(',', $self->permitted_events),
129             );
130             }
131             }
132             }
133              
134 0           return $self;
135             }
136              
137             =head1 MEASURING API
138              
139             These methods should be overridden to implement your monitor's behavior.
140              
141             =head2 $str = $mon->measurement_label();
142              
143             Returns a string to be included in the log to label the measured value. Default is "$monitor_class Observation".
144              
145             =cut
146              
147             sub measurement_label {
148 0     0 1   my $monitor = shift;
149 0 0         my $class = ref($monitor) ? ref($monitor) : $monitor;
150 0           return "$class Observation";
151             }
152              
153             =head2 @whens = $mon->permitted_events();
154              
155             Returns a list of events for which it is permitted to take a measurement for this monitor. If you instantiate a monitor, and request an event not on this list, an exception will be thrown.
156              
157             Default is all events permitted.
158              
159             =cut
160              
161 0     0 1   sub permitted_events { return @WHENS; }
162              
163             =head2 @whens = $mon->default_events();
164              
165             Returns a list of events at which measurmeents will automatically be taken, if you do not override this with the 'when' option to new().
166              
167             Default is all events.
168              
169             =cut
170              
171 0     0 1   sub default_events { return @WHENS; }
172              
173             =head2 $number = $mon->take_measurement(%event_info);
174              
175             Pure virtual - your Monitor subclass must implement this method.
176              
177             Called when the monitor needs to take a measurement. The arguments will be a hash of the event arguments (See Class::ReluctantORM::Monitor - Monitor Interface Methods section), with an additional 'event' key whose value is the name of the event.
178              
179             =cut
180              
181 0     0 1   sub take_measurement { Class::ReluctantORM::Exception::Call::PureVirtual->croak('take_measurement'); }
182              
183             =head1 HIGHWATER TRACKING
184              
185             Measuring-style Monitors may also support Highwater Tracking. As the Monitor makes observations, it maintains a list of the N worst unique observations. N is determined by the value of the highwater_count option passed to the monitor constructor.
186             Observations are considered the same if they have the same count and same origin.
187              
188             =head2 @observations = $mon->highwater_marks()
189              
190             Returns an array of hashes describing the N unique observations whose measured_value was the largest.
191              
192             Each hashref has the following keys:
193              
194             =over
195              
196             =item rank
197              
198             Current rank in the highwater scoreboard, with 1 the worst.
199              
200             =item measured_value
201              
202             The observed value.
203              
204             =item sql
205              
206             The SQL object being executed at the time.
207              
208             =item origin
209              
210             Present only if Origin Tracking is enabled (see Class::ReluctantORM->enable_origin_tracking()). If present, is a hash containing keys file, line, and package, indicating the location of the last stack frame outside of Class::ReluctantORM (usually "your" code).
211              
212             =back
213              
214             =cut
215              
216             sub highwater_marks {
217 0     0 1   my $self = shift;
218 0 0         return @{$self->highwater_marks_ref() || []};
  0            
219             }
220              
221             sub _record_highwater {
222 0     0     my ($mon, $sql) = @_;
223 0 0         return unless $mon->highwater_count();
224 0           my @marks = $mon->highwater_marks;
225 0           my $new_entry = {
226             rank => 0,
227             measured_value => $mon->last_measured_value(),
228             sql => $sql,
229             origin => (scalar $sql->last_origin_frame())
230             };
231 0           my %uniq =
232 0           map { $mon->__hash_highwater_entry($_) => $_ }
233             (@marks, $new_entry);
234              
235 0           @marks = sort { $b->{measured_value} <=> $a->{measured_value} } values %uniq;
  0            
236 0           @marks = grep { defined $_ } @marks[0..($mon->highwater_count() -1)];
  0            
237 0           for (0..$#marks) {
238 0           $marks[$_]->{rank} = $_ + 1;
239             }
240              
241 0           $mon->highwater_marks_ref(\@marks);
242             }
243              
244             sub __hash_highwater_entry {
245 0     0     my $mon = shift;
246 0           my $entry = shift;
247 0 0         my $key = ref($mon) eq 'Class::ReluctantORM::Monitor::QueryCount' ? '' : $entry->{measured_value} . '_';
248 0 0         if ($entry->{origin}) {
249 0           $key .= nz($entry->{origin}->{file}, 'unk') . '_';
250 0           $key .= nz($entry->{origin}->{line}, 'unk') . '_';
251 0           $key .= nz($entry->{origin}->{package}, 'unk') . '_';
252             } else {
253             # ewwww
254 0           $key .= $entry->{sql}->pretty_print();
255             }
256 0           return $key;
257             }
258              
259             =head2 $bool = $mon->supports_measuring();
260              
261             Returns true if the Monitor supports counting something (a metric). This implementation returns true.
262              
263             =cut
264              
265 0     0 1   sub supports_measuring { return 1; }
266              
267             =head1 MONITOR INFORMATION INTERFACE METHODS
268              
269             These methods provide information about the monitor.
270              
271             =cut
272              
273             =head2 $number = $mon->last_measured_value();
274              
275             Returns the value of the last observation.
276              
277             =cut
278              
279             =head2 $mon->reset();
280              
281             For measuring monitors, resets the last measured value to zero.
282              
283             =cut
284              
285             sub reset {
286 0     0 1   my $self = shift;
287 0           $self->last_measured_value(0);
288             }
289              
290              
291 0     0 1   sub notify_render_begin { __measuring_event(@_, event => 'render_begin'); }
292 0     0 1   sub notify_render_transform { __measuring_event(@_, event => 'render_transform'); }
293 0     0 1   sub notify_render_finish { __measuring_event(@_, event => 'render_finish'); }
294 0     0 1   sub notify_execute_begin { __measuring_event(@_, event => 'execute_begin'); }
295 0     0 1   sub notify_execute_finish { __measuring_event(@_, event => 'execute_finish') }
296 0     0 1   sub notify_fetch_row { __measuring_event(@_, event => 'fetch_row'); }
297 0     0 1   sub notify_finish { __measuring_event(@_, event => 'finish'); }
298              
299             sub __measuring_event {
300 0     0     my $mon = shift;
301 0           my %event_args = @_;
302 0           my $event = $event_args{event};
303              
304 0 0         unless ($mon->when->{$event}) { return; }
  0            
305              
306             # Take a measurement
307 0           $mon->last_measured_value($mon->take_measurement(%event_args));
308 0           $mon->_record_highwater($event_args{sql_obj});
309              
310             # Log if needed
311 0 0 0       if (!($mon->log_threshold) || ($mon->last_measured_value >= $mon->log_threshold)) {
312 0           $mon->_log_stuff(
313             %event_args,
314             log_extra => {
315             label => $mon->measurement_label(),
316             value => $mon->last_measured_value(),
317             },
318             );
319             }
320              
321             # Die if needed
322 0 0 0       if ($mon->fatal_threshold && $mon->last_measured_value >= $mon->fatal_threshold) {
323 0           Class::ReluctantORM::Exception::SQL::AbortedByMonitor->croak
324             (
325             monitor => $mon,
326             limit => $mon->fatal_threshold,
327             observed => $mon->last_measured_value,
328             sql => $event_args{sql_obj},
329             query_location => [ $event_args{sql_obj}->all_origin_traces() ],
330             );
331             }
332              
333             }
334              
335              
336              
337              
338             =head1 AUTHOR
339              
340             Clinton Wolfe January 2011
341              
342              
343             =cut
344              
345              
346             1;