Guia de rastreamento dinâmico Solaris

Capítulo 13 Rastreio especulativo

Este capítulo discute o recurso do DTrace de rastreio especulativo, a capacidade de rastrear tentativamente os dados e, em seguida, decidir entre comprometer os dados em um buffer de rastreio ou eliminá-los. No DTrace, o mecanismo principal para filtrar eventos desinteressantes é o mecanismo de predicado, discutido no Capítulo 4Estrutura de programa em D. Os predicados são úteis quando você sabe no momento que um teste é acionado, se o evento do teste é ou não interessante. Por exemplo, se estiver interessado somente na atividade associada a um determinado processo ou a determinado descritor de arquivo, você sabe quando o teste é acionado, caso esteja associado ao processo ou descritor de arquivo de interesse. Entretanto, em outras situações, talvez você não saiba se um determinado evento de teste é de seu interesse até algum tempo depois que o teste é acionado.

Por exemplo, se uma chamada do sistema estiver falhando ocasionalmente com um código de erro comum (por exemplo, EIO ou EINVAL ), talvez você queira examinar o caminho do código que está causando a condição de erro. Para capturar o caminho do código, você poderia ativar cada teste — mas apenas se a chamada que está falhando puder ser isolada de forma que um predicado consistente possa ser construído. Se as falhas forem esporádicas ou não determinísticas, você seria forçado a rastrear todos os eventos que devem ser interessantes, e mais tarde pós-processar os dados para filtrar aqueles que não estavam associados ao caminho de código que estava falhando. Neste caso, embora o número de eventos interessantes possa ser razoavelmente pequeno, o número de eventos que devem ser rastreados é muito grande, dificultando o pós-processamento.

Você pode usar o recurso de rastreio especulativo nessas situações para tentativamente rastrear os dados em um ou mais locais de teste e, em seguida, decidir entre comprometer os dados no buffer principal em outro local de teste. Como resultado, seus dados de rastreio contêm somente a saída interessante, nenhum pós-processamento é necessário, e a sobrecarga do DTrace é minimizada.

Interfaces de especulação

A tabela seguinte descreve as funções de especulação do DTrace:

Tabela 13–1 Funções de especulação do DTrace

Nome da função 

Args 

Descrição 

speculation

Nenhum 

Retorna um identificador de um novo buffer especulativo 

speculate

ID 

Significa que o restante da cláusula deve ser rastreado para o buffer especulativo especificado pelo ID 

commit

ID 

Compromete o buffer especulativo associado ao ID 

discard

ID 

Descarta o buffer especulativo associado ao ID 

Criando uma especulação

A função speculation() aloca um buffer especulativo e retorna um identificador de especulação. O identificador de especulação deve ser usado em chamadas subseqüentes à função speculate. () Os buffers especulativos são um recurso finito: Se nenhum buffer especulativo estiver disponível quando speculation() for chamada, um ID zero será retornado e um contador de erro do DTrace correspondente será incrementado. Um ID zero é sempre inválido, mas pode ser passado para speculate(), commit() ou discard(). Se uma chamada a speculation() falhar, uma mensagem do dtrace semelhante ao exemplo seguinte será gerada:


dtrace: 2 failed speculations (no speculative buffer space available)

O número de buffers especulativos é um como padrão, mas pode ser opcionalmente ajustado para mais. Consulte Opções e ajuste de especulação para obter mais informações.

Usando uma especulação

Para usar uma especulação, um identificador retornado de speculation () deve ser passado para a função speculate() em uma cláusula antes de quaisquer ações de gravação de dados. Todas as ações de gravação de dados subseqüentes em uma cláusula contendo speculate() serão rastreadas especulativamente. O compilador de D gerará um erro de tempo de compilação, se uma chamada a speculate() seguir as ações de gravação de dados em uma cláusula de teste de D. Portanto, as cláusulas podem conter rastreio especulativo ou solicitações de rastreio não especulativo, mas não ambos.

