├── requirements.txt
├── iperf3
├── __init__.py
└── iperf3.py
├── MANIFEST.in
├── docs
├── source
│ ├── _static
│ │ └── iperf3-python.png
│ ├── _templates
│ │ ├── sidebarlogo.html
│ │ └── sidebarintro.html
│ ├── modules.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── examples.rst
│ └── conf.py
└── Makefile
├── tox.ini
├── LICENSE
├── examples
├── client.py
├── server.py
└── udp_client.py
├── .gitignore
├── HISTORY.rst
├── setup.py
├── .travis.yml
├── README.rst
└── tests
├── results.json
└── test_iperf3.py
/requirements.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/iperf3/__init__.py:
--------------------------------------------------------------------------------
1 | from .iperf3 import Client, Server, TestResult, IPerf3
2 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include AUTHORS COPYING INSTALL LICENSE requirements.txt *.rst
2 | recursive-exclude *.pyc *.pyo
3 |
--------------------------------------------------------------------------------
/docs/source/_static/iperf3-python.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thiezn/iperf3-python/HEAD/docs/source/_static/iperf3-python.png
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | # content of: tox.ini , put in same dir as setup.py
2 | [tox]
3 | envlist = py27, py35
4 | [testenv]
5 | deps=pytest
6 | commands=py.test
7 |
--------------------------------------------------------------------------------
/docs/source/_templates/sidebarlogo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/source/modules.rst:
--------------------------------------------------------------------------------
1 | Modules
2 | =======
3 |
4 | iperf3
5 | ~~~~~~
6 | .. automodule:: iperf3
7 |
8 | Client
9 | ++++++
10 |
11 | .. autoclass:: Client
12 | :members:
13 |
14 | Server
15 | ++++++
16 |
17 | .. autoclass:: Server
18 | :members:
19 |
20 | TestResult
21 | ++++++++++
22 |
23 | .. autoclass:: TestResult
24 | :members:
25 |
26 | IPerf3
27 | ++++++
28 |
29 | .. autoclass:: IPerf3
30 | :members:
31 |
--------------------------------------------------------------------------------
/docs/source/_templates/sidebarintro.html:
--------------------------------------------------------------------------------
1 | About iperf3 for Python
2 |
3 | iperf3 is a tool for active measurements of the maximum achievable bandwidth on IP networks.
4 | This is a python wrapper providing a nice and pythonic interface to the tool.
5 |
6 | Useful Links
7 |
12 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. iperf3 documentation master file, created by
2 | sphinx-quickstart on Tue Aug 16 09:33:31 2016.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | iperf3 python wrapper
7 | =====================
8 |
9 | Release v\ |version|.
10 |
11 | iPerf3 is a tool for active measurements of the maximum achievable bandwidth on IP networks. More
12 | information on the iPerf3 utility can be found on their `official website `_
13 |
14 | The python iperf3 module is a wrapper around the iperf3 utility. It utilises the API libiperf
15 | that comes with the default installation. It allows you to interact with the utility in a nice and
16 | pythonic way.
17 |
18 | **warning** This module is not compatible with the original iperf/iperf2 utility which is no longer under active development
19 |
20 | .. toctree::
21 | :maxdepth: 3
22 |
23 | installation
24 | examples
25 | modules
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Mathijs Mortimer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import iperf3
4 |
5 | client = iperf3.Client()
6 | client.duration = 1
7 | client.server_hostname = '127.0.0.1'
8 | client.port = 5201
9 |
10 | print('Connecting to {0}:{1}'.format(client.server_hostname, client.port))
11 | result = client.run()
12 |
13 | if result.error:
14 | print(result.error)
15 | else:
16 | print('')
17 | print('Test completed:')
18 | print(' started at {0}'.format(result.time))
19 | print(' bytes transmitted {0}'.format(result.sent_bytes))
20 | print(' retransmits {0}'.format(result.retransmits))
21 | print(' avg cpu load {0}%\n'.format(result.local_cpu_total))
22 |
23 | print('Average transmitted data in all sorts of networky formats:')
24 | print(' bits per second (bps) {0}'.format(result.sent_bps))
25 | print(' Kilobits per second (kbps) {0}'.format(result.sent_kbps))
26 | print(' Megabits per second (Mbps) {0}'.format(result.sent_Mbps))
27 | print(' KiloBytes per second (kB/s) {0}'.format(result.sent_kB_s))
28 | print(' MegaBytes per second (MB/s) {0}'.format(result.sent_MB_s))
29 |
--------------------------------------------------------------------------------
/examples/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import iperf3
4 |
5 |
6 | server = iperf3.Server()
7 | print('Running server: {0}:{1}'.format(server.bind_address, server.port))
8 |
9 | while True:
10 | result = server.run()
11 |
12 | if result.error:
13 | print(result.error)
14 | else:
15 | print('')
16 | print('Test results from {0}:{1}'.format(result.remote_host,
17 | result.remote_port))
18 | print(' started at {0}'.format(result.time))
19 | print(' bytes received {0}'.format(result.received_bytes))
20 |
21 | print('Average transmitted received in all sorts of networky formats:')
22 | print(' bits per second (bps) {0}'.format(result.received_bps))
23 | print(' Kilobits per second (kbps) {0}'.format(result.received_kbps))
24 | print(' Megabits per second (Mbps) {0}'.format(result.received_Mbps))
25 | print(' KiloBytes per second (kB/s) {0}'.format(result.received_kB_s))
26 | print(' MegaBytes per second (MB/s) {0}'.format(result.received_MB_s))
27 | print('')
28 |
--------------------------------------------------------------------------------
/examples/udp_client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import iperf3
4 |
5 | client = iperf3.Client()
6 | client.duration = 1
7 | client.server_hostname = '127.0.0.1'
8 | client.port = 5201
9 | client.protocol = 'udp'
10 |
11 |
12 | print('Connecting to {0}:{1}'.format(client.server_hostname, client.port))
13 | result = client.run()
14 |
15 | if result.error:
16 | print(result.error)
17 | else:
18 | print('')
19 | print('Test completed:')
20 | print(' started at {0}'.format(result.time))
21 | print(' bytes transmitted {0}'.format(result.bytes))
22 | print(' jitter (ms) {0}'.format(result.jitter_ms))
23 | print(' avg cpu load {0}%\n'.format(result.local_cpu_total))
24 |
25 | print('Average transmitted data in all sorts of networky formats:')
26 | print(' bits per second (bps) {0}'.format(result.bps))
27 | print(' Kilobits per second (kbps) {0}'.format(result.kbps))
28 | print(' Megabits per second (Mbps) {0}'.format(result.Mbps))
29 | print(' KiloBytes per second (kB/s) {0}'.format(result.kB_s))
30 | print(' MegaBytes per second (MB/s) {0}'.format(result.MB_s))
31 |
--------------------------------------------------------------------------------
/docs/source/installation.rst:
--------------------------------------------------------------------------------
1 | .. _installation:
2 |
3 | Installation
4 | ============
5 |
6 | To be able to utilise the python wrapper around iperf3 you will need to have the
7 | libiperf.so.0 shared library installed. Luckily this comes with the standard iperf3
8 | build.
9 |
10 |
11 | iperf3 utility
12 | ~~~~~~~~~~~~~~
13 |
14 | Preferably get the latest build from the iperf3 `official website `__
15 |
16 | Otherwise try your OS package manager:
17 |
18 | - Ubuntu:
19 |
20 | .. code-block:: bash
21 |
22 | sudo apt-get install iperf3
23 |
24 | - CentOS/RedHat
25 |
26 | .. code-block:: bash
27 |
28 | sudo yum install iperf3
29 |
30 |
31 | iperf3 python wrapper
32 | ~~~~~~~~~~~~~~~~~~~~~
33 |
34 | The preferred installation method is through PyPi (aka pip install)
35 |
36 | .. code-block:: bash
37 |
38 | pip install iperf3
39 |
40 | If pip is unavailable for any reason you can also manually install from github:
41 |
42 | .. code-block:: bash
43 |
44 | git clone https://github.com/thiezn/iperf3-python.git
45 | cd iperf3-python
46 | python3 setup.py test # (optional) testing through py.test and/or tox
47 | python3 setup.py install
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Editing
2 | *.swp
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | env/
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *,cover
49 | .hypothesis/
50 |
51 | # Translations
52 | *.mo
53 | *.pot
54 |
55 | # Django stuff:
56 | *.log
57 | local_settings.py
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # IPython Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # dotenv
82 | .env
83 |
84 | # virtualenv
85 | venv/
86 | ENV/
87 |
88 | # Spyder project settings
89 | .spyderproject
90 |
91 | # Rope project settings
92 | .ropeproject
93 |
--------------------------------------------------------------------------------
/HISTORY.rst:
--------------------------------------------------------------------------------
1 | .. :changelog:
2 |
3 | Release History
4 | ---------------
5 |
6 | 0.1.11 (2019-04-13)
7 | ++++++++++++++++++
8 | - Fixed kB_s and MB_s in UDP test results (Thanks @gleichda)
9 | - Added omit option (Thanks @ChristofKaufmann)
10 |
11 | 0.1.10 (2018-03-28)
12 | +++++++++++++++++++
13 | - Allow manual set of libiperf library path and name using lib_name kwarg on Iperf3 Class
14 |
15 | 0.1.9 (2018-02-22)
16 | ++++++++++++++++++
17 | - Use find_library to load libiperf (Thanks to @Austinpayne). This should allow iperf3 lib to run on Mac OS X.
18 |
19 | 0.1.8 (2018-01-24)
20 | ++++++++++++++++++
21 | - Fixed segmentation fault on several Linux distro's (Thanks to @illu89)
22 | - Added Travis testing against latest iperf3 releases (3.3)
23 | - Renamed bulksize argument to blksize to keep naming in line with iperf3 C library. bulksize argument still available for backwards compatibility
24 |
25 | 0.1.7 (2017-08-02)
26 | ++++++++++++++++++
27 | - Fixed Mbps vs MB_s calculations (Thanks to @rustyhowell)
28 |
29 | 0.1.6 (2017-06-11)
30 | ++++++++++++++++++
31 | - iperf3.__del__ now properly closing FD and pipes (Thanks to @p0intR)
32 |
33 | 0.1.5 (2017-05-22)
34 | ++++++++++++++++++
35 | - iperf3.Client() now allows redirection of stdout (iperf3 version => 3.1 required)
36 |
37 | 0.1.4 (2017-05-15)
38 | ++++++++++++++++++
39 | - Fixed server json_output=False feature
40 |
41 | 0.1.3 (2017-05-15)
42 | ++++++++++++++++++
43 | - Added UDP support (thanks to @fciaccia)
44 | - Added bandwidth parameter
45 | - json_output = False will now print testresults to screen
46 |
47 | 0.1.2 (2016-09-22)
48 | ++++++++++++++++++
49 | - Improved zerocopy setter validation
50 | - Fix for incorrect return of reverse flag (thanks to @fciaccia)
51 |
52 | 0.1.1 (2016-09-12)
53 | ++++++++++++++++++
54 |
55 | - Added reverse test parameter (thanks to @cvicente)
56 | - Updated travis build to test against iperf3 versions 3.0.6 through 3.1.3
57 |
58 | 0.1.0 (2016-08-18)
59 | ++++++++++++++++++
60 |
61 | **main functionality available**
62 |
63 | - introduced TestResult class providing easy access into test results
64 | - updated client and server examples
65 | - minor documentation tweaks
66 |
67 | 0.0.1 (2016-08-18)
68 | ++++++++++++++++++
69 |
70 | **Initial Release**
71 |
72 | - Client and Server classes around iperf3's libiperf.so.0 API
73 | - Documentation on readthedocs
74 | - py.tests
75 | - blood
76 | - sweat
77 | - a lot of tears
78 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | from setuptools import setup
3 | from setuptools.command.test import test as TestCommand
4 | import os
5 | import sys
6 | from codecs import open
7 |
8 | from iperf3 import iperf3
9 |
10 | here = os.path.abspath(os.path.dirname(__file__))
11 |
12 |
13 | with open('README.rst', 'r', 'utf-8') as f:
14 | readme = f.read()
15 |
16 | with open('HISTORY.rst', 'r', 'utf-8') as f:
17 | history = f.read()
18 |
19 |
20 | class PyTest(TestCommand):
21 | def finalize_options(self):
22 | TestCommand.finalize_options(self)
23 | self.test_args = []
24 | self.test_suite = True
25 |
26 | def run_tests(self):
27 | import pytest
28 | errcode = pytest.main(self.test_args)
29 | sys.exit(errcode)
30 |
31 | setup(name='iperf3',
32 | version=iperf3.__version__,
33 | url='https://github.com/thiezn/iperf3-python',
34 | author='Mathijs Mortimer',
35 | tests_require=['pytest'],
36 | install_requires=[],
37 | cmdclass={'test': PyTest},
38 | description='Python wrapper around iperf3',
39 | keywords='iperf3 iperf',
40 | long_description=readme + '\n\n' + history,
41 | include_package_data=True,
42 | zip_safe=False,
43 | platforms='any',
44 | test_suite='iperf3.test.test_iperf3',
45 | classifiers=[
46 | 'Programming Language :: Python',
47 | 'Programming Language :: Python :: 2',
48 | 'Programming Language :: Python :: 2.6',
49 | 'Programming Language :: Python :: 2.7',
50 | 'Programming Language :: Python :: 3',
51 | 'Programming Language :: Python :: 3.3',
52 | 'Programming Language :: Python :: 3.4',
53 | 'Programming Language :: Python :: 3.5',
54 | 'Development Status :: 4 - Beta',
55 | 'Natural Language :: English',
56 | 'Intended Audience :: Developers',
57 | 'Intended Audience :: System Administrators',
58 | 'Intended Audience :: Telecommunications Industry',
59 | 'License :: OSI Approved :: MIT License',
60 | 'Operating System :: Unix',
61 | 'Operating System :: MacOS',
62 | 'Topic :: Software Development :: Libraries :: Python Modules',
63 | 'Topic :: System :: Networking',
64 | ],
65 | extras_require={'testing': ['iperf3']},
66 | author_email='mathijs@mortimer.nl',
67 | license='MIT',
68 | packages=['iperf3'])
69 |
--------------------------------------------------------------------------------
/docs/source/examples.rst:
--------------------------------------------------------------------------------
1 | .. _examples:
2 |
3 | Examples
4 | ========
5 |
6 | Check the examples/ folder for a few ready to go python scripts.
7 |
8 | Client
9 | ~~~~~~
10 |
11 | **Example 1**
12 |
13 | This example sets up a client connection to a running server on 10.10.10.10:6969.
14 | When the test finalises the results are returned. This example shows all currently
15 | available options for a :class:`Client `
16 |
17 | >>> import iperf3
18 |
19 | >>> client = iperf3.Client()
20 | >>> client.omit = 1
21 | >>> client.duration = 1
22 | >>> client.bind_address = '10.0.0.1'
23 | >>> client.server_hostname = '10.10.10.10'
24 | >>> client.port = 6969
25 | >>> client.blksize = 1234
26 | >>> client.num_streams = 10
27 | >>> client.zerocopy = True
28 | >>> client.verbose = False
29 | >>> client.reverse = True
30 | >>> client.run()
31 | {'start': {'test_start': {...
32 |
33 | **Example 2**
34 |
35 | This example shows how you can output the client test results to screen, just like
36 | the iperf3 application itself does. Note it does NOT return a :class:`TestResult `
37 | instance.
38 |
39 | >>> import iperf3
40 |
41 | >>> client = iperf3.Client()
42 | >>> client.server_hostname = '10.10.10.10'
43 | >>> client.port = 6969
44 | >>> client.json_output = False
45 | >>> result = client.run()
46 | Time: Mon, 15 May 2017 18:20:01 GMT
47 | Connecting to host 10.10.10.10, port 6969
48 | [ 8] local 127.0.0.1 port 35670 connected to 127.0.0.1 port 5201
49 | Starting Test: protocol: TCP, 1 streams, 131072 byte blocks, omitting 0 seconds, 1 second test
50 | [ ID] Interval Transfer Bandwidth Retr Cwnd
51 | [ 8] 0.00-1.00 sec 3.96 GBytes 34.0 Gbits/sec 0 3.18 MByt...
52 |
53 | >>> result
54 | None
55 |
56 | **Example 3**
57 |
58 | Here is an example of running a UDP test. Please read the official documentation on
59 | UDP testing as there can be a few catches.
60 |
61 | .. literalinclude:: ../../examples/udp_client.py
62 | :language: python
63 |
64 | Server
65 | ~~~~~~
66 |
67 | **Example 1**
68 |
69 | This example runs an iperf3 server on 10.10.10.10:6969 and prints out the test results.
70 | After each test ``server.run()`` finishes and produces the test results. This example
71 | shows all currently available options for a :class:`Server `
72 |
73 | >>> import iperf3
74 |
75 | >>> server = iperf3.Server()
76 | >>> server.bind_address = '10.10.10.10'
77 | >>> server.port = 6969
78 | >>> server.verbose = False
79 | >>> while True:
80 | ... server.run()
81 | ...
82 | {'start': {'test_start': {...
83 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | os: linux
3 | sudo: required
4 | dist: trusty
5 | python:
6 | - "2.7"
7 | - "3.6"
8 | env:
9 | - IPERF_VERSION="3.0.6"
10 | - IPERF_VERSION="3.0.7"
11 | - IPERF_VERSION="3.0.8"
12 | - IPERF_VERSION="3.0.9"
13 | - IPERF_VERSION="3.0.10"
14 | - IPERF_VERSION="3.0.11"
15 | - IPERF_VERSION="3.0.12"
16 | - IPERF_VERSION="3.1"
17 | - IPERF_VERSION="3.1.1"
18 | - IPERF_VERSION="3.1.2"
19 | - IPERF_VERSION="3.1.3"
20 | - IPERF_VERSION="3.1.4"
21 | - IPERF_VERSION="3.1.5"
22 | - IPERF_VERSION="3.1.6"
23 | - IPERF_VERSION="3.1.7"
24 | - IPERF_VERSION="3.2"
25 | - IPERF_VERSION="3.3"
26 | - IPERF_VERSION="3.4"
27 | - IPERF_VERSION="3.5"
28 | - IPERF_VERSION="3.6"
29 | before_install:
30 | # Download the various available iperf3 sources
31 | - if [[ "$IPERF_VERSION" == "3.0.6" ]]; then wget https://github.com/esnet/iperf/archive/3.0.6.tar.gz ;fi
32 | - if [[ "$IPERF_VERSION" == "3.0.7" ]]; then wget https://github.com/esnet/iperf/archive/3.0.7.tar.gz ;fi
33 | - if [[ "$IPERF_VERSION" == "3.0.8" ]]; then wget https://github.com/esnet/iperf/archive/3.0.8.tar.gz ;fi
34 | - if [[ "$IPERF_VERSION" == "3.0.9" ]]; then wget https://github.com/esnet/iperf/archive/3.0.9.tar.gz ;fi
35 | - if [[ "$IPERF_VERSION" == "3.0.10" ]]; then wget https://github.com/esnet/iperf/archive/3.0.10.tar.gz ;fi
36 | - if [[ "$IPERF_VERSION" == "3.0.11" ]]; then wget https://github.com/esnet/iperf/archive/3.0.11.tar.gz ;fi
37 | - if [[ "$IPERF_VERSION" == "3.0.12" ]]; then wget https://github.com/esnet/iperf/archive/3.0.12.tar.gz ;fi
38 | - if [[ "$IPERF_VERSION" == "3.1" ]]; then wget https://github.com/esnet/iperf/archive/3.1.tar.gz ;fi
39 | - if [[ "$IPERF_VERSION" == "3.1.1" ]]; then wget https://github.com/esnet/iperf/archive/3.1.1.tar.gz ;fi
40 | - if [[ "$IPERF_VERSION" == "3.1.2" ]]; then wget https://github.com/esnet/iperf/archive/3.1.2.tar.gz ;fi
41 | - if [[ "$IPERF_VERSION" == "3.1.3" ]]; then wget https://github.com/esnet/iperf/archive/3.1.3.tar.gz ;fi
42 | - if [[ "$IPERF_VERSION" == "3.1.4" ]]; then wget https://github.com/esnet/iperf/archive/3.1.4.tar.gz ;fi
43 | - if [[ "$IPERF_VERSION" == "3.1.5" ]]; then wget https://github.com/esnet/iperf/archive/3.1.5.tar.gz ;fi
44 | - if [[ "$IPERF_VERSION" == "3.1.6" ]]; then wget https://github.com/esnet/iperf/archive/3.1.6.tar.gz ;fi
45 | - if [[ "$IPERF_VERSION" == "3.1.7" ]]; then wget https://github.com/esnet/iperf/archive/3.1.7.tar.gz ;fi
46 | - if [[ "$IPERF_VERSION" == "3.2" ]]; then wget https://github.com/esnet/iperf/archive/3.2.tar.gz ;fi
47 | - if [[ "$IPERF_VERSION" == "3.3" ]]; then wget https://github.com/esnet/iperf/archive/3.3.tar.gz ;fi
48 | - if [[ "$IPERF_VERSION" == "3.4" ]]; then wget https://github.com/esnet/iperf/archive/3.4.tar.gz ;fi
49 | - if [[ "$IPERF_VERSION" == "3.5" ]]; then wget https://github.com/esnet/iperf/archive/3.5.tar.gz ;fi
50 | - if [[ "$IPERF_VERSION" == "3.6" ]]; then wget https://github.com/esnet/iperf/archive/3.6.tar.gz ;fi
51 |
52 | # Install iperf version selected above
53 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then tar xvf *.tar.gz ;fi
54 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cd iperf-*/ ;fi
55 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./bootstrap.sh ;fi
56 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./configure && make && sudo make install ;fi
57 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cd .. ;fi
58 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export LD_LIBRARY_PATH=/usr/local/lib ;fi
59 | install:
60 | - "pip install -r requirements.txt"
61 | - "pip install coverage"
62 | - "pip install coveralls"
63 | script:
64 | - "coverage run --source=iperf3 setup.py test"
65 | after_success:
66 | coveralls
67 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | iperf3-python: Python wrapper around iperf3
2 | ===========================================
3 |
4 | |PyPi Status| |Build Status| |Coverage Status| |Documentation Status|
5 |
6 | Detailed documentation at
7 | `iperf3-python.readthedocs.org `__
8 |
9 | iperf3 for python provides a wrapper around the excellent iperf3
10 | utility. iperf3 is a complete rewrite of the original iperf
11 | implementation. more information on the `official iperf3
12 | site `__
13 |
14 | iperf3 introduced an API called libiperf that allows you to easily
15 | interact with iperf3 from other languages. This library provides a
16 | python wrapper around libiperf for easy integration into your own python
17 | scripts in a pythonic way
18 |
19 | Installation
20 | ------------
21 |
22 | First you have to make sure the iperf3 utility is present on your system as the
23 | python module wraps around the libiperf API provided by it.
24 |
25 | The common linux distributions offer installations from their own repositories. These
26 | might be out of date so installation from the official `iperf3 website `__
27 | is preferred.
28 |
29 | **note** The libiperf API was only introduced in 3.0.6 so make sure you have an updated version
30 | of iperf3 installation.
31 |
32 | **note** The libiperf API added a feature to programmatically retrieve the json output from the library. This
33 | enables us to retrieve the results without having to scrape the output from stdout. Effectively this means
34 | that if you want to redirect your script's stdout/stderr to something else you need at least iperf3 version 3.1
35 |
36 | - Install from source (preferred)
37 |
38 | .. code:: bash
39 |
40 | wget http://downloads.es.net/pub/iperf/iperf-3-current.tar.gz
41 | tar xvf iperf-3-current.tar.gz
42 | cd iperf-3.3/ # Or whatever the latest version is
43 | ./configure && make && sudo make install
44 |
45 | - Ubuntu:
46 |
47 | .. code:: bash
48 |
49 | sudo apt-get install iperf3
50 |
51 | - CenOS/RedHat
52 |
53 | .. code:: bash
54 |
55 | sudo yum install iperf3
56 |
57 | Once the iperf3 utility is installed the simplest way to install the python wrapper is through
58 | `PyPi `__
59 |
60 | .. code:: bash
61 |
62 | pip install iperf3
63 |
64 | You can also install directly from the github repository:
65 |
66 | .. code:: bash
67 |
68 | git clone https://github.com/thiezn/iperf3-python.git
69 | cd iperf3-python
70 | python3 setup.py install
71 |
72 | Quickstart
73 | ----------
74 |
75 | For detailed examples check out the `examples page `__ or
76 | the detailed documentation at `iperf3-python.readthedocs.org `__
77 |
78 |
79 | **Server**
80 |
81 | .. code:: python
82 |
83 | >>> import iperf3
84 |
85 | >>> server = iperf3.Server()
86 | >>> result = server.run()
87 | >>> result.remote_host
88 | "10.10.10.10"
89 |
90 | **Client**
91 |
92 | .. code:: python
93 |
94 | >>> import iperf3
95 |
96 | >>> client = iperf3.Client()
97 | >>> client.duration = 1
98 | >>> client.server_hostname = '127.0.0.1'
99 | >>> client.port = 5201
100 | >>> result = client.run()
101 | >>> result.sent_Mbps
102 | 32583.293914794922
103 |
104 |
105 | External Dependencies
106 | ---------------------
107 |
108 | - iperf3
109 | - libiperf.so.0 (Comes with iperf3 >= 3.0.6)
110 |
111 | Testing
112 | -------
113 |
114 | - Tested against the following iperf3 versions on Linux:
115 |
116 | - 3.0.6
117 | - 3.0.7
118 | - 3.0.8
119 | - 3.0.9
120 | - 3.0.10
121 | - 3.0.11
122 | - 3.0.12
123 | - 3.1
124 | - 3.1.1
125 | - 3.1.2
126 | - 3.1.3
127 | - 3.1.4
128 | - 3.1.5
129 | - 3.1.6
130 | - 3.1.7
131 | - 3.2
132 | - 3.3
133 | - 3.4
134 | - 3.5
135 | - 3.6
136 |
137 | - Test coverage reporting through `coveralls.io `__
138 | - Tested against the following Python versions:
139 |
140 | - 2.7
141 | - 3.6
142 |
143 | .. |PyPi Status| image:: https://img.shields.io/pypi/v/iperf3.svg
144 | :target: https://pypi.python.org/pypi/iperf3
145 | .. |Build Status| image:: https://travis-ci.org/thiezn/iperf3-python.svg?branch=master
146 | :target: https://travis-ci.org/thiezn/iperf3-python
147 | .. |Coverage Status| image:: https://coveralls.io/repos/github/thiezn/iperf3-python/badge.svg?branch=master
148 | :target: https://coveralls.io/github/thiezn/iperf3-python?branch=master
149 | .. |Documentation Status| image:: https://readthedocs.org/projects/iperf3-python/badge/?version=latest
150 | :target: http://iperf3-python.readthedocs.io/en/latest/?badge=latest
151 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help
18 | help:
19 | @echo "Please use \`make ' where is one of"
20 | @echo " html to make standalone HTML files"
21 | @echo " dirhtml to make HTML files named index.html in directories"
22 | @echo " singlehtml to make a single large HTML file"
23 | @echo " pickle to make pickle files"
24 | @echo " json to make JSON files"
25 | @echo " htmlhelp to make HTML files and a HTML help project"
26 | @echo " qthelp to make HTML files and a qthelp project"
27 | @echo " applehelp to make an Apple Help Book"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " epub3 to make an epub3"
31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
32 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
34 | @echo " text to make text files"
35 | @echo " man to make manual pages"
36 | @echo " texinfo to make Texinfo files"
37 | @echo " info to make Texinfo files and run them through makeinfo"
38 | @echo " gettext to make PO message catalogs"
39 | @echo " changes to make an overview of all changed/added/deprecated items"
40 | @echo " xml to make Docutils-native XML files"
41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
42 | @echo " linkcheck to check all external links for integrity"
43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
44 | @echo " coverage to run coverage check of the documentation (if enabled)"
45 | @echo " dummy to check syntax errors of document sources"
46 |
47 | .PHONY: clean
48 | clean:
49 | rm -rf $(BUILDDIR)/*
50 |
51 | .PHONY: html
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | .PHONY: dirhtml
58 | dirhtml:
59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
60 | @echo
61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
62 |
63 | .PHONY: singlehtml
64 | singlehtml:
65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
66 | @echo
67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
68 |
69 | .PHONY: pickle
70 | pickle:
71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
72 | @echo
73 | @echo "Build finished; now you can process the pickle files."
74 |
75 | .PHONY: json
76 | json:
77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
78 | @echo
79 | @echo "Build finished; now you can process the JSON files."
80 |
81 | .PHONY: htmlhelp
82 | htmlhelp:
83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
84 | @echo
85 | @echo "Build finished; now you can run HTML Help Workshop with the" \
86 | ".hhp project file in $(BUILDDIR)/htmlhelp."
87 |
88 | .PHONY: qthelp
89 | qthelp:
90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
91 | @echo
92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/iperf3.qhcp"
95 | @echo "To view the help file:"
96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/iperf3.qhc"
97 |
98 | .PHONY: applehelp
99 | applehelp:
100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
101 | @echo
102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
103 | @echo "N.B. You won't be able to view it unless you put it in" \
104 | "~/Library/Documentation/Help or install it in your application" \
105 | "bundle."
106 |
107 | .PHONY: devhelp
108 | devhelp:
109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
110 | @echo
111 | @echo "Build finished."
112 | @echo "To view the help file:"
113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/iperf3"
114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/iperf3"
115 | @echo "# devhelp"
116 |
117 | .PHONY: epub
118 | epub:
119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
120 | @echo
121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
122 |
123 | .PHONY: epub3
124 | epub3:
125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
126 | @echo
127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
128 |
129 | .PHONY: latex
130 | latex:
131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
132 | @echo
133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
135 | "(use \`make latexpdf' here to do that automatically)."
136 |
137 | .PHONY: latexpdf
138 | latexpdf:
139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
140 | @echo "Running LaTeX files through pdflatex..."
141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
143 |
144 | .PHONY: latexpdfja
145 | latexpdfja:
146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
147 | @echo "Running LaTeX files through platex and dvipdfmx..."
148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
150 |
151 | .PHONY: text
152 | text:
153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
154 | @echo
155 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
156 |
157 | .PHONY: man
158 | man:
159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
160 | @echo
161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
162 |
163 | .PHONY: texinfo
164 | texinfo:
165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
166 | @echo
167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
168 | @echo "Run \`make' in that directory to run these through makeinfo" \
169 | "(use \`make info' here to do that automatically)."
170 |
171 | .PHONY: info
172 | info:
173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
174 | @echo "Running Texinfo files through makeinfo..."
175 | make -C $(BUILDDIR)/texinfo info
176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
177 |
178 | .PHONY: gettext
179 | gettext:
180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
181 | @echo
182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
183 |
184 | .PHONY: changes
185 | changes:
186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
187 | @echo
188 | @echo "The overview file is in $(BUILDDIR)/changes."
189 |
190 | .PHONY: linkcheck
191 | linkcheck:
192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
193 | @echo
194 | @echo "Link check complete; look for any errors in the above output " \
195 | "or in $(BUILDDIR)/linkcheck/output.txt."
196 |
197 | .PHONY: doctest
198 | doctest:
199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
200 | @echo "Testing of doctests in the sources finished, look at the " \
201 | "results in $(BUILDDIR)/doctest/output.txt."
202 |
203 | .PHONY: coverage
204 | coverage:
205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
206 | @echo "Testing of coverage in the sources finished, look at the " \
207 | "results in $(BUILDDIR)/coverage/python.txt."
208 |
209 | .PHONY: xml
210 | xml:
211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
212 | @echo
213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
214 |
215 | .PHONY: pseudoxml
216 | pseudoxml:
217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
218 | @echo
219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
220 |
221 | .PHONY: dummy
222 | dummy:
223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
224 | @echo
225 | @echo "Build finished. Dummy builder generates no files."
226 |
--------------------------------------------------------------------------------
/tests/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "start": {
3 | "connected": [{
4 | "socket": 4,
5 | "local_host": "192.168.0.3",
6 | "local_port": 60166,
7 | "remote_host": "192.168.0.188",
8 | "remote_port": 9987
9 | }],
10 | "version": "iperf 3.0.11",
11 | "system_info": "Linux rhowelllx 4.4.0-79-generic #100-Ubuntu SMP Wed May 17 19:58:14 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux\n",
12 | "timestamp": {
13 | "time": "Wed, 28 Jun 2017 15:31:28 GMT",
14 | "timesecs": 1498663888
15 | },
16 | "connecting_to": {
17 | "host": "192.168.0.188",
18 | "port": 9987
19 | },
20 | "cookie": "rhowelllx.1498663888.027091.78beffbd",
21 | "tcp_mss_default": 1448,
22 | "test_start": {
23 | "protocol": "TCP",
24 | "num_streams": 1,
25 | "blksize": 131072,
26 | "omit": 0,
27 | "duration": 13,
28 | "bytes": 0,
29 | "blocks": 0,
30 | "reverse": 0
31 | }
32 | },
33 | "intervals": [{
34 | "streams": [{
35 | "socket": 4,
36 | "start": 0,
37 | "end": 1.00007,
38 | "seconds": 1.00007,
39 | "bytes": 118766408,
40 | "bits_per_second": 9.50062e+08,
41 | "retransmits": 0,
42 | "snd_cwnd": 573408,
43 | "omitted": false
44 | }],
45 | "sum": {
46 | "start": 0,
47 | "end": 1.00007,
48 | "seconds": 1.00007,
49 | "bytes": 118766408,
50 | "bits_per_second": 9.50062e+08,
51 | "retransmits": 0,
52 | "omitted": false
53 | }
54 | }, {
55 | "streams": [{
56 | "socket": 4,
57 | "start": 1.00007,
58 | "end": 2.00006,
59 | "seconds": 0.999984,
60 | "bytes": 117548640,
61 | "bits_per_second": 9.40404e+08,
62 | "retransmits": 0,
63 | "snd_cwnd": 573408,
64 | "omitted": false
65 | }],
66 | "sum": {
67 | "start": 1.00007,
68 | "end": 2.00006,
69 | "seconds": 0.999984,
70 | "bytes": 117548640,
71 | "bits_per_second": 9.40404e+08,
72 | "retransmits": 0,
73 | "omitted": false
74 | }
75 | }, {
76 | "streams": [{
77 | "socket": 4,
78 | "start": 2.00006,
79 | "end": 3.00006,
80 | "seconds": 1,
81 | "bytes": 116375760,
82 | "bits_per_second": 9.31005e+08,
83 | "retransmits": 0,
84 | "snd_cwnd": 573408,
85 | "omitted": false
86 | }],
87 | "sum": {
88 | "start": 2.00006,
89 | "end": 3.00006,
90 | "seconds": 1,
91 | "bytes": 116375760,
92 | "bits_per_second": 9.31005e+08,
93 | "retransmits": 0,
94 | "omitted": false
95 | }
96 | }, {
97 | "streams": [{
98 | "socket": 4,
99 | "start": 3.00006,
100 | "end": 4.00006,
101 | "seconds": 1,
102 | "bytes": 116310600,
103 | "bits_per_second": 930484800,
104 | "retransmits": 0,
105 | "snd_cwnd": 573408,
106 | "omitted": false
107 | }],
108 | "sum": {
109 | "start": 3.00006,
110 | "end": 4.00006,
111 | "seconds": 1,
112 | "bytes": 116310600,
113 | "bits_per_second": 930484800,
114 | "retransmits": 0,
115 | "omitted": false
116 | }
117 | }, {
118 | "streams": [{
119 | "socket": 4,
120 | "start": 4.00006,
121 | "end": 5.00006,
122 | "seconds": 1,
123 | "bytes": 117678960,
124 | "bits_per_second": 941431680,
125 | "retransmits": 0,
126 | "snd_cwnd": 573408,
127 | "omitted": false
128 | }],
129 | "sum": {
130 | "start": 4.00006,
131 | "end": 5.00006,
132 | "seconds": 1,
133 | "bytes": 117678960,
134 | "bits_per_second": 941431680,
135 | "retransmits": 0,
136 | "omitted": false
137 | }
138 | }, {
139 | "streams": [{
140 | "socket": 4,
141 | "start": 5.00006,
142 | "end": 6.00006,
143 | "seconds": 1,
144 | "bytes": 116831880,
145 | "bits_per_second": 934655040,
146 | "retransmits": 0,
147 | "snd_cwnd": 640016,
148 | "omitted": false
149 | }],
150 | "sum": {
151 | "start": 5.00006,
152 | "end": 6.00006,
153 | "seconds": 1,
154 | "bytes": 116831880,
155 | "bits_per_second": 934655040,
156 | "retransmits": 0,
157 | "omitted": false
158 | }
159 | }, {
160 | "streams": [{
161 | "socket": 4,
162 | "start": 6.00006,
163 | "end": 7.00006,
164 | "seconds": 0.999999,
165 | "bytes": 117353160,
166 | "bits_per_second": 9.38826e+08,
167 | "retransmits": 0,
168 | "snd_cwnd": 640016,
169 | "omitted": false
170 | }],
171 | "sum": {
172 | "start": 6.00006,
173 | "end": 7.00006,
174 | "seconds": 0.999999,
175 | "bytes": 117353160,
176 | "bits_per_second": 9.38826e+08,
177 | "retransmits": 0,
178 | "omitted": false
179 | }
180 | }, {
181 | "streams": [{
182 | "socket": 4,
183 | "start": 7.00006,
184 | "end": 8.00006,
185 | "seconds": 1,
186 | "bytes": 115984800,
187 | "bits_per_second": 9.27877e+08,
188 | "retransmits": 0,
189 | "snd_cwnd": 640016,
190 | "omitted": false
191 | }],
192 | "sum": {
193 | "start": 7.00006,
194 | "end": 8.00006,
195 | "seconds": 1,
196 | "bytes": 115984800,
197 | "bits_per_second": 9.27877e+08,
198 | "retransmits": 0,
199 | "omitted": false
200 | }
201 | }, {
202 | "streams": [{
203 | "socket": 4,
204 | "start": 8.00006,
205 | "end": 9.00007,
206 | "seconds": 1.00001,
207 | "bytes": 117613800,
208 | "bits_per_second": 9.40903e+08,
209 | "retransmits": 0,
210 | "snd_cwnd": 640016,
211 | "omitted": false
212 | }],
213 | "sum": {
214 | "start": 8.00006,
215 | "end": 9.00007,
216 | "seconds": 1.00001,
217 | "bytes": 117613800,
218 | "bits_per_second": 9.40903e+08,
219 | "retransmits": 0,
220 | "omitted": false
221 | }
222 | }, {
223 | "streams": [{
224 | "socket": 4,
225 | "start": 9.00007,
226 | "end": 10.0001,
227 | "seconds": 1.00001,
228 | "bytes": 115984800,
229 | "bits_per_second": 9.27871e+08,
230 | "retransmits": 0,
231 | "snd_cwnd": 640016,
232 | "omitted": false
233 | }],
234 | "sum": {
235 | "start": 9.00007,
236 | "end": 10.0001,
237 | "seconds": 1.00001,
238 | "bytes": 115984800,
239 | "bits_per_second": 9.27871e+08,
240 | "retransmits": 0,
241 | "omitted": false
242 | }
243 | }, {
244 | "streams": [{
245 | "socket": 4,
246 | "start": 10.0001,
247 | "end": 11.0001,
248 | "seconds": 0.999983,
249 | "bytes": 117360680,
250 | "bits_per_second": 9.38902e+08,
251 | "retransmits": 0,
252 | "snd_cwnd": 640016,
253 | "omitted": false
254 | }],
255 | "sum": {
256 | "start": 10.0001,
257 | "end": 11.0001,
258 | "seconds": 0.999983,
259 | "bytes": 117360680,
260 | "bits_per_second": 9.38902e+08,
261 | "retransmits": 0,
262 | "omitted": false
263 | }
264 | }, {
265 | "streams": [{
266 | "socket": 4,
267 | "start": 11.0001,
268 | "end": 12.0001,
269 | "seconds": 1,
270 | "bytes": 116531240,
271 | "bits_per_second": 9.32249e+08,
272 | "retransmits": 0,
273 | "snd_cwnd": 941200,
274 | "omitted": false
275 | }],
276 | "sum": {
277 | "start": 11.0001,
278 | "end": 12.0001,
279 | "seconds": 1,
280 | "bytes": 116531240,
281 | "bits_per_second": 9.32249e+08,
282 | "retransmits": 0,
283 | "omitted": false
284 | }
285 | }, {
286 | "streams": [{
287 | "socket": 4,
288 | "start": 12.0001,
289 | "end": 13.0001,
290 | "seconds": 1.00001,
291 | "bytes": 116654080,
292 | "bits_per_second": 9.33228e+08,
293 | "retransmits": 0,
294 | "snd_cwnd": 941200,
295 | "omitted": false
296 | }],
297 | "sum": {
298 | "start": 12.0001,
299 | "end": 13.0001,
300 | "seconds": 1.00001,
301 | "bytes": 116654080,
302 | "bits_per_second": 9.33228e+08,
303 | "retransmits": 0,
304 | "omitted": false
305 | }
306 | }],
307 | "end": {
308 | "streams": [{
309 | "sender": {
310 | "socket": 4,
311 | "start": 0,
312 | "end": 13.0001,
313 | "seconds": 13.0001,
314 | "bytes": 1520994808,
315 | "bits_per_second": 9.35992e+08,
316 | "retransmits": 0
317 | },
318 | "receiver": {
319 | "socket": 4,
320 | "start": 0,
321 | "end": 13.0001,
322 | "seconds": 13.0001,
323 | "bytes": 1518193248,
324 | "bits_per_second": 9.34268e+08
325 | }
326 | }],
327 | "sum_sent": {
328 | "start": 0,
329 | "end": 13.0001,
330 | "seconds": 13.0001,
331 | "bytes": 1520994808,
332 | "bits_per_second": 9.35992e+08,
333 | "retransmits": 0
334 | },
335 | "sum_received": {
336 | "start": 0,
337 | "end": 13.0001,
338 | "seconds": 13.0001,
339 | "bytes": 1518193248,
340 | "bits_per_second": 9.34268e+08
341 | },
342 | "cpu_utilization_percent": {
343 | "host_total": 1.66812,
344 | "host_user": 0.0611844,
345 | "host_system": 1.59079,
346 | "remote_total": 4.15082,
347 | "remote_user": 0.156562,
348 | "remote_system": 3.99574
349 | }
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # iperf3 documentation build configuration file, created by
5 | # sphinx-quickstart on Tue Aug 16 09:33:31 2016.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #
20 | # import os
21 | # import sys
22 | # sys.path.insert(0, os.path.abspath('.'))
23 | import os
24 | import sys
25 | sys.path.insert(0, os.path.abspath('../../iperf3'))
26 |
27 | # -- General configuration ------------------------------------------------
28 |
29 | # If your documentation needs a minimal Sphinx version, state it here.
30 | #
31 | # needs_sphinx = '1.0'
32 |
33 | # Add any Sphinx extension module names here, as strings. They can be
34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
35 | # ones.
36 | extensions = [
37 | 'sphinx.ext.autodoc',
38 | 'sphinx.ext.todo',
39 | 'sphinx.ext.coverage',
40 | 'sphinx.ext.viewcode',
41 | ]
42 |
43 | # Add any paths that contain templates here, relative to this directory.
44 | templates_path = ['_templates']
45 |
46 | # The suffix(es) of source filenames.
47 | # You can specify multiple suffix as a list of string:
48 | #
49 | # source_suffix = ['.rst', '.md']
50 | source_suffix = '.rst'
51 |
52 | # The encoding of source files.
53 | #
54 | # source_encoding = 'utf-8-sig'
55 |
56 | # The master toctree document.
57 | master_doc = 'index'
58 |
59 | # General information about the project.
60 | project = 'iperf3'
61 | copyright = '2017, Mathijs Mortimer'
62 | author = 'Mathijs Mortimer'
63 |
64 | # The version info for the project you're documenting, acts as replacement for
65 | # |version| and |release|, also used in various other places throughout the
66 | # built documents.
67 | #
68 | # The short X.Y version.
69 | version = '0.1.11'
70 | # The full version, including alpha/beta/rc tags.
71 | release = '0.1.11'
72 |
73 | # The language for content autogenerated by Sphinx. Refer to documentation
74 | # for a list of supported languages.
75 | #
76 | # This is also used if you do content translation via gettext catalogs.
77 | # Usually you set "language" from the command line for these cases.
78 | language = None
79 |
80 | # There are two options for replacing |today|: either, you set today to some
81 | # non-false value, then it is used:
82 | #
83 | # today = ''
84 | #
85 | # Else, today_fmt is used as the format for a strftime call.
86 | #
87 | # today_fmt = '%B %d, %Y'
88 |
89 | # List of patterns, relative to source directory, that match files and
90 | # directories to ignore when looking for source files.
91 | # This patterns also effect to html_static_path and html_extra_path
92 | exclude_patterns = []
93 |
94 | # The reST default role (used for this markup: `text`) to use for all
95 | # documents.
96 | #
97 | # default_role = None
98 |
99 | # If true, '()' will be appended to :func: etc. cross-reference text.
100 | #
101 | add_function_parentheses = True
102 |
103 | # If true, the current module name will be prepended to all description
104 | # unit titles (such as .. function::).
105 | #
106 | # add_module_names = True
107 |
108 | # If true, sectionauthor and moduleauthor directives will be shown in the
109 | # output. They are ignored by default.
110 | #
111 | # show_authors = False
112 |
113 | # The name of the Pygments (syntax highlighting) style to use.
114 | pygments_style = 'sphinx'
115 |
116 | # A list of ignored prefixes for module index sorting.
117 | # modindex_common_prefix = []
118 |
119 | # If true, keep warnings as "system message" paragraphs in the built documents.
120 | # keep_warnings = False
121 |
122 | # If true, `todo` and `todoList` produce output, else they produce nothing.
123 | todo_include_todos = True
124 |
125 |
126 | # -- Options for HTML output ----------------------------------------------
127 |
128 | # The theme to use for HTML and HTML Help pages. See the documentation for
129 | # a list of builtin themes.
130 | #
131 | html_theme = 'alabaster'
132 |
133 | # Theme options are theme-specific and customize the look and feel of a theme
134 | # further. For a list of options available for each theme, see the
135 | # documentation.
136 | #
137 | # html_theme_options = {}
138 | html_theme_options = {
139 | 'show_powered_by': False,
140 | 'github_user': 'thiezn',
141 | 'github_repo': 'iperf3-python',
142 | 'github_banner': True,
143 | 'show_related': False
144 | }
145 |
146 | # Add any paths that contain custom themes here, relative to this directory.
147 | # html_theme_path = []
148 |
149 | # The name for this set of Sphinx documents.
150 | # " v documentation" by default.
151 | #
152 | # html_title = 'iperf3 v0.1'
153 |
154 | # A shorter title for the navigation bar. Default is the same as html_title.
155 | #
156 | # html_short_title = None
157 |
158 | # The name of an image file (relative to this directory) to place at the top
159 | # of the sidebar.
160 | #
161 | # html_logo = None
162 |
163 | # The name of an image file (relative to this directory) to use as a favicon of
164 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
165 | # pixels large.
166 | #
167 | # html_favicon = None
168 |
169 | # Add any paths that contain custom static files (such as style sheets) here,
170 | # relative to this directory. They are copied after the builtin static files,
171 | # so a file named "default.css" will overwrite the builtin "default.css".
172 | html_static_path = ['_static']
173 |
174 | # Add any extra paths that contain custom files (such as robots.txt or
175 | # .htaccess) here, relative to this directory. These files are copied
176 | # directly to the root of the documentation.
177 | #
178 | # html_extra_path = []
179 |
180 | # If not None, a 'Last updated on:' timestamp is inserted at every page
181 | # bottom, using the given strftime format.
182 | # The empty string is equivalent to '%b %d, %Y'.
183 | #
184 | # html_last_updated_fmt = None
185 |
186 | # If true, SmartyPants will be used to convert quotes and dashes to
187 | # typographically correct entities.
188 | #
189 | html_use_smartypants = True
190 |
191 | # Custom sidebar templates, maps document names to template names.
192 | #
193 | #html_sidebars = {}
194 | html_sidebars = {
195 | 'index': [
196 | 'sidebarintro.html',
197 | 'sourcelink.html',
198 | 'searchbox.html'
199 | ],
200 | '**': [
201 | 'sidebarlogo.html',
202 | 'localtoc.html',
203 | 'relations.html',
204 | 'sourcelink.html',
205 | 'searchbox.html'
206 | ]
207 | }
208 |
209 | # Additional templates that should be rendered to pages, maps page names to
210 | # template names.
211 | #
212 | # html_additional_pages = {}
213 |
214 | # If false, no module index is generated.
215 | #
216 | # html_domain_indices = True
217 |
218 | # If false, no index is generated.
219 | #
220 | # html_use_index = True
221 |
222 | # If true, the index is split into individual pages for each letter.
223 | #
224 | # html_split_index = False
225 |
226 | # If true, links to the reST sources are added to the pages.
227 | #
228 | html_show_sourcelink = False
229 |
230 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
231 | #
232 | html_show_sphinx = False
233 |
234 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
235 | #
236 | html_show_copyright = True
237 |
238 | # If true, an OpenSearch description file will be output, and all pages will
239 | # contain a tag referring to it. The value of this option must be the
240 | # base URL from which the finished HTML is served.
241 | #
242 | # html_use_opensearch = ''
243 |
244 | # This is the file name suffix for HTML files (e.g. ".xhtml").
245 | # html_file_suffix = None
246 |
247 | # Language to be used for generating the HTML full-text search index.
248 | # Sphinx supports the following languages:
249 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
250 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
251 | #
252 | # html_search_language = 'en'
253 |
254 | # A dictionary with options for the search language support, empty by default.
255 | # 'ja' uses this config value.
256 | # 'zh' user can custom change `jieba` dictionary path.
257 | #
258 | # html_search_options = {'type': 'default'}
259 |
260 | # The name of a javascript file (relative to the configuration directory) that
261 | # implements a search results scorer. If empty, the default will be used.
262 | #
263 | # html_search_scorer = 'scorer.js'
264 |
265 | # Output file base name for HTML help builder.
266 | htmlhelp_basename = 'iperf3doc'
267 |
268 | # -- Options for LaTeX output ---------------------------------------------
269 |
270 | latex_elements = {
271 | # The paper size ('letterpaper' or 'a4paper').
272 | #
273 | # 'papersize': 'letterpaper',
274 |
275 | # The font size ('10pt', '11pt' or '12pt').
276 | #
277 | # 'pointsize': '10pt',
278 |
279 | # Additional stuff for the LaTeX preamble.
280 | #
281 | # 'preamble': '',
282 |
283 | # Latex figure (float) alignment
284 | #
285 | # 'figure_align': 'htbp',
286 | }
287 |
288 | # Grouping the document tree into LaTeX files. List of tuples
289 | # (source start file, target name, title,
290 | # author, documentclass [howto, manual, or own class]).
291 | latex_documents = [
292 | (master_doc, 'iperf3.tex', 'iperf3 Documentation',
293 | 'Mathijs Mortimer', 'manual'),
294 | ]
295 |
296 | # The name of an image file (relative to this directory) to place at the top of
297 | # the title page.
298 | #
299 | # latex_logo = None
300 |
301 | # For "manual" documents, if this is true, then toplevel headings are parts,
302 | # not chapters.
303 | #
304 | # latex_use_parts = False
305 |
306 | # If true, show page references after internal links.
307 | #
308 | # latex_show_pagerefs = False
309 |
310 | # If true, show URL addresses after external links.
311 | #
312 | # latex_show_urls = False
313 |
314 | # Documents to append as an appendix to all manuals.
315 | #
316 | # latex_appendices = []
317 |
318 | # If false, no module index is generated.
319 | #
320 | # latex_domain_indices = True
321 |
322 |
323 | # -- Options for manual page output ---------------------------------------
324 |
325 | # One entry per manual page. List of tuples
326 | # (source start file, name, description, authors, manual section).
327 | man_pages = [
328 | (master_doc, 'iperf3', 'iperf3 Documentation',
329 | [author], 1)
330 | ]
331 |
332 | # If true, show URL addresses after external links.
333 | #
334 | # man_show_urls = False
335 |
336 |
337 | # -- Options for Texinfo output -------------------------------------------
338 |
339 | # Grouping the document tree into Texinfo files. List of tuples
340 | # (source start file, target name, title, author,
341 | # dir menu entry, description, category)
342 | texinfo_documents = [
343 | (master_doc, 'iperf3', 'iperf3 Documentation',
344 | author, 'iperf3', 'One line description of project.',
345 | 'Miscellaneous'),
346 | ]
347 |
348 | # Documents to append as an appendix to all manuals.
349 | #
350 | # texinfo_appendices = []
351 |
352 | # If false, no module index is generated.
353 | #
354 | # texinfo_domain_indices = True
355 |
356 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
357 | #
358 | # texinfo_show_urls = 'footnote'
359 |
360 | # If true, do not generate a @detailmenu in the "Top" node's menu.
361 | #
362 | # texinfo_no_detailmenu = False
363 |
--------------------------------------------------------------------------------
/tests/test_iperf3.py:
--------------------------------------------------------------------------------
1 | import os
2 | import iperf3
3 | import pytest
4 | import subprocess
5 | from time import sleep
6 |
7 | def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
8 | return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
9 |
10 | class TestPyPerf:
11 |
12 | def test_init_client(self):
13 | client = iperf3.Client()
14 | assert client._test
15 |
16 | def test_init_server(self):
17 | server = iperf3.Server()
18 | assert server._test
19 |
20 | def test_lib_name(self):
21 | client = iperf3.Client(lib_name='libiperf.so.0')
22 | assert client._test
23 |
24 | def test_run_not_implemented(self):
25 | with pytest.raises(NotImplementedError):
26 | client = iperf3.IPerf3(role='c')
27 | client.run()
28 |
29 | def test_incorrect_role(self):
30 | with pytest.raises(ValueError):
31 | iperf3.IPerf3(role='bla')
32 |
33 | def test_role_property(self):
34 | client = iperf3.Client()
35 | assert client.role == 'c'
36 |
37 | def test_protocol_property(self):
38 | client = iperf3.Client()
39 | assert client.protocol == 'tcp'
40 |
41 | client.protocol = 'udp'
42 | assert client.protocol == 'udp'
43 |
44 | client.protocol = 'tcp'
45 | assert client.protocol == 'tcp'
46 |
47 | def test_udp_blksize_limit(self):
48 | """Ensure the blksize can't exceed MAX_UDP_BULKSIZE"""
49 | MAX_UDP_BULKSIZE = (65535 - 8 - 20)
50 | client = iperf3.Client()
51 | client.protocol = 'udp'
52 |
53 | client.blksize = MAX_UDP_BULKSIZE + 10
54 | assert client.blksize == MAX_UDP_BULKSIZE
55 |
56 | def test_bind_address_empty(self):
57 | """Test if we bind to any/all address when empty bind_address is
58 | passed"""
59 | client = iperf3.Client()
60 | client.bind_address = ''
61 | assert client.bind_address == '*'
62 |
63 | def test_bind_address(self):
64 | """Test setting of the bind address is properly passed
65 | on to the iperf3 API"""
66 | client = iperf3.Client()
67 | client.bind_address = '127.0.0.1'
68 | assert client.bind_address == '127.0.0.1'
69 |
70 | def test_port(self):
71 | server = iperf3.Server()
72 | server.port = 666
73 | assert server.port == 666
74 |
75 | def test_server_hostname_empty(self):
76 | client = iperf3.Client()
77 | client.server_hostname = ''
78 | assert client.server_hostname == None
79 |
80 | def test_server_hostname(self):
81 | client = iperf3.Server()
82 | client.server_hostname = '127.0.0.1'
83 | assert client.server_hostname == '127.0.0.1'
84 |
85 | def test_omit(self):
86 | client = iperf3.Client()
87 | client.omit = 666
88 | assert client.omit == 666
89 |
90 | def test_duration(self):
91 | client = iperf3.Client()
92 | client.duration = 666
93 | assert client.duration == 666
94 |
95 | def test_iperf3_version(self):
96 | client = iperf3.Client()
97 | assert 'iperf' in client.iperf_version
98 |
99 | def test_blksize(self):
100 | client = iperf3.Client()
101 | client.blksize = 666
102 | assert client.blksize == 666
103 |
104 | def test_bandwidth(self):
105 | client = iperf3.Client()
106 | client.bandwidth = 1
107 | assert client.bandwidth == 1
108 |
109 | def test_num_streams(self):
110 | client = iperf3.Client()
111 | client.num_streams = 666
112 | assert client.num_streams == 666
113 |
114 | def test_json_output_enabled(self):
115 | client = iperf3.Client()
116 | client.json_output = True
117 | assert client.json_output
118 |
119 | def test_json_output_disabled(self):
120 | client = iperf3.Client()
121 | client.json_output = False
122 | assert not client.json_output
123 |
124 | def test_verbose_enabled(self):
125 | client = iperf3.Client()
126 | client.verbose = True
127 | assert client.verbose
128 |
129 | def test_verbose_disabled(self):
130 | client = iperf3.Client()
131 | client.verbose = False
132 | assert not client.verbose
133 |
134 | def test_zerocopy_enabled(self):
135 | client = iperf3.Client()
136 | client.zerocopy = True
137 | assert client.zerocopy
138 |
139 | def test_zerocopy_disabled(self):
140 | client = iperf3.Client()
141 | client.zerocopy = False
142 | assert not client.zerocopy
143 |
144 | def test_reverse_enabled(self):
145 | client = iperf3.Client()
146 | client.reverse = True
147 | assert client.reverse
148 |
149 | def test_reverse_disabled(self):
150 | client = iperf3.Client()
151 | client.reverse = False
152 | assert not client.reverse
153 |
154 | def test_get_last_error(self):
155 | client = iperf3.Client()
156 | print(client._error_to_string(client._errno))
157 | assert client._errno == 111
158 |
159 | def test_error_to_string(self):
160 | client = iperf3.Client()
161 | assert client._error_to_string(1) == 'cannot be both server and client'
162 |
163 | def test_client_failed_run(self):
164 | client = iperf3.Client()
165 | client.server_hostname = '127.0.0.1'
166 | client.port = 5201
167 | client.duration = 1
168 | response = client.run()
169 | assert "unable to connect to server" in response.error
170 |
171 | def test_client_succesful_run(self):
172 | client = iperf3.Client()
173 | client.server_hostname = '127.0.0.1'
174 | client.port = 5202
175 | client.duration = 1
176 |
177 | server = subprocess.Popen(["iperf3", "-s", "-p", "5202"])
178 | sleep(.3) # give the server some time to start
179 | response = client.run()
180 | server.kill()
181 |
182 | assert response.remote_host == '127.0.0.1'
183 | assert response.remote_port == 5202
184 |
185 | # These are added to check some of the TestResult variables
186 | assert not response.reverse
187 | assert response.type == 'client'
188 | assert response.__repr__()
189 |
190 | def test_client_succesful_run_reverse(self):
191 | client = iperf3.Client()
192 | client.server_hostname = '127.0.0.1'
193 | client.port = 5203
194 | client.duration = 1
195 | client.reverse = True
196 |
197 | server = subprocess.Popen(["iperf3", "-s", "-p", "5203"])
198 | sleep(.3) # give the server some time to start
199 | response = client.run()
200 | server.kill()
201 |
202 | assert response.remote_host == '127.0.0.1'
203 | assert response.remote_port == 5203
204 |
205 | # These are added to check some of the TestResult variables
206 | assert response.reverse
207 | assert response.type == 'client'
208 | assert response.__repr__()
209 |
210 | def test_client_succesful_run_udp(self):
211 | client = iperf3.Client()
212 | client.protocol = 'udp'
213 | client.server_hostname = '127.0.0.1'
214 | client.port = 5204
215 | client.duration = 1
216 |
217 | server = subprocess.Popen(["iperf3", "-s", "-p", "5204"])
218 | sleep(.3) # give the server some time to start
219 | response = client.run()
220 | server.kill()
221 |
222 | assert response.remote_host == '127.0.0.1'
223 | assert response.remote_port == 5204
224 |
225 | # These are added to check some of the TestResult variables
226 | assert not response.reverse
227 | assert response.type == 'client'
228 | assert response.__repr__()
229 |
230 | def test_server_failed_run(self):
231 | """This test will launch two server instances on the same ip:port
232 | to generate an error"""
233 | server = iperf3.Server()
234 | server.bind_address = '127.0.0.1'
235 | server.port = 5201
236 |
237 | server2 = subprocess.Popen(["iperf3", "-s"])
238 | sleep(.3) # give the server some time to start
239 |
240 | response = server.run()
241 | server2.kill()
242 |
243 | assert "unable to start listener for connections: " in response.error
244 |
245 | def test_server_run(self):
246 | server = iperf3.Server()
247 | server.bind_address = '127.0.0.1'
248 | server.port = 5205
249 |
250 | # Launching the client with a sleep timer to give our server some time to start
251 | client = subprocess.Popen(
252 | 'sleep .5 && iperf3 -c 127.0.0.1 -p 5205 -t 1',
253 | shell=True,
254 | stdout=subprocess.PIPE,
255 | stderr=subprocess.PIPE
256 | )
257 | response = server.run()
258 |
259 | if server.iperf_version.startswith('iperf 3.0') or server.iperf_version.startswith('iperf 3.1'):
260 | assert not response.error
261 | assert response.local_host == '127.0.0.1'
262 | assert response.local_port == 5205
263 | assert response.type == 'server'
264 | else:
265 | assert response.error == 'the client has unexpectedly closed the connection'
266 |
267 | def test_server_run_output_to_screen(self):
268 | server = iperf3.Server()
269 | server.bind_address = '127.0.0.1'
270 | server.port = 5206
271 | server.json_output = False
272 |
273 | # Launching the client with a sleep timer to give our server some time to start
274 | client = subprocess.Popen(
275 | 'sleep .5 && iperf3 -c 127.0.0.1 -p 5206 -t 1',
276 | shell=True,
277 | stdout=subprocess.PIPE,
278 | stderr=subprocess.PIPE
279 | )
280 | response = server.run()
281 | client.kill()
282 |
283 | assert not response
284 |
285 | def test_client_succesful_run_output_to_screen(self):
286 | """Test if we print iperf3 test output to screen when json_output = False."""
287 | client = iperf3.Client()
288 | client.server_hostname = '127.0.0.1'
289 | client.port = 5207
290 | client.duration = 1
291 | client.json_output = False
292 |
293 | server = subprocess.Popen(["iperf3", "-s", "-p", "5207"])
294 | sleep(.3) # give the server some time to start
295 | response = client.run()
296 | server.kill()
297 |
298 | assert response == None
299 |
300 | def test_client_succesful_run_udp_output_to_screen(self):
301 | """Test if we print iperf3 test output to screen when json_output = False."""
302 | client = iperf3.Client()
303 | client.protocol = 'udp'
304 | client.server_hostname = '127.0.0.1'
305 | client.port = 5208
306 | client.duration = 1
307 | client.json_output = False
308 |
309 | server = subprocess.Popen(["iperf3", "-s", "-p", "5208"])
310 | sleep(.3) # give the server some time to start
311 | response = client.run()
312 | server.kill()
313 |
314 | assert response == None
315 |
316 | def test_result(self):
317 | dirname = os.path.dirname(os.path.abspath(__file__))
318 | with open(os.path.join(dirname, 'results.json')) as f:
319 | json = f.read()
320 |
321 | result = iperf3.TestResult(json)
322 | assert result.sent_bps == 935992000
323 | assert result.sent_kbps == 935992
324 | assert result.sent_Mbps == 935.992
325 | assert isclose(result.sent_kB_s, 114256.836, rel_tol=0.01)
326 | assert isclose(result.sent_MB_s, 111.579, rel_tol=0.01)
327 |
328 | assert result.received_bps == 934268000
329 | assert result.received_kbps == 934268
330 | assert result.received_Mbps == 934.268
331 |
332 | assert isclose(result.received_kB_s, 114046.387, rel_tol=0.01)
333 | assert isclose(result.received_MB_s, 111.373, rel_tol=0.01)
334 |
335 |
--------------------------------------------------------------------------------
/iperf3/iperf3.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Python wrapper for the iperf3 libiperf.so.0 library. The module consists of two
4 | classes, :class:`Client` and :class:`Server`, that inherit from the base class
5 | :class:`IPerf3`. They provide a nice (if i say so myself) and pythonic way to
6 | interact with the iperf3 utility.
7 |
8 | At the moment the module redirects stdout and stderr to a pipe and returns the
9 | received data back after each ``client.run()`` or ``server.run()`` call. In
10 | later releases there will be an option to toggle this on or off.
11 |
12 | A user should never have to utilise the :class:`IPerf3` class directly, this
13 | class provides common settings for the :class:`Client` and :class:`Server`
14 | classes.
15 |
16 | To get started quickly see the :ref:`examples` page.
17 |
18 | .. moduleauthor:: Mathijs Mortimer
19 | """
20 |
21 | from ctypes import util, cdll, c_char_p, c_int, c_char, c_void_p, c_uint64
22 | import os
23 | import select
24 | import json
25 | import threading
26 | from socket import SOCK_DGRAM, SOCK_STREAM
27 |
28 | try:
29 | from queue import Queue
30 | except ImportError:
31 | from Queue import Queue # Python2 compatibility
32 |
33 |
34 | __version__ = '0.1.11'
35 |
36 |
37 | MAX_UDP_BULKSIZE = (65535 - 8 - 20)
38 |
39 |
40 | def more_data(pipe_out):
41 | """Check if there is more data left on the pipe
42 |
43 | :param pipe_out: The os pipe_out
44 | :rtype: bool
45 | """
46 | r, _, _ = select.select([pipe_out], [], [], 0)
47 | return bool(r)
48 |
49 |
50 | def read_pipe(pipe_out):
51 | """Read data on a pipe
52 |
53 | Used to capture stdout data produced by libiperf
54 |
55 | :param pipe_out: The os pipe_out
56 | :rtype: unicode string
57 | """
58 | out = b''
59 | while more_data(pipe_out):
60 | out += os.read(pipe_out, 1024)
61 |
62 | return out.decode('utf-8')
63 |
64 |
65 | def output_to_pipe(pipe_in):
66 | """Redirects stdout and stderr to a pipe
67 |
68 | :param pipe_out: The pipe to redirect stdout and stderr to
69 | """
70 | os.dup2(pipe_in, 1) # stdout
71 | # os.dup2(pipe_in, 2) # stderr
72 |
73 |
74 | def output_to_screen(stdout_fd, stderr_fd):
75 | """Redirects stdout and stderr to a pipe
76 |
77 | :param stdout_fd: The stdout file descriptor
78 | :param stderr_fd: The stderr file descriptor
79 | """
80 | os.dup2(stdout_fd, 1)
81 | # os.dup2(stderr_fd, 2)
82 |
83 |
84 | class IPerf3(object):
85 | """The base class used by both the iperf3 :class:`Server` and :class:`Client`
86 |
87 | .. note:: You should not use this class directly
88 | """
89 | def __init__(self,
90 | role,
91 | verbose=True,
92 | lib_name=None):
93 | """Initialise the iperf shared library
94 |
95 | :param role: 'c' = client; 's' = server
96 | :param verbose: enable verbose output
97 | :param lib_name: optional name and path for libiperf.so.0 library
98 | """
99 | if lib_name is None:
100 | lib_name = util.find_library('libiperf')
101 | if lib_name is None:
102 | # If we still couldn't find it lets try the manual approach
103 | lib_name = 'libiperf.so.0'
104 |
105 | try:
106 | self.lib = cdll.LoadLibrary(lib_name)
107 | except OSError:
108 | raise OSError(
109 | "Couldn't find shared library {}, is iperf3 installed?".format(
110 | lib_name
111 | )
112 | )
113 |
114 | # Set the appropriate C types.
115 | self.lib.iperf_client_end.restype = c_int
116 | self.lib.iperf_client_end.argtypes = (c_void_p,)
117 | self.lib.iperf_free_test.restxpe = None
118 | self.lib.iperf_free_test.argtypes = (c_void_p,)
119 | self.lib.iperf_new_test.restype = c_void_p
120 | self.lib.iperf_new_test.argtypes = None
121 | self.lib.iperf_defaults.restype = c_int
122 | self.lib.iperf_defaults.argtypes = (c_void_p,)
123 | self.lib.iperf_get_test_role.restype = c_char
124 | self.lib.iperf_get_test_role.argtypes = (c_void_p,)
125 | self.lib.iperf_set_test_role.restype = None
126 | self.lib.iperf_set_test_role.argtypes = (c_void_p, c_char,)
127 | self.lib.iperf_get_test_bind_address.restype = c_char_p
128 | self.lib.iperf_get_test_bind_address.argtypes = (c_void_p,)
129 | self.lib.iperf_set_test_bind_address.restype = None
130 | self.lib.iperf_set_test_bind_address.argtypes = (c_void_p, c_char_p,)
131 | self.lib.iperf_get_test_server_port.restype = c_int
132 | self.lib.iperf_get_test_server_port.argtypes = (c_void_p,)
133 | self.lib.iperf_set_test_server_port.restype = None
134 | self.lib.iperf_set_test_server_port.argtypes = (c_void_p, c_int,)
135 | self.lib.iperf_get_test_json_output.restype = c_int
136 | self.lib.iperf_get_test_json_output.argtypes = (c_void_p,)
137 | self.lib.iperf_set_test_json_output.restype = None
138 | self.lib.iperf_set_test_json_output.argtypes = (c_void_p, c_int,)
139 | self.lib.iperf_get_verbose.restype = c_int
140 | self.lib.iperf_get_verbose.argtypes = (c_void_p,)
141 | self.lib.iperf_set_verbose.restype = None
142 | self.lib.iperf_set_verbose.argtypes = (c_void_p, c_int)
143 | self.lib.iperf_strerror.restype = c_char_p
144 | self.lib.iperf_strerror.argtypes = (c_int,)
145 | self.lib.iperf_get_test_server_hostname.restype = c_char_p
146 | self.lib.iperf_get_test_server_hostname.argtypes = (c_void_p,)
147 | self.lib.iperf_set_test_server_hostname.restype = None
148 | self.lib.iperf_set_test_server_hostname.argtypes = (
149 | c_void_p, c_char_p,
150 | )
151 | self.lib.iperf_get_test_protocol_id.restype = c_int
152 | self.lib.iperf_get_test_protocol_id.argtypes = (c_void_p,)
153 | self.lib.set_protocol.restype = c_int
154 | self.lib.set_protocol.argtypes = (c_void_p, c_int,)
155 | self.lib.iperf_get_test_omit.restype = c_int
156 | self.lib.iperf_get_test_omit.argtypes = (c_void_p,)
157 | self.lib.iperf_set_test_omit.restype = None
158 | self.lib.iperf_set_test_omit.argtypes = (c_void_p, c_int,)
159 | self.lib.iperf_get_test_duration.restype = c_int
160 | self.lib.iperf_get_test_duration.argtypes = (c_void_p,)
161 | self.lib.iperf_set_test_duration.restype = None
162 | self.lib.iperf_set_test_duration.argtypes = (c_void_p, c_int,)
163 | self.lib.iperf_get_test_rate.restype = c_uint64
164 | self.lib.iperf_get_test_rate.argtypes = (c_void_p,)
165 | self.lib.iperf_set_test_rate.restype = None
166 | self.lib.iperf_set_test_rate.argtypes = (c_void_p, c_uint64,)
167 | self.lib.iperf_get_test_blksize.restype = c_int
168 | self.lib.iperf_get_test_blksize.argtypes = (c_void_p,)
169 | self.lib.iperf_set_test_blksize.restype = None
170 | self.lib.iperf_set_test_blksize.argtypes = (c_void_p, c_int,)
171 | self.lib.iperf_get_test_num_streams.restype = c_int
172 | self.lib.iperf_get_test_num_streams.argtypes = (c_void_p,)
173 | self.lib.iperf_set_test_num_streams.restype = None
174 | self.lib.iperf_set_test_num_streams.argtypes = (c_void_p, c_int,)
175 | self.lib.iperf_has_zerocopy.restype = c_int
176 | self.lib.iperf_has_zerocopy.argtypes = None
177 | self.lib.iperf_set_test_zerocopy.restype = None
178 | self.lib.iperf_set_test_zerocopy.argtypes = (c_void_p, c_int,)
179 | self.lib.iperf_get_test_reverse.restype = c_int
180 | self.lib.iperf_get_test_reverse.argtypes = (c_void_p,)
181 | self.lib.iperf_set_test_reverse.restype = None
182 | self.lib.iperf_set_test_reverse.argtypes = (c_void_p, c_int,)
183 | self.lib.iperf_run_client.restype = c_int
184 | self.lib.iperf_run_client.argtypes = (c_void_p,)
185 | self.lib.iperf_run_server.restype = c_int
186 | self.lib.iperf_run_server.argtypes = (c_void_p,)
187 | self.lib.iperf_reset_test.restype = None
188 | self.lib.iperf_reset_test.argtypes = (c_void_p,)
189 |
190 | try:
191 | # Only available from iperf v3.1 and onwards
192 | self.lib.iperf_get_test_json_output_string.restype = c_char_p
193 | self.lib.iperf_get_test_json_output_string.argtypes = (c_void_p,)
194 | except AttributeError:
195 | pass
196 |
197 | # The test C struct iperf_test
198 | self._test = self._new()
199 | self.defaults()
200 |
201 | # stdout/strerr redirection variables
202 | self._stdout_fd = os.dup(1)
203 | self._stderr_fd = os.dup(2)
204 | self._pipe_out, self._pipe_in = os.pipe() # no need for pipe write
205 |
206 | # Generic test settings
207 | self.role = role
208 | self.json_output = True
209 | self.verbose = verbose
210 |
211 | def __del__(self):
212 | """Cleanup the test after the :class:`IPerf3` class is terminated"""
213 | os.close(self._stdout_fd)
214 | os.close(self._stderr_fd)
215 | os.close(self._pipe_out)
216 | os.close(self._pipe_in)
217 |
218 | try:
219 | # In the current version of libiperf, the control socket isn't
220 | # closed on iperf_client_end(), see proposed pull request:
221 | # https://github.com/esnet/iperf/pull/597
222 | # Workaround for testing, don't ever do this..:
223 | #
224 | # sck=self.lib.iperf_get_control_socket(self._test)
225 | # os.close(sck)
226 |
227 | self.lib.iperf_client_end(self._test)
228 | self.lib.iperf_free_test(self._test)
229 | except AttributeError:
230 | # self.lib doesn't exist, likely because iperf3 wasn't installed or
231 | # the shared library libiperf.so.0 could not be found
232 | pass
233 |
234 | def _new(self):
235 | """Initialise a new iperf test
236 |
237 | struct iperf_test *iperf_new_test()
238 | """
239 | return self.lib.iperf_new_test()
240 |
241 | def defaults(self):
242 | """Set/reset iperf test defaults."""
243 | self.lib.iperf_defaults(self._test)
244 |
245 | @property
246 | def role(self):
247 | """The iperf3 instance role
248 |
249 | valid roles are 'c'=client and 's'=server
250 |
251 | :rtype: 'c' or 's'
252 | """
253 | try:
254 | self._role = c_char(
255 | self.lib.iperf_get_test_role(self._test)
256 | ).value.decode('utf-8')
257 | except TypeError:
258 | self._role = c_char(
259 | chr(self.lib.iperf_get_test_role(self._test))
260 | ).value.decode('utf-8')
261 | return self._role
262 |
263 | @role.setter
264 | def role(self, role):
265 | if role.lower() in ['c', 's']:
266 | self.lib.iperf_set_test_role(
267 | self._test,
268 | c_char(role.lower().encode('utf-8'))
269 | )
270 | self._role = role
271 | else:
272 | raise ValueError("Unknown role, accepted values are 'c' and 's'")
273 |
274 | @property
275 | def bind_address(self):
276 | """The bind address the iperf3 instance will listen on
277 |
278 | use * to listen on all available IPs
279 | :rtype: string
280 | """
281 | result = c_char_p(
282 | self.lib.iperf_get_test_bind_address(self._test)
283 | ).value
284 | if result:
285 | self._bind_address = result.decode('utf-8')
286 | else:
287 | self._bind_address = '*'
288 |
289 | return self._bind_address
290 |
291 | @bind_address.setter
292 | def bind_address(self, address):
293 | self.lib.iperf_set_test_bind_address(
294 | self._test,
295 | c_char_p(address.encode('utf-8'))
296 | )
297 | self._bind_address = address
298 |
299 | @property
300 | def port(self):
301 | """The port the iperf3 server is listening on"""
302 | self._port = self.lib.iperf_get_test_server_port(self._test)
303 | return self._port
304 |
305 | @port.setter
306 | def port(self, port):
307 | self.lib.iperf_set_test_server_port(self._test, int(port))
308 | self._port = port
309 |
310 | @property
311 | def json_output(self):
312 | """Toggles json output of libiperf
313 |
314 | Turning this off will output the iperf3 instance results to
315 | stdout/stderr
316 |
317 | :rtype: bool
318 | """
319 | enabled = self.lib.iperf_get_test_json_output(self._test)
320 |
321 | if enabled:
322 | self._json_output = True
323 | else:
324 | self._json_output = False
325 |
326 | return self._json_output
327 |
328 | @json_output.setter
329 | def json_output(self, enabled):
330 | if enabled:
331 | self.lib.iperf_set_test_json_output(self._test, 1)
332 | else:
333 | self.lib.iperf_set_test_json_output(self._test, 0)
334 |
335 | self._json_output = enabled
336 |
337 | @property
338 | def verbose(self):
339 | """Toggles verbose output for the iperf3 instance
340 |
341 | :rtype: bool
342 | """
343 | enabled = self.lib.iperf_get_verbose(self._test)
344 |
345 | if enabled:
346 | self._verbose = True
347 | else:
348 | self._verbose = False
349 |
350 | return self._verbose
351 |
352 | @verbose.setter
353 | def verbose(self, enabled):
354 | if enabled:
355 | self.lib.iperf_set_verbose(self._test, 1)
356 | else:
357 | self.lib.iperf_set_verbose(self._test, 0)
358 | self._verbose = enabled
359 |
360 | @property
361 | def _errno(self):
362 | """Returns the last error ID
363 |
364 | :rtype: int
365 | """
366 | return c_int.in_dll(self.lib, "i_errno").value
367 |
368 | @property
369 | def iperf_version(self):
370 | """Returns the version of the libiperf library
371 |
372 | :rtype: string
373 | """
374 | # TODO: Is there a better way to get the const char than allocating 30?
375 | VersionType = c_char * 30
376 | return VersionType.in_dll(self.lib, "version").value.decode('utf-8')
377 |
378 | def _error_to_string(self, error_id):
379 | """Returns an error string from libiperf
380 |
381 | :param error_id: The error_id produced by libiperf
382 | :rtype: string
383 | """
384 | strerror = self.lib.iperf_strerror
385 | strerror.restype = c_char_p
386 | return strerror(error_id).decode('utf-8')
387 |
388 | def run(self):
389 | """Runs the iperf3 instance.
390 |
391 | This function has to be instantiated by the Client and Server
392 | instances
393 |
394 | :rtype: NotImplementedError
395 | """
396 | raise NotImplementedError
397 |
398 |
399 | class Client(IPerf3):
400 | """An iperf3 client connection.
401 |
402 | This opens up a connection to a running iperf3 server
403 |
404 | Basic Usage::
405 |
406 | >>> import iperf3
407 |
408 | >>> client = iperf3.Client()
409 | >>> client.duration = 1
410 | >>> client.server_hostname = '127.0.0.1'
411 | >>> client.port = 5201
412 | >>> client.run()
413 | {'intervals': [{'sum': {...
414 | """
415 |
416 | def __init__(self, *args, **kwargs):
417 | """Initialise the iperf shared library"""
418 | super(Client, self).__init__(role='c', *args, **kwargs)
419 |
420 | # Internal variables
421 | self._blksize = None
422 | self._server_hostname = None
423 | self._port = None
424 | self._num_streams = None
425 | self._zerocopy = False
426 | self._omit = None
427 | self._duration = None
428 | self._bandwidth = None
429 | self._protocol = None
430 |
431 | @property
432 | def server_hostname(self):
433 | """The server hostname to connect to.
434 |
435 | Accepts DNS entries or IP addresses.
436 |
437 | :rtype: string
438 | """
439 | result = c_char_p(
440 | self.lib.iperf_get_test_server_hostname(self._test)
441 | ).value
442 | if result:
443 | self._server_hostname = result.decode('utf-8')
444 | else:
445 | self._server_hostname = None
446 | return self._server_hostname
447 |
448 | @server_hostname.setter
449 | def server_hostname(self, hostname):
450 | self.lib.iperf_set_test_server_hostname(
451 | self._test,
452 | c_char_p(hostname.encode('utf-8'))
453 | )
454 | self._server_hostname = hostname
455 |
456 | @property
457 | def protocol(self):
458 | """The iperf3 instance protocol
459 |
460 | valid protocols are 'tcp' and 'udp'
461 |
462 | :rtype: str
463 | """
464 | proto_id = self.lib.iperf_get_test_protocol_id(self._test)
465 |
466 | if proto_id == SOCK_STREAM:
467 | self._protocol = 'tcp'
468 | elif proto_id == SOCK_DGRAM:
469 | self._protocol = 'udp'
470 |
471 | return self._protocol
472 |
473 | @protocol.setter
474 | def protocol(self, protocol):
475 | if protocol == 'tcp':
476 | self.lib.set_protocol(self._test, int(SOCK_STREAM))
477 | elif protocol == 'udp':
478 | self.lib.set_protocol(self._test, int(SOCK_DGRAM))
479 |
480 | if self.blksize > MAX_UDP_BULKSIZE:
481 | self.blksize = MAX_UDP_BULKSIZE
482 |
483 | self._protocol = protocol
484 |
485 | @property
486 | def omit(self):
487 | """The test startup duration to omit in seconds."""
488 | self._omit = self.lib.iperf_get_test_omit(self._test)
489 | return self._omit
490 |
491 | @omit.setter
492 | def omit(self, omit):
493 | self.lib.iperf_set_test_omit(self._test, omit)
494 | self._omit = omit
495 |
496 | @property
497 | def duration(self):
498 | """The test duration in seconds."""
499 | self._duration = self.lib.iperf_get_test_duration(self._test)
500 | return self._duration
501 |
502 | @duration.setter
503 | def duration(self, duration):
504 | self.lib.iperf_set_test_duration(self._test, duration)
505 | self._duration = duration
506 |
507 | @property
508 | def bandwidth(self):
509 | """Target bandwidth in bits/sec"""
510 | self._bandwidth = self.lib.iperf_get_test_rate(self._test)
511 | return self._bandwidth
512 |
513 | @bandwidth.setter
514 | def bandwidth(self, bandwidth):
515 | self.lib.iperf_set_test_rate(self._test, bandwidth)
516 | self._bandwidth = bandwidth
517 |
518 | @property
519 | def blksize(self):
520 | """The test blksize."""
521 | self._blksize = self.lib.iperf_get_test_blksize(self._test)
522 | return self._blksize
523 |
524 | @blksize.setter
525 | def blksize(self, bulksize):
526 | # iperf version < 3.1.3 has some weird bugs when bulksize is
527 | # larger than MAX_UDP_BULKSIZE
528 | if self.protocol == 'udp' and bulksize > MAX_UDP_BULKSIZE:
529 | bulksize = MAX_UDP_BULKSIZE
530 |
531 | self.lib.iperf_set_test_blksize(self._test, bulksize)
532 | self._blksize = bulksize
533 |
534 | @property
535 | def bulksize(self):
536 | """The test bulksize.
537 |
538 | Deprecated argument, use blksize instead to ensure consistency
539 | with iperf3 C libary
540 | """
541 | # Keeping bulksize argument for backwards compatibility with
542 | # iperf3-python < 0.1.7
543 | return self.blksize
544 |
545 | @bulksize.setter
546 | def bulksize(self, bulksize):
547 | # Keeping bulksize argument for backwards compatibility with
548 | # iperf3-python < 0.1.7
549 | self.blksize = bulksize
550 |
551 | @property
552 | def num_streams(self):
553 | """The number of streams to use."""
554 | self._num_streams = self.lib.iperf_get_test_num_streams(self._test)
555 | return self._num_streams
556 |
557 | @num_streams.setter
558 | def num_streams(self, number):
559 | self.lib.iperf_set_test_num_streams(self._test, number)
560 | self._num_streams = number
561 |
562 | @property
563 | def zerocopy(self):
564 | """Toggle zerocopy.
565 |
566 | Use the sendfile() system call for "Zero Copy" mode. This uses much
567 | less CPU. This is not supported on all systems.
568 |
569 | **Note** there isn't a hook in the libiperf library for getting the
570 | current configured value. Relying on zerocopy.setter function
571 |
572 | :rtype: bool
573 | """
574 | return self._zerocopy
575 |
576 | @zerocopy.setter
577 | def zerocopy(self, enabled):
578 | if enabled and self.lib.iperf_has_zerocopy():
579 | self.lib.iperf_set_test_zerocopy(self._test, 1)
580 | self._zerocopy = True
581 | else:
582 | self.lib.iperf_set_test_zerocopy(self._test, 0)
583 | self._zerocopy = False
584 |
585 | @property
586 | def reverse(self):
587 | """Toggles direction of test
588 |
589 | :rtype: bool
590 | """
591 | enabled = self.lib.iperf_get_test_reverse(self._test)
592 |
593 | if enabled:
594 | self._reverse = True
595 | else:
596 | self._reverse = False
597 |
598 | return self._reverse
599 |
600 | @reverse.setter
601 | def reverse(self, enabled):
602 | if enabled:
603 | self.lib.iperf_set_test_reverse(self._test, 1)
604 | else:
605 | self.lib.iperf_set_test_reverse(self._test, 0)
606 |
607 | self._reverse = enabled
608 |
609 | def run(self):
610 | """Run the current test client.
611 |
612 | :rtype: instance of :class:`TestResult`
613 | """
614 | if self.json_output:
615 | output_to_pipe(self._pipe_in) # Disable stdout
616 | error = self.lib.iperf_run_client(self._test)
617 |
618 | if not self.iperf_version.startswith('iperf 3.1'):
619 | data = read_pipe(self._pipe_out)
620 | if data.startswith('Control connection'):
621 | data = '{' + data.split('{', 1)[1]
622 | else:
623 | data = c_char_p(
624 | self.lib.iperf_get_test_json_output_string(self._test)
625 | ).value
626 | if data:
627 | data = data.decode('utf-8')
628 |
629 | output_to_screen(self._stdout_fd, self._stderr_fd) # enable stdout
630 |
631 | if not data or error:
632 | data = '{"error": "%s"}' % self._error_to_string(self._errno)
633 |
634 | return TestResult(data)
635 |
636 |
637 | class Server(IPerf3):
638 | """An iperf3 server connection.
639 |
640 | This starts an iperf3 server session. The server terminates after each
641 | succesful client connection so it might be useful to run Server.run()
642 | in a loop.
643 |
644 | The C function iperf_run_server is called in a seperate thread to make
645 | sure KeyboardInterrupt(aka ctrl+c) can still be captured
646 |
647 | Basic Usage::
648 |
649 | >>> import iperf3
650 |
651 | >>> server = iperf3.Server()
652 | >>> server.run()
653 | {'start': {...
654 | """
655 |
656 | def __init__(self, *args, **kwargs):
657 | """Initialise the iperf3 server instance"""
658 | super(Server, self).__init__(role='s', *args, **kwargs)
659 |
660 | def run(self):
661 | """Run the iperf3 server instance.
662 |
663 | :rtype: instance of :class:`TestResult`
664 | """
665 |
666 | def _run_in_thread(self, data_queue):
667 | """Runs the iperf_run_server
668 |
669 | :param data_queue: thread-safe queue
670 | """
671 | output_to_pipe(self._pipe_in) # disable stdout
672 | error = self.lib.iperf_run_server(self._test)
673 | output_to_screen(self._stdout_fd, self._stderr_fd) # enable stdout
674 |
675 | # TODO json_output_string not available on earlier iperf3 builds
676 | # have to build in a version check using self.iperf_version
677 | # The following line should work on later versions:
678 | # data = c_char_p(
679 | # self.lib.iperf_get_test_json_output_string(self._test)
680 | # ).value
681 | data = read_pipe(self._pipe_out)
682 |
683 | if not data or error:
684 | data = '{"error": "%s"}' % self._error_to_string(self._errno)
685 |
686 | self.lib.iperf_reset_test(self._test)
687 | data_queue.put(data)
688 |
689 | if self.json_output:
690 | data_queue = Queue()
691 |
692 | t = threading.Thread(
693 | target=_run_in_thread, args=[self, data_queue]
694 | )
695 | t.daemon = True
696 |
697 | t.start()
698 | while t.is_alive():
699 | t.join(.1)
700 |
701 | return TestResult(data_queue.get())
702 | else:
703 | # setting json_output to False will output test to screen only
704 | self.lib.iperf_run_server(self._test)
705 | self.lib.iperf_reset_test(self._test)
706 |
707 | return None
708 |
709 |
710 | class TestResult(object):
711 | """Class containing iperf3 test results.
712 |
713 | :param text: The raw result from libiperf as text
714 | :param json: The raw result from libiperf asjson/dict
715 | :param error: Error captured during test, None if all ok
716 |
717 | :param time: Start time
718 | :param timesecs: Start time in seconds
719 |
720 | :param system_info: System info
721 | :param version: Iperf Version
722 |
723 | :param local_host: Local host ip
724 | :param local_port: Local port number
725 | :param remote_host: Remote host ip
726 | :param remote_port: Remote port number
727 |
728 | :param reverse: Test ran in reverse direction
729 | :param protocol: 'TCP' or 'UDP'
730 | :param num_streams: Number of test streams
731 | :param blksize:
732 | :param omit: Test duration to omit in the beginning in seconds
733 | :param duration: Test duration (following omit duration) in seconds
734 |
735 | :param local_cpu_total: The local total CPU load
736 | :param local_cpu_user: The local user CPU load
737 | :param local_cpu_system: The local system CPU load
738 | :param remote_cpu_total: The remote total CPU load
739 | :param remote_cpu_user: The remote user CPU load
740 | :param remote_cpu_system: The remote system CPU load
741 |
742 |
743 | TCP test specific
744 |
745 | :param tcp_mss_default:
746 | :param retransmits: amount of retransmits (Only returned from client)
747 |
748 | :param sent_bytes: Sent bytes
749 | :param sent_bps: Sent bits per second
750 | :param sent_kbps: sent kilobits per second
751 | :param sent_Mbps: Sent Megabits per second
752 | :param sent_kB_s: Sent kiloBytes per second
753 | :param sent_MB_s: Sent MegaBytes per second
754 |
755 | :param received_bytes: Received bytes
756 | :param received_bps: Received bits per second
757 | :param received_kbps: Received kilobits per second
758 | :param received_Mbps: Received Megabits per second
759 | :param received_kB_s: Received kiloBytes per second
760 | :param received_MB_s: Received MegaBytes per second
761 |
762 |
763 | UDP test specific
764 |
765 | :param bytes:
766 | :param bps:
767 | :param jitter_ms:
768 | :param kbps:
769 | :param Mbps:
770 | :param kB_s:
771 | :param MB_s:
772 | :param packets:
773 | :param lost_packets:
774 | :param lost_percent:
775 | :param seconds:
776 | """
777 |
778 | def __init__(self, result):
779 | """Initialise TestResult
780 |
781 | :param result: raw json output from :class:`Client` and :class:`Server`
782 | """
783 | # The full result data
784 | self.text = result
785 | self.json = json.loads(result)
786 |
787 | if 'error' in self.json:
788 | self.error = self.json['error']
789 | else:
790 | self.error = None
791 |
792 | # start time
793 | self.time = self.json['start']['timestamp']['time']
794 | self.timesecs = self.json['start']['timestamp']['timesecs']
795 |
796 | # generic info
797 | self.system_info = self.json['start']['system_info']
798 | self.version = self.json['start']['version']
799 |
800 | # connection details
801 | connection_details = self.json['start']['connected'][0]
802 | self.local_host = connection_details['local_host']
803 | self.local_port = connection_details['local_port']
804 | self.remote_host = connection_details['remote_host']
805 | self.remote_port = connection_details['remote_port']
806 |
807 | # test setup
808 | self.tcp_mss_default = self.json['start'].get('tcp_mss_default')
809 | self.protocol = self.json['start']['test_start']['protocol']
810 | self.num_streams = self.json['start']['test_start']['num_streams']
811 | self.blksize = self.json['start']['test_start']['blksize']
812 | self.omit = self.json['start']['test_start']['omit']
813 | self.duration = self.json['start']['test_start']['duration']
814 |
815 | # system performance
816 | cpu_utilization_perc = self.json['end']['cpu_utilization_percent']
817 | self.local_cpu_total = cpu_utilization_perc['host_total']
818 | self.local_cpu_user = cpu_utilization_perc['host_user']
819 | self.local_cpu_system = cpu_utilization_perc['host_system']
820 | self.remote_cpu_total = cpu_utilization_perc['remote_total']
821 | self.remote_cpu_user = cpu_utilization_perc['remote_user']
822 | self.remote_cpu_system = cpu_utilization_perc['remote_system']
823 |
824 | # TCP specific test results
825 | if self.protocol == 'TCP':
826 | sent_json = self.json['end']['sum_sent']
827 | self.sent_bytes = sent_json['bytes']
828 | self.sent_bps = sent_json['bits_per_second']
829 |
830 | recv_json = self.json['end']['sum_received']
831 | self.received_bytes = recv_json['bytes']
832 | self.received_bps = recv_json['bits_per_second']
833 |
834 | # Bits are measured in 10**3 terms
835 | # Bytes are measured in 2**10 terms
836 | # kbps = Kilobits per second
837 | # Mbps = Megabits per second
838 | # kB_s = kiloBytes per second
839 | # MB_s = MegaBytes per second
840 |
841 | self.sent_kbps = self.sent_bps / 1000
842 | self.sent_Mbps = self.sent_kbps / 1000
843 | self.sent_kB_s = self.sent_bps / (8 * 1024)
844 | self.sent_MB_s = self.sent_kB_s / 1024
845 |
846 | self.received_kbps = self.received_bps / 1000
847 | self.received_Mbps = self.received_kbps / 1000
848 | self.received_kB_s = self.received_bps / (8 * 1024)
849 | self.received_MB_s = self.received_kB_s / 1024
850 |
851 | # retransmits only returned from client
852 | self.retransmits = sent_json.get('retransmits')
853 |
854 | # UDP specific test results
855 | elif self.protocol == 'UDP':
856 | self.bytes = self.json['end']['sum']['bytes']
857 | self.bps = self.json['end']['sum']['bits_per_second']
858 | self.jitter_ms = self.json['end']['sum']['jitter_ms']
859 | self.kbps = self.bps / 1000
860 | self.Mbps = self.kbps / 1000
861 | self.kB_s = self.bps / (8 * 1024)
862 | self.MB_s = self.kB_s / 1024
863 | self.packets = self.json['end']['sum']['packets']
864 | self.lost_packets = self.json['end']['sum']['lost_packets']
865 | self.lost_percent = self.json['end']['sum']['lost_percent']
866 | self.seconds = self.json['end']['sum']['seconds']
867 |
868 | @property
869 | def reverse(self):
870 | if self.json['start']['test_start']['reverse']:
871 | return True
872 | else:
873 | return False
874 |
875 | @property
876 | def type(self):
877 | if 'connecting_to' in self.json['start']:
878 | return 'client'
879 | else:
880 | return 'server'
881 |
882 | def __repr__(self):
883 | """Print the result as received from iperf3"""
884 | return self.text
885 |
--------------------------------------------------------------------------------