~percona-dev/percona-server/release-5.5.11-20.2-fix-bug-764138

« back to all changes in this revision

Viewing changes to build/debian/additions/innotop/InnoDBParser.pm

  • Committer: Ignacio Nin
  • Date: 2011-03-13 17:18:23 UTC
  • mfrom: (33.3.17 release-5.5.8-20)
  • Revision ID: ignacio.nin@percona.com-20110313171823-m06xs104nekulywb
Merge changes from release-5.5.8-20 to 5.5.9

Merge changes from the release branch of 5.5.8 to 5.5.9. These include
the HandlerSocket and UDF directories and the building scripts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
use strict;
 
2
use warnings FATAL => 'all';
 
3
 
 
4
package InnoDBParser;
 
5
 
 
6
# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
 
7
# Feedback and improvements are gratefully received.
 
8
#
 
9
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
10
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
11
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
12
#
 
13
# This program is free software; you can redistribute it and/or modify it under
 
14
# the terms of the GNU General Public License as published by the Free Software
 
15
# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
16
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
17
 
 
18
# You should have received a copy of the GNU General Public License along with
 
19
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
20
# Place, Suite 330, Boston, MA  02111-1307  USA
 
21
 
 
22
our $VERSION = '1.6.0';
 
23
 
 
24
use Data::Dumper;
 
25
$Data::Dumper::Sortkeys = 1;
 
26
use English qw(-no_match_vars);
 
27
use List::Util qw(max);
 
28
 
 
29
# Some common patterns
 
30
my $d  = qr/(\d+)/;                    # Digit
 
31
my $f  = qr/(\d+\.\d+)/;               # Float
 
32
my $t  = qr/(\d+ \d+)/;                # Transaction ID
 
33
my $i  = qr/((?:\d{1,3}\.){3}\d+)/;    # IP address
 
