4825
4817
# ###########################################################################
4827
4819
# ###########################################################################
4828
# SysLogParser package
4829
# This package is a copy without comments from the original. The original
4830
# with comments and its test file can be found in the Bazaar repository at,
4831
# lib/SysLogParser.pm
4832
# t/lib/SysLogParser.t
4833
# See https://launchpad.net/percona-toolkit for more information.
4834
# ###########################################################################
4836
package SysLogParser;
4839
use warnings FATAL => 'all';
4840
use English qw(-no_match_vars);
4841
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4843
my $syslog_regex = qr{\A.*\w+\[\d+\]: \[(\d+)-(\d+)\] (.*)\Z};
4848
return bless $self, $class;
4852
my ( $self, %args ) = @_;
4853
my ( $next_event, $tell, $is_syslog ) = $self->generate_wrappers(%args);
4854
return $next_event->();
4857
sub generate_wrappers {
4858
my ( $self, %args ) = @_;
4860
if ( ($self->{sanity} || '') ne "$args{next_event}" ){
4861
PTDEBUG && _d("Clearing and recreating internal state");
4862
@{$self}{qw(next_event tell is_syslog)} = $self->make_closures(%args);
4863
$self->{sanity} = "$args{next_event}";
4866
return @{$self}{qw(next_event tell is_syslog)};
4870
my ( $self, %args ) = @_;
4872
my $next_event = $args{'next_event'};
4873
my $tell = $args{'tell'};
4874
my $new_event_test = $args{'misc'}->{'new_event_test'};
4875
my $line_filter = $args{'misc'}->{'line_filter'};
4877
my $test_line = $next_event->();
4878
PTDEBUG && _d('Read first sample/test line:', $test_line);
4880
if ( defined $test_line && $test_line =~ m/$syslog_regex/o ) {
4882
PTDEBUG && _d('This looks like a syslog line, PTDEBUG prefix=LLSP');
4884
my ($msg_nr, $line_nr, $content) = $test_line =~ m/$syslog_regex/o;
4885
my @pending = ($test_line);
4886
my $last_msg_nr = $msg_nr;
4889
my $new_next_event = sub {
4890
PTDEBUG && _d('LLSP: next_event()');
4892
PTDEBUG && _d('LLSP: Current virtual $fh position:', $pos_in_log);
4900
defined($line = shift @pending)
4902
eval { $new_pos = -1; $new_pos = $tell->() };
4903
defined($line = $next_event->());
4906
PTDEBUG && _d('LLSP: Line:', $line);
4908
($msg_nr, $line_nr, $content) = $line =~ m/$syslog_regex/o;
4910
die "Can't parse line: $line";
4913
elsif ( $msg_nr != $last_msg_nr ) {
4914
PTDEBUG && _d('LLSP: $msg_nr', $last_msg_nr, '=>', $msg_nr);
4915
$last_msg_nr = $msg_nr;
4919
elsif ( @arg_lines && $new_event_test && $new_event_test->($content) ) {
4920
PTDEBUG && _d('LLSP: $new_event_test matches');
4924
$content =~ s/#(\d{3})/chr(oct($1))/ge;
4925
$content =~ s/\^I/\t/g;
4926
if ( $line_filter ) {
4927
PTDEBUG && _d('LLSP: applying $line_filter');
4928
$content = $line_filter->($content);
4931
push @arg_lines, $content;
4933
PTDEBUG && _d('LLSP: Exited while-loop after finding a complete entry');
4935
my $psql_log_event = @arg_lines ? join('', @arg_lines) : undef;
4936
PTDEBUG && _d('LLSP: Final log entry:', $psql_log_event);
4938
if ( defined $line ) {
4939
PTDEBUG && _d('LLSP: Saving $line:', $line);
4941
PTDEBUG && _d('LLSP: $pos_in_log:', $pos_in_log, '=>', $new_pos);
4942
$pos_in_log = $new_pos;
4945
PTDEBUG && _d('LLSP: EOF reached');
4950
return $psql_log_event;
4953
my $new_tell = sub {
4954
PTDEBUG && _d('LLSP: tell()', $pos_in_log);
4958
return ($new_next_event, $new_tell, 1);
4963
PTDEBUG && _d('Plain log, or we are at EOF; PTDEBUG prefix=PLAIN');
4965
my @pending = defined $test_line ? ($test_line) : ();
4967
my $new_next_event = sub {
4968
PTDEBUG && _d('PLAIN: next_event(); @pending:', scalar @pending);
4969
return @pending ? shift @pending : $next_event->();
4971
my $new_tell = sub {
4972
PTDEBUG && _d('PLAIN: tell(); @pending:', scalar @pending);
4973
return @pending ? 0 : $tell->();
4975
return ($new_next_event, $new_tell, 0);
4980
my ($package, undef, $line) = caller 0;
4981
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4982
map { defined $_ ? $_ : 'undef' }
4984
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4989
# ###########################################################################
4990
# End SysLogParser package
4991
# ###########################################################################
4993
# ###########################################################################
4994
# PgLogParser package
4995
# This package is a copy without comments from the original. The original
4996
# with comments and its test file can be found in the Bazaar repository at,
4997
# lib/PgLogParser.pm
4998
# t/lib/PgLogParser.t
4999
# See https://launchpad.net/percona-toolkit for more information.
5000
# ###########################################################################
5002
package PgLogParser;
5005
use warnings FATAL => 'all';
5006
use English qw(-no_match_vars);
5007
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
5010
$Data::Dumper::Indent = 1;
5011
$Data::Dumper::Sortkeys = 1;
5012
$Data::Dumper::Quotekeys = 0;
5014
my $log_line_regex = qr{
5015
(LOG|DEBUG|CONTEXT|WARNING|ERROR|FATAL|PANIC|HINT
5016
|DETAIL|NOTICE|STATEMENT|INFO|LOCATION)
5020
my %attrib_name_for = (
5023
r => 'host', # With port
5027
m => 'ts', # With milliseconds
5041
next_event => undef,
5044
return bless $self, $class;
5048
my ( $self, %args ) = @_;
5049
my @required_args = qw(next_event tell);
5050
foreach my $arg ( @required_args ) {
5051
die "I need a $arg argument" unless $args{$arg};
5054
my ( $next_event, $tell, $is_syslog ) = $self->generate_wrappers(%args);
5056
my @properties = ();
5058
my ($pos_in_log, $line, $was_pending) = $self->get_line();
5067
if ( !$was_pending && (!defined $line || $line !~ m/$log_line_regex/o) ) {
5068
PTDEBUG && _d('Skipping lines until I find a header');
5073
($new_pos, $line) = $self->get_line();
5077
if ( $line =~ m/$log_line_regex/o ) {
5078
$pos_in_log = $new_pos;
5082
PTDEBUG && _d('Line was not a header, will fetch another');
5085
PTDEBUG && _d('Found a header line, now at pos_in_line', $pos_in_log);
5093
while ( !$done && defined $line ) {
5095
chomp $line unless $is_syslog;
5097
if ( (($line_type) = $line =~ m/$log_line_regex/o) && $line_type ne 'LOG' ) {
5100
PTDEBUG && _d('Found a non-LOG line, exiting loop');
5105
$first_line ||= $line;
5107
if ( my ($e) = $line =~ m/ERROR:\s+(\S.*)\Z/s ) {
5108
push @properties, 'Error_msg', $e;
5109
PTDEBUG && _d('Found an error msg, saving and continuing');
5110
($new_pos, $line) = $self->get_line();
5114
elsif ( my ($s) = $line =~ m/STATEMENT:\s+(\S.*)\Z/s ) {
5115
push @properties, 'arg', $s, 'cmd', 'Query';
5116
PTDEBUG && _d('Found a statement, finishing up event');
5122
PTDEBUG && _d("I don't know what to do with this line");
5130
Address\sfamily\snot\ssupported\sby\sprotocol
5131
|archived\stransaction\slog\sfile
5132
|autovacuum:\sprocessing\sdatabase
5133
|checkpoint\srecord\sis\sat
5134
|checkpoints\sare\soccurring\stoo\sfrequently\s\(
5135
|could\snot\sreceive\sdata\sfrom\sclient
5136
|database\ssystem\sis\sready
5137
|database\ssystem\sis\sshut\sdown
5138
|database\ssystem\swas\sshut\sdown
5139
|incomplete\sstartup\spacket
5140
|invalid\slength\sof\sstartup\spacket
5142
|next\stransaction\sID:
5143
|received\ssmart\sshutdown\srequest
5144
|recycled\stransaction\slog\sfile
5145
|redo\srecord\sis\sat
5147
|removing\stransaction\slog\sfile\s"
5149
|transaction\sID\swrap\slimit\sis
5152
PTDEBUG && _d('Skipping this line because it matches skip-pattern');
5153
($new_pos, $line) = $self->get_line();
5157
$first_line ||= $line;
5159
if ( $line !~ m/$log_line_regex/o && @arg_lines ) {
5161
if ( !$is_syslog ) {
5162
$line =~ s/\A\t?/\n/;
5165
push @arg_lines, $line;
5166
PTDEBUG && _d('This was a continuation line');
5170
my ( $sev, $label, $rest )
5171
= $line =~ m/$log_line_regex(.+?):\s+(.*)\Z/so
5173
PTDEBUG && _d('Line is case 1 or case 3');
5177
PTDEBUG && _d('There are saved @arg_lines, we are done');
5179
if ( $label eq 'duration' && $rest =~ m/[0-9.]+\s+\S+\Z/ ) {
5180
if ( $got_duration ) {
5181
PTDEBUG && _d('Discarding line, duration already found');
5184
push @properties, 'Query_time', $self->duration_to_secs($rest);
5185
PTDEBUG && _d("Line's duration is for previous event:", $rest);
5189
$self->pending($new_pos, $line);
5190
PTDEBUG && _d('Deferred line');
5194
elsif ( $label =~ m/\A(?:duration|statement|query)\Z/ ) {
5195
PTDEBUG && _d('Case 1: start a multi-line event');
5197
if ( $label eq 'duration' ) {
5201
= $rest =~ m/([0-9.]+ \S+)\s+(?:statement|query): *(.*)\Z/s)
5203
push @properties, 'Query_time', $self->duration_to_secs($dur);
5205
push @arg_lines, $stmt;
5206
PTDEBUG && _d('Duration + statement');
5210
$first_line = undef;
5211
($pos_in_log, $line) = $self->get_line();
5212
PTDEBUG && _d('Line applies to event we never saw, discarding');
5217
push @arg_lines, $rest;
5218
PTDEBUG && _d('Putting onto @arg_lines');
5224
PTDEBUG && _d('Line is case 3, event is done');
5227
$self->pending($new_pos, $line);
5228
PTDEBUG && _d('There was @arg_lines, putting line to pending');
5232
PTDEBUG && _d('No need to defer, process event from this line now');
5233
push @properties, 'cmd', 'Admin', 'arg', $label;
5235
if ( $label =~ m/\A(?:dis)?connection(?: received| authorized)?\Z/ ) {
5236
push @properties, $self->get_meta($rest);
5240
die "I don't understand line $line";
5249
die "I don't understand line $line";
5253
($new_pos, $line) = $self->get_line();
5257
if ( !defined $line ) {
5258
PTDEBUG && _d('Line not defined, at EOF; calling oktorun(0) if exists');
5259
$args{oktorun}->(0) if $args{oktorun};
5260
if ( !@arg_lines ) {
5261
PTDEBUG && _d('No saved @arg_lines either, we are all done');
5266
if ( $line_type && $line_type ne 'LOG' ) {
5267
PTDEBUG && _d('Line is not a LOG line');
5269
if ( $line_type eq 'ERROR' ) {
5270
PTDEBUG && _d('Line is ERROR');
5273
PTDEBUG && _d('There is @arg_lines, will peek ahead one line');
5274
my ( $temp_pos, $temp_line ) = $self->get_line();
5278
&& ( ($type, $msg) = $temp_line =~ m/$log_line_regex(.*)/o )
5279
&& ( $type ne 'STATEMENT' || $msg eq $arg_lines[-1] )
5281
PTDEBUG && _d('Error/statement line pertain to current event');
5282
push @properties, 'Error_msg', $line =~ m/ERROR:\s*(\S.*)\Z/s;
5283
if ( $type ne 'STATEMENT' ) {
5284
PTDEBUG && _d('Must save peeked line, it is a', $type);
5285
$self->pending($temp_pos, $temp_line);
5288
elsif ( defined $temp_line && defined $type ) {
5289
PTDEBUG && _d('Error/statement line are a new event');
5290
$self->pending($new_pos, $line);
5291
$self->pending($temp_pos, $temp_line);
5294
PTDEBUG && _d("Unknown line", $line);
5299
PTDEBUG && _d("Unknown line", $line);
5303
if ( $done || @arg_lines ) {
5304
PTDEBUG && _d('Making event');
5306
push @properties, 'pos_in_log', $pos_in_log;
5309
PTDEBUG && _d('Assembling @arg_lines: ', scalar @arg_lines);
5310
push @properties, 'arg', join('', @arg_lines), 'cmd', 'Query';
5313
if ( $first_line ) {
5314
if ( my ($ts) = $first_line =~ m/([0-9-]{10} [0-9:.]{8,12})/ ) {
5315
PTDEBUG && _d('Getting timestamp', $ts);
5316
push @properties, 'ts', $ts;
5319
if ( my ($meta) = $first_line =~ m/(.*?)[A-Z]{3,}: / ) {
5320
PTDEBUG && _d('Found a meta-data chunk:', $meta);
5321
push @properties, $self->get_meta($meta);
5325
PTDEBUG && _d('Properties of event:', Dumper(\@properties));
5326
my $event = { @properties };
5327
$event->{bytes} = length($event->{arg} || '');
5334
my ( $self, $meta ) = @_;
5336
foreach my $set ( $meta =~ m/(\w+=[^, ]+)/g ) {
5337
my ($key, $val) = split(/=/, $set);
5338
if ( $key && $val ) {
5339
if ( my $prop = $attrib_name_for{lc substr($key, 0, 1)} ) {
5340
push @properties, $prop, $val;
5343
PTDEBUG && _d('Bad meta key', $set);
5347
PTDEBUG && _d("Can't figure out meta from", $set);
5355
my ($pos, $line, $was_pending) = $self->pending;
5356
if ( ! defined $line ) {
5357
PTDEBUG && _d('Got nothing from pending, trying the $fh');
5358
my ( $next_event, $tell) = @{$self}{qw(next_event tell)};
5361
$line = $next_event->();
5363
if ( PTDEBUG && $EVAL_ERROR ) {
5368
PTDEBUG && _d('Got pos/line:', $pos, $line);
5369
return ($pos, $line);
5373
my ( $self, $val, $pos_in_log ) = @_;
5375
PTDEBUG && _d('In sub pending, val:', $val);
5377
push @{$self->{pending}}, [$val, $pos_in_log];
5379
elsif ( @{$self->{pending}} ) {
5380
($val, $pos_in_log) = @{ shift @{$self->{pending}} };
5383
PTDEBUG && _d('Return from pending:', $val, $pos_in_log);
5384
return ($val, $pos_in_log, $was_pending);
5387
sub generate_wrappers {
5388
my ( $self, %args ) = @_;
5390
if ( ($self->{sanity} || '') ne "$args{next_event}" ){
5391
PTDEBUG && _d("Clearing and recreating internal state");
5392
eval { require SysLogParser; }; # Required for tests to work.
5393
my $sl = new SysLogParser();
5395
$args{misc}->{new_event_test} = sub {
5396
my ( $content ) = @_;
5397
return unless defined $content;
5398
return $content =~ m/$log_line_regex/o;
5401
$args{misc}->{line_filter} = sub {
5402
my ( $content ) = @_;
5403
$content =~ s/\A\t/\n/;
5407
@{$self}{qw(next_event tell is_syslog)} = $sl->make_closures(%args);
5408
$self->{sanity} = "$args{next_event}";
5411
return @{$self}{qw(next_event tell is_syslog)};
5414
sub duration_to_secs {
5415
my ( $self, $str ) = @_;
5416
PTDEBUG && _d('Duration:', $str);
5417
my ( $num, $suf ) = split(/\s+/, $str);
5418
my $factor = $suf eq 'ms' ? 1000
5420
: die("Unknown suffix '$suf'");
5421
return $num / $factor;
5425
my ($package, undef, $line) = caller 0;
5426
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
5427
map { defined $_ ? $_ : 'undef' }
5429
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
5434
# ###########################################################################
5435
# End PgLogParser package
5436
# ###########################################################################
5438
# ###########################################################################
5439
4820
# SlowLogParser package
5440
4821
# This package is a copy without comments from the original. The original
5441
4822
# with comments and its test file can be found in the Bazaar repository at,
9694
9073
# ###########################################################################
9696
9075
# ###########################################################################
9697
# MemcachedProtocolParser package
9698
# This package is a copy without comments from the original. The original
9699
# with comments and its test file can be found in the Bazaar repository at,
9700
# lib/MemcachedProtocolParser.pm
9701
# t/lib/MemcachedProtocolParser.t
9702
# See https://launchpad.net/percona-toolkit for more information.
9703
# ###########################################################################
9705
package MemcachedProtocolParser;
9708
use warnings FATAL => 'all';
9709
use English qw(-no_match_vars);
9712
$Data::Dumper::Indent = 1;
9713
$Data::Dumper::Sortkeys = 1;
9714
$Data::Dumper::Quotekeys = 0;
9716
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
9719
my ( $class, %args ) = @_;
9722
server => $args{server},
9723
port => $args{port} || '11211',
9727
return bless $self, $class;
9731
my ( $self, %args ) = @_;
9732
my @required_args = qw(event);
9733
foreach my $arg ( @required_args ) {
9734
die "I need a $arg argument" unless $args{$arg};
9736
my $packet = @args{@required_args};
9738
if ( $packet->{data_len} == 0 ) {
9739
PTDEBUG && _d('No TCP data');
9740
$args{stats}->{no_tcp_data}++ if $args{stats};
9744
my $src_host = "$packet->{src_host}:$packet->{src_port}";
9745
my $dst_host = "$packet->{dst_host}:$packet->{dst_port}";
9747
if ( my $server = $self->{server} ) { # Watch only the given server.
9748
$server .= ":$self->{port}";
9749
if ( $src_host ne $server && $dst_host ne $server ) {
9750
PTDEBUG && _d('Packet is not to or from', $server);
9751
$args{stats}->{not_watched_server}++ if $args{stats};
9758
if ( $src_host =~ m/:$self->{port}$/ ) {
9759
$packet_from = 'server';
9760
$client = $dst_host;
9762
elsif ( $dst_host =~ m/:$self->{port}$/ ) {
9763
$packet_from = 'client';
9764
$client = $src_host;
9767
warn 'Packet is not to or from memcached server: ', Dumper($packet);
9770
PTDEBUG && _d('Client:', $client);
9772
if ( !exists $self->{sessions}->{$client} ) {
9773
PTDEBUG && _d('New session');
9774
$self->{sessions}->{$client} = {
9780
my $session = $self->{sessions}->{$client};
9782
push @{$session->{raw_packets}}, $packet->{raw_packet};
9784
$packet->{data} = pack('H*', $packet->{data});
9786
if ( $packet_from eq 'server' ) {
9787
$event = $self->_packet_from_server($packet, $session, %args);
9789
elsif ( $packet_from eq 'client' ) {
9790
$event = $self->_packet_from_client($packet, $session, %args);
9793
$args{stats}->{unknown_packet_origin}++ if $args{stats};
9794
die 'Packet origin unknown';
9797
PTDEBUG && _d('Done with packet; event:', Dumper($event));
9798
$args{stats}->{events_parsed}++ if $args{stats};
9802
sub _packet_from_server {
9803
my ( $self, $packet, $session, %args ) = @_;
9804
die "I need a packet" unless $packet;
9805
die "I need a session" unless $session;
9807
PTDEBUG && _d('Packet is from server; client state:', $session->{state});
9809
my $data = $packet->{data};
9811
if ( !$session->{state} ) {
9812
PTDEBUG && _d('Ignoring mid-stream server response');
9813
$args{stats}->{ignored_midstream_server_response}++ if $args{stats};
9817
if ( $session->{state} eq 'awaiting reply' ) {
9818
PTDEBUG && _d('State is awaiting reply');
9819
my ($line1, $rest) = $packet->{data} =~ m/\A(.*?)\r\n(.*)?/s;
9821
$args{stats}->{unknown_server_data}++ if $args{stats};
9822
die "Unknown memcached data from server";
9825
my @vals = $line1 =~ m/(\S+)/g;
9826
$session->{res} = shift @vals;
9827
PTDEBUG && _d('Result of last', $session->{cmd}, 'cmd:', $session->{res});
9829
if ( $session->{cmd} eq 'incr' || $session->{cmd} eq 'decr' ) {
9830
PTDEBUG && _d('It is an incr or decr');
9831
if ( $session->{res} !~ m/\D/ ) { # It's an integer, not an error
9832
PTDEBUG && _d('Got a value for the incr/decr');
9833
$session->{val} = $session->{res};
9834
$session->{res} = '';
9837
elsif ( $session->{res} eq 'VALUE' ) {
9838
PTDEBUG && _d('It is the result of a "get"');
9839
my ($key, $flags, $bytes) = @vals;
9840
defined $session->{flags} or $session->{flags} = $flags;
9841
defined $session->{bytes} or $session->{bytes} = $bytes;
9843
if ( $rest && $bytes ) {
9844
PTDEBUG && _d('There is a value');
9845
if ( length($rest) > $bytes ) {
9846
PTDEBUG && _d('Got complete response');
9847
$session->{val} = substr($rest, 0, $bytes);
9850
PTDEBUG && _d('Got partial response, saving for later');
9851
push @{$session->{partial}}, [ $packet->{seq}, $rest ];
9852
$session->{gathered} += length($rest);
9853
$session->{state} = 'partial recv';
9854
return; # Prevent firing an event.
9858
elsif ( $session->{res} eq 'END' ) {
9859
PTDEBUG && _d('Got an END without any data, firing NOT_FOUND');
9860
$session->{res} = 'NOT_FOUND';
9862
elsif ( $session->{res} !~ m/STORED|DELETED|NOT_FOUND/ ) {
9863
PTDEBUG && _d('Unknown result');
9866
$args{stats}->{unknown_server_response}++ if $args{stats};
9869
else { # Should be 'partial recv'
9870
PTDEBUG && _d('Session state: ', $session->{state});
9871
push @{$session->{partial}}, [ $packet->{seq}, $data ];
9872
$session->{gathered} += length($data);
9873
PTDEBUG && _d('Gathered', $session->{gathered}, 'bytes in',
9874
scalar(@{$session->{partial}}), 'packets from server');
9875
if ( $session->{gathered} >= $session->{bytes} + 2 ) { # Done.
9876
PTDEBUG && _d('End of partial response, preparing event');
9879
sort { $a->[0] <=> $b->[0] }
9880
@{$session->{partial}});
9881
$session->{val} = substr($val, 0, $session->{bytes});
9884
PTDEBUG && _d('Partial response continues, no action');
9885
return; # Prevent firing event.
9889
PTDEBUG && _d('Creating event, deleting session');
9890
my $event = make_event($session, $packet);
9891
delete $self->{sessions}->{$session->{client}}; # memcached is stateless!
9892
$session->{raw_packets} = []; # Avoid keeping forever
9896
sub _packet_from_client {
9897
my ( $self, $packet, $session, %args ) = @_;
9898
die "I need a packet" unless $packet;
9899
die "I need a session" unless $session;
9901
PTDEBUG && _d('Packet is from client; state:', $session->{state});
9904
if ( ($session->{state} || '') =~m/awaiting reply|partial recv/ ) {
9905
PTDEBUG && _d("Expected data from the client, looks like interrupted");
9906
$session->{res} = 'INTERRUPTED';
9907
$event = make_event($session, $packet);
9908
my $client = $session->{client};
9909
delete @{$session}{keys %$session};
9910
$session->{client} = $client;
9914
my ($cmd, $key, $flags, $exptime, $bytes);
9916
if ( !$session->{state} ) {
9917
PTDEBUG && _d('Session state: ', $session->{state});
9918
($line1, $val) = $packet->{data} =~ m/\A(.*?)\r\n(.+)?/s;
9920
PTDEBUG && _d('Unknown memcached data from client, skipping packet');
9921
$args{stats}->{unknown_client_data}++ if $args{stats};
9925
my @vals = $line1 =~ m/(\S+)/g;
9926
$cmd = lc shift @vals;
9927
PTDEBUG && _d('$cmd is a ', $cmd);
9928
if ( $cmd eq 'set' || $cmd eq 'add' || $cmd eq 'replace' ) {
9929
($key, $flags, $exptime, $bytes) = @vals;
9930
$session->{bytes} = $bytes;
9932
elsif ( $cmd eq 'get' ) {
9935
PTDEBUG && _d('Multiple cmds:', $val);
9939
elsif ( $cmd eq 'delete' ) {
9940
($key) = @vals; # TODO: handle the <queue_time>
9942
PTDEBUG && _d('Multiple cmds:', $val);
9946
elsif ( $cmd eq 'incr' || $cmd eq 'decr' ) {
9950
PTDEBUG && _d("Don't know how to handle", $cmd, "command");
9951
$args{stats}->{unknown_client_command}++ if $args{stats};
9955
@{$session}{qw(cmd key flags exptime)}
9956
= ($cmd, $key, $flags, $exptime);
9957
$session->{host} = $packet->{src_host};
9958
$session->{pos_in_log} = $packet->{pos_in_log};
9959
$session->{ts} = $packet->{ts};
9962
PTDEBUG && _d('Session state: ', $session->{state});
9963
$val = $packet->{data};
9966
$session->{state} = 'awaiting reply'; # Assume we got the whole packet
9968
if ( $session->{bytes} + 2 == length($val) ) { # +2 for the \r\n
9969
PTDEBUG && _d('Complete send');
9970
$val =~ s/\r\n\Z//; # We got the whole thing.
9971
$session->{val} = $val;
9973
else { # We apparently did NOT get the whole thing.
9974
PTDEBUG && _d('Partial send, saving for later');
9975
push @{$session->{partial}},
9976
[ $packet->{seq}, $val ];
9977
$session->{gathered} += length($val);
9978
PTDEBUG && _d('Gathered', $session->{gathered}, 'bytes in',
9979
scalar(@{$session->{partial}}), 'packets from client');
9980
if ( $session->{gathered} >= $session->{bytes} + 2 ) { # Done.
9981
PTDEBUG && _d('Message looks complete now, saving value');
9984
sort { $a->[0] <=> $b->[0] }
9985
@{$session->{partial}});
9987
$session->{val} = $val;
9990
PTDEBUG && _d('Message not complete');
9991
$val = '[INCOMPLETE]';
9992
$session->{state} = 'partial send';
10001
my ( $session, $packet ) = @_;
10003
cmd => $session->{cmd},
10004
key => $session->{key},
10005
val => $session->{val} || '',
10006
res => $session->{res},
10007
ts => $session->{ts},
10008
host => $session->{host},
10009
flags => $session->{flags} || 0,
10010
exptime => $session->{exptime} || 0,
10011
bytes => $session->{bytes} || 0,
10012
Query_time => timestamp_diff($session->{ts}, $packet->{ts}),
10013
pos_in_log => $session->{pos_in_log},
10018
sub _get_errors_fh {
10020
my $errors_fh = $self->{errors_fh};
10021
return $errors_fh if $errors_fh;
10023
my $o = $self->{o};
10024
if ( $o && $o->has('tcpdump-errors') && $o->got('tcpdump-errors') ) {
10025
my $errors_file = $o->get('tcpdump-errors');
10026
PTDEBUG && _d('tcpdump-errors file:', $errors_file);
10027
open $errors_fh, '>>', $errors_file
10028
or die "Cannot open tcpdump-errors file $errors_file: $OS_ERROR";
10031
$self->{errors_fh} = $errors_fh;
10036
my ($package, undef, $line) = caller 0;
10037
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
10038
map { defined $_ ? $_ : 'undef' }
10040
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
10043
sub timestamp_diff {
10044
my ( $start, $end ) = @_;
10045
my $sd = substr($start, 0, 11, '');
10046
my $ed = substr($end, 0, 11, '');
10047
my ( $sh, $sm, $ss ) = split(/:/, $start);
10048
my ( $eh, $em, $es ) = split(/:/, $end);
10049
my $esecs = ($eh * 3600 + $em * 60 + $es);
10050
my $ssecs = ($sh * 3600 + $sm * 60 + $ss);
10051
if ( $sd eq $ed ) {
10052
return sprintf '%.6f', $esecs - $ssecs;
10054
else { # Assume only one day boundary has been crossed, no DST, etc
10055
return sprintf '%.6f', ( 86_400 - $ssecs ) + $esecs;
10061
# ###########################################################################
10062
# End MemcachedProtocolParser package
10063
# ###########################################################################
10065
# ###########################################################################
10066
# MemcachedEvent package
10067
# This package is a copy without comments from the original. The original
10068
# with comments and its test file can be found in the Bazaar repository at,
10069
# lib/MemcachedEvent.pm
10070
# t/lib/MemcachedEvent.t
10071
# See https://launchpad.net/percona-toolkit for more information.
10072
# ###########################################################################
10074
package MemcachedEvent;
10077
use warnings FATAL => 'all';
10078
use English qw(-no_match_vars);
10079
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
10082
$Data::Dumper::Indent = 1;
10083
$Data::Dumper::Sortkeys = 1;
10084
$Data::Dumper::Quotekeys = 0;
10086
my %cmds = map { $_ => 1 } qw(
10100
my %cmd_handler_for = (
10101
set => \&handle_storage_cmd,
10102
add => \&handle_storage_cmd,
10103
replace => \&handle_storage_cmd,
10104
append => \&handle_storage_cmd,
10105
prepend => \&handle_storage_cmd,
10106
cas => \&handle_storage_cmd,
10107
get => \&handle_retr_cmd,
10108
gets => \&handle_retr_cmd,
10112
my ( $class, %args ) = @_;
10114
return bless $self, $class;
10118
my ( $self, %args ) = @_;
10119
my $event = $args{event};
10120
return unless $event;
10122
if ( !$event->{cmd} || !$event->{key} ) {
10123
PTDEBUG && _d('Event has no cmd or key:', Dumper($event));
10127
if ( !$cmds{$event->{cmd}} ) {
10128
PTDEBUG && _d("Don't know how to handle cmd:", $event->{cmd});
10132
$event->{arg} = "$event->{cmd} $event->{key}";
10133
$event->{fingerprint} = $self->fingerprint($event->{arg});
10134
$event->{key_print} = $self->fingerprint($event->{key});
10136
map { $event->{"Memc_$_"} = 'No' } keys %cmds;
10137
$event->{"Memc_$event->{cmd}"} = 'Yes'; # Got this cmd.
10138
$event->{Memc_error} = 'No'; # A handler may change this.
10139
$event->{Memc_miss} = 'No';
10140
if ( $event->{res} ) {
10141
$event->{Memc_miss} = 'Yes' if $event->{res} eq 'NOT_FOUND';
10144
PTDEBUG && _d('Event has no res:', Dumper($event));
10147
if ( $cmd_handler_for{$event->{cmd}} ) {
10148
return $cmd_handler_for{$event->{cmd}}->($event);
10155
my ( $self, $val ) = @_;
10156
$val =~ s/[0-9A-Fa-f]{16,}|\d+/?/g;
10160
sub handle_storage_cmd {
10161
my ( $event ) = @_;
10163
if ( !$event->{res} ) {
10164
PTDEBUG && _d('No result for event:', Dumper($event));
10168
$event->{'Memc_Not_Stored'} = $event->{res} eq 'NOT_STORED' ? 'Yes' : 'No';
10169
$event->{'Memc_Exists'} = $event->{res} eq 'EXISTS' ? 'Yes' : 'No';
10174
sub handle_retr_cmd {
10175
my ( $event ) = @_;
10177
if ( !$event->{res} ) {
10178
PTDEBUG && _d('No result for event:', Dumper($event));
10182
$event->{'Memc_error'} = $event->{res} eq 'INTERRUPTED' ? 'Yes' : 'No';
10188
sub handle_delete {
10189
my ( $event ) = @_;
10193
sub handle_incr_decr_cmd {
10194
my ( $event ) = @_;
10199
my ($package, undef, $line) = caller 0;
10200
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
10201
map { defined $_ ? $_ : 'undef' }
10203
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
10208
# ###########################################################################
10209
# End MemcachedEvent package
10210
# ###########################################################################
10212
# ###########################################################################
10213
9076
# BinaryLogParser package
10214
9077
# This package is a copy without comments from the original. The original
10215
9078
# with comments and its test file can be found in the Bazaar repository at,
10960
9823
# ###########################################################################
10962
9825
# ###########################################################################
10963
# HTTPProtocolParser package
10964
# This package is a copy without comments from the original. The original
10965
# with comments and its test file can be found in the Bazaar repository at,
10966
# lib/HTTPProtocolParser.pm
10967
# t/lib/HTTPProtocolParser.t
10968
# See https://launchpad.net/percona-toolkit for more information.
10969
# ###########################################################################
10971
package HTTPProtocolParser;
10972
use base 'ProtocolParser';
10975
use warnings FATAL => 'all';
10976
use English qw(-no_match_vars);
10977
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
10980
my ( $class, %args ) = @_;
10981
my $self = $class->SUPER::new(
10988
sub _packet_from_server {
10989
my ( $self, $packet, $session, $misc ) = @_;
10990
die "I need a packet" unless $packet;
10991
die "I need a session" unless $session;
10993
PTDEBUG && _d('Packet is from server; client state:', $session->{state});
10995
if ( !$session->{state} ) {
10996
PTDEBUG && _d('Ignoring mid-stream server response');
11000
if ( $session->{out_of_order} ) {
11001
my ($line1, $content);
11002
if ( !$session->{have_header} ) {
11003
($line1, $content) = $self->_parse_header(
11004
$session, $packet->{data}, $packet->{data_len});
11007
$session->{have_header} = 1;
11008
$packet->{content_len} = length $content;
11009
PTDEBUG && _d('Got out of order header with',
11010
$packet->{content_len}, 'bytes of content');
11012
my $have_len = $packet->{content_len} || $packet->{data_len};
11013
map { $have_len += $_->{data_len} }
11014
@{$session->{packets}};
11015
$session->{have_all_packets}
11016
= 1 if $session->{attribs}->{bytes}
11017
&& $have_len >= $session->{attribs}->{bytes};
11018
PTDEBUG && _d('Have', $have_len, 'of', $session->{attribs}->{bytes});
11022
if ( $session->{state} eq 'awaiting reply' ) {
11024
$session->{start_reply} = $packet->{ts} unless $session->{start_reply};
11026
my ($line1, $content) = $self->_parse_header($session, $packet->{data},
11027
$packet->{data_len});
11030
$session->{out_of_order} = 1; # alert parent
11031
$session->{have_all_packets} = 0;
11035
my ($version, $code, $phrase) = $line1 =~ m/(\S+)/g;
11036
$session->{attribs}->{Status_code} = $code;
11037
PTDEBUG && _d('Status code for last', $session->{attribs}->{arg},
11038
'request:', $session->{attribs}->{Status_code});
11040
my $content_len = $content ? length $content : 0;
11041
PTDEBUG && _d('Got', $content_len, 'bytes of content');
11042
if ( $session->{attribs}->{bytes}
11043
&& $content_len < $session->{attribs}->{bytes} ) {
11044
$session->{data_len} = $session->{attribs}->{bytes};
11045
$session->{buff} = $content;
11046
$session->{buff_left} = $session->{attribs}->{bytes} - $content_len;
11047
PTDEBUG && _d('Contents not complete,', $session->{buff_left},
11049
$session->{state} = 'recving content';
11053
elsif ( $session->{state} eq 'recving content' ) {
11054
if ( $session->{buff} ) {
11055
PTDEBUG && _d('Receiving content,', $session->{buff_left},
11059
PTDEBUG && _d('Contents received');
11062
warn "Server response in unknown state";
11066
PTDEBUG && _d('Creating event, deleting session');
11067
$session->{end_reply} = $session->{ts_max} || $packet->{ts};
11068
my $event = $self->make_event($session, $packet);
11069
delete $self->{sessions}->{$session->{client}}; # http is stateless!
11073
sub _packet_from_client {
11074
my ( $self, $packet, $session, $misc ) = @_;
11075
die "I need a packet" unless $packet;
11076
die "I need a session" unless $session;
11078
PTDEBUG && _d('Packet is from client; state:', $session->{state});
11081
if ( ($session->{state} || '') =~ m/awaiting / ) {
11082
PTDEBUG && _d('More client headers:', $packet->{data});
11086
if ( !$session->{state} ) {
11087
$session->{state} = 'awaiting reply';
11088
my ($line1, undef) = $self->_parse_header($session, $packet->{data}, $packet->{data_len});
11089
my ($request, $page, $version) = $line1 =~ m/(\S+)/g;
11090
if ( !$request || !$page ) {
11091
PTDEBUG && _d("Didn't get a request or page:", $request, $page);
11094
$request = lc $request;
11095
my $vh = $session->{attribs}->{Virtual_host} || '';
11096
my $arg = "$request $vh$page";
11097
PTDEBUG && _d('arg:', $arg);
11099
if ( $request eq 'get' || $request eq 'post' ) {
11100
@{$session->{attribs}}{qw(arg)} = ($arg);
11103
PTDEBUG && _d("Don't know how to handle a", $request, "request");
11107
$session->{start_request} = $packet->{ts};
11108
$session->{attribs}->{host} = $packet->{src_host};
11109
$session->{attribs}->{pos_in_log} = $packet->{pos_in_log};
11110
$session->{attribs}->{ts} = $packet->{ts};
11113
die "Probably multiple GETs from client before a server response?";
11119
sub _parse_header {
11120
my ( $self, $session, $data, $len, $no_recurse ) = @_;
11121
die "I need data" unless $data;
11122
my ($header, $content) = split(/\r\n\r\n/, $data);
11123
my ($line1, $header_vals) = $header =~ m/\A(\S+ \S+ .+?)\r\n(.+)?/s;
11124
PTDEBUG && _d('HTTP header:', $line1);
11125
return unless $line1;
11127
if ( !$header_vals ) {
11128
PTDEBUG && _d('No header vals');
11129
return $line1, undef;
11132
foreach my $val ( split(/\r\n/, $header_vals) ) {
11134
PTDEBUG && _d('HTTP header:', $val);
11135
if ( $val =~ m/^Content-Length/i ) {
11136
($session->{attribs}->{bytes}) = $val =~ /: (\d+)/;
11137
PTDEBUG && _d('Saved Content-Length:', $session->{attribs}->{bytes});
11139
if ( $val =~ m/Content-Encoding/i ) {
11140
($session->{compressed}) = $val =~ /: (\w+)/;
11141
PTDEBUG && _d('Saved Content-Encoding:', $session->{compressed});
11143
if ( $val =~ m/^Host/i ) {
11144
($session->{attribs}->{Virtual_host}) = $val =~ /: (\S+)/;
11145
PTDEBUG && _d('Saved Host:', ($session->{attribs}->{Virtual_host}));
11148
return $line1, $content;
11152
my ($package, undef, $line) = caller 0;
11153
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
11154
map { defined $_ ? $_ : 'undef' }
11156
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
11161
# ###########################################################################
11162
# End HTTPProtocolParser package
11163
# ###########################################################################
11165
# ###########################################################################
11166
9826
# MasterSlave package
11167
9827
# This package is a copy without comments from the original. The original
11168
9828
# with comments and its test file can be found in the Bazaar repository at,