Monitoramento APM customizado pra Protheus

Como instrumentar Protheus pra APM (DataDog, NewRelic, Grafana). Traces, metricas, dashboards. Identificar gargalos antes do usuario reclamar.

APM (Application Performance Monitoring) e o "raio-X" da aplicacao em producao. Protheus nao tem APM nativo, mas voce constroi um com FwLogger + tabela Z + integracao com Prometheus/DataDog.

O que monitorar

MetricaPor que
Tempo de resposta de rotinaIdentificar lentidao
Throughput (op/min)Capacidade do sistema
Taxa de erroSaude funcional
Uso de licencasSaturation
Sessoes ativasConcorrencia
Locks no bancoContensao
Tempo de query SQLGargalo de banco

Instrumentar rotinas — wrapper de timing

function Instrumentar(cRotina, bAcao)
    Local nIni := Seconds()
    Local lOk  := .T.
    Local cErro := ""

    try
        Eval(bAcao)
    catch e
        lOk := .F.
        cErro := e:getDescription()
    endtry

    Local nDur := (Seconds() - nIni) * 1000  // ms

    // Loga
    FwLogger():Info("rotina-executada", {;
        "rotina":  cRotina,    ;
        "ms":      nDur,       ;
        "ok":      lOk,        ;
        "user":    RetCodUsr(),;
        "filial":  cFilAnt,    ;
        "erro":    cErro       ;
    })

    // Persiste em tabela Z pra agregacao
    RecLock("ZMET", .T.)
    ZMET->ZMET_DATA   := dDataBase
    ZMET->ZMET_HORA   := Time()
    ZMET->ZMET_ROTINA := cRotina
    ZMET->ZMET_MS     := nDur
    ZMET->ZMET_OK     := lOk
    ZMET->(MsUnlock())
return lOk

Uso

// Em vez de chamar direto
U_GeraNF()

// Instrumente
Instrumentar("GeraNF", {|| U_GeraNF()})

Exportar pra Prometheus (pull-based)

@Get("/metrics")
function MetricasProm()
    Local cOut := ""

    // Counters acumulados
    cOut += "# HELP protheus_rotinas_total Numero total de execucoes" + CRLF
    cOut += "# TYPE protheus_rotinas_total counter" + CRLF

    Local cQry := "SELECT ZMET_ROTINA, COUNT(*) AS N " + ;
                  " FROM " + RetSqlName("ZMET") + ;
                  " WHERE ZMET_DATA = '" + DToS(dDataBase) + "'" + ;
                  " GROUP BY ZMET_ROTINA"
    TCQuery cQry New Alias "M"

    While !M->(Eof())
        cOut += 'protheus_rotinas_total{rotina="' + AllTrim(M->ZMET_ROTINA) + '"} ' + ;
                cValToChar(M->N) + CRLF
        M->(DBSkip())
    EndDo
    M->(DBCloseArea())

    // Histograma de duracao
    cOut += "# HELP protheus_rotina_duration_seconds Duracao de rotinas" + CRLF
    cOut += "# TYPE protheus_rotina_duration_seconds histogram" + CRLF
    // ... gerar buckets

    oResponse:setContentType("text/plain")
    oResponse:setBody(cOut)
return

Prometheus scrape config

# /etc/prometheus/prometheus.yml
scrape_configs:
  - job_name: 'protheus'
    scrape_interval: 30s
    metrics_path: /metrics
    static_configs:
      - targets: ['protheus.empresa.com.br:8080']

Dashboard Grafana

Importe template "Protheus Custom" (criado por voce) com paineis:

Alertas no AlertManager

groups:
  - name: protheus
    rules:
      - alert: ProtheusLento
        expr: avg(protheus_rotina_duration_seconds) > 5
        for: 5m
        annotations:
          summary: Rotina demorando >5s em media

      - alert: ProtheusTaxaErroAlta
        expr: rate(protheus_erros_total[5m]) > 0.1
        for: 2m
        annotations:
          summary: Mais de 10% de erro nos ultimos 5min

Integracoes prontas

ToolPra que
Prometheus + GrafanaOpen source, mais comum
DataDogSaaS robusto, mais caro
NewRelicSaaS, foco em traces
Elastic APMIntegracao natural com Kibana
DynatraceEnterprise, auto-discovery

Pegadinhas

Veja também