App-SpeedTest
view release on metacpan or search on metacpan
"V|version!" => sub { print "$CMD [$VERSION]\n"; exit 0; },
"v|verbose:2" => \$opt_v,
"simple!" => sub { $opt_v = 0; },
"man" => sub { pod_nroff (); },
"info" => sub { pod_text (); },
"all!" => \my $opt_a,
"g|geo!" => \$opt_g,
"c|cc|country=s" => \$opt_c,
"list-cc!" => \my $opt_cc,
"1|one-line!" => \my $opt_1,
"C|csv!" => \my $opt_C,
"csv-eol-unix|".
"csv-eol-nl!" => \my $opt_CNL,
"P|prtg!" => \my $opt_P,
"l|list!" => \my $list,
"p|ping:40" => \my $opt_ping,
"url:s" => \my $url,
"ip!" => \my $ip,
"B|bytes" => sub { $unit = [ 8, "byte" ] },
"T|try:5" => \$opt_T,
"s|server=s" => \my @server,
"t|timeout=i" => \my $timeout,
"d|download!" => \$opt_d,
"u|upload!" => \$opt_u,
"q|quick|fast:20" => \$opt_q,
"Q|realquick:10" => \$opt_q,
"U|skip-undef!" => \my $opt_U,
"m|mini=s" => \my $mini,
"source=s" => \my $source, # NYI
) or usage (1);
$opt_CNL and $opt_C++;
$opt_C || $opt_P and $opt_v = 0;
use LWP::UserAgent;
use XML::Simple; # Can safely be replaced with XML::LibXML::Simple
use HTML::TreeBuilder;
use Term::ANSIColor;
use Time::HiRes qw( gettimeofday tv_interval );
use List::Util qw( first sum );
use Socket qw( inet_ntoa );
use Math::Trig;
sub pod_text {
require Pod::Text::Color;
my $m = $ENV{NO_COLOR} ? "Pod::Text" : "Pod::Text::Color";
my $p = $m->new ();
open my $fh, ">", \my $out;
$p->parse_from_file ($0, $fh);
close $fh;
print $out;
exit 0;
} # pod_text
sub pod_nroff {
first { -x "$_/nroff" } grep { -d } split m/:+/ => $ENV{PATH} or pod_text ();
require Pod::Man;
my $p = Pod::Man->new ();
open my $fh, "|-", "nroff", "-man";
$p->parse_from_file ($0, $fh);
close $fh;
exit 0;
} # pod_nroff
# Debugging. Prefer Data::Peek over Data::Dumper if available
{ use Data::Dumper;
my $dp = eval { require Data::Peek; 1; };
sub ddumper {
$dp ? Data::Peek::DDumper (@_)
: print STDERR Dumper (@_);
} # ddumper
}
$timeout ||= 10;
my $ua = LWP::UserAgent->new (
max_redirect => 2,
agent => "speedtest/$VERSION",
parse_head => 0,
timeout => $timeout,
cookie_jar => {},
);
$ua->env_proxy;
binmode STDOUT, ":encoding(utf-8)";
# Speedtest.net defines Mbit/s and kbit/s using 1000 as multiplier,
# https://support.speedtest.net/entries/21057567-What-do-mbps-and-kbps-mean-
my $k = 1000;
my $config = get_config ();
my $client = $config->{"client"} or die "Config saw no client\n";
my $times = $config->{"times"} or die "Config saw no times\n";
my $downld = $config->{"download"} or die "Config saw no download\n";
my $upld = $config->{"upload"} or die "Config saw no upload\n";
$opt_v > 3 and ddumper {
client => $client,
times => $times,
down => $downld,
up => $upld,
};
if ($url || $mini) {
$opt_g = 0;
$opt_c = "";
@server = ();
my $ping = 0.05;
my $name = "";
my $sponsor = "CLI";
if ($mini) {
my $t0 = [ gettimeofday ];
my $rsp = $ua->request (HTTP::Request->new (GET => $mini));
$ping = tv_interval ($t0);
$rsp->is_success or die $rsp->status_line . "\n";
my $tree = HTML::TreeBuilder->new ();
$tree->parse_content ($rsp->content) or die "Cannot parse\n";
suppressempty => "",
);
$opt_v > 5 and ddumper $xml->{settings};
return $xml->{settings};
} # get_config
sub get_servers {
my $servlist;
foreach my $url (qw(
http://www.speedtest.net/speedtest-servers-static.php
http://www.speedtest.net/speedtest-servers.php
http://c.speedtest.net/speedtest-servers.php
)) {
$opt_v > 2 and warn "Fetching $url\n";
my $rsp = $ua->request (HTTP::Request->new (GET => $url));
$opt_v > 2 and warn $rsp->status_line, "\n";
$rsp->is_success and $servlist = $rsp->content and last;
}
$servlist or die "Cannot get any config\n";
my $xml = XMLin ($servlist,
keeproot => 1,
noattr => 0,
keyattr => [ ],
suppressempty => "",
);
# 4601 => {
# cc => 'NL',
# country => 'Netherlands',
# dist => '38.5028663935342602', # added later
# id => 4601,
# lat => '52.2167',
# lon => '5.9667',
# name => 'Apeldoorn',
# sponsor => 'Solcon Internetdiensten N.V.',
# url => 'http://speedtest.solcon.net/speedtest/upload.php',
# url2 => 'http://ooklaspeedtest.solcon.net/speedtest/upload.php'
# },
return map { $_->{id} => $_ } @{$xml->{settings}{servers}{server}};
} # get_servers
sub distance {
my ($lat_c, $lon_c, $lat_s, $lon_s) = @_;
my $rad = 6371; # km
# Convert angles from degrees to radians
my $dlat = deg2rad ($lat_s - $lat_c);
my $dlon = deg2rad ($lon_s - $lon_c);
my $x = sin ($dlat / 2) * sin ($dlat / 2) +
cos (deg2rad ($lat_c)) * cos (deg2rad ($lat_s)) *
sin ($dlon / 2) * sin ($dlon / 2);
return $rad * 2 * atan2 (sqrt ($x), sqrt (1 - $x)); # km
} # distance
sub servers {
my %list = get_servers ();
if (my $iid = $config->{"server-config"}{ignoreids}) {
$opt_v > 3 and warn "Removing servers $iid from server list\n";
delete @list{split m/\s*,\s*/ => $iid};
}
$opt_a or delete @list{grep { $list{$_}{cc} ne $opt_c } keys %list};
%list or die "No servers in $opt_c found\n";
for (values %list) {
$_->{dist} = distance ($client->{lat}, $client->{lon},
$_->{lat}, $_->{lon});
($_->{url0} = $_->{url}) =~ s{/speedtest/upload.*}{};
$opt_v > 7 and ddumper $_;
}
return %list;
} # servers
sub servers_by_ping {
my %list = servers;
my @list = values %list;
$opt_v > 1 and say STDERR "Finding fastest host out of @{[scalar @list]} hosts for $opt_c ...";
my $pa = LWP::UserAgent->new (
max_redirect => 2,
agent => "Opera/25.00 opera 25",
parse_head => 0,
cookie_jar => {},
timeout => $timeout,
);
$pa->env_proxy;
$opt_ping ||= 40;
if (@list > $opt_ping) {
@list = sort { $a->{dist} <=> $b->{dist} } @list;
@server or splice @list, $opt_ping;
}
foreach my $h (@list) {
my $t = 0;
if (@server and not first { $h->{id} == $_ } @server) {
$h->{ping} = 999999;
next;
}
$opt_v > 5 and printf STDERR "? %4d %-20.20s %s\n",
$h->{id}, $h->{sponsor}, $h->{url};
my $req = HTTP::Request->new (GET => "$h->{url}/latency.txt");
for (0 .. 3) {
my $t0 = [ gettimeofday ];
my $rsp = $pa->request ($req);
my $elapsed = tv_interval ($t0);
$opt_v > 8 and printf STDERR "%4d %9.2f\n", $_, $elapsed;
if ($elapsed >= 15) {
$t = 40;
last;
}
$t += ($rsp->is_success ? $elapsed : 1000);
}
$h->{ping} = $t * 1000; # report in ms
}
sort { $a->{ping} <=> $b->{ping}
|| $a->{dist} <=> $b->{dist} } @list;
} # servers_by_ping
__END__
=encoding UTF-8
=head1 NAME
( run in 0.541 second using v1.01-cache-2.11-cpan-af0e5977854 )