eclero, coletando métricas
- 7 minutes read - 1386 wordsNesta série de posts sobre Observability vamos instrumentar e estruturar uma aplicação com o objetivo de estudar as estratégias escolhidas.
O que é Observability
Resumidamente, é coletar várias visões internas e externas de uma aplicação e correlacionar posteriormente em um sistema externo.
As aplicações e dependências precisam ser instrumentados para enviarem diferentes visões:
- métricas
- traces
- logs
- alertas
Observability para BEAM
Existem várias bibliotecas e iniciativas quando usamos Elixir/Erlang. Veja uma lista neste link: https://github.com/erlef/eef-observability-wg.
Também existe a dúvida de qual utilizar. De fato um dos WG (Working Groups) chamado Observability Working Group da ERLF (Erlang Ecosystem Foundation) é para ajudar a organizar as iniciativas.
Neste post vamos utilizar o projeto beam-telemetry
.
beam-telemetry
É um projeto no qual possui algumas bibliotecas para organizar e estruturar o envio de métricas.
A principal biblioteca é telemetry
no qual quando a aplicação precisa emitir
uma métrica, a função telemetry:execute/3
é chamada para realizar a operação.
Instrumentar uma aplicação para o envio de métricas deve ser uma tarefa de dois passos:
- Definir as métricas que são importantes
- Chamar a função telemetry:execute/3 nos pontos estratágicos
O propósito da biblioteca telemetry é implementar uma interface única para todas as aplicações que precisam enviar métricas.
Uma vez definida a interface, é necessário definir quais os tipos de métricas
queremos enviar. A biblioteca telemetry_metrics
implementa estas definições
nas quais podem ser:
- counter: número total da métrica
- last_value: último valor da métrica
- sum: matem a somatória da métrica
- summary: calcula estatísticas da métrica
- distribution: constroi um histograma de acordo coms os buckets configurados
Antes de enviar a métrica para algum outro sistema externo, a decisão do que fazer e como estruturar cada métrica é responsabilidade de um tipo de aplicação chamada reporter. beam-telemetry traz alguns reporters oficiais:
E outros criados pela comunidade
Mas qual é a função de um reporter? Preparar e enviar a métrica para outro sistema. Por exemplo, o reporter para prometheus TelemetryMetricsPrometheus.Core precisa preparar os scrapes para que o Prometheus colete adequadamente. Então este reporter precisa fazer agregações na aplicação para cada nova métrica emitida pelo telemetry.
Por outro lado, os reporters TelemetryMetricsStatsd, TelemetryMetricsRiemann apenas preparam e enviam a métrica no formato esperado pelos sistemas externos. Nenhuma operação de agregação é realizada na aplicação.
Para implementar algum outro reporter o projeto beam-telemetry definiou algumas guias gerais de como fazer aqui: https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#module-reporters
Definindo as métricas
Usando a API da biblioteca Telemetry.Metrics definimos as métricas no seguinte formato:
Telemetry.Metrics.counter("http.request.stop.duration")
Onde:
Telemetry.Metrics.counter
é o tipo da métrica"http.request.stop.duration"
é interpretado da seguinte forma:"http.request.stop"
: nome do evento (event name)"duration"
: medição (measurement)
A string "http.request.stop.duration"
pode ser representada como uma lista de
atoms também: [http, request, stop, duration]
.
Cada tipo de métrica pode receber diversos parâmetros. Recomendo usar como referência a documentação https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#functions
Na sessão abaixo vamos implementar as métricas mas antes precisamos definir o que queremos contar. No momento estou interessado em saber:
Descrição | event name + measurement |
---|---|
A quantidade de http requests no endpoint /check foram feitas | [http, request, check, done] |
A quantidade de nós online no cluster | [decision, server, nodes, up] |
A quantidade de nós offline no cluster | [decision, server, nodes, down] |
Exemplo: eclero com telemetry
Neste exemplo vamos intrumentar a aplicação eclero utilizando telemetry enviando as métricas para o reporter riemann.
O módulo eclero_metric.erl foi criado para conter toda a implementação das
métricas. Durante a inicialização da aplicação eclero, a função
eclero_metric:options()
é chamada retornando a configuração necessária para o
reporter TelemetryMetricsRiemann
funcionar.
1options() ->
2 Client = 'Elixir.TelemetryMetricsRiemann.Client.Katja',
3 Metrics = metrics(),
4
5 [{metrics, Metrics},
6 {prefix, eclero},
7 {client, Client}].
Já na função eclero_metric:metrics()
definimos todas as métricas que serão
inicializadas pelo telemetry
.
1metrics() ->
2 [
3 'Elixir.Telemetry.Metrics':last_value(
4 [decision, server, nodes, up],
5 [{description, <<"Number of nodes online">>}]),
6 'Elixir.Telemetry.Metrics':last_value(
7 [decision, server, nodes, down],
8 [{description, <<"Number of nodes offline">>}]),
9 'Elixir.Telemetry.Metrics':counter(
10 [http, request, check, done],
11 [{description, <<"Number of received check requests">>}])
12 ].
Também definimos funções nas quais preenchem com os argumentos corretos na API
do telemetry
.
1request_check() ->
2 telemetry:execute(
3 [http, request, check],
4 #{value => 1},
5 #{status_code => 200}).
6
7
8node(Up, Down) ->
9 telemetry:execute(
10 [decision, server, nodes],
11 #{up => Up, down => Down},
12 #{}).
O próximo passo foi selecionar os pontos que queremos coletar métricas:
- A quantidade de http requests no endpoint /check foram feitas:
https://github.com/joaohf/eclero/blob/master/apps/eclero/src/eclero_http.erl
1to_text(Req, State) -> 2 Body = to_text(eclero_health:get()), 3 4 eclero_metric:request_check(), 5 6 {Body, Req, State}.
- A quantidade de nós online e offline no cluster:
https://github.com/joaohf/eclero/blob/master/apps/eclero/src/eclero_decision_server.erl
1handle_cast({node_status, Node, down}, #state{nodes_up = Up, 2 nodes_down = Down, 3 nodes = Nodes} = State) -> 4 NDown = Down + 1, 5 NUp = decrease(Up), 6 7 eclero_metric:node(NUp, NDown), 8 9 {noreply, State#state{nodes_up = NUp, 10 nodes_down = NDown, 11 nodes = sets:del_element(Node, Nodes)}};
- A quantidade de nós offline no cluster:
https://github.com/joaohf/eclero/blob/master/apps/eclero/src/eclero_decision_server.erl
1handle_cast({node_status, Node, up}, #state{nodes_up = Up, 2 nodes_down = Down, 3 nodes = Nodes} = State) -> 4 NDown = decrease(Down), 5 NUp = Up + 1, 6 7 eclero_metric:node(NUp, NDown), 8 9 {noreply, State#state{nodes_up = NUp, 10 nodes_down = NDown, 11 nodes = sets:add_element(Node, Nodes)}}.
O último passo foi adicionar o processo do TelemetryReportRiemann na árvore de supervisão da aplicação:
https://github.com/joaohf/eclero/blob/master/apps/eclero/src/eclero_sup.erl 1init([]) ->
2 MetricArgs = eclero_metric:options(),
3
4 SupFlags = #{strategy => one_for_all,
5 intensity => 1,
6 period => 5},
7 Decision = #{id => eclero_decision_server,
8 start => {eclero_decision_server, start_link, []},
9 shutdown => 5000},
10 Detector = #{id => eclero_detector_server,
11 start => {eclero_detector_server, start_link, []},
12 shutdown => 5000},
13 Metric = 'Elixir.TelemetryMetricsRiemann':child_spec(MetricArgs),
14
15 ChildSpecs = [Metric, Decision, Detector],
16 {ok, {SupFlags, ChildSpecs}}.
riemann
Para verificar se as métricas estão sendo enviadas para o servidor, precisamos subir um servidor riemann local:
wget https://github.com/riemann/riemann/releases/download/0.3.5/riemann-0.3.5.tar.bz2
tar jxf riemann-0.3.5.tar.bz2
cd rieamnn-0.3.5
bin/riemann start
INFO [2020-01-03 18:11:57,259] main - riemann.bin - Loading /home/joaohf/tmp/riemann-0.3.5/etc/riemann.config
INFO [2020-01-03 18:11:57,295] main - riemann.bin - PID 14129
INFO [2020-01-03 18:11:57,443] clojure-agent-send-off-pool-5 - riemann.transport.websockets - Websockets server 127.0.0.1 5556 online
INFO [2020-01-03 18:11:57,507] clojure-agent-send-off-pool-2 - riemann.transport.udp - UDP server 127.0.0.1 5555 16384 -1 online
INFO [2020-01-03 18:11:57,528] clojure-agent-send-off-pool-0 - riemann.transport.tcp - TCP server 127.0.0.1 5555 online
INFO [2020-01-03 18:11:57,530] main - riemann.core - Hyperspace core online
Com o servidor executando em um shell, vamos inspecionar os pacotes enviados. Pois no momento não estamos intressados em ver as métricas do lado do riemann server.
Verificando as métricas
Utilizando a ferramenta Wireshark, podemos inspecionar os pacotes enviados usando o protocolo riemann.
- Evento quando enviamos um contador da request check;
Frame 1: 151 bytes on wire (1208 bits), 151 bytes captured (1208 bits) on interface 0
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
User Datagram Protocol, Src Port: 43788, Dst Port: 5555
Riemann
event
time: 1578073675
service: eclero.http.request.check.done
host: porco
description: Number of received check requests
attribute
key: status_code
value: 200
metric_sint64: 1
metric_f: 1
- Para os eventos de node up e node down, temos:
Riemann
event
time: 1578073822
service: eclero.decision.server.nodes.up
host: porco
description: Number of nodes online
metric_sint64: 1
metric_f: 1
event
time: 1578073822
service: eclero.decision.server.nodes.down
host: porco
description: Number of nodes offline
metric_sint64: 0
metric_f: 0
Erlang com Elixir, gestão de dependências
eclero utiliza rebar3 para gestão de dependências. Entretanto telemetry_metric_riemann foi implementado em Elixir, usando mix como gestão de dependência. Como foi possível usar a biblioteca?
Utilizando um plugin chamado rebar_mix foi possivel utilizar os pacotes Elixir com rebar3.
O interessante desda abordagem é a possibilidade de utilizar várias bibliotecas desenvolvidas em Elixir a partir do Erlang. Afinal, tudo é BEAM.
Para mais detalhes vale a pena consultar três referências:
- rebar3 plugin: Elixir Dependencies
- rebar_mix
- Adopting Erlang: Dependencies na sessão Using Elixir Dependencies
Conclusão
Instrumentar uma aplicação é simples e o tempo deve ser gasto na definição das métricas. Respondendo se determinada métrica faz sentido e ajuda na identificação de problemas relacionados as regras de negócio. Todas as métricas que ajudem durante uma análise da saúde da aplicação fazem sentido de serem implementadas.