Degustação do Elastic RUM (Real User Monitoring)

Desculpe se fiz você pensar que degustaria um maravilhoso coquetel feito com rum e aí você se deu conta de que o RUM de que estou falando não é aquele que você imaginava. Mas tenha a certeza de que o Elastic RUM é igualmente maravilhoso! Vamos degustar! Desde já aviso que levará um pouco de tempo para examinarmos os detalhes de que tratarei neste post.

O que é o RUM?

O monitoramento de usuários reais da Elastic ou RUM, pelas iniciais em inglês, captura as interações dos usuários com o navegador da web e fornece uma visualização detalhada da “experiência real do usuário” das suas aplicações web do ponto de vista do desempenho. O Agente de RUM da Elastic é um Agente JavaScript, ou seja, ele oferece suporte a qualquer aplicação baseada em JavaScript. O RUM pode fornecer insights valiosos sobre suas aplicações. Alguns dos benefícios comuns do RUM:

  • Os dados de desempenho de RUM podem ajudar a identificar gargalos e descobrir como os problemas de desempenho do site afetam a experiência dos visitantes.
  • As informações de agente de usuário capturadas pelo RUM permitem identificar os navegadores, os dispositivos e as plataformas mais usados pelos clientes para você poder fazer otimizações fundamentadas na sua aplicação.
  • Juntamente com as informações de localização, os dados de desempenho de usuário individual do RUM ajudam a entender o desempenho regional do seu site em nível mundial.
  • O RUM fornece insights e mensurações para os acordos de nível de serviço (SLAs, pelas iniciais em inglês) das suas aplicações.
  • O RUM reúne informações sobre visitas de clientes e comportamento de cliques ao longo do tempo que podem ser úteis para as equipes de desenvolvimento identificarem o impacto dos novos recursos

Comçando a usar o RUM com o Elastic APM

Neste post, vou apresentar o passo a passo de todo o processo de instrumentação de uma aplicação web simples criada com um frontend React e um backend Spring Boot. Você verá como é fácil usar o agente de RUM. Como bônus, você também verá como o Elastic APM vincula as informações de desempenho do frontend e do backend a uma visão de trace distribuída e holística. Leia o post anterior para obter uma visão geral do Elastic APM e do tracing distribuído se tiver interesse em saber mais detalhes.

Para usar o monitoramento de usuários reais do Elastic APM, você precisa ter o Elastic Stack com o servidor do APM instalado. Você pode baixar e instalar o Elastic Stack mais recente com servidor do APM localmente no computador. Entretanto, a abordagem mais fácil seria criar uma conta de avaliação do Elastic Cloud e ter o cluster pronto em alguns minutos. O APM está habilitado para o modelo otimizado de E/S padrão. De agora em diante, vou pressupor que você tem um cluster já pronto.

Aplicação de amostra

A aplicação que vamos instrumentar é uma aplicação simples de banco de dados de carros criada com um frontend React e um backend Spring Boot que oferece acesso de API a um banco de dados de carros na memória. A aplicação é propositadamente simples. A ideia é mostrar as etapas de instrumentação detalhadas a partir do zero para você poder instrumentar suas próprias aplicações seguindo as mesmas etapas.

A simple application with a React frontend and Spring backend

Crie um diretório chamado CarApp em qualquer local no notebook. Clone a aplicação de frontend e de backend nesse diretório.

git clone https://github.com/carlyrichmond/carfront
git clone https://github.com/carlyrichmond/cardatabase

Como você pode ver, a aplicação é extremamente simples. Há somente alguns componentes no frontend React e algumas classes na aplicação Spring Boot de backend. Compile e execute a aplicação seguindo as instruções no GitHub tanto para o frontend quanto para o backend. Você deverá ver algo como a tela abaixo. Você pode navegar, filtrar carros e executar opções de CRUD neles.

The simple React user interface

Agora, com a aplicação em execução, estamos prontos para passar pela instrumentação usando o agente de RUM.

Instrumentação avançada pronta para uso com o RUM

