jueves, 9 de febrero de 2012

JMeter VII: Ejecución remota de scripts en JMeter.


Una de las características que presenta JMeter es la posibilidad de realizar la inyección de carga de un script desde varios equipos , de manera que se pueda aumentar el número de peticiones concurrentes/simultáneas a los servidores de la aplicación


Para lograr esto , hay que seguir una serie de pasos. En principio, son sencillos y no debe de presentar dificultad para un usuario novato lograr la ejecución de un script simple usando una o varias máquinas inyectoras.

Conceptos y requisitos previos.

Debemos tener en cuenta una serie de requerimientos previos, de sentido común, sobre los equipos controlador, inyectores y la aplicación a analizar.

  1. El equipo controlador tendrá el script a probar, así como los ficheros de parámetros CSV Dataset definidos para la prueba
  2. El equipo controlador tendrá conectividad con, al menos un puerto TCP de las máquinas inyectoras. Si debe ser un puerto explícito, esto debe ser configurado en el fichero jmeter.properties, sección “remote_hosts”
  3. El equipo controlador almacenará todos los ficheros ,tanto de resultados de la prueba generados por los listener o escritores de datos simples, como los de log del comportamiento del jmeter (jmeter.log)
  4. Los equipos inyectores tendrán los mismos ficheros de datos definidos en el script en las mismas rutas que se definen en el script.
  5. Los equipos inyectores dispondrán al menos de un puerto TCP disponible con el que se comunicarán con el equipo controlador
  6. Los equipos inyectores tendrán visibilidad sobre la aplicación a probar
  7. Todos los equipos ejecutarán la misma versión de JMeter.
  8. Los equipos inyectores , previo al lanzamiento de las pruebas ,tendrán en ejecución el proceso –servicio- demonio jmeter-server (.bat en el caso de Windows .sh en el caso de Unix)
La configuración de los equipos a actuar como inyectores remotos, se efectúa en el fichero jmeter.properties. Este fichero se encuentra en la ruta

JMETER_HOME\bin\jmeter.properties

El fichero tiene una sección “remote hosts” ,donde se incluye ,tanto los equipos que actuarán como inyectores remotos, como la configuración de los puertos TCP por donde se realizará la comunicación

#---------------------------------------------------------------------------
# Remote hosts and RMI configuration
#---------------------------------------------------------------------------

# Remote Hosts - comma delimited
remote_hosts=192.168.0.1,192.168.10.2,192.168.45.3
#remote_hosts=localhost:1099,localhost:2010

# RMI port to be used by the server (must start rmiregistry with same port)
#server_port=1099

# To change the port to (say) 1234:
# On the server(s)
# - set server_port=1234
# - start rmiregistry with port 1234
# On Windows this can be done by:
# SET SERVER_PORT=1234
# JMETER-SERVER
#
# On Unix:
# SERVER_PORT=1234 jmeter-server
#
# On the client:
# - set remote_hosts=server:1234

# To change the default port (1099) used to access the server:
#server.rmi.port=1234

# To use a specific port for the JMeter server engine, define
# the following property before starting the server:
#server.rmi.localport=4000

# From JMeter 2.3.1, the jmeter server creates the RMI registry as part of the server process.
# To stop the server creating the RMI registry:
#server.rmi.create=false

# From JMeter 2.3.1, define the following property to cause JMeter to exit after the first test
#server.exitaftertest=true

Tabla 1. jmeter.properties.Sección configuracion inyección remota

Invocación de inyectores remotos y ejecución de scripts


Una vez cumplimentados los requisitos y preparativos citados, la ejecución remota de scripts es sencilla. Dependiendo de si lo hacemos desde el entorno gráfico o desde línea de comandos.
Vamos a explicar ambos casos, aunque ,como cualquier que haya leido este blog, se recomienda vivamente para pruebas con niveles de carga altos, usar la opción desatendida.

Modo GUI

