O DTrace armazena os resultados das funções de agregação em objetos chamados agregações. Os resultados da agregação são indexados usando-se uma tupla de expressões similar às usadas nas matrizes de associação. Em D, a sintaxe de uma agregação é:
@name[ keys ] = aggfunc ( args );
onde nome é o nome da agregação, chaves é uma lista separada por vírgulas de expressões de D, funçagr é uma das funções de agregação do DTrace e args é uma lista de argumentos separados por vírgulas apropriada para a função de agregação. O nome da agregação é um identificador de D que possui como prefixo o caractere especial @. Todas as agregações nomeadas nos programas em D são variáveis globais; não há agregações locais de cláusula ou segmento. Os nomes de agregação são mantidos em um espaço de nome de identificador separado de outras variáveis globais de D. Lembre-se que a e @a não são a mesma variável se você reutilizar nomes. O nome de agregação especial @ pode ser usado para nomear uma agregação anônima em programas em D simples. O compilador de D trata esse nome como um alias do nome de agregação @_.
As funções de agregação de DTrace são mostradas na tabela a seguir. A maioria das funções de agregação utilizam apenas um único argumento que representa os novos dados.
Tabela 9–1 Funções de agregação do DTrace
Nome da função |
Argumentos |
Resultado |
---|---|---|
count |
nenhum |
O número de vezes chamada. |
sum |
expressão escalar |
O valor total das expressões especificadas. |
avg |
expressão escalar |
A média aritmética das expressões especificadas. |
min |
expressão escalar |
O menor valor entre as expressões especificadas. |
max |
expressão escalar |
O maior valor entre as expressões especificadas. |
lquantize |
expressão escalar, limite inferior, limite superior, valor da etapa |
Uma distribuição de freqüência linear, dimensionada pelo intervalo especificado, dos valores das expressões especificadas. Incrementa o valor no depósito mais alto que é menor que a expressão especificada. |
quantize |
expressão escalar |
Uma distribuição de freqüência em potência de dois dos valores das expressões especificadas. Incrementa o valor no depósito mais alto em potência de dois que é menor que a expressão especificada. |
Por exemplo, para contar o número de chamadas do sistema write(2) no sistema, você poderia usar uma seqüência informativa como uma chave e a função de agregação count():
syscall::write:entry { @counts["write system calls"] = count(); }
O comando dtrace imprime resultados de agregação por padrão quando o processo é encerrado, seja como o resultado de uma ação END explícita ou quando o usuário pressiona Control-C. O seguinte exemplo mostra o resultado da execução desse comando, aguardando-se alguns segundos e pressionando-se Control-C:
# dtrace -s writes.d dtrace: script './writes.d' matched 1 probe ^C write system calls 179 # |
Você pode contar chamadas do sistema por nome de processo usando a variável execname como chave de uma agregação:
syscall::write:entry { @counts[execname] = count(); }
O seguinte exemplo mostra o resultado da execução deste comando, aguardando-se alguns segundos e pressionando-se Control-C:
# dtrace -s writesbycmd.d dtrace: script './writesbycmd.d' matched 1 probe ^C dtrace 1 cat 4 sed 9 head 9 grep 14 find 15 tail 25 mountd 28 expr 72 sh 291 tee 814 def.dir.flp 1996 make.bin 2010 # |
Como alternativa, você pode querer examinar em detalhes as gravações organizadas pelo nome do executável e o descritor de arquivos. O descritor de arquivos é o primeiro argumento de write(2), sendo assim, o seguinte exemplo usa uma chave que consiste em execname e arg0:
syscall::write:entry { @counts[execname, arg0] = count(); }
A execução deste comando resulta em uma tabela com o nome do executável e o descritor de arquivos, conforme mostrado no exemplo a seguir:
# dtrace -s writesbycmdfd.d dtrace: script './writesbycmdfd.d' matched 1 probe ^C cat 1 58 sed 1 60 grep 1 89 tee 1 156 tee 3 156 make.bin 5 164 acomp 1 263 macrogen 4 286 cg 1 397 acomp 3 736 make.bin 1 880 iropt 4 1731 # |
O exemplo a seguir exibe o tempo médio gasto na chamada do sistema write, organizado por nome de processo. Este exemplo usa a função de agregação avg(), especificando a expressão para realizar média como o argumento. O exemplo faz a média do tempo gasto na chamada do sistema:
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = avg(timestamp - self->ts); self->ts = 0; }
O seguinte exemplo mostra o resultado da execução deste comando, aguardando-se alguns segundos e pressionando-se Control-C:
# dtrace -s writetime.d dtrace: script './writetime.d' matched 2 probes ^C iropt 31315 acomp 37037 make.bin 63736 tee 68702 date 84020 sh 91632 dtrace 159200 ctfmerge 321560 install 343300 mcs 394400 get 413695 ctfconvert 594400 bringover 1332465 tail 1335260 # |
A média pode ser útil, mas geralmente não fornece detalhes suficientes para compreender a distribuição dos pontos de dados. Para compreender a distribuição com mais detalhes, use a função de agregação quantize() conforme mostrado no exemplo a seguir:
syscall::write:entry { self->ts = timestamp; } syscall::write:return /self->ts/ { @time[execname] = quantize(timestamp - self->ts); self->ts = 0; }
Como cada linha de resultado torna-se um diagrama de distribuição de freqüência, o resultado deste script é substancialmente maior que os anteriores. O exemplo a seguir mostra uma seleção de resultado de amostra:
lint value ------------- Distribution ------------- count 8192 | 0 16384 | 2 32768 | 0 65536 |@@@@@@@@@@@@@@@@@@@ 74 131072 |@@@@@@@@@@@@@@@ 59 262144 |@@@ 14 524288 | 0 acomp value ------------- Distribution ------------- count 4096 | 0 8192 |@@@@@@@@@@@@ 840 16384 |@@@@@@@@@@@ 750 32768 |@@ 165 65536 |@@@@@@ 460 131072 |@@@@@@ 446 262144 | 16 524288 | 0 1048576 | 1 2097152 | 0 iropt value ------------- Distribution ------------- count 4096 | 0 8192 |@@@@@@@@@@@@@@@@@@@@@@@ 4149 16384 |@@@@@@@@@@ 1798 32768 |@ 332 65536 |@ 325 131072 |@@ 431 262144 | 3 524288 | 2 1048576 | 1 2097152 | 0 |
Observe que as linhas da distribuição de freqüência são sempre valores de potência de dois. Cada linha indica a contagem do número de elementos maiores que ou iguais ao valor correspondente, mas menores que o próximo valor maior de linha. Por exemplo, o resultado acima mostra que iropt tinha 4.149 gravações ocorrendo entre 8.192 nanossegundos e 16.383 nanossegundos, inclusive.
Embora quantize() seja útil para obter uma rápida visualização dos dados, talvez você queira examinar uma distribuição em valores lineares. Para exibir uma distribuição de valor linear, use a função de agregação lquantize (). A função lquantize() possui três argumentos além de uma expressão de D. um limite inferior, um limite superior e uma etapa. Por exemplo, se você quisesse visualizar a distribuição de gravações por descritor de arquivo, uma quantização em potência de dois não seria eficiente. Em vez disso, use uma quantização linear com um pequeno intervalo, conforme mostrado no exemplo a seguir:
syscall::write:entry { @fds[execname] = lquantize(arg0, 0, 100, 1); }
A execução deste script por alguns segundos resulta em uma grande quantidade de informação. O exemplo a seguir mostra uma seleção de resultado típico:
mountd value ------------- Distribution ------------- count 11 | 0 12 |@ 4 13 | 0 14 |@@@@@@@@@@@@@@@@@@@@@@@@@ 70 15 | 0 16 |@@@@@@@@@@@@ 34 17 | 0 xemacs-20.4 value ------------- Distribution ------------- count 6 | 0 7 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 521 8 | 0 9 | 1 10 | 0 make.bin value ------------- Distribution ------------- count 0 | 0 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3596 2 | 0 3 | 0 4 | 42 5 | 50 6 | 0 acomp value ------------- Distribution ------------- count 0 | 0 1 |@@@@@ 1156 2 | 0 3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 6635 4 |@ 297 5 | 0 iropt value ------------- Distribution ------------- count 2 | 0 3 | 299 4 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 20144 5 | 0 |
Você também pode usar a função de agregação lquantize() para agregar no momento a partir de um ponto no passado. Essa técnica permite que você observe uma alteração no comportamento ao longo do tempo. O exemplo a seguir exibe a alteração no comportamento da chamada do sistema durante um processo executando o comando date(1):
syscall::exec:return, syscall::exece:return /execname == "date"/ { self->start = vtimestamp; } syscall:::entry /self->start/ { /* * We linearly quantize on the current virtual time minus our * process's start time. We divide by 1000 to yield microseconds * rather than nanoseconds. The range runs from 0 to 10 milliseconds * in steps of 100 microseconds; we expect that no date(1) process * will take longer than 10 milliseconds to complete. */ @a["system calls over time"] = lquantize((vtimestamp - self->start) / 1000, 0, 10000, 100); } syscall::rexit:entry /self->start/ { self->start = 0; }
O script anterior fornece uma melhor avaliação do comportamento da chamada do sistema quando muitos processos date(1) são executados. Para ver esse resultado, execute sh -c 'while true; do date >/dev/null; done' em uma janela, enquanto executa o script de D em outra. O script produz um perfil do comportamento da chamada do sistema do comando date(1):
# dtrace -s dateprof.d dtrace: script './dateprof.d' matched 218 probes ^C system calls over time value ------------- Distribution ------------- count < 0 | 0 0 |@@ 20530 100 |@@@@@@ 48814 200 |@@@ 28119 300 |@ 14646 400 |@@@@@ 41237 500 | 1259 600 | 218 700 | 116 800 |@ 12783 900 |@@@ 28133 1000 | 7897 1100 |@ 14065 1200 |@@@ 27549 1300 |@@@ 25715 1400 |@@@@ 35011 1500 |@@ 16734 1600 | 498 1700 | 256 1800 | 369 1900 | 404 2000 | 320 2100 | 555 2200 | 54 2300 | 17 2400 | 5 2500 | 1 2600 | 7 2700 | 0 |
Este resultado fornece uma breve idéia das diferentes fases do comando date(1)com relação aos serviços solicitados do kernel. Para entender melhor essas fases, talvez você queira compreender quando quais chamadas do sistema estão sendo chamadas. Se esse for o caso, você poderá alterar o script de D para agregar na variável probefunc em vez de uma seqüência de constante.