domingo, 15 de abril de 2012

Performance analysis: Understanding the vmstat command output

Todo sysadmin que se digne de tal, tiene la capacidad de optimizar sus equipos para soportar las distintas situaciones de carga a las que se puede ver sometido o resolver bottlenecks que puedan llegar a producirse. Existen varias situaciones que pueden sobrecargar un equipo, y siempre es útil comprender cuando se producen y los procedimientos para remediarlas.
La performance es un área difícil de comprender, pero fundamental a la hora de administrar sistemas.
Durante este post vamos a intentar comprender la salida de vmstat(8), un comando disponible en la mayoría de los sistemas Unix, en nuestro caso vamos a enfocarnos en Linux, y analizaremos algunas situaciones las cuales pueden llevar a una sobrecarga de performance.
Existen numerosas herramientas para analizar esto, pero muchas de ellas dan su punto de partida en vmstat(8), para luego enfocar en herramientas más específicas como iostat(8), mpstat(1), o sar(1). Para un mayor entendimiento de performance y las herramientas utilizadas les recomiendo el blog de Brendan Gregg, o también leer el libro "Solaris™ Performance and Tools: DTrace and MDB Techniques for Solaris 10 and OpenSolaris" por Richard McDougall, Jim Mauro y Brendan Gregg

Según su propia manpage vmstat(8): reports information about processes, memory, paging, block IO, traps, disks and cpu activity.

Nosotros enfocaremos este post en entender la información proporcionada por los procesos, la memoría, el I/O y la utilización de la CPU. Pero antes de entrar de lleno en esto, hay algunas cosas que tenemos que comprender primero.

Los sistemas operativos modernos, trabajan en distintos modos a fin de garantizar una mayor seguridad y proteger las aplicaciones unas de otras. Actualmente en PC x86/x86_64 podemos definir algunos modos de operación como es el modo real (utilizado durante el booteo), modo protegido (proteger la memoría virtual), long mode (modo de operación 64 bits), modo virtual 8086 (retrocompatibilidad binaria con antiguos procesadores). El modo protegido, planea una interfaz de llamadas al sistema (syscall) para poder acceder al hardware, evitando de esta manera que un usuario tenga acceso directo al I/O y sea el kernel, el responsable de realizar estas actividades, es por ello que fueron diseñados básicamente un sistema de privilegios basados en rings, el ring0 también conocido como kernel mode, y el ring 3, denominado userspace.


Kernel mode: En este modo es el kernel, y sus drivers (módulos) quienes pueden ejecutarse, se reserva un espacio de memoria virtual donde solo puede estar mapeado el kernel, sus extensiones y como dije anteriormente sus drivers.

Userspace: Solo se ejecutan las aplicaciones de usuario, para acceder al kernel mode se necesitan efectuar llamadas al sistema, reserva un rango de direcciones de memoria donde las aplicaciones y librerías (o bibliotecas, para los puristas) del userspace (denominadas userland) pueden ejecutarse.

Veamos un ejemplo sencillo en ANSI C y luego en ensamblador para INTEL x86 sobre como el sistema operativo hace uso de esta interfaz para poder realizar un sencillo exit.


Las llamadas al sistema en Linux, se realizan de una manera bastante sencilla, cada una de ellas es mapeada a un código hexadecimal, por ejemplo la syscall 0x1h (1 en decimal), representa exit, la syscall 0x4h (4 en decimal), representa a write y 0x37h (55 en decimal) es kill.
Este código hexadecimal es posicionado en un registro de propósito general en la CPU, en el caso de un procesador 32 bits este registro es %EAX o %RAX si contamos con 64 bits.
Luego los siguientes argumentos que lleva la syscall son posicionados en otros registros de propósito general como es el caso de %EBX, %ECX y %EDX (en 32 bits). Si miramos dentro de la manpage de _exit(2) podemos ver:

NAME
       _exit, _Exit - terminate the calling process

SYNOPSIS         top
       #include <unistd.h>
       void _exit(int status);

       #include <stdlib.h>
       void _Exit(int status);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
       _Exit():
           _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L;
           or cc -std=c99

