IT & Engineering

Pruebas de estrés de HTTP con Twisted Python y Treq

Estar en el sector de las API tiene sus retos, y mantener la solidez del sistema durante las horas punta es uno de ellos. Por eso hacemos muchas pruebas de estrés aquí en Mailgun.
Imagen para Pruebas de estrés de HTTP con Twisted Python y Treq

Estar en el sector de las API tiene sus retos, y mantener la solidez del sistema durante las horas punta es uno de ellos. Por eso hacemos muchas pruebas de estrés aquí en Mailgun.

Con el tiempo hemos probado diferentes enfoques, desde el simple Apache bench hasta conjuntos de pruebas personalizados más complejos. Pero este artículo trata sobre unas pruebas de estrés “rápidas y rudimentarias”, aunque muy flexibles, con Python.

Cuando se trata de programar clientes HTTP en Python, somos fans de la biblioteca Requests. Esto es lo que recomendamos a los usuarios de nuestra API. Requests es genial, pero tiene un punto débil: es un sistema bloqueante de una llamada por hilo. Es difícil o imposible generar decenas de miles de peticiones rápidamente con ella.

Presentamos Treq en Twisted

Para resolver este problema, analizamos Treq (repositorio de Github). Treq es una biblioteca de cliente HTTP inspirada en Requests, pero se ejecuta en Twisted y posee las funciones típicas de Twisted: es asíncrona y altamente concurrente en lo que respecta a las E/S de red.

Treq no es exclusiva para pruebas de estrés en absoluto: es una gran herramienta para programar clientes HTTP altamente concurrentes en general, como los rastreadores web. Treq es elegante, fácil de usar y potente. Aquí tienes un ejemplo:

                                

                                     >>> from treq import get
    >>> def done(response):
    ... print response.code
    ... reactor.stop()
    >>> get("http://www.github.com").addCallback(done)
    >>> from twisted.internet import reactor
    >>> reactor.run() 200
                                
                            

El script de prueba sencillo

A continuación se muestra un script sencillo que utiliza Treq para bombardear una única URL con el mayor número de peticiones posible.

                                

                                     #!/usr/bin/env python  
    from twisted.internet import epollreactor  
    epollreactor.install()
    from twisted.internet import reactor, task  
    from twisted.web.client import HTTPConnectionPool  
    import treq  
    import random  
    from datetime import datetime
    req_generated = 0  
    req_made = 0  
    req_done = 0
    cooperator = task.Cooperator()
    pool = HTTPConnectionPool(reactor)
    def counter():  
    '''This function gets called once a second and prints the progress at one 
    second intervals. 
    '''
    print("Requests: {} generated; {} made; {} done".format(
    req_generated, req_made, req_done))
    # reset the counters and reschedule ourselves
    req_generated = req_made = req_done = 0
    reactor.callLater(1, counter)
    def body_received(body):  
    global req_done
    req_done += 1
    def request_done(response):  
    global req_made
    deferred = treq.json_content(response)
    req_made += 1
    deferred.addCallback(body_received)
    deferred.addErrback(lambda x: None)  # ignore errors
    return deferred
    def request():  
    deferred = treq.post('http://api.host/v2/loadtest/messages',
         auth=('api', 'api-key'),
         data={'from': 'Loadtest ',
               'to': 'to@example.org',
               'subject': "test"},
         pool=pool)
    deferred.addCallback(request_done)
    return deferred
    def requests_generator():  
    global req_generated
    while True:
    deferred = request()
    req_generated += 1
    # do not yield deferred here so cooperator won't pause until
    # response is received
    yield None
    if __name__ == '__main__':  
    # make cooperator work on spawning requests
    cooperator.cooperate(requests_generator())
    # run the counter that will be reporting sending speed once a second
    reactor.callLater(1, counter)
    # run the reactor
    reactor.run()
                                
                            

El resultado:

                                

                                    2013-04-25 09:30 Requests: 327 generated; 153 sent; 153 received 
2013-04-25 09:30 Requests: 306 generated; 156 sent; 156 received 
2013-04-25 09:30 Requests: 318 generated; 184 sent; 154 received
                                
                            

Las “generadas” son las peticiones que se han preparado, pero que el reactor de Twisted aún no ha enviado. Para simplificar, este script ignora todos los errores; añadir las estadísticas de los tiempos de espera se deja como ejercicio para quien lo lea.

El script puede servir como punto de partida y mejorarse y ampliarse con tu propia lógica específica de la aplicación. Una sugerencia de mejora sería utilizar collections.Counter en lugar de las antiestéticas variables globales. El script se ejecuta en un solo hilo, y para exprimir al máximo el número de peticiones de una máquina habría que usar algo como multiprocessing se puede usar.

¡Felices pruebas de estrés!

Un saludo,
El equipo de Mailgun