El modo del entorno gráfico, tiene una ventaja respecto al modo desatendido y es que permite incrementar la carga a lo largo de los inyectores a medida que avanza la prueba.

Para ello, lo que habría que hacer es ir seleccionando inyector remoto, tras inyector remoto , hasta que se llegue a los puntos de saturación o límites de carga buscados


Ilustración1. Opciones de inyección remota en modo GUI JMeter.

Modo desatendido (línea de comandos)

La manera de invocar la opción de inyección remota se haría con la siguiente sentencia y parámetros

JMETER_HOME\bin\jmeter -n -t RUTA_SCRIPT -R IP[,IP2,IP3...IPN] -X

Donde
  • RUTA_SCRIPT es la ruta al fichero .jmx del script de prueba
  • IP [,IP2,IP3...IPN] es el listado de las IP de los equipos inyectores
  • -X es opcional.Indica que ,una vez finalizada la prueba, cierre el servidor de JMeter en los equipos inyectores
Uso de variables en pruebas de carga con inyección remota

Como todos sabemos, uno de los problemas que se presentan con JMeter, es la ejecución de scripts de manera remota. La razón básica es porque no permite la exportación de las variables del equipo que ejecuta el script .jmx a los equipos que actúan como inyectores remotos

Ejemplo.

Ilustración 2. Variables definidas por usuario en script JMeter


Lógicamente, uno esperaría que al ejecutar un script donde se definen estas variables, estas tomaran los valores definidos en todos los equipos de prueba. Pero, ignoro la razón técnica, no se conservan dichas asignaciones. Por lo que las referencias en los equipos, aparecen con las cadenas literales del nombre de la variable.

Así, la variable ${FECHA}, que en un escenario ejecutado desde el equipo local tomaría el valor del año-mes-día , aparecerá en las invocaciones que las use como la cadena literal ${FECHA}

Para solventar esto, por puro método de prueba y error, hemos observado que, si uno quiere usar variables compartidas entre todos los equipos inyectores debe incluir la llamada con el formato

${__property(${expresión a asignar a la variable})}

Es decir: el uso de variables en escenarios remotos debe efectuarse mediante invocación explícita con la llamada a la función ${__property(…)}.

Para el caso de FECHA, la manera de incluir en una petición de muestreador http (o en el nombre de un fichero) sería algo como

${__property(${__time(YMD)})}.


Esto, estimado lector, puede ser algo raro si se ha parado a leer alguna vez la definición de las funciones .En concreto la relativa a property


“The property function returns the value of a JMeter property. If the property value cannot be found, and no default has been supplied, it returns the property name. When supplying a default value, there is no need to provide a function name - the parameter can be set to null, and it will be ignored.”

Es decir, la función property evalúa una propiedad de JMeter definida en los ficheros de la aplicación jmeter.properties, user.properties o el definido en la invocación por línea de comandos con el parámetro
–Gficherousuario.properties

Sin embargo , si uno intenta llevar a cabo la implementación de parámetros a usar dentro de un fichero “.properties” ,con vistas a evaluarlos como variables con la función property, el resultado es nulo. La función no implementa la variable pasada como parámetro a la función y produce el mismo resultado en su invocación explícita que el que presenta si la definimos como una variable definida por usuario en la construcción del script.

Es decir, según la imagen anterior de “Variables definidas por usuario”, una invocación en un muestreador http, que utilizara la variable YEAR, que ha sido definida como una invocación a la función time con formateo de el año ( ${__time(Y)} )
Ilustración 3. Peticion HTTP con variable de usuario

Debería haber sido invocada como

Ilustración 4. Petición HTTP con variable remota

