3
3
# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
5
# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
6
# Feedback and improvements are gratefully received.
8
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
9
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
10
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
12
# This program is free software; you can redistribute it and/or modify it under
13
# the terms of the GNU General Public License as published by the Free Software
14
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
15
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
17
# You should have received a copy of the GNU General Public License along with
18
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19
# Place, Suite 330, Boston, MA 02111-1307 USA
6
22
use warnings FATAL => 'all';
24
our $VERSION = '1.7.1';
26
# Find the home directory; it's different on different OSes.
27
our $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
30
our $default_home_conf = "$homepath/.innotop/innotop.conf";
31
our $default_central_conf = "/etc/innotop/innotop.conf";
40
$Data::Dumper::Indent = 0;
41
$Data::Dumper::Quotekeys = 0;
42
use English qw(-no_match_vars);
44
use constant MKDEBUG => $ENV{MKDEBUG};
46
# Defaults are built-in, but you can add/replace items by passing them as
47
# hashrefs of {key, desc, copy, dsn}. The desc and dsn items are optional.
48
# You can set properties with the prop() sub. Don't set the 'opts' property.
50
my ( $class, @opts ) = @_;
54
desc => 'Default character set',
59
desc => 'Database to use',
64
desc => 'Only read default options from the given file',
65
dsn => 'mysql_read_default_file',
69
desc => 'Connect to host',
74
desc => 'Password to use when connecting',
79
desc => 'Port number to use for connection',
84
desc => 'Socket file to use for connection',
85
dsn => 'mysql_socket',
89
desc => 'User for login if not current user',
95
foreach my $opt ( @opts ) {
96
MKDEBUG && _d('Adding extra property ' . $opt->{key});
97
$self->{opts}->{$opt->{key}} = { desc => $opt->{desc}, copy => $opt->{copy} };
99
return bless $self, $class;
102
# Recognized properties:
103
# * autokey: which key to treat a bareword as (typically h=host).
104
# * dbidriver: which DBI driver to use; assumes mysql, supports Pg.
105
# * required: which parts are required (hashref).
106
# * setvars: a list of variables to set after connecting
108
my ( $self, $prop, $value ) = @_;
110
MKDEBUG && _d("Setting $prop property");
111
$self->{$prop} = $value;
113
return $self->{$prop};
117
my ( $self, $dsn, $prev, $defaults ) = @_;
119
MKDEBUG && _d('No DSN to parse');
122
MKDEBUG && _d("Parsing $dsn");
127
my %opts = %{$self->{opts}};
128
my $prop_autokey = $self->prop('autokey');
131
foreach my $dsn_part ( split(/,/, $dsn) ) {
132
if ( my ($prop_key, $prop_val) = $dsn_part =~ m/^(.)=(.*)$/ ) {
133
# Handle the typical DSN parts like h=host, P=3306, etc.
134
$given_props{$prop_key} = $prop_val;
136
elsif ( $prop_autokey ) {
138
MKDEBUG && _d("Interpreting $dsn_part as $prop_autokey=$dsn_part");
139
$given_props{$prop_autokey} = $dsn_part;
142
MKDEBUG && _d("Bad DSN part: $dsn_part");
146
# Fill in final props from given, previous, and/or default props
147
foreach my $key ( keys %opts ) {
148
MKDEBUG && _d("Finding value for $key");
149
$final_props{$key} = $given_props{$key};
150
if ( !defined $final_props{$key}
151
&& defined $prev->{$key} && $opts{$key}->{copy} )
153
$final_props{$key} = $prev->{$key};
154
MKDEBUG && _d("Copying value for $key from previous DSN");
156
if ( !defined $final_props{$key} ) {
157
$final_props{$key} = $defaults->{$key};
158
MKDEBUG && _d("Copying value for $key from defaults");
163
foreach my $key ( keys %given_props ) {
164
die "Unrecognized DSN part '$key' in '$dsn'\n"
165
unless exists $opts{$key};
167
if ( (my $required = $self->prop('required')) ) {
168
foreach my $key ( keys %$required ) {
169
die "Missing DSN part '$key' in '$dsn'\n" unless $final_props{$key};
173
return \%final_props;
177
my ( $self, $dsn ) = @_;
178
return $dsn unless ref $dsn;
180
map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) }
181
grep { defined $dsn->{$_} && $self->{opts}->{$_} }
188
= "DSN syntax is key=value[,key=value...] Allowable DSN keys:\n"
189
. " KEY COPY MEANING\n"
190
. " === ==== =============================================\n";
191
my %opts = %{$self->{opts}};
192
foreach my $key ( sort keys %opts ) {
194
. ($opts{$key}->{copy} ? 'yes ' : 'no ')
195
. ($opts{$key}->{desc} || '[No description]')
198
if ( (my $key = $self->prop('autokey')) ) {
199
$usage .= " If the DSN is a bareword, the word is treated as the '$key' key.\n";
204
# Supports PostgreSQL via the dbidriver element of $info, but assumes MySQL by
207
my ( $self, $info ) = @_;
209
my %opts = %{$self->{opts}};
210
my $driver = $self->prop('dbidriver') || '';
211
if ( $driver eq 'Pg' ) {
212
$dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';'
213
. join(';', map { "$opts{$_}->{dsn}=$info->{$_}" }
214
grep { defined $info->{$_} }
218
$dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';'
219
. join(';', map { "$opts{$_}->{dsn}=$info->{$_}" }
220
grep { defined $info->{$_} }
222
. ';mysql_read_default_group=client';
225
return ($dsn, $info->{u}, $info->{p});
229
# Fills in missing info from a DSN after successfully connecting to the server.
231
my ( $self, $dbh, $dsn ) = @_;
232
my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name');
233
my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()');
235
$dsn->{h} ||= $vars->{hostname}->{Value};
236
$dsn->{S} ||= $vars->{'socket'}->{Value};
237
$dsn->{P} ||= $vars->{port}->{Value};
243
my ( $self, $cxn_string, $user, $pass, $opts ) = @_;
249
mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/ ? 1 : 0),
251
@{$defaults}{ keys %$opts } = values %$opts;
254
while ( !$dbh && $tries-- ) {
256
MKDEBUG && _d($cxn_string, ' ', $user, ' ', $pass, ' {',
257
join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults ), '}');
258
$dbh = DBI->connect($cxn_string, $user, $pass, $defaults);
259
# Immediately set character set and binmode on STDOUT.
260
if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) {
261
my $sql = "/*!40101 SET NAMES $charset*/";
262
MKDEBUG && _d("$dbh: $sql");
264
MKDEBUG && _d('Enabling charset for STDOUT');
265
if ( $charset eq 'utf8' ) {
266
binmode(STDOUT, ':utf8')
267
or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR";
270
binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR";
274
if ( !$dbh && $EVAL_ERROR ) {
275
MKDEBUG && _d($EVAL_ERROR);
276
if ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) {
277
MKDEBUG && _d("Going to try again without utf8 support");
278
delete $defaults->{mysql_enable_utf8};
285
# If setvars exists and it's MySQL connection, set them
286
my $setvars = $self->prop('setvars');
287
if ( $cxn_string =~ m/mysql/i && $setvars ) {
288
my $sql = "SET $setvars";
289
MKDEBUG && _d("$dbh: $sql");
294
MKDEBUG && _d($EVAL_ERROR);
297
MKDEBUG && _d('DBH info: ',
299
Dumper($dbh->selectrow_hashref(
300
'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')),
301
' Connection info: ', ($dbh->{mysql_hostinfo} || 'undef'),
302
' Character set info: ',
303
Dumper($dbh->selectall_arrayref(
304
'SHOW VARIABLES LIKE "character_set%"', { Slice => {}})),
305
' $DBD::mysql::VERSION: ', $DBD::mysql::VERSION,
306
' $DBI::VERSION: ', $DBI::VERSION,
311
# Tries to figure out a hostname for the connection.
313
my ( $self, $dbh ) = @_;
314
if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) {
317
my ( $hostname, $one ) = $dbh->selectrow_array(
318
'SELECT /*!50038 @@hostname, */ 1');
322
# Disconnects a database handle, but complains verbosely if there are any active
323
# children. These are usually $sth handles that haven't been finish()ed.
325
my ( $self, $dbh ) = @_;
326
MKDEBUG && $self->print_active_handles($dbh);
330
sub print_active_handles {
331
my ( $self, $thing, $level ) = @_;
333
printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level,
334
$thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : ''))
335
or die "Cannot print: $OS_ERROR";
336
foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) {
337
$self->print_active_handles( $handle, $level + 1 );
342
my ($package, undef, $line) = caller 0;
343
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
344
map { defined $_ ? $_ : 'undef' }
346
# Use $$ instead of $PID in case the package
347
# does not use English.
348
print "# $package:$line $$ ", @_, "\n";
353
package InnoDBParser;
356
$Data::Dumper::Sortkeys = 1;
357
use English qw(-no_match_vars);
358
use List::Util qw(max);
360
# Some common patterns
361
my $d = qr/(\d+)/; # Digit
362
my $f = qr/(\d+\.\d+)/; # Float
363
my $t = qr/(\d+ \d+)/; # Transaction ID
364
my $i = qr/((?:\d{1,3}\.){3}\d+)/; # IP address
365
my $n = qr/([^`\s]+)/; # MySQL object name
366
my $w = qr/(\w+)/; # Words
367
my $fl = qr/([\w\.\/]+) line $d/; # Filename and line number
368
my $h = qr/((?:0x)?[0-9a-f]*)/; # Hex
369
my $s = qr/(\d{6} .\d:\d\d:\d\d)/; # InnoDB timestamp
371
# If you update this variable, also update the SYNOPSIS in the pod.
372
my %innodb_section_headers = (
373
"TRANSACTIONS" => "tx",
374
"BUFFER POOL AND MEMORY" => "bp",
375
"SEMAPHORES" => "sm",
377
"ROW OPERATIONS" => "ro",
378
"INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib",
380
"LATEST DETECTED DEADLOCK" => "dl",
381
"LATEST FOREIGN KEY ERROR" => "fk",
385
tx => \&parse_tx_section,
386
bp => \&parse_bp_section,
387
sm => \&parse_sm_section,
388
lg => \&parse_lg_section,
389
ro => \&parse_ro_section,
390
ib => \&parse_ib_section,
391
io => \&parse_io_section,
392
dl => \&parse_dl_section,
393
fk => \&parse_fk_section,
396
my %fk_parser_for = (
397
Transaction => \&parse_fk_transaction_error,
398
Error => \&parse_fk_bad_constraint_error,
399
Cannot => \&parse_fk_cant_drop_parent_error,
402
# A thread's proc_info can be at least 98 different things I've found in the
403
# source. Fortunately, most of them begin with a gerunded verb. These are
404
# the ones that don't.
407
'Execution of init_command' => 1,
408
'FULLTEXT initialization' => 1,
409
'Reopen tables' => 1,
411
'Repair with keycache' => 1,
414
'Thread initialized' => 1,
416
'copy to tmp table' => 1,
417
'discard_or_import_tablespace' => 1,
419
'got handler lock' => 1,
420
'got old table' => 1,
426
'rename result table' => 1,
439
# Parse the status and return it.
440
# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c
441
# Pass in the text to parse, whether to be in debugging mode, which sections
442
# to parse (hashref; if empty, parse all), and whether to parse full info from
443
# locks and such (probably shouldn't unless you need to).
444
sub parse_status_text {
445
my ( $self, $fulltext, $debug, $sections, $full ) = @_;
447
die "I can't parse undef" unless defined $fulltext;
448
$fulltext =~ s/[\r\n]+/\n/g;
451
die '$sections must be a hashref' unless ref($sections) eq 'HASH';
454
got_all => 0, # Whether I was able to get the whole thing
455
ts => '', # Timestamp the server put on it
456
last_secs => 0, # Num seconds the averages are over
457
sections => {}, # Parsed values from each section
461
$innodb_data{'fulltext'} = $fulltext;
464
# Get the most basic info about the status: beginning and end, and whether
465
# I got the whole thing (if there has been a big deadlock and there are
466
# too many locks to print, the output might be truncated)
467
my ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m;
468
$innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ];
469
$innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'});
470
( $innodb_data{'last_secs'} ) = $fulltext
471
=~ m/Per second averages calculated from the last $d seconds/;
473
( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/;
474
$innodb_data{'got_all'} = $got_all || 0;
476
# Split it into sections. Each section begins with
481
my @matches = $fulltext
482
=~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs;
483
while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) {
484
$innodb_sections{$name} = [ $text, $end ? 1 : 0 ];
486
# The Row Operations section is a special case, because instead of ending
487
# with the beginning of another section, it ends with the end of the file.
488
# So this section is complete if the entire file is complete.
489
$innodb_sections{'ROW OPERATIONS'}->[1] ||= $innodb_data{'got_all'};
491
# Just for sanity's sake, make sure I understand what to do with each
494
foreach my $section ( keys %innodb_sections ) {
495
my $header = $innodb_section_headers{$section};
496
die "Unknown section $section in $fulltext\n"
498
$innodb_data{'sections'}->{ $header }
499
->{'fulltext'} = $innodb_sections{$section}->[0];
500
$innodb_data{'sections'}->{ $header }
501
->{'complete'} = $innodb_sections{$section}->[1];
505
_debug( $debug, $EVAL_ERROR);
508
# ################################################################
509
# Parse the detailed data out of the sections.
510
# ################################################################
512
foreach my $section ( keys %parser_for ) {
513
if ( defined $innodb_data{'sections'}->{$section}
514
&& (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) {
515
$parser_for{$section}->(
516
$innodb_data{'sections'}->{$section},
517
$innodb_data{'sections'}->{$section}->{'complete'},
520
or delete $innodb_data{'sections'}->{$section};
523
delete $innodb_data{'sections'}->{$section};
528
_debug( $debug, $EVAL_ERROR);
531
return \%innodb_data;
534
# Parses the status text and returns it flattened out as a single hash.
535
sub get_status_hash {
536
my ( $self, $fulltext, $debug, $sections, $full ) = @_;
538
# Parse the status text...
540
= $self->parse_status_text($fulltext, $debug, $sections, $full );
542
# Flatten the hierarchical structure into a single list by grabbing desired
545
(map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)),
546
(map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} }
547
qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads
548
awe_mem_alloc pages_modified writes_pending_lru page_creates_sec
549
reads_pending pages_total buf_pool_hits writes_pending_single_page
550
page_writes_sec pages_read pages_written page_reads_sec
551
writes_pending_flush_list buf_pool_size add_pool_alloc
552
dict_mem_alloc pages_created buf_free complete )),
553
(map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} }
554
qw( num_lock_structs history_list_len purge_done_for transactions
555
purge_undo_for is_truncated trx_id_counter complete )),
556
(map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} }
557
qw( hash_table_size hash_searches_s non_hash_searches_s
558
bufs_in_node_heap used_cells size free_list_len seg_size inserts
559
merged_recs merges complete )),
560
(map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} }
561
qw( log_ios_done pending_chkp_writes last_chkp log_ios_s
562
log_flushed_to log_seq_no pending_log_writes complete )),
563
(map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} }
564
qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits
565
mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits
566
waits signal_count reservation_count complete )),
567
(map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} }
568
qw( queries_in_queue n_reserved_extents main_thread_state
569
main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec
570
read_views_open num_rows_upd num_rows_ins num_rows_read
571
queries_inside num_rows_del complete )),
572
(map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} }
573
qw( trigger parent_table child_index parent_index attempted_op
574
child_db timestring fk_name records col_name reason txn parent_db
575
type child_table parent_col complete )),
576
(map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} }
577
qw( pending_buffer_pool_flushes pending_pwrites pending_preads
578
pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios
579
reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s
580
threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs
581
pending_log_flushes complete )),
582
(map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} }
583
qw( timestring rolled_back txns complete ));
589
return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts);
592
sub parse_innodb_timestamp {
594
my ( $y, $m, $d, $h, $i, $s )
595
= $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/;
596
die("Can't get timestamp from $text\n") unless $y;
598
return ( $y, $m, $d, $h, $i, $s );
601
sub parse_fk_section {
602
my ( $section, $complete, $debug, $full ) = @_;
603
my $fulltext = $section->{'fulltext'};
605
return 0 unless $fulltext;
607
my ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m;
608
$section->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
609
$section->{'timestring'} = ts_to_string($section->{'ts'});
610
$section->{'type'} = $type;
612
# Decide which type of FK error happened, and dispatch to the right parser.
613
if ( $type && $fk_parser_for{$type} ) {
614
$fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full );
617
delete $section->{'fulltext'} unless $debug;
622
sub parse_fk_cant_drop_parent_error {
623
my ( $section, $complete, $debug, $fulltext, $full ) = @_;
625
# Parse the parent/child table info out
626
@{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext
627
=~ m{Cannot $w table `(.*)/(.*)`}m;
628
@{$section}{ qw(child_db child_table) } = $fulltext
629
=~ m{because it is referenced by `(.*)/(.*)`}m;
631
( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s;
632
$section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm
633
if $section->{'reason'};
635
# Certain data may not be present. Make them '' if not present.
636
map { $section->{$_} ||= "" }
637
qw(child_index fk_name col_name parent_col);
640
# See dict/dict0dict.c, function dict_foreign_error_report
641
# I don't care much about these. There are lots of different messages, and
642
# they come from someone trying to create a foreign key, or similar
643
# statements. They aren't indicative of some transaction trying to insert,
644
# delete or update data. Sometimes it is possible to parse out a lot of
645
# information about the tables and indexes involved, but often the message
646
# contains the DDL string the user entered, which is way too much for this
647
# module to try to handle.
648
sub parse_fk_bad_constraint_error {
649
my ( $section, $complete, $debug, $fulltext, $full ) = @_;
651
# Parse the parent/child table and index info out
652
@{$section}{ qw(child_db child_table) } = $fulltext
653
=~ m{Error in foreign key constraint of table (.*)/(.*):$}m;
654
$section->{'attempted_op'} = 'DDL';
656
# FK name, parent info... if possible.
657
@{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
659
=~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/;
661
if ( !defined($section->{'fk_name'}) ) {
662
# Try to parse SQL a user might have typed in a CREATE statement or such
663
@{$section}{ qw(col_name parent_db parent_table parent_col) }
665
=~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i;
667
$section->{'parent_db'} ||= $section->{'child_db'};
669
# Name of the child index (index in the same table where the FK is, see
670
# definition of dict_foreign_struct in include/dict0mem.h, where it is
671
# called foreign_index, as opposed to referenced_index which is in the
672
# parent table. This may not be possible to find.
673
@{$section}{ qw(child_index) } = $fulltext
674
=~ m/^The index in the foreign key in table is $n$/m;
676
@{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms;
677
$section->{'reason'} =~ s/\s+/ /g
678
if $section->{'reason'};
680
# Certain data may not be present. Make them '' if not present.
681
map { $section->{$_} ||= "" }
682
qw(child_index fk_name col_name parent_table parent_col);
685
# see source file row/row0ins.c
686
sub parse_fk_transaction_error {
687
my ( $section, $complete, $debug, $fulltext, $full ) = @_;
689
# Parse the txn info out
690
my ( $txn ) = $fulltext
691
=~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s;
693
$section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full );
696
# Parse the parent/child table and index info out. There are two types: an
697
# update or a delete of a parent record leaves a child orphaned
698
# (row_ins_foreign_report_err), and an insert or update of a child record has
699
# no matching parent record (row_ins_foreign_report_add_err).
701
@{$section}{ qw(reason child_db child_table) }
702
= $fulltext =~ m{^(Foreign key constraint fails for table `(.*)/(.*)`:)$}m;
704
@{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
706
=~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/;
707
$section->{'parent_db'} ||= $section->{'child_db'};
709
# Special case, which I don't know how to trigger, but see
710
# innobase/row/row0ins.c row_ins_check_foreign_constraint
711
if ( $fulltext =~ m/ibd file does not currently exist!/ ) {
712
my ( $attempted_op, $index, $records )
713
= $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm;
714
$section->{'child_index'} = $index;
715
$section->{'attempted_op'} = $attempted_op || '';
716
if ( $records && $full ) {
717
( $section->{'records'} )
718
= parse_innodb_record_dump( $records, $complete, $debug );
720
@{$section}{qw(parent_db parent_table)}
721
=~ m/^But the parent table `$n`\.`$n`$/m;
724
my ( $attempted_op, $which, $index )
725
= $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m;
727
$section->{$which . '_index'} = $index;
728
$section->{'attempted_op'} = $attempted_op || '';
730
# Parse out the related records in the other table.
731
my ( $search_index, $records );
732
if ( $which eq 'child' ) {
733
( $search_index, $records ) = $fulltext
734
=~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms;
735
$section->{'parent_index'} = $search_index;
738
( $search_index, $records ) = $fulltext
739
=~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms;
740
$section->{'child_index'} = $search_index;
742
if ( $records && $full ) {
743
$section->{'records'}
744
= parse_innodb_record_dump( $records, $complete, $debug );
747
$section->{'records'} = '';
752
# Parse out the tuple trying to be updated, deleted or inserted.
753
my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m;
755
$section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug );
758
# Certain data may not be present. Make them '' if not present.
759
map { $section->{$_} ||= "" }
760
qw(child_index fk_name col_name parent_table parent_col);
763
# There are new-style and old-style record formats. See rem/rem0rec.c
764
# TODO: write some tests for this
765
sub parse_innodb_record_dump {
766
my ( $dump, $complete, $debug ) = @_;
767
return undef unless $dump;
771
if ( $dump =~ m/PHYSICAL RECORD/ ) {
772
my $style = $dump =~ m/compact format/ ? 'new' : 'old';
773
$result->{'style'} = $style;
775
# This is a new-style record.
776
if ( $style eq 'new' ) {
777
@{$result}{qw( heap_no type num_fields info_bits )}
779
=~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m;
782
# OK, it's old-style. Unfortunately there are variations here too.
783
elsif ( $dump =~ m/-byte offs / ) {
785
@{$result}{qw( heap_no type num_fields byte_offset info_bits )}
787
=~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m;
788
if ( $dump !~ m/-byte offs TRUE/ ) {
789
$result->{'byte_offset'} = 0;
794
@{$result}{qw( heap_no type num_fields byte_offset info_bits )}
796
=~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m;
801
$result->{'style'} = 'tuple';
802
@{$result}{qw( type num_fields )}
803
= $dump =~ m/^(DATA TUPLE): $d fields;$/m;
806
# Fill in default values for things that couldn't be parsed.
807
map { $result->{$_} ||= 0 }
808
qw(heap_no num_fields byte_offset info_bits);
809
map { $result->{$_} ||= '' }
812
my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm;
813
$result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ];
818
# New/old-style applies here. See rem/rem0rec.c
819
# $text should not include the leading space or the second trailing semicolon.
821
my ( $text, $complete, $debug ) = @_;
824
# '4: SQL NULL, size 4 '
825
# '1: len 6; hex 000000005601; asc V ;'
827
# '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)'
828
my ( $id, $nullsize, $len, $hex, $asc, $truncated );
829
( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/;
830
if ( !defined($id) ) {
831
( $id ) = $text =~ m/^$d: SQL NULL$/;
833
if ( !defined($id) ) {
834
( $id, $len, $hex, $asc, $truncated )
835
= $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/;
838
die "Could not parse this field: '$text'" unless defined $id;
841
len => defined($len) ? $len : defined($nullsize) ? $nullsize : 0,
842
'hex' => defined($hex) ? $hex : '',
843
asc => defined($asc) ? $asc : '',
844
trunc => $truncated ? 1 : 0,
849
sub parse_dl_section {
850
my ( $dl, $complete, $debug, $full ) = @_;
852
my $fulltext = $dl->{'fulltext'};
853
return 0 unless $fulltext;
855
my ( $ts ) = $fulltext =~ m/^$s$/m;
858
$dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
859
$dl->{'timestring'} = ts_to_string($dl->{'ts'});
865
^\*{3}\s([^\n]*) # *** (1) WAITING FOR THIS...
866
(.*?) # Followed by anything, non-greedy
867
(?=(?:^\*{3})|\z) # Followed by another three stars or EOF
871
# Loop through each section. There are no assumptions about how many
872
# there are, who holds and wants what locks, and who gets rolled back.
873
while ( my ($header, $body) = splice(@sections, 0, 2) ) {
874
my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/;
876
$dl->{'txns'}->{$txn_id} ||= {};
877
my $txn = $dl->{'txns'}->{$txn_id};
879
if ( $what eq 'TRANSACTION' ) {
880
$txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full );
883
push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full );
887
@{ $dl }{ qw(rolled_back) }
888
= $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m;
890
# Make sure certain values aren't undef
891
map { $dl->{$_} ||= '' } qw(rolled_back);
893
delete $dl->{'fulltext'} unless $debug;
897
sub parse_innodb_record_locks {
898
my ( $text, $complete, $debug, $full ) = @_;
901
foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) {
903
@{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) }
905
=~ 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;
906
( $hash->{'special'} )
907
= $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m;
908
$hash->{'insert_intention'}
909
= $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0;
911
= $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0;
913
# Some things may not be in the text, so make sure they are not
915
map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id);
916
map { $hash->{$_} ||= "" } qw(index special);
924
my ( $txn, $complete, $debug, $full ) = @_;
926
my ( $txn_id, $txn_status, $active_secs, $proc_no, $os_thread_id )
928
=~ m/^(?:---)?TRANSACTION $t, (\D*?)(?: $d sec)?, (?:process no $d, )?OS thread id $d/m;
929
my ( $thread_status, $thread_decl_inside )
931
=~ m/OS thread id \d+(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m;
933
# Parsing the line that begins 'MySQL thread id' is complicated. The only
934
# thing always in the line is the thread and query id. See function
935
# innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc.
936
my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m;
937
my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status );
939
if ( $thread_line ) {
940
# These parts can always be gotten.
941
( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, query id $d/m;
943
# If it's a master/slave thread, "Has (read|sent) all" may be the thread's
944
# proc_info. In these cases, there won't be any host/ip/user info
945
( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m;
946
if ( defined($query_status) ) {
947
$user = 'system user';
950
# It may be the case that the query id is the last thing in the line.
951
elsif ( $thread_line =~ m/query id \d+ / ) {
952
# The IP address is the only non-word thing left, so it's the most
953
# useful marker for where I have to start guessing.
954
( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m;
956
( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/;
958
else { # OK, there wasn't an IP address.
959
# There might not be ANYTHING except the query status.
960
( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/;
961
if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) {
962
# The remaining tokens are, in order: hostname, user, query_status.
963
# It's basically impossible to know which is which.
964
( $hostname, $user, $query_status ) = $thread_line
965
=~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m;
968
$user = 'system user';
974
my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries )
976
=~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m;
977
my ( $lock_wait_time )
979
=~ m/^------- TRX HAS BEEN WAITING $d SEC/m;
982
# If the transaction has locks, grab the locks.
983
if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) {
984
$locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)];
987
my ( $tables_in_use, $tables_locked )
989
=~ m/^mysql tables in use $d, locked $d$/m;
990
my ( $txn_doesnt_see_ge, $txn_sees_lt )
992
=~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m;
993
my $has_read_view = defined($txn_doesnt_see_ge);
994
# Only a certain number of bytes of the query text are included here, at least
995
# under some circumstances. Some versions include 300, some 600.
999
^MySQL\sthread\sid\s[^\n]+\n # This comes before the query text
1000
(.*?) # The query text
1001
(?= # Followed by any of...
1003
|^-------\sTRX\sHAS\sBEEN\sWAITING
1005
|^RECORD\sLOCKS\sspace\sid
1006
|^(?:---)?TRANSACTION
1011
if ( $query_text ) {
1012
$query_text =~ s/\s+$//;
1019
active_secs => $active_secs,
1020
has_read_view => $has_read_view,
1021
heap_size => $heap_size,
1022
hostname => $hostname,
1024
lock_structs => $lock_structs,
1025
lock_wait_status => $lock_wait_status,
1026
lock_wait_time => $lock_wait_time,
1027
mysql_thread_id => $mysql_thread_id,
1028
os_thread_id => $os_thread_id,
1029
proc_no => $proc_no,
1030
query_id => $query_id,
1031
query_status => $query_status,
1032
query_text => $query_text,
1033
row_locks => $row_locks,
1034
tables_in_use => $tables_in_use,
1035
tables_locked => $tables_locked,
1036
thread_decl_inside => $thread_decl_inside,
1037
thread_status => $thread_status,
1038
txn_doesnt_see_ge => $txn_doesnt_see_ge,
1040
txn_sees_lt => $txn_sees_lt,
1041
txn_status => $txn_status,
1042
undo_log_entries => $undo_log_entries,
1045
$stuff{'fulltext'} = $txn if $debug;
1046
$stuff{'locks'} = $locks if $locks;
1048
# Some things may not be in the txn text, so make sure they are not
1050
map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs
1051
tables_in_use undo_log_entries tables_locked has_read_view
1052
thread_decl_inside lock_wait_time proc_no row_locks);
1053
map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge
1054
txn_sees_lt query_status ip query_text lock_wait_status user);
1055
$stuff{'hostname'} ||= $stuff{'ip'};
1060
sub parse_tx_section {
1061
my ( $section, $complete, $debug, $full ) = @_;
1062
return unless $section && $section->{'fulltext'};
1063
my $fulltext = $section->{'fulltext'};
1064
$section->{'transactions'} = [];
1066
# Handle the individual transactions
1067
my @transactions = $fulltext =~ m/(---TRANSACTION \d.*?)(?=\n---TRANSACTION|$)/gs;
1068
foreach my $txn ( @transactions ) {
1069
my $stuff = parse_tx_text( $txn, $complete, $debug, $full );
1070
delete $stuff->{'fulltext'} unless $debug;
1071
push @{$section->{'transactions'}}, $stuff;
1074
# Handle the general info
1075
@{$section}{ 'trx_id_counter' }
1076
= $fulltext =~ m/^Trx id counter $t$/m;
1077
@{$section}{ 'purge_done_for', 'purge_undo_for' }
1078
= $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m;
1079
@{$section}{ 'history_list_len' } # This isn't present in some 4.x versions
1080
= $fulltext =~ m/^History list length $d$/m;
1081
@{$section}{ 'num_lock_structs' }
1082
= $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m;
1083
@{$section}{ 'is_truncated' }
1084
= $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0;
1086
# Fill in things that might not be present
1087
foreach ( qw(history_list_len) ) {
1088
$section->{$_} ||= 0;
1091
delete $section->{'fulltext'} unless $debug;
1095
# I've read the source for this section.
1096
sub parse_ro_section {
1097
my ( $section, $complete, $debug, $full ) = @_;
1098
return unless $section && $section->{'fulltext'};
1099
my $fulltext = $section->{'fulltext'};
1102
@{$section}{ 'queries_inside', 'queries_in_queue' }
1103
= $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m;
1104
( $section->{ 'read_views_open' } )
1105
= $fulltext =~ m/^$d read views open inside InnoDB$/m;
1106
( $section->{ 'n_reserved_extents' } )
1107
= $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m;
1108
@{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' }
1109
= $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m;
1110
@{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' }
1111
= $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m;
1112
@{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' }
1113
= $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m;
1114
$section->{'main_thread_proc_no'} ||= 0;
1116
map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents);
1117
delete $section->{'fulltext'} unless $debug;
1121
sub parse_lg_section {
1122
my ( $section, $complete, $debug, $full ) = @_;
1123
return unless $section;
1124
my $fulltext = $section->{'fulltext'};
1127
( $section->{ 'log_seq_no' } )
1128
= $fulltext =~ m/Log sequence number \s*(\d.*)$/m;
1129
( $section->{ 'log_flushed_to' } )
1130
= $fulltext =~ m/Log flushed up to \s*(\d.*)$/m;
1131
( $section->{ 'last_chkp' } )
1132
= $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m;
1133
@{$section}{ 'pending_log_writes', 'pending_chkp_writes' }
1134
= $fulltext =~ m/$d pending log writes, $d pending chkp writes/;
1135
@{$section}{ 'log_ios_done', 'log_ios_s' }
1136
= $fulltext =~ m#$d log i/o's done, $f log i/o's/second#;
1138
delete $section->{'fulltext'} unless $debug;
1142
sub parse_ib_section {
1143
my ( $section, $complete, $debug, $full ) = @_;
1144
return unless $section && $section->{'fulltext'};
1145
my $fulltext = $section->{'fulltext'};
1147
# Some servers will output ibuf information for tablespace 0, as though there
1148
# might be many tablespaces with insert buffers. (In practice I believe
1149
# the source code shows there will only ever be one). I have to parse both
1150
# cases here, but I assume there will only be one.
1151
@{$section}{ 'size', 'free_list_len', 'seg_size' }
1152
= $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d,$/m;
1153
@{$section}{ 'inserts', 'merged_recs', 'merges' }
1154
= $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m;
1156
@{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' }
1157
= $fulltext =~ m/^Hash table size $d, used cells $d, node heap has $d buffer\(s\)$/m;
1158
@{$section}{ 'hash_searches_s', 'non_hash_searches_s' }
1159
= $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m;
1161
delete $section->{'fulltext'} unless $debug;
1165
sub parse_wait_array {
1166
my ( $text, $complete, $debug, $full ) = @_;
1169
@result{ qw(thread waited_at_filename waited_at_line waited_secs) }
1170
= $text =~ m/^--Thread $d has waited at $fl for $f seconds/m;
1172
# Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED,
1173
# there will be different text output
1174
if ( $text =~ m/^Mutex at/m ) {
1175
$result{'request_type'} = 'M';
1176
@result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) }
1177
= $text =~ m/^Mutex at $h created file $fl, lock var $d$/m;
1178
@result{ qw( waiters_flag )}
1179
= $text =~ m/^waiters flag $d$/m;
1182
@result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) }
1183
= $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m;
1184
@result{ qw( writer_thread writer_lock_mode ) }
1185
= $text =~ m/^a writer \(thread id $d\) has reserved it in mode (.*)$/m;
1186
@result{ qw( num_readers waiters_flag )}
1187
= $text =~ m/^number of readers $d, waiters flag $d$/m;
1188
@result{ qw(last_s_file_name last_s_line ) }
1189
= $text =~ m/Last time read locked in file $fl$/m;
1190
@result{ qw(last_x_file_name last_x_line ) }
1191
= $text =~ m/Last time write locked in file $fl$/m;
1194
$result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1;
1195
$result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0;
1197
# Because there are two code paths, some things won't get set.
1198
map { $result{$_} ||= '' }
1199
qw(last_s_file_name last_x_file_name writer_lock_mode);
1200
map { $result{$_} ||= 0 }
1201
qw(num_readers lock_var last_s_line last_x_line writer_thread);
1206
sub parse_sm_section {
1207
my ( $section, $complete, $debug, $full ) = @_;
1208
return 0 unless $section && $section->{'fulltext'};
1209
my $fulltext = $section->{'fulltext'};
1212
@{$section}{ 'reservation_count', 'signal_count' }
1213
= $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m;
1214
@{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' }
1215
= $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m;
1216
@{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' }
1217
= $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m;
1219
# Look for info on waits.
1220
my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms;
1221
$section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ];
1222
$section->{'wait_array_size'} = scalar(@waits);
1224
delete $section->{'fulltext'} unless $debug;
1228
# I've read the source for this section.
1229
sub parse_bp_section {
1230
my ( $section, $complete, $debug, $full ) = @_;
1231
return unless $section && $section->{'fulltext'};
1232
my $fulltext = $section->{'fulltext'};
1235
@{$section}{ 'total_mem_alloc', 'add_pool_alloc' }
1236
= $fulltext =~ m/^Total memory allocated $d; in additional pool allocated $d$/m;
1237
@{$section}{'dict_mem_alloc'} = $fulltext =~ m/Dictionary memory allocated $d/;
1238
@{$section}{'awe_mem_alloc'} = $fulltext =~ m/$d MB of AWE memory/;
1239
@{$section}{'buf_pool_size'} = $fulltext =~ m/^Buffer pool size\s*$d$/m;
1240
@{$section}{'buf_free'} = $fulltext =~ m/^Free buffers\s*$d$/m;
1241
@{$section}{'pages_total'} = $fulltext =~ m/^Database pages\s*$d$/m;
1242
@{$section}{'pages_modified'} = $fulltext =~ m/^Modified db pages\s*$d$/m;
1243
@{$section}{'pages_read', 'pages_created', 'pages_written'}
1244
= $fulltext =~ m/^Pages read $d, created $d, written $d$/m;
1245
@{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'}
1246
= $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m;
1247
@{$section}{'buf_pool_hits', 'buf_pool_reads'}
1248
= $fulltext =~ m{Buffer pool hit rate $d / $d$}m;
1249
if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) {
1250
@{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0);
1251
@{$section}{'buf_pool_hit_rate'} = '--';
1254
@{$section}{'buf_pool_hit_rate'}
1255
= $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)$}m;
1257
@{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads $d/m;
1258
@{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' }
1259
= $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m;
1261
map { $section->{$_} ||= 0 }
1262
qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page
1263
awe_mem_alloc dict_mem_alloc);
1264
@{$section}{'writes_pending'} = List::Util::sum(
1265
@{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) });
1267
delete $section->{'fulltext'} unless $debug;
1271
# I've read the source for this.
1272
sub parse_io_section {
1273
my ( $section, $complete, $debug, $full ) = @_;
1274
return unless $section && $section->{'fulltext'};
1275
my $fulltext = $section->{'fulltext'};
1276
$section->{'threads'} = {};
1278
# Grab the I/O thread info
1279
my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm;
1280
foreach my $thread (@threads) {
1281
my ( $tid, $state, $purpose, $event_set )
1282
= $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m;
1283
if ( defined $tid ) {
1284
$section->{'threads'}->{$tid} = {
1287
purpose => $purpose,
1288
event_set => $event_set ? 1 : 0,
1293
# Grab the reads/writes/flushes info
1294
@{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' }
1295
= $fulltext =~ m/^Pending normal aio reads: $d, aio writes: $d,$/m;
1296
@{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' }
1297
= $fulltext =~ m{^ ibuf aio reads: $d, log i/o's: $d, sync i/o's: $d$}m;
1298
@{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' }
1299
= $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m;
1300
@{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' }
1301
= $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m;
1302
@{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' }
1303
= $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m;
1304
@{$section}{ 'pending_preads', 'pending_pwrites' }
1305
= $fulltext =~ m/$d pending preads, $d pending pwrites$/m;
1306
@{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0)
1307
unless defined($section->{'pending_preads'});
1309
delete $section->{'fulltext'} unless $debug;
1314
my ( $debug, $msg ) = @_;
1326
# end_of_package InnoDBParser
7
1330
use sigtrap qw(handler finish untrapped normal-signals);
11
1334
use English qw(-no_match_vars);
12
1335
use File::Basename qw(dirname);
13
1337
use Getopt::Long;
14
1338
use List::Util qw(max min maxstr sum);
16
1339
use POSIX qw(ceil);
17
1340
use Time::HiRes qw(time sleep);
18
1341
use Term::ReadKey qw(ReadMode ReadKey);
20
# Version, license and warranty information. {{{1
1343
# License and warranty information. {{{1
21
1344
# ###########################################################################
22
our $VERSION = '1.6.0';
23
our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g);
24
our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g);
26
1346
my $innotop_license = <<"LICENSE";