Guide rapide sur le micromètre

[[1-introduction]]

1. Introduction

https://github.com/micrometer-metrics/micrometer-docs fournit une façade simple sur les clients d’instrumentation pour un certain nombre de systèmes de surveillance courants. Actuellement, il prend en charge les systèmes de surveillance suivants: Atlas, Datadog. , Graphite, ganglions, influx, jmx et prométhée.

Dans cet article, nous présenterons l’utilisation de base de Micrometer et son intégration à Spring.

Dans un souci de simplicité, nous prendrons Micrometer Atlas comme exemple pour illustrer la plupart de nos cas d’utilisation.

[[2-maven-dependency]]

2. Dépendance Maven

Pour commencer, ajoutons la dépendance suivante au pom.xml :

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-atlas</artifactId>
    <version>0.12.0.RELEASE</version>
</dependency>

La dernière version peut être trouvée ici .

[[3-registry]]

3. MeterRegistry

Dans Micrometer, MeterRegistry est le composant de base utilisé pour enregistrer les compteurs. Nous pouvons parcourir le registre et approfondir les métriques de chaque compteur pour générer une série chronologique dans le backend avec des combinaisons de métriques et leurs valeurs de dimension.

La forme la plus simple du registre est SimpleMeterRegistry .

Mais dans la plupart des cas, nous devrions utiliser un MeterRegistry explicitement conçu pour notre système de surveillance; pour Atlas, il s’agit de https://github.com/micrometer-metrics/micrometer/blob/master/implementations/micrometer-registry-atlas/src/main/java/io/micrometer/atlas/AtlasMeterRegistry.java#L36 soumissionné. .

CompositeMeterRegistry autorise les enregistrements multiples à être ajouté. Il fournit une solution pour publier simultanément des mesures d’application sur divers systèmes de surveillance pris en charge.

Nous pouvons ajouter n’importe quel MeterRegistry nécessaire pour télécharger les données sur plusieurs plateformes:

CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry();
AtlasMeterRegistry atlasMeterRegistry
  = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM);

compositeRegistry.add(oneSimpleMeter);
compositeRegistry.add(atlasMeterRegistry);

Micrometer prend en charge le registre mondial statique:

En outre, un ensemble de générateurs statiques basés sur ce registre mondial est fourni pour générer des compteurs dans https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Metrics.java # L30[ Metrics ]:

@Test
public void givenGlobalRegistry__whenIncrementAnywhere__thenCounted() {
    class CountedObject {
        private CountedObject() {
            Metrics.counter("objects.instance").increment(1.0);
        }
    }
    Metrics.addRegistry(new SimpleMeterRegistry());

    Metrics.counter("objects.instance").increment();
    new CountedObject();

    Optional<Counter> counterOptional = Metrics.globalRegistry
      .find("objects.instance").counter();
    assertTrue(counterOptional.isPresent());
    assertTrue(counterOptional.get().count() == 2.0);
}

[[4-meters]]

4. Tags et Meters

4.1. Mots clés

Un identifiant d’un Meter est constitué de un nom et des tags. Il est suggéré que nous suivions une convention de dénomination qui sépare les mots avec un point, afin de garantir la portabilité des noms de mesures dans plusieurs systèmes de surveillance.

Counter counter = registry.counter("page.visitors", "age", "20s");

https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Tag.java# L23 métrique pour le raisonnement sur les valeurs. Dans le code ci-dessus, page.visitors est le nom du compteur, avec age = 20s comme balise. Dans ce cas, le compteur compte les visiteurs de la page âgés de 20 à 30 ans.

Pour un grand système, nous pouvons ajouter des balises communes à un registre, par exemple, les métriques proviennent d’une région spécifique:

registry.config().commonTags("region", "ua-east");

4.2. Compteur

Un Counter rapporte simplement un compte une propriété spécifiée d’une application. Nous pouvons créer un compteur personnalisé avec le générateur de fluent ou la méthode d’assistance de n’importe quel MetricRegistry :

Counter counter = Counter
  .builder("instance")
  .description("indicates instance count of the object")
  .tags("dev", "performance")
  .register(registry);

counter.increment(2.0);

assertTrue(counter.count() == 2);

counter.increment(-1);

assertTrue(counter.count() == 2);

Comme nous le voyons dans l’extrait ci-dessus, nous avons essayé de réduire le compteur de un, mais nous ne pouvons l’incrémenter que de manière monotone d’un montant positif fixe.

4.3. Timers

Pour mesurer les latences ou la fréquence d’événements dans notre système, nous pouvons utiliser https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java # L34[Minuteries] .

Une minuterie indiquera au moins le nombre total d’heures et d’événements d’une série temporelle spécifique.

