Uma diferença importante entre D e as outras linguagens de programação como C, C++ e Java é a ausência de construções de fluxo de controle, como instruções if e loops. As cláusulas do programa em D são escritas como listas únicas de instrução em linha reta que rastreiam uma quantidade opcional, fixa de dados. D fornece a capacidade de rastrear dados condicionalmente e modificar o fluxo de controle usando expressões lógicas chamadas predicados que podem ser usadas para prefixar cláusulas do programa. Uma expressão de predicado é avaliada quando o teste é disparado, antes da execução de quaisquer instruções associadas à cláusula correspondente. Se o predicado for avaliado como verdadeiro, representado por um valor diferente de zero, a lista de instruções será executada. Se o predicado for falso, representado por um valor zero, nenhuma das instruções será executada e o teste não será acionado.
Digite o seguinte código-fonte para o próximo exemplo e salve-o em um arquivo chamado countdown.d:
dtrace:::BEGIN { i = 10; } profile:::tick-1sec /i > 0/ { trace(i--); } profile:::tick-1sec /i == 0/ { trace("blastoff!"); exit(0); }
Este programa em D implementa um temporizador de contagem regressiva de 10 segundos usando predicados. Quando executado, countdown.d começa uma contagem regressiva a partir de 10 e depois imprime uma mensagem e encerra:
# 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! #
Este exemplo usa o teste BEGIN para inicializar um inteiro i como 10 para iniciar a contagem regressiva. Depois, como no exemplo anterior, o programa usa o teste tick-1sec para implementar um temporizador que é acionado uma vez por segundo. Observe que em countdown.d, a descrição do teste tick-1sec é usada em duas cláusulas diferentes, cada uma com um predicado e uma lista de ações diferentes. O predicado é uma expressão lógica entre barras / / que aparece após o nome do teste e antes das chaves { } que delimitam a lista de instruções da cláusula.
O primeiro predicado testa se i é maior que zero, indicando que o temporizador ainda está sendo executado:
profile:::tick-1sec /i > 0/ { trace(i--); }
O operador relacional > significa maior que e retorna o valor de inteiro zero para falso e um para verdadeiro. Todos os operadores relacionais de C são suportados em D. A lista completa pode ser encontrada no Capítulo 2Tipos, operadores e expressões. Se i ainda não for zero, o script rastreia i e depois o diminui em um usando o operador --.
O segundo predicado usa o operador == para retornar verdadeiro quando i for exatamente igual a zero, indicando que a contagem regressiva está concluída:
profile:::tick-1sec /i == 0/ { trace("blastoff!"); exit(0); }
Similar ao primeiro exemplo, hello.d, countdown.d usa uma seqüência de caracteres entre aspas duplas, chamada de constante de seqüências, para imprimir uma mensagem final quando a contagem regressiva estiver concluída. A função exit() é então usada para encerrar dtrace e retornar ao prompt do shell.
Se você analisar a estrutura de countdown.d, verá que ao criar duas cláusulas com a mesma descrição de teste mas predicados e ações diferentes, você criou o fluxo lógico eficientemente:
i = 10 once per second, if i is greater than zero trace(i--); otherwise if i is equal to zero trace("blastoff!"); exit(0);
Quando você desejar escrever programas complexos usando predicados, tente primeiro visualizar seu algoritmo desta maneira, e depois transforme cada caminho de suas construções condicionais em uma cláusula e um predicado separados.
Agora, vamos combinar predicados com um novo provedor, o syscall , e criar nosso primeiro programa real de rastreio em D. O provedor syscall permite que você ative testes na entrada ou retorno de qualquer chamada do sistema Solaris. O próximo exemplo usa o DTrace para observar cada vez que o shell realiza uma chamada do sistema de read(2) ou write(2). Primeiro, abra duas janelas no terminal, uma para o DTrace e a outra contendo o processo do shell que você vai observar. Na segunda janela, digite o seguinte comando para obter o ID do processo deste shell:
# echo $$ 12345 |
Agora, volte para a primeira janela do terminal e digite o seguinte programa em D e salve-o em um arquivo chamado rw.d. Quando você digitar o programa, substitua 12345 pelo ID do processo do shell impresso em resposta ao seu comando echo.
syscall::read:entry, syscall::write:entry /pid == 12345/ { }
Observe que o corpo da cláusula do teste de rw.d é deixado em branco porque o programa destina-se somente a rastrear a notificação de disparos de teste e não a rastrear dados adicionais. Quando terminar de digitar no rw.d, use o dtrace para iniciar o seu experimento e depois vá para a segunda janela do shell e digite alguns comandos, pressionando a tecla de retorno após cada comando. Enquanto você digita, verá dtrace reportar testes acionados na primeira janela, similar ao seguinte exemplo:
# 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 ... |
Você agora está observando o shell realizar chamadas do sistema de read(2) e write(2) para ler um caractere da janela do terminal e retornar o resultado! Este exemplo inclui muitos dos conceitos descritos até agora e também alguns novos. Primeiro, para instrumentar read(2) e write(2) da mesma maneira, o script usa uma única cláusula de teste com várias descrições de teste separando as descrições com vírgulas, da seguinte maneira:
syscall::read:entry, syscall::write:entry
Por questões de legibilidade, a descrição de cada teste aparece em sua própria linha. Esta organização não é obrigatória, mas facilita a leitura do script. Em seguida, o script define um predicado que corresponde somente às chamadas do sistema que são executadas pelo processo do shell:
/pid == 12345/
O predicado usa a variável predefinida do DTrace pid, que sempre tem o valor do ID do processo associado ao segmento que acionou o teste correspondente. O DTrace oferece muitas definições de variáveis internas para coisas úteis como o ID do processo. Veja a seguir uma lista de algumas variáveis do DTrace que você pode usar para escrever seus primeiros programas em D:
Nome da variável |
Tipo de dados |
Significado |
---|---|---|
errno |
int |
Valor do errno atual para chamadas do sistema |
execname |
string |
Nome do arquivo executável do processo atual |
pid |
pid_t |
ID do processo atual |
tid |
id_t |
ID do segmento atual |
probeprov |
string |
Campo do provedor da descrição do teste atual |
probemod |
string |
Campo do módulo da descrição do teste atual |
probefunc |
string |
Campo da função da descrição do teste atual |
probename |
string |
Campo do nome da descrição do teste atual |
Agora que você escreveu um programa de instrumentação real, tente experimentá-lo nos diferentes processos em execução no seu sistema, alterando o ID do processo e os testes de chamada do sistema que são instrumentados. Depois, você pode fazer mais uma simples alteração e transformar o rw.d em uma versão muito simples de uma ferramenta de rastreio de chamada do sistema como truss(1). Um campo de descrição de teste vazio atua como um curinga, correspondendo a qualquer teste, sendo assim, altere o programa para o novo código-fonte a seguir para rastrear qualquer chamada do sistema executada pelo shell:
syscall:::entry /pid == 12345/ { }
Tente digitar alguns comandos no shell como cd, ls e date e veja o que o programa DTrace reporta.