Metrics 📏
A especificação Metrics do Microprofile fornece uma maneira de construir e expor métricas do seu serviço para ferramentas de monitoramento, como por exemplo, o Prometheus/OpenMetrics.
Para utiilzar o MicroProfile Metrics em um aplicativo Quarkus, você precisa importar a extensão quarkus-smallrye-metrics
(SmallRye Metrics). Com a extensão quarkus-smallrye-metrics
adicionada no projeto, o Quarkus fornece um endpoint padrão (ex.: http://localhost:8080/q/metrics) no formato Prometheus. Entretanto, o formato pode ser alterado para JSON trocando-se o cabeçalho da requisição HTTP Accept para application/json
.
Se você olhar para a saída do endpoint padrão (http://localhost:8080/q/metrics), verás diversos parâmetros prefixados:
- base (
q/metrics/base
) - informações essenciais do servidor, como por exemplo, o número de classes carregadas por uma máquina virtual, ou o número de processos rodando na máquina virtual, etc. - vendor (
q/metrics/vendor
) - informações especificas sobre um fornecedor, como por exemplo, o tempo de CPU utilizado por cada processo, ou o uso recente de CPU por todo os sistema, etc. - application (
q/metrics/application
) - informações personalizadas desenvolvidas por meio do mecanismo de extensão do MicroProfile Metrics.
Como pode ser observado, em um serviço existem muitos valores possíveis para serem monitorados, os mais comuns são:
- Tempo de CPU
- Memória ocupada
- Espaço em disco
- Desempenho de métodos críticos
- Métricas de negócios (por exemplo, o número de pagamentos por segundo)
- Quesões de Rede
- Recursos ocupados pela JVM
- Saúde geral do seu cluster
O MicroProfile Metrics possui um conjunto de anotações que podem serem usadas para criamos métricas espefíficas (q/metrics/application
) para o serviço, são elas:
- @Counted - Conta o número de invocações de um método
- @Timed - Monitora a duração de uma invocação
- @SimplyTimed - Monitora a duração das invocações sem considerar cálculos como média e distribuição, ou seja, trata-se de uma versão simplificada do @Timed.
- @Metered - Monitora a frequência de invocações
- @Gauge - Expõe o valor de retorno do método anotado como uma métrica
- @ConcurrenceGauge - Conta as invocações paralelas
Como implementar?
Vamos implementar um exemplo de métricas em um micro serviço escrito com o Quarkus.
Primeiro, abra um terminal e crie um projeto com o seguinte comando:
mvn io.quarkus:quarkus-maven-plugin:2.1.3.Final:create \
-DprojectGroupId=dev.pw2 \
-DprojectArtifactId=metrics \
-DclassName="dev.pw2.Checker" \
-Dpath="/" \
-Dextensions="resteasy,smallrye-metrics"
code metrics
Como ilustração iremos utilizar um serviço que indica se um número é par ou ímpar como exemplo. Observe o trecho de código abaixo:
@Path("/")
public class Checker {
// Injeta um objeto da classe Histogram
@Inject
@Metric(name = "histogram", absolute = true)
Histogram histogram;
// Armazena o maior número par encontrado
private long highestEven = 1;
@GET
@Path("/{number}")
@Produces(MediaType.TEXT_PLAIN)
@Counted(name = "counter", description = "The number of execution of the check method")
@Timed(name = "timer", description = "A measure of how long it takes to execute the check method", unit = MetricUnits.MICROSECONDS)
public String check(@PathParam("number") long number) {
// Histogram
this.histogram.update(number);
if (number < 1) {
return "The number must be > 1";
}
if (number % 2 == 1) {
return number + ": Odd";
}
if (number > highestEven) {
this.highestEven = number;
}
return number + ": Even";
}
// Retorna o maior número par encontrado como uma métrica
@Gauge(name = "highestEven", unit = MetricUnits.NONE, description = "Highest even so far")
public Long highestEven() {
return this.highestEven;
}
}
Como pode ser observado no código, a classe acima implementa uma métrica chamada counter
que é incrementada cada vez que o método check
é executado. Além disso, a métrica timer
registra o tempo em micro segundos gasto na execução do método check
. Finalmente a métrica highestEven
informa o maior número par que foi encontrado pelo serviço. Para visualizar as métricas, é necessário executar o método check e depois conferir os resultados no end-point /q/metrics/application
Histograma
Existe também uma métrica, que não possui uma anotação, chamada de Histograma. Um histograma armazena dados ao longo do tempo e, com isso, gera valores como mínimo, máximo, média, desvio padrão, entre outros. No exemplo anterior, declaramos um histograma chamado “histogram” da seguinte maneira:
@Inject
@Metric(name = "histogram", absolute = true)
Histogram histogram;
Por sua vez, no método check
utilizamos o código this.histogram.update(number);
para armazenar os valores e guardar o histórico de dados do método.
Consultas 🔎
Você pode obter informações de qualquer métrica consultando um endpoint específico usando o método OPTION do HTTP. Os metadados são expostos por padrão em q/metrics/escope/metric-name
, onde o escope
pode ser: base, vendor ou application e metric-name é o nome propriamente dito da métrica (no caso de um aplicativo, aquele definido no atributo name).
Prometheus
Para rodar o Prometheus, utilize, por exemplo, o arquivo docker-compose.yml
`abaixo:
version: "3.9"
volumes:
metrics:
services:
prometheus:
image: bitnami/prometheus:latest
container_name: prometheus
ports:
- 9090:9090
volumes:
- metrics:/etc/prometheus
- ./prometheus.yml:/etc/prometheus/prometheus.yml
A última linha do arquivo docker-compose.yml
mostra que o prometheus necessita de um arquivo de configuração chamado prometheus.yml
. Um exemplo de arquivo prometheus.yml
pode ser observado abaixo:
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
metrics_path: "/q/metrics"
# No Windows utilize `host.docker.internal` ao invés de `docker.for.mac`
# para acessar um serviço externo ao container
# https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach
static_configs:
- targets: ["docker.for.mac.localhost:8080"]
Uma vez que o Prometheus esteja rodando, ele irá realizar a leitura dos endpoints da aplicação, por meio da sua configuração de “targets”.
Código 💡
um código de exemplo sobre esse tópico está disponível no Github:
git clone -b dev https://github.com/rodrigoprestesmachado/pw2
code pw2/exemplos/metrics
Referências 📚
-
Alex Soto Bueno; Jason Porter; Quarkus Cookbook: Kubernetes-Optimized Java Solutions. Editora: O’Reilly Media, 2020.
-
SmallReye Metrics. Disponível em: https://quarkus.io/guides/smallrye-metrics
CC BY 4.0 DEED