|
|
Testes de carga para chatbots implementados com a stack Rasa
|
|
|
=======
|
|
|
|
|
|
|
|
|
# Rasa
|
|
|
|
|
|
A versão do rasa utilizada nos testes de carga é a 1.2.2. Essa versão possui como dependência o projeto Senic, que é utilizado para servir os endpoints de integração com frontends diversos como rocketchat e telegram. O Senic é um servidor web asíncrono e utiliza a arquitetura asyncio do python (introduzida no python 3.4) para a implementação dos endpoints. O rasa possui um endpoint específico para integração com os frontends que é a rota /webhooks/rest/webhook. No asyncio cada chamada a um método asíncrono, dispara um nova thread no sistema operacional para a execução da chamada. O número de threads que um endpoint do Senic pode criar, é definido pela regra: `os.cpu_count() * 5`. Essa regra mudou a partir do python 3.7 (https://bugs.python.org/issue35279).
|
|
|
O ambiente de testes possui 6 cpus, então teremos, para cada instância (Pod) do Senic no máximo trinta threads disponíveis. Isso implica que, em media, para cada instância do Senic teremos a capacidade de processamento de 30 requisições por vez. Para avaliar se essa media condiz com a realidade, iremos utilizar o software JMeter (https://jmeter.apache.org) para executar cenários de teste em que requisições serão disparadas na api do Rasa, e a partir do tempo de resposta e requisições corretamente processadas estimaremos a infraestrutura necessária para diferentes volumes de acesso.
|
|
|
|
|
|
# Testes
|
|
|
|
|
|
|
|
|
O primeiro teste que iremos fazer irá avaliar nosso ponto de partida, de que uma única instância do rasa consegue processar de maneira asíncrona trinta requisições paralelas. A instância do rasa que iremos utilizar se encontra no domínio http://boilerplate.lappis.rocks/ e está sendo disponibilizada por um cluster no kubernetes, gerenciado pelo rancher. O deploy do rasa foi feito via Helm Chart, e pode ser encontrado no [repositório de charts do lappis](https://gitlab.com/lappis-unb/charts).
|
|
|
|
|
|
Para a criação dos cenários de teste estaremos utilizando o Jmeter na versão 5.1.1. As requisições http enviadas via Jmeter terão o seguinte padrão;
|
|
|
|
|
|
POST http://boilerplate.lappis.rocks/webhooks/rest/webhook/
|
|
|
POST data:
|
|
|
{
|
|
|
"sender": "default",
|
|
|
"message": "oi"
|
|
|
}
|
|
|
|
|
|
As respostas da api variam de acordo com o processamento feito pelo rasa, mas de modo geral o response vem no formato:
|
|
|
|
|
|
```
|
|
|
[{"recipient_id":"default","text":"Foi um prazer te ajudar!\nSempre que precisar, volte aqui!\nAt\u00e9 a pr\u00f3xima!\n"}]
|
|
|
```
|
|
|
|
|
|
O endpoint que processa as requisições enviadas na rota `/webhooks/rest/webhook/` possui a seguinte implementação:
|
|
|
```
|
|
|
@custom_webhook.route("/webhook", methods=["POST"])
|
|
|
async def receive(request: Request):
|
|
|
sender_id = await self._extract_sender(request)
|
|
|
text = self._extract_message(request)
|
|
|
should_use_stream = rasa.utils.endpoints.bool_arg(
|
|
|
request, "stream", default=False
|
|
|
)
|
|
|
input_channel = self._extract_input_channel(request)
|
|
|
|
|
|
if should_use_stream:
|
|
|
return response.stream(
|
|
|
self.stream_response(
|
|
|
on_new_message, text, sender_id, input_channel
|
|
|
),
|
|
|
content_type="text/event-stream",
|
|
|
)
|
|
|
else:
|
|
|
collector = CollectingOutputChannel()
|
|
|
# noinspection PyBroadException
|
|
|
try:
|
|
|
await on_new_message(
|
|
|
UserMessage(
|
|
|
text, collector, sender_id, input_channel=input_channel
|
|
|
)
|
|
|
)
|
|
|
except CancelledError:
|
|
|
logger.error(
|
|
|
"Message handling timed out for "
|
|
|
"user message '{}'.".format(text)
|
|
|
)
|
|
|
except Exception:
|
|
|
logger.exception(
|
|
|
"An exception occured while handling "
|
|
|
"user message '{}'.".format(text)
|
|
|
)
|
|
|
return response.json(collector.messages)
|
|
|
```
|
|
|
|
|
|
Note que nessa implementação o endpoint congela a execução na chamada do método `on_new_message`. É nesse ponto que cada thread vai aguardar o rasa processar o texto enviado pelo usuário. Assim que a mensagem é enviada de volta pelo rasa o endpoint encerra sua execução e a thread é removida. Os cenários de teste a seguir irão disparar requisições http no endpoint de webhook, enviando no body algum texto a ser respondido pelo bot.
|
|
|
|
|
|
## Cenario 1
|
|
|
|
|
|
| nº de threads | nº de instâncias | RAM (mb) por instância |
|
|
|
| ------ | ------ | ------ |
|
|
|
| 30 | 1 | 300 |
|
|
|
|
|
|
![flotTimesVsThreads](uploads/4e72c40899002c4be90e1811423a291e/flotTimesVsThreads.png)
|
|
|
|
|
|
|
|
|
Podemos verificar que o Senic manteve o tempo de resposta de cada thread dentro de uma faixa de 12.5 segundos, o que é um indício de que apesar de terem sido disparadas ao mesmo tempo, todas foram processadas de maneira assíncrona. Um segunda ponto é que todas as 30 requisições foram processadas, e a api retornou um response válido para todas.
|
|
|
|
|
|
## Cenario 2
|
|
|
|
|
|
| nº de threads | nº de instâncias | RAM (mb) por instância |
|
|
|
| ------ | ------ | ------ |
|
|
|
| 30 | 2 | 300 |
|
|
|
|
|
|
![flotTimesVsThreads__2_](uploads/e194e82c9e52c450793ebdeaa0b28e96/flotTimesVsThreads__2_.png)
|
|
|
|
|
|
|
|
|
|
|
|
## Cenario 3
|
|
|
|
|
|
| nº de threads | nº de instâncias | RAM (mb) por instância |
|
|
|
| ------ | ------ | ------ |
|
|
|
| 60 | 1 | 300 |
|
|
|
|
|
|
![flotTimesVsThreads__1_](uploads/0daf6c2976e9e92b7a6837fff729c3f1/flotTimesVsThreads__1_.png)
|
|
|
|