As ações de agregação, as ações destrutivas e a ação exit nunca podem ser especulativas. Qualquer tentativa de usar uma dessas ações em uma cláusula contendo os resultados de speculate() causará um erro de tempo de compilação. Uma speculate() não pode seguir uma speculate(): somente uma especulação é permitida por cláusula. Uma cláusula que contém somente uma speculate() rastreará especulativamente a ação padrão, que é definida para rastrear somente o ID de teste ativado. Consulte o Capítulo 10Ações e sub-rotinas para obter uma descrição da ação padrão.

Geralmente, você atribui o resultado de speculation() a uma variável de segmento local e, em seguida, usa essa variável como um predicado subseqüente para outros testes, assim como um argumento speculate(). Por exemplo:

syscall::open:entry
{
	self->spec = speculation();
}

syscall:::
/self->spec/
{
	speculate(self->spec);
	printf("this is speculative");
}

Comprometendo uma especulação

Você compromete as especulações usando a função commit. () Quando um buffer especulativo é comprometido, seus dados são copiados para o buffer principal. Se houver mais dados no buffer especulativo especificado do que o espaço disponível no buffer principal, nenhum dado será copiado e a contagem de soltar do buffer será incrementada. Se o buffer tiver sido rastreado especulativamente para mais de uma CPU, os dados especulativos da CPU comprometida serão copiados imediatamente, enquanto os dados especulativos em outras CPUs serão copiados algum tempo depois de commit(). Portanto, algum tempo pode passar entre uma commit() iniciando em uma CPU e os dados sendo copiados dos buffers especulativos para os buffers principais em todas as CPUs. Esse tempo não pode ser mais longo do que o tempo determinado pela taxa de limpeza. Consulte Opções e ajuste de especulação para obter mais detalhes.

Um buffer especulativo de comprometimento pode não estar disponível para chamadas de speculation() subseqüentes, até que cada buffer especulativo por CPU tenha sido completamente copiado para seu buffer principal por CPU correspondente. Semelhantemente, as chamadas subseqüentes a speculate() para o buffer de comprometimento serão silenciosamente descartadas, e as chamadas subseqüentes para commit() ou discard() falharão silenciosamente. Finalmente, uma cláusula contendo uma commit() não pode conter uma ação de gravação de dados, mas uma cláusula pode conter várias chamadas commit() para comprometer os buffers de deslocamento.

Descartando uma especulação

Você descarta as especulações usando a função discard() . Quando um buffer especulativo é descartado, seu conteúdo é jogado fora. Se a especulação estiver ativa apenas na CPU que chama discard(), o buffer será imediatamente disponibilizado para chamadas subseqüentes a speculation (). Se a especulação estiver ativa em mais de uma CPU, o buffer descartado será disponibilizado para a speculation() subseqüente algum tempo depois da chamada a discard(). O tempo entre uma discard() em uma CPU e o buffer que está sendo disponibilizado para especulações subseqüentes é garantidamente mais curto que o tempo definido pela taxa de limpeza. Se, na hora em que speculation () for chamada, não houver nenhum buffer disponível porque todos os buffers especulativos estão sendo descartados no momento ou comprometidos, será gerada uma mensagem do dtrace semelhante ao exemplo seguinte:


dtrace: 905 failed speculations (available buffer(s) still busy)

A probabilidade de todos os buffers estarem indisponíveis pode ser reduzida, através do ajuste do número de buffers de especulação ou a taxa de limpeza. Consulte Opções e ajuste de especulação para obter detalhes.

Exemplo de especulação

Um uso potencial de especulações é realçar um caminho de código particular. O exemplo seguinte mostra o caminho de código inteiro sob a chamada do sistema a open(2) somente quando open() falha:


Exemplo 13–1 specopen.d: fluxo de código para open(2) falha

#!/usr/sbin/dtrace -Fs

syscall::open:entry,
syscall::open64:entry
{
	/*
	 * The call to speculation() creates a new speculation.  If this fails,
	 * dtrace(1M) will generate an error message indicating the reason for
	 * the failed speculation(), but subsequent speculative tracing will be
	 * silently discarded.
	 */
	self->spec = speculation();
	speculate(self->spec);

	/*
	 * Because this printf() follows the speculate(), it is being 
	 * speculatively traced; it will only appear in the data buffer if the
	 * speculation is subsequently commited.
	 */
	printf("%s", stringof(copyinstr(arg0)));
}

