mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-01 18:25:59 +00:00

* Remove trailing spaces * PR-665 - Remove trailing spaces - Updated not stable test t/pt-online-schema-change/preserve_triggers.t - Updated utilities in bin directory * PR-665 - Remove trailing spaces - Fixed typos * PR-665 - Remove trailing spaces - Fixed typos --------- Co-authored-by: Sveta Smirnova <sveta.smirnova@percona.com>
403 lines
12 KiB
Perl
403 lines
12 KiB
Perl
# This program is copyright 2008-2011 Percona Ireland Ltd.
|
|
# Feedback and improvements are welcome.
|
|
#
|
|
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
|
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
|
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
|
|
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
|
|
# licenses.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
|
# Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
# ###########################################################################
|
|
# Transformers package
|
|
# ###########################################################################
|
|
{
|
|
# Package: Transformers
|
|
# Transformers exports subroutines that convert and beautify values.
|
|
package Transformers;
|
|
|
|
use strict;
|
|
use warnings FATAL => 'all';
|
|
use English qw(-no_match_vars);
|
|
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
|
|
|
use Time::Local qw(timegm timelocal);
|
|
use Digest::MD5 qw(md5_hex);
|
|
use B qw();
|
|
|
|
BEGIN {
|
|
require Exporter;
|
|
our @ISA = qw(Exporter);
|
|
our %EXPORT_TAGS = ();
|
|
our @EXPORT = ();
|
|
our @EXPORT_OK = qw(
|
|
micro_t
|
|
percentage_of
|
|
secs_to_time
|
|
time_to_secs
|
|
shorten
|
|
ts
|
|
parse_timestamp
|
|
unix_timestamp
|
|
any_unix_timestamp
|
|
make_checksum
|
|
crc32
|
|
encode_json
|
|
);
|
|
}
|
|
|
|
our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/;
|
|
our $proper_ts = qr/(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)(\.\d+)?/;
|
|
our $n_ts = qr/(\d{1,5})([shmd]?)/; # Limit \d{1,5} because \d{6} looks
|
|
# like a MySQL YYMMDD without hh:mm:ss.
|
|
|
|
sub micro_t {
|
|
my ( $t, %args ) = @_;
|
|
my $p_ms = defined $args{p_ms} ? $args{p_ms} : 0; # precision for ms vals
|
|
my $p_s = defined $args{p_s} ? $args{p_s} : 0; # precision for s vals
|
|
my $f;
|
|
|
|
$t = 0 if $t < 0;
|
|
|
|
# "Remove" scientific notation so the regex below does not make
|
|
# 6.123456e+18 into 6.123456.
|
|
$t = sprintf('%.17f', $t) if $t =~ /e/;
|
|
|
|
# Truncate after 6 decimal places to avoid 0.9999997 becoming 1
|
|
# because sprintf() rounds.
|
|
$t =~ s/\.(\d{1,6})\d*/\.$1/;
|
|
|
|
if ($t > 0 && $t <= 0.000999) {
|
|
$f = ($t * 1000000) . 'us';
|
|
}
|
|
elsif ($t >= 0.001000 && $t <= 0.999999) {
|
|
$f = sprintf("%.${p_ms}f", $t * 1000);
|
|
$f = ($f * 1) . 'ms'; # * 1 to remove insignificant zeros
|
|
}
|
|
elsif ($t >= 1) {
|
|
$f = sprintf("%.${p_s}f", $t);
|
|
$f = ($f * 1) . 's'; # * 1 to remove insignificant zeros
|
|
}
|
|
else {
|
|
$f = 0; # $t should = 0 at this point
|
|
}
|
|
|
|
return $f;
|
|
}
|
|
|
|
# Returns what percentage $is of $of.
|
|
sub percentage_of {
|
|
my ( $is, $of, %args ) = @_;
|
|
my $p = $args{p} || 0; # float precision
|
|
my $fmt = $p ? "%.${p}f" : "%d";
|
|
return sprintf $fmt, ($is * 100) / ($of ||= 1);
|
|
}
|
|
|
|
sub secs_to_time {
|
|
my ( $secs, $fmt ) = @_;
|
|
$secs ||= 0;
|
|
return '00:00' unless $secs;
|
|
|
|
# Decide what format to use, if not given
|
|
$fmt ||= $secs >= 86_400 ? 'd'
|
|
: $secs >= 3_600 ? 'h'
|
|
: 'm';
|
|
|
|
return
|
|
$fmt eq 'd' ? sprintf(
|
|
"%d+%02d:%02d:%02d",
|
|
int($secs / 86_400),
|
|
int(($secs % 86_400) / 3_600),
|
|
int(($secs % 3_600) / 60),
|
|
$secs % 60)
|
|
: $fmt eq 'h' ? sprintf(
|
|
"%02d:%02d:%02d",
|
|
int(($secs % 86_400) / 3_600),
|
|
int(($secs % 3_600) / 60),
|
|
$secs % 60)
|
|
: sprintf(
|
|
"%02d:%02d",
|
|
int(($secs % 3_600) / 60),
|
|
$secs % 60);
|
|
}
|
|
|
|
# Convert time values to number of seconds:
|
|
# 1s = 1, 1m = 60, 1h = 3600, 1d = 86400.
|
|
sub time_to_secs {
|
|
my ( $val, $default_suffix ) = @_;
|
|
die "I need a val argument" unless defined $val;
|
|
my $t = 0;
|
|
my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
|
|
$suffix = $suffix || $default_suffix || 's';
|
|
if ( $suffix =~ m/[smhd]/ ) {
|
|
$t = $suffix eq 's' ? $num * 1 # Seconds
|
|
: $suffix eq 'm' ? $num * 60 # Minutes
|
|
: $suffix eq 'h' ? $num * 3600 # Hours
|
|
: $num * 86400; # Days
|
|
|
|
$t *= -1 if $prefix && $prefix eq '-';
|
|
}
|
|
else {
|
|
die "Invalid suffix for $val: $suffix";
|
|
}
|
|
return $t;
|
|
}
|
|
|
|
sub shorten {
|
|
my ( $num, %args ) = @_;
|
|
my $p = defined $args{p} ? $args{p} : 2; # float precision
|
|
my $d = defined $args{d} ? $args{d} : 1_024; # divisor
|
|
my $n = 0;
|
|
my @units = ('', qw(k M G T P E Z Y));
|
|
while ( $num >= $d && $n < @units - 1 ) {
|
|
$num /= $d;
|
|
++$n;
|
|
}
|
|
# Added indexes 1$, 2$ to sprintf format to avoid 'redundant' warning
|
|
# https://bugs.launchpad.net/percona-toolkit/+bug/1480719
|
|
return sprintf(
|
|
$num =~ m/\./ || $n
|
|
? '%1$.'.$p.'f%2$s'
|
|
: '%1$d',
|
|
$num, $units[$n]);
|
|
}
|
|
|
|
# Turns a unix timestamp into an ISO8601 formatted date and time. $gmt makes
|
|
# this relative to GMT, for test determinism.
|
|
sub ts {
|
|
my ( $time, $gmt ) = @_;
|
|
my ( $sec, $min, $hour, $mday, $mon, $year )
|
|
= $gmt ? gmtime($time) : localtime($time);
|
|
$mon += 1;
|
|
$year += 1900;
|
|
my $val = sprintf("%d-%02d-%02dT%02d:%02d:%02d",
|
|
$year, $mon, $mday, $hour, $min, $sec);
|
|
if ( my ($us) = $time =~ m/(\.\d+)$/ ) {
|
|
$us = sprintf("%.6f", $us);
|
|
$us =~ s/^0\././;
|
|
$val .= $us;
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
# Turns MySQL's 071015 21:43:52 into a properly formatted timestamp. Also
|
|
# handles a timestamp with fractions after it.
|
|
sub parse_timestamp {
|
|
my ( $val ) = @_;
|
|
if ( my($y, $m, $d, $h, $i, $s, $f)
|
|
= $val =~ m/^$mysql_ts$/ )
|
|
{
|
|
return sprintf "%d-%02d-%02d %02d:%02d:"
|
|
. (defined $f ? '%09.6f' : '%02d'),
|
|
$y + 2000, $m, $d, $h, $i, (defined $f ? $s + $f : $s);
|
|
}
|
|
# MySQL 5.6+ uses "proper" timestamps
|
|
elsif ( $val =~ m/^$proper_ts$/ ) {
|
|
return $val;
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
# Turns a properly formatted timestamp like 2007-10-15 01:43:52
|
|
# into an int (seconds since epoch). Optional microseconds are printed. $gmt
|
|
# makes it use GMT time instead of local time (to make tests deterministic).
|
|
sub unix_timestamp {
|
|
my ( $val, $gmt ) = @_;
|
|
if ( my($y, $m, $d, $h, $i, $s, $us) = $val =~ m/^$proper_ts$/ ) {
|
|
$val = $gmt
|
|
? timegm($s, $i, $h, $d, $m - 1, $y)
|
|
: timelocal($s, $i, $h, $d, $m - 1, $y);
|
|
if ( defined $us ) {
|
|
$us = sprintf('%.6f', $us);
|
|
$us =~ s/^0\././;
|
|
$val .= $us;
|
|
}
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
# Turns several different types of timestamps into a unix timestamp.
|
|
# Each type is auto-detected. Supported types are:
|
|
# * N[shdm] Now - N[shdm]
|
|
# * 071015 21:43:52 MySQL slow log timestamp
|
|
# * 2009-07-01 [3:43:01] Proper timestamp with options HH:MM:SS
|
|
# * NOW() A MySQL time express
|
|
# For the last type, the callback arg is required. It is passed the
|
|
# given value/expression and is expected to return a single value
|
|
# (the result of the expression).
|
|
sub any_unix_timestamp {
|
|
my ( $val, $callback ) = @_;
|
|
|
|
if ( my ($n, $suffix) = $val =~ m/^$n_ts$/ ) {
|
|
$n = $suffix eq 's' ? $n # Seconds
|
|
: $suffix eq 'm' ? $n * 60 # Minutes
|
|
: $suffix eq 'h' ? $n * 3600 # Hours
|
|
: $suffix eq 'd' ? $n * 86400 # Days
|
|
: $n; # default: Seconds
|
|
PTDEBUG && _d('ts is now - N[shmd]:', $n);
|
|
return time - $n;
|
|
}
|
|
elsif ( $val =~ m/^\d{9,}/ ) {
|
|
# unix timestamp 100000000 is roughly March, 1973, so older
|
|
# dates won't be caught here; they'll probably be mistaken
|
|
# for a MySQL slow log timestamp.
|
|
PTDEBUG && _d('ts is already a unix timestamp');
|
|
return $val;
|
|
}
|
|
elsif ( my ($ymd, $hms) = $val =~ m/^(\d{6})(?:\s+(\d+:\d+:\d+))?/ ) {
|
|
PTDEBUG && _d('ts is MySQL slow log timestamp');
|
|
$val .= ' 00:00:00' unless $hms;
|
|
return unix_timestamp(parse_timestamp($val));
|
|
}
|
|
elsif ( ($ymd, $hms) = $val =~ m/^(\d{4}-\d\d-\d\d)(?:[T ](\d+:\d+:\d+))?/) {
|
|
PTDEBUG && _d('ts is properly formatted timestamp');
|
|
$val .= ' 00:00:00' unless $hms;
|
|
return unix_timestamp($val);
|
|
}
|
|
else {
|
|
PTDEBUG && _d('ts is MySQL expression');
|
|
return $callback->($val) if $callback && ref $callback eq 'CODE';
|
|
}
|
|
|
|
PTDEBUG && _d('Unknown ts type:', $val);
|
|
return;
|
|
}
|
|
|
|
# Returns the rightmost 64 bits of an MD5 checksum of the value.
|
|
sub make_checksum {
|
|
my ( $val ) = @_;
|
|
my $checksum = uc md5_hex($val);
|
|
PTDEBUG && _d($checksum, 'checksum for', $val);
|
|
return $checksum;
|
|
}
|
|
|
|
# Perl implementation of CRC32, ripped off from Digest::Crc32. The results
|
|
# ought to match what you get from any standard CRC32 implementation, such as
|
|
# that inside MySQL.
|
|
sub crc32 {
|
|
my ( $string ) = @_;
|
|
return unless $string;
|
|
my $poly = 0xEDB88320;
|
|
my $crc = 0xFFFFFFFF;
|
|
foreach my $char ( split(//, $string) ) {
|
|
my $comp = ($crc ^ ord($char)) & 0xFF;
|
|
for ( 1 .. 8 ) {
|
|
$comp = $comp & 1 ? $poly ^ ($comp >> 1) : $comp >> 1;
|
|
}
|
|
$crc = (($crc >> 8) & 0x00FFFFFF) ^ $comp;
|
|
}
|
|
return $crc ^ 0xFFFFFFFF;
|
|
}
|
|
|
|
my $got_json = eval { require JSON };
|
|
sub encode_json {
|
|
return JSON::encode_json(@_) if $got_json;
|
|
my ( $data ) = @_;
|
|
return (object_to_json($data) || '');
|
|
}
|
|
|
|
# The following is a stripped down version of JSON::PP by Makamaka Hannyaharamitu
|
|
# https://metacpan.org/module/JSON::PP
|
|
|
|
sub object_to_json {
|
|
my ($obj) = @_;
|
|
my $type = ref($obj);
|
|
|
|
if($type eq 'HASH'){
|
|
return hash_to_json($obj);
|
|
}
|
|
elsif($type eq 'ARRAY'){
|
|
return array_to_json($obj);
|
|
}
|
|
else {
|
|
return value_to_json($obj);
|
|
}
|
|
}
|
|
|
|
sub hash_to_json {
|
|
my ($obj) = @_;
|
|
my @res;
|
|
for my $k ( sort { $a cmp $b } keys %$obj ) {
|
|
push @res, string_to_json( $k )
|
|
. ":"
|
|
. ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) );
|
|
}
|
|
return '{' . ( @res ? join( ",", @res ) : '' ) . '}';
|
|
}
|
|
|
|
sub array_to_json {
|
|
my ($obj) = @_;
|
|
my @res;
|
|
|
|
for my $v (@$obj) {
|
|
push @res, object_to_json($v) || value_to_json($v);
|
|
}
|
|
|
|
return '[' . ( @res ? join( ",", @res ) : '' ) . ']';
|
|
}
|
|
|
|
sub value_to_json {
|
|
my ($value) = @_;
|
|
|
|
return 'null' if(!defined $value);
|
|
|
|
my $b_obj = B::svref_2object(\$value); # for round trip problem
|
|
my $flags = $b_obj->FLAGS;
|
|
return $value # as is
|
|
if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?
|
|
|
|
my $type = ref($value);
|
|
|
|
if( !$type ) {
|
|
return string_to_json($value);
|
|
}
|
|
else {
|
|
return 'null';
|
|
}
|
|
|
|
}
|
|
|
|
my %esc = (
|
|
"\n" => '\n',
|
|
"\r" => '\r',
|
|
"\t" => '\t',
|
|
"\f" => '\f',
|
|
"\b" => '\b',
|
|
"\"" => '\"',
|
|
"\\" => '\\\\',
|
|
"\'" => '\\\'',
|
|
);
|
|
|
|
sub string_to_json {
|
|
my ($arg) = @_;
|
|
|
|
$arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g;
|
|
$arg =~ s/\//\\\//g;
|
|
$arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg;
|
|
|
|
utf8::upgrade($arg);
|
|
utf8::encode($arg);
|
|
|
|
return '"' . $arg . '"';
|
|
}
|
|
|
|
sub _d {
|
|
my ($package, undef, $line) = caller 0;
|
|
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
|
map { defined $_ ? $_ : 'undef' }
|
|
@_;
|
|
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
|
}
|
|
|
|
1;
|
|
}
|
|
# ###########################################################################
|
|
# End Transformers package
|
|
# ###########################################################################
|