Monitorer son infra avec Warp 10 – partie 3

Monitoring de raspberry, la suite.

Maintenant que l’on a instrumenté et analysé son infra, il est temps d’afficher tout ça dans un dashboard.

Il y a plusieurs façon d’e faire de la dataviz avec Warp 10.

  • Coder sois-même son interface
  • Utiliser un outil tout fait comme Grafana
  • Utiliser WarpView pour se construire son dashboard
  • Utiliser Discovery pour un dashboard as code

Nous utiliserons ici Discovery. Un dashboard as code a de multiples intérêts :

  • versionner son dashboard
  • le rendre dynamique en fonction de certains paramètres
    • afficher certaines info quand on dépasse un seuil par exemple
    • passer un graphique en rouge en fonction d’un KPI
  • le réutiliser simplement pour d’autres usages
  • le modulariser

Mode d'emploi

Discovery est très simple à prendre en main. Il s’agit de passer une structure de donnée (que je vais détailler) en WarpScript entre 2 balises HTML.

<html>

<head>
  <title>Raspi-1 dashboard</title>
</head>

<body>
  <discovery-dashboard url="https://your warp 10/api/v0/exec">
// Le WarpScript va ici
  </discovery-dashboard>
<!-- Les imports -->
  <script nomodule src="https://unpkg.com/@senx/discovery-widgets/dist/discovery/discovery.js"></script>
  <script type="module" src="https://unpkg.com/@senx/discovery-widgets/dist/discovery/discovery.esm.js"></script>

</body>

</html>

Le WarpScript doit produire la structure suivante :

  • // dashboard
  • title : Titre du dashboard (optionnel)
  • description : Description du dashboard (optionnel)
  • options : les options communes pour le dashboard (ce sont les mêmes que pour WarpView et ici aussi) (optionnel)
    • autoRefresh : période en seconde entre 2 refresh des données, -1 pour désactiver (par défaut) (optionnel)
  • tiles : Liste des « tuiles » à afficher
    • // tuile
    • title : Titre de la tuile (optionnel)
    • x, y : position x et y sur la grille (commencent à 0 et la grille fait 12 cases de large)
    • w, h : hauteur et largeur en nombre de cases sur la grille (la largeur max est de 12)
    • type : type de graphique (‘line’, ‘area’, ‘scatter’, ‘spline-area’, ‘spline’, ‘step’, ‘step-after’, ‘step-before’, ‘annotation’, ‘bar’, ‘display’, ‘image’, ‘map’, ‘gauge’, ‘circle’, ‘pie’, ‘plot’, ‘doughnut’, ‘rose’, ‘tabular’, ‘button’)
    • unit : unité à afficher (optionnel)
    • options : les options communes pour le dashboard (ce sont les mêmes que pour WarpView et ici aussi) (optionnel)
      • autoRefresh : période en seconde entre 2 refresh des données, -1 pour désactiver (par défaut) (optionnel et ne concerne que ‘macro’)
    • data / macro
      • data : le code sera exécuté au moment de l’exécution de la structure du dashboard
      • macro : le code sera exécuté par la tuile une fois affichée

Bon, un exemple sera plus parlant :

{
  'title' 'Test'
  'options' { 'autoRefresh' 10 }  // l'ensemble du dashboard se rafraîchira toutes les 10 secondes
  'tiles' [
    {
      'title' 'Utilisation de "macro"' 
      'x' 0 'y' 0 'w' 6 'h' 1
      'options' { 'autoRefresh' 2 } // cette tuile se rafraîchira toutes les 2 secondes
      'type' 'display'
      'macro' <% // C'est une macro WarpScript
        RAND 100.0 * ROUND
      %>
    }
    {
      'title' 'Utilisation de "data"' 
      'x' 6 'y' 0 'w' 6 'h' 1
      'type' 'display'
      'data' RAND 100.0 * ROUND
    }
  ]
}
Exemple du dynamisme du dashboard
regardez, ça bouge oO

Notre dashboard

En utilisant ‘data’ on peut calculer au moment de la génération (ou du refresh) du dashboard, avec ‘macro’ c’est en ajax depuis la tuile.

Bon, cool, passons à nos métriques réalisés la dernière fois. On va utiliser ‘data’ car, ça nous permet d’utiliser des variables (genre, le token ou le host name) et on va rafraîchir le dashboard périodiquement.

Commençons par les variables :