fbt:::
/self->spec/
{
	/*
	 * A speculate() with no other actions speculates the default action:
	 * tracing the EPID.
	 */
	speculate(self->spec);
}

syscall::open:return,
syscall::open64:return
/self->spec/
{
	/*
	 * To balance the output with the -F option, we want to be sure that
	 * every entry has a matching return.  Because we speculated the
	 * open entry above, we want to also speculate the open return.
	 * This is also a convenient time to trace the errno value.
	 */
	speculate(self->spec);
	trace(errno);
}

syscall::open:return,
syscall::open64:return
/self->spec && errno != 0/
{
	/*
	 * If errno is non-zero, we want to commit the speculation.
	 */
	commit(self->spec);
	self->spec = 0;
}

syscall::open:return,
syscall::open64:return
/self->spec && errno == 0/
{
	/*
	 * If errno is not set, we discard the speculation.
	 */
	discard(self->spec);
	self->spec = 0;
}

Executar o script acima produzirá uma saída semelhante ao exemplo seguinte:


# ./specopen.d
dtrace: script './specopen.d' matched 24282 probes
CPU FUNCTION                                 
  1  => open                                  /var/ld/ld.config
  1    -> open                                
  1      -> copen                             
  1        -> falloc                          
  1          -> ufalloc                       
  1            -> fd_find                     
  1              -> mutex_owned               
  1              <- mutex_owned               
  1            <- fd_find                     
  1            -> fd_reserve                  
  1              -> mutex_owned               
  1              <- mutex_owned               
  1              -> mutex_owned               
  1              <- mutex_owned               
  1            <- fd_reserve                  
  1          <- ufalloc                       
  1          -> kmem_cache_alloc              
  1            -> kmem_cache_alloc_debug      
  1              -> verify_and_copy_pattern   
  1              <- verify_and_copy_pattern   
  1              -> file_cache_constructor    
  1                -> mutex_init              
  1                <- mutex_init              
  1              <- file_cache_constructor    
  1              -> tsc_gethrtime             
  1              <- tsc_gethrtime             
  1              -> getpcstack                
  1              <- getpcstack                
  1              -> kmem_log_enter            
  1              <- kmem_log_enter            
  1            <- kmem_cache_alloc_debug      
  1          <- kmem_cache_alloc              
  1          -> crhold                        
  1          <- crhold                        
  1        <- falloc                          
  1        -> vn_openat                       
  1          -> lookupnameat                  
  1            -> copyinstr                   
  1            <- copyinstr                   
  1            -> lookuppnat                  
  1              -> lookuppnvp                
  1                -> pn_fixslash             
  1                <- pn_fixslash             
  1                -> pn_getcomponent         
  1                <- pn_getcomponent         
  1                -> ufs_lookup              
  1                  -> dnlc_lookup           
  1                    -> bcmp                
  1                    <- bcmp                
  1                  <- dnlc_lookup           
  1                  -> ufs_iaccess           
  1                    -> crgetuid            
  1                    <- crgetuid            
  1                    -> groupmember         
  1                      -> supgroupmember    
  1                      <- supgroupmember
  1                    <- groupmember         
  1                  <- ufs_iaccess           
  1                <- ufs_lookup              
  1                -> vn_rele                 
  1                <- vn_rele                 
  1                -> pn_getcomponent         
  1                <- pn_getcomponent         
  1                -> ufs_lookup              
  1                  -> dnlc_lookup           
  1                    -> bcmp                
  1                    <- bcmp                
  1                  <- dnlc_lookup           
  1                  -> ufs_iaccess           
  1                    -> crgetuid            
  1                    <- crgetuid            
  1                  <- ufs_iaccess           
  1                <- ufs_lookup              
  1                -> vn_rele                 
  1                <- vn_rele                 
  1                -> pn_getcomponent         
  1                <- pn_getcomponent         
  1                -> ufs_lookup              
  1                  -> dnlc_lookup           
  1                    -> bcmp                
  1                    <- bcmp                
  1                  <- dnlc_lookup           
  1                  -> ufs_iaccess           
  1                    -> crgetuid            
  1                    <- crgetuid            
  1                  <- ufs_iaccess           
  1                  -> vn_rele               
  1                  <- vn_rele               
  1                <- ufs_lookup              
  1                -> vn_rele                 
  1                <- vn_rele                 
  1              <- lookuppnvp                
  1            <- lookuppnat                  
  1          <- lookupnameat                  
  1        <- vn_openat                       
  1        -> setf                            
  1          -> fd_reserve                    
  1            -> mutex_owned                 
  1            <- mutex_owned                 
  1            -> mutex_owned                 
  1            <- mutex_owned                 
  1          <- fd_reserve                    
  1          -> cv_broadcast                  
  1          <- cv_broadcast                  
  1        <- setf                            
  1        -> unfalloc                        
  1          -> mutex_owned                   
  1          <- mutex_owned                   
  1          -> crfree                        
  1          <- crfree                        
  1          -> kmem_cache_free               
  1            -> kmem_cache_free_debug       
  1              -> kmem_log_enter            
  1              <- kmem_log_enter            
  1              -> tsc_gethrtime             
  1              <- tsc_gethrtime             
  1              -> getpcstack                
  1              <- getpcstack                
  1              -> kmem_log_enter            
  1              <- kmem_log_enter
  1              -> file_cache_destructor     
  1                -> mutex_destroy           
  1                <- mutex_destroy           
  1              <- file_cache_destructor     
  1              -> copy_pattern              
  1              <- copy_pattern              
  1            <- kmem_cache_free_debug       
  1          <- kmem_cache_free               
  1        <- unfalloc                        
  1        -> set_errno                       
  1        <- set_errno                       
  1      <- copen                             
  1    <- open                                
  1  <= open                                          2

