#!/usr/bin/perl

=head1 DESCRIPTION 

    Рисует ascii-граф репликации между mysql-ными машинами

    Полагается на то, что mysql-и доступны через lm

    если имя кондукторной группы совпадает с именем инстанса:
    direct-mysql-rpl-graph -i ppcdata1 -c ppcdata1

    если не совпадает (пример искуственный, потеряна бекапная реплика):
    direct-mysql-rpl-graph -i ppcdata12 -c ppcdata12_nonstandby

    Для devtest, где машины не организованы в группы по шардам:
    direct-mysql-rpl-graph -i ppcdata6 ppctest-ppcdata6-01f.da.yandex.ru ppctest-ppcdata6-01i.da.yandex.ru


=head1 TODO 

    * показывать (на ребрах) статус репликации

=cut

use strict;
use warnings;

use Getopt::Long;
use Encode;
use Graph::Easy;
use JSON;
use Term::ANSIColor qw/colored/;
use YAML;

use Yandex::Conductor;

use utf8;
use open ':std' => ':utf8';

our @COLORS = qw/green blue magenta cyan yellow /;

run() unless caller();

sub run
{
    my $opt = parse_options();
    
    my $hosts = [];
    if ( $opt->{conductor_group} ) {
        $hosts = conductor_groups2hosts($opt->{conductor_group});
    } elsif (scalar @{$opt->{hosts}}) {
        $hosts = $opt->{hosts};
    } else {
        die;
    }
    print STDERR join "\n", @$hosts;
    print STDERR "\n";

    my %status;
    for my $h (@$hosts){
        print STDERR "$h ...";
        $status{$h} = get_host_status($h, $opt->{mysql_instance});
        print STDERR " done\n";
    }

    my $graph = Graph::Easy->new();
    #$graph->randomize();

    my %versions;
    for my $h ( sort keys %status ){
        my $label;
        my $n = $graph->add_node($h);
        if($status{$h}->{success}){
            my $slave_status = join ":", map { $status{$h}->{slave_status}->{$_} // '-' } qw/ Slave_IO_Running Slave_SQL_Running Seconds_Behind_Master /;
            $label = "$h\\n$status{$h}->{mysql_version}\\n$slave_status\\n$status{$h}->{proc_num}";
            $versions{$status{$h}->{mysql_version}}++;
            if($status{$h}->{proc_num} > 0){
                $n->set_attribute('border-style', 'double');
            }
        } else {
            $label = "$h\\nERROR";
        }
        $n->set_attribute('label', $label);
    }
    for my $slave ( sort keys %status ) {
        next unless $status{$slave}->{success};
        my $master = $status{$slave}->{slave_status}->{Master_Host};
        next unless $master;
        my $e = $graph->edge( $slave, $master );
        if ( ! defined $e ){
            # просто ребро
            $graph->add_edge( $master, $slave, );
        } else {
            # замыкается кольцо между двумя хостами, рисуем только вторую стрелку на уже существующем ребре
            $e->bidirectional(1);
            $e->set_attribute('style', 'double');
        }
    }

    my $text = $graph->as_boxart();
    #my $text = $graph->as_ascii();
    $text = color_versions($text, \%versions);
    print $text;
}

sub parse_options
{
    my %O;

    GetOptions(
        "help" => sub {
            system("podselect -section NAME -section DESCRIPTION -section OPTIONS -section EXAMPLES $0 | pod2text-utf8"); 
            exit 0;
        },
        "i|instance=s" => \$O{mysql_instance},
        "c|conductor-group=s" => \$O{conductor_group},
    ) || die "can't parse options, stop";

    die "expecting -i <instance, e.g. ppcdata2>" if !$O{mysql_instance};

    if ( $O{conductor_group} ){
        die "too many arguments" if scalar @ARGV > 0;
    } else {
        die "expection one or more hostnames" if scalar @ARGV <= 0;
        $O{hosts} = [ @ARGV ];
    }

    return \%O;
}

sub get_host_status
{
    my ($host, $inst) = @_;
    die "bad hostname '$host'" unless $host =~ /^[a-z0-9\.-]+$/i;
    die "bad instance name '$inst'" unless $inst =~ /^[a-z0-9\.-]+$/i;

    my $status;

    my $ssh_cmd = q!ssh -q -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no"!; 
    eval{
    my $status_message = my_qx(qq!$ssh_cmd $host "lm $inst status-json"!); 
    $status = decode_json($status_message);

    my $version_message = my_qx(qq!$ssh_cmd $host "lm $inst mysql 'select \@\@version\\G'"!);
    $version_message =~ /\@\@version: ([^\s]+)/;
    $status->{mysql_version} = $1 || '???';

    #$status->{lfw} = my_qx(qq!$ssh_cmd $host "lfw $inst"!);

    };
    if ($?){
        print STDERR "ERROR $host: $@\n";
        return {
            success => 0,
            error => $@,
        };
    }

    $status->{success} = 1;
    
    return $status;
}

sub my_qx
{
    my ($cmd) = @_;
    #print "exec $cmd ...\n";
    my $output = `$cmd`;
    die "non-zero exit value from '$cmd'\n" unless $? == 0;
    return $output;
}

# раскрасить версии mysql
sub color_versions
{
    my ($gr_text, $versions) = @_;

    my %v_color;

    my $i = 0;
    for my $v (reverse sort keys %$versions){
        $i++;
        if ($i < scalar(@COLORS)){
            $v_color{$v} = $COLORS[$i-1];
        } else {
            $v_color{$v} = 'white';
        }
    }

    for my $v (keys %v_color){
        $gr_text =~ s/\b($v)\b/colored($1, $v_color{$v})/ge;
    }

    return $gr_text;
}