// Variables
'your read token' 'token' STORE
'2592000000000' 'duration' STORE  // 1 mois
'rasp1-1' 'hname' STORE

Puis le dashboad (sans les tuiles) :

{
  'title' 'Raspi-1'
  'options' { 'autoRefresh' 30 } // auto refresh de 30 secondes
  'tiles' [
    // On va ajouter les tuiles là !
  ]
}

Tuile d’affichage de l’espace disque :

{
  'title' 'Disks'
  'x' 0 'y' 0 'w' 3 'h' 1 'type' 'gauge' 'unit' 'Gb'
  'data' [ $token '~linux.df.bytes.(capacity|free)' { 'hname' $hname 'device' '~/dev/.*' } NOW -1 ] FETCH  'gts' STORE
    [ $gts bucketizer.last NOW 0 1 ] BUCKETIZE [ 'device' 'mountpoint' ] PARTITION  'gts' STORE
    [] 'data' STORE
    [] 'params' STORE
    $gts KEYLIST <%
      'k' STORE
      {
        $gts $k GET
        <% 'g' STORE
          $g NAME 'linux.df.bytes.' '' REPLACE
          $g VALUES REVERSE 0 GET
        %> FOREACH
      } 'vals' STORE
      $data {
        'key'  $k 'device' GET ' (' + $k 'mountpoint' GET + ')' +
        'value'  $vals 'capacity' GET $vals 'free' GET - 1024 / 1024 / 1024.0 / 100 * ROUND 100.0 /
      } +! DROP
      $params { 'maxValue' $vals 'capacity' GET  1024 / 1024 / 1024 /  } +! DROP
  %> ASREGS FOREACH
  { 'data' $data 'params' $params }
 }

La tuile pour le réseau :

{
  'title' 'Network'
  'x' 0 'y' 1 'w' 3 'h' 1 'type' 'area'
  'data' [ $token '~linux.proc.net.dev.(receive|transmit).bytes' { 'hname' $hname 'iface' 'eth0' } NOW $duration TOLONG ] FETCH
    false RESETS 'gts' STORE
    [
      [ $gts [] 'linux.proc.net.dev.receive.bytes' filter.byclass ] FILTER
      [ $gts [] 'linux.proc.net.dev.transmit.bytes' filter.byclass ] FILTER
    ] FLATTEN [ SWAP bucketizer.mean NOW 1 h 0 ] BUCKETIZE  'gts' STORE
    [ $gts mapper.delta 1 0 0 ] MAP
}

La tuile pour la charge CPU :

{
  'title' 'Load'
  'x' 3 'y' 1 'w' 9 'h' 1 'type' 'area'
  'data' <%
    [ $token '~linux.proc.stat.userhz.(user|nice|system|idle|iowait)' { 'hname' $hname 'cpu' 'cpu' } NOW $duration TOLONG ] FETCH 'gts' STORE
    [ $gts bucketizer.mean NOW 1 h 0 ] BUCKETIZE 'gts' STORE
    [ $gts [] '~linux.proc.stat.userhz.(user|nice|system|idle|iowait)' filter.byclass ] FILTER [ SWAP [] reducer.sum ] REDUCE 0 GET 'cpuGTS' STORE
    [ $gts [] 'linux.proc.stat.userhz.idle' filter.byclass ] FILTER 0 GET 'idleGTS' STORE
    [ $gts [] '~linux.proc.stat.userhz.(iowait|idle)' filter.byclass ] FILTER [ SWAP [] reducer.sum ] REDUCE 0 GET 'iowaitGTS' STORE
    [ $gts [] '~linux.proc.stat.userhz.(system|idle)' filter.byclass ] FILTER [ SWAP [] reducer.sum ] REDUCE 0 GET 'systemGTS' STORE
    [
      [ [ $cpuGTS $idleGTS ] [] <%
       'd' STORE
       $d 7 GET 0 GET 'cpu' STORE
       $d 7 GET 1 GET 'idle' STORE
        1000.0 $cpu $idle - * $cpu 5  + / 10 / 'v' STORE
        [ $d 0 GET NaN NaN NaN $v ]
      %> ASREGS MACROREDUCER ] REDUCE 0 GET 'CPU' RENAME
    [ [ $iowaitGTS $idleGTS ] [] <%
      'd' STORE
      $d 7 GET 0 GET 'cpu' STORE
      $d 7 GET 1 GET 'idle' STORE
      1000.0 $cpu $idle - * $cpu 5  + / 10 / 'v' STORE
      [ $d 0 GET NaN NaN NaN $v ]
   %> ASREGS MACROREDUCER ] REDUCE 0 GET 'Disk IO' RENAME
   [ [ $systemGTS $idleGTS ] [] <%
    'd' STORE
     $d 7 GET 0 GET 'cpu' STORE
     $d 7 GET 1 GET 'idle' STORE
     1000.0 $cpu $idle - * $cpu 5  + / 10 / 'v' STORE
     [ $d 0 GET NaN NaN NaN $v ]
   %> ASREGS MACROREDUCER ] REDUCE 0 GET 'System' RENAME
  ] %>  <% [] %> <% %> TRY
 }