Par exemple, nous pouvons enregistrer un événement d’application pouvant durer plusieurs secondes:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = registry.timer("app.event");
timer.record(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(1500);
    } catch (InterruptedException ignored) { }
});

timer.record(3000, MILLISECONDS);

assertTrue(2 == timer.count());
assertTrue(4510 > timer.totalTime(MILLISECONDS)
  && 4500 <= timer.totalTime(MILLISECONDS));
SimpleMeterRegistry registry = new SimpleMeterRegistry();
LongTaskTimer longTaskTimer = LongTaskTimer
  .builder("3rdPartyService")
  .register(registry);

long currentTaskId = longTaskTimer.start();
try {
    TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException ignored) { }
long timeElapsed = longTaskTimer.stop(currentTaskId);

assertTrue(timeElapsed/(int) 1e9 == 2);

4.4. Jauge

  • Une jauge indique la valeur actuelle d’un mètre. **

Différent des autres compteurs, Gauges devrait ne rapporter des données que lorsqu’elles sont observées. Les jauges peuvent être utiles lors de la surveillance des statistiques de cache, de collections, etc.:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
List<String> list = new ArrayList<>(4);

Gauge gauge = Gauge
  .builder("cache.size", list, List::size)
  .register(registry);

assertTrue(gauge.value() == 0.0);

list.add("1");

assertTrue(gauge.value() == 1.0);

4.5. DistributionSummary

La distribution des événements et un résumé simple sont fournis par https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/DistributionSummary.java# L29[ DistributionSummary ]:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
DistributionSummary distributionSummary = DistributionSummary
  .builder("request.size")
  .baseUnit("bytes")
  .register(registry);

distributionSummary.record(3);
distributionSummary.record(4);
distributionSummary.record(5);

assertTrue(3 == distributionSummary.count());
assertTrue(12 == distributionSummary.totalAmount());

De plus, DistributionSummary et Timers peuvent être enrichis par des quantiles:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = Timer.builder("test.timer")
  .quantiles(WindowSketchQuantiles
    .quantiles(0.3, 0.5, 0.95)
    .create())
  .register(registry);

Dans l’extrait ci-dessus, trois jauges avec les balises quantile = 0.3 , quantile = 0.5 et quantile = 0.95 seront disponibles dans le registre, indiquant les valeurs en dessous desquelles 95%, 50% et 30% des observations tombent, respectivement.

Pour voir ces quantiles en action, ajoutons les enregistrements suivants:

timer.record(2, TimeUnit.SECONDS);
timer.record(2, TimeUnit.SECONDS);
timer.record(3, TimeUnit.SECONDS);
timer.record(4, TimeUnit.SECONDS);
timer.record(8, TimeUnit.SECONDS);
timer.record(13, TimeUnit.SECONDS);

Ensuite, nous pouvons vérifier en extrayant des valeurs dans ces trois quantiles Gauges :

List<Gauge> quantileGauges = registry.getMeters().stream()
  .filter(m -> m.getType().name().equals("Gauge"))
  .map(meter -> (Gauge) meter)
  .collect(Collectors.toList());

assertTrue(3 == quantileGauges.size());

Map<String, Integer> quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9);
assertThat(quantileMap, allOf(
  hasEntry("quantile=0.3",2),
  hasEntry("quantile=0.5", 3),
  hasEntry("quantile=0.95", 8)));

Micrometer prend également en charge les histogrammes:

DistributionSummary hist = DistributionSummary
  .builder("summary")
  .histogram(Histogram.linear(0, 10, 5))
  .register(registry);

Comme pour les quantiles, après avoir ajouté plusieurs enregistrements, nous pouvons voir que l’histogramme gère assez bien le calcul:

Map<String, Integer> histograms = extractTagValueMap(registry, Type.Counter, 1.0);
assertThat(histograms, allOf(
  hasEntry("bucket=0.0", 0),
  hasEntry("bucket=10.0", 2),
  hasEntry("bucket=20.0", 2),
  hasEntry("bucket=30.0", 1),
  hasEntry("bucket=40.0", 1),
  hasEntry("bucket=Infinity", 0)));

En général, les histogrammes peuvent aider à illustrer une comparaison directe dans des compartiments séparés. Les histogrammes peuvent également être échelonnés dans le temps, ce qui est très utile pour analyser le temps de réponse du service backend:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = Timer
  .builder("timer")
  .histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3))
  .register(registry);
//...
assertThat(histograms, allOf(
  hasEntry("bucket=0.0", 0),
  hasEntry("bucket=2.0E8", 1),
  hasEntry("bucket=4.0E8", 1),
  hasEntry("bucket=Infinity", 3)));

[[5-binders]]

5. Liants

