En este post se comparará una nueva opción de librerías SSH de Python, ssh2-python, con Paramiko para demostrar su respectivo rendimiento con especial énfasis en la concurrencia y las peticiones no bloqueantes.
Al buscar una librería SSH de Python para utilizar en una aplicación, no existen muchas opciones. De hecho, la única librería de propósito general que ha estado, hasta ahora, disponible es Paramiko, que implementa la API SSH2 en código Python.
Para bien o para mal -es indudable que la librería ha ayudado a un gran número de personas con requerimientos similares, este autor incluido ya que ha sido históricamente la única opción- Paramiko ha sido el estándar de facto para las librerías SSH de Python hasta ahora.
Sin embargo, deja mucho que desear desde el punto de vista del rendimiento, la estabilidad y el consumo de recursos.
Por suerte, ahora hay otra opción en forma de ssh2-python
que se basa en la biblioteca C libssh2.
Muchas aplicaciones de automatización como Ansible y Fabric hacen uso de Paramiko y probablemente estarían interesados en saber que ahora existen otras opciones. Esperemos que este post ayude a estas y otras aplicaciones a determinar qué biblioteca es más adecuada.
Comparación de rendimiento
ssh2-python es una nueva biblioteca SSH de Python basada en la biblioteca C libssh2. No tiene dependencias y proporciona ruedas binarias para Linux, OSX y Windows con libssh2
incluido.
paramiko está escrito en Python y hace uso de dependencias de extensiones nativas como la criptografía. Soporta Linux, OSX y Windows y sus dependencias nativas proporcionan ruedas binarias.
Configuración de la prueba
La prueba está utilizando la biblioteca estándar de Python threading
para la concurrencia. En una entrada posterior del blog se examinará el rendimiento sin bloqueo en las bibliotecas a través de la biblioteca de co-rutinas gevent.
Aunque el threading no es un buen modelo para escalar la concurrencia de la E/S de la red, es el único modo de concurrencia que Paramiko soporta de forma nativa y es lo que utilizan aplicaciones como Ansible. Otros proyectos como parallel-ssh, del que también soy autor, utilizan el monkey patching de gevent para hacer que Paramiko sea concurrente de forma cooperativa. Eso también viene con desventajas significativas que el actualmente en desarrollo ssh2-python
basado en un cliente nativo no-bloqueante pretende resolver. Vea el pull request de trabajo en progreso para más detalles sobre eso.
El script de prueba crea sesiones SSH en paralelo a un servidor SSH (OpenSSH) a través de un dispositivo de bucle de retorno (localhost), comenzando desde uno y aumentando en uno cada iteración hasta la finalización. Esto muestra cómo las dos bibliotecas escalan a medida que el número de sesiones paralelas aumenta.
El número máximo de hilos y por lo tanto de sesiones paralelas se establece en 50. Todas las pruebas se realizan en una CPU de cuatro núcleos físicos.
En todas las pruebas se utiliza la última versión disponible de cada librería, 2.2.1
y 0.5.3
para Paramiko y ssh2-python respectivamente. Para ssh2-python
se utilizó un libssh2
embebido, última versión disponible 1.8.0
. Todas las pruebas realizadas bajo Python 2.7
.
Se comparan las librerías en seis operaciones SSH distintas, así como el tiempo total empleado desde el inicio hasta el final por sesión SSH.
Las seis operaciones comparadas son:
- Inicialización de la sesión y autenticación con el agente SSH (
auth
) - Canal abierto (
channel_open
) - Canal ejecutado (
execute
) - Canal leído -. (
channel_read
) - Cierre de canal y obtención del estado de salida (
close_and_exit_status
) - Lectura de SFTP (
sftp_read
) - Inicialización y autenticación de la sesión: realiza el handshake con el servidor SSH y se autentifica a través de un agente SSH del sistema.
- Abrir canal – se requiere abrir un nuevo canal SSH en una sesión autenticada por cada comando remoto a ejecutar.
- Ejecutar canal – ejecutar el comando shell
cat
en un archivo estático - Lectura de canal – recuperar la salida del comando.
- Cierre de canal – se realiza cuando el comando ha terminado para recoger el estado de salida.
- Lectura de SFTP – inicializa la sesión de SFTP, abre la manija del archivo remoto, lee los datos. Los datos leídos no se escriben en ninguna parte.
- Script de prueba de rendimiento
Una explicación rápida de lo que hacen estas operaciones:
El archivo estático que se utiliza para el cat
comando remoto es un 26KB
archivo de licencia del repositorio ssh2-python.
El archivo utilizado para la lectura del SFTP es un archivo tar comprimido de 11MB.
Todas las duraciones están en milisegundos (ms).
Resultados de la prueba
Los gráficos muestran los valores medios por intervalos de treinta segundos.
Gráfica de todas las operaciones
Aquí se muestra cómo se comparan las dos bibliotecas en las cinco operaciones separadas más el tiempo total empleado.
Haga clic en la imagen para verla más grande.
Como se puede ver arriba, las duraciones de Paramiko están dominadas por la lectura de SFTP, la autenticación y la apertura del canal, que siguen aumentando a medida que la concurrencia aumenta.
En el caso de ssh2-python, el mayor tiempo se emplea en la lectura de SFTP, seguido de la autenticación y la apertura del canal.
Ambas bibliotecas muestran picos intermitentes de duración que son esperables dado el número de hilos y las llamadas de bloqueo utilizadas.
Operaciones individuales
Tiempos de las operaciones sin incluir SFTP y el total para mostrar una visión más cercana del resto de los tiempos.
Haga clic en la imagen para ver una versión más grande.
Rendimiento relativo
Rendimiento relativo de las medias de las operaciones medianas de las dos bibliotecas para la duración de la prueba. Las duraciones de lectura total y SFTP son para los promedios.
Por ejemplo, si una operación Paramiko
fuera dos veces más rápida que la operación equivalente ssh2-python
, su rendimiento relativo sería x0.5
de ssh2-python mientras que duraciones idénticas resultarían en x1
rendimiento relativo.
Operación | Paramiko | ssh2 | Paramiko/ssh2-python diferencia relativa |
---|---|---|---|
auth |
1.16 seg | 675 ms | x1.71 |
channel open |
1.248 seg | 141 ms | x8.85 |
channel read |
78 ms | 29 ms | x2.68 |
close and exit status |
4 ms | 1 ms | x4 |
execute |
24 ms | 3 ms | x8 |
sftp_read |
19.35 seg | 1.13 s | x17.12 |
total |
20,82 s | 2.04 s | x10.2 |
Postfacio
En total, ssh2-python
se muestra considerablemente más rápido, particularmente en operaciones pesadas como SFTP, para las que la lectura es unas x17
veces más rápida de media en comparación con Paramiko. Otras operaciones que se benefician especialmente son la apertura de canales, x8
más rápida, y la ejecución, también x8
. En total en el caso de prueba anterior ssh2-python
se muestra como un orden de magnitud (x10
) más rápido de media.
También hay que tener en cuenta que el consumo de memoria no está graficado, pero es significativamente menor con ssh2-python
como era de esperar al estar basado en una librería nativa.
En cuanto a que las comparaciones entre una biblioteca SSH de código Python puro y una basada en una biblioteca C son «injustas» – la biblioteca de código Python «puro» también utiliza extensiones de código nativo para las operaciones de criptografía, lo que hace que el argumento sea discutible. Nótese cómo la operación con menor diferencia de rendimiento es la autenticación, que se maneja mayormente con extensiones de código nativo en Paramiko.
Tampoco es necesario escribir una implementación de la API SSH de bajo nivel puramente en Python. De hecho, hay muchas razones para no hacerlo, como demuestran estos resultados.
Apéndice
El script de prueba utilizado puede encontrarse a continuación:
El script de prueba escribe datos de duración en una InfluxDB local a través de su servicio Graphite utilizando una plantilla measurement.field*
.
Los gráficos de arriba fueron generados por Grafana, como consultados desde InfluxDB por InfluxGraph usando la misma configuración de plantilla que InfluxDB.
Para replicar los resultados, tenga cuidado de usar las versiones exactas de las bibliotecas utilizadas aquí, incluyendo libssh2
. Las versiones más antiguas de libssh2
no son completamente seguras para los hilos y causarán caídas.
Descargo de responsabilidad – Aunque soy el autor de ssh2-python
, no tengo ningún interés en libssh2
ni en Paramiko
. Mi interés en su respectivo rendimiento es para su uso en parallel-ssh.