Locust: alternativa a JMeter para pruebas de carga

Apache JMeter es probablemente la herramienta de testing más conocida para hacer pruebas de carga o pruebas de rendimiento. Y por buenos motivos: es Open Source, soporta casi cualquier protocolo que puedas imaginar, tiene más de 20 años de historia y funciona en todas las plataformas.

Desafortunadamente también tiene sus puntos negativos: en mi opinión aprender a usar JMeter lleva mucho tiempo por tener tantísimas opciones, la interfaz de usuario no es especialmente amigable y existe un «lenguaje» que tienes que conocer si quieres hacer cosas un poco avanzadas como generar fechas o números aleatorios por ejemplo:

${__Random(0,10)}

Pero aprender a usar una herramienta claro que lleva tiempo, vaya novedad. ¿Por qué iba a ser JMeter diferente? Incluso si existe una alternativa, aprender a usarla también llevaría tiempo ¿no? Bueno, quizá no, puede que ya sepas utilizar esa alternativa.

Pruebas de Carga

Prueba de carga del Viaducto del Tajo, Cáceres, Spain (2019)
Prueba física de carga del Viaducto del AVE sobre el Tajo en Cáceres, España (2019)
https://estructurando.net/2019/03/25/prueba_de_carga_viaducto_del_tajo/

Cuando hablamos de software las pruebas de carga consisten en simular tráfico real sobre un servicio para observar su comportamiento. El objetivo es descubrir posibles bugs y puntos débiles del sistema que sólo aparecen cuando múltiples usuarios interactúan con nuestro servicio a la vez, bajo condiciones de estrés. Pero también puede ser un recurso interesante para medir el rendimiento en un escenario más o menos realista antes de llevarlo a producción.

Yo me voy a centrar en servicios HTTP así que las pruebas consistirán en lanzar peticiones en paralelo. No obstante las pruebas de carga pueden ser muy diferentes dependiendo del servicio con el que trabajemos: podríamos intentar procesar archivos muy grandes o bombardear el sistema con eventos en Kafka por ejemplo.

Pero como digo hoy nos vamos a centrar en servicios que atienden peticiones HTTP.

Locust: una alternativa a JMeter

Locust no se parece nada a JMeter. Sí, también se pueden hacer pruebas de carga con Locust, pero eso es prácticamente todo lo que tienen en común. En lugar de una interfaz llena de opciones Locust tiene código. Código Python para ser precisos. Y tiene una interfaz (bastante elegante por cierto) pero es para visualizar la prueba, no para configurarla.

Antes dije que quizá ya sabías usar esta nueva herramienta ¿verdad? Bien, pues si sabes algo de Python ya tienes un 90% de Locust bajo control.

¡Espera! No cunda el pánico, incluso si no sabes Python, mira qué aspecto tiene un test en Locust:

from locust import HttpUser, task


class ExampleTest(HttpUser):

    @task
    def home(self):
        self.client.request(method="GET", url="/home")

Esto es un ejemplo muy sencillo, aunque real, de un test que puedes ejecutar con Locust. Deja que lo guarde como example-test.py y lo ejecute, así podemos ver la interfaz.
Por cierto, para ejecutarlo necesitas instalar Locust. Es muy fácil así que creo que no merece la pena que nos paremos en eso, pero si tienes dudas puedes dejar un comentario.
Una vez tengamos Locust podemos lanzar el test con:

locust -f example-test.py

Eso arrancará Locust así que ahora podemos abrir http://localhost:8089 en el navegador y configurar el test:

Interfaz de Locust: configurar y lanzar el test
Interfaz de Locust: configurar y lanzar el test

Vamos a simular 10 usuarios. Es decir, un máximo de 10 peticiones concurrentes contra un servicio que tengo corriendo en local.
En este test nuestros «usuarios» van a lanzar peticiones a un endpoint /home sin parar:

Interfaz de Locust: pestaña principal
Interfaz de Locust: pestaña principal
Interfaz de Locust: pestaña gráficas
Interfaz de Locust: pestaña gráficas