Le micromètre dispose de plusieurs classeurs intégrés pour surveiller les services JVM, les caches, ExecutorService et les services de journalisation.

La surveillance du cache (actuellement, seuls les formats Guava, EhCache, Hazelcast et Caffeine sont pris en charge) est prise en charge en instrumentant avec https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/ . io/micromètre/noyau/instrument/liant/cache/GuavaCacheMetrics.java # L31[ GuavaCacheMetrics ], EhCache2Metrics , https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micromètre/noyau/instrument/liant/cache/HazelcastCacheMetrics.java # L27[ HazelcastCacheMetrics ]et https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micromètre/noyau/instrument/liant/cache/CaffeineCacheMetrics.java # L42[ CaffeineCacheMetrics ].

Et pour surveiller le service de consignation, nous pouvons lier https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/logging/LogbackMetrics.java # L36[ LogbackMetrics ]vers tout registre valide:

new LogbackMetrics().bind(registry);

L’utilisation des classeurs ci-dessus est assez similaire à LogbackMetrics et est assez simple, nous n’entrerons donc pas dans les détails.

[[6-spring-integration]]

6. Intégration Spring

  • Spring Boot Actuator assure la gestion des dépendances et la configuration automatique pour Micrometer. ** Il est désormais pris en charge par Spring Boot 2.0/1.x et Spring Framework 5.0/4.x.

Nous aurons besoin de la dépendance suivante (la dernière version est disponible https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22io.micrometer%22%20AND%20a%3A%22micrometer -spring-legacy% 22[ici]):

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-spring-legacy</artifactId>
    <version>0.12.0.RELEASE</version>
</dependency>

Sans autre modification du code existant, nous avons activé la prise en charge de Spring avec le micromètre. Les métriques de mémoire JVM de notre application Spring seront automatiquement enregistrées dans le registre global et publiées sur le noeud final d’atlas par défaut:

http://localhost : 7101/api/v1/publish__.

Plusieurs propriétés configurables sont disponibles pour contrôler les comportements d’exportation de métriques, à commencer par spring.metrics.atlas. ** . Consultez AtlasConfig pour obtenir une liste complète des propriétés de configuration pour Atlas publishing .

Si nous devons lier plus de métriques, ajoutez-les uniquement en tant que @ Bean au contexte de l’application.

Disons que nous avons besoin de JvmThreadMetrics :

@Bean
JvmThreadMetrics threadMetrics(){
    return new JvmThreadMetrics();
}

Quant à la surveillance Web, elle est configurée automatiquement pour chaque point de terminaison de notre application, mais gérable via une propriété de configuration:

spring.metrics.web.autoTimeServerRequests .

  • L’implémentation par défaut fournit quatre dimensions de métriques pour les points de terminaison: la méthode de requête HTTP, le code de réponse HTTP, l’URI du point de terminaison et les informations sur les exceptions. **

Lors de la réponse aux demandes, les mesures relatives à la méthode de demande ( GET , POST , etc.) seront publiées dans Atlas.

Avec Atlas Graph API , nous pouvons générer un graphique pour comparer le temps de réponse de différentes méthodes:

Par défaut, les codes de réponse 20x , 30x , 40x , 50x seront également signalés:

Nous pouvons également comparer différents URI:

ou vérifiez les métriques d’exception:

Notez que nous pouvons également utiliser @ Timed sur la classe du contrôleur ou sur des méthodes de noeud final spécifiques pour personnaliser les balises, les tâches longues, les quantiles et les centiles des métriques:

@RestController
@Timed("people")
public class PeopleController {

    @GetMapping("/people")
    @Timed(value = "people.all", longTask = true)
    public List<String> listPeople() {
       //...
    }

}

Sur la base du code ci-dessus, nous pouvons voir les balises suivantes en vérifiant le point de terminaison Atlas http://localhost : 7101/api/v1/tags/name :

----["people", "people.all", "jvmBufferCount", ...]----

Micrometer fonctionne également dans le framework Web de fonctions introduit dans Spring Boot 2.0. Les métriques peuvent être activées en filtrant la RouterFunction :

RouterFunctionMetrics metrics = new RouterFunctionMetrics(registry);
RouterFunctions.route(...)
  .filter(metrics.timer("server.requests"));

Les métriques de la source de données et les tâches planifiées peuvent également être collectées.

Consultez la page official official pour plus de détails.

[[7-summary]]

7. Conclusion

Dans cet article, nous avons présenté le micromètre de façade métrique. En faisant abstraction et en prenant en charge plusieurs systèmes de surveillance selon une sémantique commune, l’outil facilite la commutation entre différentes plateformes de surveillance.

Comme toujours, le code complet de mise en œuvre de cet article est disponible à l’adresse over sur Github .