O DTrace fornece funções de formatação incorporadas printf() e printa() que você pode usar em seus programas em D para formatar a saída. O compilador de D fornece recursos não encontrados na rotina de biblioteca printf(3C), sendo assim, você deve ler este capítulo mesmo que já esteja familiarizado com printf(). Este capítulo também discute o comportamento da formatação da função trace() e o formato de saída padrão usado por dtrace(1M) para exibir agregações.
A função printf() combina a capacidade de rastrear dados, como se fosse a função trace(), com a capacidade de produzir a saída de dados e de outro texto em um formato específico descrito por você. A função printf() informa ao DTrace para rastrear os dados associados a cada argumento após o primeiro argumento e depois para formatar os resultados usando as regras descritas pelo primeiro argumento de printf (), conhecido como seqüência de formato.
A seqüência de formato é uma seqüência regular que contém inúmeras conversões de formato, cada uma começando com o caractere %, que descreve como formatar o argumento correspondente. A primeira conversão na seqüência de formato corresponde ao segundo argumento printf(), a segunda conversão ao terceiro argumento, e assim por diante. Todo o texto entre conversões é impresso textualmente. O caractere que segue o caractere de conversão % descreve o formato a ser usado para o argumento correspondente.
Ao contrário de printf(3C), DTrace printf() é uma função incorporada que é reconhecida pelo compilador de D. O compilador de D fornece vários serviços úteis do DTrace printf() que não são encontrados na biblioteca de C printf():
O compilador de D compara os argumentos com as conversões na seqüência de formato. Se um tipo de argumento for incompatível com a conversão de formato, o compilador de D exibirá uma mensagem de erro explicando o problema.
O compilador de D não requer o uso de prefixos de tamanho com conversões de formato printf. () A rotina printf() de C requer que você indique o tamanho dos argumentos, adicionando prefixos, tais como %ld for long ou %lld for long long. O compilador de D conhece o tamanho e o tipo dos seus argumentos, sendo assim, esses prefixos não são necessários em suas declarações printf() de D.
O DTrace fornece caracteres de formato adicionais que são úteis para depuração e observação. Por exemplo, a conversão de formato %a pode ser usada para imprimir um ponteiro como um nome de símbolo e um deslocamento.
Para implementar esses recursos, a seqüência de formato na função printf() do DTrace deve ser especificada como uma constante de seqüência em seu programa em D. As seqüências de formato podem ser variáveis dinâmicas do tipo string.
Cada especificação de conversão na seqüência de formato é introduzida pelo caractere %, após o qual as informações seguintes aparecem em seqüência:
Zero ou mais sinalizadores (em qualquer ordem), que modificam o significado das especificações de conversão, conforme descrito na próxima seção.
Uma largura de campo mínima ótima. Se o valor convertido tiver menos bytes que a largura do campo, o valor será preenchido com espaços à esquerda, por padrão, ou à direita, se o sinalizador de ajuste esquerdo (-) for especificado. A largura do campo também pode ser especificada como um asterisco (*), nesse caso, a largura do campo é definida dinamicamente com base no valor de um argumento adicional do tipo int.
Uma precisão opcional que indica o número mínimo de dígitos que aparecem nas conversões d, i, o, u, x e X (o campo é preenchido com zeros à esquerda); o número de dígitos que aparecem após o caractere fracionário das conversões e, E e f, o número máximo de dígitos significativos das conversões g e G; ou o número máximo de bytes a serem impressos de uma seqüência pela conversão s. A precisão toma o formato de um ponto (.) seguido por um asterisco ( *), descrito abaixo, ou uma seqüência de dígito decimal.
Uma seqüência opcional de prefixos de tamanho que indica o tamanho do argumento correspondente, descrito em Prefixos de tamanho. Os prefixos de tamanho não são necessários em D e são fornecidos para compatibilidade com a função printf() de C.
Um especificador de conversão que indica o tipo de conversão a ser aplicado ao argumento.
A função printf(3C) também aceita as especificações de conversão do formato % n$ onden é um inteiro decimal. A printf() do DTrace não aceita este tipo de especificação de conversão.
Os sinalizadores de conversão printf() são ativados pela especificação de um ou mais dos seguintes caracteres, que podem aparecer em qualquer ordem:
A porção inteira do resultado de uma conversão decimal (%i, %d, %u, %f, %g ou %G) é formatada com milhares de caracteres de agrupamento por meio do caractere de agrupamento não-monetário. Algumas localidades, incluindo a localidade POSIX C, não fornece caracteres de agrupamento não-monetários a serem usados com este sinalizador.
O resultado da conversão é justificado à esquerda no campo. A conversão será justificada à direita, se este sinalizador não for especificado.
O resultado da conversão assinada sempre começa com um sinal (+ ou -). Se este sinalizador não for especificado, a conversão começa com um sinal somente quando um valor negativo for convertido.
Se o primeiro caractere de uma conversão assinada não for um sinal ou se uma conversão assinada resultar em nenhum caractere, um espaço será colocado antes do resultado. Se os sinalizadores espaço e + aparecerem, o sinalizador de espaço será ignorado.
O valor será convertido em um formato alternativo, se for definido um formato alternativo para a conversão selecionada. Os formatos alternativos para conversões são descritos junto com a conversão correspondente.
Para as conversões d, i, o, u, x, X, e, E, f, g, e G, zeros à esquerda (seguindo qualquer indicação de sinal ou base) são usados para preencher a largura do campo. Nenhum preenchimento de espaço é realizado. Se os sinalizadores 0 e - aparecerem, o sinalizador 0 será ignorado. Para as conversões d, i, o, u, x e X, se uma precisão for especificada, o sinalizador 0 será ignorado. Se os sinalizadores 0 e ' aparecerem, os caracteres de agrupamento serão inseridos antes do preenchimento de zero.
A largura de campo mínima pode ser especificada como uma seqüência de dígitos decimais seguindo qualquer especificador de sinalizador, nesse caso, a largura do campo é definida como o número de colunas especificadas. A largura do campo também pode ser especificada como um asterisco (*), nesse caso, um argumento adicional do tipo int é acessado para determinar a largura do campo. Por exemplo, para imprimir um inteiro x em uma largura de campo determinada pelo valor da variável int w, você escreveria a declaração de D:
printf("%*d", w, x);
A largura do campo também pode ser especificada com um caractere ? para indicar que a largura do campo deve ser definida com base no número de caracteres necessários para formatar um endereço em hexadecimal no modelo de dados do kernel do sistema operacional. A largura será definida como 8, se o kernel estiver usando o modelo de dados de 32 bits, ou como 16 se o kernel estiver usando o modelo de dados de 64 bits.
A precisão da conversão pode ser especificada como uma seqüência de dígitos decimais seguindo um ponto (.) ou por um asterisco ( *) seguindo um ponto. Se um asterisco for usado para especificar a precisão, um argumento adicional do tipo int anterior ao argumento de conversão será acessado para determinar a precisão. Se a largura e a precisão forem especificadas como asteriscos, a ordem de argumentos para printf() da conversão deve aparecer na seguinte ordem: largura, precisão, valor.
Os prefixos de tamanho são necessários nos programas em ANSI-C que usam printf(3C) a fim de indicar o tamanho e o tipo do argumento de conversão. O compilador de D realiza esse processamento para as suas chamadas printf () automaticamente, sendo assim, os prefixos de tamanho não são necessários. Embora os prefixos de tamanho sejam fornecidos para compatibilidade com C, seu uso é explicitamente desencorajado nos programas em D porque eles vinculam o código a um modelo de dados específico quando usam tipos derivados. Por exemplo, se um typedef for redefinido como tipos base de inteiro diferentes, dependendo do modelo de dados, não será possível usar uma única conversão de C que funcione em ambos os modelos de dados sem conhecer explicitamente os dois tipos subjacentes, incluindo uma expressão de conversão ou definindo várias seqüências de formato. O compilador de D resolve esse problema automaticamente, permitindo que você omita os prefixos de tamanho e determine automaticamente o tamanho do argumento.
Os prefixos de tamanho podem ser colocados logo antes do nome de conversão e após quaisquer sinalizadores, larguras e especificadores de precisão. Os prefixos de tamanho são da seguinte forma:
Um h opcional especifica que a seguinte conversão d, i, o, u, x ou X se aplica a um short ou unsigned short.
Um l opcional especifica que a seguinte conversão d, i, o, u, x ou X se aplica a um long ou unsigned long.
Um ll opcional especifica que a seguinte conversão d, i, o, u, x ou X se aplica a um long long ou unsigned long long.
Um L opcional especifica que a seguinte conversão e, E, f, g ou G se aplica a um long double.
Um l opcional especifica que a seguinte conversão c se aplica a um argumento wint_t, e que o seguinte caractere de conversão s se aplica a um ponteiro para um argumento wchar_t.
Cada seqüência de caractere de conversão resulta na busca de zero ou mais argumentos. Se forem fornecidos argumentos insuficientes para a seqüência de formato, ou se a seqüência de formato estiver exaurida e os argumentos permanecerem, o compilador de D emitirá uma mensagem de erro apropriada. Se um formato de conversão indefinido for especificado, o compilador de D emite uma mensagem de erro apropriada. As seqüências de caracteres de conversão são:
O ponteiro ou argumento uintptr_t é impresso como um nome de símbolo de kernel no formato módulo`nome do símbolo mais um deslocamento de byte hexadecimal opcional. Se o valor não estiver no intervalo definido por um símbolo de kernel definido, o valor será impresso como um inteiro hexadecimal.
O argumento char, short ou int é impresso como um caractere ASCII.
O argumento char, short ou int será impresso como um caractere ASCII, se o caractere for um caractere ASCII imprimível. Se o caractere não for um caractere imprimível, ele será impresso com a seqüência de escape correspondente, conforme mostrado na Tabela 2–5.
O argumento char, short, int, long ou long long é impresso como um inteiro decimal (base 10). Se o argumento for signed, ele será impresso como um valor assinado. Se o argumento for unsigned, ele será impresso como um valor não assinado. Esta conversão possui o mesmo significado que i.
O argumento float, double ou long double é convertido para o estilo [-] d.ddde± dd, onde existe um dígito antes do caractere fracionário e o número de dígitos depois dele é igual à precisão. O caractere fracionário será diferente de zero, se o argumento for diferente de zero. Se a precisão não for especificada, o valor de precisão padrão será 6. Se a precisão for 0 e o sinalizador # não estiver especificado, nenhum caractere fracionário aparecerá. O formato de conversão E produz um número com E em vez de e introduzindo o expoente. O expoente sempre contém pelo menos dois dígitos. O valor é arredondado pra o número apropriado de dígitos.
O argumento float, double ou long double é convertido para o estilo [-] ddd.ddd, onde número de dígitos após o caractere de precisão é igual à especificação de precisão. Se a precisão não for especificada, o valor de precisão padrão será 6. Se a precisão for 0 e o sinalizador # não estiver especificado, nenhum caractere fracionário aparecerá. Se um caractere fracionário aparecer, pelo menos um dígito aparecerá antes dele. O valor é arredondado pra o número apropriado de dígitos.
O argumento float, double ou long double é impresso no estilo f ou e (ou no estilo E no caso de um caractere de conversão G), com a precisão especificando o número de dígitos significativos. Se uma precisão explícita for 0, ela é considerada como 1. O estilo usado depende do valor convertido: o estilo e (ou E) será usado somente se o expoente resultante da conversão for menor que -4 ou maior ou igual à precisão. Zeros à direita são removidos da parte fracionária do resultado. Um caractere fracionário aparecerá somente se for seguido por um dígito. Se o sinalizador # for especificado, os zeros à direita não serão removidos do resultado.
O argumento char, short, int, long ou long long é impresso como um inteiro decimal (base 10). Se o argumento for signed, ele será impresso como um valor assinado. Se o argumento for unsigned, ele será impresso como um valor não assinado. Esta conversão tem o mesmo sentido de d.
O argumento char, short, int, long ou long long é impresso como um inteiro octal não assinado (base 8). Os argumentos que são signed ou unsigned podem ser usados com esta conversão. Se o sinalizador # for especificado, a precisão do resultado será aumentada, se necessário, para forçar o primeiro dígito do resultado a ser zero.
O ponteiro ou argumento uintptr_t é impresso como um inteiro hexadecimal (base 16). D aceita argumentos de ponteiro de qualquer tipo. Se o sinalizador # for especificado, um resultado diferente de zero terá 0x anteposto a ele.
O argumento deve ser uma matriz de char ou string. Os bytes da matriz ou string são lidos até um caractere nulo de terminação ou o fim dos dados, e interpretados e impressos como caracteres ASCII. Se a precisão não for especificada, ela é considerada como infinita, sendo assim, todos os caracteres até o primeiro caractere nulo serão impressos. Se a precisão for especificada, somente essa parte da matriz de caracteres, que será exibida no número correspondente de colunas da tela, será impressa. Se um argumento do tipo char * for formatado, ele deverá ser convertido para string ou prefixado com o operador stringof de D para indicar que o DTrace deve rastrear os bytes da seqüência e formatá-los.
O argumento deve ser uma matriz de char ou string. O argumento é processado como se fosse pela conversão %s, mas quaisquer caracteres ASCII que não sejam imprimíveis serão substituídos pela seqüência de escape correspondente descrita na Tabela 2–5.
O argumento char, short, int, long ou long long é impresso como um inteiro decimal não assinado (base 10). Os argumentos que são signed ou unsigned podem ser usados com esta conversão, e o resultado é sempre formatado como unsigned.
O argumento int é convertido em um caractere extenso (wchar_t ) e o caractere extenso resultante é impresso.
O argumento deve ser uma matriz de wchar_t. Os bytes da matriz ou são lidos até um caractere nulo de terminação ou o fim dos dados, e interpretados e impressos como caracteres extensos. Se a precisão não for especificada, ela é considerada como infinita, sendo assim, todos os caracteres extensos até o primeiro caractere nulo serão impressos. Se a precisão for especificada, somente essa parte da matriz de caracteres extensos, que será exibida no número correspondente de colunas da tela, será impressa.
O argumento char, short, int, long ou long long é impresso como um inteiro hexadecimal não assinado (base 16). Os argumentos que são signed ou unsigned podem ser usados com esta conversão. Se o formato x da conversão for usado, os dígitos de letra abcdef serão usados. Se o formato X da conversão for usado, os dígitos de letra ABCDEF serão usados. Se o sinalizador # for especificado, um resultado diferente de zero terá 0x (para %x) ou 0X (para %X) anteposto a ele.
O argumento uint64_t será interpretado como o número de nanossegundos desde 00:00 Horário Coordenado Universal, 1 de janeiro de 1970, e será impresso no seguinte formato cftime(3C) form: “%Y %a %b %e %T %Z”. O número de nanossegundos desde 00:00 UTC, 1 de janeiro, 1970, está disponível na variável walltimestamp.
Imprime um caractere literal %. Nenhum argumento é convertido. A especificação de conversão inteira deve ser %%.
A função printa() é usada para formatar os resultados de agregações em um programa em D. A função é chamada de uma de duas formas:
printa(@aggregation-name); printa(format-string, @aggregation-name);
Se o primeiro formato da função for usado, o comando dtrace(1M) tirará um instantâneo consistente dos dados da agregação e produzirá uma saída equivalente ao formato de saída padrão usado para agregações, descrito no Capítulo 9Agregações.
Se o segundo formato da função for usado, o comando dtrace(1M) tirará um instantâneo consistente dos dados da agregação e produzirá uma saída de acordo com as conversões especificadas na seqüência de formato, de acordo com as seguintes regras.
As conversões de formato devem corresponder à assinatura de tupla usada para criar a agregação. Cada elemento de tupla só pode aparecer uma vez. Por exemplo, se você agregar uma contagem usando as declarações de D seguintes:
@a["hello", 123] = count(); @a["goodbye", 456] = count();
e, em seguida, adicionar a declaração de D printa(seqüência de formato, @a) a uma cláusula de teste, dtrace tirará um instantâneo dos dados da agregação e produzirá uma saída, se você tiver inserido as declarações:
printf(format-string, "hello", 123); printf(format-string, "goodbye", 456);
e assim por diante para cada tupla definida na agregação.
Ao contrário de printf(), a seqüência de formato usada para printa() não precisa incluir todos os elementos da tupla. Ou seja, você pode ter uma tupla de tamanho 3 e apenas uma conversão de formato. Portanto, você pode omitir quaisquer chaves de tupla da saída de printa(), alterando a declaração de agregação para mover as chaves que você deseja omitir para o fim da tupla e, em seguida, omitir os especificadores de conversão correspondentes delas na seqüência de formato printa() .
O resultado da agregação pode ser incluído na saída, usando o caractere de sinalizador de formato adicional @, que só é válido quando usado com printa(). O sinalizador @ pode ser combinado com qualquer especificador de conversão de formato apropriado, e pode aparecer mais de uma vez em uma seqüência de formato, para que o resultado da tupla possa aparecer em qualquer local na saída e possa aparecer mais de uma vez. O conjunto de especificadores de conversão que podem ser usados com cada função de agregação são deduzidos pelo tipo de resultado da função de agregação. Os tipos de resultado de agregação são:
avg() |
uint64_t |
count() |
uint64_t |
lquantize() |
int64_t |
max() |
uint64_t |
min() |
uint64_t |
quantize() |
int64_t |
sum() |
uint64_t |
Por exemplo, para formatar os resultados de avg(), você pode aplicar as conversões de formato %d, %i, %o, %u ou %x. As funções quantize() e lquantize() formatam seus resultados como uma tabela ASCII em vez de um único valor.
O programa em D seguinte mostra um exemplo completo de printa (), usando o provedor de perfil profile para fazer uma amostra do valor de caller e, em seguida, formatando os resultados como uma tabela simples:
profile:::profile-997 { @a[caller] = count(); } END { printa("%@8u %a\n", @a); }
Se você usar o dtrace para executar este programa, espere alguns segundos, e pressione Control-C, para ver uma saída semelhante ao exemplo seguinte:
# dtrace -s printa.d ^C CPU ID FUNCTION:NAME 1 2 :END 1 0x1 1 ohci`ohci_handle_root_hub_status_change+0x148 1 specfs`spec_write+0xe0 1 0xff14f950 1 genunix`cyclic_softint+0x588 1 0xfef2280c 1 genunix`getf+0xdc 1 ufs`ufs_icheck+0x50 1 genunix`infpollinfo+0x80 1 genunix`kmem_log_enter+0x1e8 ... |
Se a função trace() for usada para capturar os dados em vez de printf(), o comando dtrace formatará os resultados usando um formato de saída padrão. Se os dados tiverem 1, 2, 4, ou 8 bytes de tamanho, o resultado será formatado como um valor inteiro decimal. Se os dados forem de qualquer outro tamanho e forem uma seqüência de caracteres imprimíveis, se interpretados como uma seqüência de bytes, eles serão impressos como uma seqüência ASCII. Se os dados forem de qualquer outro tamanho e não uma seqüência de caracteres imprimíveis, eles serão impressos como uma série de valores de byte formatados como inteiros hexadecimais.