Technique

Recherche et agrégation des données temporelles dans Elasticsearch

Historiquement, Elasticsearch est un moteur de recherche qui stocke ses index de recherche dans une base de données Lucene. Mais depuis son apparition, Elasticsearch a évolué pour devenir un datastore hautement performant, clustérisable et scalable. Son format basé sur des index reflète toujours l'histoire de ses débuts, mais il permet aujourd'hui à toutes sortes d'utilisateurs d'atteindre d'innombrables objectifs.

Parmi eux, citons le stockage, le traitement et la récupération de données temporelles. Ce qui caractérise les données temporelles, c'est que chaque point de données est associé à un horodatage précis. Le plus souvent, un point de données correspond à une mesure quelconque, prise à un moment donné. Il peut s'agir du cours d'une action, d'une observation scientifique ou de la charge d'un serveur.

Quoiqu'il existe différentes implémentations de bases de données spécialisées dans le traitement des données temporelles, aucun format commun n'a été conçu pour leur stockage et leur recherche, et en théorie, n'importe quel moteur de base de données peut servir à les traiter.

Pour résumer, une entrée de base de données temporelles comprend

  • le nom de la série temporelle
  • un horodatage
  • une valeur
  • un ensemble de paires clé-valeur, ou balises, contenant des informations supplémentaires sur la série temporelle

Dans un cas d'utilisation de monitoring de serveur, on trouve toujours une paire clé-valeur qui indique à quel hôte appartient une série temporelle, mais les informations supplémentaires qui peuvent être ajoutées peuvent être de toute sorte et peuvent ensuite servir exclusivement à rechercher des indicateurs relatifs à un ensemble d'hôtes donné (seulement les hôtes appartenant à un environnement de production, hôtes exécutant certains services, ou instances exécutées chez un fournisseur cloud donné, par exemple).

Plus concrètement, comment utiliser les requêtes Elasticsearch pour extraire de vos données les informations relatives à des séries temporelles ? Prenons l'exemple des données Metricbeat.

Chaque document Metricbeat contient les informations suivantes :

  • Un horodatage
  • Des données temporelles réelles
  • Les métadonnées relatives aux indicateurs contenus dans le document. Metricbeat se sert des champs ECS event.module et event.dataset pour indiquer quel module Metricbeat a créé le document et quel ensemble d'indicateurs il contient.
    • Cela s'avère utile pour connaître les indicateurs déjà présents dans le document avant d'en extraire les données temporelles.
  • Les métadonnées relatives à l'instance, qu'il s'agisse d'un hôte physique, d'une machine virtuelle ou d'une petite entité telle qu'un pod Kubernetes ou un conteneur Docker
    • Ces métadonnées suivent la spécification Elastic Common Schema : elles peuvent ainsi être mises en correspondance avec des données provenant d'autres sources qui s'appuient aussi sur ECS.

Par exemple, voici à quoi ressemble un document Metricbeat provenant de l'ensemble d'indicateurs system.cpu. Les commentaires insérés au niveau de l'objet _source indiquent où trouver d'autres informations sur le champ :

Remarque : Pour une meilleure lisibilité, nous insérons les # commentaires dans les extraits de JSON.