Por lo tanto, resumiendo para el uso de pruebas distribuidas con JMeter debemos de tener en cuenta:

  • Las variables que definimos dentro del script de JMeter , deben ser explicitadas con la evaluación de la expresión que se desee mediante la función property
  • Debemos recordar que la carga lanzada en varios inyectores remotos no se distribuye, sino que se ejecuta tal y como se define en el script.
  • Es necesario invocar explícitamente a los equipos inyectores desde la línea de comandos con el parámetro –R IP1,IP2,IP3…IPN
  • La inclusión de variables de un CSVDataset, forzosamente incluirá que todos los equipos inyectores tengan el mismo fichero de datos, en la misma ruta en la que fue definido en el script.
  • Los equipos que actúen como inyectores remotos deben estar ejecutando el programa/servicio/demonio jmeter-server (en Windows jmeter-server.bat) y debe ser la misma versión de JMeter que el equipo que actúe como controlador del escenario.

Conclusión
Hemos llegado de momento, a finalizar esta exposición de conocimiento sobre JMeter. Quiero agradecer a mi compañero Federico Yansón el tiempo y esfuerzo dedicados, para lograr a comprender cómo lanzar pruebas remotas en JMeter de la manera más completa posible.






viernes, 13 de enero de 2012

Unix (IV) .Monitorizando el sistema con lsof

Como indicamos en la última entrada, en esta entrega vamos a hacer un desglose de algunas de las opciones útiles de cara a detectar cuellos de botella en el sistema del comando lsof.
Este comando , permite generar un listado informativo acerca de los descriptores de ficheros asociados a uno o varios procesos. Esto entronca con la filosofía de Unix en la que el principio es que todo lo que hay en el sistema es un fichero

Ilustración 1. Man lsof v.4.76. Plataformas sobre las que existe versión de lsof

Mediante este comando, podemos, entre otras cosas,
  • Verificar los procesos que generan un fichero específico
  • Comprobar las conexiones TCP abiertas por un proceso
  • Localizar los descriptores de fichero de varios procesos simultáneamente.
Sin embargo, un vistazo a la ayuda incluida en sistemas operativos como Solaris o HP-UX acerca del comando lsof no produce muchas certezas, sino más bien una expresión de desconcierto notable. Por ejemplo ,en Solaris 2.9, vemos que el man de dicho comando tiene una extensión de 3696 líneas (3432 en HP-UX v 11.23) , frente a las 726 del comando ps o las 594 de netstat.

Debido a la complicación del comando, he llegado a ver cómo se usa el comando pfiles como la “versión del pobre” a la hora de encontrar en qué puertos están escuchando procesos https://gist.github.com/227926

Sin embargo, dada la versatilidad de lsof, es más interesante hacer un breve recorrido por algunas de las opciones que permite dicho comando y una breve descripción de su utilidad. En este caso, sólo voy a adaptar la documentación que conseguí años ha en http://www.physiol.ox.ac.uk/Computing/Online_Documentation/lsof-quickstart.txt


Hemos de recordar también que la ejecución del comando lsof sobre procesos o sistemas de ficheros requiere de operaciones privilegiadas. Tanto en el sentido de tener permisos sobre dichos procesos o ficheros , como en el uso de operaciones de sistema a bajo nivel en el núcleo del mismo. Lo cual si que puede afectar a la estabilidad y rendimiento del sistema


Ejecución de lsof.
Localización de los procesos que tienen ficheros abiertos en un sistema de ficheros
$lsof /FILESYSTEM

Ilustración 2. ejecución de lsof sobre un sistema de ficheros

Número de descriptores de fichero abiertos por un proceso

$lsof –p PID

Lógicamente, esto se combina con la limitación de descriptores definidos para un sistema

Ilustración 3. Ejecución de lsof para averiguar el número de descriptores de fichero abiertos por un proceso

Podemos observar las sesiones abiertas en terminal (TYPE CHR , /dev/pts/6) por el proceso con PID buscado

Descriptores abiertos por un grupo de programas/ instancias de un servidor

$lsof – c CADENA_TEXTO

Hace operaciones suma lógica sobre distintas opciones , con -a

