Вы находитесь на странице: 1из 56

Lentitud en el Sistema:

Análisis del rendimiento en OpenSolaris

(versión del 14/01/2009)

Autor: Roger Jordan Pérez


Licencia

Reconocimiento - Compartir bajo la misma licencia 3.0 España

Usted es libre de:

copiar, distribuir y comunicar públicamente la obra

hacer obras derivadas

Bajo las condiciones siguientes:

Reconocimiento. Debe reconocer los créditos de la obra de la manera


especificada por el autor o el licenciador (pero no de una manera que sugiera
que tiene su apoyo o apoyan el uso que hace de su obra).

Compartir bajo la misma licencia. Si transforma o modifica esta obra para


crear una obra derivada, sólo puede distribuir la obra resultante bajo la misma
licencia, una de similar o una de compatible.

• Al reutilizar o distribuir la obra, tiene que dejar bien claro los términos de la licencia de esta
obra.
• Alguna de estas condiciones puede no aplicarse si se obtiene el permiso del titular de los
derechos de autor
• Nada en esta licencia menoscaba o restringe los derechos morales del autor.

El autor de la fotografía de la portada es Unhindered by Talent.


Puede consultar su licencia aquí

2
Índice de contenido
1.- Introducción....................................................................................................................................4
2.- La llamada.......................................................................................................................................5
2.2.- Los jefes..............................................................................................................................5
2.3.- Los problemas tienen un origen..........................................................................................5
2.4.- Primeras valoraciones.........................................................................................................6
2.5.- Primera conclusión............................................................................................................11
3.- La CPU..........................................................................................................................................12
3.1.- Introducción......................................................................................................................12
3.2.- Reconociendo el estado de saturación..............................................................................12
3.3.- Quien está usando las CPUs, el diagnóstico.....................................................................14
3.4.- Conclusión........................................................................................................................16
4.- La memoria...................................................................................................................................17
4.1.- Introducción......................................................................................................................17
4.2.- Reconociendo el estado de falta de memoria....................................................................17
4.3.- Quien está consumiendo toda la memoria. ......................................................................19
4.4.- Conclusión........................................................................................................................20
5.- La I/O............................................................................................................................................21
5.1.- Introducción......................................................................................................................21
5.2.- Reconociendo los problemas de I/O.................................................................................21
5.3.- Quien está accediendo continuamente a disco..................................................................22
5.4.- Conclusión........................................................................................................................25
6.- Compitiendo por los recursos........................................................................................................26
6.1.- Introducción......................................................................................................................26
6.2.- Blocks a nivel de Kernel...................................................................................................27
6.3.- Bloqueos a nivel de usuario..............................................................................................34
6.4.- Conclusión........................................................................................................................35
7.- Analizando un proceso..................................................................................................................36
7.1.- Introducción......................................................................................................................36
7.2.- Que hace el proceso ?.......................................................................................................36
8.- Las estadísticas..............................................................................................................................42
Introducción..............................................................................................................................42
Las herramientas de monitorización.........................................................................................42
Conclusión................................................................................................................................43
Anexo A: Dtrace one liners................................................................................................................44
Anexo B: Llamadas al sistema...........................................................................................................46
Anexo C: Page Scanner......................................................................................................................47
Anexo D: Cambios de contexto..........................................................................................................51
Aenxo E: Dispatch Queues.................................................................................................................52
Anexo F: Bloqueos, mutex y R/W.....................................................................................................55

3
1.- Introducción

Desde que empecé a escribir mi blog lo he ido llenando de pequeños documentos técnicos, cosas
que me parecían interesantes, pequeños apuntes acerca de un nuevo comando aprendido, etc.

Pasado un tiempo he empezado a escribir una serie de artículos llamados Lentitud en el sistema. En
ellos trato de compartir mi experiencia en situaciones donde el rendimiento de un equipo se ve
repentinamente degradado sin que haya una aparente razón. En modo alguno pretenden ser un
manual o libro al respecto y puede estarse o no en razón en lo que expongo en ellos. Simplemente
es lo que yo suelo hacer y a juzgar por los hechos, suele darme buenos resultados.

A medida que la cantidad de documentación se ha ido acumulando, el formato blog a sacado a


relucir sus carencias para esta tarea, por lo que sentí la necesidad de darle un formato que permitiera
una fácil consulta cuando estemos frente a un problema, y que la gente pueda almacenar para
acceder a él cuando quiera.

Con lo anterior en mente he generado este pdf, se trata de casi la totalidad de documentación técnica
que hay en el blog, he tratado de organizarla de forma que su lectura guarde cierta coherencia y su
consulta sea sencilla.

Puesto que el blog es un medio vivo en el que se suele añadir contenido de forma frecuente, os
animo a comprobar si hay nuevas versiones del documento regularmente en http://rjblog.es.

4
2.- La llamada.

Los problemas de performance son un tema recurrente en la vida de un administrador de sistemas,


normalmente suelen llegar en forma de incidencia bajo el eufemismo "lentitud en el sistema" o
parecido, que en realidad significa "No tenemos ni idea de que está pasando".
Ante estas situaciones mis sentimientos son encontrados. Por un lado tienes la oportunidad de usar
tus conocimientos e investigar el origen de una incidencia, lo cual no deja de ser atractivo. Los
problemas empiezan cuando el diagnóstico no depende de ti solo, en realidad suele hacer falta la
colaboración de la gente de comunicaciones, BB.DD., aplicaciones, etc. Invariablemente siempre te
encuentras con algún personaje más interesado en pasar el marrón a otro que en colaborar para
encontrar la solución.

2.2.- Los jefes


Hay otro elemento que todavía no he mencionado, tu jefe, o aun peor, el jefe de tu jefe.
Normalmente las situaciones donde el performance se ve degradado suele venir acompañado de
cierto nerviosismo por parte de las grandes esferas, en lo que a ti respeta se traduce en presión para
resolver el problema lo antes posible.
Sin embargo un diagnóstico de este tipo suele llevar bastante tiempo, muchas veces es complicado
dar con la causa raíz del problema. No obstante, es importante que sepas dar una primera valoración
rápida de lo que esta ocurriendo en el equipo. Un prolongado silencio puede dar pie a teorías de lo
más disparatadas por parte de tus colegas, que en el peor de los casos podrían ser creídas por tus
responsables.

2.3.- Los problemas tienen un origen


Si, ¿es obvio verdad? Cuando se produce una degradación del sistema siempre hay una causa. Algo
que parece indiscutible es discutido muchas veces en el mundillo de la informática, cuantas veces
habré escuchado frases del tipo, "Se habrá quedado algo colgado", "Habrá bloqueos", "Está
pillado", etc. y después se suelen acompañarse de la sugerencia "reiniciemos el equipo".

Reiniciar vs no reiniciar
Salvo que tu puesto de trabajo corra peligro nunca reinicies un equipo sin haber tratado de realizar
un diagnóstico antes, por suerte dispones de un sistema sumamente observable, es decir, tienes a tu
mano una serie de herramientas que, junto con el conocimiento necesario, te permitirán determinar
la causa del problema. Aunque un reinicio a priori puede solucionar las cosas en un elevado
porcentaje de ocasiones, solo conseguirás que la incidencia reaparezca en unas horas o días y tu
nivel de conocimiento de la causa seguirá siendo el mismo que antes, es decir, 0.
Créeme, aunque muchas veces tus responsables no sean conscientes, seguro que prefieren tener la
producción degradada o incluso parada durante un rato a que la misma incidencia se reproduzca de
forma cíclica. Si el problema es realmente crítico puedes optar por generar un live core

5
"#savecore -L" y realizar un análisis posterior de la imagen del Kernel. Ten en cuenta que en
sistemas con mucha RAM el volcado puede demorar un rato.

2.4.- Primeras valoraciones

Lo que sigue no pretende ser una Biblia de los pasos a seguir, en ocasiones puede ser necesario un
mayor grado de profundización antes de valorar la situación, otras veces el cuello de botella es tan
evidente que puedes obviar muchos de los pasos aquí descritos.

Tanteo:
Mi primer comando a ejecutar suele ser #w. Es un comando ligero con el que obtienes información
bastante útil, el uptime del equipo, su load average y los usuarios conectados junto al comando que
están ejecutando.

root@box # w
9:28am up 135 day(s), 18:29, 3 users, load average: 3.96, 3.67, 3.91
User tty login@ idle J CPU PCPU what
explot pts/1 8:15am 1 9:48 sqlplus /script
oracle pts/2 8:39am 50 tail -25f /opt/oracle/app/oracle
sistemas pts/3 9:05am - bash

El load average es un parámetro engañoso, su nombre puede llevar a confusión, parece ser un
calculo de la carga del sistema. Sin embargo es la media del número de threads en las run queues, es
decir, un elevado load average puede ser síntoma de saturación de cpu pero no tiene en cuenta otros
aspectos como puede ser memoria, i/o, red, etc.

Comprobar recursos:
Uno de mis comandos preferidos es el vmstat, proporciona muchísima información a simple vista.
Lo normal sería lanzar un #vmstat 5 y dejarlo un buen rato ejecutándose. La primera linea son
estadísticas desde que se arrancó el sistema, por lo que en nuestro caso debe ser ignorada.

root@box # vmstat 5
kthr memory page disk faults cpu
rbw swap free re mf pi po fr de sr 2m m0 m1 m2 in sy cs us sy
id
100 42157120 7573496 585 2219 1920 7 9 0 6 44 2 1 1 19612 34991 12905 42 15 43
0 0 0 41882080 7348816 546 2233 2175 0 0 0 0 35 0 0 0 12435 29385 6561 32 9 59
0 0 0 41882512 7344728 584 2393 2162 0 0 0 0 36 20 19 19 10922 32580 7231 28 9 63

6
0 0 0 41884936 7342656 628 2809 2174 0 0 0 0 35 0 0 0 11673 43611 8204 31 9 60
[...]

Varios son los datos interesantes para nuestra primera aproximación:

• La columna r: Es el número de procesos esperando en las run queues, idealmente debería


ser 0, lo que indicaría que cualquier thread en condiciones de ser ejecutado ha encontrado un
procesador libre. Si el valor de r supera el número de procesadores de nuestro sistema
estamos empezando a saturar, si lo duplica estamos ante una situación de saturación y la
performance empieza a verse degradada.
• La columna sr: Es el número de veces que se ha ejecutado el page scanner durante la
muestra. Dado que este solo se ejecuta en casos de grave escasez de memoria cualquier valor
superior a 0 es preocupante.
• La columna in: El número de interrupciones durante la muestra. Es complicado de valorar
ya que un número elevado puede ser síntoma de muchas cosas, como mucho tráfico de red,
elevada i/o, mal funcionamiento hardware, etc. En nuestra primera aproximación solo
comprobaremos que no sea más elevado de lo habitual en nuestro sistema, si es así
deberemos tener este dato en cuenta para seguir investigando.
• La columna sy: Número total de llamadas al sistema. Si es superior a lo que tenemos de
media en condiciones normales puede ser un mal funcionamiento de alguna aplicación,
normalmente irá acompañado de un porcentaje alto de tiempo en modo sistema. (Columna
cpu/sy).
• La columna cs: Los cambios de contexto acontecidos en nuestro sistema durante la muestra.
Si el valor es muy elevado puede ser síntoma de muchos threads compitiendo por recursos.
Al igual que con la columna in deberemos tenerlo presente más adelante.
• Las columnas us sy id: Es un porcentaje de los ciclos de cpu gastados en tiempo de usuario
(us), de sistema (sy) y sin hacer nada (id). Para ver si hay saturación de cpu es mejor hacer
caso a la columna r, sin embargo un elevado tiempo de sistema es sospecho. Generalmente
suele ser alguna aplicación realizando muchas llamadas al sistema. Igual que anteriormente
será un dato a tener en cuenta para el futuro.

7
Mirar procesos:
El comando vmstat nos ha dado una idea general de los recursos de la máquina, ahora llega el
momento de ver que demonios es lo que está ejecutando. Para ello echaremos mano del comando

root@box # prstat
PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP
29921 oracle 16G 16G cpu17 40 0 1:32:53 7.4% oracle/1
24722 oracle 16G 16G sleep 60 0 0:13:25 4.6% oracle/11
5669 oracle 16G 16G sleep 60 0 0:19:29 2.3% oracle/11
27688 oracle 16G 16G sleep 48 2 1:44:05 1.7% oracle/11
28286 oracle 16G 16G sleep 60 0 0:07:51 1.3% oracle/11
5828 oracle 16G 16G sleep 59 0 115:22:45 1.2% oracle/258
27687 oracle 26M 22M cpu18 48 2 1:42:58 1.0% exp/1
[..]
Total: 340 processes, 3029 lwps, load averages: 2.73, 3.01, 3.30