Opções e ajuste de especulação

Se um buffer especulativo estiver cheio quando uma ação de rastreio especulativo for tentada, nenhum dado será armazenado no buffer e uma contagem de soltar será incrementada. Caso isso ocorra, uma mensagem do dtrace semelhante ao exemplo seguinte será gerada:


dtrace: 38 speculative drops

As solturas especulativas não evitarão que o buffer especulativo cheio seja copiado para o buffer principal quando o buffer for comprometido. Semelhantemente, as solturas especulativas poderão ocorrer, mesmo se houver tido solturas em um buffer especulativo que foi finalmente descartado. As solturas especulativas podem ser reduzidas, através do aumento do tamanho do buffer especulativo, que é ajustado através da opção specsize . A opção specsize pode ser especificada com qualquer sufixo de tamanho. A política de redimensionamento deste buffer é definida pela opção bufresize .

Os buffers especulativos talvez estejam indisponíveis quando speculation () for chamada. Se houver buffers que ainda não foram comprometidos ou descartados, uma mensagem do dtrace semelhante ao exemplo seguinte será gerada:


dtrace: 1 failed speculation (no speculative buffer available)

Você pode reduzir a probabilidade de especulações falhas desta natureza, aumentando o número de buffers especulativo com a opção nspec . O valor padrão de nspec é um.

Como alternativa, speculation() pode falhar porque todos os buffers especulativos estão ocupados. Neste caso, uma mensagem do dtrace semelhante ao exemplo seguinte será gerada:


dtrace: 1 failed speculation (available buffer(s) still busy)

Esta mensagem indica que speculation() foi chamada depois que commit() foi chamada por um buffer especulativo, mas antes que o buffer fosse realmente comprometido em todas as CPUs. Você pode reduzir a probabilidade de especulações falhas desta natureza, aumentando a taxa na qual as CPUs são limpas com a opção cleanrate. O valor padrão de cleanrate é101hz.


Observação –

Os valores da opção cleanrate devem ser especificados em número por segundo. Use o sufixo hz.