{ 
  "_index" : "metricbeat-8.0.0-2019.08.12-000001", 
  "_type" : "_doc", 
  "_id" : "vWm5hWwB6vlM0zxdF3Q5", 
  "_score" : 0.0, 
  "_source" : { 
    "@timestamp" : "2019-08-12T12:06:34.572Z", 
    "ecs" : { # ECS metadata 
      "version" : "1.0.1" 
    }, 
    "host" : { # ECS metadata 
      "name" : "noether", 
      "hostname" : "noether", 
      "architecture" : "x86_64", 
      "os" : { 
        "kernel" : "4.15.0-55-generic", 
        "codename" : "bionic", 
        "platform" : "ubuntu", 
        "version" : "18.04.3 LTS (Bionic Beaver)", 
        "family" : "debian", 
        "name" : "Ubuntu" 
      }, 
      "id" : "4e3eb308e7f24789b4ee0b6b873e5414", 
      "containerized" : false 
    }, 
    "agent" : { # ECS metadata 
      "ephemeral_id" : "7c725f8a-ac03-4f2d-a40c-3695a3591699", 
      "hostname" : "noether", 
      "id" : "e8839acc-7f5e-40be-a3ab-1cc891bcb3ce", 
      "version" : "8.0.0", 
      "type" : "metricbeat" 
    }, 
    "event" : { # ECS metadata 
      "dataset" : "system.cpu", 
      "module" : "system", 
      "duration" : 725494 
    }, 
    "metricset" : { # metricbeat metadata 
      "name" : "cpu" 
    }, 
    "service" : { # metricbeat metadata 
      "type" : "system" 
    }, 
    "system" : { # metricbeat time series data  
      "cpu" : { 
        "softirq" : { 
          "pct" : 0.0112 
        }, 
        "steal" : { 
          "pct" : 0 
        }, 
        "cores" : 8, 
        "irq" : { 
          "pct" : 0 
        }, 
        "idle" : { 
          "pct" : 6.9141 
        }, 
        "nice" : { 
          "pct" : 0 
        }, 
        "user" : { 
          "pct" : 0.7672 
        }, 
        "system" : { 
          "pct" : 0.3024 
        }, 
        "iowait" : { 
          "pct" : 0.0051 
        }, 
        "total" : { 
          "pct" : 1.0808 
        } 
      } 
    } 
  } 
}

Pour résumer, dans un document Metricbeat, les données temporelles et les métadonnées sont mélangées. Vous avez besoin de certaines informations sur le format du document pour extraire les données dont vous avez besoin.

En revanche, pour traiter, analyser ou visualiser des données temporelles, leur format est généralement semblable à celui d'un tableau :

<series name> <timestamp> <value> <key-value pairs> 
system.cpu.user.pct 1565610800000 0.843 host.name=”noether” 
system.cpu.user.pct 1565610800000 0.951 host.name=”hilbert” 
system.cpu.user.pct 1565610810000 0.865 host.name=”noether” 
system.cpu.user.pct 1565610810000 0.793 host.name=”hilbert” 
system.cpu.user.pct 1565610820000 0.802 host.name=”noether” 
system.cpu.user.pct 1565610820000 0.679 host.name=”hilbert”

Les requêtes Elasticsearch vous permettent de récupérer programmatiquement les données temporelles dans un format très similaire au tableau ci-dessus. Nous allons voir comment procéder dans les exemples suivants. Si vous voulez essayer les requêtes par vous-même, vous aurez besoin d'une instance Elasticsearch et d'une installation Metricbeat opérationnelle qui transfère des ensembles d'indicateurs system.cpu et system.network. Pour une brève introduction à Metricbeat, n'hésitez pas à consulter la documentation de prise en main.

Vous pouvez exécuter toutes les requêtes depuis la console Kibana Dev Tools (Outils de développement Kibana). Si vous ne l'avez jamais utilisée, vous trouverez une courte introduction dans la documentation relative à la console Kibana. Remarque : Vous devez modifier le nom d'hôte indiqué dans les exemples de requêtes.

Nous partons du principe que Metricbeat utilise la configuration par défaut. Autrement dit, il créera un index par jour, et ces index suivront le format "metricbeat-VERSION-DATE-COMPTEUR", par exemple, quelque chose comme metricbeat-7.3.0-2019.08.06-000009. Pour interroger tous ces index simultanément, nous utiliserons un caractère générique :

Exemple de requête :

GET metricbeat-*/_search

Et exemple de réponse :

{ 
  "took" : 2, 
  "timed_out" : false, 
  "_shards" : { 
    "total" : 1, 
    "successful" : 1, 
    "skipped" : 0, 
    "failed" : 0 
  }, 
  "hits" : { 
    "total" : { 
      "value" : 10000, 
      "relation" : "gte" 
    }, 
    "max_score" : 1.0, 
    "hits" : [...] 
  } 
}

Bien sûr, cette requête dépasse la limite de documents que retournera Elasticsearch en une seule recherche. Nous omettons ici les résultats réels, mais vous pouvez parcourir vos résultats et les comparer avec le document annoté ci-dessus.

