Merge lp:~percona-toolkit-dev/percona-toolkit/remove-pt-agent into lp:~percona-toolkit-dev/percona-toolkit/release-2.2.8

Proposed by Daniel Nichter
Status: Merged
Approved by: Daniel Nichter
Approved revision: 599
Merged at revision: 600
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/remove-pt-agent
Merge into: lp:~percona-toolkit-dev/percona-toolkit/release-2.2.8
Diff against target: 14753 lines (+2/-14551)
37 files modified
Changelog (+2/-0)
bin/pt-agent (+0/-9802)
lib/Percona/Agent/Logger.pm (+0/-341)
lib/Percona/Test/Mock/AgentLogger.pm (+0/-129)
lib/Percona/Test/Mock/UserAgent.pm (+0/-71)
lib/Percona/WebAPI/Client.pm (+0/-321)
lib/Percona/WebAPI/Exception/Request.pm (+0/-69)
lib/Percona/WebAPI/Exception/Resource.pm (+0/-66)
lib/Percona/WebAPI/Representation.pm (+0/-86)
lib/Percona/WebAPI/Resource/Agent.pm (+0/-77)
lib/Percona/WebAPI/Resource/Config.pm (+0/-55)
lib/Percona/WebAPI/Resource/LogEntry.pm (+0/-66)
lib/Percona/WebAPI/Resource/Service.pm (+0/-94)
lib/Percona/WebAPI/Resource/Task.pm (+0/-62)
t/lib/Percona/WebAPI/Client.t (+0/-236)
t/lib/Percona/WebAPI/Representation.t (+0/-51)
t/pt-agent/basics.t (+0/-101)
t/pt-agent/get_services.t (+0/-423)
t/pt-agent/init_agent.t (+0/-333)
t/pt-agent/make_new_crontab.t (+0/-151)
t/pt-agent/replace_special_vars.t (+0/-73)
t/pt-agent/run_agent.t (+0/-527)
t/pt-agent/run_service.t (+0/-503)
t/pt-agent/samples/crontab001.out (+0/-2)
t/pt-agent/samples/crontab002.in (+0/-1)
t/pt-agent/samples/crontab002.out (+0/-3)
t/pt-agent/samples/crontab003.in (+0/-3)
t/pt-agent/samples/crontab003.out (+0/-3)
t/pt-agent/samples/crontab004.in (+0/-2)
t/pt-agent/samples/crontab004.out (+0/-2)
t/pt-agent/samples/query-history/data001.json (+0/-152)
t/pt-agent/samples/query-history/data001.send (+0/-166)
t/pt-agent/samples/service001 (+0/-19)
t/pt-agent/samples/write_services001 (+0/-19)
t/pt-agent/schedule_services.t (+0/-200)
t/pt-agent/send_data.t (+0/-234)
t/pt-agent/write_services.t (+0/-108)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/remove-pt-agent
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+217325@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve
600. By Daniel Nichter

