dicembre 7, 2023 · sap abap prometheus grafana dashboard

Una dashboard coloratissima per SAP

In questi giorni ho fatto un paio di esperimenti per tentare di risolvere nel modo meno faticoso l'annoso problema "Come faccio a sapere se la mia installazione SAP sta esplodendo o se le interfacce hanno smesso di funzionare, possibilmente senza spendere un milione di euro per un sistema di monitoraggio fatto apposta?".

Fortunatamente ci sono in giro un sacco di soluzioni di monitoraggio liberamente accessibili e configurabili con poco sforzo, anche se non pensate esattamente per SAP, per cui ho voluto fare un esperimento. In questo post ho realizzato una bellissima dashboard per monitorare in un rapido colpo d'occhio alcuni parametri indicativi del funzionamento del sistema.

L'esperimento coinvolge Prometheus, uno scraper in grado autonomamente di prelevare metriche da sistemi esterni mediante chiamate http e registrarle in un archivio time-based; prevede inoltre la possibilità di eseguire query complesse su questi dati archiviati. L'esperimento coinvolge anche Grafana, un tool per la creazione di dashboard bellissime che si interfaccia direttamente con Prometheus. Più un paio di magheggi direttamente in SAP.

Esposizione delle metriche

Il primo passo da fare è esporre le metriche che ci interessano in un servizio http su SAP, dal quale poi Prometheus potrà andare a leggere (nota: c'è bisogno che Prometheus possa comunicare con SAP, quindi preparate una scatola di cioccolatini per il vostro IT di fiducia). Il modo più semplice per realizzare questo risultato è creare un nuovo servizio nella transazione SICF, esponendo un endpoint collegato a una classe ABAP che si occupa di creare l'output al momento dell'interrogazione.

Le metriche da esporre per Prometheus sono costituite da un semplice payload testuale, in cui ciascuna riga contiene il nome della metrica, un elenco facoltativo di label da associare alla misurazione, e il valore stesso. Supponendo di volere esporre una metrica con il conteggio dell'elenco completo degli IDOC presenti nel sistema divisi per tipo, stato e direzione, il mio output avrà una struttura del genere:

# TYPE total_idocs gauge
total_idocs { mestyp="CREMAS" status="03" direct="1" } 3
total_idocs { mestyp="CREMAS" status="26" direct="1" } 2
total_idocs { mestyp="DEBMAS" status="03" direct="1" } 12
total_idocs { mestyp="DEBMAS" status="09" direct="1" } 4
total_idocs { mestyp="DEBMAS" status="10" direct="1" } 1
total_idocs { mestyp="DEBMAS" status="29" direct="1" } 2
total_idocs { mestyp="DEBMAS" status="37" direct="1" } 2
total_idocs { mestyp="DESADV" status="26" direct="1" } 2
total_idocs { mestyp="DESADV" status="30" direct="1" } 13
total_idocs { mestyp="LOIPRO" status="03" direct="1" } 22
total_idocs { mestyp="LOIWCS" status="03" direct="1" } 8
total_idocs { mestyp="LOIWCS" status="29" direct="1" } 1
total_idocs { mestyp="MATMAS" status="03" direct="1" } 9
total_idocs { mestyp="MATMAS" status="26" direct="1" } 16
...

La prima riga, che inizia con #, è un commento per Prometheus che definisce il tipo di metrica (in questo caso "gauge"); esistono 4 tipi di metrica utilizzabili, questo è il più adatto a questo utilizzo. Ulteriori informazioni qui.

La classe indicata nel servizio SICF effettua una query sulla tabella che contiene le testate degli IDOC e produce un output conforme. Non prendete come esempio il codice qui sotto, ha bisogno di un pò di pulizia e ottimizzazione prima di andare in produzione :-)

CLASS zmp_idoc_monitor DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .
  PUBLIC SECTION.
    INTERFACES if_http_extension.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS ZMP_IDOC_MONITOR IMPLEMENTATION.

  METHOD if_http_extension~handle_request.

    server->response->set_status(
      code   = 200
      reason = 'ok'
    ).

    DATA: output TYPE string.

    " ----- IDOC per tipo/stato -------------------------------------

    TYPES: BEGIN OF ty_idoc_group,
             mestyp TYPE edidc-mestyp,
             status TYPE edidc-status,
             direct TYPE edidc-direct,
             count  TYPE i,
           END OF ty_idoc_group.
    TYPES: tyt_idoc_group TYPE TABLE OF ty_idoc_group.
    DATA: lt_idoc_group TYPE tyt_idoc_group.

    SELECT mestyp, status, direct, COUNT(*) AS count FROM edidc
    GROUP BY mestyp, status, direct
    INTO TABLE @lt_idoc_group.

    output = output && '# TYPE total_idocs gauge' && cl_abap_char_utilities=>newline.
    
    loop at lt_idoc_group into data(lv_idoc_group).
        output = output && |total_idocs \{ mestyp="{ lv_idoc_group-mestyp }" status="{ lv_idoc_group-status }" direct="{ lv_idoc_group-direct }" \} { lv_idoc_group-count }| && cl_abap_char_utilities=>newline.
    ENDLOOP.

    " ---------------------------------------------------------------

    server->response->set_cdata( output ).

    server->response->set_header_field(
      name  = 'Content-Type'
      value = 'text/plain'
    ).

  ENDMETHOD.
