Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Teste de carga e desempenho

Testando a capacidade e desempenho de uma API com o framework Locust, em Python

Locust logo

Inquietação

Uma preocupação que surgiu na empresa em que trabalho, a CARGOBR, é saber se nossa nova API vai dar conta do recado. E eis que alguém pode perguntar "mas se é nova, qual a preocupação, não se garantem"? Bem, ela é nova, mas usa em grande parte o motor de cotações que está em sistemas legados, que datam do surgimento da empresa.

Mas o que é dar conta do recado? Conversando com o pessoal que está mais ligado com as métricas e resultados, consegui números sobre o comportamento de nossos clientes, que me ajudaram a definir como executar esses testes, o que é desejável, o que é aceitável.

Eu já tinha em minha cabeça que coisas simples como Apache Bench não dizem muita coisa. E também que instalar um JMeter estava além de minha necessidade de um primeiro experimento.

E confesso, são duas ferramentas que já utilizei e me ajudaram muito.

A busca e o achado

Depois de uma googlada encontrei um framework, em Python, ponto a mais para isso, que me chamou a atenção. É o Locust.

A proposta, simples e direta:

"Define user behaviour with Python code, and swarm your system with millions of simultaneous users."

Daí (olha o curitibano se entregando) pensei comigo: "É disso que o velho gosta, é isso que o velho quer!".

O MVP

Dadas as instruções contidas no CARD (viva o JIRA!), construí rapidamente um modelo, copiado da excelente documentação. Aqui apresento algo mais simples ainda, pra testar localmente:


from locust import HttpLocust, TaskSet, task


class UserBehavior(TaskSet):
    @task
    def get_antifascist(self):
        self.client.get('/antifascist.png')

    @task
    def get_chupacabra(self):
        self.client.get('/chupacabra.jpg')

    @task
    def get_democracia(self):
        self.client.get('/democracia.jpg')


class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 500
    max_wait = 3000

Uma informação importante, é que esse framework usa a biblioteca requests, então use todo esse poder a seu favor.

Para testar esse script, subiu um servidor simples com Python:


➤ python3 -m http.server --directory ~/Imagens/
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

E rodei esse comando:


locust -f localhost --host=http://localhost:8000 --no-web --run-time 1m -c 20 -r 1

Que me deu uma longa saída, mas a importante mesmo está no final:


[2018-11-22 20:51:03,054] phrp/INFO/locust.main: Time limit reached. Stopping Locust.
[2018-11-22 20:51:03,054] phrp/INFO/locust.main: Shutting down (exit code 0), bye.
[2018-11-22 20:51:03,055] phrp/INFO/locust.main: Cleaning up runner...
[2018-11-22 20:51:03,056] phrp/INFO/locust.main: Running teardowns...
 Name                                                          # reqs      # fails     Avg     Min     Max  |  Median   req/s
--------------------------------------------------------------------------------------------------------------------------------------------
 GET /antifascist.png                                             203     0(0.00%)       9       5      21  |       9    4.00
 GET /chupacabra.jpg                                              187     0(0.00%)      10       5      20  |       9    3.80
 GET /democracia.jpg                                              191     0(0.00%)      10       5      27  |       9    3.30
--------------------------------------------------------------------------------------------------------------------------------------------
 Total                                                            581     0(0.00%)                                      11.10

Percentage of the requests completed within given times
 Name                                                           # reqs    50%    66%    75%    80%    90%    95%    98%    99%   100%
--------------------------------------------------------------------------------------------------------------------------------------------
 GET /antifascist.png                                              203      9     10     11     11     13     15     17     18     21
 GET /chupacabra.jpg                                               187      9     10     11     12     13     16     18     19     20
 GET /democracia.jpg                                               191      9     10     11     12     14     16     19     21     27
--------------------------------------------------------------------------------------------------------------------------------------------
 Total                                                             581      9     10     11     12     13     15     18     19     27

Por padrão, o locust disponibiliza uma interface gráfica na porta 8080, e para não ter isso é que passei a opção --no-web.

As outras opções que usei foram:

Algumas breves explicações

O decorator @task define, dentro da classe, que dada função é uma tarefa a ser executada. Opcionalmente, pode-se informar um número inteiro que será o peso com que essa tarefa será executada em relação às outras. Por exemplo, se eu precisar que um endpoint seja executa mais vezes que os outros, é aí que defino esse comportamento, algo como @task(10).

A classe TaskSet define um conjunto de tarefas a serem executadas, enquanto a classe HttpLocust define o controlador de todas as tarefas.

O que realmente importa

Mas no final das contas, eu tenho um certo caminho a seguir em meus testes: autenticar, escolher algumas coisas, simular, verificar. Para isso existe a classe TaskSequence, para usar ao invés de TaskSet. E como decorator, deve-se usar @seq_task(1), com um número (nesse exemplo 1) indicando a sequência dos endpoints para cada conjunto de tarefas.

Há ainda o problema de autenticar, e seguir com as demais requisições com um token. Mas isso é conversa pra um próximo texto!