File Coverage

blib/lib/DBIx/Fast/Profiler.pm
Criterion Covered Total %
statement 14 185 7.5
branch 0 60 0.0
condition 0 14 0.0
subroutine 5 21 23.8
pod 8 13 61.5
total 27 293 9.2


line stmt bran cond sub pod time code
1             package DBIx::Fast::Profiler;
2              
3 15     15   242 use v5.38;
  15         69  
4 15     15   102 use Object::Pad 0.807;
  15         111  
  15         732  
5 15     15   2382 use Time::HiRes qw(time);
  15         33  
  15         114  
6 15     15   13272 use JSON;
  15         255067  
  15         105  
7 15     15   14078 use Term::ANSIColor;
  15         202970  
  15         5189  
8              
9             class DBIx::Fast::Profiler :isa(DBIx::Fast::Base) {
10 0     0 0   field $auto_print :param :accessor = 1;
11 0 0   0 0   field $queries :accessor = [];
  0            
12 0 0   0 0   field $enabled :param :accessor = 1;
  0            
13 0 0   0 0   field $slow_query :param :accessor = 1.0;
  0            
14 0 0   0 0   field $max_queries :param :accessor = 1000;
  0            
15              
16 0 0   0 1   method add_query ($sql, $params, $start_time, $end_time) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
17 0 0         return unless $enabled;
18              
19 0           my $duration = $end_time - $start_time;
20 0           my $query_info = {
21             sql => $sql,
22             params => $params,
23             start => $start_time,
24             end => $end_time,
25             duration => $duration,
26             stack => $self->_get_stack_trace(),
27             time => scalar( localtime($start_time) )
28             };
29              
30 0 0         if ( @{$queries} >= $max_queries ) {
  0            
31 0           shift @{$queries};
  0            
32             }
33              
34 0           push @{$queries}, $query_info;
  0            
35              
36 0 0         $self->print_query($query_info) if $auto_print;
37             }
38              
39 0     0 1   method print_query ($query_info) {
  0            
  0            
  0            
40             my $duration_color =
41             $query_info->{duration} >= $slow_query ? 'red'
42 0 0         : $query_info->{duration} >= ( $slow_query / 2 ) ? 'yellow'
    0          
43             : 'green';
44              
45 0           print "\n", "=" x 80, "\n";
46             print color('bold blue'), "Query at ", $query_info->{time},
47 0           color('reset'), "\n";
48              
49             print color('bold'), "Duration: ",
50             color($duration_color),
51 0           sprintf( "%.6fs", $query_info->{duration} ),
52             color('reset'), "\n";
53              
54             print color('bold cyan'), "SQL: ", color('reset'),
55 0           $self->_format_sql( $query_info->{sql} ), "\n";
56              
57 0 0         if ( @{ $query_info->{params} } ) {
  0            
58             print color('bold magenta'), "Params: ", color('reset'),
59 0           encode_json( $query_info->{params} ), "\n";
60             }
61              
62 0 0         if ( $query_info->{duration} >= $slow_query ) {
63             print color('red'), "Stack Trace:\n", color('reset'),
64 0           $query_info->{stack}, "\n";
65             }
66             }
67              
68 0     0     method _format_sql ($sql) {
  0            
  0            
  0            
69 0           my @keywords =
70             qw(SELECT FROM WHERE INSERT UPDATE DELETE JOIN ON GROUP BY HAVING ORDER LIMIT);
71              
72 0           for my $keyword (@keywords) {
73 0           $sql =~ s/\b$keyword\b/\n$keyword/g;
74             }
75              
76 0           return $sql;
77             }
78              
79 0     0 1   method get_detailed_stats () {
  0            
  0            
80 0           my %stats;
81 0           for my $query ( @{$queries} ) {
  0            
82 0           my $type = $self->_get_query_type( $query->{sql} );
83              
84 0           $stats{$type}->{count}++;
85 0           $stats{$type}->{total_time} += $query->{duration};
86             $stats{$type}->{avg_time} =
87 0           $stats{$type}->{total_time} / $stats{$type}->{count};
88              
89 0 0 0       if ( !defined $stats{$type}->{min_time}
90             || $query->{duration} < $stats{$type}->{min_time} )
91             {
92 0           $stats{$type}->{min_time} = $query->{duration};
93             }
94              
95 0 0 0       if ( !defined $stats{$type}->{max_time}
96             || $query->{duration} > $stats{$type}->{max_time} )
97             {
98 0           $stats{$type}->{max_time} = $query->{duration};
99             }
100             }
101              
102 0           return \%stats;
103             }
104              
105 0     0     method _get_query_type ($sql) {
  0            
  0            
  0            
106 0 0         return 'SELECT' if $sql =~ /^\s*SELECT/i;
107 0 0         return 'INSERT' if $sql =~ /^\s*INSERT/i;
108 0 0         return 'UPDATE' if $sql =~ /^\s*UPDATE/i;
109 0 0         return 'DELETE' if $sql =~ /^\s*DELETE/i;
110 0 0         return 'CREATE' if $sql =~ /^\s*CREATE/i;
111 0 0         return 'ALTER' if $sql =~ /^\s*ALTER/i;
112 0 0         return 'DROP' if $sql =~ /^\s*DROP/i;
113 0           return 'OTHER';
114             }
115              
116 0     0 1   method print_summary () {
  0            
  0            
117 0           my $stats = $self->get_detailed_stats();
118              
119 0           print "\n", "=" x 80, "\n";
120 0           print color('bold'), "Query Statistics Summary\n", color('reset');
121 0           print "=" x 80, "\n";
122              
123 0           for my $type ( sort keys %$stats ) {
124 0           print color('bold blue'), "\n$type Queries:\n", color('reset');
125 0           printf( " Count: %d\n", $stats->{$type}->{count} );
126 0           printf( " Total Time: %.6fs\n", $stats->{$type}->{total_time} );
127 0           printf( " Avg Time: %.6fs\n", $stats->{$type}->{avg_time} );
128 0           printf( " Min Time: %.6fs\n", $stats->{$type}->{min_time} );
129 0           printf( " Max Time: %.6fs\n", $stats->{$type}->{max_time} );
130             }
131             }
132              
133 0     0     method _get_stack_trace () {
  0            
  0            
134 0           my $trace = "";
135 0           my $i = 0;
136              
137 0           while ( my @caller = caller( $i++ ) ) {
138 0 0         next if $caller[0] =~ /^DBIx::Fast/;
139 0           $trace .= sprintf( "%s line %d\n", $caller[1], $caller[2] );
140             }
141              
142 0           return $trace;
143             }
144              
145 0     0 1   method get_stats () {
  0            
  0            
146 0           my $stats = {
147             total_queries => 0,
148             total_time => 0,
149             avg_time => 0,
150             min_time => undef,
151             max_time => 0,
152             slow_queries => 0,
153             query_types => {},
154             last_query_time => 0,
155             queries_per_second => 0
156             };
157              
158 0 0         return $stats unless @{$queries};
  0            
159              
160 0           my $first_query_time = $queries->[0]->{start};
161 0           my $last_query_time = $queries->[-1]->{end};
162              
163 0           for my $query ( @{$queries} ) {
  0            
164 0           $stats->{total_queries}++;
165 0           $stats->{total_time} += $query->{duration};
166              
167 0 0 0       if ( !defined $stats->{min_time}
168             || $query->{duration} < $stats->{min_time} )
169             {
170 0           $stats->{min_time} = $query->{duration};
171             }
172              
173 0 0         if ( $query->{duration} > $stats->{max_time} ) {
174 0           $stats->{max_time} = $query->{duration};
175             }
176              
177 0 0         $stats->{slow_queries}++ if $query->{duration} > $slow_query;
178              
179 0           my $type = $self->_get_query_type( $query->{sql} );
180 0   0       $stats->{query_types}->{$type} ||= {
181             count => 0,
182             total_time => 0,
183             min_time => undef,
184             max_time => 0,
185             avg_time => 0
186             };
187              
188 0           my $type_stats = $stats->{query_types}->{$type};
189 0           $type_stats->{count}++;
190 0           $type_stats->{total_time} += $query->{duration};
191              
192 0 0 0       if ( !defined $type_stats->{min_time}
193             || $query->{duration} < $type_stats->{min_time} )
194             {
195 0           $type_stats->{min_time} = $query->{duration};
196             }
197              
198 0 0         if ( $query->{duration} > $type_stats->{max_time} ) {
199 0           $type_stats->{max_time} = $query->{duration};
200             }
201              
202             $type_stats->{avg_time} =
203 0           $type_stats->{total_time} / $type_stats->{count};
204             }
205              
206 0           $stats->{avg_time} = $stats->{total_time} / $stats->{total_queries};
207              
208 0           my $total_elapsed = $last_query_time - $first_query_time;
209 0 0         if ( $total_elapsed > 0 ) {
210             $stats->{queries_per_second} =
211 0           $stats->{total_queries} / $total_elapsed;
212             }
213              
214 0           $stats->{last_query_time} = $last_query_time;
215              
216             $stats->{slow_query_percentage} =
217 0           ( $stats->{slow_queries} / $stats->{total_queries} ) * 100;
218              
219 0           return $stats;
220             }
221              
222 0     0 1   method print_stats () {
  0            
  0            
223 0           my $stats = $self->get_stats();
224              
225 0           print "\n", "=" x 80, "\n";
226 0           print color('bold'), "Query Statistics\n", color('reset');
227 0           print "=" x 80, "\n\n";
228              
229 0           printf "Total Queries: %d\n", $stats->{total_queries};
230 0           printf "Total Time: %.4fs\n", $stats->{total_time};
231 0           printf "Average Time: %.4fs\n", $stats->{avg_time};
232 0           printf "Min Time: %.4fs\n", $stats->{min_time};
233 0           printf "Max Time: %.4fs\n", $stats->{max_time};
234             printf "Slow Queries: %d (%.2f%%)\n",
235             $stats->{slow_queries},
236 0           $stats->{slow_query_percentage};
237 0           printf "Queries/second: %.2f\n", $stats->{queries_per_second};
238              
239 0           print "\nBy Query Type:\n";
240 0           print "-" x 40, "\n";
241              
242 0           for my $type ( sort keys %{ $stats->{query_types} } ) {
  0            
243 0           my $type_stats = $stats->{query_types}->{$type};
244 0           print color('bold blue'), "\n$type:\n", color('reset');
245 0           printf " Count: %d\n", $type_stats->{count};
246 0           printf " Total Time: %.4fs\n", $type_stats->{total_time};
247 0           printf " Avg Time: %.4fs\n", $type_stats->{avg_time};
248 0           printf " Min Time: %.4fs\n", $type_stats->{min_time};
249 0           printf " Max Time: %.4fs\n", $type_stats->{max_time};
250             }
251             }
252              
253 0     0 1   method get_slow_queries ($limit = 10) {
  0            
  0            
  0            
254             my @sorted =
255 0           sort { $b->{duration} <=> $a->{duration} } @{$queries};
  0            
  0            
256              
257 0 0         $limit = scalar @sorted if $limit > scalar @sorted;
258 0           return [ @sorted[ 0 .. ( $limit - 1 ) ] ];
259             }
260              
261 0     0 1   method clear () { $queries = [] }
  0            
  0            
  0            
262             }
263              
264             1;
265              
266             __END__