Hay varios datos que son interesantes, el % de cpu (CPU) consumido por proceso, el número de
threads de cada proceso (NLWP) y el total de procesos y threads. A veces es un único proceso el
que está consumiendo toda la CPU, al ejecutar un prstat se mostrará en primer lugar.
Por otro lado, si en la salida del vmstat había muchos cambios de contexto y ahora tenemos un
proceso con muchos threads (aka LWP), o bien muchos threads en total, puede ser que estos estén
compitiendo entre ellos para conseguir los mismos recursos.
Si al comando prstat le pasamos la opción -m nos muestra los microstados de cada uno de los
procesos. La información aquí es mucho más detallada.

root@box # prstat -m
PID USER NAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/NLWP
29921 oracle 41 1.3 0.1 0.0 0.0 0.0 57 0.5 182 470 33K 0 oracle/1
27687 oracle 8.3 5.1 0.0 0.0 0.0 0.0 86 0.9 4K 120 13K 0 exp/1
5798 oracle 3.6 2.7 0.0 0.0 0.0 0.0 94 0.2 956 5 10K 8 oracle/1
5822 oracle 3.0 2.2 0.0 0.0 0.0 0.0 95 0.1 717 1 8K 3 oracle/1
6099 root 1.0 2.7 0.2 0.0 0.0 0.0 96 0.0 55 0 2K 0 sleep/1
5783 oracle 1.7 1.5 0.0 0.0 0.0 0.0 97 0.1 736 76 6K 198 oracle/1

8
Datos interesantes para nuestra primera aproximación:

• USR SYS: Porcentaje de tiempo empleado en modo usuario y modo sistema. Este dato lo
podemos relacionar con la columna sy de la salida del vmstat, es decir nos puede
proporcionar la pista para dar con un proceso que esté generando un número anormalmente
alto de llamadas a sistema.
• LCK: Porcentaje de tiempo esperado bloqueos a nivel de usuario. Podemos verificarlos
con el comando plockstat.
• SLP: Porcentaje de tiempo que el proceso ha estado durmiendo, incluye los tiempos de
espera de i/o. Que haya muchos procesos durmiendo no es síntoma de un problema, pueden
estar esperando a algún dispositivo (tarjeta de red, disco duro), a la interacción de un usuario
(una shell esperando que teclees algún comando), que deban hacer algo ( un servidor web o
BBDD esperando que alguien les pida información), etc.
• LAT: Porcentaje de tiempo esperando una CPU libre. Obviamente puede ser síntoma de
saturación en los procesadores, deberíamos verificarlo con la columna r de la salida del
vmstat.
• ICX: Número de cambios de contexto involuntarios. Si los procesos se ven continuamente
desalojados de la cpu puede ser señal de saturación o de competencia por los mismos
recursos.
• SCL: Llamadas a sistema, otra vez. Si es elevado deberíamos ver con más detalle que está
haciendo este proceso. (dtruss, truss).

Si añadimos la opción -L se nos mostrará la misma información pero por thread en lugar de por
proceso.

root@box # prstat -mL


PID USER NAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/NLWP
5425 root 31 59 .6 0.0 0.0 0.0 0.0 2.2 0 1K 49K 0 prstat/1
29921 oracle 58 1.8 0.2 0.0 0.0 0.0 39 1.0 232 934 43K 0 oracle/1
5669 oracle 23 2.3 0.1 0.0 0.0 0.0 74 0.4 508 363 26K 2 oracle/1

9
Los discos
No está de más comprobar la i/o a disco de nuestro sistema, con un iostat nos bastará para hacernos
una idea global, especialmente nos fijaremos en el % de busy en busca de datos elevados, también
verificaremos el throughput de escritura y lectura.

root@box # iostat -xn 5


extended device statistics
r/s w/s kr/s kw/s wait actv wsvc_t asvc_t %w %b device
19.5 24.1 1161.9 1360.0 0.0 0.2 0.0 3.8 0 8 2/md110
0.7 1.0 13.7 1.2 0.0 0.0 2.4 20.9 0 1 d0
0.3 1.0 6.9 1.2 0.0 0.0 0.0 22.6 0 1 d1
0.3 1.0 6.9 1.2 0.0 0.0 0.0 22.3 0 1 d2

La red
Los problemas de red suelen ser competencia de la gente de comunicaciones, sin embargo nosotros
podemos percibir sus síntomas. Una rápida comprobación puede ser hacer un ping a nuestro router
por defecto y a un equipo de fuera de nuestra red, comprobaremos que no se pierdan paquetes y que
los tiempos de respuesta estén dentro de lo normal.
También miraremos las estadísticas de red y comprobaremos que no haya errores de RX TX, hay
que tener en cuenta que son contadores desde el arranque del sistema, por lo que debemos verificar
que estén aumentando actualmente.

root@box # netstat -ni


Name Mtu Net/Dest Address Ipkts Ierrs Opkts Oerrs Collis Queue
lo0 8232 127.0.0.0 127.0.0.1 76861912 0 76861912 0 0 0
ce0 1500 192.168.103.0 192.168.103.56 149068395 0 364477655 0 0 0
ce2 1500 10.0.0.0 10.10.1.1 0 0 0 0 0 0
ce3 1500 172.16.0.128 172.16.0.130 2640701017 18 3331003982 0 0 0
ce5 1500 10.27.0.0 10.27.0.1 238843876 10 248360337 0 0 0
ce7 1500 172.16.1.0 172.16.1.2 3703162800 4 2341304790 0 0 0
eri0 1500 192.168.103.0 192.168.103.84 1767612057 0 4287819140 6 0 0

Finalmente podemos ejecutar un netstat -na para ver el número de sockets en uso de nuestro
sistema y su estado.

10
root@box # netstat -na | awk ' { print $7 } ' | sort | uniq -c
1 32/32
2 BOUND
1 CLOSE_WAIT
223 ESTABLISHED
30 IDLE
76 LISTEN
1 Remote
3 Rwind

Estos datos por si solos no suelen significar nada, es bueno tener la posibilidad de compararlos con
un histórico.

Revisar los logs

Normalmente los problemas de rendimiento no suelen dejar trazas en los logs, sin embargo no está
de más echar un vistazo para asegurarnos que no nos saltamos nada interesante, así pues revisa el
fichero de messages y ejecuta un dmesg en busca de errores.

2.5.- Primera conclusión


Seguir los pasos anteriores no debería tomarte más de 10 - 15 minutos.
Después de revisar todos estos datos muy probablemente sigas sin tener ni idea de cual es la causa
raíz del problema de rendimiento, sin embargo si deberías tener claro cuales son sus consecuencias
y empezar a descartar algunas posibilidades, por ejemplo:
• ¿Hay saturación en las CPUs?
• ¿Alguna aplicación se ha salido de su patrón de carga habitual?
• ¿Falta de memoria?
• ¿Hay problemas de competencia por un mismo recurso?
• ¿Existen problemas de acceso a disco?
• ¿Hay problemas de red?
Lógicamente puedes tener más de un síntoma a la vez.
Necesitas investigar más, sin embargo puede ser un buen momento para proporcionar un primer
feedback a tus responsables y a tus colegas, piensa que no solo debes saber diagnosticar bien,
además la gente debe pensar que sabes diagnosticar bien, mantenerlos informados es la mejor forma
de hacerlo, de paso también atajarás las posibles especulaciones sin argumentos del típico suelta
marrones que tengas cerca.

11
3.- La CPU.

3.1.- Introducción
Si nuestro sistema está dimensionado de forma correcta no deberemos tener problemas de
saturación en las CPUs, una forma sencilla de verificarlo es comprobar que el load average del
equipo no supera el número de CPUs que tenemos.
Sin embargo es bastante frecuente que una aplicación se salga de su patrón de carga habitual, los
motivos pueden ser de lo más variopintos. Un cambio de comportamiento de los usuarios, una
actualización defectuosa, una select mal hecha, ... Cuando el tiempo de proceso requerido es
superior al que pueden proporcionar nuestros procesadores los threads son puestos en las dispatch
queues a la espera que quede una CPU libre.
La perdida de performance por culpa de saturación de CPUs no es lineal, cuando se produce el S.O.
debe gestionar las run queues, cambiar las prioridades de los procesos, intercambiar los procesos
que se están ejecutandose en la CPU por otros más prioritarios, etc. Todo ello suele repercutir con
un consumo en modo sistema más elevado, es decir, para solucionar nuestra escasez de tiempo de
ejecución necesitamos tiempo de ejecución. En casos extremos puede darse el caso que el sistema
llegue a consumir más tiempo en estas tareas que en ejecutar las aplicaciones.

Nuestro objetivo
Ante una situación de saturación de CPU debemos procurar identificar la aplicación cuyo consumo
se ha salido del patrón habitual, en este artículo buscaremos identificar los Pids responsables para
posteriormente analizarlos, trataremos la fase de análisis de los procesos en un artículo futuro.

3.2.- Reconociendo el estado de saturación


El Usuario
A nivel de usuario se percibirá lentitud generalizada en todas las aplicaciones, posiblemente la shell
le tarde más en responder de lo habitual. Aunque la descripción con la que nos puede llegar la
incidencia será de lo más variopinta. Desde "error generalizado", pasando por "va lento", hasta lo
más imaginativo que se te pueda ocurrir.

Los síntomas
Son varias las utilidades que nos permitirán determinar que existe una saturación en las cpus.

Vmstat
La columna r de la salida del vmstat indica el número de threads que se encuentran en las dispatch
queues esperando a un CPU libre, si su número supera al de las CPUs de nuestro sistema estamos
empezando a sufrir saturación, si lo duplica la performance se empezará a resentir notablemente.

12
root@box # vmstat 5
kthr memory page disk faults cpu
rbw swap free re mf pi po fr de sr 2m m0 m1 m2 in sy cs us sy id
100 42157120 7573496 585 2219 1920 7 9 0 6 44 2 1 1 19612 34991 12905 42 15 43
0 0 0 41882080 7348816 546 2233 2175 0 0 0 0 35 0 0 0 12435 29385 6561 32 9 59
0 0 0 41882512 7344728 584 2393 2162 0 0 0 0 36 20 19 19 10922 32580 7231 28 9 63
0 0 0 41884936 7342656 628 2809 2174 0 0 0 0 35 0 0 0 11673 43611 8204 31 9 60
[...]

Lockstat
Si ejecutamos un #lockstat sleep 5 veremos multitud de bloqueos en los bloqueos
exclusivos (mutex) de las dispatch queues, ya que el scheduler del sistema estará constantemente
insertando nuevos threads, moviendo hilos de una cola a otra, etc:

root@box # lockstat sleep 5


[...]
Count indv cuml rcnt spin Lock Caller
-------------------------------------------------------------------------------
1866 4% 80% 0.00 4 0x60002e39860 disp+0x84
1788 4% 83% 0.00 3 0x60002e39800 disp+0x84
1702 4% 87% 0.00 3 0x60002e39740 disp+0x84
[...]

Mpstat
Proporciona información por cada una de las CPUs, si nos centramos en la saturación deberíamos
destacar:

root@box # mpstat 1
CPU minf mjf xcal intr ithr csw icsw migr smtx srw syscl usr sys wt idl
0 262 0 1096 374 7 1624 389 486 116 41 4546 44 13 0 43
1 272 0 1448 463 25 1978 451 551 134 40 4630 37 14 0 49
2 328 0 1154 620 268 1592 375 441 124 39 4515 44 15 0 41
3 257 0 1548 1758 1397 1348 323 422 153 37 3869 39 20 0 42
16 289 0 1224 842 535 1442 346 431 182 29 4256 43 15 0 42
17 297 0 1611 928 562 1708 388 479 208 28 4522 40 16 0 44
18 254 0 1088 350 7 1558 362 457 112 40 4420 45 13 0 42

13
19 255 0 1513 14420 264 1770 398 489 140 38 4395 40 15 0 45

• icsw: Número de cambios de contexto involuntarios. Estos se producen cuando un thread es


