File Coverage

blib/lib/Class/ReluctantORM/Monitor.pm
Criterion Covered Total %
statement 19 114 16.6
branch 0 44 0.0
condition 0 33 0.0
subroutine 7 22 31.8
pod 9 9 100.0
total 35 222 15.7


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::Monitor;
2              
3             =head1 NAME
4              
5             Class::ReluctantORM::Monitor - Monitor CRO Driver activity
6              
7             =head1 SYNOPSIS
8              
9             use aliased 'Class::ReluctantORM::Monitor::ColumnCount';
10             # You can also make your own
11              
12             # Create a new monitor (args vary with monitor)
13             my $mon = ColumnCount->new(log => $io, log_prefix => 'yipes', ...);
14              
15             # Install globally
16             Class::ReluctantORM->install_global_monitor($mon);
17              
18             # Install only for the Ship class's driver
19             Model::Ship->driver->install_monitor($mon);
20              
21             # Turn on Origin Tracking to find out where the query is being generated
22             Class::ReluctantORM->enable_origin_tracking(1);
23              
24             # Make queries, etc...
25             # Things get logged to $io
26              
27             =head1 DESCRIPTION
28              
29             The Monitor facility allows you to peek inside the Class::ReluctantORM
30             SQL render, execute, and fetch process, and see what is going
31             on. Several monitors are included with Class::ReluctantORM, and it is easy
32             to write your own.
33              
34             Monitors may be global or class-specific. Global monitors are
35             installed by calling Class::ReluctantORM->install_global_monitor($mon),
36             and will affect all CRO interactions. Class-specific monitors
37             are installed onto the class's driver, using TheClass->driver->install_monitor($mon), and will
38             only monitor queries originating on that class.
39              
40             Monitors are grouped into two broad categories: general monitors, which can do anything, and measuring monitors, which have special facilities for measuring, tracking, and acting on a value that they measure.
41              
42             Several Monitors are included with Class::ReluctantORM (all have Class::ReluctantORM::Monitor as a prefix):
43              
44             =over
45              
46             =item Dump
47              
48             Dumps the query structures to the log.
49              
50             =item QueryCount
51              
52             Counts the number of statements executed. A Measuring monitor.
53              
54             =item ColumnCount
55              
56             Counts the number of columns returned by a query. A Measuring monitor.
57              
58             =item JoinCount
59              
60             Counts the number of JOINs in the query. A Measuring monitor.
61              
62             =item QuerySize
63              
64             Monitors the total size, in bytes, of the data returned by a query. A Measuring monitor.
65              
66             =item RowCount
67              
68             Monitors the number of rows returned by the query. A Measuring monitor.
69              
70             =item RowSize
71              
72             Monitors the size, in bytes, of each individual row. A Measuring monitor.
73              
74             =item Timer
75              
76             Tracks execution time of each query. A Measuring monitor.
77              
78             =back
79              
80             =head1 CONTROLLING WHAT TO OUTPUT
81              
82             These are the possible values for the 'what' option to new(), which controls what data gets logged.
83              
84             =over
85              
86             =item sql_object - the abstract Class::ReluctantORM::SQL object, via Data::Dumper
87              
88             =item sql_object_pretty - the abstract Class::ReluctantORM::SQL object, pretty-printed
89              
90             =item statement - the rendered SQL statement as a string
91              
92             =item binds - the list of bind arguments, given to execute()
93              
94             =item row - the structure returned by fetchrow_hashref
95              
96             =item origin - the line, file, and package where the query originated
97              
98             =back
99              
100             =cut
101              
102 1     1   7 use strict;
  1         3  
  1         41  
103 1     1   7 use warnings;
  1         2  
  1         82  
104 1     1   7 use Class::ReluctantORM::Utilities qw(conditional_load_subdir check_args nz);
  1         2  
  1         66  
105 1     1   6 use Data::Dumper;
  1         2  
  1         64  
106              
107             our $DEBUG = 0;
108              
109 1     1   6 use base 'Class::Accessor';
  1         2  
  1         106  
110 1     1   1088 use IO::Handle;
  1         8255  
  1         71  