É necessário um servidor do Elastic APM para começar. Você precisará habilitar o RUM para capturar os eventos do seu agente de RUM. Para configurar seu agente de RUM, há duas maneiras:

  1. Você pode instalar o agente de RUM como uma dependência de projeto por meio de um gerenciador de pacotes como o npm:
    npm install @elastic/apm-rum --save
  2. Inclua o agente de RUM por meio da tag de script HTML. Observe que isso pode ser executado como uma operação com ou sem bloqueio conforme a documentação.
    <script 
    src="https://unpkg.com/@elastic/apm-rum@5.12.0/dist/bundles/elastic-apm-rum.umd.min.js">
    </script>
    <script>
    elasticApm.init({
    serviceName: 'carfront',
    serverUrl: 'http://localhost:8200',
    serviceVersion: '0.90'
    })
    </script>

Como nosso frontend é uma aplicação React, vamos usar a primeira abordagem. Depois de instalar @elastic/apm-rum em seu projeto, confira o código de inicialização no rum.js. Ele está localizado no mesmo diretório que seu index.js e será parecido com isto, mas com serviceUrl substituído por seu próprio endpoint do servidor do APM:

import { init as initApm } from '@elastic/apm-rum'
var apm = initApm({
//Defina o nome do serviço necessário (caracteres permitidos: a-z, A-Z, 0-9, -, _ e espaço)
serviceName: 'carfront',
// Defina a versão da sua aplicação
// Usado no APM Server para encontrar o mapa de origem correto
serviceVersion: '0.90',
// Definir URL customizado do APM Server (padrão: http://localhost:8200)
serverUrl: 'APM_URL',
// distributedTracingOrigins: ['http://localhost:8080'],
})
export default apm;

É só fazer isso para inicializar o agente de RUM! Se você estiver usando recursos específicos do framework, como roteamento em React, Angular ou Vue, convém instalar e configurar também as integrações específicas do framework, que são abordadas na documentação. Nesse caso, como esta é uma página única que não requer instrumentação específica do React, não instalamos a dependência adicional.

Não se preocupe com o DistributionTracingOrigins agora. A seguir está uma breve explicação de algumas das configurações:

  1. Service name: (nome do serviço) o nome do serviço precisa ser definido. Ele representa sua aplicação na UI do APM. Atribua um nome significativo.
  2. Service version: (versão do serviço) esta é a versão da sua aplicação. Este número de versão também é usado pelo servidor do APM para localizar o mapa de origem correto. Vamos tratar do mapa de origem em detalhes posteriormente.
  3. Server URL: (URL do servidor) este é o URL do servidor do APM. Observe que o URL do servidor do APM normalmente é acessível pela internet pública porque seu agente de RUM relata dados para ele a partir dos navegadores dos usuários finais na internet.

Quem tem familiaridade com os agentes de backend do Elastic APM pode estar imaginando por que o token do APM não foi informado aqui. Isso ocorre porque o agente de RUM na verdade não usa um token do APM secreto. O token só é usado para agentes de backend. Como o código de frontend é público, o token secreto não oferece segurança adicional.

Vamos carregar este arquivo JavaScript quando a aplicação for carregada e incluí-lo nos locais onde queremos fazer instrumentação customizada. Por enquanto, vamos ver o que obtemos de imediato, sem qualquer instrumentação customizada. Para fazer isso, precisamos simplesmente o rum.js no index.js. O arquivo index.js importa o rum.js e define um nome de carregamento de página. Sem definir um nome de carregamento de página, você verá o carregamento da página listado como “/” na UI do APM, o que não é muito intuitivo. Veja como fica o index.js.

import apm from './rum'
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
apm.setInitialPageLoadName("Car List")
ReactDOM.render(, document.getElementById('root'));
serviceWorker.unregister();

Gere algum tráfego para sua aplicação acessando páginas e adicionando ou excluindo carros. Em seguida, faça login no Kibana e clique no bloco Observability. A partir daí, selecione a opção Services (Serviços) no submenu APM, conforme mostrado abaixo:

Você deverá ver um serviço chamado “carfront” (dianteira do carro) listado. Se você clicar no nome do serviço, acessará a página de transação. Você deverá encontrar uma visão geral de métricas como latência e taxa de transferência para o período padrão de “Last 15 minutes” (Últimos 15 minutos). Caso contrário, altere o seletor de tempo para esse intervalo.

No segmento de transações, você deverá ver a transação “Car List” (Lista de carros). Clique no link “Car List” (Lista de carros) e você irá para a aba Transaction (Transação), que contém estatísticas para esta amostra de transações. Rolando a tela até a parte inferior da página, haverá uma visualização em cascata das interações do navegador como esta:

Surpreendeu-se com a quantidade de informações capturadas pelo agente de RUM por padrão? Preste atenção especialmente aos marcadores na parte superior como timeToFirstByte, domInteractive, domComplete e firstContentfulPaint. Passe o mouse sobre os pontos pretos para ver os nomes. Eles dão excelentes detalhes sobre recuperação de conteúdo e renderização de navegador desse conteúdo. Preste atenção também a todos os dados de desempenho sobre o carregamento de recursos do navegador. Com a simples inicialização do agente de RUM, sem qualquer instrumentação customizada, você obtém todas essas métricas de desempenho detalhadas, prontas para uso! Quando houver um problema de desempenho, essas métricas permitirão decidir facilmente se o problema é devido à lentidão nos serviços de backend, na rede ou simplesmente no navegador cliente. Isso é muito impressionante!

Para aqueles que precisam recapitular o conteúdo, a seguir está uma breve explicação das métricas de desempenho da web. É importante lembrar que, para frameworks de aplicações web modernas como o React, essas métricas podem representar somente a parte “estática” da página da web, devido à natureza assíncrona do React. Por exemplo, o conteúdo dinâmico ainda pode estar sendo carregado depois de domInteractive, como você verá mais adiante.

  • timeToFirstByte é a quantidade de tempo que um navegador espera para receber a primeira informação do servidor web depois de solicitá-la. Ele representa uma combinação da velocidade de processamento do lado do servidor e da rede.
  • domInteractive é o tempo imediatamente anterior ao momento em que o agente de usuário define como “interactive” (interativa) a prontidão do documento atual, o que significa que o navegador concluiu a análise de todo o HTML e a construção DOM foi finalizada.
  • domComplete é o tempo imediatamente anterior ao momento em que o agente de usuário define como “complete” (concluída) a prontidão do documento atual, o que significa que a página e todos os seus sub-recursos, como as imagens, tiveram o download concluído e estão prontos. O ícone giratório de carregamento parou de girar.
  • firstContentfulPaint é o tempo em que o navegador renderiza a primeira parte do conteúdo do DOM. Esse é um marco importante para os usuários porque fornece comentários de que a página realmente está sendo carregada.

Instrumentação customizada flexível

O agente de RUM fornece instrumentação detalhada e pronta para uso para a interação do navegador, como você acabou de ver. Você também poderá fazer instrumentações customizadas quando necessário. Por exemplo, como a aplicação React é uma aplicação de página única e a exclusão de um carro não disparará um carregamento de página, o RUM por padrão não captura os dados de desempenho da exclusão de um carro. Podemos usar transações customizadas para uma situação como essa.

Com nossa versão atual (Agente JavaScript 5.x do Real User Monitoring do APM), as chamadas AJAX e os eventos de clique são capturados pelo agente e enviados ao servidor do APM. A definição dos tipos de interações pode ser feita usando a configuração disableInstrumentation.

Também é possível adicionar suas próprias instrumentações customizadas para fornecer traces mais significativos. Isso pode ser particularmente útil para rastrear novos recursos. Na nossa aplicação de exemplo, o botão “New Car” (Novo carro) na nossa aplicação de frontend permite adicionar um novo carro ao banco de dados. Vamos instrumentar o código para capturar o desempenho de adição de um novo carro. Abra o arquivo Carlist.js no diretório de componentes. Você verá o código a seguir:

//Adicionar novo carro
addCar(car) {
// Adicionar metadados do carro como rótulos à transação de clique do RUM
var transaction = apm.startTransaction("Add Car", "Car");
transaction.addLabels(car);
fetch(SERVER_URL + 'api/cars',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(car)
})
.then(res => this.fetchCars())
.catch(err => console.error(err))
}
fetchCars = () => {
fetch(SERVER_URL + 'api/cars')
.then((response) => response.json())
.then((responseData) => {
this.setState({
cars: responseData._embedded.cars,
});
})
.catch(err => console.error(err));
// Encerrar a transação atual ao final da chamada de retorno de resposta
var transaction = apm.getCurrentTransaction()
if (transaction) transaction.end()
}

Basicamente, o código criou uma nova transação chamada “Add Car” (Adicionar carro) de tipo “Car” (Carro). Então, marcou a transação com o carro para fornecer informações contextuais. Em seguida, encerramos explicitamente a transação no final do método.