$lsof –p PID –au USER :

Lista los descriptores de fichero usados por un protocolo

$lsof –i

Ejemplo:

$lsof –i tcp
Ilustración 4. Ejecución lsof .Descriptores de fichero asociados al protocolo TCP
Podemos extender esto a las conexiones abiertas por un proceso específico a un puerto
$lsof -i:ssh
Ilustración 5. Ejecución de lsof sobre un puerto.

Recomendaciones sobre el rendimiento y estabilidad en uso de lsof.

Según se indica en el documento de referencia, el uso del comando lsof posee ciertas opciones recomendables de uso, para evitar que ejecute operaciones que puede afectar a la estabilidad del núcleo

$lsof –b

Asimismo también se puede ejecutar con opciones que permiten mejorar su rendimiento y tiempo de respuesta de cara a operaciones sobre red

$lsof –n : Evita lookups de nombre de host
$lsof –y: evita lookups de nombre de servicio
Nuestra próxima entrega:De lo global a lo sumamente especifico: sar y ptruss

miércoles, 14 de diciembre de 2011

Unix III:Monitorizando sistemas

Como el lector ya habrá visto, este es un blog especializado en pruebas de rendimiento y calidad. Por lo que en esta entrega del serial del Unix, vamos a mostrar una pequeña guía de comandos de monitorización específicos.
Introducción.
Habitualmente, las monitorizaciones realizadas durante las pruebas de carga, deben de incluir monitorizaciones sobre
- El estado global de la máquina
- Estado específico de los procesos que dan servicio a la aplicación
- Estado de las comunicaciones
Las monitorizaciones se pueden realizar
  • bien mediante una herramienta específica de terceros (léase Introscope, HP Diagnostics) las cuales permiten monitorizar tanto global, como especializadamente la máquina y procesos asociados. Tienen el problema de ser herramientas de pago y que precisan de la instalación de “sondas” ,agentes e incluso una infraestructura específica.
  • Mediante comandos del sistema operativo. Esto permite realizar monitorizaciones sin gasto adicional , ni infraestructuras específicas, aunque tiene limitaciones y precisan en ocasiones de permisos mediante sudo
Tanto las herramientas externas, como los comandos generan un consumo adicional en las máquinas a monitorizar. Es conveniente por lo tanto, ejecutar estos comandos en intervalos adecuados a la duración de la prueba para tomar medidas sin que afecten en demasía al comportamiento del sistema durante la ejecución.

Memoria compartida.
Habitualmente dentro de la categoría de monitorización mediante comandos usamos clásicos como vmstat o ps ,bien formateado para generar valores específicos (ps –eo PID,PPID,pcpu,rss,args ) o mediante parámetros de ejecución específicos de una versión de ps (ps –aux).
Sin embargo, tenemos algunas limitaciones con métricas recogidas por este comando, como por ejemplo, el comportamiento de la memoria asociada a un proceso, cuando se comparte memoria entre distintos procesos de una instancia de Oracle.
En estos casos, vemos que la memoria que refleja un ps sobre uno de dichos los procesos, muestra la suma total de la memoria compartida (RSS).
Ilustración 1. Salida del comando ps con cadena de formato
En este caso, monitorizado sobre un proceso con PID específico (7076), nos indica que, el campo RSS ocupa 597880 KBytes. Esta memoria es la compartida por todos los procesos de la instancia de Oracle.

Sin embargo, podemos usar el comando pmap, el cual nos indica de manera desglosada el consumo de memoria compartida, y los permisos que se tiene sobre cada página de la memoria asociada el proceso. Sin embargo, este es un comando privilegiado, que sólo puede ejecutar el usuario que está lanzando el proceso que se quiere monitorizar


