#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

use Net::SSH::Perl;
use Net::SSH::Expect;
use Net::SSH2;

use Time::HiRes qw/ gettimeofday tv_interval /;


my $key  = '/home/user/.ssh/id_rsa';
my $host = '192.168.123.123.123';
my $cmd  = 'ls -1';
my $port = 22;
my $user = 'user';
my $max  = 10;

my $ssh_expect;
my $ssh_perl;
my $ssh2;
my $ssh2_chan;
chomp( my $ssh_plain = `which ssh` );


# benchmark differnt ciphers with Net::SSH::Perl

if ( @ARGV && $ARGV[0] eq 'ciphers' ) {
    
    for my $cipher( qw/ 3des-cbc blowfish-cbc arcfour / ) {
        test( "Net::SSH::Perl - $cipher",
            sub { test_net_ssh_perl_create( $cipher ) }, \&test_net_ssh_perl_cmd, \&test_net_ssh_perl_end );
    }
    
    for my $cipher( qw/ aes128-cbc cast128-cbc aes192-cbc aes256-cbc 3des-cbc blowfish-cbc arcfour / ) {
        test( "Net::SSH2 - $cipher",
            sub { test_net_ssh2_create( $cipher ) }, \&test_net_ssh2_cmd, \&test_net_ssh2_end );
    }
}

# benchmark comparen plain ssh, Net::SSH::Perl and Net::SSH::Expect

else {
    
    test( "plain ssh",
        sub { return }, \&test_plain_ssh_cmd, sub { return } );
    test( "Net::SSH::Expect",
        \&test_net_ssh_expect_create, \&test_net_ssh_expect_cmd, \&test_net_ssh_expect_end );
    test( "Net::SSH2",
        \&test_net_ssh2_create, \&test_net_ssh2_cmd, \&test_net_ssh2_end );
    test( "Net::SSH::Perl",
        \&test_net_ssh_perl_create, \&test_net_ssh_perl_cmd, \&test_net_ssh_perl_end );
}




# -------------------------------------------------


sub test {
    my ( $title, $start_ref, $cmd_ref, $end_ref ) = @_;
    print "Test $title\n";
    my @start = gettimeofday();
    for ( my $i = 0; $i < $max; $i++ ) {
        print " Test $i\n";
        my @pre = gettimeofday();
        $start_ref->();
        my @run = gettimeofday();
        $cmd_ref->() for 0 .. 9;
        my @end = gettimeofday();
        $end_ref->();
        my @after = gettimeofday();
        
        print "   Create: ". tv_interval( \@pre, \@run ). "\n";
        print "   Cmds  : ". tv_interval( \@run, \@end ). "\n";
        print "   End   : ". tv_interval( \@end, \@after ). "\n";
        print "  Total : ". tv_interval( \@pre, \@after ). "\n\n";
    }
    my @final = gettimeofday();
    my $total = tv_interval( \@start, \@final );
    my $each  = $total / $max;
    print "-> $max Times in ${total}ms, ${each}ms each\n\n\n";
}



#
# EXPECT
#

sub test_net_ssh_expect_create {
    $ssh_expect = Net::SSH::Expect->new (
        host       => $host,
        #port       => $port,
        ssh_option => "-o Port=$port -o PreferredAuthentications=publickey -o IdentityFile=$key",
        user       => $user, 
        raw_pty    => 1
    );
    $ssh_expect->run_ssh();
}

sub test_net_ssh_expect_cmd {
    $ssh_expect->exec( $cmd );
}

sub test_net_ssh_expect_end {
    $ssh_expect->close;
    undef $ssh_expect;
}



#
# SSH2
#

sub test_net_ssh2_create {
    my ( $crypt ) = @_;
    my %crypt = $crypt ? ( ciphers => $crypt ) : ();
    $ssh2 = Net::SSH2->new;
    #$ssh2->debug(1);
    $ssh2->connect( $host, $port );
    $ssh2->method( CRYPT_CS => $crypt ) if $crypt;
    $ssh2->auth(
        rank       => [ 'publickey' ],
        username   => $user,
        privatekey => $key,
        publickey  => "$key.pub",
    );
    die $ssh2->error if $ssh2->error;
}

sub test_net_ssh2_cmd {
    unless ( $ssh2_chan ) {
        $ssh2_chan = $ssh2->channel( "session" ) or die "Oops: $!";
        $ssh2_chan->blocking( 0 );
        $ssh2_chan->shell;
    }
    print $ssh2_chan "$cmd\n";
    1 while <$ssh2_chan>;
}

sub test_net_ssh2_end {
    $ssh2_chan->close;
    undef $ssh2_chan;
    $ssh2->disconnect;
    undef $ssh2;
}



#
# PERL
#

sub test_net_ssh_perl_create {
    my ( $crypt ) = @_;
    my %crypt = $crypt ? ( ciphers => $crypt ) : ();
    $ssh_perl = Net::SSH::Perl->new( $host, options => [
        "Port $port",
        "PreferredAuthentications publickey",
        "IdentityFile $key",
        "Port $port"
    ], %crypt );
    $ssh_perl->login;
}

sub test_net_ssh_perl_cmd {
    $ssh_perl->cmd( $cmd );
}

sub test_net_ssh_perl_end {
    undef $ssh_perl;
}



#
# PLAIN
#

sub test_plain_ssh_cmd {
    my $res = `$ssh_plain -o Port=$port -o PreferredAuthentications=publickey -o IdentityFile=$key $host "$cmd" 2>/dev/null`;
    return ;
}



1;

