Professional Documents
Culture Documents
• 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.
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.
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.
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.
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
[...]
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.
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.
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.
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.
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.
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.
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:
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
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.
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.
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.
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.
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.
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.
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.
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
[...]
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.
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.
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
[...]
-------------------------------------------------------------------------------
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)
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.
30
1 4% 96% 0.00 86100 0x3001ba97c58 as_map+0x38
1 4% 100% 0.00 339800 0x6000f7a2460 sam_proc_get_lease+0x1f0
-------------------------------------------------------------------------------
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.
31
teniendo problemas para adueñarse de algunos locks.
A quien esta afectando. dtrace es nuestro amigo. Lo primero es localizar la prueba que nos interesa
para monitorizar nuestra función.
Después ejecutamos dtrace para que nos informe de todos los pids que están ejecutando dicha
función.
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;
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.
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.
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:
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:
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.
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.
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.
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%
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
[…]
#con walk thread obtenemos la dirección de las estructuras de los threads que componen el proceso,
en este caso solo tenemos uno.
#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. :)
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.
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
43
Anexo A: Dtrace one liners
44
• Minor faults por proceso,
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.
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
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.
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)
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);
[...]
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.
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():
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.
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.
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
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.
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.
53
Podemos volcar esa posición para ver la estructura del campo.
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
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