desalojado de la cpu porque agota su quantum o bien un thread con más prioridad pasa a
estar disponible para ejecutarse. Un elevado valor puede ser debido a un exceso de threads
en condición de ser ejecutado para el número de procesadores que disponemos.
• migr: Número de threads en estado runnable que se han movido de la dispatch queue de un
procesador a otro que estaba en idle. Normalmente el sistema intenta que un thread siempre
sea ejecutado en el mismo procesador ya que recuperar su contexto es menos costoso. Si el
equipo se está viendo forzado continuamente a migrar hilos entre procesadores impactará en
la performance. Puede ser debido a una alta carga de CPU o a una aplicación que escala mal
en un entorno multiprocesador.

3.3.- Quien está usando las CPUs, el diagnóstico.


Observar que las cpus del sistema están saturando es una tarea sencilla, sin embargo va acompañada
de otra algo más compleja, quien es el responsable?

Prstat

Prstat proporciona muchísima información acerca del consumo de los procesos que se están
ejecutando en nuestro sistema. Si lo ejecutamos con la opción -m nos proporcionará información
acerca de los microestados de cada uno de ellos. Si añadimos la opción -L nos lo desglosará por
thread.

root@box # prstat
PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP
29921 oracle 16G 16G cpu17 40 0 1:32:53 7.4% oracle/1
24722 oracle 16G 16G sleep 60 0 0:13:25 4.6% oracle/11
5669 oracle 16G 16G sleep 60 0 0:19:29 2.3% oracle/11
27688 oracle 16G 16G sleep 48 2 1:44:05 1.7% oracle/11
28286 oracle 16G 16G sleep 60 0 0:07:51 1.3% oracle/11
5828 oracle 16G 16G sleep 59 0 115:22:45 1.2% oracle/258
27687 oracle 26M 22M cpu18 48 2 1:42:58 1.0% exp/1
[..]
Total: 340 processes, 3029 lwps, load averages: 2.73, 3.01, 3.30

14
En el capítulo anterior puedes leer una descripción de las columnas más significativas.

Sin embargo es importante no leer sólo números sino saber relacionar los distintos campos entre
ellos. Si ejecutamos un prstat sin parámetros el listado aparece ordenado por consumo de CPU, por
lo que los procesos que están en la parte superior son los mejores candidatos para ser investigados.
Conviene observarlo durante un rato y determinar cuales mantienen una carga constante.
Uno de los problemas más frecuentes y difíciles de detectar son procesos que tienen una duración
muy corta, estos pueden suponer una carga severa en el equipo. Sin embargo pueden pasar
desapercibidos al comando prstat ya que refresca la pantalla cada pocos segundos. Si vemos un
número elevado de cambios de contexto sin motivo aparente podríamos estar frente este problema.
Podemos usar el script shortlived.d del Dtrace ToolKit
Tracing... Hit Ctrl-C to stop.

short lived processes: 0.643 secs


total sample duration: 3.617 secs

Total time by process name,


sleep 6 ms
cat 11 ms
who 12 ms
isainfo 19 ms
date 21 ms
echo 24 ms
cut 29 ms
hostname 34 ms
awk 43 ms
uname 58 ms
kill 59 ms
expr 116 ms
init.cssd 128 ms

Total time by PPID,


5559 3 ms
5547 4 ms
5613 4 ms
5638 7 ms
5640 7 ms
5572 8 ms

15
5642 9 ms
5551 10 ms
5624 31 ms
5591 36 ms
5558 37 ms
5579 58 ms
5612 59 ms
5546 61 ms
2935 135 ms

Continuando con el comando prstat, una vez tengamos claro los procesos con mayor consumo
deberíamos ejecutarlo con la opción -m para ver los microestados de los procesos. En general, si
hay saturación de las cpus, deberíamos observar un porcentaje de latencia (tiempo esperando CPU
libre) elevado para todos los procesos.

root@box # prstat -mL


PID USER NAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/NLWP
5425 root 31 59 8.6 0.0 0.0 0.0 0.0 2.2 0 1K 49K 0 prstat/1
29921 oracle 58 1.8 0.2 0.0 0.0 0.0 39 1.0 232 934 43K 0 oracle/1
5669 oracle 23 2.3 0.1 0.0 0.0 0.0 74 0.4 508 363 26K 2 oracle/1
[...]

Un buen dato para ver que procesos generan mucha carga es el número de llamadas a sistema
(columna SCL), lo normal es que vaya acompañado de un % elevado de tiempo en modo sistema.

3.4.- Conclusión
Después de estas comprobaciones deberemos tener una pequeña lista de pid que merece la atención
analizar detenidamente, en capítulos posteriores veremos como hacer un análisis detallado de estos
procesos.

16
4.- La memoria.

4.1.- Introducción

Hoy en día los servidores cuentan con gigas y gigas de RAM, comparado con hace unos años su
coste se ha abaratado sustancialmente. En mi humilde opinión esto no ha repercutido en una mejora
sustancial del rendimiento, ya que las aplicaciones actuales son auténticas devoradoras de recursos.
Debido a esto no es extraño encontrarse en una situación donde la memoria física se haya terminado
y el sistema se vea forzado a swapear.

Nuestro objetivo
Como suele ser habitual una pérdida de performance debido a escasez de memoria suele ser debido
a que una aplicación se ha salido de su patrón de carga habitual. Así deberemos centrar nuestro
análisis en localizar el/los pid/s responsables.

4.2.- Reconociendo el estado de falta de memoria.


El Usuario
En general percibirá una perdida de velocidad en sus aplicaciones, en casos realmente extremos
puede ser que no pueda lanzar nuevos comandos y obtenga un mensaje indicando que no hay
memoria suficiente para hacer un fork.

Los síntomas

Prtconf
Un primer paso sencillo es ver la cantidad de memoria física que hay instalada en el sistema, para
ello usaremos el comando #prtconf.

root@box # prtconf | more


System Configuration: Sun Microsystems sun4u Memory size: 32768 Megabytes

17
Vmstat
La columna sr de la salida del vmstat indica el número de veces que se ha ejecutado el page scanner
durante la muestra.
El proceso se activa cuando queda menos de 1/64 de la memoria física libre disponible, es decir,
cualquier valor superior a 0 significa que andamos francamente escasos de memoria, si el valor es
sostenido durante varias muestras implica que la situación es realmente grave. La ejecución del
page scanner supone un gasto notable de CPU por lo que en casos realmente extremos puede
ser que el equipo esté prácticamente dedicando todos sus recursos a liberar memoria.
La columna free es el total de memoria libre disponible, se obtiene de la suma de la Free (cachelist)
y la Free (freelist).

root@box # vmstat 5
kthr memory page disk faults cpu
rbw swap free re mf pi po fr de sr 2m m0 m1 m2 in sy cs us sy id
100 42157120 7573496 585 2219 1920 7 9 0 6 44 2 1 1 19612 34991 12905 42 15 43
0 0 0 41882080 7348816 546 2233 2175 0 0 0 0 35 0 0 0 12435 29385 6561 32 9 59
0 0 0 41882512 7344728 584 2393 2162 0 0 0 0 36 20 19 19 10922 32580 7231 28 9 63
0 0 0 41884936 7342656 628 2809 2174 0 0 0 0 35 0 0 0 11673 43611 8204 31 9 60
[...]

mdb
Con mdb podemos obtener una estadística de la memoria según el uso que se la esté dando:

root@box # mdb -k
Loading modules: [ unix krtld genunix dtrace specfs ufs ssd fcp fctl ipc md ips ctp usba random
emlxs nca ptm sd nfs sppp crypto logindmux cpc fcip isp ]
::memstat
Page Summary Pages MB %Tot
------------ ---------------- ---------------- ----
Kernel 371610 2903 9%
Anon 2406527 18800 58%
Exec and libs 22685 177 1%
Page cache 460896 3600 11%
Free (cachelist) 811777 6342 19%
Free (freelist) 105288 822 3%

18
Total 4178783 32646
Physical 4111653 32122

El total de memoria libre disponible es la suma de la Free (cachelist) y la Free (freelist). Este valor
es le que presenta la columna free del vmstat.
• La Free (freelist) es la memoria libre que nunca ha sido usada.
• La Free (cachelist) es memoria que ha sido usada y aun contiene datos validos pero en caso
de necesidad pueden ser sobrescritos, de esta manera si los mismos datos son necesitados
antes de haber sido sobrescritos no hace falta volverlos a traer a la memoria fisica.

4.3.- Quien está consumiendo toda la memoria.


Hay que tener en cuenta que el proceso que más ocupa en memoria no tiene porque ser el que esté
creando el problema si su tamaño se mantiene constante.
Pongamos un ejemplo tonto, En nuestro sistema tenemos un total de 8 gigas, tenemos 3 procesos, A
consume 3G, B 3G y C 1G. Si el proceso C tiene un funcionamiento anómalo y su consumo se
dispara su tamaño máximo será de 2 G, es decir seguirá siendo el más pequeño pero sin embargo
será el responsable de los problemas de performance.

Prstat
La salida del prstat nos indica el tamaño de cada proceso en memoria, sin embargo es engañoso. Si
varios procesos utilizan las mismas librerías, ejecutables, etc solo hay una copia cargada en
memoria, sin embargo se tienen en cuenta al mostrar el tamaño total que ocupa el proceso.

root@box # prstat -m
PID USER NAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/NLWP
29921 oracle 41 1.3 0.1 0.0 0.0 0.0 57 0.5 182 470 33K 0 oracle/1
27687 oracle 8.3 5.1 0.0 0.0 0.0 0.0 86 0.9 4K 120 13K 0 exp/1
5798 oracle 3.6 2.7 0.0 0.0 0.0 0.0 94 0.2 956 5 10K 8 oracle/1
5822 oracle 3.0 2.2 0.0 0.0 0.0 0.0 95 0.1 717 1 8K 3 oracle/1
6099 root 1.0 2.7 0.2 0.0 0.0 0.0 96 0.0 55 0 2K 0 sleep/1
5783 oracle 1.7 1.5 0.0 0.0 0.0 0.0 97 0.1 736 76 6K 198 oracle/1

Si sumamos el tamaño de todos los procesos oracle nos da un valor superior a la memoria instalada
en el equipo. Por ello el prstat NO es una buena herramienta para determinar que proceso es el
responsable del agotamiento de la memoria.

19
Dtrace
Un buen indicativo de consumo de memoria es el número de minor faults que está generando un
proceso, un número de minor faults indica que el proceso está reclamando más memoria.
Para localizar estos procesos tenemos que hechar mano de dtrace, por suerte podemos usar los
scripts del Dtrace Tool Kit que nos simplificará la vida. En concreto el script minfbypid.d.

root@box # ./minfbypid.d
Tracing... Hit Ctrl-C to end.
^C
PID CMD MINFAULTS
7313 oracle 2
13591 sleep 2
[...]
13626 init.cssd 86
4209 oracle 364

4.4.- Conclusión
Después de estas comprobaciones deberemos tener claro si tenemos problemas de memoria y en
este caso una pequeña lista de pid que merece la atención analizar detenidamente, en artículos
posteriores veremos como hacer un análisis detallado de estos procesos.

20
5.- La I/O

5.1.- Introducción

Los problemas de i/o siempre han sido complejos de resolver. Normalmente después de lanzar un
iostat detectamos que alguno de los dispositivos de nuestro sistema tiene un throughput muy
elevado, y un % de busy alto. La conclusión es evidente, algún proceso está generando muchas
escrituras o lecturas sobre ese file system.
Sin embargo llegados a este punto seguir avanzando suele ser difícil, generalmente diagnosticamos
de forma indirecta, es decir, si en esa partición están los datafiles de oracle, el problema será de las
BB.DD., si el proceso x tiene un sleep time elevado estará escribiendo en ese dispositivo, etc.
Sin embargo con el provider io de dtrace se ha abierto un nuevo abanico de herramientas para
realizar un diagnóstico de forma directa, es decir, ver directamente la cantidad de bytes escritos por
un proceso, sobre un fichero, etc. Además gracias al Dtrace ToolKit, herramienta que cualquier
sysadmin que se precie debe tener instalada, nos será más fácil de usar.

Nuestro objetivo
Como siempre, localizar el origen de la perdida de performance. En este caso trataremos de
discernir que aplicación es la responsable del aumento de la I/O.

5.2.- Reconociendo los problemas de I/O.


El Usuario
En general percibirá una perdida de velocidad en sus aplicaciones, en esta ocasión difícilmente nos
proporcionará algún tipo de descripción que ayude.

Los síntomas

iostat
La forma más sencilla para comprobar si tenemos problemas de i/o es dejar lanzado unos segundos
el comando iostat. Este tiene varios flags que modifican la presentación de los datos por la pantalla,
cualquiera de ellos nos sirve, es cuestión de seleccionar el que nos sea más cómodo.

