Ao monitorar o uso da CPU, é crucial dividir a análise em diferentes componentes para entender de forma precisa o que está afetando o desempenho do sistema. Cada parte da utilização da CPU pode refletir comportamentos distintos, que exigem abordagens específicas para diagnóstico e resolução de problemas. A medição é feita separadamente para as áreas de Sistema, Usuário, Espera de I/O e Inatividade, e é fundamental analisar cada uma delas conforme o contexto da utilização.

Sistema

A utilização da CPU no espaço do sistema está relacionada às chamadas de sistema que ocorrem no kernel. Essas chamadas envolvem a execução de métodos relacionados ao controle de processos, gerenciamento de dispositivos, manipulação de arquivos e redes, além do gerenciamento de entradas e saídas, informações do sistema e controle de tempo. Normalmente, o uso do sistema não ultrapassa 30% da utilização total da CPU. Quando se observa uma alta utilização dessa área, é importante utilizar ferramentas específicas para monitoramento de chamadas de sistema e rastreamento de pilha, como o strace, jstack e pstack. Essas ferramentas permitem identificar quais tipos de chamadas de sistema estão ocorrendo e qual o código envolvido, ajudando a traçar a relação entre as chamadas de sistema de baixo nível e as pilhas de chamadas da aplicação.

Se a utilização do sistema for elevada, você deve investigar as chamadas de sistema, que fornecem informações sobre os IDs dos processos e threads, bem como os descritores de arquivos envolvidos. Em chamadas bloqueantes, a relação entre threads e descritores de arquivos é relativamente simples, mas em chamadas não bloqueantes, onde múltiplas threads são criadas, a compreensão dessa relação torna-se mais complexa.

Esperas de I/O (IO Wait)

A utilização de CPU na categoria de Espera de I/O ocorre quando um processo ou thread foi alocado à CPU, mas está aguardando a conclusão de uma operação de I/O, o que impede o uso da CPU. Embora a thread esteja pronta para ser executada, ela fica bloqueada até que o evento de I/O seja finalizado. Em operações de leitura de disco, por exemplo, uma thread pode ser bloqueada até que uma interrupção de conclusão seja acionada por outra thread. O valor de espera de I/O não é contabilizado como uso real da CPU, mas sim como a quantidade de CPU indisponível devido ao tempo de espera por I/O. Esse tempo costuma representar menos de 20% do uso total da CPU. Se a porcentagem de espera de I/O for alta, isso pode indicar um problema relacionado ao uso excessivo de I/O ou à sobrecarga do disco.

Quando a espera de I/O é elevada, é necessário investigar outros recursos além da CPU, como o próprio disco. Se o problema não estiver na aplicação, mas em uma camada inferior da infraestrutura, o diagnóstico pode se tornar mais complexo. Problemas de infraestrutura são difíceis de isolar e requerem uma análise cuidadosa para evitar conclusões precipitadas. A análise deve focar inicialmente na espera de I/O e nas métricas de carga, desconsiderando o uso da CPU em si.

Usuário

O uso da CPU para a execução de métodos gerais, que não envolvem chamadas de sistema, é categorizado como Utilização de Usuário. Em ambientes de microserviços ou servidores de banco de dados, é comum que a utilização de CPU pelo usuário seja maior do que nas outras partes da pilha de execução. Para analisar o impacto dessa utilização, ferramentas de perfil de CPU podem ser usadas para identificar quais threads de um processo estão consumindo mais recursos. Ao identificar threads com alto uso de CPU, é possível utilizar perfis para examinar o comportamento delas com mais detalhes. Se os perfis não fornecerem informações suficientes, outras ferramentas de diagnóstico devem ser utilizadas para uma análise mais profunda.

Quando a utilização da CPU pelo usuário é elevada, o problema provavelmente está na aplicação. Após identificar os processos e threads que consomem mais recursos, a análise deve se concentrar na melhoria da eficiência dessas partes da aplicação.

Processos