DESCRIPTION
       The function _exit() terminates the calling process "immediately".  Any open
       file descriptors belonging to the process are closed; any children of the
       process are inherited by process 1, init, and the process's parent is sent a
       SIGCHLD signal.
       The value status is returned to the parent process as the process's exit
       status, and can be collected using one of the wait(2) family of calls.
       The function _Exit() is equivalent to _exit().

CONFORMING TO
       SVr4, POSIX.1-2001, 4.3BSD.  The function _Exit() was introduced by C99.

NOTES
       For a discussion on the effects of an exit, the transmission of exit status,
       zombie processes, signals sent, etc., see exit(3).

       The function _exit() is like exit(3), but does not call any functions
       registered with atexit(3) or on_exit(3).  Whether it flushes standard I/O
       buffers and removes temporary files created with tmpfile(3) is implementation-
       dependent.  On the other hand, _exit() does close open file descriptors, and
       this may cause an unknown delay, waiting for pending output to finish.  If the
       delay is undesired, it may be useful to call functions like tcflush(3) before
       calling _exit().  Whether any pending I/O is canceled, and which pending I/O
       may be canceled upon _exit(), is implementation-dependent.

       In glibc up to version 2.3, the _exit() wrapper function invoked the kernel
       system call of the same name.  Since glibc 2.3, the wrapper function invokes
       exit_group(2), in order to terminate all of the threads in a process.

SEE ALSO
       execve(2), exit_group(2), fork(2), kill(2), wait(2), wait4(2), waitpid(2),
       atexit(3), exit(3), on_exit(3), termios(3)
Por lo cual, podemos apreciar que _exit, cumple la función de terminar un proceso de manera inmediata, y además cualquier file descriptor (fd) abierto es cerrado de manera inmediata. Esto es logrado mediante un wrapper que invoca a exit_group(2) en la glibc (mayor a 2.3), haciendo la systemcall exit(2). exit_group(2) es utilizada para que todos los threads de un proceso sean terminados. En nuestro ejemplo a exit debemos pasarle un argumento, del tipo entero (int de ahora en más), para retornar un exit status, que posteriormente podemos comprobarlo en el shell instanciando a la variable $?.

Esto, en ensamblador INTEL x86 quedaría así:


Si miramos este código con algo de atención podemos notar que lo primero que se efectua es posicionar el valor 0x1h, dentro del registro %EAX, que se está refiriendo a la syscall exit(2), en el segundo paso, posiciona el exit status (0), dentro del registro %EBX, y paso final llama a la interrupción 0x80h (int 0x80). Esta interrupción es muy importante en Linux, y es la que efectúa el switcheo de un código ejecutándose binariamente en el userspace, a pasar a ejecutarse en el kernel mode. Esto, se lo conoce como Context switching.


Por lo cual, podemos concluir que una aplicación de usuario (userland) se ejecuta en el Userspace, y para poder ejecutarse en kernel mode, necesita hacer uso de llamadas al sistema (syscall), que es provista por el kernel, quién el, junto a los drivers será capaz de interactuar directamente con el hardware. Entonces, podemos plantearnos el siguiente interrogante, ¿que pasaría si notamos un consumo elevado de la CPU, y podemos apreciar una enorme cantidad de context switchs (CS)?. Bien, podemos deducir que una aplicación del userland, es quien esta generando un importante número de llamadas al sistema y debemos revisar nuestros procesos que se estén ejecutando a ver quién es el responsable.

Procesos y threads:
Podemos definir a un proceso como una instancia de un programa en ejecución que a reservado memoria para su uso, todo proceso al momento de su creación lleva asignado un número de PID (Process ID), y un número de PPID (Process Parent ID), esto es debido a que en Linux, y muchos otros Unix, los procesos nacen por medio de una familia de syscalls denominada fork (fork, vfork). Estos procesos, crean en la memoria virtual (VM) ciertos segmentos, que luego mediante el sistema de IPC (Inter Process Comunication), se establecen permisos de acceso. Entre estas secciones podemos encontrar la sección .TEXT donde se encuentra mapeado la porción de código en ejecución a nivel binario, .STACK, una estructura bastante dinamica en forma de pila que hace uso del método LIFO (Last In, First Out) sobre los datos que puede almacenar, el .HEAP, .BSS, entre otras. Los procesos, tienen la particularidad de que su segmento reservado de memoria puede ser accedido únicamente por ellos (a menos que el mismo mapee una porción como SHM). Por su parte los threads, los cuales son procesos livianos, comparten entre ellos la misma "imagen" de memoría, de forma que pueden acceder indistintamente a ellos, dando lugar en los peores casos a race conditions y deadlocks. Para evitar esto, existen algoritmos que emplean por ejemplo la exclusión mutua y se hace uso de los semáforos. Veamos un pequeño ejemplo a continuación, en el cual varios trheads intentan acceder a un mismo recurso, en el mismo instante de tiempo:


Estos trheads, de los cuales hablamos, en un sistema monoprocesador (1 CPU), no tienen demasiado sentido, ya que solamente puede ejecutarse uno por vez, y es el scheduler del sistema operativo quien se encarga de asignarles un tiempo de CPU para que los mismos se ejecuten, en caso de que un proceso haga uso de mayor cantidad de tiempo a la que le fue asignada el mismo será penalizado (mediante semáforos), teniendo que esperar N cantidad de tiempo antes de poder volver a ingresar nuevamente a la CPU. Esto se logra mediante la utilización de el algoritmo Round Robin, el cual asigna tiempos iguales de CPU a todos los procesos, pero en caso de que uno se exceda aplica penalizaciones. En caso de equipos con múltiples cores, esta limitación se ve superada, pero siempre que tengamos una sola CPU física, solo un proceso por vez podrá correr. Para que varios procesos puedan correr concurrentemente necesitamos tener un equipo con múltiples unidades de procesamiento. Pero también suele pasar que muchos procesos o threads esten esperando para entrar a la CPU, y a esto lo realizan generando una cola o también denominada run queue, estaremos tienen una cola de ejecución normal, cuando esta no supere en número a la cantidad de procesadores que tenga el equipo instalado, caso contrario, estaremos ante un bottleneck.
Una métrica que suele ser tomada en cuenta por muchos administradores para medir la carga de un equipo, es mediante la carga promedio (Load average), la misma puede ser consultada mediante el comando top o también mediante uptime, y nos informa la carga del equipo ahora, hace cinco minutos y hace quince minutos. Veamos un ejemplo:
02:20:23 up  3:06,  2 users,  load average: 3.03, 2.13, 0.19
En este ejemplo, podemos notar que la carga actual esta en 3.03, siendo que el equipo cuenta con un procesador con 2 cores podemos decir que es alta, la carga promedio hace 5 minutos también se encuentra mínímamente excedida, y hace 15 minutos se encontraba bien. Por lo cual, es probable que algún proceso esté encolado, esperando para entrar a ejecución en la CPU. Pero está métrica no es del todo confiable, ya que en Linux además, promedia el tiempo en el que el proceso se encuentra "Waiting for I/O", por lo cual, la salida de vmstat sería mas confiable, y esta la podemos encontrar en la columna "r" de dicho comando.

