14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# -*- coding: utf-8 -*-
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# This file should be kept compatible with both Python 2.6 and Python >= 3.0.
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmfrom __future__ import division
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmfrom __future__ import print_function
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"""
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmccbench, a Python concurrency benchmark.
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"""
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport time
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport os
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport sys
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport functools
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport itertools
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport threading
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport subprocess
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport socket
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmfrom optparse import OptionParser, SUPPRESS_HELP
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport platform
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Compatibility
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmtry:
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    xrange
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmexcept NameError:
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    xrange = range
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmtry:
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    map = itertools.imap
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmexcept AttributeError:
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    pass
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmTHROUGHPUT_DURATION = 2.0
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmLATENCY_PING_INTERVAL = 0.1
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmLATENCY_DURATION = 2.0
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmBANDWIDTH_PACKET_SIZE = 1024
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmBANDWIDTH_DURATION = 2.0
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef task_pidigits():
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Pi calculation (Python)"""
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _map = map
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _count = itertools.count
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _islice = itertools.islice
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def calc_ndigits(n):
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # From http://shootout.alioth.debian.org/
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def gen_x():
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return _map(lambda k: (k, 4*k + 2, 0, 2*k + 1), _count(1))
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def compose(a, b):
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            aq, ar, as_, at = a
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            bq, br, bs, bt = b
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return (aq * bq,
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    aq * br + ar * bt,
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    as_ * bq + at * bs,
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    as_ * br + at * bt)
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def extract(z, j):
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            q, r, s, t = z
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return (q*j + r) // (s*j + t)
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def pi_digits():
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            z = (1, 0, 0, 1)
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            x = gen_x()
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while 1:
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                y = extract(z, 3)
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                while y != extract(z, 4):
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    z = compose(z, next(x))
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    y = extract(z, 3)
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                z = compose((10, -10*y, 0, 1), z)
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                yield y
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return list(_islice(pi_digits(), n))
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return calc_ndigits, (50, )
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef task_regex():
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """regular expression (C)"""
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # XXX this task gives horrendous latency results.
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import re
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Taken from the `inspect` module
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)', re.MULTILINE)
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with open(__file__, "r") as f:
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        arg = f.read(2000)
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def findall(s):
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t = time.time()
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return pat.findall(s)
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        finally:
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print(time.time() - t)
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return pat.findall, (arg, )
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef task_sort():
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """list sorting (C)"""
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def list_sort(l):
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        l = l[::-1]
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        l.sort()
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return list_sort, (list(range(1000)), )
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef task_compress_zlib():
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """zlib compression (C)"""
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import zlib
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with open(__file__, "rb") as f:
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        arg = f.read(5000) * 3
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def compress(s):
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        zlib.decompress(zlib.compress(s, 5))
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return compress, (arg, )
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef task_compress_bz2():
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """bz2 compression (C)"""
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import bz2
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with open(__file__, "rb") as f:
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        arg = f.read(3000) * 2
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def compress(s):
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        bz2.compress(s)
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return compress, (arg, )
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef task_hashing():
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """SHA1 hashing (C)"""
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import hashlib
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with open(__file__, "rb") as f:
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        arg = f.read(5000) * 30
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def compute(s):
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        hashlib.sha1(s).digest()
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return compute, (arg, )
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmthroughput_tasks = [task_pidigits, task_regex]
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmfor mod in 'bz2', 'hashlib':
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        globals()[mod] = __import__(mod)
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except ImportError:
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        globals()[mod] = None
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# For whatever reasons, zlib gives irregular results, so we prefer bz2 or
1454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# hashlib if available.
1464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# (NOTE: hashlib releases the GIL from 2.7 and 3.1 onwards)
1474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmif bz2 is not None:
1484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    throughput_tasks.append(task_compress_bz2)
1494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmelif hashlib is not None:
1504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    throughput_tasks.append(task_hashing)
1514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmelse:
1524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    throughput_tasks.append(task_compress_zlib)
1534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmlatency_tasks = throughput_tasks
1554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmbandwidth_tasks = [task_pidigits]
1564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass TimedLoop:
1594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, func, args):
1604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.func = func
1614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.args = args
1624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __call__(self, start_time, min_duration, end_event, do_yield=False):
1644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        step = 20
1654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        niters = 0
1664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        duration = 0.0
1674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _time = time.time
1684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _sleep = time.sleep
1694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _func = self.func
1704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _args = self.args
1714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t1 = start_time
1724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while True:
1734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            for i in range(step):
1744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                _func(*_args)
1754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            t2 = _time()
1764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # If another thread terminated, the current measurement is invalid
1774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # => return the previous one.
1784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if end_event:
1794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return niters, duration
1804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            niters += step
1814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            duration = t2 - start_time
1824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if duration >= min_duration:
1834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                end_event.append(None)
1844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return niters, duration
1854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if t2 - t1 < 0.01:
1864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # Minimize interference of measurement on overall runtime
1874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                step = step * 3 // 2
1884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif do_yield:
1894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # OS scheduling of Python threads is sometimes so bad that we
1904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # have to force thread switching ourselves, otherwise we get
1914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # completely useless results.
1924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                _sleep(0.0001)
1934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            t1 = t2
1944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_throughput_test(func, args, nthreads):
1974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    assert nthreads >= 1
1984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Warm up
2004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    func(*args)
2014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    results = []
2034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    loop = TimedLoop(func, args)
2044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    end_event = []
2054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if nthreads == 1:
2074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Pure single-threaded performance, without any switching or
2084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # synchronization overhead.
2094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_time = time.time()
2104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        results.append(loop(start_time, THROUGHPUT_DURATION,
2114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                            end_event, do_yield=False))
2124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return results
2134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    started = False
2154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    ready_cond = threading.Condition()
2164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    start_cond = threading.Condition()
2174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    ready = []
2184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def run():
2204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        with ready_cond:
2214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ready.append(None)
2224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ready_cond.notify()
2234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        with start_cond:
2244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while not started:
2254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                start_cond.wait()
2264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        results.append(loop(start_time, THROUGHPUT_DURATION,
2274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                            end_event, do_yield=True))
2284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    threads = []
2304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for i in range(nthreads):
2314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        threads.append(threading.Thread(target=run))
2324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for t in threads:
2334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t.setDaemon(True)
2344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t.start()
2354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # We don't want measurements to include thread startup overhead,
2364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # so we arrange for timing to start after all threads are ready.
2374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with ready_cond:
2384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while len(ready) < nthreads:
2394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ready_cond.wait()
2404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with start_cond:
2414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_time = time.time()
2424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        started = True
2434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_cond.notify(nthreads)
2444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for t in threads:
2454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t.join()
2464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return results
2484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_throughput_tests(max_threads):
2504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for task in throughput_tasks:
2514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print(task.__doc__)
2524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
2534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func, args = task()
2544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        nthreads = 1
2554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        baseline_speed = None
2564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while nthreads <= max_threads:
2574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            results = run_throughput_test(func, args, nthreads)
2584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # Taking the max duration rather than average gives pessimistic
2594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # results rather than optimistic.
2604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            speed = sum(r[0] for r in results) / max(r[1] for r in results)
2614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print("threads=%d: %d" % (nthreads, speed), end="")
2624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if baseline_speed is None:
2634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print(" iterations/s.")
2644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                baseline_speed = speed
2654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
2664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print(" ( %d %%)" % (speed / baseline_speed * 100))
2674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            nthreads += 1
2684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
2694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmLAT_END = "END"
2724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef _sendto(sock, s, addr):
2744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock.sendto(s.encode('ascii'), addr)
2754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef _recv(sock, n):
2774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return sock.recv(n).decode('ascii')
2784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef latency_client(addr, nb_pings, interval):
2804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _time = time.time
2824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _sleep = time.sleep
2834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _ping():
2844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _sendto(sock, "%r\n" % _time(), addr)
2854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # The first ping signals the parent process that we are ready.
2864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _ping()
2874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # We give the parent a bit of time to notice.
2884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _sleep(1.0)
2894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for i in range(nb_pings):
2904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _sleep(interval)
2914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _ping()
2924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _sendto(sock, LAT_END + "\n", addr)
2934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_latency_client(**kwargs):
2954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    cmd_line = [sys.executable, '-E', os.path.abspath(__file__)]
2964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    cmd_line.extend(['--latclient', repr(kwargs)])
2974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return subprocess.Popen(cmd_line) #, stdin=subprocess.PIPE,
2984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                            #stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
2994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_latency_test(func, args, nthreads):
3014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Create a listening socket to receive the pings. We use UDP which should
3024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # be painlessly cross-platform.
3034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
3044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock.bind(("127.0.0.1", 0))
3054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    addr = sock.getsockname()
3064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    interval = LATENCY_PING_INTERVAL
3084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    duration = LATENCY_DURATION
3094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    nb_pings = int(duration / interval)
3104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    results = []
3124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    threads = []
3134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    end_event = []
3144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    start_cond = threading.Condition()
3154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    started = False
3164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if nthreads > 0:
3174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Warm up
3184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func(*args)
3194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        results = []
3214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        loop = TimedLoop(func, args)
3224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        ready = []
3234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        ready_cond = threading.Condition()
3244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def run():
3264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            with ready_cond:
3274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                ready.append(None)
3284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                ready_cond.notify()
3294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            with start_cond:
3304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                while not started:
3314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    start_cond.wait()
3324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            loop(start_time, duration * 1.5, end_event, do_yield=False)
3334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for i in range(nthreads):
3354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            threads.append(threading.Thread(target=run))
3364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for t in threads:
3374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            t.setDaemon(True)
3384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            t.start()
3394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Wait for threads to be ready
3404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        with ready_cond:
3414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while len(ready) < nthreads:
3424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                ready_cond.wait()
3434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Run the client and wait for the first ping(s) to arrive before
3454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # unblocking the background threads.
3464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    chunks = []
3474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    process = run_latency_client(addr=sock.getsockname(),
3484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                                 nb_pings=nb_pings, interval=interval)
3494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s = _recv(sock, 4096)
3504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _time = time.time
3514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with start_cond:
3534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_time = _time()
3544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        started = True
3554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_cond.notify(nthreads)
3564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while LAT_END not in s:
3584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        s = _recv(sock, 4096)
3594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t = _time()
3604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        chunks.append((t, s))
3614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Tell the background threads to stop.
3634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    end_event.append(None)
3644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for t in threads:
3654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t.join()
3664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    process.wait()
3674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for recv_time, chunk in chunks:
3694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # NOTE: it is assumed that a line sent by a client wasn't received
3704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # in two chunks because the lines are very small.
3714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for line in chunk.splitlines():
3724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = line.strip()
3734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if line and line != LAT_END:
3744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                send_time = eval(line)
3754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                assert isinstance(send_time, float)
3764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                results.append((send_time, recv_time))
3774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return results
3794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_latency_tests(max_threads):
3814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for task in latency_tasks:
3824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print("Background CPU task:", task.__doc__)
3834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
3844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func, args = task()
3854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        nthreads = 0
3864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while nthreads <= max_threads:
3874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            results = run_latency_test(func, args, nthreads)
3884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            n = len(results)
3894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # We print out milliseconds
3904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            lats = [1000 * (t2 - t1) for (t1, t2) in results]
3914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            #print(list(map(int, lats)))
3924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            avg = sum(lats) / n
3934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            dev = (sum((x - avg) ** 2 for x in lats) / n) ** 0.5
3944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print("CPU threads=%d: %d ms. (std dev: %d ms.)" % (nthreads, avg, dev), end="")
3954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print()
3964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            #print("    [... from %d samples]" % n)
3974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            nthreads += 1
3984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
3994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmBW_END = "END"
4024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef bandwidth_client(addr, packet_size, duration):
4044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
4054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock.bind(("127.0.0.1", 0))
4064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    local_addr = sock.getsockname()
4074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _time = time.time
4084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _sleep = time.sleep
4094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _send_chunk(msg):
4104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _sendto(sock, ("%r#%s\n" % (local_addr, msg)).rjust(packet_size), addr)
4114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # We give the parent some time to be ready.
4124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _sleep(1.0)
4134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
4144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_time = _time()
4154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        end_time = start_time + duration * 2.0
4164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        i = 0
4174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while _time() < end_time:
4184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            _send_chunk(str(i))
4194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            s = _recv(sock, packet_size)
4204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            assert len(s) == packet_size
4214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            i += 1
4224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _send_chunk(BW_END)
4234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    finally:
4244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sock.close()
4254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_bandwidth_client(**kwargs):
4274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    cmd_line = [sys.executable, '-E', os.path.abspath(__file__)]
4284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    cmd_line.extend(['--bwclient', repr(kwargs)])
4294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return subprocess.Popen(cmd_line) #, stdin=subprocess.PIPE,
4304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                            #stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
4314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_bandwidth_test(func, args, nthreads):
4334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Create a listening socket to receive the packets. We use UDP which should
4344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # be painlessly cross-platform.
4354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
4364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sock.bind(("127.0.0.1", 0))
4374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    addr = sock.getsockname()
4384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    duration = BANDWIDTH_DURATION
4404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    packet_size = BANDWIDTH_PACKET_SIZE
4414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    results = []
4434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    threads = []
4444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    end_event = []
4454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    start_cond = threading.Condition()
4464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    started = False
4474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if nthreads > 0:
4484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Warm up
4494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func(*args)
4504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        results = []
4524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        loop = TimedLoop(func, args)
4534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        ready = []
4544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        ready_cond = threading.Condition()
4554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def run():
4574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            with ready_cond:
4584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                ready.append(None)
4594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                ready_cond.notify()
4604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            with start_cond:
4614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                while not started:
4624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    start_cond.wait()
4634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            loop(start_time, duration * 1.5, end_event, do_yield=False)
4644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for i in range(nthreads):
4664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            threads.append(threading.Thread(target=run))
4674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for t in threads:
4684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            t.setDaemon(True)
4694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            t.start()
4704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Wait for threads to be ready
4714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        with ready_cond:
4724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while len(ready) < nthreads:
4734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                ready_cond.wait()
4744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Run the client and wait for the first packet to arrive before
4764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # unblocking the background threads.
4774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    process = run_bandwidth_client(addr=addr,
4784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                                   packet_size=packet_size,
4794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                                   duration=duration)
4804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    _time = time.time
4814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # This will also wait for the parent to be ready
4824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s = _recv(sock, packet_size)
4834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    remote_addr = eval(s.partition('#')[0])
4844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    with start_cond:
4864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_time = _time()
4874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        started = True
4884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        start_cond.notify(nthreads)
4894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    n = 0
4914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    first_time = None
4924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while not end_event and BW_END not in s:
4934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        _sendto(sock, s, remote_addr)
4944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        s = _recv(sock, packet_size)
4954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if first_time is None:
4964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            first_time = _time()
4974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        n += 1
4984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    end_time = _time()
4994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    end_event.append(None)
5014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for t in threads:
5024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t.join()
5034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    process.kill()
5044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return (n - 1) / (end_time - first_time)
5064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef run_bandwidth_tests(max_threads):
5084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for task in bandwidth_tasks:
5094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print("Background CPU task:", task.__doc__)
5104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
5114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func, args = task()
5124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        nthreads = 0
5134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        baseline_speed = None
5144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while nthreads <= max_threads:
5154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            results = run_bandwidth_test(func, args, nthreads)
5164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            speed = results
5174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            #speed = len(results) * 1.0 / results[-1][0]
5184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print("CPU threads=%d: %.1f" % (nthreads, speed), end="")
5194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if baseline_speed is None:
5204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print(" packets/s.")
5214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                baseline_speed = speed
5224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
5234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print(" ( %d %%)" % (speed / baseline_speed * 100))
5244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            nthreads += 1
5254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
5264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef main():
5294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    usage = "usage: %prog [-h|--help] [options]"
5304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser = OptionParser(usage=usage)
5314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("-t", "--throughput",
5324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store_true", dest="throughput", default=False,
5334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help="run throughput tests")
5344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("-l", "--latency",
5354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store_true", dest="latency", default=False,
5364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help="run latency tests")
5374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("-b", "--bandwidth",
5384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store_true", dest="bandwidth", default=False,
5394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help="run I/O bandwidth tests")
5404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("-i", "--interval",
5414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store", type="int", dest="check_interval", default=None,
5424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help="sys.setcheckinterval() value")
5434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("-I", "--switch-interval",
5444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store", type="float", dest="switch_interval", default=None,
5454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help="sys.setswitchinterval() value")
5464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("-n", "--num-threads",
5474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store", type="int", dest="nthreads", default=4,
5484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help="max number of threads in tests")
5494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Hidden option to run the pinging and bandwidth clients
5514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("", "--latclient",
5524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store", dest="latclient", default=None,
5534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help=SUPPRESS_HELP)
5544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    parser.add_option("", "--bwclient",
5554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      action="store", dest="bwclient", default=None,
5564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                      help=SUPPRESS_HELP)
5574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    options, args = parser.parse_args()
5594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if args:
5604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        parser.error("unexpected arguments")
5614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.latclient:
5634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        kwargs = eval(options.latclient)
5644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        latency_client(**kwargs)
5654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return
5664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.bwclient:
5684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        kwargs = eval(options.bwclient)
5694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        bandwidth_client(**kwargs)
5704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return
5714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if not options.throughput and not options.latency and not options.bandwidth:
5734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        options.throughput = options.latency = options.bandwidth = True
5744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.check_interval:
5754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sys.setcheckinterval(options.check_interval)
5764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.switch_interval:
5774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sys.setswitchinterval(options.switch_interval)
5784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    print("== %s %s (%s) ==" % (
5804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        platform.python_implementation(),
5814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        platform.python_version(),
5824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        platform.python_build()[0],
5834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    ))
5844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Processor identification often has repeated spaces
5854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    cpu = ' '.join(platform.processor().split())
5864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    print("== %s %s on '%s' ==" % (
5874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        platform.machine(),
5884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        platform.system(),
5894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        cpu,
5904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    ))
5914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    print()
5924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.throughput:
5944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print("--- Throughput ---")
5954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
5964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        run_throughput_tests(options.nthreads)
5974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
5984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.latency:
5994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print("--- Latency ---")
6004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
6014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        run_latency_tests(options.nthreads)
6024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
6034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if options.bandwidth:
6044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print("--- I/O bandwidth ---")
6054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print()
6064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        run_bandwidth_tests(options.nthreads)
6074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
6084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmif __name__ == "__main__":
6094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    main()
610