No Linux, os processos podem ser divididos entre os que operam no espaço do usuário e os que operam no espaço do kernel, com a comunicação entre os dois ocorrendo através de chamadas de sistema. Um processo no espaço do usuário é criado por meio de uma biblioteca que solicita ao kernel a criação do processo. No entanto, a criação de processos no espaço do kernel é mais direta, utilizando funções específicas como kthread_create(). O processo de criação no kernel é iniciado quando o método _do_fork() é chamado. Esse processo ocorre de maneira mais robusta no kernel, onde é gerenciado com maior controle.

Processos de usuário e kernel são igualmente importantes, e entender suas diferenças é fundamental para entender o fluxo de execução e o comportamento do sistema. Além disso, as ferramentas de rastreamento de pilha podem ser usadas para analisar a execução de processos e métodos, identificando aqueles que mais consomem recursos e, consequentemente, podem estar causando problemas no sistema.

Estrutura de Dados do Kernel

O kernel possui várias estruturas de dados importantes para o gerenciamento dos processos. Uma das estruturas centrais é o task_struct, que armazena informações detalhadas sobre os processos, como o nome do processo, ID, tempo de execução e recursos de memória utilizados. No topo da pilha de cada processo, existe uma estrutura chamada thread_info, que contém informações de execução do processo, incluindo o contexto e os registros executados antes do agendamento da CPU. As diferenças entre task_struct e thread_info residem no fato de que a primeira é independente da arquitetura da CPU, enquanto a segunda depende da arquitetura específica do processador utilizado. Compreender essas estruturas é essencial para diagnosticar problemas em nível de hardware, como falhas de contexto ou desempenho relacionado à troca de processos.

Rastreamento de Pilha

O rastreamento de pilha fornece uma visão detalhada dos processos e threads em execução, incluindo o tempo e os recursos consumidos por cada um deles. Ao analisar o rastreamento de pilha, é possível identificar processos que consomem grandes quantidades de recursos e determinar quais métodos estão envolvidos nesse consumo. O rastreamento de pilha inclui o ID da thread e informações relacionadas às chamadas de sistema, permitindo uma análise integrada da aplicação e do sistema. Ao identificar threads com uso excessivo de CPU, o rastreamento de pilha revela qual método está sendo executado e qual código está sendo processado nesse momento.

Além disso, o conceito de fila de execução (run-queue) é importante para entender como o sistema gerencia os processos que aguardam para ser executados na CPU. Antes de um processo ser executado, ele é inserido nessa fila, e o escalonador calcula a prioridade entre os processos, escolhendo qual será executado a seguir.

Conclusão

A análise de processos e uso da CPU é um campo complexo que exige um entendimento profundo das diferentes áreas envolvidas: sistema, usuário, espera de I/O e inatividade. Cada um desses componentes reflete um aspecto distinto da performance e deve ser analisado com ferramentas especializadas para um diagnóstico preciso. Além disso, o rastreamento de pilha e a estrutura interna do kernel são fundamentais para entender o comportamento dos processos e identificar gargalos no sistema. Esse nível de monitoramento permite não apenas diagnosticar problemas de performance, mas também otimizar a utilização de recursos e melhorar a eficiência do sistema como um todo.

Como Analisar e Detectar Anomalias em Dados de Logs

A detecção de anomalias em dados de logs é uma prática essencial para identificar comportamentos inesperados em sistemas, sem a necessidade de intervenção manual constante. A principal solução reside na compreensão do padrão dos logs gerados e na habilidade de identificar em tempo real quando um log se desvia das condições normais. Quando você analisa os dados de logs (como carga do sistema, tempo de acesso e volume de exportação de dados), consegue observar comportamentos que se diferenciam dos padrões passados ou daqueles observados em outros componentes com o mesmo papel. A partir dessa análise, é possível identificar eventos automaticamente com um valor de probabilidade, facilitando a ação diante de possíveis falhas ou comportamentos anômalos.

Um ponto crucial é a análise dos resultados dentro de intervalos de tempo específicos, conhecidos como "buckets". Analisar os resultados por bucket é útil quando você mede várias métricas e tem limiares dinâmicos. No nível do bucket, é possível entender o quão "normal" foi determinado intervalo de tempo em relação a outros períodos para uma tarefa específica. Essa abordagem permite identificar as entidades mais incomuns dentro de um escopo temporal, determinando, assim, quando ocorreu uma anomalia e qual foi sua gravidade.