Selon la taille de l'infrastructure que vous monitorez, le nombre de documents Metricbeat peut être considérable, mais vous aurez rarement besoin d'une série temporelle qui remonte à la nuit des temps. Commençons donc par une plage de dates, par exemple, les cinq dernières minutes :

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": { 
    "range": { 
      "@timestamp": { 
        "gte": "now-5m" 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  "took" : 4, 
  "timed_out" : false, 
  "_shards" : { 
    "total" : 1, 
    "successful" : 1, 
    "skipped" : 0, 
    "failed" : 0 
  }, 
  "hits" : { 
    "total" : { 
      "value" : 30, 
      "relation" : "eq" 
    }, 
    "max_score" : 0.0, 
    "hits" : [...] 
  } 
}

La taille est maintenant bien plus gérable. Cependant, le système sur lequel est exécutée cette requête ne communique qu'avec un hôte. Dans un environnement de production, le nombre de résultats sera donc encore très élevé.

Pour extraire toutes les données CPU associées à un hôte donné, on peut être tenté, de premier abord, d'ajouter à la requête Elasticsearch des filtres pour host.name et l'ensemble d'indicateurs system.cpu :

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": { 
    "bool": { 
      "filter": [ 
        { 
          "range": { 
            "@timestamp": { 
              "gte": "now-5m" 
            } 
          } 
        }, 
        { 
          "bool": { 
            "should": [ 
              { 
                "match_phrase": { 
                  "host.name": "noether" 
                } 
              }, 
              { 
                "match_phrase": { 
                  "event.dataset": "system.cpu" 
                } 
              } 
            ] 
          } 
        } 
      ] 
    } 
  } 
}

Et exemple de réponse :

{ 
  "took" : 8, 
  "timed_out" : false, 
  "_shards" : { 
    "total" : 1, 
    "successful" : 1, 
    "skipped" : 0, 
    "failed" : 0 
  }, 
  "hits" : { 
    "total" : { 
      "value" : 30, 
      "relation" : "eq" 
    }, 
    "max_score" : 0.0, 
    "hits" : [...] 
  } 
}

Cette requête renvoie encore un grand nombre de documents, qui contiennent tous l'intégralité des données envoyées par Metricbeat pour l'ensemble d'indicateurs system.cpu. Le résultat ne nous est donc pas très utile, et ce, pour un certain nombre de raisons.

Pour commencer, cela nous oblige à extraire l'ensemble des documents sur toute la période. Dès que nous atteignons la limite configurée, Elasticsearch ne renvoie pas les documents en une seule fois : il tente de les classer, ce qui n'est pas pertinent pour notre requête, et il ne les renvoie pas triés par date/heure.

Ensuite, seule une petite partie de chaque document nous intéresse : l'horodatage, quelques valeurs d'indicateurs et, éventuellement, quelques autres champs de métadonnées. Obtenir d'Elasticsearch l'intégralité de l'objet _source, puis sélectionner les données dans le résultat de la requête s'avère très inefficace.

Pour pallier ce problème, on peut utiliser les agrégations Elasticsearch.

1er exemple : pourcentage CPU, sous-échantillonné

Commençons par nous pencher sur les histogrammes de date. Une agrégation d'histogrammes de date renvoie une valeur par intervalle. Les buckets renvoyés sont déjà classés par date/heure et on peut spécifier l'intervalle ou la taille du bucket pour obtenir les données correspondantes. Dans cet exemple, nous choisissons un intervalle de 10 secondes, car par défaut, Metricbeat envoie les données provenant du module système toutes les 10 secondes. Le paramètre de niveau supérieur size: 0 spécifie que nous ne nous intéressons plus aux résultats réels, mais seulement à l'agrégation : aucun document ne sera donc renvoyé.

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "fixed_interval": "10s" 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : { 
    "total" : { 
      "value" : 30, 
      "relation" : "eq" 
    }, 
    "max_score" : null, 
    "hits" : [ ] 
  }, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:03:20.000Z", 
          "key" : 1565615000000, 
          "doc_count" : 1 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:03:30.000Z", 
          "key" : 1565615010000, 
          "doc_count" : 1 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:03:40.000Z", 
          "key" : 1565615020000, 
          "doc_count" : 1 
        }, 
        ... 
      ] 
    } 
  } 
}