21
root@box # iostat -xnmd 5
extended device statistics
r/s w/s kr/s kw/s wait actv wsvc_t asvc_t %w %b device
19.3 25.2 1148.3 1438.2 0.0 0.2 0.0 3.8 0 8 2/ md110
0.7 1.0 13.1 1.2 0.0 0.0 2.4 21.1 0 1 d0 (/)
0.0 0.1 1.7 1.8 0.0 0.0 4.7 8.3 0 0 d50 (/var)

En las estadísticas podemos ver distintos valores, como el número de kbs escritos o leídos, número
de operaciones de lectura/escritura realizadas, etc. Sin embargo estos valores absolutos no son muy
fiables, dependiendo del medio físico al que estemos accediendo (scsi, fiber channel, sata, etc)
pueden saturar o no el canal.
• La columna %b si nos ofrece una estimación acerca de la saturación que ofrece el canal, si
su valor supera un 80% de forma sostenida tendremos una seria degradación de la
performance.
• Asvc_t es otra columna interesante, nos indica el tiempo de latencia del dispositivo en ms.
Por encima de 20 ms el usuario empieza a percibir lentitud al ejecutar sus comandos.
Al leer la salida de un iostat hay que tener en cuenta que varios file systems pueden compartir un
canal de acceso, lógicamente si uno de ellos está siendo saturado por una aplicación los demás se
verán igualmente penalizados.
Iostat es una buena herramienta para focalizar donde está el problema, pero no nos sirve para
diagnosticar, ya que nos quedamos a nivel de dispositivo y no podemos llegar al punto de identificar
un proceso culpable.

5.3.- Quien está accediendo continuamente a disco


Dtrace
Desde luego dtrace es altamente recomendable para cualquier administrador de sistemas en
OpenSolaris que se precie, especialmente si estamos tratando con temas de rendimiento. En el caso
de la i/o es imprescindible, ya que abre un "mundo nuevo" para el diagnóstico de este tipo de
problemas.
Si bien programar tus propios scripts puede ser recomendable, lo cierto es que usando los de que
incluye el Dtrace ToolKit nos bastará para la mayoría de situaciones, simplificándonos el
diagnóstico y ahorrándonos tiempo.
Como primer paso podemos usar iotop y rwtop, como sus nombres sugieren presentan información
en un formato similar al top para procesos, de modo que es fácil identificar los responsables del
elevado troughput.

22
Rwtop

root@box # ./Rwtop
Tracing... Please wait.
2008 Oct 13 16:23:43, load: 1.06, app_r: 6175 KB, app_w: 2747 KB
UID PID PPID CMD D BYTES
[...]
700 5828 1 oracle W 802816
700 5832 1 oracle W 1331200
700 22524 1 oracle R 5160960

iotop

root@box # ./iotop
Tracing... Please wait.
2008 Oct 13 16:27:17, load: 1.11, disk_r: 9447 KB, disk_w: 6421 KB
UID PID PPID CMD DEVICE MAJ MIN D BYTES
[...]
700 5828 1 oracle did30 239 64 W 3571712
700 5828 1 oracle ssd30 118 240 W 3571712
700 4083 1 oracle ssd30 118 240 R 3907584
700 4083 1 oracle did30 239 64 R 3907584
700 25311 1 oracle ssd30 118 240 R 4079616
700 25311 1 oracle did30 239 64 R 4079616

Ambas herramientas nos sirven para identificar los procesos que más acceso a disco están
realizando.

pfiles
Con el comando pfiles podemos comprobar que file descriptors tiene abiertos un proceso en
concreto.

23
root@box # pfiles 5828
5828: ora_dbw0_BBDD
Current rlimit: 65536 file descriptors
[...]
7: S_IFREG mode:0664 dev:85,40 ino:113073 uid:700 gid:701 size:269939168
O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE
/opt/oracle/app/oracle/admin/BBDD/bdump/alert_BBDD.log
8: S_IFREG mode:0660 dev:85,40 ino:55179 uid:700 gid:701 size:1552
O_RDWR|O_SYNC|O_LARGEFILE
/opt/oracle/app/oracle/product/10.2.0/db_1/dbs/hc_BBDD.dat
[...]

Quizás sea el momento de hablar con el dba, no ?


A veces es necesario profundizar un poco más antes de echar a correr la liebre. Vamos a ver unos
cuantos "one liners" de dtrace que nos pueden ser útiles para completar el diagnóstico.
# Para la prueba realizo un vi a un fichero README que están en /tmp
root@box # ps -ef | grep vi

root 8129 14128 0 09:53:46 pts/4 0:00 vi /tmp/README


root 11121 11018 0 09:55:29 pts/6 0:00 grep vi

# con el siguiente one liner podemos ver a que ficheros están


# escribiendo el proceso 8129

root@box # dtrace -n ' syscall::write:entry / pid == 8129 / { @dist[fds[arg0].fi_pathname] =


count(); } '
dtrace: description ' syscall::write:entry ' matched 1 probe
^C
/tmp/README 8
/devices/pseudo/pts@0:4 9

# como vemos nos muestra nuestro fichero y el dispositivo de terminal.


# Ahora haremos la operación inversa, es decir dado un fichero
# miraremos quien está escribiendo en el, para ello he lanzado un
# segundo vi al mismo archivo.

24
root@box # dtrace -n ' syscall::write:entry / fds[arg0].fi_pathname == "/tmp/README" / { @[pid]
= count(); }'
dtrace: description ' syscall::write:entry ' matched 1 probe
^C
8129 8
27144 16

Las posibilidades son enormes, podemos usar syscall::reads:entry para ver las lecturas, podemos ver
la cantidad de bytes escritos, etc. Es recomendable adquirir unas nociones básicas con dtrace ya que
nos permitirá realizar nuestros propios scripts a medida de nuestras necesidades.

5.4.- Conclusión
Gracias a dtrace ahora somos capaces de obtener información a nivel de file system, permitiéndonos
identificar los procesos responsables de la carga de i/o y los archivos más accedidos.

25
6.- Compitiendo por los recursos.

6.1.- Introducción

Por muy evolucionado que esté el hardware los recursos siempre son limitados, es decir, el sistema
operativo debe gestionarlos para que los distintos procesos puedan ejecutarse. Como medida de
protección cuando un recurso está siendo usado por un hilo el Kernel lo bloquea para que no pueda
ser usado por otro a la vez.
La perogrullada anterior es para que se entienda que los bloqueos en un sistema son normales, es
más, son necesarios para asegurar la integridad de los datos.
También hay que tener en cuenta que "los recursos" no es solamente hardware, existen las run
queues, la memoria virtual, las distintas cachés, etc. Todo ello debe ser accedido de forma
controlada, la coordinación se realiza principalmente por bloqueos.
Es decir, el próximo DBA, administrador de weblogic, etc ... que mencione los bloqueos del sistema
para justificar una perdida repentina de rendimiento, le mandáis a paseo. Si encima pretende que
reiniciéis el equipo sacrificadlo a los dioses del binario para aplacar su ira.

Si los bloqueos son normales, por que deberemos prestar atención a ellos ?
Hay que tener siempre claro que los bloqueos son síntomas y como tales nos ayudan a diagnosticar,
pero se producen como consecuencia de la actividad del sistema y esta es la responsable de la
degradación del rendimiento. Los bloqueos no degradan el rendimiento por si mismos.
Hay dos ocasiones en las cuales un análisis del comportamiento de los bloqueos puede ser de ayuda
en el diagnóstico de una perdida de rendimiento:
1.- Exceso de carga del sistema.
2.- Una aplicación escala mal.
En el primer caso pueden darte una pista adicional acerca del origen del problema. Por ejemplo si
detectas que el driver de la tarjeta de red sufre muchos mutex es probable que la carga de red esté
saturando el equipo. Profundizaremos un poco más a continuación cuando veamos como
diagnosticar.
El segundo caso es mucho más preocupante, si la aplicación es comprada a un tercero por mucho
que detectes el problema poco puedes hacer para solventarlo, la segunda opción es casi peor, puede
ser que esté desarrollada en tu propia empresa, con lo que te tocará pelearte con la gente de
desarrollo que, en general, negarán que el problema sea suyo. Hoy en día si en una aplicación pulsas
un botón y obtienes el resultado que esperas en la pantalla, está bien. Da igual si por el camino has
consumido chorro cientos ciclos de CPU y zampado 3 gigas de memoria.
Sangrante es el caso de algunas aplicaciones java, en muchos caso nada más arrancarlas lanzan más
de un centenar de threads que no paran de desalojarse mutuamente de las distintas CPUs.

26
Síntomas
Las aplicaciones se ejecutan lentamente, a pesar de que el sistema cuenta con recursos de sobra.

6.2.- Blocks a nivel de Kernel.


Cambios de contexto
En un mundo ideal los procesos se ejecutarían hasta que su quantum se agotara, sin embargo es
frecuente que sean desalojados de los procesadores por varias causas, cuando un proceso se
encuentra en espera por que trata de acceder a un recurso bloqueado se le envía a dormir, esto
provoca un cambio de contexto voluntario.
Podemos usar el comando mpstat para monitorizar la cantidad de estos que se han producido en el
sistema (columna csw).

root@box # mpstat 5
CPU minf mjf xcal intr ithr csw icsw migr smtx srw syscl usr sys wt idl
0 254 0 1374 455 7 1773 475 485 127 40 4795 46 14 0 40
1 266 0 1724 554 25 2145 549 552 162 40 4999 40 14 0 46
2 316 0 1432 725 296 1730 457 441 134 39 4730 46 15 0 39
3 252 0 1854 1905 1499 1435 383 416 197 36 4043 40 21 0 39

El comando lockstat
Con el comando lockstat podemos ver los bloqueos que se han producido a nivel de Kernel en un
intervalo de tiempo. La salida se divide en bloques dependiendo del tipo de bloqueo.
Para recopilar datos durante 5 segundos ejecutaremos
box#lockstat sleep 5

El primer bloque de información nos muestra los mutex adaptivies en los que el thread propietario
del bloqueo se estaba ejecutando en una cpu en el momento que otro thread ha intentado tomar
posesión de este.

Adaptive mutex spin: 14204 events in 5.106 seconds (2782 events/sec)

Count indv cuml rcnt spin Lock Caller


-------------------------------------------------------------------------------
2974 21% 21% 0.00 4 0x6000aa6ef80 fifo_write+0x5c
1087 8% 29% 0.00 5 0x6000cff5960 fifo_write+0x2ac
901 6% 35% 0.00 3 0x6000aa6ef80 fifo_read+0x28

27
566 4% 39% 0.00 5 0x6000cff5960 fifo_write+0x5c
523 4% 43% 0.00 5 0x6000aa6ef80 fifo_write+0x2ac
143 1% 44% 0.00 3 0x60010420060 aionotify+0x18
111 1% 44% 0.00 3 0x60010420060 aio_cleanup+0x30
99 1% 45% 0.00 3 0x60002889658 ssdintr+0x268
65 0% 46% 0.00 2 0x60002889658 ssdintr+0x18
61 0% 46% 0.00 3 0x3003f9c0060 aio_cleanup+0x30
59 0% 46% 0.00 2 0x30000009008 mod_hold_dev_by_major+0x60
[...]

Veamos el significado de las columnas más importantes:


• Count: Número de veces que se ha producido el bloqueo.
• indv: Este es el el % de ocasiones en las que la muestra que vemos en la pantalla se
corresponde con el total de eventos.
• spin: El número de veces que el thread a tenido que realizar un spin (bucle en espera que el
block se libere) antes de conseguir adueñarse de el.
• Lock: Dirección de memoria de la estructura del lock
• caller: Dirección de memoria desde la que se ha adquirido el block, generalmente expresada
de forma funcion+offset
Desde el punto de vista de performance debemos intentar localizar eventos que se den muy
frecuentemente o bien que el thread deba haber realizado muchos spinings para adueñarse del
bloqueo. Si nos encontramos ante la combinación de ambos casos implicara un problema
importante de performance.
Veamos el siguiente bloque de la salida del lockstat.

-------------------------------------------------------------------------------

Adaptive mutex block: 334 events in 5.106 seconds (65 events/sec)

Count indv cuml rcnt nsec Lock Caller