Remove pt-agent.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Changelog'
--- Changelog 2014-02-20 08:10:16 +0000
+++ Changelog 2014-04-26 04:29:24 +0000
@@ -1,5 +1,7 @@
1Changelog for Percona Toolkit1Changelog for Percona Toolkit
22
3 * Removed pt-agent
4
3v2.2.7 released 2014-02-205v2.2.7 released 2014-02-20
46
5 * Fixed bug 1279502: --version-check behaves like spyware7 * Fixed bug 1279502: --version-check behaves like spyware
68
=== removed file 'bin/pt-agent'
--- bin/pt-agent 2014-02-20 08:10:16 +0000
+++ bin/pt-agent 1970-01-01 00:00:00 +0000
@@ -1,9802 +0,0 @@
1#!/usr/bin/env perl
2
3# This program is part of Percona Toolkit: http://www.percona.com/software/
4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
5# notices and disclaimers.
6
7use strict;
8use warnings FATAL => 'all';
9
10# This tool is "fat-packed": most of its dependent modules are embedded
11# in this file. Setting %INC to this file for each module makes Perl aware
12# of this so it will not try to load the module from @INC. See the tool's
13# documentation for a full list of dependencies.
14BEGIN {
15 $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
16 Percona::Toolkit
17 Lmo::Utils
18 Lmo::Meta
19 Lmo::Object
20 Lmo::Types
21 Lmo
22 Percona::WebAPI::Representation
23 Percona::WebAPI::Client
24 Percona::WebAPI::Exception::Request
25 Percona::WebAPI::Exception::Resource
26 Percona::WebAPI::Resource::Agent
27 Percona::WebAPI::Resource::Config
28 Percona::WebAPI::Resource::Service
29 Percona::WebAPI::Resource::Task
30 Percona::WebAPI::Resource::LogEntry
31 VersionCheck
32 DSNParser
33 OptionParser
34 Cxn
35 Quoter
36 VersionParser
37 Daemon
38 Transformers
39 Safeguards
40 Percona::Agent::Logger
41 ));
42}
43
44# ###########################################################################
45# Percona::Toolkit package
46# This package is a copy without comments from the original. The original
47# with comments and its test file can be found in the Bazaar repository at,
48# lib/Percona/Toolkit.pm
49# t/lib/Percona/Toolkit.t
50# See https://launchpad.net/percona-toolkit for more information.
51# ###########################################################################
52{
53package Percona::Toolkit;
54
55our $VERSION = '2.2.7';
56
57use strict;
58use warnings FATAL => 'all';
59use English qw(-no_match_vars);
60use constant PTDEBUG => $ENV{PTDEBUG} || 0;
61
62use Carp qw(carp cluck);
63use Data::Dumper qw();
64
65require Exporter;
66our @ISA = qw(Exporter);
67our @EXPORT_OK = qw(
68 have_required_args
69 Dumper
70 _d
71);
72
73sub have_required_args {
74 my ($args, @required_args) = @_;
75 my $have_required_args = 1;
76 foreach my $arg ( @required_args ) {
77 if ( !defined $args->{$arg} ) {
78 $have_required_args = 0;
79 carp "Argument $arg is not defined";
80 }
81 }
82 cluck unless $have_required_args; # print backtrace
83 return $have_required_args;
84}
85
86sub Dumper {
87 local $Data::Dumper::Indent = 1;
88 local $Data::Dumper::Sortkeys = 1;
89 local $Data::Dumper::Quotekeys = 0;
90 Data::Dumper::Dumper(@_);
91}
92
93sub _d {
94 my ($package, undef, $line) = caller 0;
95 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
96 map { defined $_ ? $_ : 'undef' }
97 @_;
98 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
99}
100
1011;
102}
103# ###########################################################################
104# End Percona::Toolkit package
105# ###########################################################################
106
107# ###########################################################################
108# Lmo::Utils package
109# This package is a copy without comments from the original. The original
110# with comments and its test file can be found in the Bazaar repository at,
111# lib/Lmo/Utils.pm
112# t/lib/Lmo/Utils.t
113# See https://launchpad.net/percona-toolkit for more information.
114# ###########################################################################
115{
116package Lmo::Utils;
117
118use strict;
119use warnings qw( FATAL all );
120require Exporter;
121our (@ISA, @EXPORT, @EXPORT_OK);
122
123BEGIN {
124 @ISA = qw(Exporter);
125 @EXPORT = @EXPORT_OK = qw(
126 _install_coderef
127 _unimport_coderefs
128 _glob_for
129 _stash_for
130 );
131}
132
133{
134 no strict 'refs';
135 sub _glob_for {
136 return \*{shift()}
137 }
138
139 sub _stash_for {
140 return \%{ shift() . "::" };
141 }
142}
143
144sub _install_coderef {
145 my ($to, $code) = @_;
146
147 return *{ _glob_for $to } = $code;
148}
149
150sub _unimport_coderefs {
151 my ($target, @names) = @_;
152 return unless @names;
153 my $stash = _stash_for($target);
154 foreach my $name (@names) {
155 if ($stash->{$name} and defined(&{$stash->{$name}})) {
156 delete $stash->{$name};
157 }
158 }
159}
160
1611;
162}
163# ###########################################################################
164# End Lmo::Utils package
165# ###########################################################################
166
167# ###########################################################################
168# Lmo::Meta package
169# This package is a copy without comments from the original. The original
170# with comments and its test file can be found in the Bazaar repository at,
171# lib/Lmo/Meta.pm
172# t/lib/Lmo/Meta.t
173# See https://launchpad.net/percona-toolkit for more information.
174# ###########################################################################
175{
176package Lmo::Meta;
177use strict;
178use warnings qw( FATAL all );
179
180my %metadata_for;
181
182sub new {
183 my $class = shift;
184 return bless { @_ }, $class
185}
186
187sub metadata_for {
188 my $self = shift;
189 my ($class) = @_;
190
191 return $metadata_for{$class} ||= {};
192}
193
194sub class { shift->{class} }
195
196sub attributes {
197 my $self = shift;
198 return keys %{$self->metadata_for($self->class)}
199}
200
201sub attributes_for_new {
202 my $self = shift;
203 my @attributes;
204
205 my $class_metadata = $self->metadata_for($self->class);
206 while ( my ($attr, $meta) = each %$class_metadata ) {
207 if ( exists $meta->{init_arg} ) {
208 push @attributes, $meta->{init_arg}
209 if defined $meta->{init_arg};
210 }
211 else {
212 push @attributes, $attr;
213 }
214 }
215 return @attributes;
216}
217
2181;
219}
220# ###########################################################################
221# End Lmo::Meta package
222# ###########################################################################
223
224# ###########################################################################
225# Lmo::Object package
226# This package is a copy without comments from the original. The original
227# with comments and its test file can be found in the Bazaar repository at,
228# lib/Lmo/Object.pm
229# t/lib/Lmo/Object.t
230# See https://launchpad.net/percona-toolkit for more information.
231# ###########################################################################
232{
233package Lmo::Object;
234
235use strict;
236use warnings qw( FATAL all );
237
238use Carp ();
239use Scalar::Util qw(blessed);
240
241use Lmo::Meta;
242use Lmo::Utils qw(_glob_for);
243
244sub new {
245 my $class = shift;
246 my $args = $class->BUILDARGS(@_);
247
248 my $class_metadata = Lmo::Meta->metadata_for($class);
249
250 my @args_to_delete;
251 while ( my ($attr, $meta) = each %$class_metadata ) {
252 next unless exists $meta->{init_arg};
253 my $init_arg = $meta->{init_arg};
254
255 if ( defined $init_arg ) {
256 $args->{$attr} = delete $args->{$init_arg};
257 }
258 else {
259 push @args_to_delete, $attr;
260 }
261 }
262
263 delete $args->{$_} for @args_to_delete;
264
265 for my $attribute ( keys %$args ) {
266 if ( my $coerce = $class_metadata->{$attribute}{coerce} ) {
267 $args->{$attribute} = $coerce->($args->{$attribute});
268 }
269 if ( my $isa_check = $class_metadata->{$attribute}{isa} ) {
270 my ($check_name, $check_sub) = @$isa_check;
271 $check_sub->($args->{$attribute});
272 }
273 }
274
275 while ( my ($attribute, $meta) = each %$class_metadata ) {
276 next unless $meta->{required};
277 Carp::confess("Attribute ($attribute) is required for $class")
278 if ! exists $args->{$attribute}
279 }
280
281 my $self = bless $args, $class;
282
283 my @build_subs;
284 my $linearized_isa = mro::get_linear_isa($class);
285
286 for my $isa_class ( @$linearized_isa ) {
287 unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE};
288 }
289 my @args = %$args;
290 for my $sub (grep { defined($_) && exists &$_ } @build_subs) {
291 $sub->( $self, @args);
292 }
293 return $self;
294}
295
296sub BUILDARGS {
297 shift; # No need for the classname
298 if ( @_ == 1 && ref($_[0]) ) {
299 Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]")
300 unless ref($_[0]) eq ref({});
301 return {%{$_[0]}} # We want a new reference, always
302 }
303 else {
304 return { @_ };
305 }
306}
307
308sub meta {
309 my $class = shift;
310 $class = Scalar::Util::blessed($class) || $class;
311 return Lmo::Meta->new(class => $class);
312}
313
3141;
315}
316# ###########################################################################
317# End Lmo::Object package
318# ###########################################################################
319
320# ###########################################################################
321# Lmo::Types package
322# This package is a copy without comments from the original. The original
323# with comments and its test file can be found in the Bazaar repository at,
324# lib/Lmo/Types.pm
325# t/lib/Lmo/Types.t
326# See https://launchpad.net/percona-toolkit for more information.
327# ###########################################################################
328{
329package Lmo::Types;
330
331use strict;
332use warnings qw( FATAL all );
333
334use Carp ();
335use Scalar::Util qw(looks_like_number blessed);
336
337
338our %TYPES = (
339 Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) },
340 Num => sub { defined $_[0] && looks_like_number($_[0]) },
341 Int => sub { defined $_[0] && looks_like_number($_[0]) && $_[0] == int($_[0]) },
342 Str => sub { defined $_[0] },
343 Object => sub { defined $_[0] && blessed($_[0]) },
344 FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened },
345
346 map {
347 my $type = /R/ ? $_ : uc $_;
348 $_ . "Ref" => sub { ref $_[0] eq $type }
349 } qw(Array Code Hash Regexp Glob Scalar)
350);
351
352sub check_type_constaints {
353 my ($attribute, $type_check, $check_name, $val) = @_;
354 ( ref($type_check) eq 'CODE'
355 ? $type_check->($val)
356 : (ref $val eq $type_check
357 || ($val && $val eq $type_check)
358 || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val)))
359 )
360 || Carp::confess(
361 qq<Attribute ($attribute) does not pass the type constraint because: >
362 . qq<Validation failed for '$check_name' with value >
363 . (defined $val ? Lmo::Dumper($val) : 'undef') )
364}
365
366sub _nested_constraints {
367 my ($attribute, $aggregate_type, $type) = @_;
368
369 my $inner_types;
370 if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) {
371 $inner_types = _nested_constraints($1, $2);
372 }
373 else {
374 $inner_types = $TYPES{$type};
375 }
376
377 if ( $aggregate_type eq 'ArrayRef' ) {
378 return sub {
379 my ($val) = @_;
380 return unless ref($val) eq ref([]);
381
382 if ($inner_types) {
383 for my $value ( @{$val} ) {
384 return unless $inner_types->($value)
385 }
386 }
387 else {
388 for my $value ( @{$val} ) {
389 return unless $value && ($value eq $type
390 || (Scalar::Util::blessed($value) && $value->isa($type)));
391 }
392 }
393 return 1;
394 };
395 }
396 elsif ( $aggregate_type eq 'Maybe' ) {
397 return sub {
398 my ($value) = @_;
399 return 1 if ! defined($value);
400 if ($inner_types) {
401 return unless $inner_types->($value)
402 }
403 else {
404 return unless $value eq $type
405 || (Scalar::Util::blessed($value) && $value->isa($type));
406 }
407 return 1;
408 }
409 }
410 else {
411 Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe");
412 }
413}
414
4151;
416}
417# ###########################################################################
418# End Lmo::Types package
419# ###########################################################################
420
421# ###########################################################################
422# Lmo package
423# This package is a copy without comments from the original. The original
424# with comments and its test file can be found in the Bazaar repository at,
425# lib/Lmo.pm
426# t/lib/Lmo.t
427# See https://launchpad.net/percona-toolkit for more information.
428# ###########################################################################
429{
430BEGIN {
431$INC{"Lmo.pm"} = __FILE__;
432package Lmo;
433our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo.
434
435
436use strict;
437use warnings qw( FATAL all );
438
439use Carp ();
440use Scalar::Util qw(looks_like_number blessed);
441
442use Lmo::Meta;
443use Lmo::Object;
444use Lmo::Types;
445
446use Lmo::Utils;
447
448my %export_for;
449sub import {
450 warnings->import(qw(FATAL all));
451 strict->import();
452
453 my $caller = scalar caller(); # Caller's package
454 my %exports = (
455 extends => \&extends,
456 has => \&has,
457 with => \&with,
458 override => \&override,
459 confess => \&Carp::confess,
460 );
461
462 $export_for{$caller} = \%exports;
463
464 for my $keyword ( keys %exports ) {
465 _install_coderef "${caller}::$keyword" => $exports{$keyword};
466 }
467
468 if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) {
469 @_ = "Lmo::Object";
470 goto *{ _glob_for "${caller}::extends" }{CODE};
471 }
472}
473
474sub extends {
475 my $caller = scalar caller();
476 for my $class ( @_ ) {
477 _load_module($class);
478 }
479 _set_package_isa($caller, @_);
480 _set_inherited_metadata($caller);
481}
482
483sub _load_module {
484 my ($class) = @_;
485
486 (my $file = $class) =~ s{::|'}{/}g;
487 $file .= '.pm';
488 { local $@; eval { require "$file" } } # or warn $@;
489 return;
490}
491
492sub with {
493 my $package = scalar caller();
494 require Role::Tiny;
495 for my $role ( @_ ) {
496 _load_module($role);
497 _role_attribute_metadata($package, $role);
498 }
499 Role::Tiny->apply_roles_to_package($package, @_);
500}
501
502sub _role_attribute_metadata {
503 my ($package, $role) = @_;
504
505 my $package_meta = Lmo::Meta->metadata_for($package);
506 my $role_meta = Lmo::Meta->metadata_for($role);
507
508 %$package_meta = (%$role_meta, %$package_meta);
509}
510
511sub has {
512 my $names = shift;
513 my $caller = scalar caller();
514
515 my $class_metadata = Lmo::Meta->metadata_for($caller);
516
517 for my $attribute ( ref $names ? @$names : $names ) {
518 my %args = @_;
519 my $method = ($args{is} || '') eq 'ro'
520 ? sub {
521 Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}")
522 if $#_;
523 return $_[0]{$attribute};
524 }
525 : sub {
526 return $#_
527 ? $_[0]{$attribute} = $_[1]
528 : $_[0]{$attribute};
529 };
530
531 $class_metadata->{$attribute} = ();
532
533 if ( my $type_check = $args{isa} ) {
534 my $check_name = $type_check;
535
536 if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) {
537 $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type);
538 }
539
540 my $check_sub = sub {
541 my ($new_val) = @_;
542 Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val);
543 };
544
545 $class_metadata->{$attribute}{isa} = [$check_name, $check_sub];
546 my $orig_method = $method;
547 $method = sub {
548 $check_sub->($_[1]) if $#_;
549 goto &$orig_method;
550 };
551 }
552
553 if ( my $builder = $args{builder} ) {
554 my $original_method = $method;
555 $method = sub {
556 $#_
557 ? goto &$original_method
558 : ! exists $_[0]{$attribute}
559 ? $_[0]{$attribute} = $_[0]->$builder
560 : goto &$original_method
561 };
562 }
563
564 if ( my $code = $args{default} ) {
565 Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef")
566 unless ref($code) eq 'CODE';
567 my $original_method = $method;
568 $method = sub {
569 $#_
570 ? goto &$original_method
571 : ! exists $_[0]{$attribute}
572 ? $_[0]{$attribute} = $_[0]->$code
573 : goto &$original_method
574 };
575 }
576
577 if ( my $role = $args{does} ) {
578 my $original_method = $method;
579 $method = sub {
580 if ( $#_ ) {
581 Carp::confess(qq<Attribute ($attribute) doesn't consume a '$role' role">)
582 unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) }
583 }
584 goto &$original_method
585 };
586 }
587
588 if ( my $coercion = $args{coerce} ) {
589 $class_metadata->{$attribute}{coerce} = $coercion;
590 my $original_method = $method;
591 $method = sub {
592 if ( $#_ ) {
593 return $original_method->($_[0], $coercion->($_[1]))
594 }
595 goto &$original_method;
596 }
597 }
598
599 _install_coderef "${caller}::$attribute" => $method;
600
601 if ( $args{required} ) {
602 $class_metadata->{$attribute}{required} = 1;
603 }
604
605 if ($args{clearer}) {
606 _install_coderef "${caller}::$args{clearer}"
607 => sub { delete shift->{$attribute} }
608 }
609
610 if ($args{predicate}) {
611 _install_coderef "${caller}::$args{predicate}"
612 => sub { exists shift->{$attribute} }
613 }
614
615 if ($args{handles}) {
616 _has_handles($caller, $attribute, \%args);
617 }
618
619 if (exists $args{init_arg}) {
620 $class_metadata->{$attribute}{init_arg} = $args{init_arg};
621 }
622 }
623}
624
625sub _has_handles {
626 my ($caller, $attribute, $args) = @_;
627 my $handles = $args->{handles};
628
629 my $ref = ref $handles;
630 my $kv;
631 if ( $ref eq ref [] ) {
632 $kv = { map { $_,$_ } @{$handles} };
633 }
634 elsif ( $ref eq ref {} ) {
635 $kv = $handles;
636 }
637 elsif ( $ref eq ref qr// ) {
638 Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)")
639 unless $args->{isa};
640 my $target_class = $args->{isa};
641 $kv = {
642 map { $_, $_ }
643 grep { $_ =~ $handles }
644 grep { !exists $Lmo::Object::{$_} && $target_class->can($_) }
645 grep { !$export_for{$target_class}->{$_} }
646 keys %{ _stash_for $target_class }
647 };
648 }
649 else {
650 Carp::confess("handles for $ref not yet implemented");
651 }
652
653 while ( my ($method, $target) = each %{$kv} ) {
654 my $name = _glob_for "${caller}::$method";
655 Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation")
656 if defined &$name;
657
658 my ($target, @curried_args) = ref($target) ? @$target : $target;
659 *$name = sub {
660 my $self = shift;
661 my $delegate_to = $self->$attribute();
662 my $error = "Cannot delegate $method to $target because the value of $attribute";
663 Carp::confess("$error is not defined") unless $delegate_to;
664 Carp::confess("$error is not an object (got '$delegate_to')")
665 unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target));
666 return $delegate_to->$target(@curried_args, @_);
667 }
668 }
669}
670
671sub _set_package_isa {
672 my ($package, @new_isa) = @_;
673 my $package_isa = \*{ _glob_for "${package}::ISA" };
674 @{*$package_isa} = @new_isa;
675}
676
677sub _set_inherited_metadata {
678 my $class = shift;
679 my $class_metadata = Lmo::Meta->metadata_for($class);
680 my $linearized_isa = mro::get_linear_isa($class);
681 my %new_metadata;
682
683 for my $isa_class (reverse @$linearized_isa) {
684 my $isa_metadata = Lmo::Meta->metadata_for($isa_class);
685 %new_metadata = (
686 %new_metadata,
687 %$isa_metadata,
688 );
689 }
690 %$class_metadata = %new_metadata;
691}
692
693sub unimport {
694 my $caller = scalar caller();
695 my $target = caller;
696 _unimport_coderefs($target, keys %{$export_for{$caller}});
697}
698
699sub Dumper {
700 require Data::Dumper;
701 local $Data::Dumper::Indent = 0;
702 local $Data::Dumper::Sortkeys = 0;
703 local $Data::Dumper::Quotekeys = 0;
704 local $Data::Dumper::Terse = 1;
705
706 Data::Dumper::Dumper(@_)
707}
708
709BEGIN {
710 if ($] >= 5.010) {
711 { local $@; require mro; }
712 }
713 else {
714 local $@;
715 eval {
716 require MRO::Compat;
717 } or do {
718 *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub {
719 no strict 'refs';
720
721 my $classname = shift;
722
723 my @lin = ($classname);
724 my %stored;
725 foreach my $parent (@{"$classname\::ISA"}) {
726 my $plin = mro::get_linear_isa_dfs($parent);
727 foreach (@$plin) {
728 next if exists $stored{$_};
729 push(@lin, $_);
730 $stored{$_} = 1;
731 }
732 }
733 return \@lin;
734 };
735 }
736 }
737}
738
739sub override {
740 my ($methods, $code) = @_;
741 my $caller = scalar caller;
742
743 for my $method ( ref($methods) ? @$methods : $methods ) {
744 my $full_method = "${caller}::${method}";
745 *{_glob_for $full_method} = $code;
746 }
747}
748
749}
7501;
751}
752# ###########################################################################
753# End Lmo package
754# ###########################################################################
755
756# ###########################################################################
757# Percona::WebAPI::Representation package
758# This package is a copy without comments from the original. The original
759# with comments and its test file can be found in the Bazaar repository at,
760# lib/Percona/WebAPI/Representation.pm
761# t/lib/Percona/WebAPI/Representation.t
762# See https://launchpad.net/percona-toolkit for more information.
763# ###########################################################################
764{
765package Percona::WebAPI::Representation;
766
767eval {
768 require JSON;
769};
770
771require Exporter;
772our @ISA = qw(Exporter);
773our @EXPORT_OK = qw(
774 as_hashref
775 as_json
776 as_config
777);
778
779sub as_hashref {
780 my ($resource, %args) = @_;
781
782 my $as_hashref = { %$resource };
783
784 if ( !defined $args{with_links} || !$args{with_links} ) {
785 delete $as_hashref->{links};
786 }
787
788 return $as_hashref;
789}
790
791sub as_json {
792 my ($resource, %args) = @_;
793
794 my $json = $args{json} || JSON->new;
795 $json->allow_blessed([]);
796 $json->convert_blessed([]);
797
798 my $text = $json->encode(
799 ref $resource eq 'ARRAY' ? $resource : as_hashref($resource, %args)
800 );
801 if ( $args{json} && $text ) { # for testing
802 chomp($text);
803 $text .= "\n";
804 }
805 return $text;
806}
807
808sub as_config {
809 my $resource = shift;
810 if ( !$resource->isa('Percona::WebAPI::Resource::Config') ) {
811 die "Only Config resources can be represented as config.\n";
812 }
813 my $as_hashref = as_hashref($resource);
814 my $options = $as_hashref->{options};
815 my $config = join("\n",
816 map { defined $options->{$_} ? "$_=$options->{$_}" : "$_" }
817 sort keys %$options
818 ) . "\n";
819 return $config;
820}
821
8221;
823}
824# ###########################################################################
825# End Percona::WebAPI::Representation package
826# ###########################################################################
827
828# ###########################################################################
829# Percona::WebAPI::Client package
830# This package is a copy without comments from the original. The original
831# with comments and its test file can be found in the Bazaar repository at,
832# lib/Percona/WebAPI/Client.pm
833# t/lib/Percona/WebAPI/Client.t
834# See https://launchpad.net/percona-toolkit for more information.
835# ###########################################################################
836{
837package Percona::WebAPI::Client;
838
839our $VERSION = '0.01';
840
841use strict;
842use warnings FATAL => 'all';
843use English qw(-no_match_vars);
844use constant PTDEBUG => $ENV{PTDEBUG} || 0;
845
846eval {
847 require LWP;
848 require JSON;
849};
850
851use Scalar::Util qw(blessed);
852
853use Lmo;
854use Percona::Toolkit;
855use Percona::WebAPI::Representation;
856use Percona::WebAPI::Exception::Request;
857use Percona::WebAPI::Exception::Resource;
858
859Percona::WebAPI::Representation->import(qw(as_json));
860Percona::Toolkit->import(qw(_d Dumper have_required_args));
861
862has 'api_key' => (
863 is => 'ro',
864 isa => 'Str',
865 required => 1,
866);
867
868has 'entry_link' => (
869 is => 'rw',
870 isa => 'Str',
871 required => 0,
872 default => sub { return 'https://cloud-api.percona.com' },
873);
874
875has 'ua' => (
876 is => 'rw',
877 isa => 'Object',
878 lazy => 1,
879 required => 0,
880 builder => '_build_ua',
881);
882
883has 'response' => (
884 is => 'rw',
885 isa => 'Object',
886 required => 0,
887 default => undef,
888);
889
890sub _build_ua {
891 my $self = shift;
892 my $ua = LWP::UserAgent->new;
893 $ua->agent("Percona::WebAPI::Client/$Percona::WebAPI::Client::VERSION");
894 $ua->default_header('Content-Type', 'application/json');
895 $ua->default_header('X-Percona-API-Key', $self->api_key);
896 return $ua;
897}
898
899sub get {
900 my ($self, %args) = @_;
901
902 have_required_args(\%args, qw(
903 link
904 )) or die;
905 my ($link) = $args{link};
906
907 eval {
908 $self->_request(
909 method => 'GET',
910 link => $link,
911 );
912 };
913 if ( my $e = $EVAL_ERROR ) {
914 if (blessed($e) && $e->isa('Percona::WebAPI::Exception::Request')) {
915 die $e;
916 }
917 else {
918 die "Unknown error: $e";
919 }
920 }
921
922 my $resource = eval {
923 JSON::decode_json($self->response->content);
924 };
925 if ( $EVAL_ERROR ) {
926 warn sprintf "Error decoding resource: %s: %s",
927 $self->response->content,
928 $EVAL_ERROR;
929 return;
930 }
931
932 my $resource_objects;
933 if ( my $type = $self->response->headers->{'x-percona-resource-type'} ) {
934 eval {
935 $type = "Percona::WebAPI::Resource::$type";
936 if ( ref $resource eq 'ARRAY' ) {
937 PTDEBUG && _d('Got a list of', $type, 'resources');
938 $resource_objects = [];
939 foreach my $attribs ( @$resource ) {
940 my $obj = $type->new(%$attribs);
941 push @$resource_objects, $obj;
942 }
943 }
944 else {
945 PTDEBUG && _d('Got a', $type, 'resource', Dumper($resource));
946 $resource_objects = $type->new(%$resource);
947 }
948 };
949 if ( my $e = $EVAL_ERROR ) {
950 die Percona::WebAPI::Exception::Resource->new(
951 type => $type,
952 link => $link,
953 data => (ref $resource eq 'ARRAY' ? $resource : [ $resource ]),
954 error => $e,
955 );
956 }
957 }
958 elsif ( exists $resource->{links} ) {
959 $resource_objects = $resource->{links};
960 }
961 elsif ( exists $resource->{pong} ) {
962 PTDEBUG && _d("Ping pong!");
963 }
964 else {
965 warn "Did not get X-Percona-Resource-Type or links from $link\n";
966 }
967
968 return $resource_objects;
969}
970
971sub post {
972 my $self = shift;
973 $self->_set(
974 @_,
975 method => 'POST',
976 );
977 return $self->response->header('Location');
978}
979
980sub put {
981 my $self = shift;
982 $self->_set(
983 @_,
984 method => 'PUT',
985 );
986 return $self->response->header('Location');
987}
988
989sub delete {
990 my ($self, %args) = @_;
991 have_required_args(\%args, qw(
992 link
993 )) or die;
994 my ($link) = $args{link};
995
996 eval {
997 $self->_request(
998 method => 'DELETE',
999 link => $link,
1000 headers => { 'Content-Length' => 0 },
1001 );
1002 };
1003 if ( my $e = $EVAL_ERROR ) {
1004 if (blessed($e) && $e->isa('Percona::WebAPI::Exception::Request')) {
1005 die $e;
1006 }
1007 else {
1008 die "Unknown error: $e";
1009 }
1010 }
1011
1012 return;
1013}
1014
1015sub _set {
1016 my ($self, %args) = @_;
1017 have_required_args(\%args, qw(
1018 method
1019 resources
1020 link
1021 )) or die;
1022 my $method = $args{method};
1023 my $res = $args{resources};
1024 my $link = $args{link};
1025
1026 my $headers = $args{headers};
1027
1028 my $content = '';
1029 if ( ref($res) eq 'ARRAY' ) {
1030 PTDEBUG && _d('List of resources');
1031 $content = '[' . join(",\n", map { as_json($_) } @$res) . ']';
1032 }
1033 elsif ( ref($res) ) {
1034 PTDEBUG && _d('Resource object');
1035 $content = as_json($res);
1036 }
1037 elsif ( $res !~ m/\n/ && -f $res ) {
1038 PTDEBUG && _d('List of resources in file', $res);
1039 $content = '[';
1040 my $data = do {
1041 local $INPUT_RECORD_SEPARATOR = undef;
1042 open my $fh, '<', $res
1043 or die "Error opening $res: $OS_ERROR";
1044 <$fh>;
1045 };
1046 $data =~ s/,?\s*$/]/;
1047 $content .= $data;
1048 }
1049 else {
1050 PTDEBUG && _d('Resource text');
1051 $content = $res;
1052 }
1053
1054 eval {
1055 $self->_request(
1056 method => $method,
1057 link => $link,
1058 content => $content,
1059 headers => $headers,
1060 );
1061 };
1062 if ( my $e = $EVAL_ERROR ) {
1063 if (blessed($e) && $e->isa('Percona::WebAPI::Exception::Request')) {
1064 die $e;
1065 }
1066 else {
1067 die "Unknown error: $e";
1068 }
1069 }
1070
1071 return;
1072}
1073
1074sub _request {
1075 my ($self, %args) = @_;
1076
1077 have_required_args(\%args, qw(
1078 method
1079 link
1080 )) or die;
1081 my $method = $args{method};
1082 my $link = $args{link};
1083
1084 my $content = $args{content};
1085 my $headers = $args{headers};
1086
1087 my $req = HTTP::Request->new($method => $link);
1088 if ( $content ) {
1089 $req->content($content);
1090 }
1091 if ( $headers ) {
1092 map { $req->header($_ => $headers->{$_}) } keys %$headers;
1093 }
1094 PTDEBUG && _d('Request', $method, $link, Dumper($req));
1095
1096 my $response = $self->ua->request($req);
1097 PTDEBUG && _d('Response', Dumper($response));
1098
1099 $self->response($response);
1100
1101 if ( !($response->code >= 200 && $response->code < 400) ) {
1102 die Percona::WebAPI::Exception::Request->new(
1103 method => $method,
1104 url => $link,
1105 content => $content,
1106 status => $response->code,
1107 error => "Failed to $method $link",
1108 );
1109 }
1110
1111 return;
1112}
1113
1114no Lmo;
11151;
1116}
1117# ###########################################################################
1118# End Percona::WebAPI::Client package
1119# ###########################################################################
1120
1121# ###########################################################################
1122# Percona::WebAPI::Exception::Request package
1123# This package is a copy without comments from the original. The original
1124# with comments and its test file can be found in the Bazaar repository at,
1125# lib/Percona/WebAPI/Exception/Request.pm
1126# t/lib/Percona/WebAPI/Exception/Request.t
1127# See https://launchpad.net/percona-toolkit for more information.
1128# ###########################################################################
1129{
1130package Percona::WebAPI::Exception::Request;
1131
1132use Lmo;
1133use overload '""' => \&as_string;
1134
1135has 'method' => (
1136 is => 'ro',
1137 isa => 'Str',
1138 required => 1,
1139);
1140
1141has 'url' => (
1142 is => 'ro',
1143 isa => 'Str',
1144 required => 1,
1145);
1146
1147has 'content' => (
1148 is => 'ro',
1149 isa => 'Maybe[Str]',
1150 required => 0,
1151);
1152
1153has 'status' => (
1154 is => 'ro',
1155 isa => 'Int',
1156 required => 1,
1157);
1158
1159has 'error' => (
1160 is => 'ro',
1161 isa => 'Str',
1162 required => 1,
1163);
1164
1165sub as_string {
1166 my $self = shift;
1167 chomp(my $error = $self->error);
1168 $error =~ s/\n/ /g;
1169 return sprintf "%s\nRequest: %s %s %s\nStatus: %d\n",
1170 $error, $self->method, $self->url, $self->content || '', $self->status;
1171}
1172
1173no Lmo;
11741;
1175}
1176# ###########################################################################
1177# End Percona::WebAPI::Exception::Request package
1178# ###########################################################################
1179
1180# ###########################################################################
1181# Percona::WebAPI::Exception::Resource package
1182# This package is a copy without comments from the original. The original
1183# with comments and its test file can be found in the Bazaar repository at,
1184# lib/Percona/WebAPI/Exception/Resource.pm
1185# t/lib/Percona/WebAPI/Exception/Resource.t
1186# See https://launchpad.net/percona-toolkit for more information.
1187# ###########################################################################
1188{
1189package Percona::WebAPI::Exception::Resource;
1190
1191use Lmo;
1192use overload '""' => \&as_string;
1193use Data::Dumper;
1194
1195has 'type' => (
1196 is => 'ro',
1197 isa => 'Str',
1198 required => 1,
1199);
1200
1201has 'link' => (
1202 is => 'ro',
1203 isa => 'Str',
1204 required => 1,
1205);
1206
1207has 'data' => (
1208 is => 'ro',
1209 isa => 'ArrayRef',
1210 required => 1,
1211);
1212
1213has 'error' => (
1214 is => 'ro',
1215 isa => 'Str',
1216 required => 1,
1217);
1218
1219sub as_string {
1220 my $self = shift;
1221 chomp(my $error = $self->error);
1222 local $Data::Dumper::Indent = 1;
1223 local $Data::Dumper::Sortkeys = 1;
1224 local $Data::Dumper::Quotekeys = 0;
1225 return sprintf "Invalid %s resource from %s:\n\n%s\nError: %s\n\n",
1226 $self->type, $self->link, Dumper($self->data), $error;
1227}
1228
1229no Lmo;
12301;
1231}
1232# ###########################################################################
1233# End Percona::WebAPI::Exception::Resource package
1234# ###########################################################################
1235
1236# ###########################################################################
1237# Percona::WebAPI::Resource::Agent package
1238# This package is a copy without comments from the original. The original
1239# with comments and its test file can be found in the Bazaar repository at,
1240# lib/Percona/WebAPI/Resource/Agent.pm
1241# t/lib/Percona/WebAPI/Resource/Agent.t
1242# See https://launchpad.net/percona-toolkit for more information.
1243# ###########################################################################
1244{
1245package Percona::WebAPI::Resource::Agent;
1246
1247use Lmo;
1248
1249has 'uuid' => (
1250 is => 'ro',
1251 isa => 'Str',
1252 required => 0,
1253);
1254
1255has 'username' => (
1256 is => 'rw',
1257 isa => 'Str',
1258 required => 0,
1259 default => sub { return $ENV{USER} || $ENV{LOGNAME} },
1260);
1261
1262has 'hostname' => (
1263 is => 'rw',
1264 isa => 'Str',
1265 required => 0,
1266 default => sub {
1267 chomp(my $hostname = `hostname`);
1268 return $hostname;
1269 },
1270);
1271
1272has 'alias' => (
1273 is => 'rw',
1274 isa => 'Str',
1275 required => 0,
1276);
1277
1278has 'versions' => (
1279 is => 'rw',
1280 isa => 'Maybe[HashRef]',
1281 required => 0,
1282);
1283
1284has 'links' => (
1285 is => 'rw',
1286 isa => 'Maybe[HashRef]',
1287 required => 0,
1288 default => sub { return {} },
1289);
1290
1291sub name {
1292 my ($self) = @_;
1293 return $self->alias || $self->hostname || $self->uuid || 'Unknown';
1294}
1295
1296no Lmo;
12971;
1298}
1299# ###########################################################################
1300# End Percona::WebAPI::Resource::Agent package
1301# ###########################################################################
1302
1303# ###########################################################################
1304# Percona::WebAPI::Resource::Config package
1305# This package is a copy without comments from the original. The original
1306# with comments and its test file can be found in the Bazaar repository at,
1307# lib/Percona/WebAPI/Resource/Config.pm
1308# t/lib/Percona/WebAPI/Resource/Config.t
1309# See https://launchpad.net/percona-toolkit for more information.
1310# ###########################################################################
1311{
1312package Percona::WebAPI::Resource::Config;
1313
1314use Lmo;
1315
1316has 'ts' => (
1317 is => 'ro',
1318 isa => 'Int',
1319 required => 1,
1320);
1321
1322has 'name' => (
1323 is => 'ro',
1324 isa => 'Str',
1325 required => 1,
1326);
1327
1328has 'options' => (
1329 is => 'ro',
1330 isa => 'HashRef',
1331 required => 1,
1332);
1333
1334has 'links' => (
1335 is => 'rw',
1336 isa => 'Maybe[HashRef]',
1337 required => 0,
1338 default => sub { return {} },
1339);
1340
1341no Lmo;
13421;
1343}
1344# ###########################################################################
1345# End Percona::WebAPI::Resource::Config package
1346# ###########################################################################
1347
1348# ###########################################################################
1349# Percona::WebAPI::Resource::Service package
1350# This package is a copy without comments from the original. The original
1351# with comments and its test file can be found in the Bazaar repository at,
1352# lib/Percona/WebAPI/Resource/Service.pm
1353# t/lib/Percona/WebAPI/Resource/Service.t
1354# See https://launchpad.net/percona-toolkit for more information.
1355# ###########################################################################
1356{
1357package Percona::WebAPI::Resource::Service;
1358
1359use Lmo;
1360
1361has 'ts' => (
1362 is => 'ro',
1363 isa => 'Int',
1364 required => 1,
1365);
1366
1367has 'name' => (
1368 is => 'ro',
1369 isa => 'Str',
1370 required => 1,
1371);
1372
1373has 'tasks' => (
1374 is => 'ro',
1375 isa => 'ArrayRef[Percona::WebAPI::Resource::Task]',
1376 required => 1,
1377);
1378
1379has 'run_schedule' => (
1380 is => 'ro',
1381 isa => 'Str',
1382 required => 0,
1383);
1384
1385has 'spool_schedule' => (
1386 is => 'ro',
1387 isa => 'Str',
1388 required => 0,
1389);
1390
1391has 'meta' => (
1392 is => 'ro',
1393 isa => 'Bool',
1394 required => 0,
1395 default => sub { return 0 },
1396);
1397
1398has 'run_once' => (
1399 is => 'ro',
1400 isa => 'Bool',
1401 required => 0,
1402 default => sub { return 0 },
1403);
1404
1405has 'links' => (
1406 is => 'rw',
1407 isa => 'Maybe[HashRef]',
1408 required => 0,
1409 default => sub { return {} },
1410);
1411
1412sub BUILDARGS {
1413 my ($class, %args) = @_;
1414 if ( ref $args{tasks} eq 'ARRAY' ) {
1415 my @tasks;
1416 foreach my $run_hashref ( @{$args{tasks}} ) {
1417 my $task = Percona::WebAPI::Resource::Task->new(%$run_hashref);
1418 push @tasks, $task;
1419 }
1420 $args{tasks} = \@tasks;
1421 }
1422 return $class->SUPER::BUILDARGS(%args);
1423}
1424
1425no Lmo;
14261;
1427}
1428# ###########################################################################
1429# End Percona::WebAPI::Resource::Service package
1430# ###########################################################################
1431
1432# ###########################################################################
1433# Percona::WebAPI::Resource::Task package
1434# This package is a copy without comments from the original. The original
1435# with comments and its test file can be found in the Bazaar repository at,
1436# lib/Percona/WebAPI/Resource/Task.pm
1437# t/lib/Percona/WebAPI/Resource/Task.t
1438# See https://launchpad.net/percona-toolkit for more information.
1439# ###########################################################################
1440{
1441package Percona::WebAPI::Resource::Task;
1442
1443use Lmo;
1444
1445has 'name' => (
1446 is => 'ro',
1447 isa => 'Str',
1448 required => 1,
1449);
1450
1451has 'number' => (
1452 is => 'ro',
1453 isa => 'Int',
1454 required => 1,
1455);
1456
1457has 'program' => (
1458 is => 'ro',
1459 isa => 'Maybe[Str]',
1460 required => 0,
1461);
1462
1463has 'query' => (
1464 is => 'ro',
1465 isa => 'Maybe[Str]',
1466 required => 0,
1467);
1468
1469has 'output' => (
1470 is => 'ro',
1471 isa => 'Maybe[Str]',
1472 required => 0,
1473);
1474
1475sub TO_JSON { return { %{ shift() } }; }
1476
1477no Lmo;
14781;
1479}
1480# ###########################################################################
1481# End Percona::WebAPI::Resource::Task package
1482# ###########################################################################
1483
1484# ###########################################################################
1485# Percona::WebAPI::Resource::LogEntry package
1486# This package is a copy without comments from the original. The original
1487# with comments and its test file can be found in the Bazaar repository at,
1488# lib/Percona/WebAPI/Resource/LogEntry.pm
1489# t/lib/Percona/WebAPI/Resource/LogEntry.t
1490# See https://launchpad.net/percona-toolkit for more information.
1491# ###########################################################################
1492{
1493package Percona::WebAPI::Resource::LogEntry;
1494
1495use Lmo;
1496
1497has 'pid' => (
1498 is => 'ro',
1499 isa => 'Int',
1500 required => 1,
1501);
1502
1503has 'service' => (
1504 is => 'ro',
1505 isa => 'Str',
1506 required => 0,
1507);
1508
1509has 'data_ts' => (
1510 is => 'ro',
1511 isa => 'Int',
1512 required => 0,
1513);
1514
1515has 'entry_ts' => (
1516 is => 'ro',
1517 isa => 'Str',
1518 required => 1,
1519);
1520
1521has 'log_level' => (
1522 is => 'ro',
1523 isa => 'Int',
1524 required => 1,
1525);
1526
1527has 'message' => (
1528 is => 'ro',
1529 isa => 'Str',
1530 required => 1,
1531);
1532
1533no Lmo;
15341;
1535}
1536# ###########################################################################
1537# End Percona::WebAPI::Resource::LogEntry package
1538# ###########################################################################
1539
1540# ###########################################################################
1541# VersionCheck package
1542# This package is a copy without comments from the original. The original
1543# with comments and its test file can be found in the Bazaar repository at,
1544# lib/VersionCheck.pm
1545# t/lib/VersionCheck.t
1546# See https://launchpad.net/percona-toolkit for more information.
1547# ###########################################################################
1548{
1549package VersionCheck;
1550
1551
1552use strict;
1553use warnings FATAL => 'all';
1554use English qw(-no_match_vars);
1555
1556use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1557
1558use Data::Dumper;
1559local $Data::Dumper::Indent = 1;
1560local $Data::Dumper::Sortkeys = 1;
1561local $Data::Dumper::Quotekeys = 0;
1562
1563use Digest::MD5 qw(md5_hex);
1564use Sys::Hostname qw(hostname);
1565use File::Basename qw();
1566use File::Spec;
1567use FindBin qw();
1568
1569eval {
1570 require Percona::Toolkit;
1571 require HTTP::Micro;
1572};
1573
1574{
1575 my $file = 'percona-version-check';
1576 my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
1577 my @vc_dirs = (
1578 '/etc/percona',
1579 '/etc/percona-toolkit',
1580 '/tmp',
1581 "$home",
1582 );
1583
1584 sub version_check_file {
1585 foreach my $dir ( @vc_dirs ) {
1586 if ( -d $dir && -w $dir ) {
1587 PTDEBUG && _d('Version check file', $file, 'in', $dir);
1588 return $dir . '/' . $file;
1589 }
1590 }
1591 PTDEBUG && _d('Version check file', $file, 'in', $ENV{PWD});
1592 return $file; # in the CWD
1593 }
1594}
1595
1596sub version_check_time_limit {
1597 return 60 * 60 * 24; # one day
1598}
1599
1600
1601sub version_check {
1602 my (%args) = @_;
1603
1604 my $instances = $args{instances} || [];
1605 my $instances_to_check;
1606
1607 PTDEBUG && _d('FindBin::Bin:', $FindBin::Bin);
1608 if ( !$args{force} ) {
1609 if ( $FindBin::Bin
1610 && (-d "$FindBin::Bin/../.bzr" || -d "$FindBin::Bin/../../.bzr") ) {
1611 PTDEBUG && _d("$FindBin::Bin/../.bzr disables --version-check");
1612 return;
1613 }
1614 }
1615
1616 eval {
1617 foreach my $instance ( @$instances ) {
1618 my ($name, $id) = get_instance_id($instance);
1619 $instance->{name} = $name;
1620 $instance->{id} = $id;
1621 }
1622
1623 push @$instances, { name => 'system', id => 0 };
1624
1625 $instances_to_check = get_instances_to_check(
1626 instances => $instances,
1627 vc_file => $args{vc_file}, # testing
1628 now => $args{now}, # testing
1629 );
1630 PTDEBUG && _d(scalar @$instances_to_check, 'instances to check');
1631 return unless @$instances_to_check;
1632
1633 my $protocol = 'https'; # optimistic, but...
1634 eval { require IO::Socket::SSL; };
1635 if ( $EVAL_ERROR ) {
1636 PTDEBUG && _d($EVAL_ERROR);
1637 $protocol = 'http';
1638 }
1639 PTDEBUG && _d('Using', $protocol);
1640
1641 my $advice = pingback(
1642 instances => $instances_to_check,
1643 protocol => $protocol,
1644 url => $args{url} # testing
1645 || $ENV{PERCONA_VERSION_CHECK_URL} # testing
1646 || "$protocol://v.percona.com",
1647 );
1648 if ( $advice ) {
1649 PTDEBUG && _d('Advice:', Dumper($advice));
1650 if ( scalar @$advice > 1) {
1651 print "\n# " . scalar @$advice . " software updates are "
1652 . "available:\n";
1653 }
1654 else {
1655 print "\n# A software update is available:\n";
1656 }
1657 print join("\n", map { "# * $_" } @$advice), "\n\n";
1658 }
1659 };
1660 if ( $EVAL_ERROR ) {
1661 PTDEBUG && _d('Version check failed:', $EVAL_ERROR);
1662 }
1663
1664 if ( @$instances_to_check ) {
1665 eval {
1666 update_check_times(
1667 instances => $instances_to_check,
1668 vc_file => $args{vc_file}, # testing
1669 now => $args{now}, # testing
1670 );
1671 };
1672 if ( $EVAL_ERROR ) {
1673 PTDEBUG && _d('Error updating version check file:', $EVAL_ERROR);
1674 }
1675 }
1676
1677 if ( $ENV{PTDEBUG_VERSION_CHECK} ) {
1678 warn "Exiting because the PTDEBUG_VERSION_CHECK "
1679 . "environment variable is defined.\n";
1680 exit 255;
1681 }
1682
1683 return;
1684}
1685
1686sub get_instances_to_check {
1687 my (%args) = @_;
1688
1689 my $instances = $args{instances};
1690 my $now = $args{now} || int(time);
1691 my $vc_file = $args{vc_file} || version_check_file();
1692
1693 if ( !-f $vc_file ) {
1694 PTDEBUG && _d('Version check file', $vc_file, 'does not exist;',
1695 'version checking all instances');
1696 return $instances;
1697 }
1698
1699 open my $fh, '<', $vc_file or die "Cannot open $vc_file: $OS_ERROR";
1700 chomp(my $file_contents = do { local $/ = undef; <$fh> });
1701 PTDEBUG && _d('Version check file', $vc_file, 'contents:', $file_contents);
1702 close $fh;
1703 my %last_check_time_for = $file_contents =~ /^([^,]+),(.+)$/mg;
1704
1705 my $check_time_limit = version_check_time_limit();
1706 my @instances_to_check;
1707 foreach my $instance ( @$instances ) {
1708 my $last_check_time = $last_check_time_for{ $instance->{id} };
1709 PTDEBUG && _d('Intsance', $instance->{id}, 'last checked',
1710 $last_check_time, 'now', $now, 'diff', $now - ($last_check_time || 0),
1711 'hours until next check',
1712 sprintf '%.2f',
1713 ($check_time_limit - ($now - ($last_check_time || 0))) / 3600);
1714 if ( !defined $last_check_time
1715 || ($now - $last_check_time) >= $check_time_limit ) {
1716 PTDEBUG && _d('Time to check', Dumper($instance));
1717 push @instances_to_check, $instance;
1718 }
1719 }
1720
1721 return \@instances_to_check;
1722}
1723
1724sub update_check_times {
1725 my (%args) = @_;
1726
1727 my $instances = $args{instances};
1728 my $now = $args{now} || int(time);
1729 my $vc_file = $args{vc_file} || version_check_file();
1730 PTDEBUG && _d('Updating last check time:', $now);
1731
1732 my %all_instances = map {
1733 $_->{id} => { name => $_->{name}, ts => $now }
1734 } @$instances;
1735
1736 if ( -f $vc_file ) {
1737 open my $fh, '<', $vc_file or die "Cannot read $vc_file: $OS_ERROR";
1738 my $contents = do { local $/ = undef; <$fh> };
1739 close $fh;
1740
1741 foreach my $line ( split("\n", ($contents || '')) ) {
1742 my ($id, $ts) = split(',', $line);
1743 if ( !exists $all_instances{$id} ) {
1744 $all_instances{$id} = { ts => $ts }; # original ts, not updated
1745 }
1746 }
1747 }
1748
1749 open my $fh, '>', $vc_file or die "Cannot write to $vc_file: $OS_ERROR";
1750 foreach my $id ( sort keys %all_instances ) {
1751 PTDEBUG && _d('Updated:', $id, Dumper($all_instances{$id}));
1752 print { $fh } $id . ',' . $all_instances{$id}->{ts} . "\n";
1753 }
1754 close $fh;
1755
1756 return;
1757}
1758
1759sub get_instance_id {
1760 my ($instance) = @_;
1761
1762 my $dbh = $instance->{dbh};
1763 my $dsn = $instance->{dsn};
1764
1765 my $sql = q{SELECT CONCAT(@@hostname, @@port)};
1766 PTDEBUG && _d($sql);
1767 my ($name) = eval { $dbh->selectrow_array($sql) };
1768 if ( $EVAL_ERROR ) {
1769 PTDEBUG && _d($EVAL_ERROR);
1770 $sql = q{SELECT @@hostname};
1771 PTDEBUG && _d($sql);
1772 ($name) = eval { $dbh->selectrow_array($sql) };
1773 if ( $EVAL_ERROR ) {
1774 PTDEBUG && _d($EVAL_ERROR);
1775 $name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306);
1776 }
1777 else {
1778 $sql = q{SHOW VARIABLES LIKE 'port'};
1779 PTDEBUG && _d($sql);
1780 my (undef, $port) = eval { $dbh->selectrow_array($sql) };
1781 PTDEBUG && _d('port:', $port);
1782 $name .= $port || '';
1783 }
1784 }
1785 my $id = md5_hex($name);
1786
1787 PTDEBUG && _d('MySQL instance:', $id, $name, Dumper($dsn));
1788
1789 return $name, $id;
1790}
1791
1792
1793sub pingback {
1794 my (%args) = @_;
1795 my @required_args = qw(url instances);
1796 foreach my $arg ( @required_args ) {
1797 die "I need a $arg arugment" unless $args{$arg};
1798 }
1799 my $url = $args{url};
1800 my $instances = $args{instances};
1801
1802 my $ua = $args{ua} || HTTP::Micro->new( timeout => 3 );
1803
1804 my $response = $ua->request('GET', $url);
1805 PTDEBUG && _d('Server response:', Dumper($response));
1806 die "No response from GET $url"
1807 if !$response;
1808 die("GET on $url returned HTTP status $response->{status}; expected 200\n",
1809 ($response->{content} || '')) if $response->{status} != 200;
1810 die("GET on $url did not return any programs to check")
1811 if !$response->{content};
1812
1813 my $items = parse_server_response(
1814 response => $response->{content}
1815 );
1816 die "Failed to parse server requested programs: $response->{content}"
1817 if !scalar keys %$items;
1818
1819 my $versions = get_versions(
1820 items => $items,
1821 instances => $instances,
1822 );
1823 die "Failed to get any program versions; should have at least gotten Perl"
1824 if !scalar keys %$versions;
1825
1826 my $client_content = encode_client_response(
1827 items => $items,
1828 versions => $versions,
1829 general_id => md5_hex( hostname() ),
1830 );
1831
1832 my $client_response = {
1833 headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
1834 content => $client_content,
1835 };
1836 PTDEBUG && _d('Client response:', Dumper($client_response));
1837
1838 $response = $ua->request('POST', $url, $client_response);
1839 PTDEBUG && _d('Server suggestions:', Dumper($response));
1840 die "No response from POST $url $client_response"
1841 if !$response;
1842 die "POST $url returned HTTP status $response->{status}; expected 200"
1843 if $response->{status} != 200;
1844
1845 return unless $response->{content};
1846
1847 $items = parse_server_response(
1848 response => $response->{content},
1849 split_vars => 0,
1850 );
1851 die "Failed to parse server suggestions: $response->{content}"
1852 if !scalar keys %$items;
1853 my @suggestions = map { $_->{vars} }
1854 sort { $a->{item} cmp $b->{item} }
1855 values %$items;
1856
1857 return \@suggestions;
1858}
1859
1860sub encode_client_response {
1861 my (%args) = @_;
1862 my @required_args = qw(items versions general_id);
1863 foreach my $arg ( @required_args ) {
1864 die "I need a $arg arugment" unless $args{$arg};
1865 }
1866 my ($items, $versions, $general_id) = @args{@required_args};
1867
1868 my @lines;
1869 foreach my $item ( sort keys %$items ) {
1870 next unless exists $versions->{$item};
1871 if ( ref($versions->{$item}) eq 'HASH' ) {
1872 my $mysql_versions = $versions->{$item};
1873 for my $id ( sort keys %$mysql_versions ) {
1874 push @lines, join(';', $id, $item, $mysql_versions->{$id});
1875 }
1876 }
1877 else {
1878 push @lines, join(';', $general_id, $item, $versions->{$item});
1879 }
1880 }
1881
1882 my $client_response = join("\n", @lines) . "\n";
1883 return $client_response;
1884}
1885
1886sub parse_server_response {
1887 my (%args) = @_;
1888 my @required_args = qw(response);
1889 foreach my $arg ( @required_args ) {
1890 die "I need a $arg arugment" unless $args{$arg};
1891 }
1892 my ($response) = @args{@required_args};
1893
1894 my %items = map {
1895 my ($item, $type, $vars) = split(";", $_);
1896 if ( !defined $args{split_vars} || $args{split_vars} ) {
1897 $vars = [ split(",", ($vars || '')) ];
1898 }
1899 $item => {
1900 item => $item,
1901 type => $type,
1902 vars => $vars,
1903 };
1904 } split("\n", $response);
1905
1906 PTDEBUG && _d('Items:', Dumper(\%items));
1907
1908 return \%items;
1909}
1910
1911my %sub_for_type = (
1912 os_version => \&get_os_version,
1913 perl_version => \&get_perl_version,
1914 perl_module_version => \&get_perl_module_version,
1915 mysql_variable => \&get_mysql_variable,
1916);
1917
1918sub valid_item {
1919 my ($item) = @_;
1920 return unless $item;
1921 if ( !exists $sub_for_type{ $item->{type} } ) {
1922 PTDEBUG && _d('Invalid type:', $item->{type});
1923 return 0;
1924 }
1925 return 1;
1926}
1927
1928sub get_versions {
1929 my (%args) = @_;
1930 my @required_args = qw(items);
1931 foreach my $arg ( @required_args ) {
1932 die "I need a $arg arugment" unless $args{$arg};
1933 }
1934 my ($items) = @args{@required_args};
1935
1936 my %versions;
1937 foreach my $item ( values %$items ) {
1938 next unless valid_item($item);
1939 eval {
1940 my $version = $sub_for_type{ $item->{type} }->(
1941 item => $item,
1942 instances => $args{instances},
1943 );
1944 if ( $version ) {
1945 chomp $version unless ref($version);
1946 $versions{$item->{item}} = $version;
1947 }
1948 };
1949 if ( $EVAL_ERROR ) {
1950 PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
1951 }
1952 }
1953
1954 return \%versions;
1955}
1956
1957
1958sub get_os_version {
1959 if ( $OSNAME eq 'MSWin32' ) {
1960 require Win32;
1961 return Win32::GetOSDisplayName();
1962 }
1963
1964 chomp(my $platform = `uname -s`);
1965 PTDEBUG && _d('platform:', $platform);
1966 return $OSNAME unless $platform;
1967
1968 chomp(my $lsb_release
1969 = `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
1970 PTDEBUG && _d('lsb_release:', $lsb_release);
1971
1972 my $release = "";
1973
1974 if ( $platform eq 'Linux' ) {
1975 if ( -f "/etc/fedora-release" ) {
1976 $release = `cat /etc/fedora-release`;
1977 }
1978 elsif ( -f "/etc/redhat-release" ) {
1979 $release = `cat /etc/redhat-release`;
1980 }
1981 elsif ( -f "/etc/system-release" ) {
1982 $release = `cat /etc/system-release`;
1983 }
1984 elsif ( $lsb_release ) {
1985 $release = `$lsb_release -ds`;
1986 }
1987 elsif ( -f "/etc/lsb-release" ) {
1988 $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
1989 $release =~ s/^\w+="([^"]+)".+/$1/;
1990 }
1991 elsif ( -f "/etc/debian_version" ) {
1992 chomp(my $rel = `cat /etc/debian_version`);
1993 $release = "Debian $rel";
1994 if ( -f "/etc/apt/sources.list" ) {
1995 chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
1996 $release .= " ($code_name)" if $code_name;
1997 }
1998 }
1999 elsif ( -f "/etc/os-release" ) { # openSUSE
2000 chomp($release = `grep PRETTY_NAME /etc/os-release`);
2001 $release =~ s/^PRETTY_NAME="(.+)"$/$1/;
2002 }
2003 elsif ( `ls /etc/*release 2>/dev/null` ) {
2004 if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
2005 $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
2006 }
2007 else {
2008 $release = `cat /etc/*release | head -n1`;
2009 }
2010 }
2011 }
2012 elsif ( $platform =~ m/(?:BSD|^Darwin)$/ ) {
2013 my $rel = `uname -r`;
2014 $release = "$platform $rel";
2015 }
2016 elsif ( $platform eq "SunOS" ) {
2017 my $rel = `head -n1 /etc/release` || `uname -r`;
2018 $release = "$platform $rel";
2019 }
2020
2021 if ( !$release ) {
2022 PTDEBUG && _d('Failed to get the release, using platform');
2023 $release = $platform;
2024 }
2025 chomp($release);
2026
2027 $release =~ s/^"|"$//g;
2028
2029 PTDEBUG && _d('OS version =', $release);
2030 return $release;
2031}
2032
2033sub get_perl_version {
2034 my (%args) = @_;
2035 my $item = $args{item};
2036 return unless $item;
2037
2038 my $version = sprintf '%vd', $PERL_VERSION;
2039 PTDEBUG && _d('Perl version', $version);
2040 return $version;
2041}
2042
2043sub get_perl_module_version {
2044 my (%args) = @_;
2045 my $item = $args{item};
2046 return unless $item;
2047
2048 my $var = '$' . $item->{item} . '::VERSION';
2049 my $version = eval "use $item->{item}; $var;";
2050 PTDEBUG && _d('Perl version for', $var, '=', $version);
2051 return $version;
2052}
2053
2054sub get_mysql_variable {
2055 return get_from_mysql(
2056 show => 'VARIABLES',
2057 @_,
2058 );
2059}
2060
2061sub get_from_mysql {
2062 my (%args) = @_;
2063 my $show = $args{show};
2064 my $item = $args{item};
2065 my $instances = $args{instances};
2066 return unless $show && $item;
2067
2068 if ( !$instances || !@$instances ) {
2069 PTDEBUG && _d('Cannot check', $item,
2070 'because there are no MySQL instances');
2071 return;
2072 }
2073
2074 my @versions;
2075 my %version_for;
2076 foreach my $instance ( @$instances ) {
2077 next unless $instance->{id}; # special system instance has id=0
2078 my $dbh = $instance->{dbh};
2079 local $dbh->{FetchHashKeyName} = 'NAME_lc';
2080 my $sql = qq/SHOW $show/;
2081 PTDEBUG && _d($sql);
2082 my $rows = $dbh->selectall_hashref($sql, 'variable_name');
2083
2084 my @versions;
2085 foreach my $var ( @{$item->{vars}} ) {
2086 $var = lc($var);
2087 my $version = $rows->{$var}->{value};
2088 PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version,
2089 'on', $instance->{name});
2090 push @versions, $version;
2091 }
2092 $version_for{ $instance->{id} } = join(' ', @versions);
2093 }
2094
2095 return \%version_for;
2096}
2097
2098sub _d {
2099 my ($package, undef, $line) = caller 0;
2100 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2101 map { defined $_ ? $_ : 'undef' }
2102 @_;
2103 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2104}
2105
21061;
2107}
2108# ###########################################################################
2109# End VersionCheck package
2110# ###########################################################################
2111
2112# ###########################################################################
2113# DSNParser package
2114# This package is a copy without comments from the original. The original
2115# with comments and its test file can be found in the Bazaar repository at,
2116# lib/DSNParser.pm
2117# t/lib/DSNParser.t
2118# See https://launchpad.net/percona-toolkit for more information.
2119# ###########################################################################
2120{
2121package DSNParser;
2122
2123use strict;
2124use warnings FATAL => 'all';
2125use English qw(-no_match_vars);
2126use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2127
2128use Data::Dumper;
2129$Data::Dumper::Indent = 0;
2130$Data::Dumper::Quotekeys = 0;
2131
2132my $dsn_sep = qr/(?<!\\),/;
2133
2134eval {
2135 require DBI;
2136};
2137my $have_dbi = $EVAL_ERROR ? 0 : 1;
2138
2139sub new {
2140 my ( $class, %args ) = @_;
2141 foreach my $arg ( qw(opts) ) {
2142 die "I need a $arg argument" unless $args{$arg};
2143 }
2144 my $self = {
2145 opts => {} # h, P, u, etc. Should come from DSN OPTIONS section in POD.
2146 };
2147 foreach my $opt ( @{$args{opts}} ) {
2148 if ( !$opt->{key} || !$opt->{desc} ) {
2149 die "Invalid DSN option: ", Dumper($opt);
2150 }
2151 PTDEBUG && _d('DSN option:',
2152 join(', ',
2153 map { "$_=" . (defined $opt->{$_} ? ($opt->{$_} || '') : 'undef') }
2154 keys %$opt
2155 )
2156 );
2157 $self->{opts}->{$opt->{key}} = {
2158 dsn => $opt->{dsn},
2159 desc => $opt->{desc},
2160 copy => $opt->{copy} || 0,
2161 };
2162 }
2163 return bless $self, $class;
2164}
2165
2166sub prop {
2167 my ( $self, $prop, $value ) = @_;
2168 if ( @_ > 2 ) {
2169 PTDEBUG && _d('Setting', $prop, 'property');
2170 $self->{$prop} = $value;
2171 }
2172 return $self->{$prop};
2173}
2174
2175sub parse {
2176 my ( $self, $dsn, $prev, $defaults ) = @_;
2177 if ( !$dsn ) {
2178 PTDEBUG && _d('No DSN to parse');
2179 return;
2180 }
2181 PTDEBUG && _d('Parsing', $dsn);
2182 $prev ||= {};
2183 $defaults ||= {};
2184 my %given_props;
2185 my %final_props;
2186 my $opts = $self->{opts};
2187
2188 foreach my $dsn_part ( split($dsn_sep, $dsn) ) {
2189 $dsn_part =~ s/\\,/,/g;
2190 if ( my ($prop_key, $prop_val) = $dsn_part =~ m/^(.)=(.*)$/ ) {
2191 $given_props{$prop_key} = $prop_val;
2192 }
2193 else {
2194 PTDEBUG && _d('Interpreting', $dsn_part, 'as h=', $dsn_part);
2195 $given_props{h} = $dsn_part;
2196 }
2197 }
2198
2199 foreach my $key ( keys %$opts ) {
2200 PTDEBUG && _d('Finding value for', $key);
2201 $final_props{$key} = $given_props{$key};
2202 if ( !defined $final_props{$key}
2203 && defined $prev->{$key} && $opts->{$key}->{copy} )
2204 {
2205 $final_props{$key} = $prev->{$key};
2206 PTDEBUG && _d('Copying value for', $key, 'from previous DSN');
2207 }
2208 if ( !defined $final_props{$key} ) {
2209 $final_props{$key} = $defaults->{$key};
2210 PTDEBUG && _d('Copying value for', $key, 'from defaults');
2211 }
2212 }
2213
2214 foreach my $key ( keys %given_props ) {
2215 die "Unknown DSN option '$key' in '$dsn'. For more details, "
2216 . "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
2217 . "for complete documentation."
2218 unless exists $opts->{$key};
2219 }
2220 if ( (my $required = $self->prop('required')) ) {
2221 foreach my $key ( keys %$required ) {
2222 die "Missing required DSN option '$key' in '$dsn'. For more details, "
2223 . "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
2224 . "for complete documentation."
2225 unless $final_props{$key};
2226 }
2227 }
2228
2229 return \%final_props;
2230}
2231
2232sub parse_options {
2233 my ( $self, $o ) = @_;
2234 die 'I need an OptionParser object' unless ref $o eq 'OptionParser';
2235 my $dsn_string
2236 = join(',',
2237 map { "$_=".$o->get($_); }
2238 grep { $o->has($_) && $o->get($_) }
2239 keys %{$self->{opts}}
2240 );
2241 PTDEBUG && _d('DSN string made from options:', $dsn_string);
2242 return $self->parse($dsn_string);
2243}
2244
2245sub as_string {
2246 my ( $self, $dsn, $props ) = @_;
2247 return $dsn unless ref $dsn;
2248 my @keys = $props ? @$props : sort keys %$dsn;
2249 return join(',',
2250 map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) }
2251 grep {
2252 exists $self->{opts}->{$_}
2253 && exists $dsn->{$_}
2254 && defined $dsn->{$_}
2255 } @keys);
2256}
2257
2258sub usage {
2259 my ( $self ) = @_;
2260 my $usage
2261 = "DSN syntax is key=value[,key=value...] Allowable DSN keys:\n\n"
2262 . " KEY COPY MEANING\n"
2263 . " === ==== =============================================\n";
2264 my %opts = %{$self->{opts}};
2265 foreach my $key ( sort keys %opts ) {
2266 $usage .= " $key "
2267 . ($opts{$key}->{copy} ? 'yes ' : 'no ')
2268 . ($opts{$key}->{desc} || '[No description]')
2269 . "\n";
2270 }
2271 $usage .= "\n If the DSN is a bareword, the word is treated as the 'h' key.\n";
2272 return $usage;
2273}
2274
2275sub get_cxn_params {
2276 my ( $self, $info ) = @_;
2277 my $dsn;
2278 my %opts = %{$self->{opts}};
2279 my $driver = $self->prop('dbidriver') || '';
2280 if ( $driver eq 'Pg' ) {
2281 $dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';'
2282 . join(';', map { "$opts{$_}->{dsn}=$info->{$_}" }
2283 grep { defined $info->{$_} }
2284 qw(h P));
2285 }
2286 else {
2287 $dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';'
2288 . join(';', map { "$opts{$_}->{dsn}=$info->{$_}" }
2289 grep { defined $info->{$_} }
2290 qw(F h P S A))
2291 . ';mysql_read_default_group=client'
2292 . ($info->{L} ? ';mysql_local_infile=1' : '');
2293 }
2294 PTDEBUG && _d($dsn);
2295 return ($dsn, $info->{u}, $info->{p});
2296}
2297
2298sub fill_in_dsn {
2299 my ( $self, $dbh, $dsn ) = @_;
2300 my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name');
2301 my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()');
2302 $user =~ s/@.*//;
2303 $dsn->{h} ||= $vars->{hostname}->{Value};
2304 $dsn->{S} ||= $vars->{'socket'}->{Value};
2305 $dsn->{P} ||= $vars->{port}->{Value};
2306 $dsn->{u} ||= $user;
2307 $dsn->{D} ||= $db;
2308}
2309
2310sub get_dbh {
2311 my ( $self, $cxn_string, $user, $pass, $opts ) = @_;
2312 $opts ||= {};
2313 my $defaults = {
2314 AutoCommit => 0,
2315 RaiseError => 1,
2316 PrintError => 0,
2317 ShowErrorStatement => 1,
2318 mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/i ? 1 : 0),
2319 };
2320 @{$defaults}{ keys %$opts } = values %$opts;
2321 if (delete $defaults->{L}) { # L for LOAD DATA LOCAL INFILE, our own extension
2322 $defaults->{mysql_local_infile} = 1;
2323 }
2324
2325 if ( $opts->{mysql_use_result} ) {
2326 $defaults->{mysql_use_result} = 1;
2327 }
2328
2329 if ( !$have_dbi ) {
2330 die "Cannot connect to MySQL because the Perl DBI module is not "
2331 . "installed or not found. Run 'perl -MDBI' to see the directories "
2332 . "that Perl searches for DBI. If DBI is not installed, try:\n"
2333 . " Debian/Ubuntu apt-get install libdbi-perl\n"
2334 . " RHEL/CentOS yum install perl-DBI\n"
2335 . " OpenSolaris pkg install pkg:/SUNWpmdbi\n";
2336
2337 }
2338
2339 my $dbh;
2340 my $tries = 2;
2341 while ( !$dbh && $tries-- ) {
2342 PTDEBUG && _d($cxn_string, ' ', $user, ' ', $pass,
2343 join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults ));
2344
2345 $dbh = eval { DBI->connect($cxn_string, $user, $pass, $defaults) };
2346
2347 if ( !$dbh && $EVAL_ERROR ) {
2348 if ( $EVAL_ERROR =~ m/locate DBD\/mysql/i ) {
2349 die "Cannot connect to MySQL because the Perl DBD::mysql module is "
2350 . "not installed or not found. Run 'perl -MDBD::mysql' to see "
2351 . "the directories that Perl searches for DBD::mysql. If "
2352 . "DBD::mysql is not installed, try:\n"
2353 . " Debian/Ubuntu apt-get install libdbd-mysql-perl\n"
2354 . " RHEL/CentOS yum install perl-DBD-MySQL\n"
2355 . " OpenSolaris pgk install pkg:/SUNWapu13dbd-mysql\n";
2356 }
2357 elsif ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) {
2358 PTDEBUG && _d('Going to try again without utf8 support');
2359 delete $defaults->{mysql_enable_utf8};
2360 }
2361 if ( !$tries ) {
2362 die $EVAL_ERROR;
2363 }
2364 }
2365 }
2366
2367 if ( $cxn_string =~ m/mysql/i ) {
2368 my $sql;
2369
2370 $sql = 'SELECT @@SQL_MODE';
2371 PTDEBUG && _d($dbh, $sql);
2372 my ($sql_mode) = eval { $dbh->selectrow_array($sql) };
2373 if ( $EVAL_ERROR ) {
2374 die "Error getting the current SQL_MODE: $EVAL_ERROR";
2375 }
2376
2377 if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) {
2378 $sql = qq{/*!40101 SET NAMES "$charset"*/};
2379 PTDEBUG && _d($dbh, $sql);
2380 eval { $dbh->do($sql) };
2381 if ( $EVAL_ERROR ) {
2382 die "Error setting NAMES to $charset: $EVAL_ERROR";
2383 }
2384 PTDEBUG && _d('Enabling charset for STDOUT');
2385 if ( $charset eq 'utf8' ) {
2386 binmode(STDOUT, ':utf8')
2387 or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR";
2388 }
2389 else {
2390 binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR";
2391 }
2392 }
2393
2394 if ( my $vars = $self->prop('set-vars') ) {
2395 $self->set_vars($dbh, $vars);
2396 }
2397
2398 $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1'
2399 . '/*!40101, @@SQL_MODE=\'NO_AUTO_VALUE_ON_ZERO'
2400 . ($sql_mode ? ",$sql_mode" : '')
2401 . '\'*/';
2402 PTDEBUG && _d($dbh, $sql);
2403 eval { $dbh->do($sql) };
2404 if ( $EVAL_ERROR ) {
2405 die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE"
2406 . ($sql_mode ? " and $sql_mode" : '')
2407 . ": $EVAL_ERROR";
2408 }
2409 }
2410
2411 PTDEBUG && _d('DBH info: ',
2412 $dbh,
2413 Dumper($dbh->selectrow_hashref(
2414 'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')),
2415 'Connection info:', $dbh->{mysql_hostinfo},
2416 'Character set info:', Dumper($dbh->selectall_arrayref(
2417 "SHOW VARIABLES LIKE 'character_set%'", { Slice => {}})),
2418 '$DBD::mysql::VERSION:', $DBD::mysql::VERSION,
2419 '$DBI::VERSION:', $DBI::VERSION,
2420 );
2421
2422 return $dbh;
2423}
2424
2425sub get_hostname {
2426 my ( $self, $dbh ) = @_;
2427 if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) {
2428 return $host;
2429 }
2430 my ( $hostname, $one ) = $dbh->selectrow_array(
2431 'SELECT /*!50038 @@hostname, */ 1');
2432 return $hostname;
2433}
2434
2435sub disconnect {
2436 my ( $self, $dbh ) = @_;
2437 PTDEBUG && $self->print_active_handles($dbh);
2438 $dbh->disconnect;
2439}
2440
2441sub print_active_handles {
2442 my ( $self, $thing, $level ) = @_;
2443 $level ||= 0;
2444 printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level,
2445 $thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : ''))
2446 or die "Cannot print: $OS_ERROR";
2447 foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) {
2448 $self->print_active_handles( $handle, $level + 1 );
2449 }
2450}
2451
2452sub copy {
2453 my ( $self, $dsn_1, $dsn_2, %args ) = @_;
2454 die 'I need a dsn_1 argument' unless $dsn_1;
2455 die 'I need a dsn_2 argument' unless $dsn_2;
2456 my %new_dsn = map {
2457 my $key = $_;
2458 my $val;
2459 if ( $args{overwrite} ) {
2460 $val = defined $dsn_1->{$key} ? $dsn_1->{$key} : $dsn_2->{$key};
2461 }
2462 else {
2463 $val = defined $dsn_2->{$key} ? $dsn_2->{$key} : $dsn_1->{$key};
2464 }
2465 $key => $val;
2466 } keys %{$self->{opts}};
2467 return \%new_dsn;
2468}
2469
2470sub set_vars {
2471 my ($self, $dbh, $vars) = @_;
2472
2473 return unless $vars;
2474
2475 foreach my $var ( sort keys %$vars ) {
2476 my $val = $vars->{$var}->{val};
2477
2478 (my $quoted_var = $var) =~ s/_/\\_/;
2479 my ($var_exists, $current_val);
2480 eval {
2481 ($var_exists, $current_val) = $dbh->selectrow_array(
2482 "SHOW VARIABLES LIKE '$quoted_var'");
2483 };
2484 my $e = $EVAL_ERROR;
2485 if ( $e ) {
2486 PTDEBUG && _d($e);
2487 }
2488
2489 if ( $vars->{$var}->{default} && !$var_exists ) {
2490 PTDEBUG && _d('Not setting default var', $var,
2491 'because it does not exist');
2492 next;
2493 }
2494
2495 if ( $current_val && $current_val eq $val ) {
2496 PTDEBUG && _d('Not setting var', $var, 'because its value',
2497 'is already', $val);
2498 next;
2499 }
2500
2501 my $sql = "SET SESSION $var=$val";
2502 PTDEBUG && _d($dbh, $sql);
2503 eval { $dbh->do($sql) };
2504 if ( my $set_error = $EVAL_ERROR ) {
2505 chomp($set_error);
2506 $set_error =~ s/ at \S+ line \d+//;
2507 my $msg = "Error setting $var: $set_error";
2508 if ( $current_val ) {
2509 $msg .= " The current value for $var is $current_val. "
2510 . "If the variable is read only (not dynamic), specify "
2511 . "--set-vars $var=$current_val to avoid this warning, "
2512 . "else manually set the variable and restart MySQL.";
2513 }
2514 warn $msg . "\n\n";
2515 }
2516 }
2517
2518 return;
2519}
2520
2521sub _d {
2522 my ($package, undef, $line) = caller 0;
2523 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2524 map { defined $_ ? $_ : 'undef' }
2525 @_;
2526 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2527}
2528
25291;
2530}
2531# ###########################################################################
2532# End DSNParser package
2533# ###########################################################################
2534
2535# ###########################################################################
2536# OptionParser package
2537# This package is a copy without comments from the original. The original
2538# with comments and its test file can be found in the Bazaar repository at,
2539# lib/OptionParser.pm
2540# t/lib/OptionParser.t
2541# See https://launchpad.net/percona-toolkit for more information.
2542# ###########################################################################
2543{
2544package OptionParser;
2545
2546use strict;
2547use warnings FATAL => 'all';
2548use English qw(-no_match_vars);
2549use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2550
2551use List::Util qw(max);
2552use Getopt::Long;
2553use Data::Dumper;
2554
2555my $POD_link_re = '[LC]<"?([^">]+)"?>';
2556
2557sub new {
2558 my ( $class, %args ) = @_;
2559 my @required_args = qw();
2560 foreach my $arg ( @required_args ) {
2561 die "I need a $arg argument" unless $args{$arg};
2562 }
2563
2564 my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/;
2565 $program_name ||= $PROGRAM_NAME;
2566 my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
2567
2568 my %attributes = (
2569 'type' => 1,
2570 'short form' => 1,
2571 'group' => 1,
2572 'default' => 1,
2573 'cumulative' => 1,
2574 'negatable' => 1,
2575 );
2576
2577 my $self = {
2578 head1 => 'OPTIONS', # These args are used internally
2579 skip_rules => 0, # to instantiate another Option-
2580 item => '--(.*)', # Parser obj that parses the
2581 attributes => \%attributes, # DSN OPTIONS section. Tools
2582 parse_attributes => \&_parse_attribs, # don't tinker with these args.
2583
2584 %args,
2585
2586 strict => 1, # disabled by a special rule
2587 program_name => $program_name,
2588 opts => {},
2589 got_opts => 0,
2590 short_opts => {},
2591 defaults => {},
2592 groups => {},
2593 allowed_groups => {},
2594 errors => [],
2595 rules => [], # desc of rules for --help
2596 mutex => [], # rule: opts are mutually exclusive
2597 atleast1 => [], # rule: at least one opt is required
2598 disables => {}, # rule: opt disables other opts
2599 defaults_to => {}, # rule: opt defaults to value of other opt
2600 DSNParser => undef,
2601 default_files => [
2602 "/etc/percona-toolkit/percona-toolkit.conf",
2603 "/etc/percona-toolkit/$program_name.conf",
2604 "$home/.percona-toolkit.conf",
2605 "$home/.$program_name.conf",
2606 ],
2607 types => {
2608 string => 's', # standard Getopt type
2609 int => 'i', # standard Getopt type
2610 float => 'f', # standard Getopt type
2611 Hash => 'H', # hash, formed from a comma-separated list
2612 hash => 'h', # hash as above, but only if a value is given
2613 Array => 'A', # array, similar to Hash
2614 array => 'a', # array, similar to hash
2615 DSN => 'd', # DSN
2616 size => 'z', # size with kMG suffix (powers of 2^10)
2617 time => 'm', # time, with an optional suffix of s/h/m/d
2618 },
2619 };
2620
2621 return bless $self, $class;
2622}
2623
2624sub get_specs {
2625 my ( $self, $file ) = @_;
2626 $file ||= $self->{file} || __FILE__;
2627 my @specs = $self->_pod_to_specs($file);
2628 $self->_parse_specs(@specs);
2629
2630 open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
2631 my $contents = do { local $/ = undef; <$fh> };
2632 close $fh;
2633 if ( $contents =~ m/^=head1 DSN OPTIONS/m ) {
2634 PTDEBUG && _d('Parsing DSN OPTIONS');
2635 my $dsn_attribs = {
2636 dsn => 1,
2637 copy => 1,
2638 };
2639 my $parse_dsn_attribs = sub {
2640 my ( $self, $option, $attribs ) = @_;
2641 map {
2642 my $val = $attribs->{$_};
2643 if ( $val ) {
2644 $val = $val eq 'yes' ? 1
2645 : $val eq 'no' ? 0
2646 : $val;
2647 $attribs->{$_} = $val;
2648 }
2649 } keys %$attribs;
2650 return {
2651 key => $option,
2652 %$attribs,
2653 };
2654 };
2655 my $dsn_o = new OptionParser(
2656 description => 'DSN OPTIONS',
2657 head1 => 'DSN OPTIONS',
2658 dsn => 0, # XXX don't infinitely recurse!
2659 item => '\* (.)', # key opts are a single character
2660 skip_rules => 1, # no rules before opts
2661 attributes => $dsn_attribs,
2662 parse_attributes => $parse_dsn_attribs,
2663 );
2664 my @dsn_opts = map {
2665 my $opts = {
2666 key => $_->{spec}->{key},
2667 dsn => $_->{spec}->{dsn},
2668 copy => $_->{spec}->{copy},
2669 desc => $_->{desc},
2670 };
2671 $opts;
2672 } $dsn_o->_pod_to_specs($file);
2673 $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts);
2674 }
2675
2676 if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) {
2677 $self->{version} = $1;
2678 PTDEBUG && _d($self->{version});
2679 }
2680
2681 return;
2682}
2683
2684sub DSNParser {
2685 my ( $self ) = @_;
2686 return $self->{DSNParser};
2687};
2688
2689sub get_defaults_files {
2690 my ( $self ) = @_;
2691 return @{$self->{default_files}};
2692}
2693
2694sub _pod_to_specs {
2695 my ( $self, $file ) = @_;
2696 $file ||= $self->{file} || __FILE__;
2697 open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
2698
2699 my @specs = ();
2700 my @rules = ();
2701 my $para;
2702
2703 local $INPUT_RECORD_SEPARATOR = '';
2704 while ( $para = <$fh> ) {
2705 next unless $para =~ m/^=head1 $self->{head1}/;
2706 last;
2707 }
2708
2709 while ( $para = <$fh> ) {
2710 last if $para =~ m/^=over/;
2711 next if $self->{skip_rules};
2712 chomp $para;
2713 $para =~ s/\s+/ /g;
2714 $para =~ s/$POD_link_re/$1/go;
2715 PTDEBUG && _d('Option rule:', $para);
2716 push @rules, $para;
2717 }
2718
2719 die "POD has no $self->{head1} section" unless $para;
2720
2721 do {
2722 if ( my ($option) = $para =~ m/^=item $self->{item}/ ) {
2723 chomp $para;
2724 PTDEBUG && _d($para);
2725 my %attribs;
2726
2727 $para = <$fh>; # read next paragraph, possibly attributes
2728
2729 if ( $para =~ m/: / ) { # attributes
2730 $para =~ s/\s+\Z//g;
2731 %attribs = map {
2732 my ( $attrib, $val) = split(/: /, $_);
2733 die "Unrecognized attribute for --$option: $attrib"
2734 unless $self->{attributes}->{$attrib};
2735 ($attrib, $val);
2736 } split(/; /, $para);
2737 if ( $attribs{'short form'} ) {
2738 $attribs{'short form'} =~ s/-//;
2739 }
2740 $para = <$fh>; # read next paragraph, probably short help desc
2741 }
2742 else {
2743 PTDEBUG && _d('Option has no attributes');
2744 }
2745
2746 $para =~ s/\s+\Z//g;
2747 $para =~ s/\s+/ /g;
2748 $para =~ s/$POD_link_re/$1/go;
2749
2750 $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s;
2751 PTDEBUG && _d('Short help:', $para);
2752
2753 die "No description after option spec $option" if $para =~ m/^=item/;
2754
2755 if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) {
2756 $option = $base_option;
2757 $attribs{'negatable'} = 1;
2758 }
2759
2760 push @specs, {
2761 spec => $self->{parse_attributes}->($self, $option, \%attribs),
2762 desc => $para
2763 . (defined $attribs{default} ? " (default $attribs{default})" : ''),
2764 group => ($attribs{'group'} ? $attribs{'group'} : 'default'),
2765 };
2766 }
2767 while ( $para = <$fh> ) {
2768 last unless $para;
2769 if ( $para =~ m/^=head1/ ) {
2770 $para = undef; # Can't 'last' out of a do {} block.
2771 last;
2772 }
2773 last if $para =~ m/^=item /;
2774 }
2775 } while ( $para );
2776
2777 die "No valid specs in $self->{head1}" unless @specs;
2778
2779 close $fh;
2780 return @specs, @rules;
2781}
2782
2783sub _parse_specs {
2784 my ( $self, @specs ) = @_;
2785 my %disables; # special rule that requires deferred checking
2786
2787 foreach my $opt ( @specs ) {
2788 if ( ref $opt ) { # It's an option spec, not a rule.
2789 PTDEBUG && _d('Parsing opt spec:',
2790 map { ($_, '=>', $opt->{$_}) } keys %$opt);
2791
2792 my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/;
2793 if ( !$long ) {
2794 die "Cannot parse long option from spec $opt->{spec}";
2795 }
2796 $opt->{long} = $long;
2797
2798 die "Duplicate long option --$long" if exists $self->{opts}->{$long};
2799 $self->{opts}->{$long} = $opt;
2800
2801 if ( length $long == 1 ) {
2802 PTDEBUG && _d('Long opt', $long, 'looks like short opt');
2803 $self->{short_opts}->{$long} = $long;
2804 }
2805
2806 if ( $short ) {
2807 die "Duplicate short option -$short"
2808 if exists $self->{short_opts}->{$short};
2809 $self->{short_opts}->{$short} = $long;
2810 $opt->{short} = $short;
2811 }
2812 else {
2813 $opt->{short} = undef;
2814 }
2815
2816 $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0;
2817 $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0;
2818 $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0;
2819
2820 $opt->{group} ||= 'default';
2821 $self->{groups}->{ $opt->{group} }->{$long} = 1;
2822
2823 $opt->{value} = undef;
2824 $opt->{got} = 0;
2825
2826 my ( $type ) = $opt->{spec} =~ m/=(.)/;
2827 $opt->{type} = $type;
2828 PTDEBUG && _d($long, 'type:', $type);
2829
2830
2831 $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ );
2832
2833 if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) {
2834 $self->{defaults}->{$long} = defined $def ? $def : 1;
2835 PTDEBUG && _d($long, 'default:', $def);
2836 }
2837
2838 if ( $long eq 'config' ) {
2839 $self->{defaults}->{$long} = join(',', $self->get_defaults_files());
2840 }
2841
2842 if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) {
2843 $disables{$long} = $dis;
2844 PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis);
2845 }
2846
2847 $self->{opts}->{$long} = $opt;
2848 }
2849 else { # It's an option rule, not a spec.
2850 PTDEBUG && _d('Parsing rule:', $opt);
2851 push @{$self->{rules}}, $opt;
2852 my @participants = $self->_get_participants($opt);
2853 my $rule_ok = 0;
2854
2855 if ( $opt =~ m/mutually exclusive|one and only one/ ) {
2856 $rule_ok = 1;
2857 push @{$self->{mutex}}, \@participants;
2858 PTDEBUG && _d(@participants, 'are mutually exclusive');
2859 }
2860 if ( $opt =~ m/at least one|one and only one/ ) {
2861 $rule_ok = 1;
2862 push @{$self->{atleast1}}, \@participants;
2863 PTDEBUG && _d(@participants, 'require at least one');
2864 }
2865 if ( $opt =~ m/default to/ ) {
2866 $rule_ok = 1;
2867 $self->{defaults_to}->{$participants[0]} = $participants[1];
2868 PTDEBUG && _d($participants[0], 'defaults to', $participants[1]);
2869 }
2870 if ( $opt =~ m/restricted to option groups/ ) {
2871 $rule_ok = 1;
2872 my ($groups) = $opt =~ m/groups ([\w\s\,]+)/;
2873 my @groups = split(',', $groups);
2874 %{$self->{allowed_groups}->{$participants[0]}} = map {
2875 s/\s+//;
2876 $_ => 1;
2877 } @groups;
2878 }
2879 if( $opt =~ m/accepts additional command-line arguments/ ) {
2880 $rule_ok = 1;
2881 $self->{strict} = 0;
2882 PTDEBUG && _d("Strict mode disabled by rule");
2883 }
2884
2885 die "Unrecognized option rule: $opt" unless $rule_ok;
2886 }
2887 }
2888
2889 foreach my $long ( keys %disables ) {
2890 my @participants = $self->_get_participants($disables{$long});
2891 $self->{disables}->{$long} = \@participants;
2892 PTDEBUG && _d('Option', $long, 'disables', @participants);
2893 }
2894
2895 return;
2896}
2897
2898sub _get_participants {
2899 my ( $self, $str ) = @_;
2900 my @participants;
2901 foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) {
2902 die "Option --$long does not exist while processing rule $str"
2903 unless exists $self->{opts}->{$long};
2904 push @participants, $long;
2905 }
2906 PTDEBUG && _d('Participants for', $str, ':', @participants);
2907 return @participants;
2908}
2909
2910sub opts {
2911 my ( $self ) = @_;
2912 my %opts = %{$self->{opts}};
2913 return %opts;
2914}
2915
2916sub short_opts {
2917 my ( $self ) = @_;
2918 my %short_opts = %{$self->{short_opts}};
2919 return %short_opts;
2920}
2921
2922sub set_defaults {
2923 my ( $self, %defaults ) = @_;
2924 $self->{defaults} = {};
2925 foreach my $long ( keys %defaults ) {
2926 die "Cannot set default for nonexistent option $long"
2927 unless exists $self->{opts}->{$long};
2928 $self->{defaults}->{$long} = $defaults{$long};
2929 PTDEBUG && _d('Default val for', $long, ':', $defaults{$long});
2930 }
2931 return;
2932}
2933
2934sub get_defaults {
2935 my ( $self ) = @_;
2936 return $self->{defaults};
2937}
2938
2939sub get_groups {
2940 my ( $self ) = @_;
2941 return $self->{groups};
2942}
2943
2944sub _set_option {
2945 my ( $self, $opt, $val ) = @_;
2946 my $long = exists $self->{opts}->{$opt} ? $opt
2947 : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt}
2948 : die "Getopt::Long gave a nonexistent option: $opt";
2949 $opt = $self->{opts}->{$long};
2950 if ( $opt->{is_cumulative} ) {
2951 $opt->{value}++;
2952 }
2953 elsif ( ($opt->{type} || '') eq 's' && $val =~ m/^--?(.+)/ ) {
2954 my $next_opt = $1;
2955 if ( exists $self->{opts}->{$next_opt}
2956 || exists $self->{short_opts}->{$next_opt} ) {
2957 $self->save_error("--$long requires a string value");
2958 return;
2959 }
2960 else {
2961 $opt->{value} = $val;
2962 }
2963 }
2964 else {
2965 $opt->{value} = $val;
2966 }
2967 $opt->{got} = 1;
2968 PTDEBUG && _d('Got option', $long, '=', $val);
2969}
2970
2971sub get_opts {
2972 my ( $self ) = @_;
2973
2974 foreach my $long ( keys %{$self->{opts}} ) {
2975 $self->{opts}->{$long}->{got} = 0;
2976 $self->{opts}->{$long}->{value}
2977 = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long}
2978 : $self->{opts}->{$long}->{is_cumulative} ? 0
2979 : undef;
2980 }
2981 $self->{got_opts} = 0;
2982
2983 $self->{errors} = [];
2984
2985 if ( @ARGV && $ARGV[0] eq "--config" ) {
2986 shift @ARGV;
2987 $self->_set_option('config', shift @ARGV);
2988 }
2989 if ( $self->has('config') ) {
2990 my @extra_args;
2991 foreach my $filename ( split(',', $self->get('config')) ) {
2992 eval {
2993 push @extra_args, $self->_read_config_file($filename);
2994 };
2995 if ( $EVAL_ERROR ) {
2996 if ( $self->got('config') ) {
2997 die $EVAL_ERROR;
2998 }
2999 elsif ( PTDEBUG ) {
3000 _d($EVAL_ERROR);
3001 }
3002 }
3003 }
3004 unshift @ARGV, @extra_args;
3005 }
3006
3007 Getopt::Long::Configure('no_ignore_case', 'bundling');
3008 GetOptions(
3009 map { $_->{spec} => sub { $self->_set_option(@_); } }
3010 grep { $_->{long} ne 'config' } # --config is handled specially above.
3011 values %{$self->{opts}}
3012 ) or $self->save_error('Error parsing options');
3013
3014 if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) {
3015 if ( $self->{version} ) {
3016 print $self->{version}, "\n";
3017 }
3018 else {
3019 print "Error parsing version. See the VERSION section of the tool's documentation.\n";
3020 }
3021 exit 1;
3022 }
3023
3024 if ( @ARGV && $self->{strict} ) {
3025 $self->save_error("Unrecognized command-line options @ARGV");
3026 }
3027
3028 foreach my $mutex ( @{$self->{mutex}} ) {
3029 my @set = grep { $self->{opts}->{$_}->{got} } @$mutex;
3030 if ( @set > 1 ) {
3031 my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
3032 @{$mutex}[ 0 .. scalar(@$mutex) - 2] )
3033 . ' and --'.$self->{opts}->{$mutex->[-1]}->{long}
3034 . ' are mutually exclusive.';
3035 $self->save_error($err);
3036 }
3037 }
3038
3039 foreach my $required ( @{$self->{atleast1}} ) {
3040 my @set = grep { $self->{opts}->{$_}->{got} } @$required;
3041 if ( @set == 0 ) {
3042 my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
3043 @{$required}[ 0 .. scalar(@$required) - 2] )
3044 .' or --'.$self->{opts}->{$required->[-1]}->{long};
3045 $self->save_error("Specify at least one of $err");
3046 }
3047 }
3048
3049 $self->_check_opts( keys %{$self->{opts}} );
3050 $self->{got_opts} = 1;
3051 return;
3052}
3053
3054sub _check_opts {
3055 my ( $self, @long ) = @_;
3056 my $long_last = scalar @long;
3057 while ( @long ) {
3058 foreach my $i ( 0..$#long ) {
3059 my $long = $long[$i];
3060 next unless $long;
3061 my $opt = $self->{opts}->{$long};
3062 if ( $opt->{got} ) {
3063 if ( exists $self->{disables}->{$long} ) {
3064 my @disable_opts = @{$self->{disables}->{$long}};
3065 map { $self->{opts}->{$_}->{value} = undef; } @disable_opts;
3066 PTDEBUG && _d('Unset options', @disable_opts,
3067 'because', $long,'disables them');
3068 }
3069
3070 if ( exists $self->{allowed_groups}->{$long} ) {
3071
3072 my @restricted_groups = grep {
3073 !exists $self->{allowed_groups}->{$long}->{$_}
3074 } keys %{$self->{groups}};
3075
3076 my @restricted_opts;
3077 foreach my $restricted_group ( @restricted_groups ) {
3078 RESTRICTED_OPT:
3079 foreach my $restricted_opt (
3080 keys %{$self->{groups}->{$restricted_group}} )
3081 {
3082 next RESTRICTED_OPT if $restricted_opt eq $long;
3083 push @restricted_opts, $restricted_opt
3084 if $self->{opts}->{$restricted_opt}->{got};
3085 }
3086 }
3087
3088 if ( @restricted_opts ) {
3089 my $err;
3090 if ( @restricted_opts == 1 ) {
3091 $err = "--$restricted_opts[0]";
3092 }
3093 else {
3094 $err = join(', ',
3095 map { "--$self->{opts}->{$_}->{long}" }
3096 grep { $_ }
3097 @restricted_opts[0..scalar(@restricted_opts) - 2]
3098 )
3099 . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long};
3100 }
3101 $self->save_error("--$long is not allowed with $err");
3102 }
3103 }
3104
3105 }
3106 elsif ( $opt->{is_required} ) {
3107 $self->save_error("Required option --$long must be specified");
3108 }
3109
3110 $self->_validate_type($opt);
3111 if ( $opt->{parsed} ) {
3112 delete $long[$i];
3113 }
3114 else {
3115 PTDEBUG && _d('Temporarily failed to parse', $long);
3116 }
3117 }
3118
3119 die "Failed to parse options, possibly due to circular dependencies"
3120 if @long == $long_last;
3121 $long_last = @long;
3122 }
3123
3124 return;
3125}
3126
3127sub _validate_type {
3128 my ( $self, $opt ) = @_;
3129 return unless $opt;
3130
3131 if ( !$opt->{type} ) {
3132 $opt->{parsed} = 1;
3133 return;
3134 }
3135
3136 my $val = $opt->{value};
3137
3138 if ( $val && $opt->{type} eq 'm' ) { # type time
3139 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value');
3140 my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
3141 if ( !$suffix ) {
3142 my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/;
3143 $suffix = $s || 's';
3144 PTDEBUG && _d('No suffix given; using', $suffix, 'for',
3145 $opt->{long}, '(value:', $val, ')');
3146 }
3147 if ( $suffix =~ m/[smhd]/ ) {
3148 $val = $suffix eq 's' ? $num # Seconds
3149 : $suffix eq 'm' ? $num * 60 # Minutes
3150 : $suffix eq 'h' ? $num * 3600 # Hours
3151 : $num * 86400; # Days
3152 $opt->{value} = ($prefix || '') . $val;
3153 PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val);
3154 }
3155 else {
3156 $self->save_error("Invalid time suffix for --$opt->{long}");
3157 }
3158 }
3159 elsif ( $val && $opt->{type} eq 'd' ) { # type DSN
3160 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN');
3161 my $prev = {};
3162 my $from_key = $self->{defaults_to}->{ $opt->{long} };
3163 if ( $from_key ) {
3164 PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN');
3165 if ( $self->{opts}->{$from_key}->{parsed} ) {
3166 $prev = $self->{opts}->{$from_key}->{value};
3167 }
3168 else {
3169 PTDEBUG && _d('Cannot parse', $opt->{long}, 'until',
3170 $from_key, 'parsed');
3171 return;
3172 }
3173 }
3174 my $defaults = $self->{DSNParser}->parse_options($self);
3175 $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults);
3176 }
3177 elsif ( $val && $opt->{type} eq 'z' ) { # type size
3178 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value');
3179 $self->_parse_size($opt, $val);
3180 }
3181 elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) {
3182 $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) };
3183 }
3184 elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) {
3185 $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ];
3186 }
3187 else {
3188 PTDEBUG && _d('Nothing to validate for option',
3189 $opt->{long}, 'type', $opt->{type}, 'value', $val);
3190 }
3191
3192 $opt->{parsed} = 1;
3193 return;
3194}
3195
3196sub get {
3197 my ( $self, $opt ) = @_;
3198 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
3199 die "Option $opt does not exist"
3200 unless $long && exists $self->{opts}->{$long};
3201 return $self->{opts}->{$long}->{value};
3202}
3203
3204sub got {
3205 my ( $self, $opt ) = @_;
3206 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
3207 die "Option $opt does not exist"
3208 unless $long && exists $self->{opts}->{$long};
3209 return $self->{opts}->{$long}->{got};
3210}
3211
3212sub has {
3213 my ( $self, $opt ) = @_;
3214 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
3215 return defined $long ? exists $self->{opts}->{$long} : 0;
3216}
3217
3218sub set {
3219 my ( $self, $opt, $val ) = @_;
3220 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
3221 die "Option $opt does not exist"
3222 unless $long && exists $self->{opts}->{$long};
3223 $self->{opts}->{$long}->{value} = $val;
3224 return;
3225}
3226
3227sub save_error {
3228 my ( $self, $error ) = @_;
3229 push @{$self->{errors}}, $error;
3230 return;
3231}
3232
3233sub errors {
3234 my ( $self ) = @_;
3235 return $self->{errors};
3236}
3237
3238sub usage {
3239 my ( $self ) = @_;
3240 warn "No usage string is set" unless $self->{usage}; # XXX
3241 return "Usage: " . ($self->{usage} || '') . "\n";
3242}
3243
3244sub descr {
3245 my ( $self ) = @_;
3246 warn "No description string is set" unless $self->{description}; # XXX
3247 my $descr = ($self->{description} || $self->{program_name} || '')
3248 . " For more details, please use the --help option, "
3249 . "or try 'perldoc $PROGRAM_NAME' "
3250 . "for complete documentation.";
3251 $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g)
3252 unless $ENV{DONT_BREAK_LINES};
3253 $descr =~ s/ +$//mg;
3254 return $descr;
3255}
3256
3257sub usage_or_errors {
3258 my ( $self, $file, $return ) = @_;
3259 $file ||= $self->{file} || __FILE__;
3260
3261 if ( !$self->{description} || !$self->{usage} ) {
3262 PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file);
3263 my %synop = $self->_parse_synopsis($file);
3264 $self->{description} ||= $synop{description};
3265 $self->{usage} ||= $synop{usage};
3266 PTDEBUG && _d("Description:", $self->{description},
3267 "\nUsage:", $self->{usage});
3268 }
3269
3270 if ( $self->{opts}->{help}->{got} ) {
3271 print $self->print_usage() or die "Cannot print usage: $OS_ERROR";
3272 exit 0 unless $return;
3273 }
3274 elsif ( scalar @{$self->{errors}} ) {
3275 print $self->print_errors() or die "Cannot print errors: $OS_ERROR";
3276 exit 1 unless $return;
3277 }
3278
3279 return;
3280}
3281
3282sub print_errors {
3283 my ( $self ) = @_;
3284 my $usage = $self->usage() . "\n";
3285 if ( (my @errors = @{$self->{errors}}) ) {
3286 $usage .= join("\n * ", 'Errors in command-line arguments:', @errors)
3287 . "\n";
3288 }
3289 return $usage . "\n" . $self->descr();
3290}
3291
3292sub print_usage {
3293 my ( $self ) = @_;
3294 die "Run get_opts() before print_usage()" unless $self->{got_opts};
3295 my @opts = values %{$self->{opts}};
3296
3297 my $maxl = max(
3298 map {
3299 length($_->{long}) # option long name
3300 + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable
3301 + ($_->{type} ? 2 : 0) # "=x" where x is the opt type
3302 }
3303 @opts);
3304
3305 my $maxs = max(0,
3306 map {
3307 length($_)
3308 + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0)
3309 + ($self->{opts}->{$_}->{type} ? 2 : 0)
3310 }
3311 values %{$self->{short_opts}});
3312
3313 my $lcol = max($maxl, ($maxs + 3));
3314 my $rcol = 80 - $lcol - 6;
3315 my $rpad = ' ' x ( 80 - $rcol );
3316
3317 $maxs = max($lcol - 3, $maxs);
3318
3319 my $usage = $self->descr() . "\n" . $self->usage();
3320
3321 my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}};
3322 push @groups, 'default';
3323
3324 foreach my $group ( reverse @groups ) {
3325 $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n";
3326 foreach my $opt (
3327 sort { $a->{long} cmp $b->{long} }
3328 grep { $_->{group} eq $group }
3329 @opts )
3330 {
3331 my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long};
3332 my $short = $opt->{short};
3333 my $desc = $opt->{desc};
3334
3335 $long .= $opt->{type} ? "=$opt->{type}" : "";
3336
3337 if ( $opt->{type} && $opt->{type} eq 'm' ) {
3338 my ($s) = $desc =~ m/\(suffix (.)\)/;
3339 $s ||= 's';
3340 $desc =~ s/\s+\(suffix .\)//;
3341 $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, "
3342 . "d=days; if no suffix, $s is used.";
3343 }
3344 $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol}(?!\W))(?:\s+|(?<=\W)|$)/g);
3345 $desc =~ s/ +$//mg;
3346 if ( $short ) {
3347 $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc);
3348 }
3349 else {
3350 $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc);
3351 }
3352 }
3353 }
3354
3355 $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n";
3356
3357 if ( (my @rules = @{$self->{rules}}) ) {
3358 $usage .= "\nRules:\n\n";
3359 $usage .= join("\n", map { " $_" } @rules) . "\n";
3360 }
3361 if ( $self->{DSNParser} ) {
3362 $usage .= "\n" . $self->{DSNParser}->usage();
3363 }
3364 $usage .= "\nOptions and values after processing arguments:\n\n";
3365 foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) {
3366 my $val = $opt->{value};
3367 my $type = $opt->{type} || '';
3368 my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/;
3369 $val = $bool ? ( $val ? 'TRUE' : 'FALSE' )
3370 : !defined $val ? '(No value)'
3371 : $type eq 'd' ? $self->{DSNParser}->as_string($val)
3372 : $type =~ m/H|h/ ? join(',', sort keys %$val)
3373 : $type =~ m/A|a/ ? join(',', @$val)
3374 : $val;
3375 $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val);
3376 }
3377 return $usage;
3378}
3379
3380sub prompt_noecho {
3381 shift @_ if ref $_[0] eq __PACKAGE__;
3382 my ( $prompt ) = @_;
3383 local $OUTPUT_AUTOFLUSH = 1;
3384 print $prompt
3385 or die "Cannot print: $OS_ERROR";
3386 my $response;
3387 eval {
3388 require Term::ReadKey;
3389 Term::ReadKey::ReadMode('noecho');
3390 chomp($response = <STDIN>);
3391 Term::ReadKey::ReadMode('normal');
3392 print "\n"
3393 or die "Cannot print: $OS_ERROR";
3394 };
3395 if ( $EVAL_ERROR ) {
3396 die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR";
3397 }
3398 return $response;
3399}
3400
3401sub _read_config_file {
3402 my ( $self, $filename ) = @_;
3403 open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n";
3404 my @args;
3405 my $prefix = '--';
3406 my $parse = 1;
3407
3408 LINE:
3409 while ( my $line = <$fh> ) {
3410 chomp $line;
3411 next LINE if $line =~ m/^\s*(?:\#|\;|$)/;
3412 $line =~ s/\s+#.*$//g;
3413 $line =~ s/^\s+|\s+$//g;
3414 if ( $line eq '--' ) {
3415 $prefix = '';
3416 $parse = 0;
3417 next LINE;
3418 }
3419 if ( $parse
3420 && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/)
3421 ) {
3422 push @args, grep { defined $_ } ("$prefix$opt", $arg);
3423 }
3424 elsif ( $line =~ m/./ ) {
3425 push @args, $line;
3426 }
3427 else {
3428 die "Syntax error in file $filename at line $INPUT_LINE_NUMBER";
3429 }
3430 }
3431 close $fh;
3432 return @args;
3433}
3434
3435sub read_para_after {
3436 my ( $self, $file, $regex ) = @_;
3437 open my $fh, "<", $file or die "Can't open $file: $OS_ERROR";
3438 local $INPUT_RECORD_SEPARATOR = '';
3439 my $para;
3440 while ( $para = <$fh> ) {
3441 next unless $para =~ m/^=pod$/m;
3442 last;
3443 }
3444 while ( $para = <$fh> ) {
3445 next unless $para =~ m/$regex/;
3446 last;
3447 }
3448 $para = <$fh>;
3449 chomp($para);
3450 close $fh or die "Can't close $file: $OS_ERROR";
3451 return $para;
3452}
3453
3454sub clone {
3455 my ( $self ) = @_;
3456
3457 my %clone = map {
3458 my $hashref = $self->{$_};
3459 my $val_copy = {};
3460 foreach my $key ( keys %$hashref ) {
3461 my $ref = ref $hashref->{$key};
3462 $val_copy->{$key} = !$ref ? $hashref->{$key}
3463 : $ref eq 'HASH' ? { %{$hashref->{$key}} }
3464 : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ]
3465 : $hashref->{$key};
3466 }
3467 $_ => $val_copy;
3468 } qw(opts short_opts defaults);
3469
3470 foreach my $scalar ( qw(got_opts) ) {
3471 $clone{$scalar} = $self->{$scalar};
3472 }
3473
3474 return bless \%clone;
3475}
3476
3477sub _parse_size {
3478 my ( $self, $opt, $val ) = @_;
3479
3480 if ( lc($val || '') eq 'null' ) {
3481 PTDEBUG && _d('NULL size for', $opt->{long});
3482 $opt->{value} = 'null';
3483 return;
3484 }
3485
3486 my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824);
3487 my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/;
3488 if ( defined $num ) {
3489 if ( $factor ) {
3490 $num *= $factor_for{$factor};
3491 PTDEBUG && _d('Setting option', $opt->{y},
3492 'to num', $num, '* factor', $factor);
3493 }
3494 $opt->{value} = ($pre || '') . $num;
3495 }
3496 else {
3497 $self->save_error("Invalid size for --$opt->{long}: $val");
3498 }
3499 return;
3500}
3501
3502sub _parse_attribs {
3503 my ( $self, $option, $attribs ) = @_;
3504 my $types = $self->{types};
3505 return $option
3506 . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' )
3507 . ($attribs->{'negatable'} ? '!' : '' )
3508 . ($attribs->{'cumulative'} ? '+' : '' )
3509 . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' );
3510}
3511
3512sub _parse_synopsis {
3513 my ( $self, $file ) = @_;
3514 $file ||= $self->{file} || __FILE__;
3515 PTDEBUG && _d("Parsing SYNOPSIS in", $file);
3516
3517 local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs
3518 open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
3519 my $para;
3520 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/;
3521 die "$file does not contain a SYNOPSIS section" unless $para;
3522 my @synop;
3523 for ( 1..2 ) { # 1 for the usage, 2 for the description
3524 my $para = <$fh>;
3525 push @synop, $para;
3526 }
3527 close $fh;
3528 PTDEBUG && _d("Raw SYNOPSIS text:", @synop);
3529 my ($usage, $desc) = @synop;
3530 die "The SYNOPSIS section in $file is not formatted properly"
3531 unless $usage && $desc;
3532
3533 $usage =~ s/^\s*Usage:\s+(.+)/$1/;
3534 chomp $usage;
3535
3536 $desc =~ s/\n/ /g;
3537 $desc =~ s/\s{2,}/ /g;
3538 $desc =~ s/\. ([A-Z][a-z])/. $1/g;
3539 $desc =~ s/\s+$//;
3540
3541 return (
3542 description => $desc,
3543 usage => $usage,
3544 );
3545};
3546
3547sub set_vars {
3548 my ($self, $file) = @_;
3549 $file ||= $self->{file} || __FILE__;
3550
3551 my %user_vars;
3552 my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
3553 if ( $user_vars ) {
3554 foreach my $var_val ( @$user_vars ) {
3555 my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
3556 die "Invalid --set-vars value: $var_val\n" unless $var && defined $val;
3557 $user_vars{$var} = {
3558 val => $val,
3559 default => 0,
3560 };
3561 }
3562 }
3563
3564 my %default_vars;
3565 my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
3566 if ( $default_vars ) {
3567 %default_vars = map {
3568 my $var_val = $_;
3569 my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
3570 die "Invalid --set-vars value: $var_val\n" unless $var && defined $val;
3571 $var => {
3572 val => $val,
3573 default => 1,
3574 };
3575 } split("\n", $default_vars);
3576 }
3577
3578 my %vars = (
3579 %default_vars, # first the tool's defaults
3580 %user_vars, # then the user's which overwrite the defaults
3581 );
3582 PTDEBUG && _d('--set-vars:', Dumper(\%vars));
3583 return \%vars;
3584}
3585
3586sub _d {
3587 my ($package, undef, $line) = caller 0;
3588 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3589 map { defined $_ ? $_ : 'undef' }
3590 @_;
3591 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3592}
3593
3594if ( PTDEBUG ) {
3595 print STDERR '# ', $^X, ' ', $], "\n";
3596 if ( my $uname = `uname -a` ) {
3597 $uname =~ s/\s+/ /g;
3598 print STDERR "# $uname\n";
3599 }
3600 print STDERR '# Arguments: ',
3601 join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n";
3602}
3603
36041;
3605}
3606# ###########################################################################
3607# End OptionParser package
3608# ###########################################################################
3609
3610# ###########################################################################
3611# Cxn package
3612# This package is a copy without comments from the original. The original
3613# with comments and its test file can be found in the Bazaar repository at,
3614# lib/Cxn.pm
3615# t/lib/Cxn.t
3616# See https://launchpad.net/percona-toolkit for more information.
3617# ###########################################################################
3618{
3619package Cxn;
3620
3621use strict;
3622use warnings FATAL => 'all';
3623use English qw(-no_match_vars);
3624use Scalar::Util qw(blessed);
3625use constant {
3626 PTDEBUG => $ENV{PTDEBUG} || 0,
3627 PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
3628};
3629
3630sub new {
3631 my ( $class, %args ) = @_;
3632 my @required_args = qw(DSNParser OptionParser);
3633 foreach my $arg ( @required_args ) {
3634 die "I need a $arg argument" unless $args{$arg};
3635 };
3636 my ($dp, $o) = @args{@required_args};
3637
3638 my $dsn_defaults = $dp->parse_options($o);
3639 my $prev_dsn = $args{prev_dsn};
3640 my $dsn = $args{dsn};
3641 if ( !$dsn ) {
3642 $args{dsn_string} ||= 'h=' . ($dsn_defaults->{h} || 'localhost');
3643
3644 $dsn = $dp->parse(
3645 $args{dsn_string}, $prev_dsn, $dsn_defaults);
3646 }
3647 elsif ( $prev_dsn ) {
3648 $dsn = $dp->copy($prev_dsn, $dsn);
3649 }
3650
3651 my $dsn_name = $dp->as_string($dsn, [qw(h P S)])
3652 || $dp->as_string($dsn, [qw(F)])
3653 || '';
3654
3655 my $self = {
3656 dsn => $dsn,
3657 dbh => $args{dbh},
3658 dsn_name => $dsn_name,
3659 hostname => '',
3660 set => $args{set},
3661 NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
3662 dbh_set => 0,
3663 ask_pass => $args{ask_pass},
3664 DSNParser => $dp,
3665 is_cluster_node => undef,
3666 parent => $args{parent},
3667 };
3668
3669 return bless $self, $class;
3670}
3671
3672sub connect {
3673 my ( $self, %opts ) = @_;
3674 my $dsn = $opts{dsn} || $self->{dsn};
3675 my $dp = $self->{DSNParser};
3676
3677 my $dbh = $self->{dbh};
3678 if ( !$dbh || !$dbh->ping() ) {
3679 if ( $self->{ask_pass} && !$self->{asked_for_pass} ) {
3680 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
3681 $self->{asked_for_pass} = 1;
3682 }
3683 $dbh = $dp->get_dbh(
3684 $dp->get_cxn_params($dsn),
3685 {
3686 AutoCommit => 1,
3687 %opts,
3688 },
3689 );
3690 }
3691
3692 $dbh = $self->set_dbh($dbh);
3693 if ( $opts{dsn} ) {
3694 $self->{dsn} = $dsn;
3695 $self->{dsn_name} = $dp->as_string($dsn, [qw(h P S)])
3696 || $dp->as_string($dsn, [qw(F)])
3697 || '';
3698
3699 }
3700 PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
3701 return $dbh;
3702}
3703
3704sub set_dbh {
3705 my ($self, $dbh) = @_;
3706
3707 if ( $self->{dbh} && $self->{dbh} == $dbh && $self->{dbh_set} ) {
3708 PTDEBUG && _d($dbh, 'Already set dbh');
3709 return $dbh;
3710 }
3711
3712 PTDEBUG && _d($dbh, 'Setting dbh');
3713
3714 $dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
3715
3716 my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
3717 PTDEBUG && _d($dbh, $sql);
3718 my ($server_id, $hostname) = $dbh->selectrow_array($sql);
3719 PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
3720 if ( $hostname ) {
3721 $self->{hostname} = $hostname;
3722 }
3723
3724 if ( $self->{parent} ) {
3725 PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
3726 $dbh->{InactiveDestroy} = 1;
3727 }
3728
3729 if ( my $set = $self->{set}) {
3730 $set->($dbh);
3731 }
3732
3733 $self->{dbh} = $dbh;
3734 $self->{dbh_set} = 1;
3735 return $dbh;
3736}
3737
3738sub lost_connection {
3739 my ($self, $e) = @_;
3740 return 0 unless $e;
3741 return $e =~ m/MySQL server has gone away/
3742 || $e =~ m/Lost connection to MySQL server/;
3743}
3744
3745sub dbh {
3746 my ($self) = @_;
3747 return $self->{dbh};
3748}
3749
3750sub dsn {
3751 my ($self) = @_;
3752 return $self->{dsn};
3753}
3754
3755sub name {
3756 my ($self) = @_;
3757 return $self->{dsn_name} if PERCONA_TOOLKIT_TEST_USE_DSN_NAMES;
3758 return $self->{hostname} || $self->{dsn_name} || 'unknown host';
3759}
3760
3761sub remove_duplicate_cxns {
3762 my ($self, %args) = @_;
3763 my @cxns = @{$args{cxns}};
3764 my $seen_ids = $args{seen_ids} || {};
3765 PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
3766 my @trimmed_cxns;
3767
3768 for my $cxn ( @cxns ) {
3769 my $dbh = $cxn->dbh();
3770 my $sql = q{SELECT @@server_id};
3771 PTDEBUG && _d($sql);
3772 my ($id) = $dbh->selectrow_array($sql);
3773 PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
3774
3775 if ( ! $seen_ids->{$id}++ ) {
3776 push @trimmed_cxns, $cxn
3777 }
3778 else {
3779 PTDEBUG && _d("Removing ", $cxn->name,
3780 ", ID ", $id, ", because we've already seen it");
3781 }
3782 }
3783
3784 return \@trimmed_cxns;
3785}
3786
3787sub DESTROY {
3788 my ($self) = @_;
3789
3790 PTDEBUG && _d('Destroying cxn');
3791
3792 if ( $self->{parent} ) {
3793 PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
3794 }
3795 elsif ( $self->{dbh}
3796 && blessed($self->{dbh})
3797 && $self->{dbh}->can("disconnect") )
3798 {
3799 PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
3800 $self->{dsn_name});
3801 $self->{dbh}->disconnect();
3802 }
3803
3804 return;
3805}
3806
3807sub _d {
3808 my ($package, undef, $line) = caller 0;
3809 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3810 map { defined $_ ? $_ : 'undef' }
3811 @_;
3812 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3813}
3814
38151;
3816}
3817# ###########################################################################
3818# End Cxn package
3819# ###########################################################################
3820
3821# ###########################################################################
3822# Quoter package
3823# This package is a copy without comments from the original. The original
3824# with comments and its test file can be found in the Bazaar repository at,
3825# lib/Quoter.pm
3826# t/lib/Quoter.t
3827# See https://launchpad.net/percona-toolkit for more information.
3828# ###########################################################################
3829{
3830package Quoter;
3831
3832use strict;
3833use warnings FATAL => 'all';
3834use English qw(-no_match_vars);
3835use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3836
3837use Data::Dumper;
3838$Data::Dumper::Indent = 1;
3839$Data::Dumper::Sortkeys = 1;
3840$Data::Dumper::Quotekeys = 0;
3841
3842sub new {
3843 my ( $class, %args ) = @_;
3844 return bless {}, $class;
3845}
3846
3847sub quote {
3848 my ( $self, @vals ) = @_;
3849 foreach my $val ( @vals ) {
3850 $val =~ s/`/``/g;
3851 }
3852 return join('.', map { '`' . $_ . '`' } @vals);
3853}
3854
3855sub quote_val {
3856 my ( $self, $val, %args ) = @_;
3857
3858 return 'NULL' unless defined $val; # undef = NULL
3859 return "''" if $val eq ''; # blank string = ''
3860 return $val if $val =~ m/^0x[0-9a-fA-F]+$/ # quote hex data
3861 && !$args{is_char}; # unless is_char is true
3862
3863 return $val if $args{is_float};
3864
3865 $val =~ s/(['\\])/\\$1/g;
3866 return "'$val'";
3867}
3868
3869sub split_unquote {
3870 my ( $self, $db_tbl, $default_db ) = @_;
3871 my ( $db, $tbl ) = split(/[.]/, $db_tbl);
3872 if ( !$tbl ) {
3873 $tbl = $db;
3874 $db = $default_db;
3875 }
3876 for ($db, $tbl) {
3877 next unless $_;
3878 s/\A`//;
3879 s/`\z//;
3880 s/``/`/g;
3881 }
3882
3883 return ($db, $tbl);
3884}
3885
3886sub literal_like {
3887 my ( $self, $like ) = @_;
3888 return unless $like;
3889 $like =~ s/([%_])/\\$1/g;
3890 return "'$like'";
3891}
3892
3893sub join_quote {
3894 my ( $self, $default_db, $db_tbl ) = @_;
3895 return unless $db_tbl;
3896 my ($db, $tbl) = split(/[.]/, $db_tbl);
3897 if ( !$tbl ) {
3898 $tbl = $db;
3899 $db = $default_db;
3900 }
3901 $db = "`$db`" if $db && $db !~ m/^`/;
3902 $tbl = "`$tbl`" if $tbl && $tbl !~ m/^`/;
3903 return $db ? "$db.$tbl" : $tbl;
3904}
3905
3906sub serialize_list {
3907 my ( $self, @args ) = @_;
3908 PTDEBUG && _d('Serializing', Dumper(\@args));
3909 return unless @args;
3910
3911 my @parts;
3912 foreach my $arg ( @args ) {
3913 if ( defined $arg ) {
3914 $arg =~ s/,/\\,/g; # escape commas
3915 $arg =~ s/\\N/\\\\N/g; # escape literal \N
3916 push @parts, $arg;
3917 }
3918 else {
3919 push @parts, '\N';
3920 }
3921 }
3922
3923 my $string = join(',', @parts);
3924 PTDEBUG && _d('Serialized: <', $string, '>');
3925 return $string;
3926}
3927
3928sub deserialize_list {
3929 my ( $self, $string ) = @_;
3930 PTDEBUG && _d('Deserializing <', $string, '>');
3931 die "Cannot deserialize an undefined string" unless defined $string;
3932
3933 my @parts;
3934 foreach my $arg ( split(/(?<!\\),/, $string) ) {
3935 if ( $arg eq '\N' ) {
3936 $arg = undef;
3937 }
3938 else {
3939 $arg =~ s/\\,/,/g;
3940 $arg =~ s/\\\\N/\\N/g;
3941 }
3942 push @parts, $arg;
3943 }
3944
3945 if ( !@parts ) {
3946 my $n_empty_strings = $string =~ tr/,//;
3947 $n_empty_strings++;
3948 PTDEBUG && _d($n_empty_strings, 'empty strings');
3949 map { push @parts, '' } 1..$n_empty_strings;
3950 }
3951 elsif ( $string =~ m/(?<!\\),$/ ) {
3952 PTDEBUG && _d('Last value is an empty string');
3953 push @parts, '';
3954 }
3955
3956 PTDEBUG && _d('Deserialized', Dumper(\@parts));
3957 return @parts;
3958}
3959
3960sub _d {
3961 my ($package, undef, $line) = caller 0;
3962 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3963 map { defined $_ ? $_ : 'undef' }
3964 @_;
3965 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3966}
3967
39681;
3969}
3970# ###########################################################################
3971# End Quoter package
3972# ###########################################################################
3973
3974# ###########################################################################
3975# VersionParser package
3976# This package is a copy without comments from the original. The original
3977# with comments and its test file can be found in the Bazaar repository at,
3978# lib/VersionParser.pm
3979# t/lib/VersionParser.t
3980# See https://launchpad.net/percona-toolkit for more information.
3981# ###########################################################################
3982{
3983package VersionParser;
3984
3985use Lmo;
3986use Scalar::Util qw(blessed);
3987use English qw(-no_match_vars);
3988use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3989
3990use overload (
3991 '""' => "version",
3992 '<=>' => "cmp",
3993 'cmp' => "cmp",
3994 fallback => 1,
3995);
3996
3997use Carp ();
3998
3999has major => (
4000 is => 'ro',
4001 isa => 'Int',
4002 required => 1,
4003);
4004
4005has [qw( minor revision )] => (
4006 is => 'ro',
4007 isa => 'Num',
4008);
4009
4010has flavor => (
4011 is => 'ro',
4012 isa => 'Str',
4013 default => sub { 'Unknown' },
4014);
4015
4016has innodb_version => (
4017 is => 'ro',
4018 isa => 'Str',
4019 default => sub { 'NO' },
4020);
4021
4022sub series {
4023 my $self = shift;
4024 return $self->_join_version($self->major, $self->minor);
4025}
4026
4027sub version {
4028 my $self = shift;
4029 return $self->_join_version($self->major, $self->minor, $self->revision);
4030}
4031
4032sub is_in {
4033 my ($self, $target) = @_;
4034
4035 return $self eq $target;
4036}
4037
4038sub _join_version {
4039 my ($self, @parts) = @_;
4040
4041 return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts;
4042}
4043sub _split_version {
4044 my ($self, $str) = @_;
4045 my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g;
4046 return @version_parts[0..2];
4047}
4048
4049sub normalized_version {
4050 my ( $self ) = @_;
4051 my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major,
4052 $self->minor,
4053 $self->revision);
4054 PTDEBUG && _d($self->version, 'normalizes to', $result);
4055 return $result;
4056}
4057
4058sub comment {
4059 my ( $self, $cmd ) = @_;
4060 my $v = $self->normalized_version();
4061
4062 return "/*!$v $cmd */"
4063}
4064
4065my @methods = qw(major minor revision);
4066sub cmp {
4067 my ($left, $right) = @_;
4068 my $right_obj = (blessed($right) && $right->isa(ref($left)))
4069 ? $right
4070 : ref($left)->new($right);
4071
4072 my $retval = 0;
4073 for my $m ( @methods ) {
4074 last unless defined($left->$m) && defined($right_obj->$m);
4075 $retval = $left->$m <=> $right_obj->$m;
4076 last if $retval;
4077 }
4078 return $retval;
4079}
4080
4081sub BUILDARGS {
4082 my $self = shift;
4083
4084 if ( @_ == 1 ) {
4085 my %args;
4086 if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) {
4087 PTDEBUG && _d("VersionParser got a dbh, trying to get the version");
4088 my $dbh = $_[0];
4089 local $dbh->{FetchHashKeyName} = 'NAME_lc';
4090 my $query = eval {
4091 $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} })
4092 };
4093 if ( $query ) {
4094 $query = { map { $_->{variable_name} => $_->{value} } @$query };
4095 @args{@methods} = $self->_split_version($query->{version});
4096 $args{flavor} = delete $query->{version_comment}
4097 if $query->{version_comment};
4098 }
4099 elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) {
4100 @args{@methods} = $self->_split_version($query);
4101 }
4102 else {
4103 Carp::confess("Couldn't get the version from the dbh while "
4104 . "creating a VersionParser object: $@");
4105 }
4106 $args{innodb_version} = eval { $self->_innodb_version($dbh) };
4107 }
4108 elsif ( !ref($_[0]) ) {
4109 @args{@methods} = $self->_split_version($_[0]);
4110 }
4111
4112 for my $method (@methods) {
4113 delete $args{$method} unless defined $args{$method};
4114 }
4115 @_ = %args if %args;
4116 }
4117
4118 return $self->SUPER::BUILDARGS(@_);
4119}
4120
4121sub _innodb_version {
4122 my ( $self, $dbh ) = @_;
4123 return unless $dbh;
4124 my $innodb_version = "NO";
4125
4126 my ($innodb) =
4127 grep { $_->{engine} =~ m/InnoDB/i }
4128 map {
4129 my %hash;
4130 @hash{ map { lc $_ } keys %$_ } = values %$_;
4131 \%hash;
4132 }
4133 @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) };
4134 if ( $innodb ) {
4135 PTDEBUG && _d("InnoDB support:", $innodb->{support});
4136 if ( $innodb->{support} =~ m/YES|DEFAULT/i ) {
4137 my $vars = $dbh->selectrow_hashref(
4138 "SHOW VARIABLES LIKE 'innodb_version'");
4139 $innodb_version = !$vars ? "BUILTIN"
4140 : ($vars->{Value} || $vars->{value});
4141 }
4142 else {
4143 $innodb_version = $innodb->{support}; # probably DISABLED or NO
4144 }
4145 }
4146
4147 PTDEBUG && _d("InnoDB version:", $innodb_version);
4148 return $innodb_version;
4149}
4150
4151sub _d {
4152 my ($package, undef, $line) = caller 0;
4153 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4154 map { defined $_ ? $_ : 'undef' }
4155 @_;
4156 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4157}
4158
4159no Lmo;
41601;
4161}
4162# ###########################################################################
4163# End VersionParser package
4164# ###########################################################################
4165
4166# ###########################################################################
4167# Daemon package
4168# This package is a copy without comments from the original. The original
4169# with comments and its test file can be found in the Bazaar repository at,
4170# lib/Daemon.pm
4171# t/lib/Daemon.t
4172# See https://launchpad.net/percona-toolkit for more information.
4173# ###########################################################################
4174{
4175package Daemon;
4176
4177use strict;
4178use warnings FATAL => 'all';
4179use English qw(-no_match_vars);
4180
4181use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4182
4183use POSIX qw(setsid);
4184use Fcntl qw(:DEFAULT);
4185
4186sub new {
4187 my ($class, %args) = @_;
4188 my $self = {
4189 log_file => $args{log_file},
4190 pid_file => $args{pid_file},
4191 daemonize => $args{daemonize},
4192 force_log_file => $args{force_log_file},
4193 parent_exit => $args{parent_exit},
4194 pid_file_owner => 0,
4195 };
4196 return bless $self, $class;
4197}
4198
4199sub run {
4200 my ($self) = @_;
4201
4202 my $daemonize = $self->{daemonize};
4203 my $pid_file = $self->{pid_file};
4204 my $log_file = $self->{log_file};
4205 my $force_log_file = $self->{force_log_file};
4206 my $parent_exit = $self->{parent_exit};
4207
4208 PTDEBUG && _d('Starting daemon');
4209
4210 if ( $pid_file ) {
4211 eval {
4212 $self->_make_pid_file(
4213 pid => $PID, # parent's pid
4214 pid_file => $pid_file,
4215 );
4216 };
4217 die "$EVAL_ERROR\n" if $EVAL_ERROR;
4218 if ( !$daemonize ) {
4219 $self->{pid_file_owner} = $PID; # parent's pid
4220 }
4221 }
4222
4223 if ( $daemonize ) {
4224 defined (my $child_pid = fork()) or die "Cannot fork: $OS_ERROR";
4225 if ( $child_pid ) {
4226 PTDEBUG && _d('Forked child', $child_pid);
4227 $parent_exit->($child_pid) if $parent_exit;
4228 exit 0;
4229 }
4230
4231 POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
4232 chdir '/' or die "Cannot chdir to /: $OS_ERROR";
4233
4234 if ( $pid_file ) {
4235 $self->_update_pid_file(
4236 pid => $PID, # child's pid
4237 pid_file => $pid_file,
4238 );
4239 $self->{pid_file_owner} = $PID;
4240 }
4241 }
4242
4243 if ( $daemonize || $force_log_file ) {
4244 PTDEBUG && _d('Redirecting STDIN to /dev/null');
4245 close STDIN;
4246 open STDIN, '/dev/null'
4247 or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
4248 if ( $log_file ) {
4249 PTDEBUG && _d('Redirecting STDOUT and STDERR to', $log_file);
4250 close STDOUT;
4251 open STDOUT, '>>', $log_file
4252 or die "Cannot open log file $log_file: $OS_ERROR";
4253
4254 close STDERR;
4255 open STDERR, ">&STDOUT"
4256 or die "Cannot dupe STDERR to STDOUT: $OS_ERROR";
4257 }
4258 else {
4259 if ( -t STDOUT ) {
4260 PTDEBUG && _d('No log file and STDOUT is a terminal;',
4261 'redirecting to /dev/null');
4262 close STDOUT;
4263 open STDOUT, '>', '/dev/null'
4264 or die "Cannot reopen STDOUT to /dev/null: $OS_ERROR";
4265 }
4266 if ( -t STDERR ) {
4267 PTDEBUG && _d('No log file and STDERR is a terminal;',
4268 'redirecting to /dev/null');
4269 close STDERR;
4270 open STDERR, '>', '/dev/null'
4271 or die "Cannot reopen STDERR to /dev/null: $OS_ERROR";
4272 }
4273 }
4274
4275 $OUTPUT_AUTOFLUSH = 1;
4276 }
4277
4278 PTDEBUG && _d('Daemon running');
4279 return;
4280}
4281
4282sub _make_pid_file {
4283 my ($self, %args) = @_;
4284 my @required_args = qw(pid pid_file);
4285 foreach my $arg ( @required_args ) {
4286 die "I need a $arg argument" unless $args{$arg};
4287 };
4288 my $pid = $args{pid};
4289 my $pid_file = $args{pid_file};
4290
4291 eval {
4292 sysopen(PID_FH, $pid_file, O_RDWR|O_CREAT|O_EXCL) or die $OS_ERROR;
4293 print PID_FH $PID, "\n";
4294 close PID_FH;
4295 };
4296 if ( my $e = $EVAL_ERROR ) {
4297 if ( $e =~ m/file exists/i ) {
4298 my $old_pid = $self->_check_pid_file(
4299 pid_file => $pid_file,
4300 pid => $PID,
4301 );
4302 if ( $old_pid ) {
4303 warn "Overwriting PID file $pid_file because PID $old_pid "
4304 . "is not running.\n";
4305 }
4306 $self->_update_pid_file(
4307 pid => $PID,
4308 pid_file => $pid_file
4309 );
4310 }
4311 else {
4312 die "Error creating PID file $pid_file: $e\n";
4313 }
4314 }
4315
4316 return;
4317}
4318
4319sub _check_pid_file {
4320 my ($self, %args) = @_;
4321 my @required_args = qw(pid_file pid);
4322 foreach my $arg ( @required_args ) {
4323 die "I need a $arg argument" unless $args{$arg};
4324 };
4325 my $pid_file = $args{pid_file};
4326 my $pid = $args{pid};
4327
4328 PTDEBUG && _d('Checking if PID in', $pid_file, 'is running');
4329
4330 if ( ! -f $pid_file ) {
4331 PTDEBUG && _d('PID file', $pid_file, 'does not exist');
4332 return;
4333 }
4334
4335 open my $fh, '<', $pid_file
4336 or die "Error opening $pid_file: $OS_ERROR";
4337 my $existing_pid = do { local $/; <$fh> };
4338 chomp($existing_pid) if $existing_pid;
4339 close $fh
4340 or die "Error closing $pid_file: $OS_ERROR";
4341
4342 if ( $existing_pid ) {
4343 if ( $existing_pid == $pid ) {
4344 warn "The current PID $pid already holds the PID file $pid_file\n";
4345 return;
4346 }
4347 else {
4348 PTDEBUG && _d('Checking if PID', $existing_pid, 'is running');
4349 my $pid_is_alive = kill 0, $existing_pid;
4350 if ( $pid_is_alive ) {
4351 die "PID file $pid_file exists and PID $existing_pid is running\n";
4352 }
4353 }
4354 }
4355 else {
4356 die "PID file $pid_file exists but it is empty. Remove the file "
4357 . "if the process is no longer running.\n";
4358 }
4359
4360 return $existing_pid;
4361}
4362
4363sub _update_pid_file {
4364 my ($self, %args) = @_;
4365 my @required_args = qw(pid pid_file);
4366 foreach my $arg ( @required_args ) {
4367 die "I need a $arg argument" unless $args{$arg};
4368 };
4369 my $pid = $args{pid};
4370 my $pid_file = $args{pid_file};
4371
4372 open my $fh, '>', $pid_file
4373 or die "Cannot open $pid_file: $OS_ERROR";
4374 print { $fh } $pid, "\n"
4375 or die "Cannot print to $pid_file: $OS_ERROR";
4376 close $fh
4377 or warn "Cannot close $pid_file: $OS_ERROR";
4378
4379 return;
4380}
4381
4382sub remove_pid_file {
4383 my ($self, $pid_file) = @_;
4384 $pid_file ||= $self->{pid_file};
4385 if ( $pid_file && -f $pid_file ) {
4386 unlink $self->{pid_file}
4387 or warn "Cannot remove PID file $pid_file: $OS_ERROR";
4388 PTDEBUG && _d('Removed PID file');
4389 }
4390 else {
4391 PTDEBUG && _d('No PID to remove');
4392 }
4393 return;
4394}
4395
4396sub DESTROY {
4397 my ($self) = @_;
4398
4399 if ( $self->{pid_file_owner} == $PID ) {
4400 $self->remove_pid_file();
4401 }
4402
4403 return;
4404}
4405
4406sub _d {
4407 my ($package, undef, $line) = caller 0;
4408 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4409 map { defined $_ ? $_ : 'undef' }
4410 @_;
4411 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4412}
4413
44141;
4415}
4416# ###########################################################################
4417# End Daemon package
4418# ###########################################################################
4419
4420# ###########################################################################
4421# Transformers package
4422# This package is a copy without comments from the original. The original
4423# with comments and its test file can be found in the Bazaar repository at,
4424# lib/Transformers.pm
4425# t/lib/Transformers.t
4426# See https://launchpad.net/percona-toolkit for more information.
4427# ###########################################################################
4428{
4429package Transformers;
4430
4431use strict;
4432use warnings FATAL => 'all';
4433use English qw(-no_match_vars);
4434use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4435
4436use Time::Local qw(timegm timelocal);
4437use Digest::MD5 qw(md5_hex);
4438use B qw();
4439
4440BEGIN {
4441 require Exporter;
4442 our @ISA = qw(Exporter);
4443 our %EXPORT_TAGS = ();
4444 our @EXPORT = ();
4445 our @EXPORT_OK = qw(
4446 micro_t
4447 percentage_of
4448 secs_to_time
4449 time_to_secs
4450 shorten
4451 ts
4452 parse_timestamp
4453 unix_timestamp
4454 any_unix_timestamp
4455 make_checksum
4456 crc32
4457 encode_json
4458 );
4459}
4460
4461our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/;
4462our $proper_ts = qr/(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)(\.\d+)?/;
4463our $n_ts = qr/(\d{1,5})([shmd]?)/; # Limit \d{1,5} because \d{6} looks
4464
4465sub micro_t {
4466 my ( $t, %args ) = @_;
4467 my $p_ms = defined $args{p_ms} ? $args{p_ms} : 0; # precision for ms vals
4468 my $p_s = defined $args{p_s} ? $args{p_s} : 0; # precision for s vals
4469 my $f;
4470
4471 $t = 0 if $t < 0;
4472
4473 $t = sprintf('%.17f', $t) if $t =~ /e/;
4474
4475 $t =~ s/\.(\d{1,6})\d*/\.$1/;
4476
4477 if ($t > 0 && $t <= 0.000999) {
4478 $f = ($t * 1000000) . 'us';
4479 }
4480 elsif ($t >= 0.001000 && $t <= 0.999999) {
4481 $f = sprintf("%.${p_ms}f", $t * 1000);
4482 $f = ($f * 1) . 'ms'; # * 1 to remove insignificant zeros
4483 }
4484 elsif ($t >= 1) {
4485 $f = sprintf("%.${p_s}f", $t);
4486 $f = ($f * 1) . 's'; # * 1 to remove insignificant zeros
4487 }
4488 else {
4489 $f = 0; # $t should = 0 at this point
4490 }
4491
4492 return $f;
4493}
4494
4495sub percentage_of {
4496 my ( $is, $of, %args ) = @_;
4497 my $p = $args{p} || 0; # float precision
4498 my $fmt = $p ? "%.${p}f" : "%d";
4499 return sprintf $fmt, ($is * 100) / ($of ||= 1);
4500}
4501
4502sub secs_to_time {
4503 my ( $secs, $fmt ) = @_;
4504 $secs ||= 0;
4505 return '00:00' unless $secs;
4506
4507 $fmt ||= $secs >= 86_400 ? 'd'
4508 : $secs >= 3_600 ? 'h'
4509 : 'm';
4510
4511 return
4512 $fmt eq 'd' ? sprintf(
4513 "%d+%02d:%02d:%02d",
4514 int($secs / 86_400),
4515 int(($secs % 86_400) / 3_600),
4516 int(($secs % 3_600) / 60),
4517 $secs % 60)
4518 : $fmt eq 'h' ? sprintf(
4519 "%02d:%02d:%02d",
4520 int(($secs % 86_400) / 3_600),
4521 int(($secs % 3_600) / 60),
4522 $secs % 60)
4523 : sprintf(
4524 "%02d:%02d",
4525 int(($secs % 3_600) / 60),
4526 $secs % 60);
4527}
4528
4529sub time_to_secs {
4530 my ( $val, $default_suffix ) = @_;
4531 die "I need a val argument" unless defined $val;
4532 my $t = 0;
4533 my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
4534 $suffix = $suffix || $default_suffix || 's';
4535 if ( $suffix =~ m/[smhd]/ ) {
4536 $t = $suffix eq 's' ? $num * 1 # Seconds
4537 : $suffix eq 'm' ? $num * 60 # Minutes
4538 : $suffix eq 'h' ? $num * 3600 # Hours
4539 : $num * 86400; # Days
4540
4541 $t *= -1 if $prefix && $prefix eq '-';
4542 }
4543 else {
4544 die "Invalid suffix for $val: $suffix";
4545 }
4546 return $t;
4547}
4548
4549sub shorten {
4550 my ( $num, %args ) = @_;
4551 my $p = defined $args{p} ? $args{p} : 2; # float precision
4552 my $d = defined $args{d} ? $args{d} : 1_024; # divisor
4553 my $n = 0;
4554 my @units = ('', qw(k M G T P E Z Y));
4555 while ( $num >= $d && $n < @units - 1 ) {
4556 $num /= $d;
4557 ++$n;
4558 }
4559 return sprintf(
4560 $num =~ m/\./ || $n
4561 ? "%.${p}f%s"
4562 : '%d',
4563 $num, $units[$n]);
4564}
4565
4566sub ts {
4567 my ( $time, $gmt ) = @_;
4568 my ( $sec, $min, $hour, $mday, $mon, $year )
4569 = $gmt ? gmtime($time) : localtime($time);
4570 $mon += 1;
4571 $year += 1900;
4572 my $val = sprintf("%d-%02d-%02dT%02d:%02d:%02d",
4573 $year, $mon, $mday, $hour, $min, $sec);
4574 if ( my ($us) = $time =~ m/(\.\d+)$/ ) {
4575 $us = sprintf("%.6f", $us);
4576 $us =~ s/^0\././;
4577 $val .= $us;
4578 }
4579 return $val;
4580}
4581
4582sub parse_timestamp {
4583 my ( $val ) = @_;
4584 if ( my($y, $m, $d, $h, $i, $s, $f)
4585 = $val =~ m/^$mysql_ts$/ )
4586 {
4587 return sprintf "%d-%02d-%02d %02d:%02d:"
4588 . (defined $f ? '%09.6f' : '%02d'),
4589 $y + 2000, $m, $d, $h, $i, (defined $f ? $s + $f : $s);
4590 }
4591 elsif ( $val =~ m/^$proper_ts$/ ) {
4592 return $val;
4593 }
4594 return $val;
4595}
4596
4597sub unix_timestamp {
4598 my ( $val, $gmt ) = @_;
4599 if ( my($y, $m, $d, $h, $i, $s, $us) = $val =~ m/^$proper_ts$/ ) {
4600 $val = $gmt
4601 ? timegm($s, $i, $h, $d, $m - 1, $y)
4602 : timelocal($s, $i, $h, $d, $m - 1, $y);
4603 if ( defined $us ) {
4604 $us = sprintf('%.6f', $us);
4605 $us =~ s/^0\././;
4606 $val .= $us;
4607 }
4608 }
4609 return $val;
4610}
4611
4612sub any_unix_timestamp {
4613 my ( $val, $callback ) = @_;
4614
4615 if ( my ($n, $suffix) = $val =~ m/^$n_ts$/ ) {
4616 $n = $suffix eq 's' ? $n # Seconds
4617 : $suffix eq 'm' ? $n * 60 # Minutes
4618 : $suffix eq 'h' ? $n * 3600 # Hours
4619 : $suffix eq 'd' ? $n * 86400 # Days
4620 : $n; # default: Seconds
4621 PTDEBUG && _d('ts is now - N[shmd]:', $n);
4622 return time - $n;
4623 }
4624 elsif ( $val =~ m/^\d{9,}/ ) {
4625 PTDEBUG && _d('ts is already a unix timestamp');
4626 return $val;
4627 }
4628 elsif ( my ($ymd, $hms) = $val =~ m/^(\d{6})(?:\s+(\d+:\d+:\d+))?/ ) {
4629 PTDEBUG && _d('ts is MySQL slow log timestamp');
4630 $val .= ' 00:00:00' unless $hms;
4631 return unix_timestamp(parse_timestamp($val));
4632 }
4633 elsif ( ($ymd, $hms) = $val =~ m/^(\d{4}-\d\d-\d\d)(?:[T ](\d+:\d+:\d+))?/) {
4634 PTDEBUG && _d('ts is properly formatted timestamp');
4635 $val .= ' 00:00:00' unless $hms;
4636 return unix_timestamp($val);
4637 }
4638 else {
4639 PTDEBUG && _d('ts is MySQL expression');
4640 return $callback->($val) if $callback && ref $callback eq 'CODE';
4641 }
4642
4643 PTDEBUG && _d('Unknown ts type:', $val);
4644 return;
4645}
4646
4647sub make_checksum {
4648 my ( $val ) = @_;
4649 my $checksum = uc substr(md5_hex($val), -16);
4650 PTDEBUG && _d($checksum, 'checksum for', $val);
4651 return $checksum;
4652}
4653
4654sub crc32 {
4655 my ( $string ) = @_;
4656 return unless $string;
4657 my $poly = 0xEDB88320;
4658 my $crc = 0xFFFFFFFF;
4659 foreach my $char ( split(//, $string) ) {
4660 my $comp = ($crc ^ ord($char)) & 0xFF;
4661 for ( 1 .. 8 ) {
4662 $comp = $comp & 1 ? $poly ^ ($comp >> 1) : $comp >> 1;
4663 }
4664 $crc = (($crc >> 8) & 0x00FFFFFF) ^ $comp;
4665 }
4666 return $crc ^ 0xFFFFFFFF;
4667}
4668
4669my $got_json = eval { require JSON };
4670sub encode_json {
4671 return JSON::encode_json(@_) if $got_json;
4672 my ( $data ) = @_;
4673 return (object_to_json($data) || '');
4674}
4675
4676
4677sub object_to_json {
4678 my ($obj) = @_;
4679 my $type = ref($obj);
4680
4681 if($type eq 'HASH'){
4682 return hash_to_json($obj);
4683 }
4684 elsif($type eq 'ARRAY'){
4685 return array_to_json($obj);
4686 }
4687 else {
4688 return value_to_json($obj);
4689 }
4690}
4691
4692sub hash_to_json {
4693 my ($obj) = @_;
4694 my @res;
4695 for my $k ( sort { $a cmp $b } keys %$obj ) {
4696 push @res, string_to_json( $k )
4697 . ":"
4698 . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) );
4699 }
4700 return '{' . ( @res ? join( ",", @res ) : '' ) . '}';
4701}
4702
4703sub array_to_json {
4704 my ($obj) = @_;
4705 my @res;
4706
4707 for my $v (@$obj) {
4708 push @res, object_to_json($v) || value_to_json($v);
4709 }
4710
4711 return '[' . ( @res ? join( ",", @res ) : '' ) . ']';
4712}
4713
4714sub value_to_json {
4715 my ($value) = @_;
4716
4717 return 'null' if(!defined $value);
4718
4719 my $b_obj = B::svref_2object(\$value); # for round trip problem
4720 my $flags = $b_obj->FLAGS;
4721 return $value # as is
4722 if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?
4723
4724 my $type = ref($value);
4725
4726 if( !$type ) {
4727 return string_to_json($value);
4728 }
4729 else {
4730 return 'null';
4731 }
4732
4733}
4734
4735my %esc = (
4736 "\n" => '\n',
4737 "\r" => '\r',
4738 "\t" => '\t',
4739 "\f" => '\f',
4740 "\b" => '\b',
4741 "\"" => '\"',
4742 "\\" => '\\\\',
4743 "\'" => '\\\'',
4744);
4745
4746sub string_to_json {
4747 my ($arg) = @_;
4748
4749 $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g;
4750 $arg =~ s/\//\\\//g;
4751 $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg;
4752
4753 utf8::upgrade($arg);
4754 utf8::encode($arg);
4755
4756 return '"' . $arg . '"';
4757}
4758
4759sub _d {
4760 my ($package, undef, $line) = caller 0;
4761 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4762 map { defined $_ ? $_ : 'undef' }
4763 @_;
4764 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4765}
4766
47671;
4768}
4769# ###########################################################################
4770# End Transformers package
4771# ###########################################################################
4772
4773# ###########################################################################
4774# Safeguards package
4775# This package is a copy without comments from the original. The original
4776# with comments and its test file can be found in the Bazaar repository at,
4777# lib/Safeguards.pm
4778# t/lib/Safeguards.t
4779# See https://launchpad.net/percona-toolkit for more information.
4780# ###########################################################################
4781{
4782package Safeguards;
4783
4784use strict;
4785use warnings FATAL => 'all';
4786use English qw(-no_match_vars);
4787
4788use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4789
4790sub new {
4791 my ($class, %args) = @_;
4792 my $self = {
4793 disk_bytes_free => $args{disk_bytes_free} || 104857600, # 100 MiB
4794 disk_pct_free => $args{disk_pct_free} || 5,
4795 };
4796 return bless $self, $class;
4797}
4798
4799sub get_disk_space {
4800 my ($self, %args) = @_;
4801 my $filesystem = $args{filesystem} || $ENV{PWD};
4802
4803 my $disk_space = `df -P -k "$filesystem"`;
4804 chop($disk_space) if $disk_space;
4805 PTDEBUG && _d('Disk space on', $filesystem, $disk_space);
4806
4807 return $disk_space;
4808}
4809
4810sub check_disk_space() {
4811 my ($self, %args) = @_;
4812 my $disk_space = $args{disk_space};
4813 PTDEBUG && _d("Checking disk space:\n", $disk_space);
4814
4815 my ($partition) = $disk_space =~ m/^\s*(\/.+)/m;
4816 PTDEBUG && _d('Partition:', $partition);
4817 die "Failed to parse partition from disk space:\n$disk_space"
4818 unless $partition;
4819
4820 my (undef, undef, $bytes_used, $bytes_free, $pct_used, undef)
4821 = $partition =~ m/(\S+)/g;
4822 PTDEBUG && _d('Bytes used:', $bytes_used, 'free:', $bytes_free,
4823 'Percentage used:', $pct_used);
4824
4825 $bytes_used = ($bytes_used || 0) * 1024;
4826 $bytes_free = ($bytes_free || 0) * 1024;
4827
4828 $pct_used =~ s/%//;
4829 my $pct_free = 100 - ($pct_used || 0);
4830
4831 return $bytes_free >= $self->{disk_bytes_free}
4832 && $pct_free >= $self->{disk_pct_free};
4833}
4834
4835sub _d {
4836 my ($package, undef, $line) = caller 0;
4837 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4838 map { defined $_ ? $_ : 'undef' }
4839 @_;
4840 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4841}
4842
48431;
4844}
4845# ###########################################################################
4846# End Safeguards package
4847# ###########################################################################
4848
4849# ###########################################################################
4850# Percona::Agent::Logger package
4851# This package is a copy without comments from the original. The original
4852# with comments and its test file can be found in the Bazaar repository at,
4853# lib/Percona/Agent/Logger.pm
4854# t/lib/Percona/Agent/Logger.t
4855# See https://launchpad.net/percona-toolkit for more information.
4856# ###########################################################################
4857{
4858package Percona::Agent::Logger;
4859
4860use strict;
4861use warnings FATAL => 'all';
4862use English qw(-no_match_vars);
4863
4864use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4865
4866use POSIX qw(SIGALRM);
4867
4868use Lmo;
4869use Transformers;
4870use Percona::WebAPI::Resource::LogEntry;
4871
4872Transformers->import(qw(ts));
4873
4874has 'exit_status' => (
4875 is => 'rw',
4876 isa => 'ScalarRef',
4877 required => 1,
4878);
4879
4880has 'pid' => (
4881 is => 'ro',
4882 isa => 'Int',
4883 required => 1,
4884);
4885
4886has 'service' => (
4887 is => 'rw',
4888 isa => 'Maybe[Str]',
4889 required => 0,
4890 default => sub { return; },
4891);
4892
4893has 'data_ts' => (
4894 is => 'rw',
4895 isa => 'Maybe[Int]',
4896 required => 0,
4897 default => sub { return; },
4898);
4899
4900has 'online_logging' => (
4901 is => 'ro',
4902 isa => 'Bool',
4903 required => 0,
4904 default => sub { return 1 },
4905);
4906
4907has 'online_logging_enabled' => (
4908 is => 'rw',
4909 isa => 'Bool',
4910 required => 0,
4911 default => sub { return 0 },
4912);
4913
4914has 'quiet' => (
4915 is => 'rw',
4916 isa => 'Int',
4917 required => 0,
4918 default => sub { return 0 },
4919);
4920
4921has '_buffer' => (
4922 is => 'rw',
4923 isa => 'ArrayRef',
4924 required => 0,
4925 default => sub { return []; },
4926);
4927
4928has '_pipe_write' => (
4929 is => 'rw',
4930 isa => 'Maybe[FileHandle]',
4931 required => 0,
4932);
4933
4934sub read_stdin {
4935 my ( $t ) = @_;
4936
4937 POSIX::sigaction(
4938 SIGALRM,
4939 POSIX::SigAction->new(sub { die 'read timeout'; }),
4940 ) or die "Error setting SIGALRM handler: $OS_ERROR";
4941
4942 my $timeout = 0;
4943 my @lines;
4944 eval {
4945 alarm $t;
4946 while(defined(my $line = <STDIN>)) {
4947 push @lines, $line;
4948 }
4949 alarm 0;
4950 };
4951 if ( $EVAL_ERROR ) {
4952 PTDEBUG && _d('Read error:', $EVAL_ERROR);
4953 die $EVAL_ERROR unless $EVAL_ERROR =~ m/read timeout/;
4954 $timeout = 1;
4955 }
4956 return unless scalar @lines || $timeout;
4957 return \@lines;
4958}
4959
4960sub start_online_logging {
4961 my ($self, %args) = @_;
4962 my $client = $args{client};
4963 my $log_link = $args{log_link};
4964 my $read_timeout = $args{read_timeout} || 3;
4965
4966 return unless $self->online_logging;
4967
4968 my $pid = open(my $pipe_write, "|-");
4969
4970 if ($pid) {
4971 select $pipe_write;
4972 $OUTPUT_AUTOFLUSH = 1;
4973 $self->_pipe_write($pipe_write);
4974 $self->online_logging_enabled(1);
4975 }
4976 else {
4977 my @log_entries;
4978 my $n_errors = 0;
4979 my $oktorun = 1;
4980 QUEUE:
4981 while ($oktorun) {
4982 my $lines = read_stdin($read_timeout);
4983 last QUEUE unless $lines;
4984 LINE:
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: