]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
add utils
authorAnthony Minessale <anthm@freeswitch.org>
Tue, 6 Jan 2015 21:09:50 +0000 (15:09 -0600)
committerAnthony Minessale <anthm@freeswitch.org>
Wed, 7 Jan 2015 03:09:55 +0000 (21:09 -0600)
support-d/utils/bt.sh [new file with mode: 0644]
support-d/utils/btgrep [new file with mode: 0755]
support-d/utils/hashfinder [new file with mode: 0755]
support-d/utils/rwlock.pl [new file with mode: 0755]
support-d/utils/speedtest-cli [new file with mode: 0755]

diff --git a/support-d/utils/bt.sh b/support-d/utils/bt.sh
new file mode 100644 (file)
index 0000000..548a9f8
--- /dev/null
@@ -0,0 +1,4 @@
+gdb /usr/local/freeswitch/bin/freeswitch $1 \
+        --eval-command='set pagination off' \
+        --eval-command='thread apply all bt' \
+        --eval-command='quit'
diff --git a/support-d/utils/btgrep b/support-d/utils/btgrep
new file mode 100755 (executable)
index 0000000..56a7056
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+# Copyright (c) 2007-2014, Anthony Minessale II
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+# Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 
+# Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 
+# Neither the name of the original author; nor the names of any contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+# 
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# Contributors: 
+# 
+# btgrep - Search for regex in backtraces
+#
+
+$| = 0;
+$/ = undef;
+
+my $file = shift;
+open I, $file or die;
+my $i = <I>;
+close I;
+
+my @all = $i =~ /Thread \d.*?\n\n/smg;
+
+foo:
+foreach my $m (@all) {
+
+  foreach (@ARGV) {
+    my $arg;
+    my $neg = 0;
+
+    if (/^\-(.*)$/) {
+      $arg = $1;
+      $neg = 1;
+    } else {
+      $arg = $_;
+    }
+
+    if ($neg) {
+      next foo if($m =~ /$arg/);
+    } else {
+      next foo unless($m =~ /$arg/);
+    }
+  }
+
+  print "Match: $m";
+}
+
+# For Emacs:
+# Local Variables:
+# mode:perl
+# indent-tabs-mode:t
+# tab-width:4
+# c-basic-offset:4
+# End:
+# For VIM:
+# vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
diff --git a/support-d/utils/hashfinder b/support-d/utils/hashfinder
new file mode 100755 (executable)
index 0000000..f6ec9df
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+# Copyright (c) 2007-2014, Anthony Minessale II
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+# Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 
+# Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 
+# Neither the name of the original author; nor the names of any contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+# 
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# Contributors: 
+# 
+# hashfinder - Find origin of a particular line of code
+#
+
+
+my $file = shift;
+my $regex = shift;
+my $delim = "  ";
+
+$file and $regex or die "missing params.  Syntax: <file> <regex>";
+
+sub doit($$) {
+  my $rev = shift;
+  my $pattern = shift;
+  my $loops = shift || 0;
+  my $linematch = 0;
+
+  $pattern =~ s/\(/\\\(/g;
+  $pattern =~ s/\)/\\\)/g;
+
+  if ($pattern =~ /^(\d+)$/) {
+    $linematch = 1;
+  }
+
+  open GIT, "git blame -n $file $rev|";
+
+  my $mc = 0;
+  my @matches = ();
+
+  while (<GIT>) {
+    my $matched = 0;
+
+    if ($linematch) {
+      $matched = (/^\S+\s+$pattern\s+/);
+    } else {
+      $matched = (/$pattern/);
+    }
+
+    if ($matched) {
+      s/[\r\n]//g;
+      push @matches, $_;
+      $mc++;
+    }
+  }
+
+  close(GIT);
+
+
+  if ($mc > 5) {
+    print $delim x $loops;
+    print "$mc matches; Maybe more specific?\n";
+    
+    foreach (@matches) {
+      print "$_\n";
+    }
+
+       return;
+
+  }
+
+  my $x = 0;
+
+  foreach (@matches) {
+    my ($hash, $lno, $author, $line);
+    my $done = 0;
+
+    if (/$file/) {
+      ($hash, $lno, $author, $line) = /(\S+)\s+\S+\s+(\S+)\s+(\([^\)]+\))\s*(.*)/;
+      $done = 1;
+    } else {
+      ($hash, $lno, $author, $line) = /(\S+)\s+(\S+)\s+(\([^\)]+\))\s*(.*)/;
+    }
+
+    if ($hash) {
+               $line =~ s/^\s+//g;
+
+               print "\n";
+               print $delim x $loops;
+               my $msg = `git log -1 --pretty=format:%B $hash`;
+               print "[$hash] [$lno] [$author] [$line]\n";
+               print $delim x $loops;
+               print ":: $msg\n";
+               doit("$hash" . "^", $line, $loops+1);
+               print "\n";
+    }
+
+    last if $done;
+
+  }
+}
+
+doit(undef, $regex);
+
+
+# For Emacs:
+# Local Variables:
+# mode:perl
+# indent-tabs-mode:t
+# tab-width:4
+# c-basic-offset:4
+# End:
+# For VIM:
+# vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
+
diff --git a/support-d/utils/rwlock.pl b/support-d/utils/rwlock.pl
new file mode 100755 (executable)
index 0000000..ca2cc61
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+
+my $start = 8;
+my $i = $start;
+my $step = "4";
+my $wr = 0;
+
+  printf "%s %0.4d START    $list[4]\n", " " x $i, $i;
+
+while(<>) {
+  my $sub = 0;
+  my $indent = 0;
+
+  next unless /ERR/;
+
+  @list = split;
+
+  if ($list[9] eq "ACQUIRED") {
+
+    if ($list[7] eq "Read") {
+      $mark = "READLOCK ";
+      $i += $step;
+      $indent = $i;
+    } else {
+      $mark = "WRITELOCK";
+      $wr = 1;
+      $indent = 0;
+    }
+
+  } elsif($list[9] eq "CLEARED") {
+    if ($wr && $i <= $start) {
+      $mark = "WRCLEARED";
+      $indent = 0;
+    } else {
+      $sub = $step;
+      $mark = "CLEARED  ";
+      $indent = $i;
+    }
+  } elsif($list[9] eq "FAIL") {
+    $mark = "FAIL     ";
+    $indent = $i;
+  }
+
+  printf "%s %0.4d $mark $list[4]\n", " " x $indent, $indent;
+
+  if ($sub) {
+    $i -= $sub;
+    $sub = 0;
+    print "\n";
+  }
+
+
+}
diff --git a/support-d/utils/speedtest-cli b/support-d/utils/speedtest-cli
new file mode 100755 (executable)
index 0000000..b5e9ef8
--- /dev/null
@@ -0,0 +1,657 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright 2013 Matt Martz
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+__version__ = '0.2.4'
+
+# Some global variables we use
+source = None
+shutdown_event = None
+
+import math
+import time
+import os
+import sys
+import threading
+import re
+import signal
+import socket
+
+# Used for bound_interface
+socket_socket = socket.socket
+
+try:
+    import xml.etree.cElementTree as ET
+except ImportError:
+    try:
+        import xml.etree.ElementTree as ET
+    except ImportError:
+        from xml.dom import minidom as DOM
+        ET = None
+
+# Begin import game to handle Python 2 and Python 3
+try:
+    from urllib2 import urlopen, Request, HTTPError, URLError
+except ImportError:
+    from urllib.request import urlopen, Request, HTTPError, URLError
+
+try:
+    from Queue import Queue
+except ImportError:
+    from queue import Queue
+
+try:
+    from urlparse import urlparse
+except ImportError:
+    from urllib.parse import urlparse
+
+try:
+    from urlparse import parse_qs
+except ImportError:
+    try:
+        from urllib.parse import parse_qs
+    except ImportError:
+        from cgi import parse_qs
+
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
+try:
+    from argparse import ArgumentParser as ArgParser
+except ImportError:
+    from optparse import OptionParser as ArgParser
+
+try:
+    import builtins
+except ImportError:
+    def print_(*args, **kwargs):
+        """The new-style print function taken from
+        https://pypi.python.org/pypi/six/
+
+        """
+        fp = kwargs.pop("file", sys.stdout)
+        if fp is None:
+            return
+
+        def write(data):
+            if not isinstance(data, basestring):
+                data = str(data)
+            fp.write(data)
+
+        want_unicode = False
+        sep = kwargs.pop("sep", None)
+        if sep is not None:
+            if isinstance(sep, unicode):
+                want_unicode = True
+            elif not isinstance(sep, str):
+                raise TypeError("sep must be None or a string")
+        end = kwargs.pop("end", None)
+        if end is not None:
+            if isinstance(end, unicode):
+                want_unicode = True
+            elif not isinstance(end, str):
+                raise TypeError("end must be None or a string")
+        if kwargs:
+            raise TypeError("invalid keyword arguments to print()")
+        if not want_unicode:
+            for arg in args:
+                if isinstance(arg, unicode):
+                    want_unicode = True
+                    break
+        if want_unicode:
+            newline = unicode("\n")
+            space = unicode(" ")
+        else:
+            newline = "\n"
+            space = " "
+        if sep is None:
+            sep = space
+        if end is None:
+            end = newline
+        for i, arg in enumerate(args):
+            if i:
+                write(sep)
+            write(arg)
+        write(end)
+else:
+    print_ = getattr(builtins, 'print')
+    del builtins
+
+
+def bound_socket(*args, **kwargs):
+    """Bind socket to a specified source IP address"""
+
+    global source
+    sock = socket_socket(*args, **kwargs)
+    sock.bind((source, 0))
+    return sock
+
+
+def distance(origin, destination):
+    """Determine distance between 2 sets of [lat,lon] in km"""
+
+    lat1, lon1 = origin
+    lat2, lon2 = destination
+    radius = 6371  # km
+
+    dlat = math.radians(lat2 - lat1)
+    dlon = math.radians(lon2 - lon1)
+    a = (math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos(math.radians(lat1))
+         * math.cos(math.radians(lat2)) * math.sin(dlon / 2)
+         * math.sin(dlon / 2))
+    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
+    d = radius * c
+
+    return d
+
+
+class FileGetter(threading.Thread):
+    """Thread class for retrieving a URL"""
+
+    def __init__(self, url, start):
+        self.url = url
+        self.result = None
+        self.starttime = start
+        threading.Thread.__init__(self)
+
+    def run(self):
+        self.result = [0]
+        try:
+            if (time.time() - self.starttime) <= 10:
+                f = urlopen(self.url)
+                while 1 and not shutdown_event.isSet():
+                    self.result.append(len(f.read(10240)))
+                    if self.result[-1] == 0:
+                        break
+                f.close()
+        except IOError:
+            pass
+
+
+def downloadSpeed(files, quiet=False):
+    """Function to launch FileGetter threads and calculate download speeds"""
+
+    start = time.time()
+
+    def producer(q, files):
+        for file in files:
+            thread = FileGetter(file, start)
+            thread.start()
+            q.put(thread, True)
+            if not quiet and not shutdown_event.isSet():
+                sys.stdout.write('.')
+                sys.stdout.flush()
+
+    finished = []
+
+    def consumer(q, total_files):
+        while len(finished) < total_files:
+            thread = q.get(True)
+            while thread.isAlive():
+                thread.join(timeout=0.1)
+            finished.append(sum(thread.result))
+            del thread
+
+    q = Queue(6)
+    prod_thread = threading.Thread(target=producer, args=(q, files))
+    cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
+    start = time.time()
+    prod_thread.start()
+    cons_thread.start()
+    while prod_thread.isAlive():
+        prod_thread.join(timeout=0.1)
+    while cons_thread.isAlive():
+        cons_thread.join(timeout=0.1)
+    return (sum(finished) / (time.time() - start))
+
+
+class FilePutter(threading.Thread):
+    """Thread class for putting a URL"""
+
+    def __init__(self, url, start, size):
+        self.url = url
+        chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+        data = chars * (int(round(int(size) / 36.0)))
+        self.data = ('content1=%s' % data[0:int(size) - 9]).encode()
+        del data
+        self.result = None
+        self.starttime = start
+        threading.Thread.__init__(self)
+
+    def run(self):
+        try:
+            if ((time.time() - self.starttime) <= 10 and
+                    not shutdown_event.isSet()):
+                f = urlopen(self.url, self.data)
+                f.read(11)
+                f.close()
+                self.result = len(self.data)
+            else:
+                self.result = 0
+        except IOError:
+            self.result = 0
+
+
+def uploadSpeed(url, sizes, quiet=False):
+    """Function to launch FilePutter threads and calculate upload speeds"""
+
+    start = time.time()
+
+    def producer(q, sizes):
+        for size in sizes:
+            thread = FilePutter(url, start, size)
+            thread.start()
+            q.put(thread, True)
+            if not quiet and not shutdown_event.isSet():
+                sys.stdout.write('.')
+                sys.stdout.flush()
+
+    finished = []
+
+    def consumer(q, total_sizes):
+        while len(finished) < total_sizes:
+            thread = q.get(True)
+            while thread.isAlive():
+                thread.join(timeout=0.1)
+            finished.append(thread.result)
+            del thread
+
+    q = Queue(6)
+    prod_thread = threading.Thread(target=producer, args=(q, sizes))
+    cons_thread = threading.Thread(target=consumer, args=(q, len(sizes)))
+    start = time.time()
+    prod_thread.start()
+    cons_thread.start()
+    while prod_thread.isAlive():
+        prod_thread.join(timeout=0.1)
+    while cons_thread.isAlive():
+        cons_thread.join(timeout=0.1)
+    return (sum(finished) / (time.time() - start))
+
+
+def getAttributesByTagName(dom, tagName):
+    """Retrieve an attribute from an XML document and return it in a
+    consistent format
+
+    Only used with xml.dom.minidom, which is likely only to be used
+    with python versions older than 2.5
+    """
+    elem = dom.getElementsByTagName(tagName)[0]
+    return dict(list(elem.attributes.items()))
+
+
+def getConfig():
+    """Download the speedtest.net configuration and return only the data
+    we are interested in
+    """
+
+    uh = urlopen('http://www.speedtest.net/speedtest-config.php')
+    configxml = []
+    while 1:
+        configxml.append(uh.read(10240))
+        if len(configxml[-1]) == 0:
+            break
+    if int(uh.code) != 200:
+        return None
+    uh.close()
+    try:
+        root = ET.fromstring(''.encode().join(configxml))
+        config = {
+            'client': root.find('client').attrib,
+            'times': root.find('times').attrib,
+            'download': root.find('download').attrib,
+            'upload': root.find('upload').attrib}
+    except AttributeError:
+        root = DOM.parseString(''.join(configxml))
+        config = {
+            'client': getAttributesByTagName(root, 'client'),
+            'times': getAttributesByTagName(root, 'times'),
+            'download': getAttributesByTagName(root, 'download'),
+            'upload': getAttributesByTagName(root, 'upload')}
+    del root
+    del configxml
+    return config
+
+
+def closestServers(client, all=False):
+    """Determine the 5 closest speedtest.net servers based on geographic
+    distance
+    """
+
+    uh = urlopen('http://www.speedtest.net/speedtest-servers.php')
+    serversxml = []
+    while 1:
+        serversxml.append(uh.read(10240))
+        if len(serversxml[-1]) == 0:
+            break
+    if int(uh.code) != 200:
+        return None
+    uh.close()
+    try:
+        root = ET.fromstring(''.encode().join(serversxml))
+        elements = root.getiterator('server')
+    except AttributeError:
+        root = DOM.parseString(''.join(serversxml))
+        elements = root.getElementsByTagName('server')
+    servers = {}
+    for server in elements:
+        try:
+            attrib = server.attrib
+        except AttributeError:
+            attrib = dict(list(server.attributes.items()))
+        d = distance([float(client['lat']), float(client['lon'])],
+                     [float(attrib.get('lat')), float(attrib.get('lon'))])
+        attrib['d'] = d
+        if d not in servers:
+            servers[d] = [attrib]
+        else:
+            servers[d].append(attrib)
+    del root
+    del serversxml
+    del elements
+
+    closest = []
+    for d in sorted(servers.keys()):
+        for s in servers[d]:
+            closest.append(s)
+            if len(closest) == 5 and not all:
+                break
+        else:
+            continue
+        break
+
+    del servers
+    return closest
+
+
+def getBestServer(servers):
+    """Perform a speedtest.net "ping" to determine which speedtest.net
+    server has the lowest latency
+    """
+
+    results = {}
+    for server in servers:
+        cum = []
+        url = os.path.dirname(server['url'])
+        for i in range(0, 3):
+            try:
+                uh = urlopen('%s/latency.txt' % url)
+            except HTTPError:
+                cum.append(3600)
+                continue
+            start = time.time()
+            text = uh.read(9)
+            total = time.time() - start
+            if int(uh.code) == 200 and text == 'test=test'.encode():
+                cum.append(total)
+            else:
+                cum.append(3600)
+            uh.close()
+        avg = round((sum(cum) / 3) * 1000000, 3)
+        results[avg] = server
+
+    fastest = sorted(results.keys())[0]
+    best = results[fastest]
+    best['latency'] = fastest
+
+    return best
+
+
+def ctrl_c(signum, frame):
+    """Catch Ctrl-C key sequence and set a shutdown_event for our threaded
+    operations
+    """
+
+    global shutdown_event
+    shutdown_event.set()
+    raise SystemExit('\nCancelling...')
+
+
+def version():
+    """Print the version"""
+
+    raise SystemExit(__version__)
+
+
+def speedtest():
+    """Run the full speedtest.net test"""
+
+    global shutdown_event, source
+    shutdown_event = threading.Event()
+
+    signal.signal(signal.SIGINT, ctrl_c)
+
+    description = (
+        'Command line interface for testing internet bandwidth using '
+        'speedtest.net.\n'
+        '------------------------------------------------------------'
+        '--------------\n'
+        'https://github.com/sivel/speedtest-cli')
+
+    parser = ArgParser(description=description)
+    # Give optparse.OptionParser an `add_argument` method for
+    # compatibility with argparse.ArgumentParser
+    try:
+        parser.add_argument = parser.add_option
+    except AttributeError:
+        pass
+    parser.add_argument('--share', action='store_true',
+                        help='Generate and provide a URL to the speedtest.net '
+                             'share results image')
+    parser.add_argument('--simple', action='store_true',
+                        help='Suppress verbose output, only show basic '
+                             'information')
+    parser.add_argument('--list', action='store_true',
+                        help='Display a list of speedtest.net servers '
+                             'sorted by distance')
+    parser.add_argument('--server', help='Specify a server ID to test against')
+    parser.add_argument('--mini', help='URL of the Speedtest Mini server')
+    parser.add_argument('--source', help='Source IP address to bind to')
+    parser.add_argument('--version', action='store_true',
+                        help='Show the version number and exit')
+
+    options = parser.parse_args()
+    if isinstance(options, tuple):
+        args = options[0]
+    else:
+        args = options
+    del options
+
+    # Print the version and exit
+    if args.version:
+        version()
+
+    # If specified bind to a specific IP address
+    if args.source:
+        source = args.source
+        socket.socket = bound_socket
+
+    if not args.simple:
+        print_('Retrieving speedtest.net configuration...')
+    try:
+        config = getConfig()
+    except URLError:
+        print_('Cannot retrieve speedtest configuration')
+        sys.exit(1)
+
+    if not args.simple:
+        print_('Retrieving speedtest.net server list...')
+    if args.list or args.server:
+        servers = closestServers(config['client'], True)
+        if args.list:
+            serverList = []
+            for server in servers:
+                line = ('%(id)4s) %(sponsor)s (%(name)s, %(country)s) '
+                        '[%(d)0.2f km]' % server)
+                serverList.append(line)
+            # Python 2.7 and newer seem to be ok with the resultant encoding
+            # from parsing the XML, but older versions have some issues.
+            # This block should detect whether we need to encode or not
+            try:
+                unicode()
+                print_('\n'.join(serverList).encode('utf-8', 'ignore'))
+            except NameError:
+                print_('\n'.join(serverList))
+            except IOError:
+                pass
+            sys.exit(0)
+    else:
+        servers = closestServers(config['client'])
+
+    if not args.simple:
+        print_('Testing from %(isp)s (%(ip)s)...' % config['client'])
+
+    if args.server:
+        try:
+            best = getBestServer(filter(lambda x: x['id'] == args.server,
+                                        servers))
+        except IndexError:
+            print_('Invalid server ID')
+            sys.exit(1)
+    elif args.mini:
+        name, ext = os.path.splitext(args.mini)
+        if ext:
+            url = os.path.dirname(args.mini)
+        else:
+            url = args.mini
+        urlparts = urlparse(url)
+        try:
+            f = urlopen(args.mini)
+        except:
+            print_('Invalid Speedtest Mini URL')
+            sys.exit(1)
+        else:
+            text = f.read()
+            f.close()
+        extension = re.findall('upload_extension: "([^"]+)"', text.decode())
+        if not urlparts or not extension:
+            print_('Please provide the full URL of your Speedtest Mini server')
+            sys.exit(1)
+        servers = [{
+            'sponsor': 'Speedtest Mini',
+            'name': urlparts[1],
+            'd': 0,
+            'url': '%s/speedtest/upload.%s' % (url.rstrip('/'), extension[0]),
+            'latency': 0,
+            'id': 0
+        }]
+        try:
+            best = getBestServer(servers)
+        except:
+            best = servers[0]
+    else:
+        if not args.simple:
+            print_('Selecting best server based on ping...')
+        best = getBestServer(servers)
+
+    if not args.simple:
+        # Python 2.7 and newer seem to be ok with the resultant encoding
+        # from parsing the XML, but older versions have some issues.
+        # This block should detect whether we need to encode or not
+        try:
+            unicode()
+            print_(('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
+                   '%(latency)s ms' % best).encode('utf-8', 'ignore'))
+        except NameError:
+            print_('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
+                   '%(latency)s ms' % best)
+    else:
+        print_('Ping: %(latency)s ms' % best)
+
+    sizes = [350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000]
+    urls = []
+    for size in sizes:
+        for i in range(0, 4):
+            urls.append('%s/random%sx%s.jpg' %
+                        (os.path.dirname(best['url']), size, size))
+    if not args.simple:
+        print_('Testing download speed', end='')
+    dlspeed = downloadSpeed(urls, args.simple)
+    if not args.simple:
+        print_()
+    print_('Download: %0.2f Mbit/s' % ((dlspeed / 1000 / 1000) * 8))
+
+    sizesizes = [int(.25 * 1000 * 1000), int(.5 * 1000 * 1000)]
+    sizes = []
+    for size in sizesizes:
+        for i in range(0, 25):
+            sizes.append(size)
+    if not args.simple:
+        print_('Testing upload speed', end='')
+    ulspeed = uploadSpeed(best['url'], sizes, args.simple)
+    if not args.simple:
+        print_()
+    print_('Upload: %0.2f Mbit/s' % ((ulspeed / 1000 / 1000) * 8))
+
+    if args.share and args.mini:
+        print_('Cannot generate a speedtest.net share results image while '
+               'testing against a Speedtest Mini server')
+    elif args.share:
+        dlspeedk = int(round((dlspeed / 1000) * 8, 0))
+        ping = int(round(best['latency'], 0))
+        ulspeedk = int(round((ulspeed / 1000) * 8, 0))
+
+        # Build the request to send results back to speedtest.net
+        # We use a list instead of a dict because the API expects parameters
+        # in a certain order
+        apiData = [
+            'download=%s' % dlspeedk,
+            'ping=%s' % ping,
+            'upload=%s' % ulspeedk,
+            'promo=',
+            'startmode=%s' % 'pingselect',
+            'recommendedserverid=%s' % best['id'],
+            'accuracy=%s' % 1,
+            'serverid=%s' % best['id'],
+            'hash=%s' % md5(('%s-%s-%s-%s' %
+                             (ping, ulspeedk, dlspeedk, '297aae72'))
+                            .encode()).hexdigest()]
+
+        req = Request('http://www.speedtest.net/api/api.php',
+                      data='&'.join(apiData).encode())
+        req.add_header('Referer', 'http://c.speedtest.net/flash/speedtest.swf')
+        f = urlopen(req)
+        response = f.read()
+        code = f.code
+        f.close()
+
+        if int(code) != 200:
+            print_('Could not submit results to speedtest.net')
+            sys.exit(1)
+
+        qsargs = parse_qs(response.decode())
+        resultid = qsargs.get('resultid')
+        if not resultid or len(resultid) != 1:
+            print_('Could not submit results to speedtest.net')
+            sys.exit(1)
+
+        print_('Share results: http://www.speedtest.net/result/%s.png' %
+               resultid[0])
+
+
+def main():
+    try:
+        speedtest()
+    except KeyboardInterrupt:
+        print_('\nCancelling...')
+
+
+if __name__ == '__main__':
+    main()
+
+# vim:ts=4:sw=4:expandtab