ENDCLASS.

A questo punto possiamo provare a effettuare una richiesta direttamente dal browser, inserendo l'indirizzo completo dell'endpoint, e vedremo il risultato.

Installazione Prometheus e Grafana

L'installazione è sempre semplice quando si usa Docker, per l'esattezza Docker Compose. Come unico accorgimento prima di procedere è necessario creare due cartelle, una per la configurazione di prometheus e una per i dati di grafana (più lo storage per prometheus, ma al momento non mi interessa conservare i dati tra un run e l'altro). Nella cartella dedicata a prometheus dobbiamo creare un file prometheus.yml con la configurazione, in cu inseriamo il puntamento al nostro server SAP con le relative credenziali utente.

global:
  scrape_interval:     15s

  external_labels:
    monitor: 'codelab-monitor'

scrape_configs:

  - job_name: my_idocs

    metrics_path: '/ztestidoc'

    basic_auth:
      username: 'my_sap_username'
      password: 'my_sap_pwd'

    static_configs:
      - targets: ['my_sap_url:port']

Si noti che è stata configurata una frequenza di scrape di 15 secondi, per questa applicazione probabilmente è fin troppo frequente (e occhio se il codice di estrazione diventa troppo pesante, meglio fare delle prove). Nel mio caso l'estrazione dal db dura complessivamente una decina di millisecondi quindi direi che problemi per ora non ce ne sono :-) .

A questo punto si può predisporre il file docker-compose.yml per l'avvio dei servizi. In questo caso ho deciso di esporre le porte di prometheus e grafana rispettivamente sulle porte 9010 e 9011 dell'host.

version: '3.3'

services:

   prometheus:
     image: prom/prometheus
     ports:
       - 9010:9090
     volumes:
       - my_prometheus_config:/etc/prometheus
     restart: always

   grafana:
     image: grafana/grafana-oss
     ports:
       - 9011:3000
     volumes:
       - my_grafana_storage:/var/lib/grafana
     restart: always
     # id -u
     user: "1001"

Nota: per qualche motivo grafana si arrabbia per problemi di permessi nella creazione delle sottocartelle sull'host, quindi è necessario indicare esplicitamente l'utente con cui eseguirlo (lo stesso con cui sono state create le cartelle). Per ottenere l'id dell'utente corrente è sufficiente usare il comando "id -u" da riga di comando (sotto linux).

Ora possiamo avviare entrambi i servizi con il solito comando:

docker-compose up -d

Configurazione Prometheus

..che poi non c'è nulla da configurare, abbiamo già fatto tutto nel file di configurazione.

Possiamo accedere all'interfaccia web di Prometheus alla porta 9010, dovrebbe essere già tutto configurato correttamente e nella schermata "Status->Targets" dovremmo vedere il nostro servizio up.

Nella schermata "Graph" possiamo effettuare una semplice query inserendo il nome del nostro datapoint (total_idocs), per vedere gli ultimi valori letti.

Configurazione Grafana

Per quanto riguarda Grafana invece non abbiamo ancora configurato nulla. Si poteva passare all'avvio un file di configurazione già fatto, ma dall'interfaccia si fa tutto in 10 secondi.

Si accede all'interfaccia usando la porta esposta da docker (nel nostro caso 9011). Le credenziali di default dell'amministratore sono admin - admin (viene chiesto di cambiare la password al primo accesso).

Da qui possiamo andare su "Connections -> Add new connection" e cercare il plugin per Prometheus.

Per il nostro setup è sufficiente compilare l'indirizzo della macchina. Ricordando che la macchina si trova già nella rete creata da docker-compose, possiamo utilizzare il nome del container e la porta "vera" (NON quella esposta sull'host).

Non sono necessari altri parametri, basta salvare e la connessione è già disponibile. A questo punto c'è "solo" da creare le dashboard per esporre i dati, e qui non c'è modo migliore di andare a tentativi come sto facendo io da qualche tempo :-)

Una volta che si è presa la mano, i risultati sono spettacolari.

Eccetera...

Alla prossima!