Por exemplo, ao examinar o log do servidor, podemos observar um campo específico, como o response.keyword, que pode ter um valor como 404. Essa informação se torna relevante quando, ao investigar o endereço IP que gerou a anomalia, constatamos que 100% das requisições resultaram em um código de resposta 404. O fato de todas as requisições gerarem o mesmo código de erro torna esse valor altamente significativo. A análise de como os valores 404 variam ao longo do tempo pode indicar se o comportamento é realmente anômalo, pois, em muitos casos, picos de erros podem ser causados por fatores pontuais e não por uma falha do sistema.

Outro exemplo prático pode ser a inserção de documentos falsos nos índices monitorados pela tarefa de detecção de anomalias. Após essa inserção, seria possível observar um aumento nas requisições de um endereço IP incomum (como o 0.0.0.0), que gera um código de resposta 404. Isso permite que o sistema detecte comportamentos fora do comum. Para validar esse processo, a hora atual em UTC deve ser determinada, visto que os documentos são armazenados em UTC. Com isso, você pode criar documentos únicos, modificando aleatoriamente seus valores através de scripts e verificando no painel de controle se a detecção de anomalias funciona corretamente.

No contexto de aprendizado de máquina, a detecção de anomalias pode gerar uma pontuação de confiança e um grau de anomalia. Configurar limiares de detecção de anomalias diretamente pode ser problemático, pois, em muitos casos, os desenvolvedores e engenheiros não conhecem os limiares de antemão. Para isso, a utilização de uma detecção de anomalias não supervisionada é uma boa solução inicial, ajudando a encontrar os limiares apropriados sem a necessidade de configurações prévias.

Além disso, a detecção de anomalias envolve diversas abordagens analíticas. Uma delas é a análise dos dados individuais e registros, observando se o número de anomalias aumenta em determinados períodos do dia. Também é fundamental analisar as categorias de anomalias, para entender quais são mais relevantes dentro de determinado contexto. Outra análise importante é a observação do comportamento das anomalias durante diferentes períodos do dia, armazenando pontos de dados de períodos específicos e analisando-os em busca de padrões anômalos. A avaliação dos dados deve ser feita levando em consideração o tempo anterior e posterior, e a comparação entre anomalias similares.

É importante compreender que a detecção de anomalias não se baseia apenas em contagens, frequências ou valores de eventos, mas também na comparação de como os dados se comportam ao longo do tempo. Para isso, o modelo utiliza dois parâmetros principais: grau de anomalia e confiança. O grau de anomalia é um número entre 0 e 1 que indica a severidade da anomalia, enquanto a confiança é uma estimativa da probabilidade de que o grau de anomalia detectado seja de fato correto, com valores de confiança mais altos indicando maior precisão. Ambos os parâmetros ajudam a determinar a gravidade de um evento e quando é necessário agir.

Por fim, ao utilizar a API de Análise de Resultados, você pode armazenar as anomalias identificadas em um índice de resultados, facilitando a análise posterior. Embora a definição de categorias ajude a identificar as anomalias com mais precisão, é necessário garantir que o conjunto de dados seja suficientemente grande e que a detecção de anomalias seja feita ao longo de uma janela temporal extensa. Caso contrário, o que pode parecer uma anomalia em uma janela de cinco minutos pode não representar um problema quando analisado em uma janela de 24 horas.

Como a Propagação de Traces Funciona no OpenTelemetry?

A propagação de traces é um conceito essencial para o rastreamento distribuído, especialmente em sistemas compostos por microserviços. No OpenTelemetry, esse processo depende da transmissão de informações através de cabeçalhos HTTP específicos, como os identificadores de trace e span, que precisam ser corretamente configurados e propagados entre os sistemas. A configuração incorreta desses identificadores pode resultar em falhas no rastreamento, com traces incompletos ou com erros de execução.