Pour chaque bucket, nous obtenons l'horodatage dans le paramètre key, un paramètre key_as_string très utile, contenant une chaîne date/heure lisible, et le nombre de documents contenus dans le bucket.

Le paramètre doc_count est égal à 1, car la taille du bucket correspond à la période définie par défaut pour Metricbeat. Il n'est autrement pas très utile. Pour afficher les valeurs d'indicateurs réelles, nous allons donc ajouter une autre agrégation. À ce stade, nous devons décider quelle sera cette agrégation. Pour les valeurs numériques, les agrégations avg, min et max sont de bonnes candidates – mais tant que nous n'avons qu'un document par bucket, n'importe laquelle fera l'affaire. L'exemple ci-dessous renvoie les agrégations avg, min et max pour les valeurs de l'indicateur system.cpu.user.pct dans des buckets de 10 secondes :

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "fixed_interval": "10s" 
      }, 
      "aggregations": { 
        "myActualCpuUserMax": { 
          "max": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myActualCpuUserAvg": { 
          "avg": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myActualCpuUserMin": { 
          "min": { 
            "field": "system.cpu.user.pct" 
          } 
        } 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:12:40.000Z", 
          "key" : 1565615560000, 
          "doc_count" : 1, 
          "myActualCpuUserMin" : { 
            "value" : 1.002 
          }, 
          "myActualCpuUserAvg" : { 
            "value" : 1.002 
          }, 
          "myActualCpuUserMax" : { 
            "value" : 1.002 
          } 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:12:50.000Z", 
          "key" : 1565615570000, 
          "doc_count" : 1, 
          "myActualCpuUserMin" : { 
            "value" : 0.866 
          }, 
          "myActualCpuUserAvg" : { 
            "value" : 0.866 
          }, 
          "myActualCpuUserMax" : { 
            "value" : 0.866 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

Comme on peut le voir, les valeurs de myActualCpuUserMin, myActualCpuUserAvg et myActualCpuUserMax sont identiques dans chaque bucket. Si vous devez extraire les valeurs brutes d'une série temporelle transmise à intervalles réguliers, vous pouvez donc utiliser un histogramme de date pour le faire.

Toutefois, la plupart du temps, vous n'aurez pas besoin de tous les point de données, notamment lorsque les mesures sont prises à des intervalles de quelques secondes. Dans bien des cas, il est en effet plus utile de disposer de données dont la granularité est moins fine. Par exemple, une visualisation n'utilise qu'une quantité limitée de pixels pour afficher les variations d'une série temporelle : les données à granularité élevée seront donc supprimées au moment du rendu.

Les données temporelles sont souvent sous-échantillonnées à un niveau de granularité correspondant aux exigences de l'étape de traitement qui suit. Le processus de sous-échantillonnage convertit ainsi différents points de données en un seul. Dans notre exemple de monitoring de serveur, bien que les données soient mesurées toutes les 10 secondes, dans la majorité des cas, la moyenne des valeurs mesurées en une minute suffit. Par ailleurs, c'est précisément un sous-échantillonnage qu'opère une agrégation d'histogrammes de date lorsqu'elle trouve plus d'un document par bucket et que les bonnes agrégations imbriquées sont utilisées.

L'exemple suivant montre le résultat d'un histogramme de date avec des agrégations avg, min et max imbriquées sur un bucket d'une minute entière. Nous avons donc là un premier exemple de sous-échantillonnage. L'utilisation de calendar_interval en lieu et place de fixed_interval permet d'aligner les limites du bucket sur des minutes entières.

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "calendar_interval": "1m" 
      }, 
      "aggregations": { 
        "myDownsampledCpuUserMax": { 
          "max": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myDownsampledCpuUserAvg": { 
          "avg": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myDownsampledCpuUserMin": { 
          "min": { 
            "field": "system.cpu.user.pct" 
          } 
        } 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:27:00.000Z", 
          "key" : 1565616420000, 
          "doc_count" : 4, 
          "myDownsampledCpuUserMax" : { 
            "value" : 0.927 
          }, 
          "myDownsampledCpuUserMin" : { 
            "value" : 0.6980000000000001 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.8512500000000001 
          } 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:28:00.000Z", 
          "key" : 1565616480000, 
          "doc_count" : 6, 
          "myDownsampledCpuUserMax" : { 
            "value" : 0.838 
          }, 
          "myDownsampledCpuUserMin" : { 
            "value" : 0.5670000000000001 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.7040000000000001 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

Comme vous pouvez le voir, les valeurs de myActualCpuUserMin, myActualCpuUserAvg et myActualCpuUserMax ne sont plus identiques et dépendent maintenant de l'agrégation utilisée.

La méthode de sous-échantillonnage employée dépend quant à elle de l'indicateur. Pour le pourcentage CPU, une agrégation avg sur une minute est suffisante, mais pour des indicateurs tels que la longueur des files d'attente ou la charge système, une agrégation max peut-être plus adaptée.

À ce stade, il est aussi possible d'exploiter Elasticsearch pour effectuer quelques opérations arithmétiques élémentaires et calculer une série temporelle non comprise dans les données d'origine. En admettant que nous utilisons l'agrégation avg pour le CPU, nous pouvons améliorer notre exemple pour qu'il renvoie le CPU utilisateur (ci-dessous, "user CPU"), le CPU système ("system CPU") et la somme utilisateur + système divisées par les cœurs de processeur ("CPU cores"), comme suit :

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...},   # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "calendar_interval": "1m" 
      }, 
      "aggregations": { 
        "myDownsampledCpuUserAvg": { 
          "avg": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myDownsampledCpuSystemAvg": { 
          "avg": { 
            "field": "system.cpu.system.pct" 
          } 
        }, 
        "myCpuCoresMax": { 
          "max": { 
            "field": "system.cpu.cores" 
          } 
        }, 
        "myCalculatedCpu": { 
          "bucket_script": { 
            "buckets_path": { 
              "user": "myDownsampledCpuUserAvg", 
              "system": "myDownsampledCpuSystemAvg", 
              "cores": "myCpuCoresMax" 
            }, 
            "script": { 
              "source": "(params.user + params.system) / params.cores", 
              "lang": "painless" 
            } 
          } 
        } 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:32:00.000Z", 
          "key" : 1565616720000, 
          "doc_count" : 2, 
          "myDownsampledCpuSystemAvg" : { 
            "value" : 0.344 
          }, 
          "myCpuCoresMax" : { 
            "value" : 8.0 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.8860000000000001 
          }, 
          "myCalculatedCpu" : { 
            "value" : 0.15375 
          } 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:33:00.000Z", 
          "key" : 1565616780000, 
          "doc_count" : 6, 
          "myDownsampledCpuSystemAvg" : { 
            "value" : 0.33416666666666667 
          }, 
          "myCpuCoresMax" : { 
            "value" : 8.0 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.8895 
          }, 
          "myCalculatedCpu" : { 
            "value" : 0.15295833333333334 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

2exemple : trafic réseau – agrégations de termes et agrégations dérivées

Légèrement plus élaboré, l'exemple de l'ensemble d'indicateurs system.network montre à quel point les agrégations Elasticsearch peuvent s'avérer utiles en matière de données temporelles. Dans un document relatif à l'ensemble d'indicateurs system.network, la partie qui nous intéresse ressemble à ceci :

{ 
... 
"system": { 
        "network": { 
            "in": { 
                "bytes": 37904869172, 
                "dropped": 32, 
                "errors": 0, 
                "packets": 32143403 
            }, 
            "name": "wlp4s0", 
            "out": { 
                "bytes": 6299331926, 
                "dropped": 0, 
                "errors": 0, 
                "packets": 13362703 
            } 
        } 
    } 
... 
}

Metricbeat envoie un document pour chaque interface réseau présente dans le système. Pour le champ system.network.name, ces documents présentent le même horodatage, mais des valeurs différentes – une par interface réseau.

Toute nouvelle agrégation devra se faire par interface. Nous remplaçons donc l'agrégation d'histogrammes de date de niveau supérieur que nous avons utilisée dans les exemples précédents par une agrégation de termes sur le champ system.network.name.

Remarque : Pour que cela fonctionne, le champ utilisé pour l'agrégation doit être mappé comme champ de mot-clé. Si vous utilisez le modèle d'index par défaut fourni avec Metricbeat, ce mapping est déjà défini. Dans le cas contraire, la page de documentation dédiée aux modèles Metricbeat vous explique brièvement les étapes à suivre.

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...}, # same as above 
  "size": 0, 
  "aggregations": { 
    "myNetworkInterfaces": { 
      "terms": { 
        "field": "system.network.name", 
        "size": 50 
      }, 
      "aggs": { 
        "myDateHistogram": { 
          "date_histogram": { 
            "field": "@timestamp", 
            "calendar_interval": "1m" 
          } 
        } 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myNetworkInterfaces" : { 
      "doc_count_error_upper_bound" : 0, 
      "sum_other_doc_count" : 0, 
      "buckets" : [ 
        { 
          "key" : "docker0", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [...] 
          } 
        }, 
        { 
          "key" : "enp0s31f6", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [...] 
          } 
        }, 
        { 
          "key" : "lo", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [...] 
          } 
        }, 
        { 
          "key" : "wlp61s0", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [ 
              { 
                "key_as_string" : "2019-08-12T13:39:00.000Z", 
                "key" : 1565617140000, 
                "doc_count" : 1 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:40:00.000Z", 
                "key" : 1565617200000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:41:00.000Z", 
                "key" : 1565617260000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:42:00.000Z", 
                "key" : 1565617320000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:43:00.000Z", 
                "key" : 1565617380000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:44:00.000Z", 
                "key" : 1565617440000, 
                "doc_count" : 4 
              } 
            ] 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

Tout comme dans l'exemple du CPU, sans agrégation imbriquée, l'agrégation d'histogrammes de date ne renvoie que doc_count, ce qui n'est pas très utile.

Les champs associés aux octets (ci-dessous, "bytes") font apparaître une augmentation monotone des valeurs. Les valeurs de ces champs indiquent le nombre d'octets envoyés ou reçus depuis le dernier démarrage de la machine, et augmentent donc à chaque mesure. Dans cet exemple, la bonne agrégation imbriquée est max, afin que la valeur sous-échantillonnée contienne la mesure la plus élevée (et donc la plus récente) prise durant l'intervalle du bucket.

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myNetworkInterfaces": { 
      "terms": { 
        "field": "system.network.name", 
        "size": 50 
      }, 
      "aggs": { 
        "myDateHistogram": { 
          "date_histogram": { 
            "field": "@timestamp", 
            "calendar_interval": "1m" 
          }, 
          "aggregations": { 
            "myNetworkInBytesMax": { 
              "max": { 
                "field": "system.network.in.bytes" 
              } 
            }, 
            "myNetworkOutBytesMax": { 
              "max": { 
                "field": "system.network.out.bytes" 
              } 
            } 
          } 
        } 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myNetworkInterfaces" : { 
      "doc_count_error_upper_bound" : 0, 
      "sum_other_doc_count" : 0, 
      "buckets" : [ 
        { 
          "key" : "docker0", 
          ... 
        }, 
        { 
          "key" : "enp0s31f6", 
          ... 
        }, 
        { 
          "key" : "lo", 
          ... 
        }, 
        { 
          "key" : "wlp61s0", 
          "doc_count" : 30, 
          "myDateHistogram" : { 
            "buckets" : [ 
              { 
                "key_as_string" : "2019-08-12T13:50:00.000Z", 
                "key" : 1565617800000, 
                "doc_count" : 2, 
                "myNetworkInBytesMax" : { 
                  "value" : 2.991659837E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.46578365E8 
                } 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:51:00.000Z", 
                "key" : 1565617860000, 
                "doc_count" : 6, 
                "myNetworkInBytesMax" : { 
                  "value" : 2.992027006E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.46791988E8 
                }, 
                "myNetworkInBytesPerSecond" : { 
                  "value" : 367169.0, 
                  "normalized_value" : 6119.483333333334 
                }, 
                "myNetworkoutBytesPerSecond" : { 
                  "value" : 213623.0, 
                  "normalized_value" : 3560.383333333333 
                } 
              }, 
              ... 
            ] 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

Pour obtenir le taux d'octets par seconde à partir du compteur à croissance monotone, on peut utiliser une agrégation dérivée. Lorsqu'on transmet à cette agrégation le paramètre facultatif unit, on obtient la valeur souhaitée par unité dans le champ normalized_value :

Exemple de requête :

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myNetworkInterfaces": { 
      "terms": { 
        "field": "system.network.name", 
        "size": 50 
      }, 
      "aggs": { 
        "myDateHistogram": { 
          "date_histogram": { 
            "field": "@timestamp", 
            "calendar_interval": "1m" 
          }, 
          "aggregations": { 
            "myNetworkInBytesMax": { 
              "max": { 
                "field": "system.network.in.bytes" 
              } 
            }, 
            "myNetworkInBytesPerSecond": { 
              "derivative": { 
                "buckets_path": "myNetworkInBytesMax", 
                "unit": "1s" 
              } 
            }, 
            "myNetworkOutBytesMax": { 
              "max": { 
                "field": "system.network.out.bytes" 
              } 
            }, 
            "myNetworkoutBytesPerSecond": { 
              "derivative": { 
                "buckets_path": "myNetworkOutBytesMax", 
                "unit": "1s" 
              } 
            } 
          } 
        } 
      } 
    } 
  } 
}

Et exemple de réponse :

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myNetworkInterfaces" : { 
      "doc_count_error_upper_bound" : 0, 
      "sum_other_doc_count" : 0, 
      "buckets" : [ 
        { 
          "key" : "docker0", 
          ... 
        }, 
        { 
          "key" : "enp0s31f6", 
          ... 
        }, 
        { 
          "key" : "lo", 
          ... 
        }, 
        { 
          "key" : "wlp61s0", 
          "doc_count" : 30, 
          "myDateHistogram" : { 
            "buckets" : [ 
              { 
                "key_as_string" : "2019-08-12T14:07:00.000Z", 
                "key" : 1565618820000, 
                "doc_count" : 4, 
                "myNetworkInBytesMax" : { 
                  "value" : 3.030494669E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.56084749E8 
                } 
              }, 
              { 
                "key_as_string" : "2019-08-12T14:08:00.000Z", 
                "key" : 1565618880000, 
                "doc_count" : 6, 
                "myNetworkInBytesMax" : { 
                  "value" : 3.033793744E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.56323416E8 
                }, 
                "myNetworkInBytesPerSecond" : { 
                  "value" : 3299075.0, 
                  "normalized_value" : 54984.583333333336 
                }, 
                "myNetworkoutBytesPerSecond" : { 
                  "value" : 238667.0, 
                  "normalized_value" : 3977.7833333333333 
                } 
              }, 
              { 
                "key_as_string" : "2019-08-12T14:09:00.000Z", 
                "key" : 1565618940000, 
                "doc_count" : 6, 
                "myNetworkInBytesMax" : { 
                  "value" : 3.037045046E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.56566282E8 
                }, 
                "myNetworkInBytesPerSecond" : { 
                  "value" : 3251302.0, 
                  "normalized_value" : 54188.36666666667 
                }, 
                "myNetworkoutBytesPerSecond" : { 
                  "value" : 242866.0, 
                  "normalized_value" : 4047.766666666667 
                } 
              }, 
              ...   
            ] 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

Vous pouvez essayer toutes ces étapes sur votre propre cluster. Si vous n'avez pas encore de cluster, vous pouvez démarrer un essai gratuit d'Elasticsearch Service sur Elastic Cloud ou télécharger la distribution par défaut de la Suite Elastic. Une fois cela fait, vous pouvez commencer à envoyer les données de vos systèmes avec Metricbeat – à vos requêtes, prêts, partez !