Para reportar las estadísticas, vmstat hace uso del procfs, especificamente de /proc/stats, /proc/mem/info y /proc/*/stat, que como muchos de ustedes sabrán, cada proceso en ejecución, crea una entrada (mediante un directorio) en el directorio /proc, cuyo nombre es el PID del proceso y dentro del mismo existe un archivo denominado stat.

La invocación de vmstat es sencilla, si lo invocamos sin parámetros, nos imprimirá por la salida estándar, una sola muestra, podemos especificarle al mismo mediante un entero, la cantidad de tiempo que debe pasar entre muestra y muestra, y además la cantidad de muestras que queremos que nos brinde. Es un factor a tener en cuenta el periodo que debe pasar entre cada una de las muestras, ya que puede afectar notablemente a nuestro entendimiento de la salida. Con el argumento -a, nos brindara estadísticas mas avanzadas (imprimiendo la memoria activa e inactiva) y con -t además nos imprimirá un timestamp sobre cuando se produjo dicha muestra. vmstat Además imprime información sobre el estado de la swap y del IO (discos), utilizando como medida bloques de datos, cada bloque actualmente para kernels 2.6 y 3.x tiene un tamaño total de 1024 bytes, antiguamente estos podían ser reportados por vmstat en bloques de 512 bytes, 2048 bytes o 4096 bytes.

Veamos nuestro primer ejemplo:
$ vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0  71096 200036  12252 277544    0    3    64    45  662 1212 11  4 82  3  0 
 3  0  71096 199788  12252 277928    0    0     0     0  470  811  2  1 98  0  0 
 0  0  71096 201276  12260 277980    0    0     0    40  686  839 10  1 88  2  0 
 1  0  71096 201276  12260 277604    0    0     0     0  479  743  2  1 98  0  0 
 0  0  71096 201084  12260 277604    0    0     0   232  561  811  2  2 96  0  0 
 1  0  71096 201180  12260 277604    0    0     0    28  466  804  2  1 98  0  0 
 0  0  71096 201180  12260 277604    0    0     0     0  702  871 11  1 89  0  0 
 0  0  71096 201644  12260 277604    0    0     0     0  465  756  2  0 98  0  0 
 0  0  71096 201668  12260 277604    0    0     0     0  577  750  2  1 97  0  0 
 0  0  71096 201700  12276 277588    0    0     0    36  641  750  1  0 92  7  0 
La invocación en este caso se hizo imprimiendo muestras cada un segundo, y un total de diez muestras. Lo primero que nos llama la atención es que por un instante (2 segundos), la columna "r" tiene un valor de 3, en mi caso tengo solo dos cores y una CPU, por lo cual este valor se encuentra minimamente alto, pero dura muy poco como para preocuparnos. Seguido a nuestra columna "r", nos encontramos con la columna "b", esto según la manpage de vmstat, significa: The number of processes in uninterruptible sleep., como seguramente sabrán un proceso puede encontrarse en diversos estados, y utilizan un mecanismo de señales para comunicarse entre ellos (para listarlas: kill -l), Estos procesos, pueden encontrarse entre otros estados: Running (en ejecución), Sleeping (Durmiendo, esperando alguna señal o evento que los despierte), Stopped (parados), Zombie (Murio el padre, pero no el hijo), etc. Dentro de Sleeping, existen dos estados principales en los que puede estar durmiendo el proceso:

Interruptible sleep: Aquellos procesos que están esperando alguna señal para despertar y volver a ejecutarse (por ejemplo que se les notifique que X tarea fue completada).

Uninterruptible sleep: Aquellos procesos los cuales se encuentran aguardando algún tipo de evento externo para poder continuar, por ejemplo esperando I/O.

Por lo cual, nuestra columna "b" representa a la cantidad de procesos que se encuentran esperando alguna actividad externa para poder continuar como se dijo, por ejemplo I/O. Por lo cual, hacia el final de la salidad de vmstat encontramos la columna "wa" la cual nos dice que es la cantidad de tiempo gastado por la CPU esperando I/O. Generalmente ambas columnas están relacionadas, y tener altos valores aquí, puede ser sinónimo de algún desperfecto en el filesystem (posiblemente optimizable), baja velocidad en el bus de datos utilizado por las controladoras de disco, o algún tipo de desperfecto en el dispositivo de almacenamiento o sus controladoras. Para tener un detalle superior y resolver problemas de performance de I/O puede ser consultado iostat(1).
Veamos un ejemplo de esto:

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  1  71096  83596  18112 356208    0    3    61    44  644 1182 11  4 83  3  0 
 2  1  71420  72564   9772 379456    0  324 69760   412 2310 3041  4 45 20 31  0 
 1  1  71420  70584   4292 391272    0    0 84224     0 2589 3295  3 55 21 21  0 
 2  0  71420  72744   4292 393968    0    0 79104     0 2489 3131  3 51 23 23  0 
 3  1  71420  71796   4292 397928    0    0 81280     0 2472 3147  3 52 24 21  0 
 2  1  71420  69932   4292 402356    0    0 83968     0 2634 3308  3 55 21 21  0 
 1  1  71420  73220   4284 401044    0    0 84864    40 2673 3282  3 56 20 22  0 
 1  2  71420  67416   4292 408544    0    0 83208     0 2699 3202  3 54 16 26  0 
 0  2  71420  71728   4292 408500    0    0 83328     0 2602 3230  3 55 11 32  0 
 2  0  71420  74236   4300 408988    0    0 60456    32 2069 2752  4 39 12 45  0 
Si bien la carga no es excesivamente alta, podemos ver que nuestra columna "r" se encuentra entre 1 y 3 la mayor parte del tiempo. Nuestra columna "b" la encontramos variando entre 1 y 2 y la columna "wa" en un valor estimado entre 21 y 45. Lo cual en este caso es indicio de que o el disco conectado a la máquina es lento, o la controladora lo es. También encontramos otras columnas sumamente interesantes y muy importantes para nuestro análisis, esta es "us" dicha columna nos informa la cantidad de tiempo gastado en el userspace, por ejemplo alguna aplicación o aplicaciones que esten consumiendo un alto porcentaje de la CPU. Por su parte la columna "sy", nos dice la cantidad de tiempo que el kernel mode se encuentra consumiendo. "id", del término inglés idle, nos dice que tan libre se encuentra el sistema, cuando este valor es cercano a 100, mas libre se encuentra el mismo. Estás métricas son interesantes para conocer que la carga de la CPU y a esto se le denomina utilización, cuando la utilización supera el 100%, cambia su denominación a saturación, y esta saturación puede ser responsable de procesos encolados en la "run queue" (la columna "r"). Posiblemente, obviamente dependiendo el caso, la saturación pueda estar dada por una poca cantidad de RAM, o un bajo poder de procesamiento en el equipo.

Otro campo interesante es "cs", que contabiliza la cantidad de context switchs durante la muestra, si vemos un número elevado de context switchs puede ser indicio de que alguna aplicación en el userspace se encuentra ejecutando una gran cantidad de syscalls que pueden ser o no un problema.

El campo "in", nos muestra la cantidad de interrupciones producidas en el tiempo de muestra por el equipo. Hace un tiempo, tuve una experiencia personal con un firewall ejecutando la tecnología de filtrado IP Filter (IPF), el cual estaba produciendo un número enorme de interrupciones (entre 70000 y 100000) en muestras de 1 segundo, en este caso era debido a un daño físico (aunque podía deberse al driver también) en una interfaz de red. A esto se lo conoce como interrupt storm, y como un dato curioso, la primera de ellas se produjo durante el alunisaje del Apollo XI en 1969. Una tormenta de interrpuciones puede consumir el mayor tiempo (o todo el tiempo) en kernel mode ("sy"), y dejar el sistema sin respuesta, en un estado denominado live lock. Existen algunas formas de mitigar las tormentas de interrupciones, por ejemplo en el siguiente planteamiento: Una NIC ethernet, la cual debe efectuar una interrupción por cada paquete que ingresa o sale de la misma, para que esto no genere una interrupt storm, se hace uso de un mecanismo de polling el cual genera una interrupción cuando X cantidad de paquetes deben ser enviados o recibidos. Caso contrario, mientras mayor througput de red exista, mayor será la cantidad de interrupciones generadas, pudiendo producirse una interrput storm.

El campo "st", hace referencia a la cantidad de tiempo de procesamiento en caso de utilizarse virtualización robado al hypervisor por las máquinas virtuales.

Un grupo de campos bastante relacionados son los de la sección "Memory", en los cuales podemos encontrar "swpd" como la cantidad total de swap en el sistema (mas información: swapon -s), "free" la cantidad de memoria libre y que puede ser ser utilizada. "buff", Es la cantidad de memoria utilizada como buffers. "cache", La cantidad de memoria utilizada como cache por los distintos procesos.

Por su parte dentro de el grupo de campos "Swap", encontramos dos columnas, "si" o swap-in y "so" o swap-on. Esto es la cantidad de bloques que son escritos a swap, y son leídos de swap respectivamente. El sistema operativo hace uso de la memoría swap, cuando se queda sin memoria física a la que direccionar las páginas de memoría, bajando las menos utilizadas a dicha memoría de intercambio. La memoría swap, mas los buffers y la memoria física conforman un grupo al que se denomina virtual memory (VM). Por lo cual, notar actividad en estas columnas, puede ser síntoma de que la memoria física instalada en el equipo esta siendo insuficiente.

Veamos el siguiente ejemplo:
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs  us sy id wa st
 3  3  71420  93312  14888 373588    1    2    97    41  623 1140  84  3 10  3  0   
 8  2  72280 104204  28608 321432    2  860  1864  2704 2689 9901  61 22  1 17  0
 7  2  72784  75128  28040 350996  100    2   100    41  623 1143  93  3  0  3  0   
11  5  72812  75128  28116 351024  132   28 11392   668 1962 8491  32 16 11 41  0   
 8  3  72840  70540  28092 355984    1   28  9184   780 1766 11739 27 16  1 47  0   
 9  4  72972  74964  28068 351312    7  132 15860   644 2138 6639  34 20  5 40  0   
 8  2  73104  75264  21200 358408    0  132 11208   628 1775 5542  18 16  8 58  0
 3  2  73388  72768  13444 368200  345  284  2724   524 2032 25138 29 20  5 46  0   
 5  4  73848  74540  31100 347204  123  208  8120   208 3566 59935 41 35  0 24  0   
 4  3  74092  73084  36244 342524   98  244  5436  1392 3434 43193 33 27  0 40  0   
 7  4  74196  66204  43588 341380   15  104  6084   148 4437 64503 34 37  8 21  0   
 5  2  75344  70212  45964 335144    2 1148  4432  1368 3115 56056 30 32  7 31  0
La swap, es memoria lenta, ya que reside en el disco, por lo tanto pasa a ser un problema para nuestro analisis, por su parte la memoría física mantiene comunicación directa con la o las CPU's del equipo, mediante el bus de memoria, el cual trabaja a una frecuencia mucho mayor brindando una velocidad que jamaz los discos que conocemos podrán alcanzar, incluido aquellos de estados solido, los cuales suelen utilizarse entre otras cosas para hacer web cache (con Varnish). La tendencia a swappear por parte del kernel, puede ser optimizada mediante una sysctl, esta es vm.swappines.

En las muestras proporcionadas por el último ejemplo vemos una clara situación de saturación: La columna "r" en este caso es mayor a nuestra cantidad de cores (2, según /proc/cpuinfo), la columna "b", muestra un número constante y bastante alto de procesos con uninterruptible sleep, lo cual se apoya en el tiempo empleado en Waiting for I/O, según la columna "w", además podemos notar que el equipo esta swappeando ("si" y "so"), por lo cual aquí el problema puede deberse a una combinación de factores como baja velocidad de las controladoras, o daño en ellas o en los discos (para ello buscar por errores en los logs del sistema), además de una baja cantidad de memoria, o la existencia de un proceso o varios que se esten cosumiendo todos los recursos. Además podemos ver que el userspace es bastante alto en proporción a nuestro tiempo idle, por lo cual podría ser una aplicación o varias aplicaciones pequeñas del userland quien este produciendo esto. Para determinar esto, podemos ejecutar en una termianl lo siguiente:
# ps -eo user,pid,pcpu --no-headers | awk '{ if ( $3 > 80 ) print $1,$2,$3}'
zimbra 28442 88.5

# ps aux | grep 28442 
zimbra   28442  88.5 60.3 1213164 924904 ?      Sl   Apr11 306:57 /opt/zimbra/java/bin/java -Dfile.encoding=UTF-8 -server -Djava.awt.headless=true -Dsun.net.inetaddr.ttl=60 -XX:+UseConcMarkSweepGC -XX:PermSize=128m -XX:MaxPermSize=128m -XX:SoftRefLRUPolicyMSPerMB=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:-OmitStackTraceInFastThrow -Xss256k -Xms256m -Xmx256m -Xmn64m -Djava.io.tmpdir=/opt/zimbra/mailboxd/work -Djava.library.path=/opt/zimbra/lib -Djava.endorsed.dirs=/opt/zimbra/mailboxd/common/endorsed -Dzimbra.config=/opt/zimbra/conf/localconfig.xml -Djetty.home=/opt/zimbra/mailboxd -DSTART=/opt/zimbra/mailboxd/etc/start.config -jar /opt/zimbra/mailboxd/start.jar /opt/zimbra/mailboxd/etc/jetty.properties /opt/zimbra/mailboxd/etc/jetty-setuid.xml /opt/zimbra/mailboxd/etc/jetty.xml
¡Bingo! encontramos al responsable de quien se esta comiendo nuestra CPU y en gran parte a la memoria. Con esto, estamos imprimiendo mediante ps(1), aquellos procesos cuyo consumo de CPU sea superior al 80%. Si en vez de querer consultar la CPU, quisiéramos hacer lo mismo con la memoria ejecutaríamos:
# ps -eo user,pid,pmem --no-headers | awk '{ if ( $3 > 80 ) print $1,$2,$3}'
Pero también pueden existir situaciones en la que no sea un gran proceso quien este consumiendo los recursos, sino que pueden ser múltiples procesos chiquitos, que comen un poco cada uno y en conjunto si estén sobrecargando el sistema. Veamos un ejemplo de esto:
zimbra   28468  4.2  3.9  21540   840 ?        S    Apr11   0:05 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28471  1.2  1.1 109432  6196 ?        S    Apr11   0:00 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28488  6.0  4.7 109432  6196 ?        S    Apr11   0:04 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28489  1.3  3.2  54028  3476 ?        S    Apr11   0:05 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28490  1.8  1.6  54028  3476 ?        S    Apr11   0:00 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28491  6.2  9.1  54028  3476 ?        S    Apr11   0:01 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28492  0.4  1.0  54028  3476 ?        S    Apr11   0:02 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28493  0.1  1.0  54028  3476 ?        S    Apr11   0:02 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28494  0.7  1.0  54028  3476 ?        S    Apr11   0:02 /opt/zimbra/httpd-2.2.19/bin/httpd
zimbra   28495  0.4  1.0  54028  3476 ?        S    Apr11   0:02 /opt/zimbra/httpd-2.2.19/bin/httpd
--- OUTPUT OMMITED ---
Si bien omití algunos procesos por comodidad al momento de leer este, esta situación también podría darse, logrando saturar la CPU/Memoria, o bien producir una alta utilización. A veces estos procesos suelen ser difíciles de tracear debido a que pueden durar muy poco, muchas veces para conocer que esta haciendo un proceso solemos utilizar strace(1),(o truss en Solaris), si bien nos permite conocer con exactitud que se encuentra ejecutando el proceso, impacta en el mismo produciendo una baja perfomance. Pero cuando no podemos realizar esto, una forma bastante sencilla de conocer la cantidad de procesos o threads nuevos que se están creando en nuestro sistema es mediante el parámetro -f de vmstat, la cual contabiliza la cantidad de forks y vforks, producidas desde el último booteo del equipo.
La diferencia mas sustancial entre fork(2), y vfork(2), es que este último crea un nuevo child process suspendiendo a su padre (bloqueándolo) hasta que el mismo termine o envíe alguna señal que lo haga finalizar (por ejemplo llamando a _exit(2)). vfork(2) además, no copia su tabla de páginas de su padre, como si lo haría un proceso creado con fork(2), pero este si comparte su propia memoria con el padre, incluido el stack. Veamos un ejemplo, en el cual un webserver (Apache), esta produciendo forks continuamente, consumiendo de manera excesiva la CPU del equipo:
# while true; do sleep 1 && vmstat -f ; done
283986702 forks
283986734 forks
283986769 forks
283986812 forks
283986855 forks
283986938 forks
283986963 forks
283987083 forks
283988034 forks
283988918 forks
A esto debemos restarle los aproximadamente 2 forks que realiza vmstat y while para producir esta salida para tener un resultado aún más exacto. Lo que nos da un promedio de aproximadamente 50 forks por segundo.

Toda esta salida que hemos analizado a lo largo del post puede ser graficada con numerosas herramientas tales como gnuplot, en la cual podemos obtener resultados como este:

2 comentarios:

  1. Debo agregar ... todo desarrollador que se precie de tal debe tenes las mismas capacidades ;)

    ResponderEliminar
  2. Este comentario ha sido eliminado por el autor.

    ResponderEliminar