34
my $n  = qr/([^`\s]+)/;                # MySQL object name
 
35
my $w  = qr/(\w+)/;                    # Words
 
36
my $fl = qr/([\w\.\/]+) line $d/;      # Filename and line number
 
37
my $h  = qr/((?:0x)?[0-9a-f]*)/;       # Hex
 
38
my $s  = qr/(\d{6} .\d:\d\d:\d\d)/;    # InnoDB timestamp
 
39
 
 
40
# If you update this variable, also update the SYNOPSIS in the pod.
 
41
my %innodb_section_headers = (
 
42
   "TRANSACTIONS"                          => "tx",
 
43
   "BUFFER POOL AND MEMORY"                => "bp",
 
44
   "SEMAPHORES"                            => "sm",
 
45
   "LOG"                                   => "lg",
 
46
   "ROW OPERATIONS"                        => "ro",
 
47
   "INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib",
 
48
   "FILE I/O"                              => "io",
 
49
   "LATEST DETECTED DEADLOCK"              => "dl",
 
50
   "LATEST FOREIGN KEY ERROR"              => "fk",
 
51
);
 
52
 
 
53
my %parser_for = (
 
54
   tx => \&parse_tx_section,
 
55
   bp => \&parse_bp_section,
 
56
   sm => \&parse_sm_section,
 
57
   lg => \&parse_lg_section,
 
58
   ro => \&parse_ro_section,
 
59
   ib => \&parse_ib_section,
 
60
   io => \&parse_io_section,
 
61
   dl => \&parse_dl_section,
 
62
   fk => \&parse_fk_section,
 
63
);
 
64
 
 
65
my %fk_parser_for = (
 
66
   Transaction => \&parse_fk_transaction_error,
 
67
   Error       => \&parse_fk_bad_constraint_error,
 
68
   Cannot      => \&parse_fk_cant_drop_parent_error,
 
69
);
 
70
 
 
71
# A thread's proc_info can be at least 98 different things I've found in the
 
72
# source.  Fortunately, most of them begin with a gerunded verb.  These are
 
73
# the ones that don't.
 
74
my %is_proc_info = (
 
75
   'After create'                 => 1,
 
76
   'Execution of init_command'    => 1,
 
77
   'FULLTEXT initialization'      => 1,
 
78
   'Reopen tables'                => 1,
 
79
   'Repair done'                  => 1,
 
80
   'Repair with keycache'         => 1,
 
81
   'System lock'                  => 1,
 
82
   'Table lock'                   => 1,
 
83
   'Thread initialized'           => 1,
 
84
   'User lock'                    => 1,
 
85
   'copy to tmp table'            => 1,
 
86
   'discard_or_import_tablespace' => 1,
 
87
   'end'                          => 1,
 
88
   'got handler lock'             => 1,
 
89
   'got old table'                => 1,
 
90
   'init'                         => 1,
 
91
   'key cache'                    => 1,
 
92
   'locks'                        => 1,
 
93
   'malloc'                       => 1,
 
94
   'query end'                    => 1,
 
95
   'rename result table'          => 1,
 
96
   'rename'                       => 1,
 
97
   'setup'                        => 1,
 
98
   'statistics'                   => 1,
 
99
   'status'                       => 1,
 
100
   'table cache'                  => 1,
 
101
   'update'                       => 1,
 
102
);
 
103
 
 
104
sub new {
 
105
   bless {}, shift;
 
106
}
 
107
 
 
108
# Parse the status and return it.
 
109
# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c
 
110
# Pass in the text to parse, whether to be in debugging mode, which sections
 
111
# to parse (hashref; if empty, parse all), and whether to parse full info from
 
112
# locks and such (probably shouldn't unless you need to).
 
113
sub parse_status_text {
 
114
   my ( $self, $fulltext, $debug, $sections, $full ) = @_;
 
115
 
 
116
   die "I can't parse undef" unless defined $fulltext;
 
117
   $fulltext =~ s/[\r\n]+/\n/g;
 
118
 
 
119
   $sections ||= {};
 
120
   die '$sections must be a hashref' unless ref($sections) eq 'HASH';
 
121
 
 
122
   my %innodb_data = (
 
123
      got_all   => 0,         # Whether I was able to get the whole thing
 
124
      ts        => '',        # Timestamp the server put on it
 
125
      last_secs => 0,         # Num seconds the averages are over
 
126
      sections  => {},        # Parsed values from each section
 
127
   );
 
128
 
 
129
   if ( $debug ) {
 
130
      $innodb_data{'fulltext'} = $fulltext;
 
131
   }
 
132
 
 
133
   # Get the most basic info about the status: beginning and end, and whether
 
134
   # I got the whole thing (if there has been a big deadlock and there are
 
135
   # too many locks to print, the output might be truncated)
 
136
   my ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m;
 
137
   $innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ];
 
138
   $innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'});
 
139
   ( $innodb_data{'last_secs'} ) = $fulltext
 
140
      =~ m/Per second averages calculated from the last $d seconds/;
 
141
 
 
142
   ( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/;
 
143
   $innodb_data{'got_all'} = $got_all || 0;
 
144
 
 
145
   # Split it into sections.  Each section begins with
 
146
   # -----
 
147
   # LABEL
 
148
   # -----
 
149
   my %innodb_sections;
 
150
   my @matches = $fulltext
 
151
      =~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs;
 
152
   while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) {
 
153
      $innodb_sections{$name} = [ $text, $end ? 1 : 0 ];
 
154
   }
 
155
   # The Row Operations section is a special case, because instead of ending
 
156
   # with the beginning of another section, it ends with the end of the file.
 
157
   # So this section is complete if the entire file is complete.
 
158
   $innodb_sections{'ROW OPERATIONS'}->[1] ||= $innodb_data{'got_all'};
 
159
 
 
160
   # Just for sanity's sake, make sure I understand what to do with each
 
161
   # section
 
162
   eval {
 
163
      foreach my $section ( keys %innodb_sections ) {
 
164
         my $header = $innodb_section_headers{$section};
 
165
         die "Unknown section $section in $fulltext\n"
 
166
            unless $header;
 
167
         $innodb_data{'sections'}->{ $header }
 
168
            ->{'fulltext'} = $innodb_sections{$section}->[0];
 
169
         $innodb_data{'sections'}->{ $header }
 
170
            ->{'complete'} = $innodb_sections{$section}->[1];
 
171
      }
 
172
   };
 
173
   if ( $EVAL_ERROR ) {
 
174
      _debug( $debug, $EVAL_ERROR);
 
175
   }
 
176
 
 
177
   # ################################################################
 
178
   # Parse the detailed data out of the sections.
 
179
   # ################################################################
 
180
   eval {
 
181
      foreach my $section ( keys %parser_for ) {
 
182
         if ( defined $innodb_data{'sections'}->{$section}
 
183
               && (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) {
 
184
            $parser_for{$section}->(
 
185
                  $innodb_data{'sections'}->{$section},
 
186
                  $innodb_data{'sections'}->{$section}->{'complete'},
 
187
                  $debug,
 
188
                  $full )
 
189
               or delete $innodb_data{'sections'}->{$section};
 
190
         }
 
191
         else {
 
192
            delete $innodb_data{'sections'}->{$section};
 
193
         }
 
194
      }
 
195
   };
 
196
   if ( $EVAL_ERROR ) {
 
197
      _debug( $debug, $EVAL_ERROR);
 
198
   }
 
199
 
 
200
   return \%innodb_data;
 
201
}
 
202
 
 
203
# Parses the status text and returns it flattened out as a single hash.
 
204
sub get_status_hash {
 
205
   my ( $self, $fulltext, $debug, $sections, $full ) = @_;
 
206
 
 
207
   # Parse the status text...
 
208
   my $innodb_status
 
209
      = $self->parse_status_text($fulltext, $debug, $sections, $full );
 
210
 
 
211
   # Flatten the hierarchical structure into a single list by grabbing desired
 
212
   # sections from it.
 
213
   return
 
214
      (map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)),
 
215
      (map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} }
 
216
         qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads
 
217
            awe_mem_alloc pages_modified writes_pending_lru page_creates_sec
 
218
            reads_pending pages_total buf_pool_hits writes_pending_single_page
 
219
            page_writes_sec pages_read pages_written page_reads_sec
 
220
            writes_pending_flush_list buf_pool_size add_pool_alloc
 
221
            dict_mem_alloc pages_created buf_free complete )),
 
222
      (map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} }
 
223
         qw( num_lock_structs history_list_len purge_done_for transactions
 
224
            purge_undo_for is_truncated trx_id_counter complete )),
 
225
      (map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} }
 
226
         qw( hash_table_size hash_searches_s non_hash_searches_s
 
227
            bufs_in_node_heap used_cells size free_list_len seg_size inserts
 
228
            merged_recs merges complete )),
 
229
      (map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} }
 
230
         qw( log_ios_done pending_chkp_writes last_chkp log_ios_s
 
231
            log_flushed_to log_seq_no pending_log_writes complete )),
 
232
      (map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} }
 
233
         qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits
 
234
            mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits
 
235
            waits signal_count reservation_count complete )),
 
236
      (map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} }
 
237
         qw( queries_in_queue n_reserved_extents main_thread_state
 
238
         main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec
 
239
         read_views_open num_rows_upd num_rows_ins num_rows_read
 
240
         queries_inside num_rows_del complete )),
 
241
      (map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} }
 
242
         qw( trigger parent_table child_index parent_index attempted_op
 
243
         child_db timestring fk_name records col_name reason txn parent_db
 
244
         type child_table parent_col complete )),
 
245
      (map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} }
 
246
         qw( pending_buffer_pool_flushes pending_pwrites pending_preads
 
247
         pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios
 
248
         reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s
 
249
         threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs
 
250
         pending_log_flushes complete )),
 
251
      (map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} }
 
252
         qw( timestring rolled_back txns complete ));
 
253
 
 
254
}
 
255
 
 
256
sub ts_to_string {
 
257
   my $parts = shift;
 
258
   return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts);
 
259
}
 
260
 
 
261
sub parse_innodb_timestamp {
 
262
   my $text = shift;
 
263
   my ( $y, $m, $d, $h, $i, $s )
 
264
      = $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/;
 
265
   die("Can't get timestamp from $text\n") unless $y;
 
266
   $y += 2000;
 
267
   return ( $y, $m, $d, $h, $i, $s );
 
268
}
 
269
 
 
270
sub parse_fk_section {
 
271
   my ( $section, $complete, $debug, $full ) = @_;
 
272
   my $fulltext = $section->{'fulltext'};
 
273
 
 
274
   return 0 unless $fulltext;
 
275
 
 
276
   my ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m;
 
277
   $section->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
 
278
   $section->{'timestring'} = ts_to_string($section->{'ts'});
 
279
   $section->{'type'} = $type;
 
280
 
 
281
   # Decide which type of FK error happened, and dispatch to the right parser.
 
282
   if ( $type && $fk_parser_for{$type} ) {
 
283
      $fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full );
 
284
   }
 
285
 
 
286
   delete $section->{'fulltext'} unless $debug;
 
287
 
 
288
   return 1;
 
289
}
 
290
 
 
291
sub parse_fk_cant_drop_parent_error {
 
292
   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
 
293
 
 
294
   # Parse the parent/child table info out
 
295
   @{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext
 
296
      =~ m{Cannot $w table `(.*)/(.*)`}m;
 
297
   @{$section}{ qw(child_db child_table) } = $fulltext
 
298
      =~ m{because it is referenced by `(.*)/(.*)`}m;
 
299
 
 
300
   ( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s;
 
301
   $section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm
 
302
      if $section->{'reason'};
 
303
 
 
304
   # Certain data may not be present.  Make them '' if not present.
 
305
   map { $section->{$_} ||= "" }
 
306
      qw(child_index fk_name col_name parent_col);
 
307
}
 
308
 
 
309
# See dict/dict0dict.c, function dict_foreign_error_report
 
310
# I don't care much about these.  There are lots of different messages, and
 
311
# they come from someone trying to create a foreign key, or similar
 
312
# statements.  They aren't indicative of some transaction trying to insert,
 
313
# delete or update data.  Sometimes it is possible to parse out a lot of
 
314
# information about the tables and indexes involved, but often the message
 
315
# contains the DDL string the user entered, which is way too much for this
 
316
# module to try to handle.
 
317
sub parse_fk_bad_constraint_error {
 
318
   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
 
319
 
 
320
   # Parse the parent/child table and index info out
 
321
   @{$section}{ qw(child_db child_table) } = $fulltext
 
322
      =~ m{Error in foreign key constraint of table (.*)/(.*):$}m;
 
323
   $section->{'attempted_op'} = 'DDL';
 
324
 
 
325
   # FK name, parent info... if possible.
 
326
   @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
 
327
      = $fulltext
 
328
      =~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/;
 
329
 
 
330
   if ( !defined($section->{'fk_name'}) ) {
 
331
      # Try to parse SQL a user might have typed in a CREATE statement or such
 
332
      @{$section}{ qw(col_name parent_db parent_table parent_col) }
 
333
         = $fulltext
 
334
         =~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i;
 
335
   }
 
336
   $section->{'parent_db'} ||= $section->{'child_db'};
 
337
 
 
338
   # Name of the child index (index in the same table where the FK is, see
 
339
   # definition of dict_foreign_struct in include/dict0mem.h, where it is
 
340
   # called foreign_index, as opposed to referenced_index which is in the
 
341
   # parent table.  This may not be possible to find.
 
342
   @{$section}{ qw(child_index) } = $fulltext
 
343
      =~ m/^The index in the foreign key in table is $n$/m;
 
344
 
 
345
   @{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms;
 
346
   $section->{'reason'} =~ s/\s+/ /g
 
347
      if $section->{'reason'};
 
348
   
 
349
   # Certain data may not be present.  Make them '' if not present.
 
350
   map { $section->{$_} ||= "" }
 
351
      qw(child_index fk_name col_name parent_table parent_col);
 
352
}
 
353
 
 
354
# see source file row/row0ins.c
 
355
sub parse_fk_transaction_error {
 
356
   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
 
357
 
 
358
   # Parse the txn info out
 
359
   my ( $txn ) = $fulltext
 
360
      =~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s;
 
361
   if ( $txn ) {
 
362
      $section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full );
 
363
   }
 
364
 
 
365
   # Parse the parent/child table and index info out.  There are two types: an
 
366
   # update or a delete of a parent record leaves a child orphaned
 
367
   # (row_ins_foreign_report_err), and an insert or update of a child record has
 
368
   # no matching parent record (row_ins_foreign_report_add_err).
 
369
 
 
370
   @{$section}{ qw(reason child_db child_table) }
 
371
      = $fulltext =~ m{^(Foreign key constraint fails for table `(.*)/(.*)`:)$}m;
 
372
 
 
373
   @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
 
374
      = $fulltext
 
375
      =~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/;
 
376
   $section->{'parent_db'} ||= $section->{'child_db'};
 
377
 
 
378
   # Special case, which I don't know how to trigger, but see
 
379
   # innobase/row/row0ins.c row_ins_check_foreign_constraint
 
380
   if ( $fulltext =~ m/ibd file does not currently exist!/ ) {
 
381
      my ( $attempted_op, $index, $records )
 
382
         = $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm;
 
383
      $section->{'child_index'} = $index;
 
384
      $section->{'attempted_op'} = $attempted_op || '';
 
385
      if ( $records && $full ) {
 
386
         ( $section->{'records'} )
 
387
            = parse_innodb_record_dump( $records, $complete, $debug );
 
388
      }
 
389
      @{$section}{qw(parent_db parent_table)}
 
390
         =~ m/^But the parent table `$n`\.`$n`$/m;
 
391
   }
 
392
   else {
 
393
      my ( $attempted_op, $which, $index )
 
394
         = $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m;
 
395
      if ( $which ) {
 
396
         $section->{$which . '_index'} = $index;
 
397
         $section->{'attempted_op'} = $attempted_op || '';
 
398
 
 
399
         # Parse out the related records in the other table.
 
400
         my ( $search_index, $records );
 
401
         if ( $which eq 'child' ) {
 
402
            ( $search_index, $records ) = $fulltext
 
403
               =~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms;
 
404
            $section->{'parent_index'} = $search_index;
 
405
         }
 
406
         else {
 
407
            ( $search_index, $records ) = $fulltext
 
408
               =~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms;
 
409
            $section->{'child_index'} = $search_index;
 
410
         }
 
411
         if ( $records && $full ) {
 
412
            $section->{'records'}
 
413
               = parse_innodb_record_dump( $records, $complete, $debug );
 
414
         }
 
415
         else {
 
416
            $section->{'records'} = '';
 
417
         }
 
418
      }
 
419
   }
 
420
 
 
421
   # Parse out the tuple trying to be updated, deleted or inserted.
 
422
   my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m;
 
423
   if ( $trigger ) {
 
424
      $section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug );
 
425
   }
 
426
 
 
427
   # Certain data may not be present.  Make them '' if not present.
 
428
   map { $section->{$_} ||= "" }
 
429
      qw(child_index fk_name col_name parent_table parent_col);
 
430
}
 
431
 
 
432
# There are new-style and old-style record formats.  See rem/rem0rec.c
 
433
# TODO: write some tests for this
 
434
sub parse_innodb_record_dump {
 
435
   my ( $dump, $complete, $debug ) = @_;
 
436
   return undef unless $dump;
 
437
 
 
438
   my $result = {};
 
439
 
 
440
   if ( $dump =~ m/PHYSICAL RECORD/ ) {
 
441
      my $style = $dump =~ m/compact format/ ? 'new' : 'old';
 
442
      $result->{'style'} = $style;
 
443
 
 
444
      # This is a new-style record.
 
445
      if ( $style eq 'new' ) {
 
446
         @{$result}{qw( heap_no type num_fields info_bits )}
 
447
            = $dump
 
448
            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m;
 
449
      }
 
450
 
 
451
      # OK, it's old-style.  Unfortunately there are variations here too.
 
452
      elsif ( $dump =~ m/-byte offs / ) {
 
453
         # Older-old style.
 
454
         @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
 
455
            = $dump
 
456
            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m;
 
457
            if ( $dump !~ m/-byte offs TRUE/ ) {
 
458
               $result->{'byte_offset'} = 0;
 
459
            }
 
460
      }
 
461
      else {
 
462
         # Newer-old style.
 
463
         @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
 
464
            = $dump
 
465
            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m;
 
466
      }
 
467
 
 
468
   }
 
469
   else {
 
470
      $result->{'style'} = 'tuple';
 
471
      @{$result}{qw( type num_fields )}
 
472
         = $dump =~ m/^(DATA TUPLE): $d fields;$/m;
 
473
   }
 
474
 
 
475
   # Fill in default values for things that couldn't be parsed.
 
476
   map { $result->{$_} ||= 0 }
 
477
      qw(heap_no num_fields byte_offset info_bits);
 
478
   map { $result->{$_} ||= '' }
 
479
      qw(style type );
 
480
 
 
481
   my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm;
 
482
   $result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ];
 
483
 
 
484
   return $result;
 
485
}
 
486
 
 
487
# New/old-style applies here.  See rem/rem0rec.c
 
488
# $text should not include the leading space or the second trailing semicolon.
 
489
sub parse_field {
 
490
   my ( $text, $complete, $debug ) = @_;
 
491
 
 
492
   # Sample fields:
 
493
   # '4: SQL NULL, size 4 '
 
494
   # '1: len 6; hex 000000005601; asc     V ;'
 
495
   # '6: SQL NULL'
 
496
   # '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)'
 
497
   my ( $id, $nullsize, $len, $hex, $asc, $truncated );
 
498
   ( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/;
 
499
   if ( !defined($id) ) {
 
500
      ( $id ) = $text =~ m/^$d: SQL NULL$/;
 
501
   }
 
502
   if ( !defined($id) ) {
 
503
      ( $id, $len, $hex, $asc, $truncated )
 
504
         = $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/;
 
505
   }
 
506
 
 
507
   die "Could not parse this field: '$text'" unless defined $id;
 
508
   return {
 
509
      id    => $id,
 
510
      len   => defined($len) ? $len : defined($nullsize) ? $nullsize : 0,
 
511
      'hex' => defined($hex) ? $hex : '',
 
512
      asc   => defined($asc) ? $asc : '',
 
513
      trunc => $truncated ? 1 : 0,
 
514
   };
 
515
 
 
516
}
 
517
 
 
518
sub parse_dl_section {
 
519
   my ( $dl, $complete, $debug, $full ) = @_;
 
520
   return unless $dl;
 
521
   my $fulltext = $dl->{'fulltext'};
 
522
   return 0 unless $fulltext;
 
523
 
 
524
   my ( $ts ) = $fulltext =~ m/^$s$/m;
 
525
   return 0 unless $ts;
 
526
 
 
527
   $dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
 
528
   $dl->{'timestring'} = ts_to_string($dl->{'ts'});
 
529
   $dl->{'txns'} = {};
 
530
 
 
531
   my @sections
 
532
      = $fulltext
 
533
      =~ m{
 
534
         ^\*{3}\s([^\n]*)  # *** (1) WAITING FOR THIS...
 
535
         (.*?)             # Followed by anything, non-greedy
 
536
         (?=(?:^\*{3})|\z) # Followed by another three stars or EOF
 
537
      }gmsx;
 
538
 
 
539
 
 
540
   # Loop through each section.  There are no assumptions about how many
 
541
   # there are, who holds and wants what locks, and who gets rolled back.
 
542
   while ( my ($header, $body) = splice(@sections, 0, 2) ) {
 
543
      my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/;
 
544
      next unless $txn_id;
 
545
      $dl->{'txns'}->{$txn_id} ||= {};
 
546
      my $txn = $dl->{'txns'}->{$txn_id};
 
547
 
 
548
      if ( $what eq 'TRANSACTION' ) {
 
549
         $txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full );
 
550
      }
 
551
      else {
 
552
         push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full );
 
553
      }
 
554
   }
 
555
 
 
556
   @{ $dl }{ qw(rolled_back) }
 
557
      = $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m;
 
558
 
 
559
   # Make sure certain values aren't undef
 
560
   map { $dl->{$_} ||= '' } qw(rolled_back);
 
561
 
 
562
   delete $dl->{'fulltext'} unless $debug;
 
563
   return 1;
 
564
}
 
565
 
 
566
sub parse_innodb_record_locks {
 
567
   my ( $text, $complete, $debug, $full ) = @_;
 
568
   my @result;
 
569
 
 
570
   foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) {
 
571
      my $hash = {};
 
572
      @{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) }
 
573
         = $lock
 
574
         =~ m{^(RECORD|TABLE) LOCKS? (?:space id $d page no $d n bits $d index `?$n`? of )?table `$n(?:/|`\.`)$n` trx id $t lock.mode (\S+)}m;
 
575
      ( $hash->{'special'} )
 
576
         = $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m;
 
577
      $hash->{'insert_intention'}
 
578
         = $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0;
 
579
      $hash->{'waiting'}
 
580
         = $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0;
 
581
 
 
582
      # Some things may not be in the text, so make sure they are not
 
583
      # undef.
 
584
      map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id);
 
585
      map { $hash->{$_} ||= "" } qw(index special);
 
586
      push @result, $hash;
 
587
   }
 
588
 
 
589
   return @result;
 
590
}
 
591
 
 
592
sub parse_tx_text {
 
593
   my ( $txn, $complete, $debug, $full ) = @_;
 
594
 
 
595
   my ( $txn_id, $txn_status, $active_secs, $proc_no, $os_thread_id )
 
596
      = $txn
 
597
      =~ m/^(?:---)?TRANSACTION $t, (\D*?)(?: $d sec)?, (?:process no $d, )?OS thread id $d/m;
 
598
   my ( $thread_status, $thread_decl_inside )
 
599
      = $txn
 
600
      =~ m/OS thread id \d+(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m;
 
601
 
 
602
   # Parsing the line that begins 'MySQL thread id' is complicated.  The only
 
603
   # thing always in the line is the thread and query id.  See function
 
604
   # innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc.
 
605
   my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m;
 
606
   my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status );
 
607
 
 
608
   if ( $thread_line ) {
 
609
      # These parts can always be gotten.
 
610
      ( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, query id $d/m;
 
611
 
 
612
      # If it's a master/slave thread, "Has (read|sent) all" may be the thread's
 
613
      # proc_info.  In these cases, there won't be any host/ip/user info
 
614
      ( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m;
 
615
      if ( defined($query_status) ) {
 
616
         $user = 'system user';
 
617
      }
 
618
 
 
619
      # It may be the case that the query id is the last thing in the line.
 
620
      elsif ( $thread_line =~ m/query id \d+ / ) {
 
621
         # The IP address is the only non-word thing left, so it's the most
 
622
         # useful marker for where I have to start guessing.
 
623
         ( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m;
 
624
         if ( defined $ip ) {
 
625
            ( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/;
 
626
         }
 
627
         else { # OK, there wasn't an IP address.
 
628
            # There might not be ANYTHING except the query status.
 
629
            ( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/;
 
630
            if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) {
 
631
               # The remaining tokens are, in order: hostname, user, query_status.
 
632
               # It's basically impossible to know which is which.
 
633
               ( $hostname, $user, $query_status ) = $thread_line
 
634
                  =~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m;
 
635
            }
 
636
            else {
 
637
               $user = 'system user';
 
638
            }
 
639
         }
 
640
      }
 
641
   }
 
642
 
 
643
   my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries )
 
644
      = $txn
 
645
      =~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m;
 
646
   my ( $lock_wait_time )
 
647
      = $txn
 
648
      =~ m/^------- TRX HAS BEEN WAITING $d SEC/m;
 
649
 
 
650
   my $locks;
 
651
   # If the transaction has locks, grab the locks.
 
652
   if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) {
 
653
      $locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)];
 
654
   }
 
655
   
 
656
   my ( $tables_in_use, $tables_locked )
 
657
      = $txn
 
658
      =~ m/^mysql tables in use $d, locked $d$/m;
 
659
   my ( $txn_doesnt_see_ge, $txn_sees_lt )
 
660
      = $txn
 
661
      =~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m;
 
662
   my $has_read_view = defined($txn_doesnt_see_ge);
 
663
   # Only a certain number of bytes of the query text are included here, at least
 
664
   # under some circumstances.  Some versions include 300, some 600.
 
665
   my ( $query_text )
 
666
      = $txn
 
667
      =~ m{
 
668
         ^MySQL\sthread\sid\s[^\n]+\n           # This comes before the query text
 
669
         (.*?)                                  # The query text
 
670
         (?=                                    # Followed by any of...
 
671
            ^Trx\sread\sview
 
672
            |^-------\sTRX\sHAS\sBEEN\sWAITING
 
673
            |^TABLE\sLOCK
 
674
            |^RECORD\sLOCKS\sspace\sid
 
675
            |^(?:---)?TRANSACTION
 
676
            |^\*\*\*\s\(\d\)
 
677
            |\Z
 
678
         )
 
679
      }xms;
 
680
   if ( $query_text ) {
 
681
      $query_text =~ s/\s+$//;
 
682
   }
 
683
   else {
 
684
      $query_text = '';
 
685
   }
 
686
 
 
687
   my %stuff = (
 
688
      active_secs        => $active_secs,
 
689
      has_read_view      => $has_read_view,
 
690
      heap_size          => $heap_size,
 
691
      hostname           => $hostname,
 
692
      ip                 => $ip,
 
693
      lock_structs       => $lock_structs,
 
694
      lock_wait_status   => $lock_wait_status,
 
695
      lock_wait_time     => $lock_wait_time,
 
696
      mysql_thread_id    => $mysql_thread_id,
 
697
      os_thread_id       => $os_thread_id,
 
698
      proc_no            => $proc_no,
 
699
      query_id           => $query_id,
 
700
      query_status       => $query_status,
 
701
      query_text         => $query_text,
 
702
      row_locks          => $row_locks,
 
703
      tables_in_use      => $tables_in_use,
 
704
      tables_locked      => $tables_locked,
 
705
      thread_decl_inside => $thread_decl_inside,
 
706
      thread_status      => $thread_status,
 
707
      txn_doesnt_see_ge  => $txn_doesnt_see_ge,
 
708
      txn_id             => $txn_id,
 
709
      txn_sees_lt        => $txn_sees_lt,
 
710
      txn_status         => $txn_status,
 
711
      undo_log_entries   => $undo_log_entries,
 
712
      user               => $user,
 
713
   );
 
714
   $stuff{'fulltext'} = $txn if $debug;
 
715
   $stuff{'locks'} = $locks if $locks;
 
716
 
 
717
   # Some things may not be in the txn text, so make sure they are not
 
718
   # undef.
 
719
   map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs
 
720
         tables_in_use undo_log_entries tables_locked has_read_view
 
721
         thread_decl_inside lock_wait_time proc_no row_locks);
 
722
   map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge
 
723
         txn_sees_lt query_status ip query_text lock_wait_status user);
 
724
   $stuff{'hostname'} ||= $stuff{'ip'};
 
725
 
 
726
   return \%stuff;
 
727
}
 
728
 
 
729
sub parse_tx_section {
 
730
   my ( $section, $complete, $debug, $full ) = @_;
 
731
   return unless $section && $section->{'fulltext'};
 
732
   my $fulltext = $section->{'fulltext'};
 
733
   $section->{'transactions'} = [];
 
734
 
 
735
   # Handle the individual transactions
 
736
   my @transactions = $fulltext =~ m/(---TRANSACTION \d.*?)(?=\n---TRANSACTION|$)/gs;
 
737
   foreach my $txn ( @transactions ) {
 
738
      my $stuff = parse_tx_text( $txn, $complete, $debug, $full );
 
739
      delete $stuff->{'fulltext'} unless $debug;
 
740
      push @{$section->{'transactions'}}, $stuff;
 
741
   }
 
742
 
 
743
   # Handle the general info
 
744
   @{$section}{ 'trx_id_counter' }
 
745
      = $fulltext =~ m/^Trx id counter $t$/m;
 
746
   @{$section}{ 'purge_done_for', 'purge_undo_for' }
 
747
      = $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m;
 
748
   @{$section}{ 'history_list_len' } # This isn't present in some 4.x versions
 
749
      = $fulltext =~ m/^History list length $d$/m;
 
750
   @{$section}{ 'num_lock_structs' }
 
751
      = $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m;
 
752
   @{$section}{ 'is_truncated' }
 
753
      = $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0;
 
754
 
 
755
   # Fill in things that might not be present
 
756
   foreach ( qw(history_list_len) ) {
 
757
      $section->{$_} ||= 0;
 
758
   }
 
759
 
 
760
   delete $section->{'fulltext'} unless $debug;
 
761
   return 1;
 
762
}
 
763
 
 
764
# I've read the source for this section.
 
765
sub parse_ro_section {
 
766
   my ( $section, $complete, $debug, $full ) = @_;
 
767
   return unless $section && $section->{'fulltext'};
 
768
   my $fulltext = $section->{'fulltext'};
 
769
 
 
770
   # Grab the info
 
771
   @{$section}{ 'queries_inside', 'queries_in_queue' }
 
772
      = $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m;
 
773
   ( $section->{ 'read_views_open' } )
 
774
      = $fulltext =~ m/^$d read views open inside InnoDB$/m;
 
775
   ( $section->{ 'n_reserved_extents' } )
 
776
      = $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m;
 
777
   @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' }
 
778
      = $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m;
 
779
   @{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' }
 
780
      = $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m;
 
781
   @{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' }
 
782
      = $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m;
 
783
   $section->{'main_thread_proc_no'} ||= 0;
 
784
 
 
785
   map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents);
 
786
   delete $section->{'fulltext'} unless $debug;
 
787
   return 1;
 
788
}
 
789
 
 
790
sub parse_lg_section {
 
791
   my ( $section, $complete, $debug, $full ) = @_;
 
792
   return unless $section;
 
793
   my $fulltext = $section->{'fulltext'};
 
794
 
 
795
   # Grab the info
 
796
   ( $section->{ 'log_seq_no' } )
 
797
      = $fulltext =~ m/Log sequence number \s*(\d.*)$/m;
 
798
   ( $section->{ 'log_flushed_to' } )
 
799
      = $fulltext =~ m/Log flushed up to \s*(\d.*)$/m;
 
800
   ( $section->{ 'last_chkp' } )
 
801
      = $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m;
 
802
   @{$section}{ 'pending_log_writes', 'pending_chkp_writes' }
 
803
      = $fulltext =~ m/$d pending log writes, $d pending chkp writes/;
 
804
   @{$section}{ 'log_ios_done', 'log_ios_s' }
 
805
      = $fulltext =~ m#$d log i/o's done, $f log i/o's/second#;
 
806
 
 
807
   delete $section->{'fulltext'} unless $debug;
 
808
   return 1;
 
809
}
 
810
 
 
811
sub parse_ib_section {
 
812
   my ( $section, $complete, $debug, $full ) = @_;
 
813
   return unless $section && $section->{'fulltext'};
 
814
   my $fulltext = $section->{'fulltext'};
 
815
 
 
816
   # Some servers will output ibuf information for tablespace 0, as though there
 
817
   # might be many tablespaces with insert buffers.  (In practice I believe
 
818
   # the source code shows there will only ever be one).  I have to parse both
 
819
   # cases here, but I assume there will only be one.
 
820
   @{$section}{ 'size', 'free_list_len', 'seg_size' }
 
821
      = $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d,$/m;
 
822
   @{$section}{ 'inserts', 'merged_recs', 'merges' }
 
823
      = $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m;
 
824
 
 
825
   @{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' }
 
826
      = $fulltext =~ m/^Hash table size $d, used cells $d, node heap has $d buffer\(s\)$/m;
 
827
   @{$section}{ 'hash_searches_s', 'non_hash_searches_s' }
 
828
      = $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m;
 
829
 
 
830
   delete $section->{'fulltext'} unless $debug;
 
831
   return 1;
 
832
}
 
833
 
 
834
sub parse_wait_array {
 
835
   my ( $text, $complete, $debug, $full ) = @_;
 
836
   my %result;
 
837
 
 
838
   @result{ qw(thread waited_at_filename waited_at_line waited_secs) }
 
839
      = $text =~ m/^--Thread $d has waited at $fl for $f seconds/m;
 
840
 
 
841
   # Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED,
 
842
   # there will be different text output
 
843
   if ( $text =~ m/^Mutex at/m ) {
 
844
      $result{'request_type'} = 'M';
 
845
      @result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) }
 
846
         = $text =~ m/^Mutex at $h created file $fl, lock var $d$/m;
 
847
      @result{ qw( waiters_flag )}
 
848
         = $text =~ m/^waiters flag $d$/m;
 
849
   }
 
850
   else {
 
851
      @result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) }
 
852
         = $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m;
 
853
      @result{ qw( writer_thread writer_lock_mode ) }
 
854
         = $text =~ m/^a writer \(thread id $d\) has reserved it in mode  (.*)$/m;
 
855
      @result{ qw( num_readers waiters_flag )}
 
856
         = $text =~ m/^number of readers $d, waiters flag $d$/m;
 
857
      @result{ qw(last_s_file_name last_s_line ) }
 
858
         = $text =~ m/Last time read locked in file $fl$/m;
 
859
      @result{ qw(last_x_file_name last_x_line ) }
 
860
         = $text =~ m/Last time write locked in file $fl$/m;
 
861
   }
 
862
 
 
863
   $result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1;
 
864
   $result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0;
 
865
 
 
866
   # Because there are two code paths, some things won't get set.
 
867
   map { $result{$_} ||= '' }
 
868
      qw(last_s_file_name last_x_file_name writer_lock_mode);
 
869
   map { $result{$_} ||= 0 }
 
870
      qw(num_readers lock_var last_s_line last_x_line writer_thread);
 
871
 
 
872
   return \%result;
 
873
}
 
874
 
 
875
sub parse_sm_section {
 
876
   my ( $section, $complete, $debug, $full ) = @_;
 
877
   return 0 unless $section && $section->{'fulltext'};
 
878
   my $fulltext = $section->{'fulltext'};
 
879
 
 
880
   # Grab the info
 
881
   @{$section}{ 'reservation_count', 'signal_count' }
 
882
      = $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m;
 
883
   @{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' }
 
884
      = $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m;
 
885
   @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' }
 
886
      = $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m;
 
887
 
 
888
   # Look for info on waits.
 
889
   my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms;
 
890
   $section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ];
 
891
   $section->{'wait_array_size'} = scalar(@waits);
 
892
 
 
893
   delete $section->{'fulltext'} unless $debug;
 
894
   return 1;
 
895
}
 
896
 
 
897
# I've read the source for this section.
 
898
sub parse_bp_section {
 
899
   my ( $section, $complete, $debug, $full ) = @_;
 
900
   return unless $section && $section->{'fulltext'};
 
901
   my $fulltext = $section->{'fulltext'};
 
902
 
 
903
   # Grab the info
 
904
   @{$section}{ 'total_mem_alloc', 'add_pool_alloc' }
 
905
      = $fulltext =~ m/^Total memory allocated $d; in additional pool allocated $d$/m;
 
906
   @{$section}{'dict_mem_alloc'}     = $fulltext =~ m/Dictionary memory allocated $d/;
 
907
   @{$section}{'awe_mem_alloc'}      = $fulltext =~ m/$d MB of AWE memory/;
 
908
   @{$section}{'buf_pool_size'}      = $fulltext =~ m/^Buffer pool size\s*$d$/m;
 
909
   @{$section}{'buf_free'}           = $fulltext =~ m/^Free buffers\s*$d$/m;
 
910
   @{$section}{'pages_total'}        = $fulltext =~ m/^Database pages\s*$d$/m;
 
911
   @{$section}{'pages_modified'}     = $fulltext =~ m/^Modified db pages\s*$d$/m;
 
912
   @{$section}{'pages_read', 'pages_created', 'pages_written'}
 
913
      = $fulltext =~ m/^Pages read $d, created $d, written $d$/m;
 
914
   @{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'}
 
915
      = $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m;
 
916
   @{$section}{'buf_pool_hits', 'buf_pool_reads'}
 
917
      = $fulltext =~ m{Buffer pool hit rate $d / $d$}m;
 
918
   if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) {
 
919
      @{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0);
 
920
      @{$section}{'buf_pool_hit_rate'} = '--';
 
921
   }
 
922
   else {
 
923
      @{$section}{'buf_pool_hit_rate'}
 
924
         = $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)$}m;
 
925
   }
 
926
   @{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads $d/m;
 
927
   @{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' }
 
928
      = $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m;
 
929
 
 
930
   map { $section->{$_} ||= 0 }
 
931
      qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page
 
932
      awe_mem_alloc dict_mem_alloc);
 
933
   @{$section}{'writes_pending'} = List::Util::sum(
 
934
      @{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) });
 
935
 
 
936
   delete $section->{'fulltext'} unless $debug;
 
937
   return 1;
 
938
}
 
939
 
 
940
# I've read the source for this.
 
941
sub parse_io_section {
 
942
   my ( $section, $complete, $debug, $full ) = @_;
 
943
   return unless $section && $section->{'fulltext'};
 
944
   my $fulltext = $section->{'fulltext'};
 
945
   $section->{'threads'} = {};
 
946
 
 
947
   # Grab the I/O thread info
 
948
   my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm;
 
949
   foreach my $thread (@threads) {
 
950
      my ( $tid, $state, $purpose, $event_set )
 
951
         = $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m;
 
952
      if ( defined $tid ) {
 
953
         $section->{'threads'}->{$tid} = {
 
954
            thread    => $tid,
 
955
            state     => $state,
 
956
            purpose   => $purpose,
 
957
            event_set => $event_set ? 1 : 0,
 
958
         };
 
959
      }
 
960
   }
 
961
 
 
962
   # Grab the reads/writes/flushes info
 
963
   @{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' }
 
964
      = $fulltext =~ m/^Pending normal aio reads: $d, aio writes: $d,$/m;
 
965
   @{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' }
 
966
      = $fulltext =~ m{^ ibuf aio reads: $d, log i/o's: $d, sync i/o's: $d$}m;
 
967
   @{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' }
 
968
      = $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m;
 
969
   @{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' }
 
970
      = $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m;
 
971
   @{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' }
 
972
      = $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m;
 
973
   @{$section}{ 'pending_preads', 'pending_pwrites' }
 
974
      = $fulltext =~ m/$d pending preads, $d pending pwrites$/m;
 
975
   @{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0)
 
976
      unless defined($section->{'pending_preads'});
 
977
 
 
978
   delete $section->{'fulltext'} unless $debug;
 
979
   return 1;
 
980
}
 
981
 
 
982
sub _debug {
 
983
   my ( $debug, $msg ) = @_;
 
984
   if ( $debug ) {
 
985
      die $msg;
 
986
   }
 
987
   else {
 
988
      warn $msg;
 
989
   }
 
990
   return 1;
 
991
}
 
992
 
 
993
1;
 
994
 
 
995
# end_of_package
 
996
# ############################################################################
 
997
# Perldoc section.  I put this last as per the Dog book.
 
998
# ############################################################################
 
999
=pod
 
1000
 
 
1001
=head1 NAME
 
1002
 
 
1003
InnoDBParser - Parse InnoDB monitor text.
 
1004
 
 
1005
=head1 DESCRIPTION
 
1006
 
 
1007
InnoDBParser tries to parse the output of the InnoDB monitor.  One way to get
 
1008
this output is to connect to a MySQL server and issue the command SHOW ENGINE
 
1009
INNODB STATUS (omit 'ENGINE' on earlier versions of MySQL).  The goal is to
 
1010
turn text into data that something else (e.g. innotop) can use.
 
1011
 
 
1012
The output comes from all over, but the place to start in the source is
 
1013
innobase/srv/srv0srv.c.
 
1014
 
 
1015
=head1 SYNOPSIS
 
1016
 
 
1017
   use InnoDBParser;
 
1018
   use DBI;
 
1019
 
 
1020
   # Get the status text.
 
1021
   my $dbh = DBI->connect(
 
1022
      "DBI::mysql:test;host=localhost",
 
1023
      'user',
 
1024
      'password'
 
1025
   );
 
1026
   my $query = 'SHOW /*!5 ENGINE */ INNODB STATUS';
 
1027
   my $text  = $dbh->selectcol_arrayref($query)->[0];
 
1028
 
 
1029
   # 1 or 0
 
1030
   my $debug = 1;
 
1031
 
 
1032
   # Choose sections of the monitor text you want.  Possible values:
 
1033
   # TRANSACTIONS                          => tx
 
1034
   # BUFFER POOL AND MEMORY                => bp
 
1035
   # SEMAPHORES                            => sm
 
1036
   # LOG                                   => lg
 
1037
   # ROW OPERATIONS                        => ro
 
1038
   # INSERT BUFFER AND ADAPTIVE HASH INDEX => ib
 
1039
   # FILE I/O                              => io
 
1040
   # LATEST DETECTED DEADLOCK              => dl
 
1041
   # LATEST FOREIGN KEY ERROR              => fk
 
1042
 
 
1043
   my $required_sections = {
 
1044
      tx => 1,
 
1045
   };
 
1046
 
 
1047
   # Parse the status text.
 
1048
   my $parser = InnoDBParser->new;
 
1049
   $innodb_status = $parser->parse_status_text(
 
1050
      $text,
 
1051
      $debug,
 
1052
      # Omit the following parameter to get all sections.
 
1053
      $required_sections,
 
1054
   );
 
1055
 
 
1056
=head1 COPYRIGHT, LICENSE AND WARRANTY
 
1057
 
 
1058
This package is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
 
1059
Feedback and improvements are gratefully received.
 
1060
 
 
1061
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
1062
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
1063
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
1064
 
 
1065
This program is free software; you can redistribute it and/or modify it under
 
1066
the terms of the GNU General Public License as published by the Free Software
 
1067
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
1068
systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
1069
licenses.
 
1070
 
 
1071
You should have received a copy of the GNU General Public License along with
 
1072
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
1073
Place, Suite 330, Boston, MA  02111-1307  USA
 
1074
 
 
1075
=head1 AUTHOR
 
1076
 
 
1077
Baron Schwartz, baron at xaprb dot com.
 
1078
 
 
1079
=head1 BUGS
 
1080
 
 
1081
None known, but I bet there are some.  The InnoDB monitor text wasn't really
 
1082
designed to be parsable.
 
1083
 
 
1084
=head1 SEE ALSO
 
1085
 
 
1086
innotop - a program that can format the parsed status information for humans
 
1087
to read and enjoy.
 
1088
 
 
1089
=cut