#!/usr/bin/perl

=encoding utf8

=head1 NAME

direct-binlog-grep - обертка над mysqlbinlog для поиска по бинлогам

=head1 SYNOPSIS

direct-binlog-grep [-h] [-j JOBS] [-o PATH] [-e] expression FILE [...]

=head1 OPTIONS

  -h, -help         Вывести эту справку
  -j, --jobs JOBS   Задает максимальное количество (JOBS) параллельно работающих форков
                    По умолчанию равно 5
  -o, --out PATH    Задает путь PATH до директории, в которую будут сохранены результаты.
                    По умолчанию используется "/tmp/`whoami`", при отсутствии будет создана.
  -e expression     Регулярное выражение для поиска в запросах из бинлогов.
                    Параметр -e может быть опущен (в качестве expression берется
                      первый неименованный параметр запуска).
   FILE             Имя файла (можно указывать несколько раз)

=head1 DESCRIPTION

  Что делает скрипт:
    распаковывает файлы с бинлогами, параллельно в JOBS потоков (по одному на файл)
    выделяет из данных бинлог дату/время и исходные запрос (из комментариев)
    проверяет запрос на совпадение с регулярным выражением qr/expression/.
    в случаев совпадения - выводит результат в PATH/FILE.result

  Для простой "распаковки" бинлогов (без фильтрации), в качестве expression можно указать '.'

=head1 EXAMPLES

  Погрепать по reqid бинлоги в текущей директории в 3 потока,
  результат положить в домашнюю:
    direct-binlog-grep -o ~ -j 3 7733182973602942879 ppcdata3-bin.0112*

  Распаковать в 10 потоков все бинлоги из /tmp/binlog/ в /tmp/`whoami`:
    direct-binlog-grep -j 10 . /tmp/binlog/*

=cut

use Direct::Modern;

use Getopt::Long;
use Path::Tiny;
use Parallel::ForkManager;

my $jobs = 5;
my $condition_text;
my $out_dir;
GetOptions(
    'jobs=i' => \$jobs,
    'e=s' => \$condition_text,
    'out=s' => \$out_dir,
    'help' => sub { exec ("podselect $0 | pod2text-utf8") },
);

unless ($out_dir) {
    my $login = [getpwuid($<)]->[0];
    $out_dir = "/tmp/$login";
    mkdir $out_dir;
}

unless (-d $out_dir && -w $out_dir) {
    die "output path '$out_dir' is not writable directory";
}

$condition_text ||= shift @ARGV;
unless (@ARGV) {
    die 'Empty files list. Possible you forgot to specify expression.';
}

my $cond = qr/$condition_text/;

my @files = map { path($_) } @ARGV;

# nice'имся
my $priority;
if ($jobs <= 3) {
    $priority = 0;
} elsif (3 < $jobs && $jobs <= 10) {
    $priority = 5;
} elsif (10 < $jobs && $jobs <= 17) {
    $priority = 10;
} elsif (17 < $jobs && $jobs <= 25) {
    $priority = 15;
} else {
    $priority = 20;
}
setpriority(0, 0, getpriority(0, 0) + $priority);


my $pm = new Parallel::ForkManager($jobs);

for my $file (@files) {
    $pm->start and next;

    my @lines;
    my $match = 0;
    my $looks_like_query = 0;

    open(my $in, '-|', sprintf('/usr/bin/mysqlbinlog -v -v --base64-output=DECODE-ROWS %s', $file->absolute)) or die $!;
    my $out;

    while (my $line = <$in>) {
        if ($looks_like_query && $line =~ m/^# at [0-9]+$/) {
            # query end
            $looks_like_query = 0;
            push @lines, "\n";

            if ($match) {
                unless ($out) {
                    open($out, '>', sprintf('%s/%s.result', $out_dir, $file->basename)) or die $!;
                }
                print {$out} @lines;
            }
        } elsif ($looks_like_query && $line =~ s/^# //) {
            push @lines, $line;
            $match ||= ($line =~ $cond);
        } elsif (!$looks_like_query && $line =~ m/^#([0-9]{2})([0-9]{2})([0-9]{2}) ([ 0-9][0-9]:[0-9]{2}:[0-9]{2})/) {
            @lines = ();
            $match = 0;

            push @lines, "### $1/$2/$3 $4\n";
            $looks_like_query = 1;
        }
    }

    close($in);
    close($out) if $out;

    $pm->finish;
}

$pm->wait_all_children;

__END__

=head1 Источник бинлогов в Директе

=head2 Просмотр списка имеющихся бинлогов

=head3 1, 2 шард

rsync --list-only ppcbackup.yandex.ru::mysqlbackup/ppcdata?-binlogs/

=head3 3, 4, 9, 10 шард

rsync --list-only ppcbackup04i.yandex.ru::mysqlbackup/ppcdata*-binlogs/

=head3 5, 6, 7, 8 шард

rsync --list-only ppcbackup01i.yandex.ru::mysqlbackup/ppcdata?-binlogs/

=head2 Копирование бинлогов

Сначала по номеру шарда определяемся, с какого хоста будем копировать бинлоги.
Затем по дате изменения (при просмотре списка всех бинлогов) опеределяемся с
конкретными файлами для копирование.

В общем случае команда для копирования в текущую директорию такая:

  rsync --bwlimit 38400 --progress -h PPCBACKUP_HOST::mysqlbackup/ppcdata?-binlogs/ppcdata?-bin.?????? ./

Пример для 3го шарда и файлов с ppcdata3-bin.011288 по ppcdata3-bin.011293,
с сохранением в домашнюю директорию:

  rsync --bwlimit 38400 --progress -h ppcbackup04i.yandex.ru::mysqlbackup/ppcdata3-binlogs/ppcdata3-bin.0112{8{8,9},9{0,1,2,3}} ~/

=cut
