Guía de seguimiento dinámico de Solaris

Predicados

Una gran diferencia entre D y otros lenguajes de programación como, por ejemplo, C, C++ y el lenguaje de programación de Java es la ausencia de construcciones de flujo de control como, por ejemplo, las instrucciones "if" y los bucles. Las cláusulas de un programa D se escriben en forma de listas de instrucciones de una sola línea que realizan un seguimiento de una cantidad fija y opcional de datos. D permite realizar un seguimiento condicional de los datos y modificar el flujo de control mediante expresiones lógicas denominadas predicados, que pueden utilizarse para agregar prefijos a las cláusulas del programa. Una expresión de predicado se evalúa al activarse un sondeo, antes de ejecutar cualquier instrucción asociada a la cláusula correspondiente. Si el predicado se evalúa como "true", se representa con un valor diferente a cero y se ejecuta la lista de instrucciones. Si, por el contrario, el predicado es "false", se representa con el valor cero, no se ejecuta ninguna instrucción y se omite la activación del sondeo.

Escriba el siguiente código fuente para el próximo ejemplo y guárdelo en un archivo con el nombre countdown.d:

dtrace:::BEGIN
{
	i = 10;
}

profile:::tick-1sec
/i > 0/
{
	trace(i--);
}

profile:::tick-1sec
/i == 0/
{
	trace("blastoff!");
	exit(0);
}

Este programa D implementa un temporizador de cuenta atrás de 10 segundos utilizando predicados. Al ejecutarlo, countdown.d realiza la cuenta atrás a partir de 10 y, a continuación, imprime un mensaje y se cierra:

# dtrace -s countdown.d
dtrace: script 'countdown.d' matched 3 probes
CPU     ID                    FUNCTION:NAME
	0  25499                       :tick-1sec        10
	0  25499                       :tick-1sec         9
	0  25499                       :tick-1sec         8
	0  25499                       :tick-1sec         7
	0  25499                       :tick-1sec         6
	0  25499                       :tick-1sec         5
	0  25499                       :tick-1sec         4
	0  25499                       :tick-1sec         3
	0  25499                       :tick-1sec         2
	0  25499                       :tick-1sec         1
	0  25499                       :tick-1sec   blastoff!
# 

En este ejemplo, se utiliza el sondeo BEGIN para inicializar un entero i con el valor 10 y comenzar la cuenta atrás. A continuación, al igual que en el ejemplo anterior, el programa utiliza el sondeo tick-1sec para implementar un temporizador que se active una vez por segundo. Tenga en cuenta que, en countdown.d, la descripción del sondeo tick-1sec se utiliza en dos cláusulas diferentes, cada una con una lista de acciones y un predicado distintos. El predicado es una expresión lógica que debe incluirse entre barras diagonales / /, y que aparece detrás del nombre del sondeo y delante de los corchetes { } que engloban la lista de instrucciones de la cláusula.

El primer predicado comprueba si i es superior a cero, lo que indica que el temporizador aún se está ejecutando:

profile:::tick-1sec
/i > 0/
{
	trace(i--);
}

El operador relacional > significa superior a y devuelve el valor de entero cero para "false" y el valor uno para "true". Todos los operadores aritméticos de C habituales están disponibles en D; encontrará la lista completa en el Capítulo 2Tipos, operadores y expresiones. Si i no es aún cero, la secuencia de comandos realiza un seguimiento de i y, a continuación, se reduce en uno mediante el operador --.

El segundo predicado utiliza el operador == para devolver el valor "true" cuando i es exactamente igual a cero, lo que indica que se ha completado la cuenta atrás.

profile:::tick-1sec
/i == 0/
{
	trace("blastoff!");
	exit(0);
}

Del mismo modo que en el primer ejemplo, hello.d, countdown.d utiliza una secuencia de caracteres entre comillas dobles denominada constante de cadena para imprimir un mensaje final una vez completada la cuenta atrás. La función exit() se utiliza a continuación para salir de dtrace y volver al indicador de la shell.

Si observa la estructura de countdown.d, comprobará que, al crear dos cláusulas con la misma descripción de sondeo, pero diferentes acciones y predicados, hemos creado, de hecho, el flujo lógico:

i = 10
once per second,
	if i is greater than zero
		trace(i--);
	otherwise if i is equal to zero
		trace("blastoff!");
		exit(0);

Si desea escribir programas complejos utilizando predicados, intente visualizar primero el algoritmo de esta forma y, a continuación, transforme cada ruta de la construcción condicional en una cláusula y predicado independientes.

