Guide rapide du micromètre

1. Introduction

Micrometer fournit une façade simple sur les clients d'instrumentation pour un certain nombre de systèmes de surveillance populaires. Actuellement, il prend en charge les systèmes de surveillance suivants: Atlas, Datadog, Graphite, Ganglia, Influx, JMX et Prometheus.

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

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

2. Dépendance de Maven

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

 io.micrometer micrometer-registry-atlas 0.12.0.RELEASE 

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

3. MeterRegistry

Dans Micromètre, un MeterRegistry est le composant principal utilisé pour enregistrer les compteurs. Nous pouvons parcourir le registre et approfondir les métriques de chaque indicateur, 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, c'est AtlasMeterRegistry .

CompositeMeterRegistry permet d'ajouter plusieurs registres. Il fournit une solution pour publier simultanément des métriques 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 plates-formes:

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

Il existe un support de registre global statique dans Micrometer: Metrics.globalRegistry . En outre, un ensemble de générateurs statiques basés sur ce registre global est fourni pour générer des compteurs dans les métriques :

@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 counterOptional = Metrics.globalRegistry .find("objects.instance").counter(); assertTrue(counterOptional.isPresent()); assertTrue(counterOptional.get().count() == 2.0); }

4. Balises et compteurs

4.1. Mots clés

Un identifiant d'un multimètre se compose d'un nom et de balises. Il est suggéré de suivre une convention de dénomination qui sépare les mots par un point, pour aider à garantir la portabilité des noms de métriques sur plusieurs systèmes de surveillance.

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

Les balises peuvent être utilisées pour découper la métrique pour raisonner 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 est destiné à compter les visiteurs de la page âgés de 20 à 30 ans.

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

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

4.2. Compteur

Un compteur rapporte simplement un décompte sur une propriété spécifiée d'une application. Nous pouvons créer un compteur personnalisé avec le générateur 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 le montre l'extrait ci-dessus, nous avons essayé de réduire le compteur de un, mais nous ne pouvons incrémenter le compteur de manière monotone que d'un montant positif fixe.

4.3. Minuteries

Pour mesurer les latences ou la fréquence des événements dans notre système, nous pouvons utiliser des minuteries . Un minuteur indiquera au moins le temps total et le nombre d'événements d'une série chronologique spécifique.

Par exemple, nous pouvons enregistrer un événement d'application qui peut 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));

Pour enregistrer des événements de longue durée, nous utilisons LongTaskTimer :

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 compteur.

Contrairement aux autres compteurs, les jauges ne doivent rapporter les données que lorsqu'elles sont observées. Les jauges peuvent être utiles pour surveiller les statistiques du cache, des collections, etc.:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); List 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. DistributionRésumé

Distribution des événements et un résumé simple sont fournis par 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 des balises quantile = 0,3 , quantile = 0,5 et quantile = 0,95 seront disponibles dans le registre, indiquant les valeurs en dessous desquelles se situent respectivement 95%, 50% et 30% des observations.

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 jauges quantiles :

List quantileGauges = registry.getMeters().stream() .filter(m -> m.getType().name().equals("Gauge")) .map(meter -> (Gauge) meter) .collect(Collectors.toList()); assertTrue(3 == quantileGauges.size()); Map quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9); assertThat(quantileMap, allOf( hasEntry("quantile=0.3",2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8)));

En outre, Micrometer prend également en charge les histogrammes:

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

Semblable aux quantiles, après avoir ajouté plusieurs enregistrements, nous pouvons voir que l'histogramme gère assez bien le calcul:

Map 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)));

Generally, histograms can help illustrate a direct comparison in separate buckets. Histograms can also be time scaled, which is quite useful for analyzing backend service response time:

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

The Micrometer has multiple built-in binders to monitor the JVM, caches, ExecutorService and logging services.

When it comes to JVM and system monitoring, we can monitor class loader metrics (ClassLoaderMetrics), JVM memory pool (JvmMemoryMetrics) and GC metrics (JvmGcMetrics), thread and CPU utilization (JvmThreadMetrics, ProcessorMetrics).

Cache monitoring (currently, only Guava, EhCache, Hazelcast, and Caffeine are supported) is supported by instrumenting with GuavaCacheMetrics, EhCache2Metrics, HazelcastCacheMetrics, and CaffeineCacheMetrics. And to monitor log back service, we can bind LogbackMetrics to any valid registry:

new LogbackMetrics().bind(registry);

The usage of above binders are quite similar to LogbackMetrics and are all rather simple, so we won’t dive into further details here.

6. Spring Integration

Spring Boot Actuator provides dependency management and auto-configuration for Micrometer. Now it's supported in Spring Boot 2.0/1.x and Spring Framework 5.0/4.x.

We'll need the following dependency (the latest version can be found here):

 io.micrometer micrometer-spring-legacy 0.12.0.RELEASE 

Without any further change to existing code, we have enabled Spring support with the Micrometer. JVM memory metrics of our Spring application will be automatically registered in the global registry and published to the default atlas endpoint: //localhost:7101/api/v1/publish.

There're several configurable properties available to control metrics exporting behaviors, starting with spring.metrics.atlas.*. Check AtlasConfig to see a full list of configuration properties for Atlas publishing.

If we need to bind more metrics, only add them as @Bean to the application context.

Say we need the JvmThreadMetrics:

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

As for web monitoring, it's auto-configured for every endpoint in our application, yet manageable via a configuration property: spring.metrics.web.autoTimeServerRequests.

The default implementation provides four dimensions of metrics for endpoints: HTTP request method, HTTP response code, endpoint URI, and exception information.

When requests are responded, metrics relating to request method (GET, POST, etc.) will be published in Atlas.

With Atlas Graph API, we can generate a graph to compare the response time for different methods:

By default, response codes of 20x, 30x, 40x, 50x will also be reported:

We can also compare different URIs :

or check exception metrics:

Note that we can also use @Timed on the controller class or specific endpoint methods to customize tags, long task, quantiles, and percentiles of the metrics:

@RestController @Timed("people") public class PeopleController { @GetMapping("/people") @Timed(value = "people.all", longTask = true) public List listPeople() { //... } }

Based on the code above, we can see the following tags by checking Atlas endpoint //localhost:7101/api/v1/tags/name:

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

Micrometer also works in the function web framework introduced in Spring Boot 2.0. Metrics can be enabled by filtering the RouterFunction:

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

Metrics from the data source and scheduled tasks can also be collected. Check the official documentation for more details.

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 sous une sémantique commune, l'outil facilite la commutation entre différentes plates-formes de surveillance.

Comme toujours, le code d'implémentation complet de cet article est disponible sur Github.