-------------------------------------------------------------------------------
25 7% 7% 0.00 1495276 0x600104f8068 aio_cleanup+0x28
23 7% 14% 0.00 75221 0x6000aa6ef80 fifo_write+0x5c
22 7% 21% 0.00 54495 0x6000cff5960 cv_wait_sig_swap_core+0x158
14 4% 25% 0.00 231178 0x60001728c40 post_syscall+0x2fc
12 4% 29% 0.00 53233 0x6000aa6ef80 cv_wait_sig_swap_core+0x158
9 3% 31% 0.00 59322 0x60010420060 cv_timedwait_sig+0x188

28
7 2% 34% 0.00 37771 0x60001725900 issig_forreal+0x20
5 1% 35% 0.00 82760 0x60010420060 aio_cleanup+0x30
5 1% 37% 0.00 48300 0x3003f9c0060 cv_timedwait_sig+0x188
4 1% 38% 0.00 58625 0x6000cff5960 fifo_write+0x2ac
4 1% 39% 0.00 66850 0x6000cff5960 fifo_write+0x5c
4 1% 40% 0.00 232650 0x6000aa6ef80 fifo_write+0x2ac
[...]

El siguiente bloque nos da estadísticas acerca de los bloqueos tipo mutex que cuando el thread ha
tratado de adquirirlo estaba ocupado por otro thread que no se estaba ejecutando actualmente. Como
resultado hemos enviado nuestro hilo a dormir. Obviamente el impacto desde el punto de vista de la
performance es más acusado.
Los campos son los mismos que en el anterior bloque con la salvedad que la columna spin ha sido
sustituida por la nsec. Esto es el numero de nanosegundos que el thread a tenido que permanecer el
la sleep queue antes de que se liberara el lock.
La interpretación de los datos es parecida que en el caso anterior, si el evento se produce muchas
veces o tiene un coste de tiempo muy elevado deberemos tratar de obtener más información acerca
de él.

-------------------------------------------------------------------------------
Spin lock spin: 59660 events in 5.106 seconds (11683 events/sec)

Count indv cuml rcnt spin Lock Caller


-------------------------------------------------------------------------------
10349 17% 17% 0.00 6 cp_default disp_getbest+0x4
4119 7% 24% 0.00 6 0x60001a37cb0 disp_getbest+0x4
3987 7% 31% 0.00 6 0x60001a37980 disp_getbest+0x4
3888 7% 37% 0.00 6 0x60001a378c0 disp_getbest+0x4
3450 6% 43% 0.00 6 0x60001a37800 disp_getbest+0x4
3334 6% 49% 0.00 6 0x60001a37aa0 disp_getbest+0x4
3330 6% 54% 0.00 6 cpu0_disp disp_getbest+0x4
2895 5% 59% 0.00 4 0x60001a37aa0 disp+0x84
[...]

El tercer bloque nos presenta información acerca de los bloqueos mutex spin. En estos casos el
thread que quiere adueñarse del lock siempre se quedara haciendo un spining (bucle) y nunca se
mandara a dormir. Este tipo de locks se usa para sincronizar el acceso a estructuras de datos que
suelen sufrir bloqueos breves. Muchos de ellos corresponden a estructuras de datos del Kernel como
por ejemplo las dispatch queues.

29
Los criterios para su análisis deben ser parecidos al primer bloque.

R/W writer blocked by writer: 42 events in 5.106 seconds (8 events/sec)

Count indv cuml rcnt nsec Lock Caller


-------------------------------------------------------------------------------
6 14% 14% 0.00 216083 0x6000f7ea168 sam_proc_get_lease+0x1f0
6 14% 29% 0.00 91800 0x6000f7ddc90 sam_proc_get_lease+0x1f0
6 14% 43% 0.00 167066 0x6000f7a2460 sam_proc_get_lease+0x1f0
4 10% 52% 0.00 205875 0x6000f7ea168 sam_dk_direct_io+0xb6c
4 10% 62% 0.00 298950 0x6000f7ea168 sam_write_vn+0x13c
4 10% 71% 0.00 419550 0x6000f7a2460 sam_write_vn+0x13c
3 7% 79% 0.00 183866 0x6000f7ddc90 sam_write_vn+0x13c
2 5% 83% 0.00 123250 0x6000f7e9ca0 sam_proc_get_lease+0x1f0
2 5% 88% 0.00 104450 0x6000f7de620 sam_proc_get_lease+0x1f0
1 2% 90% 0.00 74100 0x6000f7e9ca0 sam_write_vn+0x13c
1 2% 93% 0.00 28700 0x6000f7e9ca0 sam_dk_direct_io+0xb6c
1 2% 95% 0.00 128500 0x6000f7e37d0 sam_proc_get_lease+0x1f0
1 2% 98% 0.00 95600 0x6000f7de620 sam_write_vn+0x13c
1 2% 100% 0.00 59100 0x6000f7de158 sam_proc_get_lease+0x1f0
-------------------------------------------------------------------------------

R/W writer blocked by readers: 23 events in 5.106 seconds (5 events/sec)

Count indv cuml rcnt nsec Lock Caller


-------------------------------------------------------------------------------
4 17% 17% 0.00 118800 0x6000f7ddc90 sam_write_vn+0x13c
3 13% 30% 0.00 299133 0x6000f7ea168 sam_write_vn+0x13c
3 13% 43% 0.00 283566 0x6000f7a2460 sam_write_vn+0x13c
2 9% 52% 0.00 144900 0x6000f7ea168 sam_dk_direct_io+0xb6c
2 9% 61% 0.00 78600 0x6000f7ea168 sam_proc_get_lease+0x1f0
1 4% 65% 0.00 100000 0x6000f7e9ca0 sam_write_vn+0x13c
1 4% 70% 0.00 53300 0x6000f7e37d0 sam_write_vn+0x13c
1 4% 74% 0.00 160700 0x6000f7de620 sam_write_vn+0x13c
1 4% 78% 0.00 79900 0x6000f7de158 sam_write_vn+0x13c
1 4% 83% 0.00 201100 0x6000f7ddc90 sam_dk_direct_io+0xb6c
1 4% 87% 0.00 36600 0x6000f7ddc90 sam_proc_get_lease+0x1f0
1 4% 91% 0.00 65100 0x6000f7a2460 sam_dk_direct_io+0xb6c

30
1 4% 96% 0.00 86100 0x3001ba97c58 as_map+0x38
1 4% 100% 0.00 339800 0x6000f7a2460 sam_proc_get_lease+0x1f0
-------------------------------------------------------------------------------

R/W reader blocked by writer: 23 events in 5.106 seconds (5 events/sec)

Count indv cuml rcnt nsec Lock Caller


-------------------------------------------------------------------------------
6 26% 26% 0.00 61300 0x6000f7ea168 sam_dk_direct_io+0x5d8
5 22% 48% 0.00 59180 0x6000f7ddc90 sam_dk_direct_io+0x5d8
4 17% 65% 0.00 70900 0x6000f7a2460 sam_dk_direct_io+0x5d8
3 13% 78% 0.00 72333 0x6000f7de620 sam_dk_direct_io+0x5d8
2 9% 87% 0.00 99850 0x6000f7e9ca0 sam_dk_direct_io+0x5d8
1 4% 91% 0.00 54600 0x6000f7e37d0 sam_dk_direct_io+0xb6c
1 4% 96% 0.00 141200 0x6000f7e37d0 sam_dk_direct_io+0x5d8
1 4% 100% 0.00 82400 0x6000f7de158 sam_dk_direct_io+0x5d8
-------------------------------------------------------------------------------

R/W reader blocked by write wanted: 8 events in 5.106 seconds (2 events/sec)

Count indv cuml rcnt nsec Lock Caller


-------------------------------------------------------------------------------
3 38% 38% 0.00 124533 0x6000f7ea168 sam_dk_direct_io+0x5d8
3 38% 75% 0.00 104266 0x6000f7a2460 sam_dk_direct_io+0x5d8
2 25% 100% 0.00 129600 0x6000f7ddc90 sam_dk_direct_io+0x5d8
-------------------------------------------------------------------------------

El último grupo de bloques se refiere a los lock R/W, divididos en grupos según la actividad que
estén realizando los threads involucrados ( lectura o escritura).
Como en los casos anteriores nos fijaremos en el número de eventos y el coste en nanosegundos
para estimar cuales pueden ser los que estén penalizando el rendimiento del sistema.

Quién es el responsable de todos estos bloqueos?


Después de leer todo lo anterior os estaréis preguntando que utilidad tiene, al fin y al cabo solo
hemos podido recopilar un montón de direcciones de memoria, funciones, y estadísticas ... pero lo
que le interesa a un administrador es poder identificar que PIDs son los que están generando todos
esos bloqueos.
En nuestro ejemplo vemos que la siguiente función esta siendo ejecutada un montón de veces y esta

31
teniendo problemas para adueñarse de algunos locks.

25 7% 7% 0.00 1495276 0x600104f8068 aio_cleanup+0x28


111 1% 44% 0.00 3 0x60010420060 aio_cleanup+0x30
61 0% 46% 0.00 3 0x3003f9c0060 aio_cleanup+0x30

A quien esta afectando. dtrace es nuestro amigo. Lo primero es localizar la prueba que nos interesa
para monitorizar nuestra función.

root@box # dtrace -l | grep aio_cleanup


2023 fbt unix dr_aio_cleanup_thread entry
2024 fbt unix dr_aio_cleanup_thread return
5196 fbt genunix aio_cleanup_cleanupq entry
5197 fbt genunix aio_cleanup_cleanupq return
[...]
13875 fbt genunix aio_cleanup entry
13876 fbt genunix aio_cleanup return
[...]

Después ejecutamos dtrace para que nos informe de todos los pids que están ejecutando dicha
función.

root@box # dtrace -n 'fbt::aio_cleanup: { printf("%6d %s\n", pid, execname); }'


dtrace: description 'fbt::aio_cleanup: ' matched 2 probes
CPU ID FUNCTION:NAME
16 13875 aio_cleanup:entry 21939 oracle
16 13876 aio_cleanup:return 21939 oracle
0 13875 aio_cleanup:entry 21939 oracle
0 13876 aio_cleanup:return 21939 oracle
0 13875 aio_cleanup:entry 21939 oracle
0 13876 aio_cleanup:return 21939 oracle

Lo siguiente es un poco de la "magia" de la shell, redirigimos a un fichero, ordenamos y contamos


para ver los pids que más frecuentemente llaman a la función. La primera columna es el número de
ocurrencias, la segunda el pid.

32
root@box # cat /tmp/aio.txt | awk ' { print $4 }' | sort | uniq -c
4 13846
4 14950
22 18856
2 18872
4 21659
2366 21939
194 24669
8 27434
4 27581
18 27585
130 27632
52 27634
1250 27636
402 27638
4 27677
4 28103
2 29590
4 3759
594 7966
8 8172

Podemos observar que un gran % de eventos los generan únicamente dos pids (21939 y 27636).
Finalmente combinamos ese resultado con un grep para ver que es cada uno de los pids.

root@box # for i in `cat /tmp/aio.txt | awk ' { print $4 }' | sort | uniq`; do ps -ef | grep $i | grep -v
grep; done;

oracle 13846 1 0 Sep 27 ? 7248:54 ora_j003_BBDD2


oracle 14950 1 0 Nov 08 ? 410:33 ora_j005_BBDD2
oracle 18856 1 0 09:26:24 ? 0:35 oracleBBDD2 (LOCAL=NO)
oracle 18872 1 0 09:26:24 ? 0:33 oracleBBDD2 (LOCAL=NO)
oracle 21659 1 0 09:28:01 ? 0:26 oracleBBDD2 (LOCAL=NO)
oracle 21939 21931 4 22:00:03 ? 185:39 oracleBBDD2 (DESCRIPTION=(LOCAL=YES)
(ADDRESS=(PROTOCOL=beq)))
oracle 24669 1 0 07:46:59 ? 1:52 oracleBBDD2 (LOCAL=NO)
oracle 27434 1 0 Nov 02 ? 741:49 ora_j006_BBDD2
oracle 27581 1 0 Apr 22 ? 362:22 ora_diag_BBDD2
oracle 27585 1 0 Apr 22 ? 2917:07 ora_lmon_BBDD2