$ sudo -u /bin/pmap -x 7076
7076: oracleIESECFI0 (LOCAL=NO)
Address Kbytes RSS Anon Locked Mode Mapped File
0000000100000000 51992 50312 - - r-x-- oracle
00000001033C4000 720 680 64 - rwx-- oracle
0000000103478000 504 392 160 - rwx-- [ heap ]
0000000380000000 544768 544768 - 544768 rwxsR [ ismhmid=0x1000032 ]
FFFFFFFF7D100000 64 64 32 - rwx-- [ anon ]
FFFFFFFF7D200000 640 192 - - r-x-- libm.so.2
FFFFFFFF7D300000 16 16 16 - rw--R [ anon ]
FFFFFFFF7D350000 80 80 40 - rw--R [ anon ]
FFFFFFFF7D366000 40 40 32 - rw--R [ anon ]
FFFFFFFF7D39E000 40 24 8 - rwx-- libm.so.2
FFFFFFFF7D400000 56 16 - - r-x-- libmd.so.1
FFFFFFFF7D50E000 8 8 - - rwx-- libmd.so.1
FFFFFFFF7D600000 24 24 - - r-x-- libm.so.1
FFFFFFFF7D704000 8 8 8 - rwx-- libm.so.1
FFFFFFFF7D800000 8 8 - - r-x-- libkstat.so.1
FFFFFFFF7D902000 8 8 - - rwx-- libkstat.so.1
FFFFFFFF7DA00000 32 24 - - r-x-- librt.so.1
FFFFFFFF7DB08000 8 8 8 - rwx-- librt.so.1
FFFFFFFF7DC00000 32 32 - - r-x-- libaio.so.1
FFFFFFFF7DD08000 8 8 8 - rwx-- libaio.so.1
FFFFFFFF7DE00000 944 760 - - r-x-- libc.so.1
FFFFFFFF7DF00000 64 32 24 - rwx-- [ anon ]
FFFFFFFF7DFEC000 64 64 48 - rwx-- libc.so.1
FFFFFFFF7DFFC000 8 8 8 - rwx-- libc.so.1
FFFFFFFF7E000000 8 8 - - r-x-- libdl.so.1
FFFFFFFF7E102000 8 8 8 - rwx-- libdl.so.1
FFFFFFFF7E200000 32 16 - - r-x-- libgen.so.1
FFFFFFFF7E308000 8 8 - - rwx-- libgen.so.1
FFFFFFFF7E400000 56 40 - - r-x-- libsocket.so.1
FFFFFFFF7E500000 8 8 8 - rwx-- [ anon ]
FFFFFFFF7E50E000 16 16 16 - rwx-- libsocket.so.1
FFFFFFFF7E600000 688 272 - - r-x-- libnsl.so.1
FFFFFFFF7E700000 8 8 8 - rwx-- [ anon ]
FFFFFFFF7E7AC000 64 64 48 - rwx-- libnsl.so.1
FFFFFFFF7E7BC000 32 32 8 - rwx-- libnsl.so.1
FFFFFFFF7E800000 5384 1704 - - r-x-- libjox9.so
FFFFFFFF7EE00000 8 8 8 - rwx-- [ anon ]
FFFFFFFF7EE40000 376 280 112 - rwx-- libjox9.so
FFFFFFFF7EE9E000 16 - - - rwx-- libjox9.so
FFFFFFFF7EF00000 32 24 - - r-x-- libskgxn9.so
FFFFFFFF7F000000 8 8 8 - rwx-- [ anon ]
FFFFFFFF7F006000 8 8 - - rwx-- libskgxn9.so
FFFFFFFF7F100000 8 8 - - r-x-- libskgxp9.so
FFFFFFFF7F200000 8 8 - - rwx-- libskgxp9.so
FFFFFFFF7F300000 8 8 - - r-x-- libodmd9.so
FFFFFFFF7F400000 8 8 8 - rwx-- libodmd9.so
FFFFFFFF7F500000 24 16 8 - rwx-- [ anon ]
FFFFFFFF7F600000 176 176 - - r-x-- ld.so.1
FFFFFFFF7F700000 8 8 8 - rwx-- [ anon ]
FFFFFFFF7F72C000 16 16 16 - rwx-- ld.so.1
FFFFFFFF7F78C000 8 8 - - rwxs- [ anon ]
FFFFFFFF7FFF0000 64 64 40 - rw--- [ stack ]
---------------- ---------- ---------- ---------- ----------
total Kb 607224 600408 760 544768