Si os fijáis veréis que todas las peticiones están fallando. Normal, en realidad no tengo nada corriendo en local en el puerto 8080 (ha sido una pequeña mentira) pero era solo un ejemplo. Vamos a parar el test y a hacer uno un poco más sofisticado.

Creando un test en Locust

Imaginemos que tenemos una web tipo Twitter, con dos endpoints principales:

  • /timeline para ver todos los mensajes ordenados de más a menos recientes
  • /shoutout para escribir un nuevo mensaje que puedan ver todos los demás

Y vamos a intentar ver cómo se comportaría si hubiese muchos usuarios refrescando su timeline y escribiendo mensajes.

Nuestro test de Locust podría ser algo así:

from locust import HttpUser, task


class WebsiteUser(HttpUser):

    @task
    def timeline(self):
        self.client.get("/timeline")

    @task
    def shoutout(self):
        self.client.post("/shoutout", {"username": "testuser", "message": "hello!"})

Un usuario en Locust puede constar de varios @task. Mientras dure el test cada usuario irá ejecutando esas tareas aleatoriamente, sin parar, una tras otra. Así que si lanzamos este test la mitad de ellos estarán cargando su timeline y la otra mitad escribiendo mensajes, así todo el rato.

Vamos a cambiar eso, lo normal es que los usuarios lean el timeline con mucha más frecuencia en comparación con escribir nuevos mensajes. Podemos hacer eso fácilmente:

@task(10)
def timeline(self):
    self.client.get("/timeline")

Perfecto, ahora es 10 veces más probable que se ejecute esa tarea ya que por defecto las tareas tienen un peso de 1.
Vamos a añadir también un margen de tiempo entre tareas para simular usuarios un poco más humanos. Pongamos que entre 1 y 3 segundos por ejemplo:

wait_time = between(1, 3)

Pinta bien, qué tal si a cada usuario le ponemos un nombre y apellido ficticio y ya de paso también estaría bien que los mensajes no fuesen siempre «hello!», podrían ser más realistas. Por pedir…
Como soy un programador bastante vago voy a pillar un par de bibliotecas que me hagan el trabajo. Recordad, un test en Locust es código Python así que podemos usar cualquier biblioteca que queramos:

De lujo, con todos estos cambios el test quedaría así:

from locust import HttpUser, task, between
from lorem_text import lorem
import names


class WebsiteUser(HttpUser):
    username = None
    wait_time = between(1, 3)

    @task(10)
    def timeline(self):
        self.client.get("/timeline")

    @task
    def shoutout(self):
        message = lorem.words(10)
        self.client.post("/shoutout", {"username": self.username, "message": message})

    def on_start(self):
        self.username = names.get_full_name()

20 líneas de código, ¿no está mal a que no? Y es bastante sencillo de entender. Si alguna vez has usado JMeter te puedes imaginar lo que costaría replicar un test así. Y ya no te digo si el test lo hubiese hecho otra persona y a ti te tocase entenderlo y mantenerlo… ¡buena suerte con eso!

No obstante apenas hemos rozado la superficie. Con Locust podemos hacer validaciones sobre la respuesta, podemos tener usuarios con comportamientos diferentes dentro de un mismo test (WebsiteUser y MobileUser por ejemplo) para simular flujos distintos, también podemos etiquetar los @task para elegir cuáles lanzar en un momento dado, guardar cookies para cada usuario, ejecutar las pruebas desde varias máquinas (sí, como un cluster de Locust si necesitamos muchos miles de peticiones concurrentes), generar informes, CSVs con los resultados…

Así que sí, es una herramienta muy completa. Os animo a echar un ojo a la documentación oficial de Locust porque está realmente bien.
Por cierto, cabe mencionar que Locust es Software Libre (Licencia MIT). Podéis ver su repo en Github y darles una estrella.

Espero que os haya servido y que disfrutéis lanzando millones de peticiones contra vuestros servicios. *
Y si os ha gustado, tenéis cualquier duda o queréis apuntar algo dejad un comentario.

* Para un disfrute máximo se aconseja no lanzar las peticiones contra producción.


Artículo original (en inglés): JMeter Alternative: Load Testing with Locust

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para fines de afiliación y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad