App-SpeedTest
view release on metacpan or search on metacpan
#!/pro/bin/perl
# speedtest - test network speed using speedtest.net
# (m)'26 [2026-01-09] Copyright H.M.Brand 2014-2026
require 5.010;
use strict;
use warnings;
our $VERSION = "0.31";
our $CMD = $0; $CMD =~ s{.*/}{};
sub usage {
my $err = shift and select STDERR;
(my $p = $0) =~ s{.*/}{};
print <<"EOH";
usage: $p [ --no-geo | --country=NL ] [ --list | --ping[=n] ] [ options ]
--geo use Geo location (default true) for closest testserver
--all include *all* servers (default only in own country)
-c --country=IS use ISO country code for closest test server
--list-cc list country codes and countries with server count
-1 --one-line show summary in one line
-C --csv output in CSV (stamp,id,ping,tests,direction,speed,min,max)
--csv-eol-unix EOL = NL (default = CR NL) implies -C
-P --prtg output in XML for PRTG
-l --list list test servers in chosen country sorted by distance
-p --ping[=40] list test servers in chosen country sorted by latency
--url show server url in list
-s --server=nnn use testserver with id nnn
--server=file use testserver from file
-t --timeout=nnn set server timeout to nnn seconds
--url=sss use specific server url (do not scan) ext php
--mini=sss use specific server url (do not scan) ext from sss
--download test download speed (default true)
--upload test upload speed (default true)
-q --quick[=20] do a quick test (only the fastest 20 tests)
-Q --realquick do a real quick test (only the fastest 10 tests)
-T --try[=5] try all tests on the n fastest servers
-U --skip-undef skip results with no actual measurements
-v --verbose[=1] set verbosity
--simple alias for -v0
--ip show IP for server
-V --version show version and exit
-? --help show this help
--man show the builtin manual (requires nroff)
--info show the builtin manual as plain text
$p --list
$p --ping --country=BE
$p
$p -s 4358
$p --url=http://ookla.extraip.net
$p -q --no-download
$p -Q --no-upload
EOH
exit $err;
} # usage
use Getopt::Long qw(:config bundling noignorecase);
my $opt_c = "";
my $opt_v = 1;
my $opt_d = 1;
my $opt_u = 1;
my $opt_g = 1;
my $opt_q = 0;
my $opt_T = 1;
my $unit = [ 1, "bit" ];
GetOptions (
"help|h|?" => sub { usage (0); },
"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 {
( run in 0.545 second using v1.01-cache-2.11-cpan-5a3173703d6 )