4

I'm writing a simple speed tester program that should measure quic throughtput between two devices. There is a python script that can launch client or server side. Client generates traffic, server measures how much traffic it receives. As simple as that. But somehow, even if I run both server and client on the same machine, it caps at ca. 115 Mb/s. On the same machine when i generate TCP or UDP traffic its around 30 Gb/s so bandwidth is not an issue. The library i use is aioquic, unfortunately its almost undocumented. The question is how to increase throughtput? Im primarily worried about a bit of code around #HERE comment. It looks like transmit() is a blocking function, but when i dont have that pause/continue mechanism or some kind of crude sleep(), the program just hangs and even then generates low traffic.

from connector import * #my base class
import asyncio as asc
import aioquic.asyncio
from aioquic.quic.configuration import QuicConfiguration
import ssl

class QUIC_client(client):
    def __init__(self, host, port, use_json):
        super().__init__(host, port, use_json)
        self.traffic_type = 'quic'
        self.payload = b'p'*(self.quic_payload_size-1)+b'\n'

        self.config = QuicConfiguration(
            is_client = True,
            verify_mode = ssl.CERT_NONE
            )

    async def _maintask(self, time):
        self._infomessage(f'connecting to server {self.host}:{self.port}')
        async with aioquic.asyncio.connect(host=self.host, port=self.port, configuration=self.config) as client:
            await client.wait_connected()
            self._infomessage(message=f'connection successful')
            reader, writer = await client.create_stream()
            self._infomessage(message=f'stream created, now transmitting')

            timetofinish = millis() + (time*1000)
            while(millis() < timetofinish):
                for i in range(300):
                    writer.write(self.payload)
                writer.write(b'PAUSE\n')
                client.transmit()
                #HERE
                #when i just send data over and over again program hangs on client side
                #thats why i send 'PAUSE' and wait for 'CONTINUE'
                #its just a temporary solution but i couldnt find anything to just wait until send is complete
                line = await reader.readline()
                if line == b'CONTINUE\n':
                    #self._infomessage(message=f'continuing...')
                    pass
                else:
                    self._infomessage(message=f'connection closed')
                    break

            writer.write(b'STOP\n')
            client.transmit()
            client.close()
            await client.wait_closed()
            self._infomessage(message=f'client finished')

    def run_test(self, time):
        super().run_test(time)
        loop = asc.get_event_loop()
        loop.run_until_complete(self._maintask(time))



class QUIC_server(server):
    def __init__(self, port, interval, use_json):
        super().__init__(port, interval, use_json)
        self.traffic_type = 'quic'
        self.config = QuicConfiguration(
            is_client = False
            )
        self.config.load_cert_chain('cert.pem', 'key.pem')
        self.loop = asc.get_event_loop()

    def _streamhandler(self, reader, writer):
        self._infomessage(message='stream created')
        self.currentstreamtask = self.loop.create_task(self._currentstreamhandler(reader, writer))

    async def _currentstreamhandler(self, reader, writer):
        data_counter = 0
        timer = millis()
        while(True):
            line = await reader.readline()
            if line == b'':
                self._infomessage(message='connection interrupted! now exitting', is_error=True)
                return
            elif line == b'STOP\n':
                self._infomessage('server finished')
                self.loop.stop()
                return
            elif line == b'PAUSE\n':
                    writer.write(b'CONTINUE\n')
                    #TODO find a better way to control data flow
            else:
                data_counter += 1
                if (millis() - timer) > (self.interval*1000):
                    timer = millis()
                    self._datamessage(bps_value=(data_counter*self.quic_payload_size*8/self.interval))
                    data_counter = 0

    def listen(self):
        super().listen()
        try:
            self.server_task = self.loop.create_task(
                aioquic.asyncio.serve(host='0.0.0.0',
                port=self.port,
                configuration=self.config,
                stream_handler=self._streamhandler
                ))
            self.loop.run_forever()
        except asc.CancelledError:
            print('cancelled error')

#basically when running a test
#QUIC_client or QUIC_server instance is created
#and then run_test() or listen() is called

tomaszu
  • 53
  • 5
  • You could try https://github.com/MagicStack/uvloop which is generally much faster than standard asyncio implementation (not sure if compatible with aioquic though). Also, you might want to avoid readline which may be slow, and send data in fairly large chunks (at least 64 kB) to avoid Python slowness. – L. Kärkkäinen Apr 04 '20 at 15:47
  • Unfortunately when I increase payload it doesn't work: If i increase loop range (now 300) it doesnt change anything. If i increase payload size (now 65500, below udp data field size) program freezes Also, no, unfortunately this library is not compatible with uvloop. Do you think it might be something not efficiency related, but rather me doing something wrong? – tomaszu Apr 04 '20 at 15:59
  • Freeze is probably due to the deadlock situation explained here https://trio.readthedocs.io/en/stable/tutorial.html#flow-control-in-our-echo-client-and-server (TLDR: client or server needs to send and recv concurrently; deadlocks may occur if both of them alternate between send and recv) – L. Kärkkäinen Apr 04 '20 at 16:05
  • 1
    About the speed you are getting: it is very hard to say if this is normal with the framework being used. E.g. how much does the encryption cost and whether Python is using AES-NI instructions for that (they can do several gigabytes per second, software encryption is much slower). With plain TCP I can reach above 1 GB/s on a single connection in Python but I haven't tested TLS that much, QUIC and UDP even less. – L. Kärkkäinen Apr 04 '20 at 16:12
  • The thing is that it freezes even if i modify code so that I dont send any data from server to client. I just want to generate as much traffic as possible, one way only.
    edit: is there any good library, maybe in other language, that you would recommend for such task? I'm sorry, but i feel a bit lost, there are so many implementations all i want is to measure throughtput, and many of them offer HTTP/3 server out of the box but no simple QUIC-only communication.
    – tomaszu Apr 04 '20 at 16:15
  • QUIC is still so uncommon that it is hard to find good implementation. I've been looking for one on Python myself, with the purpose of developing HTTP/3 server on it. – L. Kärkkäinen Apr 04 '20 at 16:23
  • Okay, I get it. Thank you so much for your support :) At least i feel more confinent about this matter now – tomaszu Apr 04 '20 at 16:28
  • @tomaszu I'm looking for the same client/server. Have you found something useful in last months? – Gorgo Sep 10 '20 at 14:37
  • @Davide Sorry, but I gave up. I was trying other languages as well, but could not find any efficient library and some of them couldn't even build properly. I think implementation in golang was a little bit faster. Hope you will find something :) – tomaszu Sep 19 '20 at 08:54

0 Answers0