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.
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