IT & Engineering

Teste de carga HTTP com Twisted Python e Treq

Estar no mercado de APIs tem seus desafios, e manter a robustez do sistema nos horários de pico é um deles. É por isso que fazemos muitos testes de carga aqui na Mailgun.
Imagem para Teste de carga HTTP com Twisted Python e Treq

Estar no mercado de APIs tem seus desafios, e manter a robustez do sistema nos horários de pico é um deles. É por isso que fazemos muitos testes de carga aqui na Mailgun.

Tentamos diversas abordagens ao longo do tempo, de um simples Apache Bench a suítes de teste personalizadas mais complexas. Mas esta publicação é sobre um teste de carga “rápido e improvisado”, porém muito flexível, usando Python.

Quando se trata de programar clientes HTTP em Python, somos fãs da biblioteca Requests. É isso que recomendamos aos usuários da nossa API. A Requests é ótima, mas tem um ponto fraco: o bloqueio de uma chamada por thread. É difícil ou impossível gerar dezenas de milhares de solicitações rapidamente com ela.

Apresentando o Treq no Twisted

Para resolver esse problema, avaliamos o Treq (repositório do Github). O Treq é uma biblioteca de clientes HTTP inspirada na Requests, mas executada no Twisted, e possui as vantagens típicas do Twisted: é assíncrona e altamente simultânea quando se trata de E/S de rede.

O Treq não é específico para testes de carga: é uma ótima ferramenta para criar clientes HTTP de alta simultaneidade no geral, como os rastreadores da web. O Treq é elegante, simples de usar e poderoso. Aqui está um exemplo:

                                

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

O script de teste simples

Abaixo, mostramos um script simples que usa o Treq para bombardear um único URL com o maior número possível de solicitações.

                                

                                     #!/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()
                                
                            

A saída:

                                

                                    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
                                
                            

As solicitações marcadas como “Generated” são aquelas que já foram preparadas, mas que o reator do Twisted ainda não enviou. Por simplicidade, este script ignora todos os erros. Incluir as estatísticas de tempo limite fica como um exercício para você.

O script pode ser usado como um ponto de partida, sendo aprimorado e ampliado com a sua própria lógica personalizada e específica do aplicativo. Uma sugestão de melhoria seria usar  collections.Counter em vez das deselegantes variáveis globais. O script é executado em uma única thread. Para conseguir o número máximo de solicitações de uma máquina, tente usar algo como a multiprocessing pode ser usado.

Bons testes de carga!

Abraços,
Mailgunners