Adicione um novo carro da UI web da aplicação. Clique na UI do APM no Kibana. Você deverá ver uma transação “Add Car” (Adicionar carro) listada. Lembre-se de selecionar “Car” (Carro) no menu suspenso “Filter by type” (Filtrar por tipo). Por padrão, ele exibe transações “page-load” (carregamento de página).

Clique no link de transação “Add Car” (Adicionar carro). Você deverá ver informações de desempenho da transação customizada “Add Car” (Adicionar carro):

Clique na aba “Metadata” (Metadados). Você verá os rótulos que adicionamos junto com os rótulos padrão capturados pelo agente. Rótulos e logs adicionam informações contextuais valiosas aos seus traces do APM.

Isso realmente é tudo o que precisamos para fazer uma instrumentação customizada — um processo simples, mas poderoso! Para obter mais detalhes, consulte a documentação da API.

Dashboard de experiência do usuário

O Elastic APM oferece uma UI do APM selecionada e dashboards do APM integrados para visualizar todos os dados do APM capturados pelos agentes e prontos para uso.

Você também pode criar suas próprias visualizações customizadas no Elastic usando pipelines de nós de ingestão para enriquecer e transformar seus dados de APM. Por exemplo, os dados de agente de usuário e de IP de usuário capturados pelo agente RUM representam informações muito ricas sobre seus clientes. Com todas as informações de agente de usuário e de IP de usuário, é possível criar uma visualização como esta para mostrar a origem do tráfego da web em um mapa e quais sistemas operacionais e navegadores seus clientes estão usando.

No entanto, muitos dos dados do usuário de interesse podem estar presentes no dashboard User Experience (Experiência do usuário) visível no Elastic Observability. Amostras de visualizações são apresentadas abaixo:

Veja o panorama geral com tracing distribuído

Como bônus, também vamos instrumentar nossa aplicação Spring Boot de backend para que você tenha uma visão completa da transação, desde o navegador da web até o banco de dados de backend. O tracing distribuído do Elastic APM permite fazer isso.

Configuração do tracing distribuído nos agentes de RUM

O tracing distribuído é habilitado por padrão no agente de RUM. Entretanto, ele inclui somente solicitações feitas na mesma origem. Para incluir solicitações de origem cruzada, você precisa definir a opção de configuração distributedTracingOrigins. Você também terá de definir a política CORS na aplicação de backend, como falaremos na próxima seção.

Para nossa aplicação, o frontend é servido de http://localhost:3000. Para incluir solicitações feitas a http://localhost:8080, precisamos adicionar a configuração distributedTracingOrigins à nossa aplicação React. Isso é feito no arquivo rum.js. O código já está lá. Basta retornar ao código-fonte o comentário da linha.

var apm = initApm({
...
distributedTracingOrigins: ['http://localhost:8080']
})

Novas versões do agente implementam a especificação W3C Trace Context e o cabeçalho traceparent em solicitações feitas para http://localhost:8080. No entanto, observe que, anteriormente, isso era obtido adicionando o cabeçalho customizado elastic-apm-traceparent a essas solicitações.

De acordo com a documentação da versão mais recente, a instrumentação do lado do servidor pode ser configurada de três maneiras:

  1. Anexação automática à JVM em execução usando o apm-agent-attach-cli.jar
  2. Configuração programática usando o apm-agent-attach, que requer uma alteração de código na sua aplicação Java
  3. Configuração manual usando o flag -javaagent, como faremos no exemplo subsequente

Para usar a abordagem de instrumentação manual no lado do servidor, você precisa baixar o agente Java e iniciar sua aplicação com ele. Em seu IDE favorito, você precisará adicionar os vmArgs abaixo à configuração de inicialização.

-javaagent:apm/wrapper/elastic-apm-agent-1.33.0.jar 
-Delastic.apm.service_name=cardatabase
-Delastic.apm.application_packages=com.packt.cardatabase
-Delastic.apm.server_urls=
-Delastic.apm.secret_token=

Se você estiver usando o Elastic Cloud, a configuração completa para os agentes de RUM e APM pode ser encontrada na integração do APM de sua implantação, da qual vemos uma amostra abaixo.

O local onde os agentes serão configurados dependerá do IDE de sua escolha. A captura de tela abaixo é da minha configuração de inicialização do VSCode para a aplicação Spring Boot:

Agora, atualize a lista de carros do navegador para gerar outra solicitação. Vá até a UI do APM do Kibana e marque o último carregamento de página “car list” (lista de carros). Você deverá ver um trace completo, incluindo invocações de métodos Java, semelhante a esta captura de tela:

Como você pode ver, os dados de desempenho do lado do cliente provenientes do navegador e os dados de desempenho do lado do servidor, incluindo acesso JDBC, aparecem perfeitamente em um único trace distribuído. Observe diferentes cores para diferentes partes do trace distribuído. Lembre-se de que esse é o tracing padrão obtido, sem necessidade de fazer qualquer instrumentação customizada no lado do servidor, bastando apenas iniciar a aplicação com o agente. Sinta o poder do Elastic APM e do tracing distribuído!

Para leitores que realmente estejam prestando atenção à visualização de linha do tempo acima, você pode estar imaginando por que a transação de carregamento de página “Car List” (Lista de carros) se encerra em 193 ms, que é o tempo domInteractive, enquanto os dados ainda estão sendo servidos do backend. Essa é uma excelente pergunta! Isso se deve ao fato de que as chamadas de busca são assíncronas por padrão. O navegador “acha” que concluiu a análise de todo o HTML e a construção DOM em 193 ms porque carregou todo o conteúdo HTML “estático” proveniente do servidor web. Em contrapartida, o React ainda está carregando os dados do servidor de backend de forma assíncrona.

Compartilhamento de recursos de origem cruzada (CORS)

O agente de RUM é somente uma parte do quebra-cabeças em um trace distribuído. Para usar o tracing distribuído, precisamos configurar adequadamente outros componentes também. Um dos componentes que normalmente você precisará configurar é o compartilhamento de recursos de origem cruzada, vulgo CORS! Isso porque os serviços de frontend e backend geralmente são implantados em separado. Com a política same-origin, suas solicitações de frontend de uma origem diferente até o backend falharão sem o CORS devidamente configurado. Basicamente, o CORS é uma maneira de o lado do servidor verificar se as solicitações provenientes de uma origem diferente são permitidas. Para ler mais sobre as solicitações de origem cruzada e por que esse processo é necessário, veja a página da MDN sobre CORS (Compartilhamento de recursos de origem cruzada).

O que isso significa para nós? Duas coisas:

  1. Que devemos definir a opção de configuração distributedTracingOrigins, como fizemos.
  2. Que, com essa configuração, o agente de RUM também envia uma solicitação HTTP OPTIONS antes da solicitação HTTP real para verificar se todos os cabeçalhos e métodos HTTP têm suporte e se a origem é permitida. Especificamente, http://localhost:8080 receberá uma solicitação OPTIONS com os seguintes cabeçalhos:
    Access-Control-Request-Headers: traceparent, tracestate
    Access-Control-Request-Method: [request-method]
    Origin: [request-origin]
    E o servidor do APM deverá responder a ele com estes cabeçalhos e um código de status 200:
    Access-Control-Allow-Headers: traceparent, tracestate
    Access-Control-Allow-Methods: [allowed-methods]
    Access-Control-Allow-Origin: [request-origin]

A classe MyCorsConfiguration na nossa aplicação Spring Boot faz exatamente isso. Há diferentes maneiras de configurar o Spring Boot para fazer isso, mas aqui estamos usando uma abordagem baseada em filtros. Ou seja, configurar nossa aplicação Spring Boot do lado do servidor para permitir solicitações de qualquer origem com quaisquer cabeçalhos HTTP e quaisquer métodos HTTP. Talvez você não queira ter esse grau de abertura em suas aplicações de produção.

@Configuration
public class MyCorsConfiguration {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}

Resumo

Espero que este post tenha esclarecido que a instrumentação das aplicações com o Elastic RUM é simples e fácil, pórém extremamente poderosa. Junto com outros agentes de APM para serviços de backend, o RUM proporciona uma visão holística do desempenho das aplicações do ponto de vista do usuário final por meio do tracing distribuído.

Volto a lembrar que, para começar com o Elastic APM, você pode baixar o servidor do Elastic APM para executar localmente ou criar uma conta de avaliação do Elastic Cloud e ter um cluster pronto em alguns minutos.

Como sempre, acesse o fórum do Elastic APM se quiser abrir uma discussão ou tiver dúvidas. Deguste o RUM sem moderação!

Post publicado originalmente em 1º de abril de 2019 e atualizado em 20 de outubro de 2022.