Quando se trata de propagação de trace, o OpenTelemetry oferece diversos formatos e métodos para transmitir as informações necessárias. Entre as opções mais comuns estão: tracecontext (W3C Trace Context), baggage (W3C Baggage), b3 (B3 Single), b3multi (B3 Multi), jaeger, xray (AWS X-Ray) e ot trace. O valor padrão da variável de ambiente OTEL_PROPAGATORS é "tracecontext, baggage", mas isso pode ser configurado conforme a necessidade, utilizando outros valores para atender a diferentes requisitos de sistemas e agentes.

A propagação de trace é uma parte crítica para a comunicação entre diferentes sistemas e para garantir que o contexto correto seja transmitido ao longo do ciclo de vida de uma requisição. A falha em seguir as convenções de propagação de trace pode gerar diversos problemas, como a perda de spans, erros nas relações de dependência entre os spans ou até a falha no rastreamento completo da requisição. O trace ID, o span ID e o parent span ID devem ser configurados corretamente e propagados através dos sistemas de acordo com os padrões estabelecidos.

Um aspecto importante é que, enquanto o OpenTelemetry utiliza uma estrutura de dados do tipo "textmap" para a propagação de contextos, muitas soluções de observabilidade comerciais, como as implementações de observabilidade próprias de fornecedores, utilizam suas próprias convenções sem seguir o padrão do OpenTelemetry. Esse descompasso pode gerar confusão, especialmente para quem está começando a trabalhar com rastreamento distribuído. É fundamental entender que a compatibilidade entre os sistemas e as convenções adotadas por cada ferramenta de observabilidade pode impactar diretamente a eficácia do rastreamento.

Além disso, a propagação de trace pode ser interrompida em alguns cenários, especialmente quando há servidores de mensagens envolvidos. O protocolo HTTP foi originalmente pensado para ser utilizado com o conceito de trace, mas quando o trace atravessa sistemas que não seguem exatamente as mesmas convenções (como servidores de mensagens), a propagação precisa ser manualmente instrumentada. A inserção de um contexto de trace em servidores de mensagens, como JMS ou JCSMP, pode exigir ajustes adicionais no código, incluindo a instrumentação manual para garantir que os spans sejam corretamente transmitidos entre os diferentes pontos do sistema.

Em um sistema distribuído baseado em microserviços, a propagação de trace geralmente envolve o seguinte fluxo de trabalho: primeiro, o trace precisa ser instrumentado, ou seja, a identificação das partes críticas do código que precisam ser rastreadas. Depois, o contexto precisa ser extraído e injetado nos cabeçalhos de comunicação entre os microserviços. Cada microserviço, ao receber uma requisição, deve verificar se o contexto de trace já está presente e, caso contrário, gerar um novo contexto de trace para a requisição.

Um dos desafios principais é garantir que a comunicação entre os microserviços utilize o mesmo formato de propagação. Se um microserviço upstream utiliza um propagador B3, o microserviço downstream precisa estar preparado para lidar com esse formato específico. Caso contrário, um novo trace será gerado, o que prejudica a visibilidade do processo completo. Após a propagação, as informações do trace devem ser transferidas para o backend de observabilidade, onde serão armazenadas e analisadas.

É importante notar que a instrumentação automática, frequentemente implementada por meio de agentes, oferece uma solução para sistemas que já são conhecidos e amplamente utilizados, como o Spring Boot. No entanto, para aplicações customizadas ou novos serviços que não foram previamente configurados no agente, pode ser necessário realizar a instrumentação manual, o que exige maior cuidado. A instrumentação manual pode ser mais suscetível a falhas, uma vez que os desenvolvedores precisam garantir que o código esteja corretamente ajustado para lidar com o processo de rastreamento.

Por fim, vale ressaltar que as soluções comerciais de observabilidade frequentemente oferecem facilidades para configuração da instrumentação, permitindo que os desenvolvedores selecionem quais classes e métodos desejam instrumentar, sem a necessidade de ajustes manuais no código. Isso facilita a implementação do rastreamento em larga escala e reduz a sobrecarga operacional, embora ainda seja necessário garantir a compatibilidade entre os diferentes formatos e agentes utilizados.