Et enfin, la tuile pour la RAM/Swap :

{
  'title' 'RAM/Swap'
  'x' 3 'y' 0 'w' 9 'h' 1 'type' 'area'
  'data' [ $token '~linux.proc.meminfo.(MemFree|SwapFree)' { 'hname' $hname } NOW  $duration TOLONG ] FETCH 'gts' STORE
  [ $gts bucketizer.mean NOW 1 h 0 ] BUCKETIZE 'gts' STORE
  [ $gts 1.0 1024.0 1024.0 * 1024.0 * / mapper.mul 0 0 0 ] MAP
}

Vous trouverez l’ensemble dans ce Gist. Et voici le résultat :

Mon premier dashboard
c’est beau

Pimp my dashboard

Bon, les goûts et les couleurs… On va skiner un peut tout ça.

D’abord, on peut utiliser un des thèmes de couleurs proposé par la lib, il y en a quelques uns. Choisissons ‘CHARTANA’ au pif.

{
  'title' 'Raspi-1'
  'options' { 'scheme' 'CHARTANA' 'autoRefresh' 30 }
  'tiles' [ ... ]
}

Ensuite, on peut utiliser quelques variables CSS :

<head>
  <title>Raspi-1 dashboard</title>
  <style>

* {
    box-sizing: border-box;
}

:root {
    --wc-split-gutter-color            : #404040;
    --warp-view-pagination-bg-color    : #343a40 !important;
    --warp-view-pagination-border-color: #6c757d;
    --warp-view-datagrid-odd-bg-color  : rgba(255, 255, 255, .05);
    --warp-view-datagrid-odd-color     : #FFFFFF;
    --warp-view-datagrid-even-bg-color : #212529;
    --warp-view-datagrid-even-color    : #FFFFFF;
    --warp-view-font-color             : #FFFFFF;
    --warp-view-chart-label-color      : #FFFFFF;
    --gts-stack-font-color             : #FFFFFF;
    --warp-view-resize-handle-color    : #111111;
    --warp-view-chart-legend-bg        : #000;
    --gts-labelvalue-font-color        : #ccc;
    --gts-separator-font-color         : #FFFFFF;
    --gts-labelname-font-color         : rgb(105, 223, 184);
    --gts-classname-font-color         : rgb(126, 189, 245);
    --warp-view-chart-legend-color     : #FFFFFF;
    --wc-tab-header-color              : #FFFFFF;
    --wc-tab-header-selected-color     : #404040;
    --warp-view-tile-background        : #3A3C46;
}

body {
    font-size       : 12px;
    line-height     : 1.52;
    background-color: #333540;
    color           : #FFFFFF;
    padding         : 1rem;
    height          : calc(100vh - 2rem);
}

discovery-dashboard {
    color: transparent;
}

  </style>
</head>

Et voilà :

Résultat dark mode du dashboard
C’est beau

On peut aller beaucoup plus loin, on peut par exemple changer la couleur de fond d’une tuile en fonction d’une valeur :

RAND 100.0 * ROUND 'value' STORE // ce qu'on surveille, ici du random entre 0 et 100
{
  'type' 'display' 'unit' '%25' // %
   'w' 1 'h' 1 'x' 0 'y' 1
   'data' {
     'data' $value
    'globalParams' {
      'bgColor'
      <% $value 33 < %> <% '#77BE69' %> // value < 33 ? -> vert
      <% $value 66 < %> <% '#FF9830' %> // value < 66 ? -> orange
      <% '#F24865' %> 2 SWITCH // sinon rouge
      'fontColor' 'white' // affichage de 'value' en blanc
    }
  }

Bref, faites vous plaisir 🙂

Partager c'est la vie

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *