@@ -50,10 +50,22 @@ GetOptions(\%opt,
50
50
' h=s' , # hostname/basename of db server for *-slow.log filename (can be wildcard)
51
51
' i=s' , # name of server instance (if using mysql.server startup script)
52
52
' l!' , # don't subtract lock time from total time
53
+ ' json|j!' , # print as a JSON-formatted string
53
54
) or usage(" bad option" );
54
55
55
56
$opt {' help' } and usage ();
56
57
58
+ # check if JSON module is available
59
+ if ($opt {json}) {
60
+ eval {
61
+ require JSON;
62
+ JSON->import ();
63
+ 1;
64
+ } or do {
65
+ die " JSON module not found. Please install the JSON module to use --json option.\n" ;
66
+ };
67
+ }
68
+
57
69
unless (@ARGV) {
58
70
my $defaults = ` my_print_defaults --mysqld` ;
59
71
@@ -99,14 +111,16 @@ warn "\nReading mysql slow query log from @ARGV\n";
99
111
my @pending;
100
112
my %stmt;
101
113
$/ = " ;\n#" ; # read entire statements using paragraph mode
102
- while ( defined( $_ = shift @pending) or defined( $_ = <> ) ) {
114
+ while (<> ) {
103
115
warn " [[$_ ]]\n" if $opt {d}; # show raw paragraph being read
104
116
105
- my @chunks = split /^\/ .* Version.* started with[\0 00-\3 77]*? Time.* Id.* Command.* Argument.* \n /m;
106
- if (@chunks > 1) {
107
- unshift @pending, map { length($_ ) ? $_ : () } @chunks;
108
- warn " <<" .join(" >>\n<<" ,@chunks)." >>" if $opt {d};
109
- next;
117
+ # remove fluff that mysqld writes to log when it (re)starts:
118
+ s! ^.* Version.* started with:.* \n !! mg;
119
+ s! ^Tcp port: \d + Unix socket: \S +\n !! mg;
120
+ s! ^Time.* Id.* Command.* Argument.* \n !! mg;
121
+ # if there is only header info, skip
122
+ if ($_ eq ' ' ) {
123
+ next;
110
124
}
111
125
112
126
s/^# ? Time: \d{6}\s+\d+:\d+:\d+.*\n//;
@@ -120,18 +134,13 @@ while ( defined($_ = shift @pending) or defined($_ = <>) ) {
120
134
121
135
$t -= $l unless $opt {l};
122
136
123
- # remove fluff that mysqld writes to log when it (re)starts:
124
- s! ^/.* Version.* started with:.* \n !! mg;
125
- s! ^Tcp port: \d + Unix socket: \S +\n !! mg;
126
- s! ^Time.* Id.* Command.* Argument.* \n !! mg;
127
-
128
137
# Remove optimizer info
129
138
s! ^# QC_Hit: \S+\s+Full_scan: \S+\s+Full_join: \S+\s+Tmp_table: \S+\s+Tmp_table_on_disk: \S+[^\n]+\n!!mg;
130
139
s! ^# Filesort: \S+\s+Filesort_on_disk: \S+[^\n]+\n!!mg;
131
140
s! ^# Full_scan: \S+\s+Full_join: \S+[^\n]+\n!!mg;
132
141
133
- s/^use \w +; \n // ; # not consistently added
134
- s/^SET timestamp= \d +; \n // ;
142
+ s! ^SET timestamp= \d +; \n !! m ; # remove the redundant timestamp that is always added to each query
143
+ s! ^use \w +; \n !! m ; # not consistently added
135
144
136
145
s/^[ ]* \n //mg; # delete blank lines
137
146
s/^[ ]* / /mg; # normalize leading whitespace
@@ -181,15 +190,86 @@ my @sorted = sort { $stmt{$b}->{$opt{s}} <=> $stmt{$a}->{$opt{s}} } keys %stmt;
181
190
@sorted = @sorted[0 .. $opt {t}-1] if $opt {t};
182
191
@sorted = reverse @sorted if $opt {r};
183
192
184
- foreach (@sorted) {
185
- my $v = $stmt {$_ } || die;
186
- my ($c , $t , $at , $l , $al , $r , $ar , $e , $ae , $a , $aa ) = @{ $v }{qw(c t at l al r ar e ae a aa)};
187
- my @users = keys %{$v ->{users}};
188
- my $user = (@users==1) ? $users [0] : sprintf " %dusers" ,scalar @users;
189
- my @hosts = keys %{$v ->{hosts}};
190
- my $host = (@hosts==1) ? $hosts [0] : sprintf " %dhosts" ,scalar @hosts;
191
- printf " Count: %d Time=%.2fs (%ds) Lock=%.2fs (%ds) Rows_sent=%.1f (%d), Rows_examined=%.1f (%d), Rows_affected=%.1f (%d), $user \@ $host \n %s\n\n " ,
192
- $c , $at ,$t , $al ,$l , $ar ,$r , $ae , $e , $aa , $a , $_ ;
193
+ if(!$opt {json}) {
194
+ foreach (@sorted) {
195
+ my $v = $stmt {$_ } || die;
196
+ my ($c , $t , $at , $l , $al , $r , $ar , $e , $ae , $a , $aa ) = @{ $v }{qw(c t at l al r ar e ae a aa)};
197
+ my @users = keys %{$v ->{users}};
198
+ my $user = (@users==1) ? $users [0] : sprintf " %dusers" ,scalar @users;
199
+ my @hosts = keys %{$v ->{hosts}};
200
+ my $host = (@hosts==1) ? $hosts [0] : sprintf " %dhosts" ,scalar @hosts;
201
+ printf " Count: %d Time=%.2fs (%ds) Lock=%.2fs (%ds) Rows_sent=%.1f (%d), Rows_examined=%.1f (%d), Rows_affected=%.1f (%d), $user \@ $host \n %s\n\n " ,
202
+ $c , $at ,$t , $al ,$l , $ar ,$r , $ae , $e , $aa , $a , $_ ;
203
+ }
204
+ } else {
205
+ my @json_output;
206
+ foreach (@sorted) {
207
+ my $v = $stmt {$_ } || die;
208
+ my ($c , $t , $at , $l , $al , $r , $ar , $e , $ae , $a , $aa ) = @{ $v }{qw(c t at l al r ar e ae a aa)};
209
+ my @users = keys %{$v ->{users}};
210
+ my $user = (@users==1) ? $users [0] : sprintf " %dusers" ,scalar @users;
211
+ my @hosts = keys %{$v ->{hosts}};
212
+ my $host = (@hosts==1) ? $hosts [0] : sprintf " %dhosts" ,scalar @hosts;
213
+
214
+ # parse the engine data
215
+ my %engine;
216
+ if ($_ =~ /^\s*#\s*Pages_accessed:\s*(\S+)\s+Pages_read:\s*(\S+)\s+Pages_prefetched:\s*(\S+)\s+Pages_updated:\s*(\S+)\s+Old_rows_read:\s*(\S+)/m) {
217
+ @engine{qw(Pages_accessed Pages_read Pages_prefetched Pages_updated Old_rows_read)} = ($1 , $2 , $3 , $4 , $5 );
218
+ }
219
+ if ($_ =~ /^\s*#\s*Pages_read_time:\s*(\S+)\s+Engine_time:\s*(\S+)/m) {
220
+ @engine{qw(Pages_read_time Engine_time)} = ($1 , $2 );
221
+ }
222
+ # convert engine data to numbers
223
+ map { $engine {$_ } += 0 } keys %engine if $opt {a};
224
+
225
+ # build a structured explain output
226
+ my @explain_lines = ($_ =~ /^\s*# explain: (.+)$/mg);
227
+ my $explain ;
228
+ if (@explain_lines >= 2) {
229
+ my @headers = split /\s+/, shift @explain_lines;
230
+ $explain = [
231
+ map {
232
+ my @values = split /\s+/, $_ ;
233
+ my %row;
234
+ @row{@headers} = @values;
235
+ \%row;
236
+ } @explain_lines
237
+ ];
238
+ # normalize the explain data
239
+ foreach my $row (@$explain ) {
240
+ foreach my $key (keys %$row ) {
241
+ my $val = $row ->{$key };
242
+ $row ->{$key } = undef if $val eq 'NULL';
243
+ $row ->{$key } = $val + 0 if $opt {a} and $val =~ /^\d+(?:\.\d+)?$/;
244
+ }
245
+ }
246
+ }
247
+
248
+ # get the query string
249
+ (my $query = $_ ) =~ s/^\s*#.*\n//mg;
250
+ $query =~ s/^\s+|\s+$//g; # trim leading/trailing whitespace
251
+
252
+ # output the data as JSON
253
+ push @json_output, {
254
+ count => $c ,
255
+ avg_time => $at ,
256
+ total_time => $t ,
257
+ avg_lock => $al ,
258
+ total_lock => $l ,
259
+ avg_rows_sent => $ar ,
260
+ total_rows_sent => $r ,
261
+ avg_examined => $ae ,
262
+ total_examined => $e ,
263
+ avg_affected => $aa ,
264
+ total_affected => $a ,
265
+ user => $user ,
266
+ host => $host ,
267
+ query => $query ,
268
+ engine => (%engine ? \%engine : undef),
269
+ explain => ($explain ? $explain : undef),
270
+ };
271
+ }
272
+ print JSON->new->canonical(1)->pretty->encode(\@json_output);
193
273
}
194
274
195
275
sub usage {
@@ -202,6 +282,7 @@ Parse and summarize the MySQL slow query log. Options are
202
282
--verbose verbose
203
283
--debug debug
204
284
--help write this text to standard output
285
+ --json print as a JSON-formatted string
205
286
206
287
-v verbose
207
288
-d debug
@@ -226,6 +307,7 @@ Parse and summarize the MySQL slow query log. Options are
226
307
default is '*', i.e. match all
227
308
-i NAME name of server instance (if using mysql.server startup script)
228
309
-l don't subtract lock time from total time
310
+ -j print as a JSON-formatted string
229
311
230
312
HERE
231
313
if ($str ) {
0 commit comments