111              
112             our @MONITOR_CLASSES;
113             BEGIN {
114 1     1   5 @MONITOR_CLASSES = conditional_load_subdir(__PACKAGE__);
115             }
116              
117              
118             =head1 CONSTRUCTORS
119              
120             =head2 $mon = SomeMonitor->new(...);
121              
122             Creates a new monitor. Monitors may extend the list of supported options, but all support:
123              
124             =over
125              
126             =item log - an IO::Handle or the string 'STDOUT' or 'STDERR'
127              
128             Append any log messages to this handle. If not present, logging is disabled.
129              
130             =item log_prefix - optional string
131              
132             Prefix to be used in log messages. Can be used to distinguish this monitor from others.
133              
134             =item trace_limit - optional integer
135              
136             If you use the 'origin' option to 'what', use this to specify how many frames to go back from the origin of the query. Default: no limit.
137              
138             =item what - optional arrayref of strings, or the string 'all'.
139              
140             When logging, indicates what values to log. Different monitors have different defaults for this. See CONTROLLING WHAT TO OUTPUT for more info.
141              
142             =item when - optional arrayref of strings, or the string 'all'.
143              
144             Indicates which events to pay attention to. Some monitors may constrain this value because they must listen at certain events. See CONTROLLING WHEN TO OUTPUT for more info.
145              
146             =back
147              
148             Measuring monitors have additional options:
149              
150             =over
151              
152             =item log_threshold - optional number
153              
154             If the measured value is less than this, no log entry is made. Default: always log.
155              
156             =item fatal_threshold - optional number
157              
158             Reflects a hard limit. If the measured value exceeds the limit, an exception is thrown. Default: no exceptions.
159              
160             =item highwater_count - integer
161              
162             If present, enables a "scoreboard" effect. This many records will be kept (for example, the top 5 queries by column count). See Class::ReluctantORM::Monitor::Measure - highwater_marks(). Default: remember 5 records.
163              
164             =back
165              
166             =cut
167              
168             our @WHENS = qw(render_begin render_transform render_finish execute_begin execute_finish fetch_row finish);
169             our @WHATS = qw(sql_object statement binds row sql_object_pretty origin);
170              
171             sub _monitor_base_check_args_spec {
172             return {
173 0     0     optional => [qw(
174             log
175             log_prefix
176             what
177             when
178             trace_limit
179             )],
180             };
181             }
182              
183 0     0     sub _monitor_check_args_spec { return $_[0]->_monitor_base_check_args_spec(); }
184              
185              
186             __PACKAGE__->mk_accessors(qw(
187             log
188             log_prefix
189             what
190             when
191             trace_limit
192             ));
193              
194             sub _new {
195 0     0     my $class = shift;
196 0           my %args =
197             check_args(
198 0           %{$class->_monitor_check_args_spec()},
199             args => \@_,
200             );
201              
202 0           my $self = bless {}, $class;
203 0 0         if ($args{log}) {
204 0 0 0       if (ref($args{log}) && $args{log}->isa('IO::Handle')) {
    0          
    0          
205 0           $self->log($args{log});
206             } elsif ($args{log} eq 'STDOUT') {
207 0           $self->log(IO::Handle->new_from_fd(fileno(STDOUT), 'w'));
208             } elsif ($args{log} eq 'STDERR') {
209 0           $self->log(IO::Handle->new_from_fd(fileno(STDERR), 'w'));
210             } else {
211 0           Class::ReluctantORM::Exception::Param::WrongType->croak
212             (
213             param => 'log',
214             expected => 'IO::Handle, or the string STDERR or STDOUT',
215             value => $args{log}
216             );
217             }
218 0           delete $args{log};
219             }
220              
221 0           $self->log_prefix($args{log_prefix});
222 0           $self->trace_limit($args{trace_limit});
223              
224 0 0 0       if (!($args{when}) || ($args{when} eq 'all')) {
    0          
225 0           $self->when( { map { $_ => 1 } @WHENS });
  0            
226             } elsif ($args{when}) {
227 0           $self->when( { map { $_ => 1 } @{$args{when}} } );
  0            
  0            
228             }
229              
230              
231 0 0 0       if (!($args{what}) || ($args{what} eq 'all')) {
    0          
232 0           $self->what( { map { $_ => 1 } grep { $_ ne 'sql_object' } @WHATS } );
  0            
  0            
233             } elsif ($args{what}) {
234 0           $self->what( { map { $_ => 1 } @{$args{what}} } );
  0            
  0            
235             }
236              
237 0           return $self;
238             }
239              
240              
241             =head2 $bool = $mon->supports_measuring();
242              
243             Returns true if the Monitor supports measuring something (a metric). Default implementation returns false.
244              
245             =cut
246              
247 0     0 1   sub supports_measuring { return 0; }
248              
249              
250             =head1 MONITOR EVENT INTERFACE METHODS
251              
252             These methods are called whenever a Driver event occurs.
253              
254             The default implementation is a no-op.
255              
256             All methods take named parameters. Each method lists its required arguments. The arguments are as follows:
257              
258             =over
259              
260             =item driver
261              
262             The Driver that is performing the work.
263              
264             =item sql_obj
265              
266             The Class::ReluctantORM::SQL object being rendered.
267              
268             =item sql_str
269              
270             The rendered SQL string, ready for a prepare(). This will be in the driver's dialect.
271              
272             =item sth
273              
274             The DBI statement handle.
275              
276             =item binds
277              
278             An arrayref of arguments to DBI execute().
279              
280             =item row
281              
282             A hashref of data returned by a single row, as returned by $sth->fetchrow_hashref
283              
284             =back
285              
286             =cut
287              
288             =head2 $d->notify_render_begin(sql_obj => $so);
289              
290             Notifies the monitoring system that the driver has begun work to render the given SQL object.
291              
292             Arguments: sql_obj, original, untouched Class::ReluctantORM::SQL object.
293              
294             =cut
295              
296 0     0 1   sub notify_render_begin { }
297              
298             =head2 $d->notify_render_transform(sql_obj => $so);
299              
300             Notifies the monitoring system that the driver has finished transforming the SQL object.
301              
302             Arguments: sql_obj, the post-transformation Class::ReluctantORM::SQL object.
303              
304             =cut
305              
306 0     0 1   sub notify_render_transform { }
307              
308             =head2 $d->notify_render_finish(sql_obj => $so, sql_str => $ss);
309              
310             Notifies the monitoring system that the driver has finished rendering the SQL object.
311              
312             =cut
313              
314 0     0 1   sub notify_render_finish { }
315              
316             =head2 $d->notify_execute_begin(sql_obj => $so, sql_str => $ss, sth =>$sth, binds => \@binds);
317              
318             Notifies the monitoring system that the driver is about to perform a DBI execute.
319              
320             =cut
321              
322 0     0 1   sub notify_execute_begin { }
323              
324             =head2 $d->notify_execute_finish(sql_obj => $so, sql_str => $ss, sth =>$sth, binds => \@binds);
325              
326             Notifies the monitoring system that the driver has returned from performing a DBI execute.
327              
328             =cut
329              
330 0     0 1   sub notify_execute_finish { }
331              
332             =head2 $d->notify_fetch_row(sql_obj => $so, sql_str => $ss, sth =>$sth, binds => \@binds, row => \%row);
333              
334             Notifies the monitoring system that the driver has returned from performing a DBI fetchrow.
335              
336             =cut
337              
338 0     0 1   sub notify_fetch_row { }
339              
340             =head2 $d->notify_finish(sql_obj => $so, sql_str => $ss, sth => $sth);
341              
342             Notifies the monitoring system that the driver has finished the query.
343              
344             =cut
345              
346 0     0 1   sub notify_finish { }
347              
348              
349             sub _log_stuff {
350 0     0     my $mon = shift;
351 0           my %args = @_;
352 0 0         return unless $mon->log;
353              
354 0           my $msg = '';
355              
356 0 0 0       if ($args{sql_obj} && exists($mon->what->{sql_object})) {
357 0           $msg .= $mon->_indent(2, "---SQL Object Dump:---");
358 0           $msg .= $mon->_indent(4, Dumper($args{sql_obj}));
359             }
360 0 0 0       if ($args{sql_obj} && exists($mon->what->{sql_object_pretty})) {
361 0           $msg .= $mon->_indent(2, "---SQL Object pretty print:---");
362 0           $msg .= $mon->_indent(4, $args{sql_obj}->pretty_print());
363             }
364 0 0 0       if ($args{sql_str} && exists($mon->what->{statement})) {
365 0           $msg .= $mon->_indent(2, "---SQL Statement:---");
366 0           $msg .= $mon->_indent(4, $args{sql_str});
367             }
368 0 0 0       if ($args{binds} && exists($mon->what->{binds})) {
369 0           $msg .= $mon->_indent(2, "---Bind values:---");
370 0           $msg .= $mon->_indent(4, Data::Dumper->Dump([$args{binds}], ['*binds']));
371             }
372 0 0 0       if ($args{row} && exists($mon->what->{row})) {
373 0           $msg .= $mon->_indent(2, "---Row values:---");
374 0           $msg .= $mon->_indent(4, Dumper($args{row}));
375             }
376 0 0 0       if ($args{sql_obj} && exists($mon->what->{origin}) && $args{sql_obj}->last_origin_frame()) {
      0        
377 0           $msg .= $mon->_indent(2, "---Query Origin:---");
378 0           my @trace = $args{sql_obj}->last_origin_trace(); # Don't need all - SQL objects can have at most one
379 0           my $frames_printed = 0;
380 0           foreach my $frame (@trace) {
381 0           $msg .= $mon->_indent(4, $mon->render_origin_frame($frame));
382 0           $frames_printed++;
383 0 0 0       if ($mon->trace_limit() && $frames_printed >= $mon->trace_limit) {
384 0           last;
385             }
386             }
387             }
388              
389 0 0         if ($args{log_extra}) {
390 0 0         if ($args{log_extra}{one_line}) {
391 0           $msg .= $mon->_indent(2, "---" . $args{log_extra}{label} . ":" . $args{log_extra}{value});
392             } else {
393 0           $msg .= $mon->_indent(2, "---" . $args{log_extra}{label} . ":---");
394 0           $msg .= $mon->_indent(4, $args{log_extra}{value});
395             }
396             }
397              
398 0 0         return unless ($msg);
399              
400 0           $msg = $mon->_log_prefix($args{event}) . "\n" . $msg;
401 0           $mon->log->print($msg);
402             }
403              
404             =begin devnotes
405              
406             =head2 $str = $mon->render_origin_frame()
407              
408             Compress the origin frame to a string in a pretty way.
409              
410             =cut
411              
412             sub render_origin_frame {
413 0     0 1   my $mon = shift;
414 0           my $frame = shift;
415              
416             # TODO - TB2CRO - OmniTI-ism
417             # Special hook for Mungo support
418 0 0         if ($frame->{package} =~ /Mungo::FilePage/) {
419 0           my $file = $main::Response->{Mungo}->demangle_name($frame->{package} . '::__content');
420 0           $file =~ s{^Mungo::FilePage\(}{};
421 0           $file =~ s{\)$}{};
422 0           return "file: " . $file . " line (approx): " . $frame->{line};
423             }
424              
425 0           return "file: " . $frame->{file} . " line: " . $frame->{line};
426             }
427              
428              
429             =begin devnotes
430              
431             Returns a prefix string for use in monitor logging.
432              
433             =cut
434              
435             sub _log_prefix {
436 0     0     my $self = shift;
437 0           my $event = shift;
438 0           my $str = '[' . localtime() . ']';
439 0 0         if ($self->log_prefix) {
440 0           $str .= '[' . $self->log_prefix . ']';
441             }
442 0           $str .= sprintf('[pid%05d]', $$);
443 0           $str .= '[' . $event . ']';
444 0           return $str . ' ';
445             }
446              
447             sub _indent {
448 0     0     my $monitor = shift;
449 0           my $spaces = shift;
450 0           my $str = shift;
451 0           my $indent = ' ' x $spaces;
452 0 0         return (join "\n", map { $_ ? ($indent . $_) : $_ } split /\n/, $str) . "\n";
  0            
453             }
454              
455             =head1 AUTHOR
456              
457             Clinton Wolfe January 2009 - January 2011
458              
459              
460             =cut
461              
462              
463             1;