[Pkg-mysql-commits] r1650 - in mysql-dfsg-5.1/branches/experimental/debian: . additions/innotop

Christian Hammers ch at alioth.debian.org
Wed Jul 22 18:28:55 UTC 2009


Author: ch
Date: 2009-07-22 18:28:54 +0000 (Wed, 22 Jul 2009)
New Revision: 1650

Removed:
   mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/InnoDBParser.pm
Modified:
   mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/changelog.innotop
   mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop
   mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop.1
   mysql-dfsg-5.1/branches/experimental/debian/rules
Log:
Updated to innotop-1.7.1


Deleted: mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/InnoDBParser.pm
===================================================================
--- mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/InnoDBParser.pm	2009-07-22 17:36:02 UTC (rev 1649)
+++ mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/InnoDBParser.pm	2009-07-22 18:28:54 UTC (rev 1650)
@@ -1,1089 +0,0 @@
-use strict;
-use warnings FATAL => 'all';
-
-package InnoDBParser;
-
-# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
-# Feedback and improvements are gratefully received.
-#
-# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
-# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
-# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
-# systems, you can issue `man perlgpl' or `man perlartistic' to read these
-
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-# Place, Suite 330, Boston, MA  02111-1307  USA
-
-our $VERSION = '1.6.0';
-
-use Data::Dumper;
-$Data::Dumper::Sortkeys = 1;
-use English qw(-no_match_vars);
-use List::Util qw(max);
-
-# Some common patterns
-my $d  = qr/(\d+)/;                    # Digit
-my $f  = qr/(\d+\.\d+)/;               # Float
-my $t  = qr/(\d+ \d+)/;                # Transaction ID
-my $i  = qr/((?:\d{1,3}\.){3}\d+)/;    # IP address
-my $n  = qr/([^`\s]+)/;                # MySQL object name
-my $w  = qr/(\w+)/;                    # Words
-my $fl = qr/([\w\.\/]+) line $d/;      # Filename and line number
-my $h  = qr/((?:0x)?[0-9a-f]*)/;       # Hex
-my $s  = qr/(\d{6} .\d:\d\d:\d\d)/;    # InnoDB timestamp
-
-# If you update this variable, also update the SYNOPSIS in the pod.
-my %innodb_section_headers = (
-   "TRANSACTIONS"                          => "tx",
-   "BUFFER POOL AND MEMORY"                => "bp",
-   "SEMAPHORES"                            => "sm",
-   "LOG"                                   => "lg",
-   "ROW OPERATIONS"                        => "ro",
-   "INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib",
-   "FILE I/O"                              => "io",
-   "LATEST DETECTED DEADLOCK"              => "dl",
-   "LATEST FOREIGN KEY ERROR"              => "fk",
-);
-
-my %parser_for = (
-   tx => \&parse_tx_section,
-   bp => \&parse_bp_section,
-   sm => \&parse_sm_section,
-   lg => \&parse_lg_section,
-   ro => \&parse_ro_section,
-   ib => \&parse_ib_section,
-   io => \&parse_io_section,
-   dl => \&parse_dl_section,
-   fk => \&parse_fk_section,
-);
-
-my %fk_parser_for = (
-   Transaction => \&parse_fk_transaction_error,
-   Error       => \&parse_fk_bad_constraint_error,
-   Cannot      => \&parse_fk_cant_drop_parent_error,
-);
-
-# A thread's proc_info can be at least 98 different things I've found in the
-# source.  Fortunately, most of them begin with a gerunded verb.  These are
-# the ones that don't.
-my %is_proc_info = (
-   'After create'                 => 1,
-   'Execution of init_command'    => 1,
-   'FULLTEXT initialization'      => 1,
-   'Reopen tables'                => 1,
-   'Repair done'                  => 1,
-   'Repair with keycache'         => 1,
-   'System lock'                  => 1,
-   'Table lock'                   => 1,
-   'Thread initialized'           => 1,
-   'User lock'                    => 1,
-   'copy to tmp table'            => 1,
-   'discard_or_import_tablespace' => 1,
-   'end'                          => 1,
-   'got handler lock'             => 1,
-   'got old table'                => 1,
-   'init'                         => 1,
-   'key cache'                    => 1,
-   'locks'                        => 1,
-   'malloc'                       => 1,
-   'query end'                    => 1,
-   'rename result table'          => 1,
-   'rename'                       => 1,
-   'setup'                        => 1,
-   'statistics'                   => 1,
-   'status'                       => 1,
-   'table cache'                  => 1,
-   'update'                       => 1,
-);
-
-sub new {
-   bless {}, shift;
-}
-
-# Parse the status and return it.
-# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c
-# Pass in the text to parse, whether to be in debugging mode, which sections
-# to parse (hashref; if empty, parse all), and whether to parse full info from
-# locks and such (probably shouldn't unless you need to).
-sub parse_status_text {
-   my ( $self, $fulltext, $debug, $sections, $full ) = @_;
-
-   die "I can't parse undef" unless defined $fulltext;
-   $fulltext =~ s/[\r\n]+/\n/g;
-
-   $sections ||= {};
-   die '$sections must be a hashref' unless ref($sections) eq 'HASH';
-
-   my %innodb_data = (
-      got_all   => 0,         # Whether I was able to get the whole thing
-      ts        => '',        # Timestamp the server put on it
-      last_secs => 0,         # Num seconds the averages are over
-      sections  => {},        # Parsed values from each section
-   );
-
-   if ( $debug ) {
-      $innodb_data{'fulltext'} = $fulltext;
-   }
-
-   # Get the most basic info about the status: beginning and end, and whether
-   # I got the whole thing (if there has been a big deadlock and there are
-   # too many locks to print, the output might be truncated)
-   my ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m;
-   $innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ];
-   $innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'});
-   ( $innodb_data{'last_secs'} ) = $fulltext
-      =~ m/Per second averages calculated from the last $d seconds/;
-
-   ( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/;
-   $innodb_data{'got_all'} = $got_all || 0;
-
-   # Split it into sections.  Each section begins with
-   # -----
-   # LABEL
-   # -----
-   my %innodb_sections;
-   my @matches = $fulltext
-      =~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs;
-   while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) {
-      $innodb_sections{$name} = [ $text, $end ? 1 : 0 ];
-   }
-   # The Row Operations section is a special case, because instead of ending
-   # with the beginning of another section, it ends with the end of the file.
-   # So this section is complete if the entire file is complete.
-   $innodb_sections{'ROW OPERATIONS'}->[1] ||= $innodb_data{'got_all'};
-
-   # Just for sanity's sake, make sure I understand what to do with each
-   # section
-   eval {
-      foreach my $section ( keys %innodb_sections ) {
-         my $header = $innodb_section_headers{$section};
-         die "Unknown section $section in $fulltext\n"
-            unless $header;
-         $innodb_data{'sections'}->{ $header }
-            ->{'fulltext'} = $innodb_sections{$section}->[0];
-         $innodb_data{'sections'}->{ $header }
-            ->{'complete'} = $innodb_sections{$section}->[1];
-      }
-   };
-   if ( $EVAL_ERROR ) {
-      _debug( $debug, $EVAL_ERROR);
-   }
-
-   # ################################################################
-   # Parse the detailed data out of the sections.
-   # ################################################################
-   eval {
-      foreach my $section ( keys %parser_for ) {
-         if ( defined $innodb_data{'sections'}->{$section}
-               && (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) {
-            $parser_for{$section}->(
-                  $innodb_data{'sections'}->{$section},
-                  $innodb_data{'sections'}->{$section}->{'complete'},
-                  $debug,
-                  $full )
-               or delete $innodb_data{'sections'}->{$section};
-         }
-         else {
-            delete $innodb_data{'sections'}->{$section};
-         }
-      }
-   };
-   if ( $EVAL_ERROR ) {
-      _debug( $debug, $EVAL_ERROR);
-   }
-
-   return \%innodb_data;
-}
-
-# Parses the status text and returns it flattened out as a single hash.
-sub get_status_hash {
-   my ( $self, $fulltext, $debug, $sections, $full ) = @_;
-
-   # Parse the status text...
-   my $innodb_status
-      = $self->parse_status_text($fulltext, $debug, $sections, $full );
-
-   # Flatten the hierarchical structure into a single list by grabbing desired
-   # sections from it.
-   return
-      (map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)),
-      (map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} }
-         qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads
-            awe_mem_alloc pages_modified writes_pending_lru page_creates_sec
-            reads_pending pages_total buf_pool_hits writes_pending_single_page
-            page_writes_sec pages_read pages_written page_reads_sec
-            writes_pending_flush_list buf_pool_size add_pool_alloc
-            dict_mem_alloc pages_created buf_free complete )),
-      (map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} }
-         qw( num_lock_structs history_list_len purge_done_for transactions
-            purge_undo_for is_truncated trx_id_counter complete )),
-      (map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} }
-         qw( hash_table_size hash_searches_s non_hash_searches_s
-            bufs_in_node_heap used_cells size free_list_len seg_size inserts
-            merged_recs merges complete )),
-      (map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} }
-         qw( log_ios_done pending_chkp_writes last_chkp log_ios_s
-            log_flushed_to log_seq_no pending_log_writes complete )),
-      (map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} }
-         qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits
-            mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits
-            waits signal_count reservation_count complete )),
-      (map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} }
-         qw( queries_in_queue n_reserved_extents main_thread_state
-         main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec
-         read_views_open num_rows_upd num_rows_ins num_rows_read
-         queries_inside num_rows_del complete )),
-      (map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} }
-         qw( trigger parent_table child_index parent_index attempted_op
-         child_db timestring fk_name records col_name reason txn parent_db
-         type child_table parent_col complete )),
-      (map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} }
-         qw( pending_buffer_pool_flushes pending_pwrites pending_preads
-         pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios
-         reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s
-         threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs
-         pending_log_flushes complete )),
-      (map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} }
-         qw( timestring rolled_back txns complete ));
-
-}
-
-sub ts_to_string {
-   my $parts = shift;
-   return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts);
-}
-
-sub parse_innodb_timestamp {
-   my $text = shift;
-   my ( $y, $m, $d, $h, $i, $s )
-      = $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/;
-   die("Can't get timestamp from $text\n") unless $y;
-   $y += 2000;
-   return ( $y, $m, $d, $h, $i, $s );
-}
-
-sub parse_fk_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   my $fulltext = $section->{'fulltext'};
-
-   return 0 unless $fulltext;
-
-   my ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m;
-   $section->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
-   $section->{'timestring'} = ts_to_string($section->{'ts'});
-   $section->{'type'} = $type;
-
-   # Decide which type of FK error happened, and dispatch to the right parser.
-   if ( $type && $fk_parser_for{$type} ) {
-      $fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full );
-   }
-
-   delete $section->{'fulltext'} unless $debug;
-
-   return 1;
-}
-
-sub parse_fk_cant_drop_parent_error {
-   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
-
-   # Parse the parent/child table info out
-   @{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext
-      =~ m{Cannot $w table `(.*)/(.*)`}m;
-   @{$section}{ qw(child_db child_table) } = $fulltext
-      =~ m{because it is referenced by `(.*)/(.*)`}m;
-
-   ( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s;
-   $section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm
-      if $section->{'reason'};
-
-   # Certain data may not be present.  Make them '' if not present.
-   map { $section->{$_} ||= "" }
-      qw(child_index fk_name col_name parent_col);
-}
-
-# See dict/dict0dict.c, function dict_foreign_error_report
-# I don't care much about these.  There are lots of different messages, and
-# they come from someone trying to create a foreign key, or similar
-# statements.  They aren't indicative of some transaction trying to insert,
-# delete or update data.  Sometimes it is possible to parse out a lot of
-# information about the tables and indexes involved, but often the message
-# contains the DDL string the user entered, which is way too much for this
-# module to try to handle.
-sub parse_fk_bad_constraint_error {
-   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
-
-   # Parse the parent/child table and index info out
-   @{$section}{ qw(child_db child_table) } = $fulltext
-      =~ m{Error in foreign key constraint of table (.*)/(.*):$}m;
-   $section->{'attempted_op'} = 'DDL';
-
-   # FK name, parent info... if possible.
-   @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
-      = $fulltext
-      =~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/;
-
-   if ( !defined($section->{'fk_name'}) ) {
-      # Try to parse SQL a user might have typed in a CREATE statement or such
-      @{$section}{ qw(col_name parent_db parent_table parent_col) }
-         = $fulltext
-         =~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i;
-   }
-   $section->{'parent_db'} ||= $section->{'child_db'};
-
-   # Name of the child index (index in the same table where the FK is, see
-   # definition of dict_foreign_struct in include/dict0mem.h, where it is
-   # called foreign_index, as opposed to referenced_index which is in the
-   # parent table.  This may not be possible to find.
-   @{$section}{ qw(child_index) } = $fulltext
-      =~ m/^The index in the foreign key in table is $n$/m;
-
-   @{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms;
-   $section->{'reason'} =~ s/\s+/ /g
-      if $section->{'reason'};
-   
-   # Certain data may not be present.  Make them '' if not present.
-   map { $section->{$_} ||= "" }
-      qw(child_index fk_name col_name parent_table parent_col);
-}
-
-# see source file row/row0ins.c
-sub parse_fk_transaction_error {
-   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
-
-   # Parse the txn info out
-   my ( $txn ) = $fulltext
-      =~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s;
-   if ( $txn ) {
-      $section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full );
-   }
-
-   # Parse the parent/child table and index info out.  There are two types: an
-   # update or a delete of a parent record leaves a child orphaned
-   # (row_ins_foreign_report_err), and an insert or update of a child record has
-   # no matching parent record (row_ins_foreign_report_add_err).
-
-   @{$section}{ qw(reason child_db child_table) }
-      = $fulltext =~ m{^(Foreign key constraint fails for table `(.*)/(.*)`:)$}m;
-
-   @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
-      = $fulltext
-      =~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/;
-   $section->{'parent_db'} ||= $section->{'child_db'};
-
-   # Special case, which I don't know how to trigger, but see
-   # innobase/row/row0ins.c row_ins_check_foreign_constraint
-   if ( $fulltext =~ m/ibd file does not currently exist!/ ) {
-      my ( $attempted_op, $index, $records )
-         = $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm;
-      $section->{'child_index'} = $index;
-      $section->{'attempted_op'} = $attempted_op || '';
-      if ( $records && $full ) {
-         ( $section->{'records'} )
-            = parse_innodb_record_dump( $records, $complete, $debug );
-      }
-      @{$section}{qw(parent_db parent_table)}
-         =~ m/^But the parent table `$n`\.`$n`$/m;
-   }
-   else {
-      my ( $attempted_op, $which, $index )
-         = $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m;
-      if ( $which ) {
-         $section->{$which . '_index'} = $index;
-         $section->{'attempted_op'} = $attempted_op || '';
-
-         # Parse out the related records in the other table.
-         my ( $search_index, $records );
-         if ( $which eq 'child' ) {
-            ( $search_index, $records ) = $fulltext
-               =~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms;
-            $section->{'parent_index'} = $search_index;
-         }
-         else {
-            ( $search_index, $records ) = $fulltext
-               =~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms;
-            $section->{'child_index'} = $search_index;
-         }
-         if ( $records && $full ) {
-            $section->{'records'}
-               = parse_innodb_record_dump( $records, $complete, $debug );
-         }
-         else {
-            $section->{'records'} = '';
-         }
-      }
-   }
-
-   # Parse out the tuple trying to be updated, deleted or inserted.
-   my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m;
-   if ( $trigger ) {
-      $section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug );
-   }
-
-   # Certain data may not be present.  Make them '' if not present.
-   map { $section->{$_} ||= "" }
-      qw(child_index fk_name col_name parent_table parent_col);
-}
-
-# There are new-style and old-style record formats.  See rem/rem0rec.c
-# TODO: write some tests for this
-sub parse_innodb_record_dump {
-   my ( $dump, $complete, $debug ) = @_;
-   return undef unless $dump;
-
-   my $result = {};
-
-   if ( $dump =~ m/PHYSICAL RECORD/ ) {
-      my $style = $dump =~ m/compact format/ ? 'new' : 'old';
-      $result->{'style'} = $style;
-
-      # This is a new-style record.
-      if ( $style eq 'new' ) {
-         @{$result}{qw( heap_no type num_fields info_bits )}
-            = $dump
-            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m;
-      }
-
-      # OK, it's old-style.  Unfortunately there are variations here too.
-      elsif ( $dump =~ m/-byte offs / ) {
-         # Older-old style.
-         @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
-            = $dump
-            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m;
-            if ( $dump !~ m/-byte offs TRUE/ ) {
-               $result->{'byte_offset'} = 0;
-            }
-      }
-      else {
-         # Newer-old style.
-         @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
-            = $dump
-            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m;
-      }
-
-   }
-   else {
-      $result->{'style'} = 'tuple';
-      @{$result}{qw( type num_fields )}
-         = $dump =~ m/^(DATA TUPLE): $d fields;$/m;
-   }
-
-   # Fill in default values for things that couldn't be parsed.
-   map { $result->{$_} ||= 0 }
-      qw(heap_no num_fields byte_offset info_bits);
-   map { $result->{$_} ||= '' }
-      qw(style type );
-
-   my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm;
-   $result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ];
-
-   return $result;
-}
-
-# New/old-style applies here.  See rem/rem0rec.c
-# $text should not include the leading space or the second trailing semicolon.
-sub parse_field {
-   my ( $text, $complete, $debug ) = @_;
-
-   # Sample fields:
-   # '4: SQL NULL, size 4 '
-   # '1: len 6; hex 000000005601; asc     V ;'
-   # '6: SQL NULL'
-   # '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)'
-   my ( $id, $nullsize, $len, $hex, $asc, $truncated );
-   ( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/;
-   if ( !defined($id) ) {
-      ( $id ) = $text =~ m/^$d: SQL NULL$/;
-   }
-   if ( !defined($id) ) {
-      ( $id, $len, $hex, $asc, $truncated )
-         = $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/;
-   }
-
-   die "Could not parse this field: '$text'" unless defined $id;
-   return {
-      id    => $id,
-      len   => defined($len) ? $len : defined($nullsize) ? $nullsize : 0,
-      'hex' => defined($hex) ? $hex : '',
-      asc   => defined($asc) ? $asc : '',
-      trunc => $truncated ? 1 : 0,
-   };
-
-}
-
-sub parse_dl_section {
-   my ( $dl, $complete, $debug, $full ) = @_;
-   return unless $dl;
-   my $fulltext = $dl->{'fulltext'};
-   return 0 unless $fulltext;
-
-   my ( $ts ) = $fulltext =~ m/^$s$/m;
-   return 0 unless $ts;
-
-   $dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
-   $dl->{'timestring'} = ts_to_string($dl->{'ts'});
-   $dl->{'txns'} = {};
-
-   my @sections
-      = $fulltext
-      =~ m{
-         ^\*{3}\s([^\n]*)  # *** (1) WAITING FOR THIS...
-         (.*?)             # Followed by anything, non-greedy
-         (?=(?:^\*{3})|\z) # Followed by another three stars or EOF
-      }gmsx;
-
-
-   # Loop through each section.  There are no assumptions about how many
-   # there are, who holds and wants what locks, and who gets rolled back.
-   while ( my ($header, $body) = splice(@sections, 0, 2) ) {
-      my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/;
-      next unless $txn_id;
-      $dl->{'txns'}->{$txn_id} ||= {};
-      my $txn = $dl->{'txns'}->{$txn_id};
-
-      if ( $what eq 'TRANSACTION' ) {
-         $txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full );
-      }
-      else {
-         push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full );
-      }
-   }
-
-   @{ $dl }{ qw(rolled_back) }
-      = $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m;
-
-   # Make sure certain values aren't undef
-   map { $dl->{$_} ||= '' } qw(rolled_back);
-
-   delete $dl->{'fulltext'} unless $debug;
-   return 1;
-}
-
-sub parse_innodb_record_locks {
-   my ( $text, $complete, $debug, $full ) = @_;
-   my @result;
-
-   foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) {
-      my $hash = {};
-      @{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) }
-         = $lock
-         =~ 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;
-      ( $hash->{'special'} )
-         = $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m;
-      $hash->{'insert_intention'}
-         = $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0;
-      $hash->{'waiting'}
-         = $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0;
-
-      # Some things may not be in the text, so make sure they are not
-      # undef.
-      map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id);
-      map { $hash->{$_} ||= "" } qw(index special);
-      push @result, $hash;
-   }
-
-   return @result;
-}
-
-sub parse_tx_text {
-   my ( $txn, $complete, $debug, $full ) = @_;
-
-   my ( $txn_id, $txn_status, $active_secs, $proc_no, $os_thread_id )
-      = $txn
-      =~ m/^(?:---)?TRANSACTION $t, (\D*?)(?: $d sec)?, (?:process no $d, )?OS thread id $d/m;
-   my ( $thread_status, $thread_decl_inside )
-      = $txn
-      =~ m/OS thread id \d+(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m;
-
-   # Parsing the line that begins 'MySQL thread id' is complicated.  The only
-   # thing always in the line is the thread and query id.  See function
-   # innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc.
-   my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m;
-   my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status );
-
-   if ( $thread_line ) {
-      # These parts can always be gotten.
-      ( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, query id $d/m;
-
-      # If it's a master/slave thread, "Has (read|sent) all" may be the thread's
-      # proc_info.  In these cases, there won't be any host/ip/user info
-      ( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m;
-      if ( defined($query_status) ) {
-         $user = 'system user';
-      }
-
-      # It may be the case that the query id is the last thing in the line.
-      elsif ( $thread_line =~ m/query id \d+ / ) {
-         # The IP address is the only non-word thing left, so it's the most
-         # useful marker for where I have to start guessing.
-         ( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m;
-         if ( defined $ip ) {
-            ( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/;
-         }
-         else { # OK, there wasn't an IP address.
-            # There might not be ANYTHING except the query status.
-            ( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/;
-            if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) {
-               # The remaining tokens are, in order: hostname, user, query_status.
-               # It's basically impossible to know which is which.
-               ( $hostname, $user, $query_status ) = $thread_line
-                  =~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m;
-            }
-            else {
-               $user = 'system user';
-            }
-         }
-      }
-   }
-
-   my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries )
-      = $txn
-      =~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m;
-   my ( $lock_wait_time )
-      = $txn
-      =~ m/^------- TRX HAS BEEN WAITING $d SEC/m;
-
-   my $locks;
-   # If the transaction has locks, grab the locks.
-   if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) {
-      $locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)];
-   }
-   
-   my ( $tables_in_use, $tables_locked )
-      = $txn
-      =~ m/^mysql tables in use $d, locked $d$/m;
-   my ( $txn_doesnt_see_ge, $txn_sees_lt )
-      = $txn
-      =~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m;
-   my $has_read_view = defined($txn_doesnt_see_ge);
-   # Only a certain number of bytes of the query text are included here, at least
-   # under some circumstances.  Some versions include 300, some 600.
-   my ( $query_text )
-      = $txn
-      =~ m{
-         ^MySQL\sthread\sid\s[^\n]+\n           # This comes before the query text
-         (.*?)                                  # The query text
-         (?=                                    # Followed by any of...
-            ^Trx\sread\sview
-            |^-------\sTRX\sHAS\sBEEN\sWAITING
-            |^TABLE\sLOCK
-            |^RECORD\sLOCKS\sspace\sid
-            |^(?:---)?TRANSACTION
-            |^\*\*\*\s\(\d\)
-            |\Z
-         )
-      }xms;
-   if ( $query_text ) {
-      $query_text =~ s/\s+$//;
-   }
-   else {
-      $query_text = '';
-   }
-
-   my %stuff = (
-      active_secs        => $active_secs,
-      has_read_view      => $has_read_view,
-      heap_size          => $heap_size,
-      hostname           => $hostname,
-      ip                 => $ip,
-      lock_structs       => $lock_structs,
-      lock_wait_status   => $lock_wait_status,
-      lock_wait_time     => $lock_wait_time,
-      mysql_thread_id    => $mysql_thread_id,
-      os_thread_id       => $os_thread_id,
-      proc_no            => $proc_no,
-      query_id           => $query_id,
-      query_status       => $query_status,
-      query_text         => $query_text,
-      row_locks          => $row_locks,
-      tables_in_use      => $tables_in_use,
-      tables_locked      => $tables_locked,
-      thread_decl_inside => $thread_decl_inside,
-      thread_status      => $thread_status,
-      txn_doesnt_see_ge  => $txn_doesnt_see_ge,
-      txn_id             => $txn_id,
-      txn_sees_lt        => $txn_sees_lt,
-      txn_status         => $txn_status,
-      undo_log_entries   => $undo_log_entries,
-      user               => $user,
-   );
-   $stuff{'fulltext'} = $txn if $debug;
-   $stuff{'locks'} = $locks if $locks;
-
-   # Some things may not be in the txn text, so make sure they are not
-   # undef.
-   map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs
-         tables_in_use undo_log_entries tables_locked has_read_view
-         thread_decl_inside lock_wait_time proc_no row_locks);
-   map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge
-         txn_sees_lt query_status ip query_text lock_wait_status user);
-   $stuff{'hostname'} ||= $stuff{'ip'};
-
-   return \%stuff;
-}
-
-sub parse_tx_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return unless $section && $section->{'fulltext'};
-   my $fulltext = $section->{'fulltext'};
-   $section->{'transactions'} = [];
-
-   # Handle the individual transactions
-   my @transactions = $fulltext =~ m/(---TRANSACTION \d.*?)(?=\n---TRANSACTION|$)/gs;
-   foreach my $txn ( @transactions ) {
-      my $stuff = parse_tx_text( $txn, $complete, $debug, $full );
-      delete $stuff->{'fulltext'} unless $debug;
-      push @{$section->{'transactions'}}, $stuff;
-   }
-
-   # Handle the general info
-   @{$section}{ 'trx_id_counter' }
-      = $fulltext =~ m/^Trx id counter $t$/m;
-   @{$section}{ 'purge_done_for', 'purge_undo_for' }
-      = $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m;
-   @{$section}{ 'history_list_len' } # This isn't present in some 4.x versions
-      = $fulltext =~ m/^History list length $d$/m;
-   @{$section}{ 'num_lock_structs' }
-      = $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m;
-   @{$section}{ 'is_truncated' }
-      = $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0;
-
-   # Fill in things that might not be present
-   foreach ( qw(history_list_len) ) {
-      $section->{$_} ||= 0;
-   }
-
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-# I've read the source for this section.
-sub parse_ro_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return unless $section && $section->{'fulltext'};
-   my $fulltext = $section->{'fulltext'};
-
-   # Grab the info
-   @{$section}{ 'queries_inside', 'queries_in_queue' }
-      = $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m;
-   ( $section->{ 'read_views_open' } )
-      = $fulltext =~ m/^$d read views open inside InnoDB$/m;
-   ( $section->{ 'n_reserved_extents' } )
-      = $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m;
-   @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' }
-      = $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m;
-   @{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' }
-      = $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m;
-   @{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' }
-      = $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m;
-   $section->{'main_thread_proc_no'} ||= 0;
-
-   map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents);
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-sub parse_lg_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return unless $section;
-   my $fulltext = $section->{'fulltext'};
-
-   # Grab the info
-   ( $section->{ 'log_seq_no' } )
-      = $fulltext =~ m/Log sequence number \s*(\d.*)$/m;
-   ( $section->{ 'log_flushed_to' } )
-      = $fulltext =~ m/Log flushed up to \s*(\d.*)$/m;
-   ( $section->{ 'last_chkp' } )
-      = $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m;
-   @{$section}{ 'pending_log_writes', 'pending_chkp_writes' }
-      = $fulltext =~ m/$d pending log writes, $d pending chkp writes/;
-   @{$section}{ 'log_ios_done', 'log_ios_s' }
-      = $fulltext =~ m#$d log i/o's done, $f log i/o's/second#;
-
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-sub parse_ib_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return unless $section && $section->{'fulltext'};
-   my $fulltext = $section->{'fulltext'};
-
-   # Some servers will output ibuf information for tablespace 0, as though there
-   # might be many tablespaces with insert buffers.  (In practice I believe
-   # the source code shows there will only ever be one).  I have to parse both
-   # cases here, but I assume there will only be one.
-   @{$section}{ 'size', 'free_list_len', 'seg_size' }
-      = $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d,$/m;
-   @{$section}{ 'inserts', 'merged_recs', 'merges' }
-      = $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m;
-
-   @{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' }
-      = $fulltext =~ m/^Hash table size $d, used cells $d, node heap has $d buffer\(s\)$/m;
-   @{$section}{ 'hash_searches_s', 'non_hash_searches_s' }
-      = $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m;
-
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-sub parse_wait_array {
-   my ( $text, $complete, $debug, $full ) = @_;
-   my %result;
-
-   @result{ qw(thread waited_at_filename waited_at_line waited_secs) }
-      = $text =~ m/^--Thread $d has waited at $fl for $f seconds/m;
-
-   # Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED,
-   # there will be different text output
-   if ( $text =~ m/^Mutex at/m ) {
-      $result{'request_type'} = 'M';
-      @result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) }
-         = $text =~ m/^Mutex at $h created file $fl, lock var $d$/m;
-      @result{ qw( waiters_flag )}
-         = $text =~ m/^waiters flag $d$/m;
-   }
-   else {
-      @result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) }
-         = $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m;
-      @result{ qw( writer_thread writer_lock_mode ) }
-         = $text =~ m/^a writer \(thread id $d\) has reserved it in mode  (.*)$/m;
-      @result{ qw( num_readers waiters_flag )}
-         = $text =~ m/^number of readers $d, waiters flag $d$/m;
-      @result{ qw(last_s_file_name last_s_line ) }
-         = $text =~ m/Last time read locked in file $fl$/m;
-      @result{ qw(last_x_file_name last_x_line ) }
-         = $text =~ m/Last time write locked in file $fl$/m;
-   }
-
-   $result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1;
-   $result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0;
-
-   # Because there are two code paths, some things won't get set.
-   map { $result{$_} ||= '' }
-      qw(last_s_file_name last_x_file_name writer_lock_mode);
-   map { $result{$_} ||= 0 }
-      qw(num_readers lock_var last_s_line last_x_line writer_thread);
-
-   return \%result;
-}
-
-sub parse_sm_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return 0 unless $section && $section->{'fulltext'};
-   my $fulltext = $section->{'fulltext'};
-
-   # Grab the info
-   @{$section}{ 'reservation_count', 'signal_count' }
-      = $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m;
-   @{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' }
-      = $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m;
-   @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' }
-      = $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m;
-
-   # Look for info on waits.
-   my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms;
-   $section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ];
-   $section->{'wait_array_size'} = scalar(@waits);
-
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-# I've read the source for this section.
-sub parse_bp_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return unless $section && $section->{'fulltext'};
-   my $fulltext = $section->{'fulltext'};
-
-   # Grab the info
-   @{$section}{ 'total_mem_alloc', 'add_pool_alloc' }
-      = $fulltext =~ m/^Total memory allocated $d; in additional pool allocated $d$/m;
-   @{$section}{'dict_mem_alloc'}     = $fulltext =~ m/Dictionary memory allocated $d/;
-   @{$section}{'awe_mem_alloc'}      = $fulltext =~ m/$d MB of AWE memory/;
-   @{$section}{'buf_pool_size'}      = $fulltext =~ m/^Buffer pool size\s*$d$/m;
-   @{$section}{'buf_free'}           = $fulltext =~ m/^Free buffers\s*$d$/m;
-   @{$section}{'pages_total'}        = $fulltext =~ m/^Database pages\s*$d$/m;
-   @{$section}{'pages_modified'}     = $fulltext =~ m/^Modified db pages\s*$d$/m;
-   @{$section}{'pages_read', 'pages_created', 'pages_written'}
-      = $fulltext =~ m/^Pages read $d, created $d, written $d$/m;
-   @{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'}
-      = $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m;
-   @{$section}{'buf_pool_hits', 'buf_pool_reads'}
-      = $fulltext =~ m{Buffer pool hit rate $d / $d$}m;
-   if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) {
-      @{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0);
-      @{$section}{'buf_pool_hit_rate'} = '--';
-   }
-   else {
-      @{$section}{'buf_pool_hit_rate'}
-         = $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)$}m;
-   }
-   @{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads $d/m;
-   @{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' }
-      = $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m;
-
-   map { $section->{$_} ||= 0 }
-      qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page
-      awe_mem_alloc dict_mem_alloc);
-   @{$section}{'writes_pending'} = List::Util::sum(
-      @{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) });
-
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-# I've read the source for this.
-sub parse_io_section {
-   my ( $section, $complete, $debug, $full ) = @_;
-   return unless $section && $section->{'fulltext'};
-   my $fulltext = $section->{'fulltext'};
-   $section->{'threads'} = {};
-
-   # Grab the I/O thread info
-   my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm;
-   foreach my $thread (@threads) {
-      my ( $tid, $state, $purpose, $event_set )
-         = $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m;
-      if ( defined $tid ) {
-         $section->{'threads'}->{$tid} = {
-            thread    => $tid,
-            state     => $state,
-            purpose   => $purpose,
-            event_set => $event_set ? 1 : 0,
-         };
-      }
-   }
-
-   # Grab the reads/writes/flushes info
-   @{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' }
-      = $fulltext =~ m/^Pending normal aio reads: $d, aio writes: $d,$/m;
-   @{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' }
-      = $fulltext =~ m{^ ibuf aio reads: $d, log i/o's: $d, sync i/o's: $d$}m;
-   @{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' }
-      = $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m;
-   @{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' }
-      = $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m;
-   @{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' }
-      = $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m;
-   @{$section}{ 'pending_preads', 'pending_pwrites' }
-      = $fulltext =~ m/$d pending preads, $d pending pwrites$/m;
-   @{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0)
-      unless defined($section->{'pending_preads'});
-
-   delete $section->{'fulltext'} unless $debug;
-   return 1;
-}
-
-sub _debug {
-   my ( $debug, $msg ) = @_;
-   if ( $debug ) {
-      die $msg;
-   }
-   else {
-      warn $msg;
-   }
-   return 1;
-}
-
-1;
-
-# end_of_package
-# ############################################################################
-# Perldoc section.  I put this last as per the Dog book.
-# ############################################################################
-=pod
-
-=head1 NAME
-
-InnoDBParser - Parse InnoDB monitor text.
-
-=head1 DESCRIPTION
-
-InnoDBParser tries to parse the output of the InnoDB monitor.  One way to get
-this output is to connect to a MySQL server and issue the command SHOW ENGINE
-INNODB STATUS (omit 'ENGINE' on earlier versions of MySQL).  The goal is to
-turn text into data that something else (e.g. innotop) can use.
-
-The output comes from all over, but the place to start in the source is
-innobase/srv/srv0srv.c.
-
-=head1 SYNOPSIS
-
-   use InnoDBParser;
-   use DBI;
-
-   # Get the status text.
-   my $dbh = DBI->connect(
-      "DBI::mysql:test;host=localhost",
-      'user',
-      'password'
-   );
-   my $query = 'SHOW /*!5 ENGINE */ INNODB STATUS';
-   my $text  = $dbh->selectcol_arrayref($query)->[0];
-
-   # 1 or 0
-   my $debug = 1;
-
-   # Choose sections of the monitor text you want.  Possible values:
-   # TRANSACTIONS                          => tx
-   # BUFFER POOL AND MEMORY                => bp
-   # SEMAPHORES                            => sm
-   # LOG                                   => lg
-   # ROW OPERATIONS                        => ro
-   # INSERT BUFFER AND ADAPTIVE HASH INDEX => ib
-   # FILE I/O                              => io
-   # LATEST DETECTED DEADLOCK              => dl
-   # LATEST FOREIGN KEY ERROR              => fk
-
-   my $required_sections = {
-      tx => 1,
-   };
-
-   # Parse the status text.
-   my $parser = InnoDBParser->new;
-   $innodb_status = $parser->parse_status_text(
-      $text,
-      $debug,
-      # Omit the following parameter to get all sections.
-      $required_sections,
-   );
-
-=head1 COPYRIGHT, LICENSE AND WARRANTY
-
-This package is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
-Feedback and improvements are gratefully received.
-
-THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
-MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
-systems, you can issue `man perlgpl' or `man perlartistic' to read these
-licenses.
-
-You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place, Suite 330, Boston, MA  02111-1307  USA
-
-=head1 AUTHOR
-
-Baron Schwartz, baron at xaprb dot com.
-
-=head1 BUGS
-
-None known, but I bet there are some.  The InnoDB monitor text wasn't really
-designed to be parsable.
-
-=head1 SEE ALSO
-
-innotop - a program that can format the parsed status information for humans
-to read and enjoy.
-
-=cut

Modified: mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/changelog.innotop
===================================================================
--- mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/changelog.innotop	2009-07-22 17:36:02 UTC (rev 1649)
+++ mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/changelog.innotop	2009-07-22 18:28:54 UTC (rev 1650)
@@ -1,5 +1,44 @@
-Changelog for innotop and InnoDBParser:
+Changelog for innotop:
 
+2009-03-09: version 1.7.1
+
+   Changes:
+   * Don't display the CXN column if only one connection is active in 
+     the current view
+
+   Bugs fixed:
+   * fixed bug where trying to aggregate the time column would result 
+     in a crash if the time column had an undef value in it, which is 
+     the case when a thread is in the 'Connect' state
+   * updated innotop.spec file to reflect current version
+
+2009-02-23: version 1.7.0
+
+   Changes:
+   * supports a central config (/etc/innotop/innotop.conf)
+   * changed the default home directory config to ~/.innotop/innotop.conf
+     (away from .ini)
+   * embedded InnoDBParser.pm into innotop so it can be run with no 
+     installation
+   * no longer writes a new config file by default
+   * added --skipcentral (skip reading central config) and --write (write
+     a config if none were loaded at start-up)
+   * if no config file is loaded, connect to a MySQL database on
+     localhost using mysql_read_default_group=client
+   * embedded maatkit's DSNParser.pm and added support for --user,
+     --password, --host, --port
+   * changed default mode from T (InnoDB Transactions) to Q (Query List)
+   * in addition to connected threads, now displays running and cached
+     threads in statusbar
+   * don't load connections from a config file if any DSN information or
+     a username or password is specified on the command-line
+   
+   Bugs fixed:
+   * fixed bug preventing utilization of command-line options that
+     override default config settings if no config file was loaded
+   * fixed a bug where migrating from an old version of the config will
+     delete ~/innotop.ini, if it exists.  Now uses File::Temp::tempfile().
+
 2007-11-09: version 1.6.0
 
    * S mode crashed on non-numeric values.

Modified: mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop
===================================================================
--- mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop	2009-07-22 17:36:02 UTC (rev 1649)
+++ mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop	2009-07-22 18:28:54 UTC (rev 1650)
@@ -2,26 +2,1346 @@
 
 # vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
 
+# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
+# Feedback and improvements are gratefully received.
+#
+# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
+# systems, you can issue `man perlgpl' or `man perlartistic' to read these
+
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+# Place, Suite 330, Boston, MA  02111-1307  USA
+
 use strict;
 use warnings FATAL => 'all';
+
+our $VERSION = '1.7.1';
+
+# Find the home directory; it's different on different OSes.
+our $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
+
+# Configuration files
+our $default_home_conf = "$homepath/.innotop/innotop.conf";
+our $default_central_conf = "/etc/innotop/innotop.conf";
+our $conf_file = "";
+
+## Begin packages ##
+
+package DSNParser;
+
+use DBI;
+use Data::Dumper;
+$Data::Dumper::Indent    = 0;
+$Data::Dumper::Quotekeys = 0;
+use English qw(-no_match_vars);
+
+use constant MKDEBUG => $ENV{MKDEBUG};
+
+# Defaults are built-in, but you can add/replace items by passing them as
+# hashrefs of {key, desc, copy, dsn}.  The desc and dsn items are optional.
+# You can set properties with the prop() sub.  Don't set the 'opts' property.
+sub new {
+   my ( $class, @opts ) = @_;
+   my $self = {
+      opts => {
+         A => {
+            desc => 'Default character set',
+            dsn  => 'charset',
+            copy => 1,
+         },
+         D => {
+            desc => 'Database to use',
+            dsn  => 'database',
+            copy => 1,
+         },
+         F => {
+            desc => 'Only read default options from the given file',
+            dsn  => 'mysql_read_default_file',
+            copy => 1,
+         },
+         h => {
+            desc => 'Connect to host',
+            dsn  => 'host',
+            copy => 1,
+         },
+         p => {
+            desc => 'Password to use when connecting',
+            dsn  => 'password',
+            copy => 1,
+         },
+         P => {
+            desc => 'Port number to use for connection',
+            dsn  => 'port',
+            copy => 1,
+         },
+         S => {
+            desc => 'Socket file to use for connection',
+            dsn  => 'mysql_socket',
+            copy => 1,
+         },
+         u => {
+            desc => 'User for login if not current user',
+            dsn  => 'user',
+            copy => 1,
+         },
+      },
+   };
+   foreach my $opt ( @opts ) {
+      MKDEBUG && _d('Adding extra property ' . $opt->{key});
+      $self->{opts}->{$opt->{key}} = { desc => $opt->{desc}, copy => $opt->{copy} };
+   }
+   return bless $self, $class;
+}
+
+# Recognized properties:
+# * autokey:   which key to treat a bareword as (typically h=host).
+# * dbidriver: which DBI driver to use; assumes mysql, supports Pg.
+# * required:  which parts are required (hashref).
+# * setvars:   a list of variables to set after connecting
+sub prop {
+   my ( $self, $prop, $value ) = @_;
+   if ( @_ > 2 ) {
+      MKDEBUG && _d("Setting $prop property");
+      $self->{$prop} = $value;
+   }
+   return $self->{$prop};
+}
+
+sub parse {
+   my ( $self, $dsn, $prev, $defaults ) = @_;
+   if ( !$dsn ) {
+      MKDEBUG && _d('No DSN to parse');
+      return;
+   }
+   MKDEBUG && _d("Parsing $dsn");
+   $prev     ||= {};
+   $defaults ||= {};
+   my %given_props;
+   my %final_props;
+   my %opts = %{$self->{opts}};
+   my $prop_autokey = $self->prop('autokey');
+
+   # Parse given props
+   foreach my $dsn_part ( split(/,/, $dsn) ) {
+      if ( my ($prop_key, $prop_val) = $dsn_part =~  m/^(.)=(.*)$/ ) {
+         # Handle the typical DSN parts like h=host, P=3306, etc.
+         $given_props{$prop_key} = $prop_val;
+      }
+      elsif ( $prop_autokey ) {
+         # Handle barewords
+         MKDEBUG && _d("Interpreting $dsn_part as $prop_autokey=$dsn_part");
+         $given_props{$prop_autokey} = $dsn_part;
+      }
+      else {
+         MKDEBUG && _d("Bad DSN part: $dsn_part");
+      }
+   }
+
+   # Fill in final props from given, previous, and/or default props
+   foreach my $key ( keys %opts ) {
+      MKDEBUG && _d("Finding value for $key");
+      $final_props{$key} = $given_props{$key};
+      if (   !defined $final_props{$key}
+           && defined $prev->{$key} && $opts{$key}->{copy} )
+      {
+         $final_props{$key} = $prev->{$key};
+         MKDEBUG && _d("Copying value for $key from previous DSN");
+      }
+      if ( !defined $final_props{$key} ) {
+         $final_props{$key} = $defaults->{$key};
+         MKDEBUG && _d("Copying value for $key from defaults");
+      }
+   }
+
+   # Sanity check props
+   foreach my $key ( keys %given_props ) {
+      die "Unrecognized DSN part '$key' in '$dsn'\n"
+         unless exists $opts{$key};
+   }
+   if ( (my $required = $self->prop('required')) ) {
+      foreach my $key ( keys %$required ) {
+         die "Missing DSN part '$key' in '$dsn'\n" unless $final_props{$key};
+      }
+   }
+
+   return \%final_props;
+}
+
+sub as_string {
+   my ( $self, $dsn ) = @_;
+   return $dsn unless ref $dsn;
+   return join(',',
+      map  { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) }
+      grep { defined $dsn->{$_} && $self->{opts}->{$_} }
+      sort keys %$dsn );
+}
+
+sub usage {
+   my ( $self ) = @_;
+   my $usage
+      = "DSN syntax is key=value[,key=value...]  Allowable DSN keys:\n"
+      . "  KEY  COPY  MEANING\n"
+      . "  ===  ====  =============================================\n";
+   my %opts = %{$self->{opts}};
+   foreach my $key ( sort keys %opts ) {
+      $usage .= "  $key    "
+             .  ($opts{$key}->{copy} ? 'yes   ' : 'no    ')
+             .  ($opts{$key}->{desc} || '[No description]')
+             . "\n";
+   }
+   if ( (my $key = $self->prop('autokey')) ) {
+      $usage .= "  If the DSN is a bareword, the word is treated as the '$key' key.\n";
+   }
+   return $usage;
+}
+
+# Supports PostgreSQL via the dbidriver element of $info, but assumes MySQL by
+# default.
+sub get_cxn_params {
+   my ( $self, $info ) = @_;
+   my $dsn;
+   my %opts = %{$self->{opts}};
+   my $driver = $self->prop('dbidriver') || '';
+   if ( $driver eq 'Pg' ) {
+      $dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';'
+         . join(';', map  { "$opts{$_}->{dsn}=$info->{$_}" }
+                     grep { defined $info->{$_} }
+                     qw(h P));
+   }
+   else {
+      $dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';'
+         . join(';', map  { "$opts{$_}->{dsn}=$info->{$_}" }
+                     grep { defined $info->{$_} }
+                     qw(F h P S A))
+         . ';mysql_read_default_group=client';
+   }
+   MKDEBUG && _d($dsn);
+   return ($dsn, $info->{u}, $info->{p});
+}
+
+
+# Fills in missing info from a DSN after successfully connecting to the server.
+sub fill_in_dsn {
+   my ( $self, $dbh, $dsn ) = @_;
+   my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name');
+   my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()');
+   $user =~ s/@.*//;
+   $dsn->{h} ||= $vars->{hostname}->{Value};
+   $dsn->{S} ||= $vars->{'socket'}->{Value};
+   $dsn->{P} ||= $vars->{port}->{Value};
+   $dsn->{u} ||= $user;
+   $dsn->{D} ||= $db;
+}
+
+sub get_dbh {
+   my ( $self, $cxn_string, $user, $pass, $opts ) = @_;
+   $opts ||= {};
+   my $defaults = {
+      AutoCommit        => 0,
+      RaiseError        => 1,
+      PrintError        => 0,
+      mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/ ? 1 : 0),
+   };
+   @{$defaults}{ keys %$opts } = values %$opts;
+   my $dbh;
+   my $tries = 2;
+   while ( !$dbh && $tries-- ) {
+      eval {
+         MKDEBUG && _d($cxn_string, ' ', $user, ' ', $pass, ' {',
+            join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults ), '}');
+         $dbh = DBI->connect($cxn_string, $user, $pass, $defaults);
+         # Immediately set character set and binmode on STDOUT.
+         if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) {
+            my $sql = "/*!40101 SET NAMES $charset*/";
+            MKDEBUG && _d("$dbh: $sql");
+            $dbh->do($sql);
+            MKDEBUG && _d('Enabling charset for STDOUT');
+            if ( $charset eq 'utf8' ) {
+               binmode(STDOUT, ':utf8')
+                  or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR";
+            }
+            else {
+               binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR";
+            }
+         }
+      };
+      if ( !$dbh && $EVAL_ERROR ) {
+         MKDEBUG && _d($EVAL_ERROR);
+         if ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) {
+            MKDEBUG && _d("Going to try again without utf8 support");
+            delete $defaults->{mysql_enable_utf8};
+         }
+         if ( !$tries ) {
+            die $EVAL_ERROR;
+         }
+      }
+   }
+   # If setvars exists and it's MySQL connection, set them
+   my $setvars = $self->prop('setvars');
+   if ( $cxn_string =~ m/mysql/i && $setvars ) {
+      my $sql = "SET $setvars";
+      MKDEBUG && _d("$dbh: $sql");
+      eval {
+         $dbh->do($sql);
+      };
+      if ( $EVAL_ERROR ) {
+         MKDEBUG && _d($EVAL_ERROR);
+      }
+   }
+   MKDEBUG && _d('DBH info: ',
+      $dbh,
+      Dumper($dbh->selectrow_hashref(
+         'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')),
+      ' Connection info: ', ($dbh->{mysql_hostinfo} || 'undef'),
+      ' Character set info: ',
+      Dumper($dbh->selectall_arrayref(
+         'SHOW VARIABLES LIKE "character_set%"', { Slice => {}})),
+      ' $DBD::mysql::VERSION: ', $DBD::mysql::VERSION,
+      ' $DBI::VERSION: ', $DBI::VERSION,
+   );
+   return $dbh;
+}
+
+# Tries to figure out a hostname for the connection.
+sub get_hostname {
+   my ( $self, $dbh ) = @_;
+   if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) {
+      return $host;
+   }
+   my ( $hostname, $one ) = $dbh->selectrow_array(
+      'SELECT /*!50038 @@hostname, */ 1');
+   return $hostname;
+}
+
+# Disconnects a database handle, but complains verbosely if there are any active
+# children.  These are usually $sth handles that haven't been finish()ed.
+sub disconnect {
+   my ( $self, $dbh ) = @_;
+   MKDEBUG && $self->print_active_handles($dbh);
+   $dbh->disconnect;
+}
+
+sub print_active_handles {
+   my ( $self, $thing, $level ) = @_;
+   $level ||= 0;
+   printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level,
+      $thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : ''))
+      or die "Cannot print: $OS_ERROR";
+   foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) {
+      $self->print_active_handles( $handle, $level + 1 );
+   }
+}
+
+sub _d {
+   my ($package, undef, $line) = caller 0;
+   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
+        map { defined $_ ? $_ : 'undef' }
+        @_;
+   # Use $$ instead of $PID in case the package
+   # does not use English.
+   print "# $package:$line $$ ", @_, "\n";
+}
+
+1;
+
+package InnoDBParser;
+
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+use English qw(-no_match_vars);
+use List::Util qw(max);
+
+# Some common patterns
+my $d  = qr/(\d+)/;                    # Digit
+my $f  = qr/(\d+\.\d+)/;               # Float
+my $t  = qr/(\d+ \d+)/;                # Transaction ID
+my $i  = qr/((?:\d{1,3}\.){3}\d+)/;    # IP address
+my $n  = qr/([^`\s]+)/;                # MySQL object name
+my $w  = qr/(\w+)/;                    # Words
+my $fl = qr/([\w\.\/]+) line $d/;      # Filename and line number
+my $h  = qr/((?:0x)?[0-9a-f]*)/;       # Hex
+my $s  = qr/(\d{6} .\d:\d\d:\d\d)/;    # InnoDB timestamp
+
+# If you update this variable, also update the SYNOPSIS in the pod.
+my %innodb_section_headers = (
+   "TRANSACTIONS"                          => "tx",
+   "BUFFER POOL AND MEMORY"                => "bp",
+   "SEMAPHORES"                            => "sm",
+   "LOG"                                   => "lg",
+   "ROW OPERATIONS"                        => "ro",
+   "INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib",
+   "FILE I/O"                              => "io",
+   "LATEST DETECTED DEADLOCK"              => "dl",
+   "LATEST FOREIGN KEY ERROR"              => "fk",
+);
+
+my %parser_for = (
+   tx => \&parse_tx_section,
+   bp => \&parse_bp_section,
+   sm => \&parse_sm_section,
+   lg => \&parse_lg_section,
+   ro => \&parse_ro_section,
+   ib => \&parse_ib_section,
+   io => \&parse_io_section,
+   dl => \&parse_dl_section,
+   fk => \&parse_fk_section,
+);
+
+my %fk_parser_for = (
+   Transaction => \&parse_fk_transaction_error,
+   Error       => \&parse_fk_bad_constraint_error,
+   Cannot      => \&parse_fk_cant_drop_parent_error,
+);
+
+# A thread's proc_info can be at least 98 different things I've found in the
+# source.  Fortunately, most of them begin with a gerunded verb.  These are
+# the ones that don't.
+my %is_proc_info = (
+   'After create'                 => 1,
+   'Execution of init_command'    => 1,
+   'FULLTEXT initialization'      => 1,
+   'Reopen tables'                => 1,
+   'Repair done'                  => 1,
+   'Repair with keycache'         => 1,
+   'System lock'                  => 1,
+   'Table lock'                   => 1,
+   'Thread initialized'           => 1,
+   'User lock'                    => 1,
+   'copy to tmp table'            => 1,
+   'discard_or_import_tablespace' => 1,
+   'end'                          => 1,
+   'got handler lock'             => 1,
+   'got old table'                => 1,
+   'init'                         => 1,
+   'key cache'                    => 1,
+   'locks'                        => 1,
+   'malloc'                       => 1,
+   'query end'                    => 1,
+   'rename result table'          => 1,
+   'rename'                       => 1,
+   'setup'                        => 1,
+   'statistics'                   => 1,
+   'status'                       => 1,
+   'table cache'                  => 1,
+   'update'                       => 1,
+);
+
+sub new {
+   bless {}, shift;
+}
+
+# Parse the status and return it.
+# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c
+# Pass in the text to parse, whether to be in debugging mode, which sections
+# to parse (hashref; if empty, parse all), and whether to parse full info from
+# locks and such (probably shouldn't unless you need to).
+sub parse_status_text {
+   my ( $self, $fulltext, $debug, $sections, $full ) = @_;
+
+   die "I can't parse undef" unless defined $fulltext;
+   $fulltext =~ s/[\r\n]+/\n/g;
+
+   $sections ||= {};
+   die '$sections must be a hashref' unless ref($sections) eq 'HASH';
+
+   my %innodb_data = (
+      got_all   => 0,         # Whether I was able to get the whole thing
+      ts        => '',        # Timestamp the server put on it
+      last_secs => 0,         # Num seconds the averages are over
+      sections  => {},        # Parsed values from each section
+   );
+
+   if ( $debug ) {
+      $innodb_data{'fulltext'} = $fulltext;
+   }
+
+   # Get the most basic info about the status: beginning and end, and whether
+   # I got the whole thing (if there has been a big deadlock and there are
+   # too many locks to print, the output might be truncated)
+   my ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m;
+   $innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ];
+   $innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'});
+   ( $innodb_data{'last_secs'} ) = $fulltext
+      =~ m/Per second averages calculated from the last $d seconds/;
+
+   ( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/;
+   $innodb_data{'got_all'} = $got_all || 0;
+
+   # Split it into sections.  Each section begins with
+   # -----
+   # LABEL
+   # -----
+   my %innodb_sections;
+   my @matches = $fulltext
+      =~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs;
+   while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) {
+      $innodb_sections{$name} = [ $text, $end ? 1 : 0 ];
+   }
+   # The Row Operations section is a special case, because instead of ending
+   # with the beginning of another section, it ends with the end of the file.
+   # So this section is complete if the entire file is complete.
+   $innodb_sections{'ROW OPERATIONS'}->[1] ||= $innodb_data{'got_all'};
+
+   # Just for sanity's sake, make sure I understand what to do with each
+   # section
+   eval {
+      foreach my $section ( keys %innodb_sections ) {
+         my $header = $innodb_section_headers{$section};
+         die "Unknown section $section in $fulltext\n"
+            unless $header;
+         $innodb_data{'sections'}->{ $header }
+            ->{'fulltext'} = $innodb_sections{$section}->[0];
+         $innodb_data{'sections'}->{ $header }
+            ->{'complete'} = $innodb_sections{$section}->[1];
+      }
+   };
+   if ( $EVAL_ERROR ) {
+      _debug( $debug, $EVAL_ERROR);
+   }
+
+   # ################################################################
+   # Parse the detailed data out of the sections.
+   # ################################################################
+   eval {
+      foreach my $section ( keys %parser_for ) {
+         if ( defined $innodb_data{'sections'}->{$section}
+               && (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) {
+            $parser_for{$section}->(
+                  $innodb_data{'sections'}->{$section},
+                  $innodb_data{'sections'}->{$section}->{'complete'},
+                  $debug,
+                  $full )
+               or delete $innodb_data{'sections'}->{$section};
+         }
+         else {
+            delete $innodb_data{'sections'}->{$section};
+         }
+      }
+   };
+   if ( $EVAL_ERROR ) {
+      _debug( $debug, $EVAL_ERROR);
+   }
+
+   return \%innodb_data;
+}
+
+# Parses the status text and returns it flattened out as a single hash.
+sub get_status_hash {
+   my ( $self, $fulltext, $debug, $sections, $full ) = @_;
+
+   # Parse the status text...
+   my $innodb_status
+      = $self->parse_status_text($fulltext, $debug, $sections, $full );
+
+   # Flatten the hierarchical structure into a single list by grabbing desired
+   # sections from it.
+   return
+      (map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)),
+      (map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} }
+         qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads
+            awe_mem_alloc pages_modified writes_pending_lru page_creates_sec
+            reads_pending pages_total buf_pool_hits writes_pending_single_page
+            page_writes_sec pages_read pages_written page_reads_sec
+            writes_pending_flush_list buf_pool_size add_pool_alloc
+            dict_mem_alloc pages_created buf_free complete )),
+      (map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} }
+         qw( num_lock_structs history_list_len purge_done_for transactions
+            purge_undo_for is_truncated trx_id_counter complete )),
+      (map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} }
+         qw( hash_table_size hash_searches_s non_hash_searches_s
+            bufs_in_node_heap used_cells size free_list_len seg_size inserts
+            merged_recs merges complete )),
+      (map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} }
+         qw( log_ios_done pending_chkp_writes last_chkp log_ios_s
+            log_flushed_to log_seq_no pending_log_writes complete )),
+      (map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} }
+         qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits
+            mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits
+            waits signal_count reservation_count complete )),
+      (map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} }
+         qw( queries_in_queue n_reserved_extents main_thread_state
+         main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec
+         read_views_open num_rows_upd num_rows_ins num_rows_read
+         queries_inside num_rows_del complete )),
+      (map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} }
+         qw( trigger parent_table child_index parent_index attempted_op
+         child_db timestring fk_name records col_name reason txn parent_db
+         type child_table parent_col complete )),
+      (map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} }
+         qw( pending_buffer_pool_flushes pending_pwrites pending_preads
+         pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios
+         reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s
+         threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs
+         pending_log_flushes complete )),
+      (map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} }
+         qw( timestring rolled_back txns complete ));
+
+}
+
+sub ts_to_string {
+   my $parts = shift;
+   return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts);
+}
+
+sub parse_innodb_timestamp {
+   my $text = shift;
+   my ( $y, $m, $d, $h, $i, $s )
+      = $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/;
+   die("Can't get timestamp from $text\n") unless $y;
+   $y += 2000;
+   return ( $y, $m, $d, $h, $i, $s );
+}
+
+sub parse_fk_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   my $fulltext = $section->{'fulltext'};
+
+   return 0 unless $fulltext;
+
+   my ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m;
+   $section->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
+   $section->{'timestring'} = ts_to_string($section->{'ts'});
+   $section->{'type'} = $type;
+
+   # Decide which type of FK error happened, and dispatch to the right parser.
+   if ( $type && $fk_parser_for{$type} ) {
+      $fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full );
+   }
+
+   delete $section->{'fulltext'} unless $debug;
+
+   return 1;
+}
+
+sub parse_fk_cant_drop_parent_error {
+   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
+
+   # Parse the parent/child table info out
+   @{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext
+      =~ m{Cannot $w table `(.*)/(.*)`}m;
+   @{$section}{ qw(child_db child_table) } = $fulltext
+      =~ m{because it is referenced by `(.*)/(.*)`}m;
+
+   ( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s;
+   $section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm
+      if $section->{'reason'};
+
+   # Certain data may not be present.  Make them '' if not present.
+   map { $section->{$_} ||= "" }
+      qw(child_index fk_name col_name parent_col);
+}
+
+# See dict/dict0dict.c, function dict_foreign_error_report
+# I don't care much about these.  There are lots of different messages, and
+# they come from someone trying to create a foreign key, or similar
+# statements.  They aren't indicative of some transaction trying to insert,
+# delete or update data.  Sometimes it is possible to parse out a lot of
+# information about the tables and indexes involved, but often the message
+# contains the DDL string the user entered, which is way too much for this
+# module to try to handle.
+sub parse_fk_bad_constraint_error {
+   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
+
+   # Parse the parent/child table and index info out
+   @{$section}{ qw(child_db child_table) } = $fulltext
+      =~ m{Error in foreign key constraint of table (.*)/(.*):$}m;
+   $section->{'attempted_op'} = 'DDL';
+
+   # FK name, parent info... if possible.
+   @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
+      = $fulltext
+      =~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/;
+
+   if ( !defined($section->{'fk_name'}) ) {
+      # Try to parse SQL a user might have typed in a CREATE statement or such
+      @{$section}{ qw(col_name parent_db parent_table parent_col) }
+         = $fulltext
+         =~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i;
+   }
+   $section->{'parent_db'} ||= $section->{'child_db'};
+
+   # Name of the child index (index in the same table where the FK is, see
+   # definition of dict_foreign_struct in include/dict0mem.h, where it is
+   # called foreign_index, as opposed to referenced_index which is in the
+   # parent table.  This may not be possible to find.
+   @{$section}{ qw(child_index) } = $fulltext
+      =~ m/^The index in the foreign key in table is $n$/m;
+
+   @{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms;
+   $section->{'reason'} =~ s/\s+/ /g
+      if $section->{'reason'};
+   
+   # Certain data may not be present.  Make them '' if not present.
+   map { $section->{$_} ||= "" }
+      qw(child_index fk_name col_name parent_table parent_col);
+}
+
+# see source file row/row0ins.c
+sub parse_fk_transaction_error {
+   my ( $section, $complete, $debug, $fulltext, $full ) = @_;
+
+   # Parse the txn info out
+   my ( $txn ) = $fulltext
+      =~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s;
+   if ( $txn ) {
+      $section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full );
+   }
+
+   # Parse the parent/child table and index info out.  There are two types: an
+   # update or a delete of a parent record leaves a child orphaned
+   # (row_ins_foreign_report_err), and an insert or update of a child record has
+   # no matching parent record (row_ins_foreign_report_add_err).
+
+   @{$section}{ qw(reason child_db child_table) }
+      = $fulltext =~ m{^(Foreign key constraint fails for table `(.*)/(.*)`:)$}m;
+
+   @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
+      = $fulltext
+      =~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/;
+   $section->{'parent_db'} ||= $section->{'child_db'};
+
+   # Special case, which I don't know how to trigger, but see
+   # innobase/row/row0ins.c row_ins_check_foreign_constraint
+   if ( $fulltext =~ m/ibd file does not currently exist!/ ) {
+      my ( $attempted_op, $index, $records )
+         = $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm;
+      $section->{'child_index'} = $index;
+      $section->{'attempted_op'} = $attempted_op || '';
+      if ( $records && $full ) {
+         ( $section->{'records'} )
+            = parse_innodb_record_dump( $records, $complete, $debug );
+      }
+      @{$section}{qw(parent_db parent_table)}
+         =~ m/^But the parent table `$n`\.`$n`$/m;
+   }
+   else {
+      my ( $attempted_op, $which, $index )
+         = $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m;
+      if ( $which ) {
+         $section->{$which . '_index'} = $index;
+         $section->{'attempted_op'} = $attempted_op || '';
+
+         # Parse out the related records in the other table.
+         my ( $search_index, $records );
+         if ( $which eq 'child' ) {
+            ( $search_index, $records ) = $fulltext
+               =~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms;
+            $section->{'parent_index'} = $search_index;
+         }
+         else {
+            ( $search_index, $records ) = $fulltext
+               =~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms;
+            $section->{'child_index'} = $search_index;
+         }
+         if ( $records && $full ) {
+            $section->{'records'}
+               = parse_innodb_record_dump( $records, $complete, $debug );
+         }
+         else {
+            $section->{'records'} = '';
+         }
+      }
+   }
+
+   # Parse out the tuple trying to be updated, deleted or inserted.
+   my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m;
+   if ( $trigger ) {
+      $section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug );
+   }
+
+   # Certain data may not be present.  Make them '' if not present.
+   map { $section->{$_} ||= "" }
+      qw(child_index fk_name col_name parent_table parent_col);
+}
+
+# There are new-style and old-style record formats.  See rem/rem0rec.c
+# TODO: write some tests for this
+sub parse_innodb_record_dump {
+   my ( $dump, $complete, $debug ) = @_;
+   return undef unless $dump;
+
+   my $result = {};
+
+   if ( $dump =~ m/PHYSICAL RECORD/ ) {
+      my $style = $dump =~ m/compact format/ ? 'new' : 'old';
+      $result->{'style'} = $style;
+
+      # This is a new-style record.
+      if ( $style eq 'new' ) {
+         @{$result}{qw( heap_no type num_fields info_bits )}
+            = $dump
+            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m;
+      }
+
+      # OK, it's old-style.  Unfortunately there are variations here too.
+      elsif ( $dump =~ m/-byte offs / ) {
+         # Older-old style.
+         @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
+            = $dump
+            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m;
+            if ( $dump !~ m/-byte offs TRUE/ ) {
+               $result->{'byte_offset'} = 0;
+            }
+      }
+      else {
+         # Newer-old style.
+         @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
+            = $dump
+            =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m;
+      }
+
+   }
+   else {
+      $result->{'style'} = 'tuple';
+      @{$result}{qw( type num_fields )}
+         = $dump =~ m/^(DATA TUPLE): $d fields;$/m;
+   }
+
+   # Fill in default values for things that couldn't be parsed.
+   map { $result->{$_} ||= 0 }
+      qw(heap_no num_fields byte_offset info_bits);
+   map { $result->{$_} ||= '' }
+      qw(style type );
+
+   my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm;
+   $result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ];
+
+   return $result;
+}
+
+# New/old-style applies here.  See rem/rem0rec.c
+# $text should not include the leading space or the second trailing semicolon.
+sub parse_field {
+   my ( $text, $complete, $debug ) = @_;
+
+   # Sample fields:
+   # '4: SQL NULL, size 4 '
+   # '1: len 6; hex 000000005601; asc     V ;'
+   # '6: SQL NULL'
+   # '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)'
+   my ( $id, $nullsize, $len, $hex, $asc, $truncated );
+   ( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/;
+   if ( !defined($id) ) {
+      ( $id ) = $text =~ m/^$d: SQL NULL$/;
+   }
+   if ( !defined($id) ) {
+      ( $id, $len, $hex, $asc, $truncated )
+         = $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/;
+   }
+
+   die "Could not parse this field: '$text'" unless defined $id;
+   return {
+      id    => $id,
+      len   => defined($len) ? $len : defined($nullsize) ? $nullsize : 0,
+      'hex' => defined($hex) ? $hex : '',
+      asc   => defined($asc) ? $asc : '',
+      trunc => $truncated ? 1 : 0,
+   };
+
+}
+
+sub parse_dl_section {
+   my ( $dl, $complete, $debug, $full ) = @_;
+   return unless $dl;
+   my $fulltext = $dl->{'fulltext'};
+   return 0 unless $fulltext;
+
+   my ( $ts ) = $fulltext =~ m/^$s$/m;
+   return 0 unless $ts;
+
+   $dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
+   $dl->{'timestring'} = ts_to_string($dl->{'ts'});
+   $dl->{'txns'} = {};
+
+   my @sections
+      = $fulltext
+      =~ m{
+         ^\*{3}\s([^\n]*)  # *** (1) WAITING FOR THIS...
+         (.*?)             # Followed by anything, non-greedy
+         (?=(?:^\*{3})|\z) # Followed by another three stars or EOF
+      }gmsx;
+
+
+   # Loop through each section.  There are no assumptions about how many
+   # there are, who holds and wants what locks, and who gets rolled back.
+   while ( my ($header, $body) = splice(@sections, 0, 2) ) {
+      my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/;
+      next unless $txn_id;
+      $dl->{'txns'}->{$txn_id} ||= {};
+      my $txn = $dl->{'txns'}->{$txn_id};
+
+      if ( $what eq 'TRANSACTION' ) {
+         $txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full );
+      }
+      else {
+         push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full );
+      }
+   }
+
+   @{ $dl }{ qw(rolled_back) }
+      = $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m;
+
+   # Make sure certain values aren't undef
+   map { $dl->{$_} ||= '' } qw(rolled_back);
+
+   delete $dl->{'fulltext'} unless $debug;
+   return 1;
+}
+
+sub parse_innodb_record_locks {
+   my ( $text, $complete, $debug, $full ) = @_;
+   my @result;
+
+   foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) {
+      my $hash = {};
+      @{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) }
+         = $lock
+         =~ 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;
+      ( $hash->{'special'} )
+         = $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m;
+      $hash->{'insert_intention'}
+         = $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0;
+      $hash->{'waiting'}
+         = $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0;
+
+      # Some things may not be in the text, so make sure they are not
+      # undef.
+      map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id);
+      map { $hash->{$_} ||= "" } qw(index special);
+      push @result, $hash;
+   }
+
+   return @result;
+}
+
+sub parse_tx_text {
+   my ( $txn, $complete, $debug, $full ) = @_;
+
+   my ( $txn_id, $txn_status, $active_secs, $proc_no, $os_thread_id )
+      = $txn
+      =~ m/^(?:---)?TRANSACTION $t, (\D*?)(?: $d sec)?, (?:process no $d, )?OS thread id $d/m;
+   my ( $thread_status, $thread_decl_inside )
+      = $txn
+      =~ m/OS thread id \d+(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m;
+
+   # Parsing the line that begins 'MySQL thread id' is complicated.  The only
+   # thing always in the line is the thread and query id.  See function
+   # innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc.
+   my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m;
+   my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status );
+
+   if ( $thread_line ) {
+      # These parts can always be gotten.
+      ( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, query id $d/m;
+
+      # If it's a master/slave thread, "Has (read|sent) all" may be the thread's
+      # proc_info.  In these cases, there won't be any host/ip/user info
+      ( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m;
+      if ( defined($query_status) ) {
+         $user = 'system user';
+      }
+
+      # It may be the case that the query id is the last thing in the line.
+      elsif ( $thread_line =~ m/query id \d+ / ) {
+         # The IP address is the only non-word thing left, so it's the most
+         # useful marker for where I have to start guessing.
+         ( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m;
+         if ( defined $ip ) {
+            ( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/;
+         }
+         else { # OK, there wasn't an IP address.
+            # There might not be ANYTHING except the query status.
+            ( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/;
+            if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) {
+               # The remaining tokens are, in order: hostname, user, query_status.
+               # It's basically impossible to know which is which.
+               ( $hostname, $user, $query_status ) = $thread_line
+                  =~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m;
+            }
+            else {
+               $user = 'system user';
+            }
+         }
+      }
+   }
+
+   my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries )
+      = $txn
+      =~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m;
+   my ( $lock_wait_time )
+      = $txn
+      =~ m/^------- TRX HAS BEEN WAITING $d SEC/m;
+
+   my $locks;
+   # If the transaction has locks, grab the locks.
+   if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) {
+      $locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)];
+   }
+   
+   my ( $tables_in_use, $tables_locked )
+      = $txn
+      =~ m/^mysql tables in use $d, locked $d$/m;
+   my ( $txn_doesnt_see_ge, $txn_sees_lt )
+      = $txn
+      =~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m;
+   my $has_read_view = defined($txn_doesnt_see_ge);
+   # Only a certain number of bytes of the query text are included here, at least
+   # under some circumstances.  Some versions include 300, some 600.
+   my ( $query_text )
+      = $txn
+      =~ m{
+         ^MySQL\sthread\sid\s[^\n]+\n           # This comes before the query text
+         (.*?)                                  # The query text
+         (?=                                    # Followed by any of...
+            ^Trx\sread\sview
+            |^-------\sTRX\sHAS\sBEEN\sWAITING
+            |^TABLE\sLOCK
+            |^RECORD\sLOCKS\sspace\sid
+            |^(?:---)?TRANSACTION
+            |^\*\*\*\s\(\d\)
+            |\Z
+         )
+      }xms;
+   if ( $query_text ) {
+      $query_text =~ s/\s+$//;
+   }
+   else {
+      $query_text = '';
+   }
+
+   my %stuff = (
+      active_secs        => $active_secs,
+      has_read_view      => $has_read_view,
+      heap_size          => $heap_size,
+      hostname           => $hostname,
+      ip                 => $ip,
+      lock_structs       => $lock_structs,
+      lock_wait_status   => $lock_wait_status,
+      lock_wait_time     => $lock_wait_time,
+      mysql_thread_id    => $mysql_thread_id,
+      os_thread_id       => $os_thread_id,
+      proc_no            => $proc_no,
+      query_id           => $query_id,
+      query_status       => $query_status,
+      query_text         => $query_text,
+      row_locks          => $row_locks,
+      tables_in_use      => $tables_in_use,
+      tables_locked      => $tables_locked,
+      thread_decl_inside => $thread_decl_inside,
+      thread_status      => $thread_status,
+      txn_doesnt_see_ge  => $txn_doesnt_see_ge,
+      txn_id             => $txn_id,
+      txn_sees_lt        => $txn_sees_lt,
+      txn_status         => $txn_status,
+      undo_log_entries   => $undo_log_entries,
+      user               => $user,
+   );
+   $stuff{'fulltext'} = $txn if $debug;
+   $stuff{'locks'} = $locks if $locks;
+
+   # Some things may not be in the txn text, so make sure they are not
+   # undef.
+   map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs
+         tables_in_use undo_log_entries tables_locked has_read_view
+         thread_decl_inside lock_wait_time proc_no row_locks);
+   map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge
+         txn_sees_lt query_status ip query_text lock_wait_status user);
+   $stuff{'hostname'} ||= $stuff{'ip'};
+
+   return \%stuff;
+}
+
+sub parse_tx_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return unless $section && $section->{'fulltext'};
+   my $fulltext = $section->{'fulltext'};
+   $section->{'transactions'} = [];
+
+   # Handle the individual transactions
+   my @transactions = $fulltext =~ m/(---TRANSACTION \d.*?)(?=\n---TRANSACTION|$)/gs;
+   foreach my $txn ( @transactions ) {
+      my $stuff = parse_tx_text( $txn, $complete, $debug, $full );
+      delete $stuff->{'fulltext'} unless $debug;
+      push @{$section->{'transactions'}}, $stuff;
+   }
+
+   # Handle the general info
+   @{$section}{ 'trx_id_counter' }
+      = $fulltext =~ m/^Trx id counter $t$/m;
+   @{$section}{ 'purge_done_for', 'purge_undo_for' }
+      = $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m;
+   @{$section}{ 'history_list_len' } # This isn't present in some 4.x versions
+      = $fulltext =~ m/^History list length $d$/m;
+   @{$section}{ 'num_lock_structs' }
+      = $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m;
+   @{$section}{ 'is_truncated' }
+      = $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0;
+
+   # Fill in things that might not be present
+   foreach ( qw(history_list_len) ) {
+      $section->{$_} ||= 0;
+   }
+
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+# I've read the source for this section.
+sub parse_ro_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return unless $section && $section->{'fulltext'};
+   my $fulltext = $section->{'fulltext'};
+
+   # Grab the info
+   @{$section}{ 'queries_inside', 'queries_in_queue' }
+      = $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m;
+   ( $section->{ 'read_views_open' } )
+      = $fulltext =~ m/^$d read views open inside InnoDB$/m;
+   ( $section->{ 'n_reserved_extents' } )
+      = $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m;
+   @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' }
+      = $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m;
+   @{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' }
+      = $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m;
+   @{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' }
+      = $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m;
+   $section->{'main_thread_proc_no'} ||= 0;
+
+   map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents);
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+sub parse_lg_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return unless $section;
+   my $fulltext = $section->{'fulltext'};
+
+   # Grab the info
+   ( $section->{ 'log_seq_no' } )
+      = $fulltext =~ m/Log sequence number \s*(\d.*)$/m;
+   ( $section->{ 'log_flushed_to' } )
+      = $fulltext =~ m/Log flushed up to \s*(\d.*)$/m;
+   ( $section->{ 'last_chkp' } )
+      = $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m;
+   @{$section}{ 'pending_log_writes', 'pending_chkp_writes' }
+      = $fulltext =~ m/$d pending log writes, $d pending chkp writes/;
+   @{$section}{ 'log_ios_done', 'log_ios_s' }
+      = $fulltext =~ m#$d log i/o's done, $f log i/o's/second#;
+
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+sub parse_ib_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return unless $section && $section->{'fulltext'};
+   my $fulltext = $section->{'fulltext'};
+
+   # Some servers will output ibuf information for tablespace 0, as though there
+   # might be many tablespaces with insert buffers.  (In practice I believe
+   # the source code shows there will only ever be one).  I have to parse both
+   # cases here, but I assume there will only be one.
+   @{$section}{ 'size', 'free_list_len', 'seg_size' }
+      = $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d,$/m;
+   @{$section}{ 'inserts', 'merged_recs', 'merges' }
+      = $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m;
+
+   @{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' }
+      = $fulltext =~ m/^Hash table size $d, used cells $d, node heap has $d buffer\(s\)$/m;
+   @{$section}{ 'hash_searches_s', 'non_hash_searches_s' }
+      = $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m;
+
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+sub parse_wait_array {
+   my ( $text, $complete, $debug, $full ) = @_;
+   my %result;
+
+   @result{ qw(thread waited_at_filename waited_at_line waited_secs) }
+      = $text =~ m/^--Thread $d has waited at $fl for $f seconds/m;
+
+   # Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED,
+   # there will be different text output
+   if ( $text =~ m/^Mutex at/m ) {
+      $result{'request_type'} = 'M';
+      @result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) }
+         = $text =~ m/^Mutex at $h created file $fl, lock var $d$/m;
+      @result{ qw( waiters_flag )}
+         = $text =~ m/^waiters flag $d$/m;
+   }
+   else {
+      @result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) }
+         = $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m;
+      @result{ qw( writer_thread writer_lock_mode ) }
+         = $text =~ m/^a writer \(thread id $d\) has reserved it in mode  (.*)$/m;
+      @result{ qw( num_readers waiters_flag )}
+         = $text =~ m/^number of readers $d, waiters flag $d$/m;
+      @result{ qw(last_s_file_name last_s_line ) }
+         = $text =~ m/Last time read locked in file $fl$/m;
+      @result{ qw(last_x_file_name last_x_line ) }
+         = $text =~ m/Last time write locked in file $fl$/m;
+   }
+
+   $result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1;
+   $result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0;
+
+   # Because there are two code paths, some things won't get set.
+   map { $result{$_} ||= '' }
+      qw(last_s_file_name last_x_file_name writer_lock_mode);
+   map { $result{$_} ||= 0 }
+      qw(num_readers lock_var last_s_line last_x_line writer_thread);
+
+   return \%result;
+}
+
+sub parse_sm_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return 0 unless $section && $section->{'fulltext'};
+   my $fulltext = $section->{'fulltext'};
+
+   # Grab the info
+   @{$section}{ 'reservation_count', 'signal_count' }
+      = $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m;
+   @{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' }
+      = $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m;
+   @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' }
+      = $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m;
+
+   # Look for info on waits.
+   my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms;
+   $section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ];
+   $section->{'wait_array_size'} = scalar(@waits);
+
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+# I've read the source for this section.
+sub parse_bp_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return unless $section && $section->{'fulltext'};
+   my $fulltext = $section->{'fulltext'};
+
+   # Grab the info
+   @{$section}{ 'total_mem_alloc', 'add_pool_alloc' }
+      = $fulltext =~ m/^Total memory allocated $d; in additional pool allocated $d$/m;
+   @{$section}{'dict_mem_alloc'}     = $fulltext =~ m/Dictionary memory allocated $d/;
+   @{$section}{'awe_mem_alloc'}      = $fulltext =~ m/$d MB of AWE memory/;
+   @{$section}{'buf_pool_size'}      = $fulltext =~ m/^Buffer pool size\s*$d$/m;
+   @{$section}{'buf_free'}           = $fulltext =~ m/^Free buffers\s*$d$/m;
+   @{$section}{'pages_total'}        = $fulltext =~ m/^Database pages\s*$d$/m;
+   @{$section}{'pages_modified'}     = $fulltext =~ m/^Modified db pages\s*$d$/m;
+   @{$section}{'pages_read', 'pages_created', 'pages_written'}
+      = $fulltext =~ m/^Pages read $d, created $d, written $d$/m;
+   @{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'}
+      = $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m;
+   @{$section}{'buf_pool_hits', 'buf_pool_reads'}
+      = $fulltext =~ m{Buffer pool hit rate $d / $d$}m;
+   if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) {
+      @{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0);
+      @{$section}{'buf_pool_hit_rate'} = '--';
+   }
+   else {
+      @{$section}{'buf_pool_hit_rate'}
+         = $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)$}m;
+   }
+   @{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads $d/m;
+   @{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' }
+      = $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m;
+
+   map { $section->{$_} ||= 0 }
+      qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page
+      awe_mem_alloc dict_mem_alloc);
+   @{$section}{'writes_pending'} = List::Util::sum(
+      @{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) });
+
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+# I've read the source for this.
+sub parse_io_section {
+   my ( $section, $complete, $debug, $full ) = @_;
+   return unless $section && $section->{'fulltext'};
+   my $fulltext = $section->{'fulltext'};
+   $section->{'threads'} = {};
+
+   # Grab the I/O thread info
+   my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm;
+   foreach my $thread (@threads) {
+      my ( $tid, $state, $purpose, $event_set )
+         = $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m;
+      if ( defined $tid ) {
+         $section->{'threads'}->{$tid} = {
+            thread    => $tid,
+            state     => $state,
+            purpose   => $purpose,
+            event_set => $event_set ? 1 : 0,
+         };
+      }
+   }
+
+   # Grab the reads/writes/flushes info
+   @{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' }
+      = $fulltext =~ m/^Pending normal aio reads: $d, aio writes: $d,$/m;
+   @{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' }
+      = $fulltext =~ m{^ ibuf aio reads: $d, log i/o's: $d, sync i/o's: $d$}m;
+   @{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' }
+      = $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m;
+   @{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' }
+      = $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m;
+   @{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' }
+      = $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m;
+   @{$section}{ 'pending_preads', 'pending_pwrites' }
+      = $fulltext =~ m/$d pending preads, $d pending pwrites$/m;
+   @{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0)
+      unless defined($section->{'pending_preads'});
+
+   delete $section->{'fulltext'} unless $debug;
+   return 1;
+}
+
+sub _debug {
+   my ( $debug, $msg ) = @_;
+   if ( $debug ) {
+      die $msg;
+   }
+   else {
+      warn $msg;
+   }
+   return 1;
+}
+
+1;
+
+# end_of_package InnoDBParser
+
+package main;
+
 use sigtrap qw(handler finish untrapped normal-signals);
 
 use Data::Dumper;
 use DBI;
 use English qw(-no_match_vars);
 use File::Basename qw(dirname);
+use File::Temp;
 use Getopt::Long;
 use List::Util qw(max min maxstr sum);
-use InnoDBParser;
 use POSIX qw(ceil);
 use Time::HiRes qw(time sleep);
 use Term::ReadKey qw(ReadMode ReadKey);
 
-# Version, license and warranty information. {{{1
+# License and warranty information. {{{1
 # ###########################################################################
-our $VERSION = '1.6.0';
-our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g);
-our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g);
 
 my $innotop_license = <<"LICENSE";
 
@@ -55,6 +1375,7 @@
 );
 
 my $clear_screen_sub;
+my $dsn_parser = new DSNParser();
 
 # This defines expected properties and defaults for the column definitions that
 # eventually end up in tbl_meta.
@@ -89,7 +1410,13 @@
    { s => 'delay|d=f',  d => 'Delay between updates in seconds',  c => 'interval' },
    { s => 'mode|m=s',   d => 'Operating mode to start in',        c => 'mode' },
    { s => 'inc|i!',     d => 'Measure incremental differences',   c => 'status_inc' },
+   { s => 'write|w',    d => 'Write running configuration into home directory if no config files were loaded' },
+   { s => 'skipcentral|s',     d => 'Skip reading the central configuration file' },
    { s => 'version',    d => 'Output version information and exit' },
+   { s => 'user|u=s',   d => 'User for login if not current user' },
+   { s => 'password|p=s',   d => 'Password to use for connection' },
+   { s => 'host|h=s',   d => 'Connect to host' },
+   { s => 'port|P=i',   d => 'Port number to use for connection' },
 );
 
 # This is the container for the command-line options' values to be stored in
@@ -113,10 +1440,14 @@
 GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1;
 
 if ( $opts{version} ) {
-   print "innotop  Ver $VERSION Changeset $SVN_REV from $SVN_URL\n";
+   print "innotop  Ver $VERSION\n";
    exit(0);
 }
 
+if ( $opts{c} and ! -f $opts{c} ) {
+   print $opts{c} . " doesn't exist.  Exiting.\n";
+   exit(1);
+}
 if ( $opts{'help'} ) {
    print "Usage: innotop <options> <innodb-status-file>\n\n";
    my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec);
@@ -695,7 +2026,10 @@
       my @args = grep { defined $_ } @_;
       return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1);
    },
-   sum   => \&sum,
+   sum   => sub {
+      my @args = grep { defined $_ } @_;
+      return sum(@args);
+   }
 );
 
 # ###########################################################################
@@ -1188,7 +2522,7 @@
          info            => { src => 'info',       minw => 0,  maxw => 0, trans => [ qw(no_ctrl_char) ] },
          cnt             => { src => 'id',         minw => 0,  maxw => 0 },
       },
-      visible => [ qw(cxn cmd cnt mysql_thread_id user hostname db time info)],
+      visible => [ qw(cxn cmd cnt mysql_thread_id state user hostname db time info)],
       filters => [ qw(hide_self hide_inactive hide_slave_io) ],
       sort_cols => '-time cxn hostname mysql_thread_id',
       sort_dir => '1',
@@ -2440,9 +3774,6 @@
 my $clock               = 0;   # Incremented with every wake-sleep cycle
 my $clearing_deadlocks  = 0;
 
-# Find the home directory; it's different on different OSes.
-my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
-
 # If terminal coloring is available, use it.  The only function I want from
 # the module is the colored() function.
 eval {
@@ -2541,7 +3872,7 @@
       pat  => $bool_regex,
    },
    readonly => {
-      val  => 0,
+      val  => 1,
       note => 'Whether the config file is read-only',
       conf => [ qw() ],
       pat  => $bool_regex,
@@ -2604,7 +3935,7 @@
       conf => 'ALL',
    },
    mode => {
-      val  => "T",
+      val  => "Q",
       note => "Which mode to start in",
       cmdline => 1,
    },
@@ -2822,6 +4153,21 @@
 # Clear the screen and load the configuration.
 $clear_screen_sub->();
 load_config();
+
+# Override config variables with command-line options
+my %cmdline =
+   map  { $_->{c} => $opts{$_->{k}} }
+   grep { exists $_->{c} && exists $opts{$_->{k}} }
+   @opt_spec;
+
+foreach my $name (keys %cmdline) {
+   next if not defined $cmdline{$name};
+   my $val = $cmdline{$name};
+   if ( exists($config{$name}) and (!$config{$name}->{pat} or $val =~ m/$config{$name}->{pat}/ )) {
+      $config{$name}->{val} = $val;
+   }
+}
+
 post_process_tbl_meta();
 
 # Make sure no changes are written to config file in non-interactive mode.
@@ -2848,7 +4194,7 @@
 
    while (++$clock) {
 
-      my $mode = $config{mode}->{val} || 'T';
+      my $mode = $config{mode}->{val} || 'Q';
       if ( !$modes{$mode} ) {
          die "Mode '$mode' doesn't exist; try one of these:\n"
             . join("\n", map { "  $_ $modes{$_}->{hdr}" }  sort keys %modes)
@@ -4435,6 +5781,16 @@
    my ( $rows, $tbl ) = @_;
    my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta";
 
+   # don't show cxn if there's only one connection being displayed
+   my @visible;
+   if (scalar @{$modes{$config{mode}->{val}}->{connections}} == 1) {
+      map { push @visible, $_ if $_ !~ /^cxn$/ } @{$meta->{visible}};
+      delete $$rows[0]{cxn} if defined $$rows[0]{cxn};
+   }
+   else {
+      @visible = @{$meta->{visible}};
+   }
+
    if ( !$meta->{pivot} ) {
 
       # Hook in event listeners
@@ -4587,7 +5943,7 @@
       # If the table isn't pivoted, just show all columns that are supposed to
       # be shown; but eliminate aggonly columns if the table isn't aggregated.
       my $aggregated = $meta->{aggregate};
-      $fmt_cols = [ grep { $aggregated || !$meta->{cols}->{$_}->{aggonly} } @{$meta->{visible}} ];
+      $fmt_cols = [ grep { $aggregated || !$meta->{cols}->{$_}->{aggonly} } @visible ];
       $fmt_meta = { map  { $_ => $meta->{cols}->{$_}                      } @$fmt_cols };
 
       # If the table is aggregated, re-order the group_by columns to the left of
@@ -4988,7 +6344,7 @@
             $uptime,
             $ibinfo,
             shorten($qps) . " QPS",
-            ($vars->{Threads} || 0) . " thd",
+            ($vars->{Threads} || 0) . "/" . ($vars->{Threads_running} || 0) . "/" . ($vars->{Threads_cached} || 0) . " con/run/cac thds",
             $cxn)));
    }
    else {
@@ -5003,7 +6359,7 @@
 
 # Database connections {{{3
 sub add_new_dsn {
-   my ( $name ) = @_;
+   my ( $name, $dsn, $dl_table, $have_user, $user, $have_pass, $pass, $savepass ) = @_;
 
    if ( defined $name ) {
       $name =~ s/[\s:;]//g;
@@ -5018,23 +6374,30 @@
       } until ( $name );
    }
 
-   my $dsn;
-   do {
+   if ( !$dsn ) {
+      do {
+         $clear_screen_sub->();
+         print "Typical DSN strings look like\n   DBI:mysql:;host=hostname;port=port\n"
+            . "The db and port are optional and can usually be omitted.\n"
+            . "If you specify 'mysql_read_default_group=mysql' many options can be read\n"
+            . "from your mysql options files (~/.my.cnf, /etc/my.cnf).\n\n";
+         $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;mysql_read_default_group=mysql;host=$name");
+      } until ( $dsn );
+   }
+   if ( !$dl_table ) {
       $clear_screen_sub->();
-      print "Typical DSN strings look like\n   DBI:mysql:;host=hostname;port=port\n"
-         . "The db and port are optional and can usually be omitted.\n"
-         . "If you specify 'mysql_read_default_group=mysql' many options can be read\n"
-         . "from your mysql options files (~/.my.cnf, /etc/my.cnf).\n\n";
-      $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;mysql_read_default_group=mysql;host=$name");
-   } until ( $dsn );
+      my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information",
+         undef, 'test.innotop_dl');
+   }
 
-   $clear_screen_sub->();
-   my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information",
-      undef, 'test.innotop_dl');
-
    $connections{$name} = {
-      dsn      => $dsn,
-      dl_table => $dl_table,
+      dsn       => $dsn,
+      dl_table  => $dl_table,
+      have_user => $have_user,
+      user      => $user,
+      have_pass => $have_pass,
+      pass      => $pass,
+      savepass  => $savepass
    };
 }
 
@@ -5375,12 +6738,14 @@
    my $dsn = $connections{$connection}
       or die "No connection named '$connection' is defined in your configuration";
 
-   if ( !defined $dsn->{have_user} ) {
+   # don't ask for a username if mysql_read_default_group=client is in the DSN
+   if ( !defined $dsn->{have_user} and $dsn->{dsn} !~ /mysql_read_default_group=client/ ) {
       my $answer = prompt("Do you want to specify a username for $connection?", undef, 'n');
       $dsn->{have_user} = $answer && $answer =~ m/1|y/i;
    }
 
-   if ( !defined $dsn->{have_pass} ) {
+   # don't ask for a password if mysql_read_default_group=client is in the DSN
+   if ( !defined $dsn->{have_pass} and $dsn->{dsn} !~ /mysql_read_default_group=client/ ) {
       my $answer = prompt("Do you want to specify a password for $connection?", undef, 'n');
       $dsn->{have_pass} = $answer && $answer =~ m/1|y/i;
    }
@@ -5523,39 +6888,91 @@
    print $msg;
 }
 
+# migrate_config {{{3
+sub migrate_config {
+
+   my ($old_filename, $new_filename) = @_;
+
+   # don't proceed if old file doesn't exist
+   if ( ! -f $old_filename ) {
+      die "Error migrating '$old_filename': file doesn't exist.\n";
+   }
+   # don't migrate files if new file exists
+   elsif ( -f $new_filename ) {
+      die "Error migrating '$old_filename' to '$new_filename': new file already exists.\n";
+   }
+   # if migrating from one file to another in the same directory, just rename them
+   if (dirname($old_filename) eq dirname($new_filename)) {
+      rename($old_filename, $new_filename)
+         or die "Can't rename '$old_filename' to '$new_filename': $OS_ERROR";
+   }
+   # otherwise, move the existing conf file to a temp file, make the necessary directory structure,
+   # and move the temp conf file to its new home
+   else {
+      my $tmp = File::Temp->new( TEMPLATE => 'innotopXXXXX', DIR => $homepath, SUFFIX => '.conf');
+      my $tmp_filename = $tmp->filename;
+      my $dirname = dirname($new_filename);
+      rename($old_filename, $tmp_filename)
+         or die "Can't rename '$old_filename' to '$tmp_filename': $OS_ERROR";
+      mkdir($dirname) or die "Can't create directory '$dirname': $OS_ERROR";
+      mkdir("$dirname/plugins") or die "Can't create directory '$dirname/plugins': $OS_ERROR";
+      rename($tmp_filename, $new_filename)
+         or die "Can't rename '$tmp_filename' to '$new_filename': $OS_ERROR";
+   }
+}
+
 # load_config {{{3
 sub load_config {
+    
+   my ($old_filename, $answer);
 
-   my $filename = $opts{c} || "$homepath/.innotop/innotop.ini";
-   my $dirname  = dirname($filename);
-   if ( -f $dirname && !$opts{c} ) {
-      # innotop got upgraded and this is the old config file.
-      my $answer = pause("Innotop's default config location has moved to $filename.  Move old config file $dirname there now? y/n");
+   if ( $opts{u} or $opts{p} or $opts{h} or $opts{P} ) {
+     my @params = $dsn_parser->get_cxn_params(\%opts); # dsn=$params[0]
+     add_new_dsn($opts{h} || 'localhost', $params[0], 'test.innotop_dl', 
+                 $opts{u} ? 1 : 0, $opts{u}, $opts{p} ? 1 : 0, $opts{p});
+   }
+   if ($opts{c}) {
+      $conf_file = $opts{c};
+   }  
+   # innotop got upgraded and this is an old config file.
+   elsif ( -f "$homepath/.innotop" or -f "$homepath/.innotop/innotop.ini" ) {
+      $conf_file = $default_home_conf;
+      if ( -f  "$homepath/.innotop") {
+         $old_filename = "$homepath/.innotop";
+      }
+      elsif ( -f "$homepath/.innotop/innotop.ini" ) {
+         $old_filename = "$homepath/.innotop/innotop.ini";
+      }
+      $answer = pause("Innotop's default config location has moved to '$conf_file'.  Move old config file '$old_filename' there now? y/n");
       if ( lc $answer eq 'y' ) {
-         rename($dirname, "$homepath/innotop.ini")
-            or die "Can't rename '$dirname': $OS_ERROR";
-         mkdir($dirname) or die "Can't create directory '$dirname': $OS_ERROR";
-         mkdir("$dirname/plugins") or die "Can't create directory '$dirname/plugins': $OS_ERROR";
-         rename("$homepath/innotop.ini", $filename)
-            or die "Can't rename '$homepath/innotop.ini' to '$filename': $OS_ERROR";
+         migrate_config($old_filename, $conf_file);
       }
       else {
          print "\nInnotop will now exit so you can fix the config file.\n";
          exit(0);
       }
    }
-
-   if ( ! -d $dirname ) {
-      mkdir $dirname
-         or die "Can't create directory '$dirname': $OS_ERROR";
+   elsif ( -f $default_home_conf ) {
+      $conf_file = $default_home_conf;
    }
-   if ( ! -d "$dirname/plugins" ) {
-      mkdir "$dirname/plugins"
-         or die "Can't create directory '$dirname/plugins': $OS_ERROR";
+   elsif ( -f $default_central_conf and not $opts{s} ) {
+      $conf_file = $default_central_conf;
    }
+   else {
+      # If no config file was loaded, set readonly to 0 if the user wants to 
+      # write a config
+      $config{readonly}->{val} = 0 if $opts{w};
+      # If no connections have been defined, connect to a MySQL database 
+      # on localhost using mysql_read_default_group=client
+      if (!%connections) {
+         add_new_dsn('localhost', 
+                     'DBI:mysql:;host=localhost;mysql_read_default_group=client', 
+                     'test.innotop_dl');
+      }
+   }
 
-   if ( -f $filename ) {
-      open my $file, "<", $filename or die("Can't open '$filename': $OS_ERROR");
+   if ( -f "$conf_file" ) {
+      open my $file, "<", $conf_file or die("Can't open '$conf_file': $OS_ERROR");
 
       # Check config file version.  Just ignore if either innotop or the file has
       # garbage in the version number.
@@ -5577,7 +6994,7 @@
                   # If the config file is between the endpoints and innotop is greater than
                   # the endpoint, innotop has a newer config file format than the file.
                   if ( $cfg_ver ge $start && $cfg_ver lt $end && $innotop_ver ge $end ) {
-                     my $msg = "innotop's config file format has changed.  Overwrite $filename?  y or n";
+                     my $msg = "innotop's config file format has changed.  Overwrite $conf_file?  y or n";
                      if ( pause($msg) eq 'n' ) {
                         $config{readonly}->{val} = 1;
                         print "\ninnotop will not save any configuration changes you make.";
@@ -5602,7 +7019,7 @@
             warn "Unknown config file section '$1'";
          }
       }
-      close $file or die("Can't close $filename: $OS_ERROR");
+      close $file or die("Can't close $conf_file: $OS_ERROR");
    }
 
 }
@@ -5999,12 +7416,6 @@
 sub load_config_config {
    my ( $file ) = @_;
 
-   # Look in the command-line parameters for things stored in the same slot.
-   my %cmdline =
-      map  { $_->{c} => $opts{$_->{k}} }
-      grep { exists $_->{c} && exists $opts{$_->{k}} }
-      @opt_spec;
-
    while ( my $line = <$file> ) {
       chomp $line;
       next if $line =~ m/^#/;
@@ -6013,9 +7424,6 @@
       my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/;
       next unless defined $name && defined $val;
 
-      # Values might already have been set at the command line.
-      $val = defined($cmdline{$name}) ? $cmdline{$name} : $val;
-
       # Validate the incoming values...
       if ( $name && exists( $config{$name} ) ) {
          if ( !$config{$name}->{pat} || $val =~ m/$config{$name}->{pat}/ ) {
@@ -6080,12 +7488,41 @@
 
 # save_config {{{3
 sub save_config {
+   print "\n";
    return if $config{readonly}->{val};
+   # return if no config file was loaded and -w wasn't specified
+   if (not $conf_file) {
+      if (not $opts{w}) {
+         return;
+      }
+      else {
+         # if no config was loaded but -w was specified,
+         # write to $default_home_conf
+         $conf_file = $default_home_conf;
+      }
+   }
+   elsif ($conf_file and $opts{w}) {
+     print "Loaded config file on start-up, so ignoring -w (see --help)\n"
+   }
+   
+   my $dirname  = dirname($conf_file);
+
+   # if directories don't exist, create them.  This could cause errors
+   # or warnings if a central config doesn't have readonly=1, but being
+   # flexible requires giving the user enough rope to hang themselves with.
+   if ( ! -d $dirname ) {
+      mkdir $dirname
+         or die "Can't create directory '$dirname': $OS_ERROR";
+   }
+   if ( ! -d "$dirname/plugins" ) {
+      mkdir "$dirname/plugins"
+         or warn "Can't create directory '$dirname/plugins': $OS_ERROR\n";
+   }
+
    # Save to a temp file first, so a crash doesn't destroy the main config file
-   my $newname  = $opts{c} || "$homepath/.innotop/innotop.ini";
-   my $filename = $newname . '_tmp';
-   open my $file, "+>", $filename
-      or die("Can't write to $filename: $OS_ERROR");
+   my $tmpfile = File::Temp->new( TEMPLATE => 'innotopXXXXX', DIR => $dirname, SUFFIX => '.conf.tmp');
+   open my $file, "+>", $tmpfile
+      or die("Can't write to $tmpfile: $OS_ERROR");
    print $file "version=$VERSION\n";
 
    foreach my $section ( @ordered_config_file_sections ) {
@@ -6096,12 +7533,13 @@
    }
 
    # Now clobber the main config file with the temp.
-   close $file or die("Can't close $filename: $OS_ERROR");
-   rename($filename, $newname) or die("Can't rename $filename to $newname: $OS_ERROR");
+   close $file or die("Can't close $tmpfile: $OS_ERROR");
+   rename($tmpfile, $conf_file) or die("Can't rename $tmpfile to $conf_file: $OS_ERROR");
 }
 
 # load_config_connections {{{3
 sub load_config_connections {
+   return if $opts{u} or $opts{p} or $opts{h} or $opts{P}; # don't load connections if DSN or user/pass options used
    my ( $file ) = @_;
    while ( my $line = <$file> ) {
       chomp $line;
@@ -7250,6 +8688,10 @@
 
  innotop --count 5 -d 1 -n
 
+To monitor a database on another system using a particular username and password:
+
+ innotop -u <username> -p <password> -h <hostname>
+
 =head1 DESCRIPTION
 
 innotop monitors MySQL servers.  Each of its modes shows you a different aspect
@@ -7269,42 +8711,30 @@
 Enter; otherwise, you will need to change to innotop's directory and type "perl
 innotop".
 
-The first thing innotop needs to know is how to connect to a MySQL server.  You
-can just enter the hostname of the server, for example "localhost" or
-"127.0.0.1" if the server is on the same machine as innotop.  After this innotop
-will prompt you for a DSN (data source name).  You should be able to just accept
-the defaults by pressing Enter.
+With no options specified, innotop will attempt to connect to a MySQL server on
+localhost using mysql_read_default_group=client for other connection
+parameters.  If you need to specify a different username and password, use the
+-u and -p options, respectively.  To monitor a MySQL database on another
+host, use the -h option.
 
-When innotop asks you about a table to use when resetting InnoDB deadlock
-information, just accept the default for now.  This is an advanced feature you
-can configure later (see L<"D: InnoDB Deadlocks"> for more).
+After you've connected, innotop should show you something like the following:
 
-If you have a .my.cnf file with your MySQL connection defaults, innotop can read
-it, and you won't need to specify a username and password if it's in that file.
-Otherwise, you should answer 'y' to the next couple of prompts.
+ [RO] Query List (? for help) localhost, 01:11:19, 449.44 QPS, 14/7/163 con/run
+ 
+ CXN        When   Load  QPS    Slow  QCacheHit  KCacheHit  BpsIn    BpsOut 
+ localhost  Total  0.00  1.07k   697      0.00%     98.17%  476.83k  242.83k
 
-After this, you should be connected, and innotop should show you something like
-the following:
+ CXN        Cmd    ID         User  Host      DB   Time   Query
+ localhost  Query  766446598  test  10.0.0.1  foo  00:02  INSERT INTO table (
 
- InnoDB Txns (? for help) localhost, 01:11:19, InnoDB 10s :-), 50 QPS,
- 
- CXN        History  Versions  Undo  Dirty Buf  Used Bufs  Txns  MaxTxn
- localhost        7      2035  0 0       0.00%     92.19%     1   07:34
- 
- CXN        ID     User   Host       Txn Status  Time   Undo  Query Tex
- localhost  98379  user1  webserver  ACTIVE      07:34     0  SELECT `c
- localhost  98450  user1  webserver  ACTIVE      01:06     0  INSERT IN
- localhost  97750  user1  webserver  not starte  00:00     0      
- localhost  98375  user1  appserver  not starte  00:00     0      
 
 (This sample is truncated at the right so it will fit on a terminal when running
 'man innotop')
 
-This sample comes from a quiet server with few transactions active.  If your
-server is busy, you'll see more output.  Notice the first line on the screen,
-which tells you what mode you're in and what server you're connected to.  You
-can change to other modes with keystrokes; press 'Q' to switch to a list of
-currently running queries.
+If your server is busy, you'll see more output.  Notice the first line on the
+screen, which tells you that readonly is set to true ([RO]), what mode you're
+in and what server you're connected to.  You can change to other modes with
+keystrokes; press 'T' to switch to a list of InnoDB transactions, for example.
 
 Press the '?' key to see what keys are active in the current mode.  You can
 press any of these keys and innotop will either take the requested action or
@@ -7325,10 +8755,6 @@
 
 =over
 
-=item --help
-
-Print a summary of command-line usage and exit.
-
 =item --color
 
 Enable or disable terminal coloring.  Corresponds to the L<"color"> config file
@@ -7339,10 +8765,6 @@
 Specifies a configuration file to read.  This option is non-sticky, that is to
 say it does not persist to the configuration file itself.
 
-=item --nonint
-
-Enable non-interactive operation.  See L<"NON-INTERACTIVE OPERATION"> for more.
-
 =item --count
 
 Refresh only the specified number of times (ticks) before exiting.  Each refresh
@@ -7354,21 +8776,55 @@
 Specifies the amount of time to pause between ticks (refreshes).  Corresponds to
 the configuration option L<"interval">.
 
-=item --mode
+=item --help
 
-Specifies the mode in which innotop should start.  Corresponds to the
-configuration option L<"mode">.
+Print a summary of command-line usage and exit.
 
+=item --host
+
+Host to connect to.
+
 =item --inc
 
 Specifies whether innotop should display absolute numbers or relative numbers
 (offsets from their previous values).  Corresponds to the configuration option
 L<"status_inc">.
 
+=item --mode
+
+Specifies the mode in which innotop should start.  Corresponds to the
+configuration option L<"mode">.
+
+=item --nonint
+
+Enable non-interactive operation.  See L<"NON-INTERACTIVE OPERATION"> for more.
+
+=item --password
+
+Password to use for connection.
+
+=item --port
+
+Port to use for connection.
+
+=item --skipcentral
+
+Don't read the central configuration file.
+
+=item --user
+
+User to use for connection.
+
 =item --version
 
 Output version information and exit.
 
+=item --write
+
+Sets the configuration option L<"readonly"> to 0, making innotop write the
+running configuration to ~/.innotop/innotop.conf on exit, if no configuration
+file was loaded at start-up.
+
 =back
 
 =head1 HOTKEYS
@@ -7623,8 +9079,9 @@
 
 The first line innotop displays is a "status bar" of sorts.  What it contains
 depends on the mode you're in, and what servers you're monitoring.  The first
-few words are always the innotop mode, such as "InnoDB Txns" for T mode,
-followed by a reminder to press '?' for help at any time.
+few words are always [RO] (if readonly is set to 1), the innotop mode, such as
+"InnoDB Txns" for T mode, followed by a reminder to press '?' for help at any
+time.
 
 =head2 ONE SERVER
 
@@ -7701,8 +9158,8 @@
 
 =head1 SERVER CONNECTIONS
 
-When you create a server connection, innotop asks you for a series of inputs, as
-follows:
+When you create a server connection using '@', innotop asks you for a series of
+inputs, as follows:
 
 =over
 
@@ -7755,9 +9212,6 @@
 server connections and switch between them by pressing the '@' key.  See
 L<"SWITCHING BETWEEN CONNECTIONS">.
 
-To create a new connection, press the '@' key and type the name of the new
-connection, then follow the steps given above.
-
 =head1 SERVER GROUPS
 
 If you have multiple MySQL instances, you can put them into named groups, such
@@ -7958,20 +9412,23 @@
 
 =head1 CONFIGURATION FILE
 
-innotop's default configuration file location is in $HOME/.innotop, but can be
+innotop's default configuration file locations are $HOME/.innotop and
+/etc/innotop/innotop.conf, and they are looked for in that order.  If the first
+configuration file exists, the second will not be processed.  Those can be
 overridden with the L<"--config"> command-line option.  You can edit it by hand
-safely.  innotop reads the configuration file when it starts, and writes it out
-again when it exits, so any changes you make while innotop is running will be
-lost.
+safely, however innotop reads the configuration file when it starts, and, if
+readonly is set to 0, writes it out again when it exits.  Thus, if readonly is
+set to 0, any changes you make by hand while innotop is running will be lost.
 
 innotop doesn't store its entire configuration in the configuration file.  It
-has a huge set of default configuration that it holds only in memory, and the
-configuration file only overrides these defaults.  When you customize a default
-setting, innotop notices, and then stores the customizations into the file.
-This keeps the file size down, makes it easier to edit, and makes upgrades
-easier.
+has a huge set of default configuration values that it holds only in memory,
+and the configuration file only overrides these defaults.  When you customize a
+default setting, innotop notices, and then stores the customizations into the
+file.  This keeps the file size down, makes it easier to edit, and makes
+upgrades easier.
 
-A configuration file can be made read-only.  See L<"readonly">.
+A configuration file is read-only be default.  You can override that with
+L<"--write">.  See L<"readonly">.
 
 The configuration file is arranged into sections like an INI file.  Each
 section begins with [section-name] and ends with [/section-name].  Each
@@ -8119,8 +9576,7 @@
 
 =item readonly
 
-Whether the configuration file is readonly.  This cannot be set interactively,
-because it would prevent itself from being written to the configuration file.
+Whether the configuration file is readonly.  This cannot be set interactively.
 
 =item show_cxn_errors
 
@@ -8185,10 +9641,12 @@
 
 =item connections
 
-This section holds the server connections you have defined.  Each line is in the
-format name=properties, where the properties are a name=value list.  The
+This section holds the server connections you have defined.  Each line is in
+the format name=properties, where the properties are a name=value list.  The
 properties are self-explanatory, and the only one that is treated specially is
-'pass' which is only present if 'savepass' is set.  See L<"SERVER CONNECTIONS">.
+'pass' which is only present if 'savepass' is set.  This section of the
+configuration file will be skipped if any DSN, username, or password
+command-line options are used.  See L<"SERVER CONNECTIONS">.
 
 =item active_connections
 
@@ -8646,7 +10104,7 @@
 
 =head2 GROUPING
 
-innotop can group, or aggregate, rows together (I use the terms
+innotop can group, or aggregate, rows together (the terms are used
 interchangeably).  This is quite similar to an SQL GROUP BY clause.  You can
 specify to group on certain columns, or if you don't specify any, the entire set
 of rows is treated as one group.  This is quite like SQL so far, but unlike SQL,
@@ -9389,15 +10847,16 @@
 I don't know for sure.  It also runs on Windows under ActivePerl without
 problem.
 
-I use innotop on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26, 5.1.15,
-and 5.2.3.  If it doesn't run correctly for you, that is a bug and I hope you
-report it.
+innotop has been used on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26,
+5.1.15, and 5.2.3.  If it doesn't run correctly for you, that is a bug that
+should be reported.
 
 =head1 FILES
 
-$HOMEDIR/.innotop is used to store configuration information.  Files include the
-configuration file innotop.ini, the core_dump file which contains verbose error
-messages if L<"debug"> is enabled, and the plugins/ subdirectory.
+$HOMEDIR/.innotop and/or /etc/innotop are used to store
+configuration information.  Files include the configuration file innotop.conf,
+the core_dump file which contains verbose error messages if L<"debug"> is
+enabled, and the plugins/ subdirectory.
 
 =head1 GLOSSARY OF TERMS
 
@@ -9412,8 +10871,8 @@
 
 =head1 ACKNOWLEDGEMENTS
 
-I'm grateful to the following people for various reasons, and hope I haven't
-forgotten to include anyone:
+The following people and organizations are acknowledged for various reasons.
+Hopefully no one has been forgotten.
 
 Allen K. Smith,
 Aurimas Mikalauskas,
@@ -9426,6 +10885,7 @@
 Dr. Frank Ullrich,
 Giuseppe Maxia,
 Google.com Site Reliability Engineers,
+Google Code,
 Jan Pieter Kunst,
 Jari Aalto,
 Jay Pipes,
@@ -9443,9 +10903,9 @@
 The Gentoo MySQL Team,
 Trevor Price,
 Yaar Schnitman,
-and probably more people I've neglected to include.
+and probably more people that have not been included.
 
-(If I misspelled your name, it's probably because I'm afraid of putting
+(If your name has been misspelled, it's probably out of fear of putting
 international characters into this documentation; earlier versions of Perl might
 not be able to compile it then).
 
@@ -9472,14 +10932,15 @@
 
 =head1 AUTHOR
 
-Baron Schwartz.
+Originally written by Baron Schwartz; currently maintained by Aaron Racine.
 
 =head1 BUGS
 
 You can report bugs, ask for improvements, and get other help and support at
-L<http://sourceforge.net/projects/innotop>.  There are mailing lists, forums,
-a bug tracker, etc.  Please use these instead of contacting me directly, as it
-makes my job easier and benefits others if the discussions are permanent and
-public.  Of course, if you need to contact me in private, please do.
+L<http://code.google.com/p/innotop/>.  There are mailing lists, a source code
+browser, a bug tracker, etc.  Please use these instead of contacting the
+maintainer or author directly, as it makes our job easier and benefits others if the
+discussions are permanent and public.  Of course, if you need to contact us in
+private, please do.
 
 =cut

Modified: mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop.1
===================================================================
--- mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop.1	2009-07-22 17:36:02 UTC (rev 1649)
+++ mysql-dfsg-5.1/branches/experimental/debian/additions/innotop/innotop.1	2009-07-22 18:28:54 UTC (rev 1650)
@@ -1,15 +1,7 @@
-.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\" Automatically generated by Pod::Man 2.1801 (Pod::Simple 3.07)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
-.de Sh \" Subsection heading
-.br
-.if t .Sp
-.ne 5
-.PP
-\fB\\$1\fR
-.PP
-..
 .de Sp \" Vertical space (when we can't use .PP)
 .if t .sp .5v
 .if n .sp
@@ -48,23 +40,26 @@
 .    ds R" ''
 'br\}
 .\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el       .ds Aq '
+.\"
 .\" If the F register is turned on, we'll generate index entries on stderr for
-.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
 .\" entries marked with X<> in POD.  Of course, you'll have to process the
 .\" output yourself in some meaningful fashion.
-.if \nF \{\
+.ie \nF \{\
 .    de IX
 .    tm Index:\\$1\t\\n%\t"\\$2"
 ..
 .    nr % 0
 .    rr F
 .\}
+.el \{\
+.    de IX
+..
+.\}
 .\"
-.\" For nroff, turn off justification.  Always turn off hyphenation; it makes
-.\" way too many mistakes in technical documents.
-.hy 0
-.if n .na
-.\"
 .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
 .\" Fear.  Run.  Save yourself.  No user-serviceable parts.
 .    \" fudge factors for nroff and troff
@@ -129,7 +124,11 @@
 .\" ========================================================================
 .\"
 .IX Title "INNOTOP 1p"
-.TH INNOTOP 1p "2007-11-09" "perl v5.8.8" "User Contributed Perl Documentation"
+.TH INNOTOP 1p "2009-03-09" "perl v5.10.0" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification.  Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
 .SH "NAME"
 innotop \- MySQL and InnoDB transaction/status monitor.
 .SH "SYNOPSIS"
@@ -151,6 +150,12 @@
 .Vb 1
 \& innotop \-\-count 5 \-d 1 \-n
 .Ve
+.PP
+To monitor a database on another system using a particular username and password:
+.PP
+.Vb 1
+\& innotop \-u <username> \-p <password> \-h <hostname>
+.Ve
 .SH "DESCRIPTION"
 .IX Header "DESCRIPTION"
 innotop monitors MySQL servers.  Each of its modes shows you a different aspect
@@ -160,7 +165,7 @@
 .PP
 innotop has lots of features for power users, but you can start and run it with
 virtually no configuration.  If you're just getting started, see
-\&\*(L"\s-1QUICK\-START\s0\*(R".  Press '?' at any time while running innotop for
+\&\*(L"QUICK-START\*(R".  Press '?' at any time while running innotop for
 context-sensitive help.
 .SH "QUICK-START"
 .IX Header "QUICK-START"
@@ -169,48 +174,31 @@
 Enter; otherwise, you will need to change to innotop's directory and type \*(L"perl
 innotop\*(R".
 .PP
-The first thing innotop needs to know is how to connect to a MySQL server.  You
-can just enter the hostname of the server, for example \*(L"localhost\*(R" or
-\&\*(L"127.0.0.1\*(R" if the server is on the same machine as innotop.  After this innotop
-will prompt you for a \s-1DSN\s0 (data source name).  You should be able to just accept
-the defaults by pressing Enter.
+With no options specified, innotop will attempt to connect to a MySQL server on
+localhost using mysql_read_default_group=client for other connection
+parameters.  If you need to specify a different username and password, use the
+\&\-u and \-p options, respectively.  To monitor a MySQL database on another
+host, use the \-h option.
 .PP
-When innotop asks you about a table to use when resetting InnoDB deadlock
-information, just accept the default for now.  This is an advanced feature you
-can configure later (see \*(L"D: InnoDB Deadlocks\*(R" for more).
+After you've connected, innotop should show you something like the following:
 .PP
-If you have a .my.cnf file with your MySQL connection defaults, innotop can read
-it, and you won't need to specify a username and password if it's in that file.
-Otherwise, you should answer 'y' to the next couple of prompts.
-.PP
-After this, you should be connected, and innotop should show you something like
-the following:
-.PP
 .Vb 1
-\& InnoDB Txns (? for help) localhost, 01:11:19, InnoDB 10s :\-), 50 QPS,
+\& [RO] Query List (? for help) localhost, 01:11:19, 449.44 QPS, 14/7/163 con/run
+\& 
+\& CXN        When   Load  QPS    Slow  QCacheHit  KCacheHit  BpsIn    BpsOut 
+\& localhost  Total  0.00  1.07k   697      0.00%     98.17%  476.83k  242.83k
+\&
+\& CXN        Cmd    ID         User  Host      DB   Time   Query
+\& localhost  Query  766446598  test  10.0.0.1  foo  00:02  INSERT INTO table (
 .Ve
 .PP
-.Vb 2
-\& CXN        History  Versions  Undo  Dirty Buf  Used Bufs  Txns  MaxTxn
-\& localhost        7      2035  0 0       0.00%     92.19%     1   07:34
-.Ve
-.PP
-.Vb 5
-\& CXN        ID     User   Host       Txn Status  Time   Undo  Query Tex
-\& localhost  98379  user1  webserver  ACTIVE      07:34     0  SELECT `c
-\& localhost  98450  user1  webserver  ACTIVE      01:06     0  INSERT IN
-\& localhost  97750  user1  webserver  not starte  00:00     0      
-\& localhost  98375  user1  appserver  not starte  00:00     0
-.Ve
-.PP
 (This sample is truncated at the right so it will fit on a terminal when running
 \&'man innotop')
 .PP
-This sample comes from a quiet server with few transactions active.  If your
-server is busy, you'll see more output.  Notice the first line on the screen,
-which tells you what mode you're in and what server you're connected to.  You
-can change to other modes with keystrokes; press 'Q' to switch to a list of
-currently running queries.
+If your server is busy, you'll see more output.  Notice the first line on the
+screen, which tells you that readonly is set to true ([\s-1RO\s0]), what mode you're
+in and what server you're connected to.  You can change to other modes with
+keystrokes; press 'T' to switch to a list of InnoDB transactions, for example.
 .PP
 Press the '?' key to see what keys are active in the current mode.  You can
 press any of these keys and innotop will either take the requested action or
@@ -227,20 +215,14 @@
 .PP
 You can negate some options by prefixing the option name with \-\-no.  For
 example, \-\-noinc (or \-\-no\-inc) negates \*(L"\-\-inc\*(R".
-.IP "\-\-help" 4
-.IX Item "--help"
-Print a summary of command-line usage and exit.
 .IP "\-\-color" 4
 .IX Item "--color"
 Enable or disable terminal coloring.  Corresponds to the \*(L"color\*(R" config file
 setting.
 .IP "\-\-config" 4
 .IX Item "--config"
-Specifies a configuration file to read.  This option is non\-sticky, that is to
+Specifies a configuration file to read.  This option is non-sticky, that is to
 say it does not persist to the configuration file itself.
-.IP "\-\-nonint" 4
-.IX Item "--nonint"
-Enable non-interactive operation.  See \*(L"\s-1NON\-INTERACTIVE\s0 \s-1OPERATION\s0\*(R" for more.
 .IP "\-\-count" 4
 .IX Item "--count"
 Refresh only the specified number of times (ticks) before exiting.  Each refresh
@@ -250,21 +232,47 @@
 .IX Item "--delay"
 Specifies the amount of time to pause between ticks (refreshes).  Corresponds to
 the configuration option \*(L"interval\*(R".
-.IP "\-\-mode" 4
-.IX Item "--mode"
-Specifies the mode in which innotop should start.  Corresponds to the
-configuration option \*(L"mode\*(R".
+.IP "\-\-help" 4
+.IX Item "--help"
+Print a summary of command-line usage and exit.
+.IP "\-\-host" 4
+.IX Item "--host"
+Host to connect to.
 .IP "\-\-inc" 4
 .IX Item "--inc"
 Specifies whether innotop should display absolute numbers or relative numbers
 (offsets from their previous values).  Corresponds to the configuration option
 \&\*(L"status_inc\*(R".
+.IP "\-\-mode" 4
+.IX Item "--mode"
+Specifies the mode in which innotop should start.  Corresponds to the
+configuration option \*(L"mode\*(R".
+.IP "\-\-nonint" 4
+.IX Item "--nonint"
+Enable non-interactive operation.  See \*(L"NON-INTERACTIVE \s-1OPERATION\s0\*(R" for more.
+.IP "\-\-password" 4
+.IX Item "--password"
+Password to use for connection.
+.IP "\-\-port" 4
+.IX Item "--port"
+Port to use for connection.
+.IP "\-\-skipcentral" 4
+.IX Item "--skipcentral"
+Don't read the central configuration file.
+.IP "\-\-user" 4
+.IX Item "--user"
+User to use for connection.
 .IP "\-\-version" 4
 .IX Item "--version"
 Output version information and exit.
+.IP "\-\-write" 4
+.IX Item "--write"
+Sets the configuration option \*(L"readonly\*(R" to 0, making innotop write the
+running configuration to ~/.innotop/innotop.conf on exit, if no configuration
+file was loaded at start-up.
 .SH "HOTKEYS"
 .IX Header "HOTKEYS"
-innotop is interactive, and you control it with key\-presses.
+innotop is interactive, and you control it with key-presses.
 .IP "\(bu" 4
 Uppercase keys switch between modes.
 .IP "\(bu" 4
@@ -295,7 +303,7 @@
 .Sp
 .Vb 8
 \& Command Summary (? for help) localhost, 25+07:16:43, 2.45 QPS, 3 thd, 5.0.40
-\& _____________________ Command Summary _____________________
+\& _\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_ Command Summary _\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_
 \& Name                    Value    Pct     Last Incr  Pct    
 \& Select_scan             3244858  69.89%          2  100.00%
 \& Select_range            1354177  29.17%          0    0.00%
@@ -338,7 +346,8 @@
 .Sp
 If it has, you can create a small deadlock to replace the large one.  Use the
 \&'w' key to 'wipe' the large deadlock with a small one.  This will not work
-unless you have defined a deadlock table for the connection (see \*(L"\s-1SERVER\s0 \s-1CONNECTIONS\s0\*(R").
+unless you have defined a deadlock table for the connection (see \*(L"\s-1SERVER\s0
+\&\s-1CONNECTIONS\s0\*(R").
 .Sp
 You can also configure innotop to automatically detect when a large deadlock
 needs to be replaced with a small one (see \*(L"auto_wipe_dl\*(R").
@@ -396,7 +405,7 @@
 the screen when one connection is waiting for locks another connection holds:
 .Sp
 .Vb 7
-\& _________________________________ InnoDB Locks __________________________
+\& _\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_ InnoDB Locks _\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_\|_
 \& CXN        ID  Type    Waiting  Wait   Active  Mode  DB    Table  Index
 \& localhost  12  RECORD        1  00:10   00:10  X     test  t1     PRIMARY
 \& localhost  12  TABLE         0  00:10   00:10  IX    test  t1
@@ -440,7 +449,7 @@
 You can \s-1EXPLAIN\s0 a query from this mode with the 'e' key.  This displays the
 query's full text, the results of \s-1EXPLAIN\s0, and in newer MySQL versions, even
 the optimized query resulting from \s-1EXPLAIN\s0 \s-1EXTENDED\s0.  innotop also tries to
-rewrite certain queries to make them EXPLAIN\-able.  For example, \s-1INSERT/SELECT\s0
+rewrite certain queries to make them EXPLAIN-able.  For example, \s-1INSERT/SELECT\s0
 statements are rewritable.
 .Sp
 This mode displays the \*(L"q_header\*(R" and \*(L"processlist\*(R" tables by default.
@@ -491,9 +500,10 @@
 .IX Header "INNOTOP STATUS"
 The first line innotop displays is a \*(L"status bar\*(R" of sorts.  What it contains
 depends on the mode you're in, and what servers you're monitoring.  The first
-few words are always the innotop mode, such as \*(L"InnoDB Txns\*(R" for T mode,
-followed by a reminder to press '?' for help at any time.
-.Sh "\s-1ONE\s0 \s-1SERVER\s0"
+few words are always [\s-1RO\s0] (if readonly is set to 1), the innotop mode, such as
+\&\*(L"InnoDB Txns\*(R" for T mode, followed by a reminder to press '?' for help at any
+time.
+.SS "\s-1ONE\s0 \s-1SERVER\s0"
 .IX Subsection "ONE SERVER"
 The simplest case is when you're monitoring a single server.  In this case, the
 name of the connection is next on the status line.  This is the name you gave
@@ -513,7 +523,7 @@
 The next two words indicate the server's queries per second (\s-1QPS\s0) and how many
 threads (connections) exist.  Finally, the server's version number is the last
 thing on the line.
-.Sh "\s-1MULTIPLE\s0 \s-1SERVERS\s0"
+.SS "\s-1MULTIPLE\s0 \s-1SERVERS\s0"
 .IX Subsection "MULTIPLE SERVERS"
 If you are monitoring multiple servers (see \*(L"\s-1SERVER\s0 \s-1CONNECTIONS\s0\*(R"), the status
 line does not show any details about individual servers.  Instead, it shows the
@@ -527,7 +537,7 @@
 don't have errors.
 .PP
 See \*(L"\s-1ERROR\s0 \s-1HANDLING\s0\*(R" for more details about innotop's error handling.
-.Sh "\s-1MONITORING\s0 A \s-1FILE\s0"
+.SS "\s-1MONITORING\s0 A \s-1FILE\s0"
 .IX Subsection "MONITORING A FILE"
 If you give a filename on the command line, innotop will not connect to \s-1ANY\s0
 servers at all.  It will watch the specified file for InnoDB status output and
@@ -549,7 +559,7 @@
 innotop pre-selects the longest-running query, or the oldest connection.
 Confirm the command with 'y'.
 .PP
-In \*(L"M: Master/Slave Replication Status\*(R" mode, you can start and stop slaves
+In \*(L"Slave Replication Status\*(R"\*(L" in \*(R"M: Master mode, you can start and stop slaves
 with the 'a' and 'o' keys, respectively.  You can send these commands to many
 slaves at once.  innotop fills in a default command of \s-1START\s0 \s-1SLAVE\s0 or \s-1STOP\s0 \s-1SLAVE\s0
 for you, but you can actually edit the command and send anything you wish, such
@@ -564,8 +574,8 @@
 these slave connections and suggest it as the argument to \s-1PURGE\s0 \s-1MASTER\s0 \s-1LOGS\s0.
 .SH "SERVER CONNECTIONS"
 .IX Header "SERVER CONNECTIONS"
-When you create a server connection, innotop asks you for a series of inputs, as
-follows:
+When you create a server connection using '@', innotop asks you for a series of
+inputs, as follows:
 .IP "\s-1DSN\s0" 4
 .IX Item "DSN"
 A \s-1DSN\s0 is a Data Source Name, which is the initial argument passed to the \s-1DBI\s0
@@ -576,7 +586,7 @@
 .Ve
 .Sp
 Since this \s-1DSN\s0 is passed to the DBD::mysql driver, you should read the driver's
-documentation at \*(L"http://search.cpan.org/dist/DBD\-mysql/lib/DBD/mysql.pm\*(R" for
+documentation at \*(L"/search.cpan.org/dist/DBD\-mysql/lib/DBD/mysql.pm\*(R"\*(L" in \*(R"http: for
 the exact details on all the options you can pass the driver in the \s-1DSN\s0.  You
 can read more about \s-1DBI\s0 at <http://dbi.perl.org/docs/>, and especially at
 <http://search.cpan.org/~timb/DBI/DBI.pm>.
@@ -611,9 +621,6 @@
 But innotop isn't limited to monitoring a single server; you can define many
 server connections and switch between them by pressing the '@' key.  See
 \&\*(L"\s-1SWITCHING\s0 \s-1BETWEEN\s0 \s-1CONNECTIONS\s0\*(R".
-.PP
-To create a new connection, press the '@' key and type the name of the new
-connection, then follow the steps given above.
 .SH "SERVER GROUPS"
 .IX Header "SERVER GROUPS"
 If you have multiple MySQL instances, you can put them into named groups, such
@@ -629,7 +636,7 @@
 .IX Header "SWITCHING BETWEEN CONNECTIONS"
 innotop lets you quickly switch which servers you're monitoring.  The most basic
 way is by pressing the '@' key and typing the name(s) of the connection(s) you
-want to use.  This setting is per\-mode, so you can monitor different connections
+want to use.  This setting is per-mode, so you can monitor different connections
 in each mode, and innotop remembers which connections you choose.
 .PP
 You can quickly switch to the 'next' connection in alphabetical order with the
@@ -663,7 +670,7 @@
 .PP
 innotop does not continue to query connections that have errors, because they
 may slow innotop and make it hard to use, especially if the error is a problem
-connecting and causes a long time\-out.  Instead, innotop retries the connection
+connecting and causes a long time-out.  Instead, innotop retries the connection
 occasionally to see if the error still exists.  If so, it will wait until some
 point in the future.  The wait time increases in ticks as the Fibonacci series,
 so it tries less frequently as time passes.
@@ -704,7 +711,8 @@
 .IP "\(bu" 4
 innotop only displays the first table in each mode.  This is so the output can
 be easily processed with other command-line utilities such as awk and sed.  To
-change which tables display in each mode, see \*(L"\s-1TABLES\s0\*(R".  Since \*(L"Q: Query List\*(R" mode is so important, innotop automatically disables the \*(L"q_header\*(R"
+change which tables display in each mode, see \*(L"\s-1TABLES\s0\*(R".  Since \*(L"Q: Query
+List\*(R" mode is so important, innotop automatically disables the \*(L"q_header\*(R"
 table.  This ensures you'll see the \*(L"processlist\*(R" table, even if you have
 innotop configured to show the q_header table during interactive operation.
 Similarly, in \*(L"T: InnoDB Transactions\*(R" mode, the \*(L"t_header\*(R" table is
@@ -775,20 +783,23 @@
 \&\*(L"\s-1TABLES\s0\*(R".
 .SH "CONFIGURATION FILE"
 .IX Header "CONFIGURATION FILE"
-innotop's default configuration file location is in \f(CW$HOME\fR/.innotop, but can be
+innotop's default configuration file locations are \f(CW$HOME\fR/.innotop and
+/etc/innotop/innotop.conf, and they are looked for in that order.  If the first
+configuration file exists, the second will not be processed.  Those can be
 overridden with the \*(L"\-\-config\*(R" command-line option.  You can edit it by hand
-safely.  innotop reads the configuration file when it starts, and writes it out
-again when it exits, so any changes you make while innotop is running will be
-lost.
+safely, however innotop reads the configuration file when it starts, and, if
+readonly is set to 0, writes it out again when it exits.  Thus, if readonly is
+set to 0, any changes you make by hand while innotop is running will be lost.
 .PP
 innotop doesn't store its entire configuration in the configuration file.  It
-has a huge set of default configuration that it holds only in memory, and the
-configuration file only overrides these defaults.  When you customize a default
-setting, innotop notices, and then stores the customizations into the file.
-This keeps the file size down, makes it easier to edit, and makes upgrades
-easier.
+has a huge set of default configuration values that it holds only in memory,
+and the configuration file only overrides these defaults.  When you customize a
+default setting, innotop notices, and then stores the customizations into the
+file.  This keeps the file size down, makes it easier to edit, and makes
+upgrades easier.
 .PP
-A configuration file can be made read\-only.  See \*(L"readonly\*(R".
+A configuration file is read-only be default.  You can override that with
+\&\*(L"\-\-write\*(R".  See \*(L"readonly\*(R".
 .PP
 The configuration file is arranged into sections like an \s-1INI\s0 file.  Each
 section begins with [section\-name] and ends with [/section\-name].  Each
@@ -799,7 +810,7 @@
 files are still useful, though.
 .PP
 The first line in the file is innotop's version number.  This lets innotop
-notice when the file format is not backwards\-compatible, and upgrade smoothly
+notice when the file format is not backwards-compatible, and upgrade smoothly
 without destroying your customized configuration.
 .PP
 The following list describes each section of the configuration file and the data
@@ -807,7 +818,7 @@
 .IP "general" 4
 .IX Item "general"
 The 'general' section contains global configuration variables and variables that
-may be mode\-specific, but don't belong in any other section.  The syntax is a
+may be mode-specific, but don't belong in any other section.  The syntax is a
 simple key=value list.  innotop writes a comment above each value to help you
 edit the file by hand.
 .RS 4
@@ -902,7 +913,8 @@
 \&\*(L"shorten\*(R", and \*(L"percent\*(R" transformations.
 .IP "num_status_sets" 4
 .IX Item "num_status_sets"
-Controls how many sets of status variables to display in pivoted \*(L"S: Variables & Status\*(R" mode.  It also controls the number of old sets of variables innotop
+Controls how many sets of status variables to display in pivoted \*(L"S: Variables
+& Status\*(R" mode.  It also controls the number of old sets of variables innotop
 keeps in its memory, so the larger this variable is, the more memory innotop
 uses.
 .IP "plugin_dir" 4
@@ -911,8 +923,7 @@
 \&'plugins' subdirectory of your innotop configuration directory.
 .IP "readonly" 4
 .IX Item "readonly"
-Whether the configuration file is readonly.  This cannot be set interactively,
-because it would prevent itself from being written to the configuration file.
+Whether the configuration file is readonly.  This cannot be set interactively.
 .IP "show_cxn_errors" 4
 .IX Item "show_cxn_errors"
 Makes innotop print connection errors to \s-1STDOUT\s0.  See \*(L"\s-1ERROR\s0 \s-1HANDLING\s0\*(R".
@@ -926,7 +937,8 @@
 transformation.
 .IP "show_statusbar" 4
 .IX Item "show_statusbar"
-Controls whether to show the status bar in the display.  See \*(L"\s-1INNOTOP\s0 \s-1STATUS\s0\*(R".
+Controls whether to show the status bar in the display.  See \*(L"\s-1INNOTOP\s0
+\&\s-1STATUS\s0\*(R".
 .IP "skip_innodb" 4
 .IX Item "skip_innodb"
 Disables fetching \s-1SHOW\s0 \s-1INNODB\s0 \s-1STATUS\s0, in case your server(s) do not have InnoDB
@@ -966,10 +978,12 @@
 name=quoted\-value list.
 .IP "connections" 4
 .IX Item "connections"
-This section holds the server connections you have defined.  Each line is in the
-format name=properties, where the properties are a name=value list.  The
-properties are self\-explanatory, and the only one that is treated specially is
-\&'pass' which is only present if 'savepass' is set.  See \*(L"\s-1SERVER\s0 \s-1CONNECTIONS\s0\*(R".
+This section holds the server connections you have defined.  Each line is in
+the format name=properties, where the properties are a name=value list.  The
+properties are self-explanatory, and the only one that is treated specially is
+\&'pass' which is only present if 'savepass' is set.  This section of the
+configuration file will be skipped if any \s-1DSN\s0, username, or password
+command-line options are used.  See \*(L"\s-1SERVER\s0 \s-1CONNECTIONS\s0\*(R".
 .IP "active_connections" 4
 .IX Item "active_connections"
 This section holds a list of which connections are active in each mode.  Each
@@ -1025,7 +1039,7 @@
 Choose which columns are in those tables, and create new columns.
 .IP "\(bu" 4
 Filter which rows display with built-in filters, user-defined filters, and
-quick\-filters.
+quick-filters.
 .IP "\(bu" 4
 Sort the rows to put important data first or group together related rows.
 .IP "\(bu" 4
@@ -1039,7 +1053,7 @@
 you unlimited flexibility.
 .PP
 All these and more are explained in the following sections.
-.Sh "\s-1TABLES\s0"
+.SS "\s-1TABLES\s0"
 .IX Subsection "TABLES"
 A table is what you'd expect: a collection of columns.  It also has some other
 properties, such as a caption.  Filters, sorting rules, and colorization rules
@@ -1192,7 +1206,7 @@
 .IP "wait_array" 4
 .IX Item "wait_array"
 Displays data about InnoDB's \s-1OS\s0 wait array.  Data source: \*(L"\s-1OS_WAIT_ARRAY\s0\*(R".
-.Sh "\s-1COLUMNS\s0"
+.SS "\s-1COLUMNS\s0"
 .IX Subsection "COLUMNS"
 Columns belong to tables.  You can choose a table's columns by pressing the '^'
 key, which starts the \*(L"\s-1TABLE\s0 \s-1EDITOR\s0\*(R" and lets you choose and edit columns.
@@ -1200,7 +1214,7 @@
 .IP "\(bu" 4
 hdr: a column header.  This appears in the first row of the table.
 .IP "\(bu" 4
-just: justification.  '\-' means left-justified and '' means right\-justified,
+just: justification.  '\-' means left-justified and '' means right-justified,
 just as with printf formatting codes (not a coincidence).
 .IP "\(bu" 4
 dec: whether to further align the column on the decimal point.
@@ -1230,12 +1244,12 @@
 the table.  Several columns are set this way, such as the count column on
 \&\*(L"processlist\*(R" and \*(L"innodb_transactions\*(R", so you don't see a count when the
 grouping isn't enabled, but you do when it is.
-.Sh "\s-1FILTERS\s0"
+.SS "\s-1FILTERS\s0"
 .IX Subsection "FILTERS"
 Filters remove rows from the display.  They behave much like a \s-1WHERE\s0 clause in
 \&\s-1SQL\s0.  innotop has several built-in filters, which remove irrelevant information
 like inactive queries, but you can define your own as well.  innotop also lets
-you create quick\-filters, which do not get saved to the configuration file, and
+you create quick-filters, which do not get saved to the configuration file, and
 are just an easy way to quickly view only some rows.
 .PP
 You can enable or disable a filter on any table.  Press the '%' key (mnemonic: %
@@ -1255,7 +1269,7 @@
 For example, imagine you want to filter the processlist table so you only see
 queries that have been running more than five minutes.  Type a new name for your
 filter, and when prompted for the subroutine body, press \s-1TAB\s0 to initiate your
-terminal's auto\-completion.  You'll see the names of the columns in the
+terminal's auto-completion.  You'll see the names of the columns in the
 \&\*(L"processlist\*(R" table (innotop generally tries to help you with auto-completion
 lists).  You want to filter on the 'time' column.  Type the text \*(L"$set\->{time} >
 300\*(R" to return true when the query is more than five minutes old.  That's all
@@ -1279,7 +1293,7 @@
 .IX Subsection "QUICK-FILTERS"
 .PP
 innotop's quick-filters are a shortcut to create a temporary filter that doesn't
-persist when you restart innotop.  To create a quick\-filter, press the '/' key.
+persist when you restart innotop.  To create a quick-filter, press the '/' key.
 innotop will prompt you for the column name and filter text.  Again, you can use
 auto-completion on column names.  The filter text can be just the text you want
 to \*(L"search for.\*(R"  For example, to filter the \*(L"processlist\*(R" table on queries
@@ -1292,9 +1306,9 @@
 filter that is otherwise like any other filter.  It just isn't saved to the
 configuration file.
 .PP
-To clear quick\-filters, press the '\e' key and innotop will clear them all at
+To clear quick-filters, press the '\e' key and innotop will clear them all at
 once.
-.Sh "\s-1SORTING\s0"
+.SS "\s-1SORTING\s0"
 .IX Subsection "SORTING"
 innotop has sensible built-in defaults to sort the most important rows to the
 top of the table.  Like anything else in innotop, you can customize how any
@@ -1311,9 +1325,9 @@
 .PP
 Some modes have keys mapped to open this dialog directly, and to quickly reverse
 sort direction.  Press '?' as usual to see which keys are mapped in any mode.
-.Sh "\s-1GROUPING\s0"
+.SS "\s-1GROUPING\s0"
 .IX Subsection "GROUPING"
-innotop can group, or aggregate, rows together (I use the terms
+innotop can group, or aggregate, rows together (the terms are used
 interchangeably).  This is quite similar to an \s-1SQL\s0 \s-1GROUP\s0 \s-1BY\s0 clause.  You can
 specify to group on certain columns, or if you don't specify any, the entire set
 of rows is treated as one group.  This is quite like \s-1SQL\s0 so far, but unlike \s-1SQL\s0,
@@ -1365,9 +1379,7 @@
 .PP
 .Vb 1
 \& Query List (? for help) localhost, 32:33, 0.11 QPS, 1 thd, 5.0.38\-log
-.Ve
-.PP
-.Vb 5
+\& 
 \& CXN        Cmd        Cnt  ID      User   Host           Time   Query       
 \& localhost  Query      49    12933  webusr localhost      19:38  SELECT * FROM
 \& localhost  Sending Da 23     2383  webusr localhost      12:43  SELECT col1,
@@ -1383,7 +1395,7 @@
 milliseconds to optimize queries.  You might not have seen this pattern if you
 didn't look at your connections in aggregate.  (This is a made-up example, but
 it can happen in real life).
-.Sh "\s-1PIVOTING\s0"
+.SS "\s-1PIVOTING\s0"
 .IX Subsection "PIVOTING"
 innotop can pivot a table for more compact display, similar to a Pivot Table in
 a spreadsheet (also known as a crosstab).  Pivoting a table makes columns into
@@ -1407,7 +1419,7 @@
 .PP
 To get reasonable results, you might need to group as well as pivoting.
 innotop currently does this for \*(L"S: Variables & Status\*(R" mode.
-.Sh "\s-1COLORS\s0"
+.SS "\s-1COLORS\s0"
 .IX Subsection "COLORS"
 By default, innotop highlights rows with color so you can see at a glance which
 rows are more important.  You can customize the colorization rules and add your
@@ -1451,7 +1463,7 @@
 for example in the first rule above, you should enter 'Locked' surrounded by
 quotes.  If you get an error message about a bareword, you probably should have
 quoted something.
-.Sh "\s-1EXPRESSIONS\s0"
+.SS "\s-1EXPRESSIONS\s0"
 .IX Subsection "EXPRESSIONS"
 Expressions are at the core of how innotop works, and are what enables you to
 extend innotop as you wish.  Recall the table lifecycle explained in
@@ -1486,7 +1498,8 @@
 \& }
 .Ve
 .PP
-Here's a concrete example, taken from the header table \*(L"q_header\*(R" in \*(L"Q: Query List\*(R" mode.  This expression calculates the qps, or Queries Per Second,
+Here's a concrete example, taken from the header table \*(L"q_header\*(R" in \*(L"Q:
+Query List\*(R" mode.  This expression calculates the qps, or Queries Per Second,
 column's values, from the values returned by \s-1SHOW\s0 \s-1STATUS:\s0
 .PP
 .Vb 1
@@ -1511,7 +1524,7 @@
 Every column in innotop is computed by subroutines compiled in the same fashion.
 There is no difference between innotop's built-in columns and user-defined
 columns.  This keeps things consistent and predictable.
-.Sh "\s-1TRANSFORMATIONS\s0"
+.SS "\s-1TRANSFORMATIONS\s0"
 .IX Subsection "TRANSFORMATIONS"
 Transformations change how a value is rendered.  For example, they can take a
 number of seconds and display it in H:M:S format.  The following transformations
@@ -1548,7 +1561,7 @@
 .IX Item "shorten"
 Formats a number as a unit of 1024 (k/M/G/T) and with \*(L"num_digits\*(R" number of
 digits after the decimal point.
-.Sh "\s-1TABLE\s0 \s-1EDITOR\s0"
+.SS "\s-1TABLE\s0 \s-1EDITOR\s0"
 .IX Subsection "TABLE EDITOR"
 The innotop table editor lets you customize tables with keystrokes.  You start
 the table editor with the '^' key.  If there's more than one table on the
@@ -1557,9 +1570,7 @@
 .PP
 .Vb 1
 \& Editing table definition for Buffer Pool.  Press ? for help, q to quit.
-.Ve
-.PP
-.Vb 9
+\& 
 \& name               hdr          label                  src          
 \& cxn                CXN          Connection from which  cxn          
 \& buf_pool_size      Size         Buffer pool size       IB_bp_buf_poo
@@ -1568,7 +1579,7 @@
 \& pages_modified     Dirty Pages  Pages modified (dirty  IB_bp_pages_m
 \& buf_pool_hit_rate  Hit Rate     Buffer pool hit rate   IB_bp_buf_poo
 \& total_mem_alloc    Memory       Total memory allocate  IB_bp_total_m
-\& add_pool_alloc     Add\(aql Pool   Additonal pool alloca  IB_bp_add_poo
+\& add_pool_alloc     Add\*(Aql Pool   Additonal pool alloca  IB_bp_add_poo
 .Ve
 .PP
 The first line shows which table you're editing, and reminds you again to press
@@ -1581,7 +1592,7 @@
 with a couple of its properties such as its header and source expression (see
 \&\*(L"\s-1EXPRESSIONS\s0\*(R").
 .PP
-The key mappings are Vim\-style, as in many other places.  Pressing 'j' and 'k'
+The key mappings are Vim-style, as in many other places.  Pressing 'j' and 'k'
 moves the highlight up or down.  You can then (d)elete or (e)dit the highlighted
 column.  You can also (a)dd a column to the table.  This actually just activates
 one of the columns already defined for the table; it prompts you to choose from
@@ -1601,7 +1612,7 @@
 .IP "\(bu" 4
 The column header: this is the label that appears at the top of the column, in
 the table header.  This can have spaces and funny characters, but be careful not
-to make it too wide and waste space on\-screen.
+to make it too wide and waste space on-screen.
 .IP "\(bu" 4
 The column's data source: this is an expression that determines what data from
 the source (see \*(L"\s-1TABLES\s0\*(R") innotop will put into the column.  This can just be
@@ -1680,7 +1691,7 @@
 .IX Header "PLUGINS"
 innotop has a simple but powerful plugin mechanism by which you can extend
 or modify its existing functionality, and add new functionality.  innotop's
-plugin functionality is event\-based: plugins register themselves to be called
+plugin functionality is event-based: plugins register themselves to be called
 when events happen.  They then have a chance to influence the event.
 .PP
 An innotop plugin is a Perl module placed in innotop's \*(L"plugin_dir\*(R"
@@ -1692,7 +1703,7 @@
 The module must conform to innotop's plugin interface.  Additionally, the source
 code of the module must be written in such a way that innotop can inspect the
 file and determine the package name and description.
-.Sh "Package Source Convention"
+.SS "Package Source Convention"
 .IX Subsection "Package Source Convention"
 innotop inspects the plugin module's source to determine the Perl package name.
 It looks for a line of the form \*(L"package Foo;\*(R" and if found, considers the
@@ -1700,10 +1711,10 @@
 package name, with double semicolons and so on.
 .PP
 It also looks for a description in the source code, to make the plugin editor
-more human\-friendly.  The description is a comment line of the form \*(L"#
+more human-friendly.  The description is a comment line of the form \*(L"#
 description: Foo\*(R", where \*(L"Foo\*(R" is the text innotop will consider to be the
 plugin's description.
-.Sh "Plugin Interface"
+.SS "Plugin Interface"
 .IX Subsection "Plugin Interface"
 The innotop plugin interface is quite simple: innotop expects the plugin to be
 an object-oriented module it can call certain methods on.  The methods are
@@ -1736,7 +1747,7 @@
 \&\fIregister_for_events()\fR, it must have \fIfoo()\fR and \fIbar()\fR methods.  These methods are
 callbacks for the events.  See \*(L"Plugin Events\*(R" for more details about each
 event.
-.Sh "Plugin Variables"
+.SS "Plugin Variables"
 .IX Subsection "Plugin Variables"
 The plugin's constructor is passed a hash of innotop's variables, which it can
 manipulate.  It is probably a good idea if the plugin object saves a copy of it
@@ -1744,7 +1755,7 @@
 \&\f(CW%pluggable_vars\fR, and are as follows:
 .IP "action_for" 4
 .IX Item "action_for"
-A hashref of key mappings.  These are innotop's global hot\-keys.
+A hashref of key mappings.  These are innotop's global hot-keys.
 .IP "agg_funcs" 4
 .IX Item "agg_funcs"
 A hashref of functions that can be used for grouping.  See \*(L"\s-1GROUPING\s0\*(R".
@@ -1770,7 +1781,7 @@
 A hashref of server groups.  See \*(L"\s-1SERVER\s0 \s-1GROUPS\s0\*(R".
 .IP "tbl_meta" 4
 .IX Item "tbl_meta"
-A hashref of innotop's table meta\-data, with one entry per table (see
+A hashref of innotop's table meta-data, with one entry per table (see
 \&\*(L"\s-1TABLES\s0\*(R" for more information).
 .IP "trans_funcs" 4
 .IX Item "trans_funcs"
@@ -1778,13 +1789,13 @@
 .IP "var_sets" 4
 .IX Item "var_sets"
 A hashref of variable sets.  See \*(L"\s-1VARIABLE\s0 \s-1SETS\s0\*(R".
-.Sh "Plugin Events"
+.SS "Plugin Events"
 .IX Subsection "Plugin Events"
 Each event is defined somewhere in the innotop source code.  When innotop runs
 that code, it executes the callback function for each plugin that expressed its
 interest in the event.  innotop passes some data for each event.  The events are
 defined in the \f(CW%event_listener_for\fR variable, and are as follows:
-.ie n .IP "extract_values($set, $cur\fR, \f(CW$pre\fR, \f(CW$tbl)" 4
+.ie n .IP "extract_values($set, $cur, $pre, $tbl)" 4
 .el .IP "extract_values($set, \f(CW$cur\fR, \f(CW$pre\fR, \f(CW$tbl\fR)" 4
 .IX Item "extract_values($set, $cur, $pre, $tbl)"
 This event occurs inside the function that extracts values from a data source.
@@ -1803,7 +1814,7 @@
 .IX Item "draw_screen($lines)"
 This event occurs inside the subroutine that prints the lines to the screen.
 \&\f(CW$lines\fR is an arrayref of strings.
-.Sh "Simple Plugin Example"
+.SS "Simple Plugin Example"
 .IX Subsection "Simple Plugin Example"
 The easiest way to explain the plugin functionality is probably with a simple
 example.  The following module adds a column to the beginning of every table and
@@ -1811,75 +1822,59 @@
 .PP
 .Vb 2
 \& use strict;
-\& use warnings FATAL => \(aqall\(aq;
-.Ve
-.PP
-.Vb 2
+\& use warnings FATAL => \*(Aqall\*(Aq;
+\& 
 \& package Innotop::Plugin::Example;
-\& # description: Adds an \(aqexample\(aq column to every table
-.Ve
-.PP
-.Vb 4
+\& # description: Adds an \*(Aqexample\*(Aq column to every table
+\& 
 \& sub new {
 \&    my ( $class, %vars ) = @_;
-\&    # Store reference to innotop\(aqs variables in $self
+\&    # Store reference to innotop\*(Aqs variables in $self
 \&    my $self = bless { %vars }, $class;
-.Ve
-.PP
-.Vb 11
+\& 
 \&    # Design the example column
 \&    my $col = {
-\&       hdr   => \(aqExample\(aq,
-\&       just  => \(aq\(aq,
+\&       hdr   => \*(AqExample\*(Aq,
+\&       just  => \*(Aq\*(Aq,
 \&       dec   => 0,
 \&       num   => 1,
-\&       label => \(aqExample\(aq,
-\&       src   => \(aqexample\(aq, # Get data from this column in the data source
-\&       tbl   => \(aq\(aq,
+\&       label => \*(AqExample\*(Aq,
+\&       src   => \*(Aqexample\*(Aq, # Get data from this column in the data source
+\&       tbl   => \*(Aq\*(Aq,
 \&       trans => [],
 \&    };
-.Ve
-.PP
-.Vb 8
+\& 
 \&    # Add the column to every table.
 \&    my $tbl_meta = $vars{tbl_meta};
 \&    foreach my $tbl ( values %$tbl_meta ) {
 \&       # Add the column to the list of defined columns
 \&       $tbl\->{cols}\->{example} = $col;
 \&       # Add the column to the list of visible columns
-\&       unshift @{$tbl\->{visible}}, \(aqexample\(aq;
+\&       unshift @{$tbl\->{visible}}, \*(Aqexample\*(Aq;
 \&    }
-.Ve
-.PP
-.Vb 3
+\& 
 \&    # Be sure to return a reference to the object.
 \&    return $self;
 \& }
-.Ve
-.PP
-.Vb 5
-\& # I\(aqd like to be called when a data set is being rendered into a table, please.
+\& 
+\& # I\*(Aqd like to be called when a data set is being rendered into a table, please.
 \& sub register_for_events {
 \&    my ( $self ) = @_;
 \&    return qw(set_to_tbl_pre_filter);
 \& }
-.Ve
-.PP
-.Vb 8
+\& 
 \& # This method will be called when the event fires.
 \& sub set_to_tbl_pre_filter {
 \&    my ( $self, $rows, $tbl ) = @_;
-\&    # Set the example column\(aqs data source to the value 1.
+\&    # Set the example column\*(Aqs data source to the value 1.
 \&    foreach my $row ( @$rows ) {
 \&       $row\->{example} = 1;
 \&    }
 \& }
-.Ve
-.PP
-.Vb 1
+\& 
 \& 1;
 .Ve
-.Sh "Plugin Editor"
+.SS "Plugin Editor"
 .IX Subsection "Plugin Editor"
 The plugin editor lets you view the plugins innotop discovered and activate or
 deactivate them.  Start the editor by pressing $ to start the configuration
@@ -1920,7 +1915,7 @@
 Whenever innotop fetches data from MySQL, it adds two extra bits to each set:
 cxn and Uptime_hires.  cxn is the name of the connection from which the data
 came.  Uptime_hires is a high-resolution version of the server's Uptime status
-variable, which is important if your \*(L"interval\*(R" setting is sub\-second.
+variable, which is important if your \*(L"interval\*(R" setting is sub-second.
 .PP
 Here are the kinds of data sources from which data is extracted:
 .IP "\s-1STATUS_VARIABLES\s0" 4
@@ -2002,14 +1997,15 @@
 I don't know for sure.  It also runs on Windows under ActivePerl without
 problem.
 .PP
-I use innotop on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26, 5.1.15,
-and 5.2.3.  If it doesn't run correctly for you, that is a bug and I hope you
-report it.
+innotop has been used on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26,
+5.1.15, and 5.2.3.  If it doesn't run correctly for you, that is a bug that
+should be reported.
 .SH "FILES"
 .IX Header "FILES"
-$HOMEDIR/.innotop is used to store configuration information.  Files include the
-configuration file innotop.ini, the core_dump file which contains verbose error
-messages if \*(L"debug\*(R" is enabled, and the plugins/ subdirectory.
+\&\f(CW$HOMEDIR\fR/.innotop and/or /etc/innotop are used to store
+configuration information.  Files include the configuration file innotop.conf,
+the core_dump file which contains verbose error messages if \*(L"debug\*(R" is
+enabled, and the plugins/ subdirectory.
 .SH "GLOSSARY OF TERMS"
 .IX Header "GLOSSARY OF TERMS"
 .IP "tick" 4
@@ -2018,8 +2014,8 @@
 displays it.
 .SH "ACKNOWLEDGEMENTS"
 .IX Header "ACKNOWLEDGEMENTS"
-I'm grateful to the following people for various reasons, and hope I haven't
-forgotten to include anyone:
+The following people and organizations are acknowledged for various reasons.
+Hopefully no one has been forgotten.
 .PP
 Allen K. Smith,
 Aurimas Mikalauskas,
@@ -2032,6 +2028,7 @@
 Dr. Frank Ullrich,
 Giuseppe Maxia,
 Google.com Site Reliability Engineers,
+Google Code,
 Jan Pieter Kunst,
 Jari Aalto,
 Jay Pipes,
@@ -2049,9 +2046,9 @@
 The Gentoo MySQL Team,
 Trevor Price,
 Yaar Schnitman,
-and probably more people I've neglected to include.
+and probably more people that have not been included.
 .PP
-(If I misspelled your name, it's probably because I'm afraid of putting
+(If your name has been misspelled, it's probably out of fear of putting
 international characters into this documentation; earlier versions of Perl might
 not be able to compile it then).
 .SH "COPYRIGHT, LICENSE AND WARRANTY"
@@ -2076,11 +2073,12 @@
 Execute innotop and press '!' to see this information at any time.
 .SH "AUTHOR"
 .IX Header "AUTHOR"
-Baron Schwartz.
+Originally written by Baron Schwartz; currently maintained by Aaron Racine.
 .SH "BUGS"
 .IX Header "BUGS"
 You can report bugs, ask for improvements, and get other help and support at
-<http://sourceforge.net/projects/innotop>.  There are mailing lists, forums,
-a bug tracker, etc.  Please use these instead of contacting me directly, as it
-makes my job easier and benefits others if the discussions are permanent and
-public.  Of course, if you need to contact me in private, please do.
+<http://code.google.com/p/innotop/>.  There are mailing lists, a source code
+browser, a bug tracker, etc.  Please use these instead of contacting the
+maintainer or author directly, as it makes our job easier and benefits others if the
+discussions are permanent and public.  Of course, if you need to contact us in
+private, please do.

Modified: mysql-dfsg-5.1/branches/experimental/debian/rules
===================================================================
--- mysql-dfsg-5.1/branches/experimental/debian/rules	2009-07-22 17:36:02 UTC (rev 1649)
+++ mysql-dfsg-5.1/branches/experimental/debian/rules	2009-07-22 18:28:54 UTC (rev 1650)
@@ -188,7 +188,7 @@
 	@echo "RULES.$@"
 	dh_testdir
 	dh_testroot
-	dh_clean -k
+	dh_prep
 	dh_installdirs
 
 	# some self written manpages which hopefully
@@ -235,7 +235,6 @@
 	install -m 0755 debian/additions/mysqlreport $(TMP)/usr/bin/
 	install -m 0755 debian/additions/innotop/innotop $(TMP)/usr/bin/
 	install -m 0644 debian/additions/innotop/innotop.1 $(TMP)/usr/share/man/man1/
-	install -m 0644 -D debian/additions/innotop/InnoDBParser.pm $(TMP)/usr/share/perl5/InnoDBParser.pm
 
 	# mysql-server
 	install -m 0755 $(BUILDDIR)/scripts/mysqld_safe $(TMP)/usr/bin/mysqld_safe




More information about the Pkg-mysql-commits mailing list