33
oracle 27632 1 0 Apr 22 ? 8275:19 ora_dbw0_BBDD2
oracle 27634 1 0 Apr 22 ? 9106:27 ora_dbw1_BBDD2
oracle 27636 1 1 Apr 22 ? 5161:12 ora_lgwr_BBDD2 oracle 27638 1 0 Apr 22 ?
551:26 ora_ckpt_BBDD2
oracle 27677 1 0 Apr 22 ? 224:17 ora_mmon_BBDD2
oracle 28103 1 0 Apr 22 ? 133:56 ora_arc1_BBDD2 oracle 29590 1 0 Jun 04 ?
175:06 ora_j007_BBDD2
oracle 3759 1 0 Nov 09 ? 7:39 ora_j008_BBDD2
oracle 7966 1 1 19:52:26 ? 99:50 ora_j000_BBDD2
oracle 8172 1 0 09:20:22 ? 0:37 oracleBBDD2 (LOCAL=NO)

Como vemos los procesos pertenecen a una B.D. oracle, en este momento deberíamos hablar con
nuestro DBA para contar con su ayuda para continuar nuestra investigación.

6.3.- Bloqueos a nivel de usuario


El comando plockstat
Nuestra aplicación también puede sufrir bloqueos a nivel de usuario. Estos se crean desde el código
de una aplicación usando las llamadas mutex_init() o rwlock_init(). Lógicamente su función es
sincronizar el acceso de los distintos threads a determinados recursos.
OpenSolaris nos permite tracear dichos bloqueos con el comando plockstat, usa dtrace
internamente, de forma que podemos medir el impacto en la performance. En la página man
encontraremos explicadas las distintas opciones; nosotros usamos las opciones -A para que nos
reporte todos los eventos, con la opción -p podemos monitorizar a un pid existente.

plockstat -A cat /etc/passwd


[...]
Mutex hold
Count nsec Lock Caller
1 70200 libc.so.1`_uberdata+0xfc0 cat`_start+0x110
1 40900 libc.so.1`__sbrk_lock libc.so.1`_smalloc+0x4c
1 39400 libc.so.1`_uberdata+0x40 LM1`ld.so.1`call_init+0x70
1 34300 libc.so.1`_uberdata+0x40 LM1`ld.so.1`call_init+0x70
1 27600 libc.so.1`_uberdata+0xfc0 cat`_start+0xac
1 24700 libc.so.1`_uberdata+0x40 cat`main+0x24
1 23100 libc.so.1`_uberdata+0x40 cat`main+0x24
1 4900 libc.so.1`_uberdata+0xfc0 cat`_start+0xb8

34
La salida es casi autoexplicativa:
• Count: Número de veces que se ha producido el evento
• nsec: Duración media en nanosegundos del bloqueo
• Lock: Nombre del bloqueo
• Caller: Función más offset que ha llamado al bloqueo.
Estos datos pueden ser de una gran ayuda para que nuestros desarrolladores mejoren la performance
de sus aplicaciones.

6.4.- Conclusión
Desgraciadamente el mundo de los bloqueos es complejo y difuso, en general solo obtendremos un
montón de direcciones de memoria, funciones y estadísticas de difícil interpretación. Llegar a
identificar un proceso culpable suele ser complicado y es necesaria bastante experiencia.
No se debe olvidar que el bloqueo en si no suele ser el problema, más bien es un acción necesaria
como consecuencia de la actividad en el sistema, por lo que debemos centrarnos en investigar que
se está ejecutando usando los bloqueos como información para llegar a los procesos problemáticos.

35
7.- Analizando un proceso.

7.1.- Introducción

En los artículos anteriores después de realizar nuestros análisis nos quedábamos con una pequeña
lista de PIDs los cuales deberíamos estudiar con detenimiento.
En la mayoría de sistemas *nix nuestras opciones son bastante limitadas, podemos obtener
información de las llamadas al sistemas que están realizando con herramientas tipo strace o truss,
también podemos ver a grosso modo consumo de cpu y memoria usando el top y finalmente en
/proc obtendremos datos acerca de File Descriptors y algunas cosas más, sin embargo muchas veces
no son datos suficientes como para realizar un análisis detallado.
En OpenSolaris afortunadamente podemos ver cualquier cosa referente a nuestro proceso,
empezando por su estructura en el Kernel, su stack, sus contadores, etc. La limitación será nuestro
conocimiento y el tiempo que queramos dedicarle.
Analizar un proceso no es algo sistemático, no hay una lista de tareas a realizar, lo mejor es conocer
las herramientas de las que dispone tu sistema y dejarte guiar por la intuición. Este capítulo es un
compendio de cosas que suelo hacer, ni están todas ni es definitivo, espero poco a poco irlo
mejorando.

7.2.- Que hace el proceso ?


Una primera aproximación puede ser comprobar que llamadas al sistema esta realizando nuestro
proceso, para ello podemos usar truss o dtruss, en caso de una alta carga en el equipo es
recomendable la segunda opción.
Veamos un ejemplo sencillo con un proceso correspondiente a una sesión ssh.

bash-3.00# ps -ef | grep 1565


sistemas 1567 1565 0 13:08:01 pts/1 0:00 -sh
sistemas 1565 1562 0 13:08:01 ? 0:01 /usr/lib/ssh/sshd

bash-3.00# truss -c -p 1565


^C
syscall seconds calls errors
read .000 1
write .000 1
lwp_sigmask .000 4

36
pollsys .000 2
-------- ------ ----
sys totals: .000 8 0
usr time: .000
elapsed: 1.410
sh-3.00# dtruss -c -p 1565
^C
CALL COUNT
write 2026
pollsys 2031
read 2198
lwp_sigmask 4064

Lo que podemos decir es que nuestro proceso esta escribiendo y leyendo aunque no sabemos donde,
las llamadas pollsys y lwp_sigmask puede que nos suenen a chino, en la sección 2 de las páginas
man tenemos una descripción completa de ellas. La primera está relacionada con la gestión de los
FD (File Descriptors) y la segunda con las señales del proceso.
Veamos donde están escribiendo nuestro proceso:

bash-3.00# dtruss -t write -p 1565