Ahora combinemos los predicados con un nuevo proveedor, syscall, y creemos nuestro primer programa de seguimiento D real. El proveedor syscall le permite habilitar sondeos en la entrada o en la devolución de cualquier llamada del sistema Solaris. En el ejemplo siguiente se utiliza DTrace para efectuar la supervisión cada vez que la shell realice una llamada del sistema read(2) o write(2). En primer lugar, abra dos ventanas de terminal, una para DTrace y otra que contenga el proceso de la shell que va a supervisar. En la segunda ventana, escriba el siguiente comando para obtener el Id. de proceso de esta shell:


# echo $$
12345

Ahora regrese a la primera ventana de terminal y escriba el siguiente programa D, y guárdelo en un archivo con el nombre rw.d. A medida que escribe el programa, sustituya la constante 12345 por el ID de proceso de la shell que se ha imprimido en respuesta al comando echo.

syscall::read:entry,
syscall::write:entry
/pid == 12345/
{

}

Tenga en cuenta que el cuerpo de la cláusula del sondeo rw.d se ha dejado vacío, ya que el programa sólo pretende realizar un seguimiento de las notificaciones de activación del sondeo, pero no de ningún dato adicional. Una vez que haya terminado de escribir en rw.d, utilice dtrace para iniciar el experimento. A continuación, acceda a la segunda ventana de la shell y escriba varios comandos, pulsando Intro después de introducir cada comando. Mientras escribe, debería ver la información sobre las activaciones del sondeo proporcionada por dtrace en la primera ventana, como se muestra en el siguiente ejemplo:


# dtrace -s rw.d
dtrace: script 'rw.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
	0     34                      write:entry
	0     32                       read:entry
	0     34                      write:entry
	0     32                       read:entry
	0     34                      write:entry
	0     32                       read:entry
	0     34                      write:entry
	0     32                       read:entry
...

Ahora puede ver cómo la shell realiza llamadas del sistema read(2) y write(2) para leer un carácter de la ventana del terminal y devolver el resultado. En este ejemplo, se incluyen muchos de los conceptos descritos hasta ahora, así como algunos nuevos. En primer lugar, para instrumentar read(2) y write(2) del mismo modo, la secuencia de comandos utiliza una única cláusula del sondeo con múltiples descripciones de sondeos separadas por comas, como se muestra a continuación:

syscall::read:entry,
syscall::write:entry

Para una mejor lectura, cada descripción de sondeo aparece en su propia línea. Esta disposición no es estrictamente necesaria, pero permite leer con mayor facilidad la secuencia de comandos. A continuación, la secuencia de comandos define un predicado que coincida sólo con las llamadas del sistema que ha ejecutado el proceso de la shell:

/pid == 12345/

El predicado utiliza la variable pid predefinida de DTrace, que se evalúa siempre con el Id. de proceso asociado al subproceso que ha activado el sondeo correspondiente. DTrace proporciona un gran número de definiciones de variables integradas para cosas útiles como, por ejemplo, el Id. de proceso. A continuación, se muestra una lista de algunas de las variables de DTrace que puede utilizar para escribir sus primeros programas D:

Nombre de la variable 

Tipo de datos 

Significado 

errno

int

Valor actual de errno para las llamadas del sistema

execname

string

Nombre del archivo ejecutable del proceso actual 

pid

pid_t

Id. de proceso del proceso actual 

tid

id_t

Id. de subproceso del subproceso actual 

probeprov

string

Campo de proveedor de la descripción de sondeo actual 

probemod

string

Campo de módulo de la descripción de sondeo actual 

probefunc

string

Campo de función de la descripción de sondeo actual 

probename

string

Campo de nombre de la descripción de sondeo actual 

Ahora que ya ha escrito un programa de instrumentación real, intente probarlo en diferentes procesos que se ejecuten en el sistema, cambiando el Id. de proceso y los sondeos de llamadas del sistema instrumentados. A continuación, puede realizar un sencillo cambio más y convertir rw.d en una versión muy sencilla de una herramienta de seguimiento de llamadas del sistema como, por ejemplo, truss(1). El campo de descripción de sondeo vacío actúa como comodín, devolviendo cada sondeo coincidente, por lo que puede cambiar el programa al siguiente nuevo código fuente para realizar un seguimiento de cualquier llamada del sistema ejecutada por la shell:

syscall:::entry
/pid == 12345/
{

}

Intente escribir varios comandos en la shell como, por ejemplo, cd, ls y date para comprobar la información que devuelve el programa de DTrace.