Tabla 1. Salida del comando pmap sobre un proceso de Oracle
Podemos ver que la columna “Locked mode” indica el modo de acceso al segmento de memoria indicado. En este caso, debemos fijarnos sólo en aquellos valores de la columna que sean
  • rwx-
  • rw---
El valor de la columna que incluya el campo “s”, indica memoria “shared” ,es decir, compartida.
El resto de columnas de la muestra, reflejan memorias de uso compartido con el resto de procesos lanzados de la instancia de Oracle a la que pertenece.
Como idea, un posible script para sacar la monitorización de memoria usada en exclusiva por un proceso sería

sudo -u USUARIO /bin/pmap -x $1 | grep -v Address | awk ' BEGIN {
primero=1
}
/:/ {
if(primero==1) {
primero=0;
} else {
print “tiempo,"$1, proc, ",", suma;
}
suma=0;
proc=$0;
}
$6 !~ /s/ {
if (($6 =="rw---") || ($6 =="rwx--"))
suma=suma+$3;
}
END {
print “tiempo,”,$1,proc,",",suma;
}'
Donde USUARIO sería el usuario que ejecuta el proceso a monitorizar y $1 el pid del proceso.


Threads en Solaris.Siguiendo el hilo
La monitorización específica de procesos también puede extenderse al uso de los threads activos . Para ello un comando útil bajo Unix (versión Solaris) es prstat

Ilustración 2. Salida del comando prstat sobre un usuario ($prstat -u hzweb)
El comando admite varias opciones de ejecución , dependiendo de si se quiere
  • monitorizar un proceso/s específico/s $ prstat –p PIDLIST
  • monitorizar los procesos lanzados por un/os usuario/s $ prstat –U UIDLIST
  • monitorizar los threads (en Solaris “Light weight processes”) en ejecución ($ prstat –L –U UIDLIST –p PIDLIST
Este último caso es quizás el más interesante para observar el comportamiento de todos los threads asociados al proceso. Ya que el número de threads asociados puede superar el tamaño de líneas del terminal , lo ideal es lanzar el comando con alguna de las opciones de ordenación que permiten establecer
También podemos conocer el número total de threads asociados, usando la opción “-t” con el comando
$ prstat -t -L -U UIDLIST

Ilustración 3.Salida del comando prstat sobre procesos de dos usuarios
El comando que se puede usar para conocer cuáles son los hilos que están consumiendo más recurso de procesador (opción “-s cpu)” y en qué CPUs físicas
$ prstat –L –p PID –s cpu

Ilustración 4.Salida del comando prstat sobre los threads de un proceso de usuario

La columna STATE permite distinguir los procesos en ejecución en un procesador determinado (cpuN), en cola de ejecución (run) en espera (sleep) o, llegado el caso desechados (zombi) o parados (stop).
En resumen ,este comando, nos permite observar

  • Número de threads asociados a un proceso
  • Consumo desglosado por thread
  • Balanceo y distribución de los threads en un sistema multiprocesador
  • Bloqueos presentes en los threads

Con estos valores, podemos por lo tanto detectar si un sistema gestionan adecuadamente los hilos de ejecución de una JVM y sus consumos asociados (por ejemplo ,en un servidor weblogic o SunOne )
En la próxima entrega tratermos sobre la localización de los procesos que generan elevado consumo de espacio en disco (lo cual afecta decisivamente al rendimiento) ,así como el agotamiento de descriptores de fichero: lsof. ¡El comando con el manual incomprensible!