SYSCALL(args) = return
write(0x8, "h\0", 0x1) =10
write(0x4,"\377U\312\246q\237PrG\301\321\357\001\354\314\266P\004\266V\v>\245\177\330\3
55Q\311up\375\256S\254\220#\267zf\346\305\250LJ\033\213\300\aW\026\225b\0",0x34)
= 52 0
write(0x8, "o\0", 0x1) =10
write(0x4,
"y\277\274\251\232b)\204\236F+\004\213\260,1\231\254\"\030\314\205\360\204\257\ 35242m\
357\a\311\335\304\v\245I\340\260\257\342\036Z\300\346\312\217\216f'\026d\0", 0x34)
= 52 0
write(0x8, "l\0", 0x1) =10
write(0x4, "TL\332\362\314\366\356\343*\273H<x3h@\004\276\rNOI!Y \
001\210m\231\333\267\006\324\004iFHVo\324\302\025\t\033O\264\"\302\337\023\363h\0",0x34
) = 52 0
write(0x8, "a\0", 0x1) =10
write(0x4,
"\312\001\262J$c*+f\347a7\323\024\276\203?\256\023\322E\241r\204\276\211\001<\373\364\2
27\224\336\304Wj'\v+\323x\3434!\3155\214@\343\0309\0", 0x34) = 52 0
write(0x8, "\r\0", 0x1) =10
write(0x4,":e\211~k\022@\337\300:Qq\025\177\357UN\363\223F+t\253\353~\320\254\352*W7\
213\341 \272\225!\220*\"\031\210\311\324\211g\326\204j\210%*\0", 0x34) = 52 0

37
write(0x4,
"\033\360C\245\242\344bp[\320\332\202\220\237\335\035\032n\245Z\236fH\005D\330\210\
314\200\274\017\nm\307]KW\276\003%b\252~\215!&w\0", 0x54) = 84 0
write(0x4, "/_0.8\0", 0x34) = 52 0

El primer campo de la llamada write es el FD, en este caso estamos escribiendo sobre dos distintos
el 0x4 y 0x8, con el comando pfiles podemos ver los FD abiertos por un proceso:

bash-3.00# pfiles 1565


1565: /usr/lib/ssh/sshd
Current rlimit: 256 file descriptors
[...]
4: S_IFSOCK mode:0666 dev:360,0 ino:20920 uid:0 gid:0 size:0
O_RDWR|O_NONBLOCK
SOCK_STREAM
SO_REUSEADDR,SO_KEEPALIVE,SO_SNDBUF(49152),SO_RCVBUF(49640),IP_NEXTHOP(0
sockname: AF_INET6 ::ffff:10.100.9.30 port: 22
peername: AF_INET6 ::ffff:172.28.4.77 port: 1434
[...]
8: S_IFCHR mode:0000 dev:353,0 ino:32641 uid:0 gid:0 rdev:23,1
O_RDWR|O_NONBLOCK|O_NOCTTY|O_LARGEFILE
/devices/pseudo/clone@0:ptm

Bien ahora si que está claro, nuestro proceso está escribiendo por un lado en un socket y por el otro
en una terminal, si repitiésemos el experimento con la llamada read veríamos resultados parecidos.
Como vemos analizar una sesión ssh ha sido sencillo, sin embargo las cosas no son tan simples
siempre.

Alto consumo de CPU.


El comando prstat puede ser un buen punto de partida para detectar un uso anómalo de la cpu ya
que nos desglosa el tiempo de sys y usr por proceso.

PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/NLWP 21346 oracle
93 3,9 0,1 0,0 0,0 0,0 3,1 0,1 16 529 1K 0 gzip/1

38
Cuando un proceso está consumiendo mucho procesador deberíamos tratar de averiguar el porque,
con truss podemos desglosar su consumo entre tiempo de usuario y de sistema, y este último
dividirlo por las distintas llamadas que está realizando.

syscall seconds calls errors


read .069 42
semop .014 652
pollsys .010 342
-------- ------ ----
sys totals: .094 1036 0
usr time: 4.803
elapsed: 5.520

Como vemos en el ejemplo cada llamada read ha costado .069 segundos y se han producido 42
llamadas de ese tipo, de los 5.520 segundos de muestra 0.094 han sido consumidos en tiempo de
sistema y 4.803 en tiempo de usuario. Deberíamos fijarnos si hay un exceso de consumo en tiempo
de sistema y si se centra en alguna/s llamada/s en concreto, en dicho caso debemos consultarlo con
el desarrollador o el administrador de dicha aplicación.

La memoria
El consumo de memoria por parte de un proceso siempre ha sido uno de los temas más enigmáticos
para mí, ya que el tamaño que reportan las herramientas tipo ps, prstat, etc no suele ser útil para
saber si está reclamando más memoria o no. Con esta simple línea de Dtrace podemos ver si nuestro
proceso está provocando minor faults, síntoma de que está reclamando más páginas.

bash-3.00# dtrace -n ' vminfo:::as_fault /pid==4538/ { @mem[pid] = sum(arg0);


}'
dtrace: description ' vminfo:::as_fault ' matched 1 probe
^C
4538 12

Comprobar la IO
Como hemos visto pfiles nos lista los FD abiertos por el proceso, sin embargo no nos lista la
cantidad de bytes escritos o leídos en cada uno de ellos, podemos usar el comando pfilestat incluido
en Dtrace Toolkit para ver la cantidad de datos escritos o leídos en cada FD.

39
bash-3.00# ./pfilestat 4768
STATE FDNUM Time Filename
read 9 0% /export/home/sistemas/DTraceToolkit-0.99.tar
read 9 0% /export/home/sistemas/install_stb.sh
read 9 0% /export/home/sistemas/install_stb[1].sh.tar
sleep-w 0 1%
write 1 2% /devices/pseudo/pts@0:6
write 3 7% /test.tgz
sleep 0 12%
running 0 13%
waitcpu 0 22%
sleep-r 0 35%

STATE FDNUM KB/s Filename


read 13 157 /etc/gconf/gconf.xml.defaults/schemas/apps/n
read 9 176 /etc/gconf/schemas/apps_nautilus_preferences
read 9 185 /etc/gconf/schemas/metacity.schemas
read 9 187 /etc/gconf/schemas/gok.schemas
read 9 226 /etc/gconf/schemas/gnome-terminal.schemas
read 9 457 /kernel/drv/sparcv9/ip
read 9 755 /export/home/sistemas/DTraceToolkit-0.99.tar
read 9 1913 /export/home/sistemas/install_stb.sh
read 9 1914 /export/home/sistemas/install_stb[1].sh.tar
write 3 11779 /test.tgz

Total event time (ms): 4976 Total Mbytes/sec: 22

Volcado del stack de un thread


A veces puede ser interesante comprobar que está haciendo un thread en concreto, podemos volcar
su stack usando pstack o mdb:

bash-3.00# mdb -k
Loading modules: [ unix genunix specfs dtrace ufs ssd fcp fctl qlc pcisch ip
hook neti sctp arp usba nca md lofs zfs nfs random sd sppp crypto ptm cpc
wrsmd fcip logindmux ]
> ::ps

40
S PID PPID PGID SID UID FLAGS ADDR NAME
R 0 0 0 0 0 0x00000001 0000000001839750 sched
R 3 0 0 0 0 0x00020001 00000600111e9848 fsflush
R 2 0 0 0 0 0x00020001 00000600111ea468 pageout
[...]
R 8015 8011 8015 8007 0 0x4a014000 00000600171f50f8 bash
[…]

#ps nos devuelve la dirección de memoria de la estructura del proceso

> 00000600171f50f8 ::walk thread


300023744a0

#con walk thread obtenemos la dirección de las estructuras de los threads que componen el proceso,
en este caso solo tenemos uno.

> 300023744a0 ::findstack


stack pointer for thread 300023744a0: 2a100d6adb1
[ 000002a100d6adb1 cv_wait_sig+0x114() ]
000002a100d6ae61 str_cv_wait+0x28()
000002a100d6af21 strwaitq+0x238()
000002a100d6afe1 strread+0x19c()
000002a100d6b0b1 fop_read+0x20()
000002a100d6b161 read+0x274()
000002a100d6b2e1 syscall_trap32+0xcc()

#findstack nos vuelca el stack de ese thread, como vemos está detenido en un condition wait.

41
8.- Las estadísticas.

Introducción

Todos los sistemas suelen tener un patrón de carga habitual, es frecuente que un administrador se lo
conozca de memoria, especialmente si tiene pocos servidores a su cargo. Sin embargo, es
importante guardar un registro. Algunos motivos pueden ser:

• Si administras muchos equipos es imposible conocer el patrón de carga de cada uno de ellos.
• Aunque lo conozcas siempre habrá alguien dispuesto a discutirlo, especialmente compañeros
de otras áreas más interesados en escurrir el bulto que en solucionar el problema. En estas
situaciones tener un histórico almacenado te evitará enzarzarte en discusiones inútiles.
• Si tu herramienta es buena puedes acceder a datos que habitualmente no compruebas.
• Muchas más que seguro que se os ocurren a vosotros mismos. :)

Es sumamente importante conocer (o poder consultar) el patrón de carga habitual en el equipo


donde tenemos la perdida de rendimiento, si esa información no está disponible nos dificultará
detectar el origen de esta.

Las herramientas de monitorización

Hay herramientas de monitorización realmente estupendas, algunas mediocres y muchas pésimas.


El problema siempre suele ser el mismo, tu no la vas a poder escoger, ese tipo de decisiones las
suelen tomar unas personas que van con corbata y se sientan hablar con comerciales (que también
llevan corbata) y que, desde luego, su principal preocupación es que el producto escogido sea la
mejor solución técnica posible para vuestras necesidades... o no.
Si eres afortunado y cuentas con una herramienta que almacena datos de todos los parámetros que
necesitas, los presenta de forma que es fácil interpretarlos y además funciona relativamente ágil,
enciende un cirio a los dioses del binario pues te han bendecido. Lo normal es que la herramienta
con la que vayas a lidiar tenga alguna (o muchas) carencias importantes.

Los scripts

Una buena forma de paliar las deficiencias que pueda haber en tu sistema de monitorización es
programar una tarea en el crontab que ejecute un script, y almacenar su salida en un fichero/s para
posteriormente ser consultada.
Puedes optar por desarrollarlo tu mismo, sin embargo es fácil encontrar scripts muy completos ya
hechos y que puedes adaptar rápidamente a tus necesidades.

42
Personalmente habitualmente suelo usar sys_diag de Todd A. Jobson, muy completo y
personalizable.

¿Que datos debo almacenar?


Normalmente no podremos almacenar todos los datos que queremos, así que es importante
seleccionar aquellos que nos definan de forma más fiable los patrones de carga de nuestros equipos.
La siguiente lista es de mínimos y orientativa, dependiendo del tipo de servicio que ofrezca nuestro
sistema puede ser interesante algún dato adicional.

• Procesos, threads y sus microstados (prstat -m).


• Cantidad de procesos en las run queues.
• % de tiempo de sistema, usuario e idle.
• Llamadas al sistema por proceso.
• Llamadas al sistema totales.
• Número de cambios de contexto (voluntarios e involuntarios).
• Cantidad de memoria usada.
• Throughput en los distintos dispositivos.
• Listado de sockets y su estado.
• Salida de un lockstat.
• etc

A efectos prácticos disponer de la salida de un prstat, prstat -m, vmstat, iostat, netstat y lockstat nos
proporcionará datos suficientes para constatar las desviaciones que se han producido.
El patrón de carga es distinto según la hora del día dependiendo de la actividad que se está
realizando, así que asegúrate que tienes muestras de las distintas franjas.

Conclusión

En muchos casos careceremos de capacidad de análisis si carecemos de un histórico de la carga del


sistema, en general usaremos una herramienta de monitorización a la que probablemente
necesitaremos complementar con algún script local.

43
Anexo A: Dtrace one liners

• Ver actividad del Page Scanner,

dtrace -n 'vminfo::pageout_scanner: { @num[probefunc] = count (); }'

• Procesos nuevos con argumentos,

dtrace -n 'proc:::exec-success { trace(curpsinfo->pr_psargs); }'

• Ficheros abiertos por proceso,

dtrace -n 'syscall::open*:entry { printf("%s %s",execname,copyinstr(arg0)); }'

• Número de cada tipo de llamadas al sistema,

dtrace -n 'syscall:::entry { @num[probefunc] = count(); }'

• Llamadas al sistema por proceso,

dtrace -n 'syscall:::entry { @num[pid,execname] = count(); }'

• Bytes leidos por proceso,

dtrace -n 'sysinfo:::readch { @bytes[execname] = sum(arg0); }'

• Bytes escritos por proceso,

dtrace -n 'sysinfo:::writech { @bytes[execname] = sum(arg0); }'

• Número de páginas paged in por proceso,

dtrace -n 'vminfo:::pgpgin { @pg[execname] = sum(arg0); }'

44
• Minor faults por proceso,

dtrace -n 'vminfo:::as_fault { @mem[execname] = sum(arg0); }'

45
Anexo B: Llamadas al sistema

Podemos definir las llamadas a sistema como una interfaz entre el área de usuario y la de Kernel, si
un proceso necesita realizar una tarea que implique acceder a la área de Kernel, posiblemente por
que no tenga privilegios para hacerlo el directamente, debe hacerlo a través de una llamada al
sistema. Ejemplos típicos seria acceder a un dispositivo de i/o, crear un proceso hijo, esperar a que
se cumpla una condición, ...
El listado completo de llamadas al sistema que existen lo encontramos en el fichero
/usr/include/sys/syscall.h.

Rastreando las llamadas de sistema en nuestro equipo


La información acerca de que llamadas a sistema se están ejecutando nos puede dar pistas de la
actividad de los distintos procesos en nuestro equipo. Lógicamente procesar una llamada tiene un
coste a nivel de recursos, sin embargo dependiendo de la tarea que deba realizar su coste es distinto.
El tiempo empleado en dicha tarea es computado como tiempo de sistema, por ello la columna sy en
el apartado faults (system calls) y sy en el apartado cpu (tiempo de sistema) de la salida del vmstat
están íntimamente relacionadas.

root@box # vmstat 5
kthr memory page disk faults cpu
rbw swap free re mf pi po fr de sr 2m m0 m1 m2 in sy cs us sy
id
100 42157120 7573496 585 2219 1920 7 9 0 6 44 2 1 1 19612 34991 12905 42 15 43
0 0 0 41882080 7348816 546 2233 2175 0 0 0 0 35 0 0 0 12435 29385 6561 32 9 59
0 0 0 41882512 7344728 584 2393 2162 0 0 0 0 36 20 19 19 10922 32580 7231 28 9 63
0 0 0 41884936 7342656 628 2809 2174 0 0 0 0 35 0 0 0 11673 43611 8204 31 9 60
[...]

46
Anexo C: Page Scanner

Normalmente cuando leemos un manual de tunning en Solaris, en el apartado de memoria siempre


se menciona que observes la columna sr de la salida del vmstat, si esta es mayor que 0 indica que el
sistema tiene escasez de memoria física. Esta columna (scan rate) indica el número de veces que se
ha ejecutado el Page Scanner en el intervalo de tiempo que hayamos especificado al vmstat. Pero
¿como funciona ? en este articulo lo trataremos con algo más de profundidad.
El Page Scanner se encarga de robar páginas de memoria dejándolas libres para ser usadas y
enviando a la swap su contenido. Si estas páginas son requeridas más adelante se producirá un page
fault y volverán a ser copiadas a la memoria física. Lógicamente esto afecta al rendimiento de las
aplicaciones que las estaban usando por ello se trata de robar aquellas que hace más tiempo que no
han sido accedidas.
El Page Scanner controla que páginas han sido accedidas mediante la lectura de dos bits en el
hardware de la memoria . Estos indican si la página ha sido leída o modificada desde la última vez
que se pusieron a cero. Entra en acción cuando la cantidad de memoria libre es menor que lostfree
que se calcula en el arranque del sistema y suele ser 1/64 de la memoria física disponible. Esta
comprobación se realiza mediante la función schedpaging(). Existe una entrada en la tabla callout
que la ejecuta 4 veces por segundo. También puede ser llamada por el page_allocator.
Para explicar su funcionamiento pensaremos en el como si se tratara de un reloj, las dos manillas
son dos threads del Kernel y las horas las distintas páginas de la memoria. A diferencia de un reloj
normal, las dos manillas del nuestro giran a la misma velocidad manteniendo entre ellas un espacio
y nunca se llegan a superponer.

El thread A pone a 0 los bits del hardware que indican si la memoria a sido modificada/leída, el
thread B comprueba si estos bits han sido modificados, en caso negativo envía a la swap el
contenido de la página y la marca como libre. Ambos threads corresponden al pid 2.

47
La velocidad de "rotación" los threads es inversamente proporcional a la cantidad de memoria libre
del sistema, y el espacio entre ellos es determinado por el parámetro handsoreadpages que se
calcula de forma dinámica. El parámetro pageout_new_spread indica el número máximo de páginas
que pueden ser escaneadas en un segundo o dicho de otra forma fija la velocidad máxima de
escaneo, es calculado la primera vez que el scanner se ejecuta y se almacena en la variable fastscan.
Algunas peculiaridades:
Existe una limitación para que el scanner no robe páginas de librerías compartidas, el número de
mapeos que debe existir para no ser robada es almacenado en el parámetro po_share, inicialmente
tiene un valor de 8 pero puede decrementarse en caso de que el scanner no encuentre páginas para
liberar.
También existe una limitación del tiempo de cpu que puede ser consumido por los threads, hay dos
parámetros el min_precent_cpu y el max_percent_cpu, cuando la memoria libre es igual al
parámetro lostfree su consumo es el valor mínimo, cuando no hay memoria libre su valor es el
máximo. Por defecto son el 4% y el 80% de una cpu respectivamente.

Viendo el código fuente.

Podemos echar un vistazo al código fuente en la web de opensolaris.org, concretamente en el


fichero vm_pageout.c, voy a copiar algunos extractos para ilustrar el articulo pero esta
sobradamente documentado por lo que su lectura completa puede resultar interesante.
Cálculo del parámetro lostfree:

252 /*
253 * Lotsfree is threshold where paging daemon turns on.
254 */
255 if (init_lfree == 0 || init_lfree >= looppages)
256 lotsfree = MAX(looppages / 64, btop(512 * 1024));
257 else
258 lotsfree = init_lfree;
Inicialización de fastscan:
416 if (init_mfscan == 0) {
417 if (pageout_new_spread != 0)
418 maxfastscan = pageout_new_spread;
419 else
420 maxfastscan = MAXHANDSPREADPAGES;
421 } else {
422 maxfastscan = init_mfscan;
423 }
424 if (init_fscan == 0)
425 fastscan =MIN(looppages / loopfraction,maxfastscan);

48
426 else
427 fastscan =init_fscan;
428 if (fastscan >looppages / loopfraction)

429 fastscan = looppages /loopfraction

La función pageout_scanner es el scanner propiamente dicho, copio solo el comienzo debido a su


longitud, visitad el enlace al fichero completo para verla entera:

731 pageout_scann(void)
732 {
733 struct page *fronthand, *backhand;
734 uint_t count;
735 callb_cpr_t cprinfo;
736 pgcnt_t nscan_limit;
737 pgcnt_t pcount;
738
739 CALLB_CPR_INIT(&cprinfo,&pageout_mutex, callb_generic_cpr,"poscan");
740 mutex_enter(&pageout_mutex);
[...]

Rastreando la actividad del Page Scanner en nuestro sistema

Como decía al principio del artículo la columna sr de la salida del vmstat es el rastro más evidente
que deja el scanner al ejecutarse y la mayoría de manuales de tunning recomiendan revisarla para
verificar si hay escasez de memoria física.
Sin embargo podemos obtener algo más de información del scanner en nuestro sistema. El comando
"kstat -n system_pages" nos da los valores de algunos de los parámetros antes comentados.

module: unix instance: 0


name: system_pages class: pages
availrmem 5851
crtime 0
desfree 239
desscan 3825
econtig 4274888704
fastscan 15302

49
freemem 230
kernelbase 3556769792
lotsfree 478
minfree 119
nalloc 18895816

nalloc_calls 12648
nfree 17047790
nfree_calls 7993
nscan 3825
pagesfree 230
pageslocked 24754
pagestotal 30605
physmem 30606
pp_kernel 26396
slowscan 100
snaptime 5882.612731441

Podemos ver el lotsfree, el fastscan, slowscan, ... otro dato interesante es el nscan que indica el
número de páginas que ha comprobado el scanner desde la última vez que se activó.
Con dtrace también podemos hacer varias comprobaciones, por ejemplo podemos ver el número de
veces que se llama a la función pageout_scann():

dtrace -n 'vminfo::pageout_scanner: { @num[probefunc] = count (); }'


dtrace: description 'vminfo::pageout_scanner: ' matched 3 probes

pageout_scanner 60997

50
Anexo D: Cambios de contexto

Los cambios de contexto son provocados cuando un thread que se esta ejecutando en una cpu se
reemplaza por uno nuevo. Pero ¿porqué se realiza ese reemplazo ?
Los motivos son varios:
• Cada thread tiene un quantum (tiempo de cpu para ejecutarse) dependiendo a la scheduling
class que pertenezca, cuando se agota el thread se va a dormir.
• Un thread puede irse a dormir voluntariamente a la espera que cierta condición se cumpla,
por ejemplo cuando solicito datos que se hallan en un almacenamiento libre me voy a dormir
mientras espero que estén disponibles y dejo la cpu libre para otros threads. Otro ejemplo
frecuente sería un thread accediendo a un recurso que actualmente esta bloqueado,
Dependiendo del tipo de bloqueo enviaríamos el thread a dormir.
• La llegada de una interrupción o el cambio de estado de un thread de más prioridad a
running puede hacer que el scheduler decida desalojarnos de la cpu y dársela a otro thread.
En los dos primeros casos se realiza un cambio de contexto voluntario, en el tercero se realiza un
cambio de contexto involuntario.
Cuando se produce este desalojo es necesario salvar el estado en el que se encuentra nuestro thread
para poder continuar posteriormente en el mismo punto. Ello implica almacenar el estado de los
registros de la cpu y el stack de nuestro thread.
Realizar un cambio de contexto es costoso, nos interesaría que hubiera el mínimo de ellos posibles
especialmente involuntarios. Si en nuestro sistema vemos un número muy elevado deberíamos
investigar las posibles causas. Estas pueden ser desde threads que constantemente se están yendo a
dormir por problemas a accesos a recursos, hay demasiados hilos en ejecución lo que hace que haya
mucha competencia para tomar una CPU, ...
La función responsable de gestionar los cambios de contextos se llama swtch() y la responsable de
salvar el contexto del thread y cargar el del siguiente a ejecutar se llama resume().
Un caso real de lo expuesto lo viví en mi anterior empresa, una aplicación en java mal hecha
generaba tal cantidad de threads que ellos mismos provocaban innumerables bloqueos en las
dispatch queues y otros recursos forzando continuos cambios de contexto. Ese era uno de los
motivos por los que a pesar de que el equipo no tenia un exceso de carga la aplicación se arrastraba.
Para finalizar la columna del vmstat que nos dá este dato es la cs, el comando mpstat nos desglosa
entre cambios voluntarios e involuntarios, columnas csw e icsw respectivamente.

51
Aenxo E: Dispatch Queues

Como veíamos en el Anexo de los cambios de contexto, un thread puede ser desalojado de la CPU
por varios motivos. Lógicamente llegará un punto en el que tendrá que ser de nuevo alojado para
poder continuar con su ejecución. Normalmente las CPUs suelen ser un recurso escaso por lo que
fácilmente puede darse el caso que dos o más threads deseen ejecutarse a la vez y no dispongamos
de suficientes CPUs libres. Cuando esto sucede el thread de menos prioridad queda a la espera en
una dispatch queue, también llamada run queue. Cuando el thread que ocupa la cpu termina, el
primero esperando en la queue pasa a ser ejecutado.
Hasta aquí es más o menos sencillo, pero puede darse la situación que un thread de más prioridad
que el que esta esperando y menor que el que esta ocupando la CPU desee ejecutarse también por lo
que tendrá que ser insertado en la posición de la cola que le corresponda, en este caso delante del
thread que teníamos esperando.
¿ Fácil verdad ?, bueno pues así es como NO funciona OpenSolaris, digamos que sería el modelo de
un kernel Linux 2.4.x, sin embargo lo he planteado para que se entienda el problema que debemos
resolver.

Dispatch Queues en Solaris.

La implementación de las colas de ejecución en OpenSolaris es algo más compleja que el modelo
anterior, básicamente porque este plantea dos graves problemas, el primero es que con una sola cola
en un sistema multiprocesador habría númerosos interbloqueos en ella, además la necesidad de
reordenarla continuamente a medida que nuevos threads con prioridades distintas se ponen en
estado runnable es un gran gasto de recursos.
Para solventar ambos problemas OpenSolaris crea un set de dispatch queues por cada núcleo de
procesador que tenemos. En cada set hay una cola por cada una de las prioridades globales.
La excepción son los threads de la scheduling class real time, para estos solo hay un set de colas
para todos los procesadores.

Jugando con kmdb

Primero vamos a obtener información acerca de las cpus disponibles en nuestro equipo

#mdb -k
> ::cpuinfo
ID ADDR FLG NRUN BSPL PRI RNRN KRNRN SWITCH THREAD PROC
0 3000121a000 1b 0 0 -1 no no t-0 2a10041dcc0 (idle)
1 3000121e000 1b 0 0 -1 no no t-0 2a1004a5cc0 (idle)
2 30001224000 1b 0 0 59 no no t-0 30010cc9c80 sshd

52
3 0000180c000 1b 0 0 59 no no t-0 300267d9c60 mdb

16 30001228000 1b 0 0 49 no no t-0 30029c3b2c0 oracle


17 3000122c000 1b 0 0 49 no no t-2 30010cd0340 oracle
18 30001232000 1b 0 0 0 no no t-1 30012caf620 oracle
19 30001236000 1b 0 0 165 no no t-0 2a10079fcc0 sched

Como vemos nuestro sistema tenemos 8 procesadores, el dcm cpuinfo nos da información acerca de
que thread se esta ejecutando y con que prioridad. El campo ADDR apunta a la estructura cpu_t de
cada procesador.

> 3000122c000 ::print cpu_t ! grep disp


cpu_disp = 0x60001a35980
cpu_dispthread = 0x30018c53900
cpu_disp_flags = 0
cpu_dispatch_pri = 0x3b

Si volcamos esa estructura vemos que tiene varios campos relacionados con las dispatch queues, el
puntero cpu_disp es la dirección de memoria de la estructura disp_t con la información más
importante de las colas, otro campos interesantes son cpu_dispthread y cpu_dispatch_pri , que son
el thread que se esta ejecutando actualmente y su prioridad respectivamente.

> 0x60001a35980 ::print disp_t


{
disp_lock = 0
disp_npri = 0xaa
disp_q = 0x60003914000
disp_q_limit = 0x60003914ff0
disp_qactmap = 0x6000296c828
disp_maxrunpri = 0xffff
disp_max_unbound_pri = 0xffff
disp_nrunnable = 0
disp_cpu = 0x3000122c000

Si volcamos la dirección de memoria de la estructura disp_t vemos más información interesante,


disp_q es un puntero a la dirección de memoria donde está el primer registro del set de colas, el
array esta ordenado de más prioridad a menos.

53
Podemos volcar esa posición para ver la estructura del campo.

> 0x60003914000 ::print dispq_t


{
dq_first = 0
dq_last = 0
dq_sruncnt = 0
}

dq_first apunta al primer thread de esta cola, dq_last al último y dq_sruncnt al total de ellos.

Como podéis ver los datos a través del ejemplo no son coherentes eso es debido ha que he hecho un
volcado con el sistema ejecutando por lo que la información de las colas ha sido modificado varios
miles de veces en el tiempo que yo ejecutaba los comandos.

Finalmente podemos usar el walker cpu_dispq para ver los threads esperando en el array de colas
dada una cpu

> ::cpuinfo
ID ADDR FLG NRUN BSPL PRI RNRN KRNRN SWITCH THREAD PROC
0 fec220e8 1b 4 0 59 no no t-1 d49e9c00 mdb
> fec220e8 ::walk cpu_dispq
d8835600
d8f82a00

54
Anexo F: Bloqueos, mutex y R/W

Intoducción: ¿ Porqué existen bloqueos en el sistema ?

El uso de bloqueos es debido a la necesidad de sincronizar el acceso a algún recurso, por ejemplo
dos procesos no pueden modificar a la vez un dato en una determinada posición de memoria ya que
el resultado sería una corrupción de dichos datos.
OpenSolaris proporciona varias herramientas que permiten garantizar que un dato solo es accedido
por un thread o por varios pero teniendo solo uno de ellos acceso como escritura.
Debes imaginar los bloqueos como una puerta que te puedes encontrar o bien cerrada o bien abierta,
limitándote el acceso a la información que hay detrás, los bloqueos son especifico para los datos a
los cuales controlan el acceso

Mutex Blocks.

El bloqueo más común es el de exclusión mutua (mutex), dando acceso de lectura y escritura a un
único thread. Cuando vayamos a acceder para leer o modificar un dato, por ejemplo la lista de una
cola de ejecución, podemos encontrar que su bloqueo se encuentre libre o ocupado.
Si el bloqueo se encuentra libre, nos adueñaremos de él, realizaremos las acciones oportunas, y lo
liberaremos de nuevo.
¿ y si el bloqueo esta ya ocupado ?
Existen dos subtipos de mutex:
Spin mutex:
Si un bloqueo se encuentra ocupado lo "más lógico" seria enviar al thread que debe esperar a que
ese recurso se libere a dormir y despertarlo una vez el bloqueo este disponible. Sin embargo esto
provoca un cambio de contexto en la cpu con su correspondiente coste en recursos. Si el bloqueo va
a ser mantenido durante muy poco tiempo el coste del cambio de contexto puede ser mayor que el
coste de esperar a que se libere.
Cuando un thread trata de adueñarse de un spin mutex ocupado, se queda en un bucle a la espera
que el bloqueo quede libre, obviamente esto consume 1 cpu por lo que los spin mutex solo son
usados en estructuras cuyos bloqueos van a ser extremadamente cortos.
Mutex Adaptive:
Hay estructuras de datos en los que no podemos prever si el tiempo que se encontraran bloqueadas
sera corto o no, para acceder a ellas se utilizan los mutex adaptives.
Cuando encontramos un mutex adaptive ocupado comprobamos que thread es el actual dueño, si
ese thread esta actualmente en un cpu ejecutándose nos quedaremos haciendo un spinning, es decir
nos quedaremos ejecutando un bucle a la espera que se libere.

55
Por contra si el el thread que actualmente tiene el bloqueo no se está ejecutando nos iremos a
dormir.
Este comportamiento es debido a que se considera que si el thread se esta ejecutando el bloqueo se
liberara en breve y, por lo tanto, el coste del spinning será menor que el de un cambio de contexto.
En caso de que no se esté ejecutando se considera más óptimo la opción contraria.

Read/write blocks:

Por último puede darse el caso que solo nos interese bloquear una estructura para que no se pueda
modificar sin embargo no nos importa que otros accedan en modo lectura.
Cuando el lock esta ocupado mandaremos el thread a dormir.

56