Lors du XKE de Mars, j’ai réalisé un hands-on autour de Metrics, cette librairie permet de créer facilement des métriques applicatives. Avec Metrics, vous allez pouvoir standardiser votre supervision technique et métier tout en garantissant la possibilité de les exposer sous différentes formes allant du fichier de log, à Graphite en passant par une API Rest.
Dans cet article, je vous propose de faire le tour d’horizon de la librairie, vous pourrez aussi récupérer le hands-on pour le réaliser par vous-même.
Ce qu’il faut savoir
Metrics est développé autour de trois axes:
- Les métriques qui sont maintenues dans un registry, sont au cœur du système ;
- Les Reporter qui se chargent d’exporter les métriques vers différents supports (log, JMX, Graphite, …) ;
- Les HealthChecks maintenues dans un autre registry et qui permettent de vérifier l’état de santé de l’application.

Dans ce diagramme, vous pouvez voir les interactions entre les composants. Nos services applicatifs en vert produisent des métriques qu’ils enregistrent dans le Registry. Les reporters de leurs côtés consomment le registre pour exporter les métriques. Le Healthcheck registry n’est pas listé dans le diagramme mais il peut être vu comme une simple liste de HealthCheck.
Retenez pour l’instant le MetricRegistry qui est la colonne vertébrale de l’API, elle s’instancie en singleton et doit être disponible partout où vous voulez créer des métriques. Chaque métrique associe un nom à une méthode d’évaluation.
Metrics est disponible en dépendance maven :
<dependencies> <dependency> <groupId>com.codahale.metrics</groupId> <artifactId>metrics-core</artifactId> <version>3.0.1</version> </dependency> </dependencies>
Les Métriques
Gauge
La Gauge représente une mesure instantanée que l’on souhaite monitorer, par exemple la taille d’un cache.
registry.register("app.cache.size", new Gauge<Integer>() { @Override public Integer getValue() { return cache.size(); } });
Vous pouvez créer des JMXGauge pour superviser des valeurs déjà exposées en JMX, cela peut s’avérer utile pour monitorer des frameworks et librairies non intégrés dans Metrics.
Le CachedGauge permet de définir un taux de rafraîchissement de la valeur exposée dans le cas où un traitement lourd pour la calculer est nécessaire.
Counter
Le counter est en fait une Gauge exposant un AtomicInteger accompagné de ses méthodes d’incrément et décrément. Il permet de maintenir par exemple très facilement le nombre de requêtes HTTP en cours de traitement.
final Counter requestCounter = registry.counter("app.requests.count")); requestCounter.inc(); // Process request … requestCounter.dec();
Histograms
C’est le moment où la librairie commence à trouver sa valeur ajoutée. Les histogrammes permettent de mesurer la distribution des valeurs d’un traitement. Dit comme ça, je pense que ce n’est pas clair, alors prenons un exemple simple : Si je désire connaître le prix moyen du panier de mes clients, je peux utiliser un Histogram sur le prix du panier.
final Histogram basketPrice = registry.histogram("app.basket.price"); resultCounts.update(basket.totalPrice());
J’ai maintenant le suivi du prix minimum, maximum, moyen, médian et les prix pour 95% des utilisateurs. Pour estimer ces valeurs, les Histograms reposent sur un Reservoir permettant de maintenir un nombre restreint de valeurs en mémoire. Il en existe plusieurs, à sélectionner selon votre besoin :
- UniformReservoir pour des estimations à long terme ;
- ExponentiallyDecayingReservoir pour observer rapidement les changements dans la distribution ;
- SlidingWindow / SlidingTimeWindow pour limiter strictement le nombre d’évènements maintenus.
Histogram histogram = new Histogram(new UniformReservoir()); registry.register("app.basket.price", histogram);
Meters
Les Meters sont utilisés pour évaluer le nombre d’occurrences ou le taux d’un évènement particulier de l’application, par exemple le cache-hit et le cache-miss. Chaque Meter calcule le taux d’occurrence de l’évènement par seconde, pour 1 minute, 5 minutes, 15 minutes et en moyenne.
final Meter hitMeter = registry.meter("app.cache.hit"); hitMeter.mark();
Timers
Les Timer sont des histogrammes spécialement adaptés au chronométrage des temps de traitement et des Meter mesurant le nombre d’exécutions du traitement.
final Timer timer = registry.timer("app.find.time")); final Timer.Context context = timer.time(); try { // handle request } finally { context.stop(); }
Vous connaissez maintenant l’ensemble des composants de mesure fournis par Metrics, passons à leur exposition.
Les Reporters
Les reporters sont les composants en charge d’exposer les métriques en dehors de l’application. Il en existe de toute sorte. N’hésitez pas à regarder le code de l’une d’entre elles pour en apprécier la simplicité.
Je choisis ici de vous montrer un exemple pour JMX et Graphite car ce sont les deux reporters que j’utilise au quotidien.
final JmxReporter reporter = JmxReporter.forRegistry(registry).build(); reporter.start(); final Graphite graphite = new Graphite(new InetSocketAddress("graphite.example.com", 2003)); final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry) .prefixedWith("web1.example.com") .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) .filter(MetricFilter.ALL) .build(graphite); reporter.start(1, TimeUnit.MINUTES);
Notez aussi que Metrics fournit des Servlets pour exposer facilement vos mesure en Rest + JSON accompagné de petits utilitaires comme le ThreadDump. Personnellement, j’aime cette approche, mais soyez prudent de ne pas exposer les servlets à l’extérieur.
Les HealthChecks
Metrics propose aussi un HealthCheckRegistry pour maintenir des senseurs chargés de vérifier l’état de santé de l’application ou des services qu’elle utilise. Cette API est prévue pour répondre au LoadBalancer sur des URLs de ping dont le but est de sortir l’instance du cluster si elle n’est plus en état de fonctionner.
Implémenter son HealthCheck
Il suffit d’hériter de la classe HealthCheck et de surcharger la méthode check().
public class DatabaseHealthCheck extends HealthCheck { private final Database database; public DatabaseHealthCheck(Database database) { this.database = database; } @Override protected Result check() throws Exception { if (database.ping()) { return Result.healthy(); } return Result.unhealthy("Can't ping database"); } }
Une fois le service implémenté, vous pouvez l’enregistrer dans le registre.
registry.register("database", new DatabaseHealthCheck(database));
Vous pouvez enfin exécuter les HealthCheck et obtenir le résultat pour chacun.
for (Entry<String, Result> entry : registry.runHealthChecks().entrySet()) { if (entry.getValue().isHealthy()) { System.out.println(entry.getKey() + ": OK"); } else { System.out.println(entry.getKey() + ": FAIL"); } }
Cet exemple est tiré de la documentation de Metrics.
Pour aller plus loin
Au-delà des avantages déjà vus, Metrics fournit plusieurs extensions pour instrumenter des librairies existantes comme EhCache, ou httpClient. Mais il y a aussi des annotations pour simplifier la déclaration des métriques, car telle que je l’ai présenté, l’API peut paraître verbeuse.
Metrics s’intègre aussi très facilement à Spring et Jersey. La librairie est légère, et vraiment simple à intégrer dans n’importe quel type d’application. Vous n’aurez aucune difficulté à définir votre propre extension pour limiter l’aspect intrusif des métriques.
Hands-On
Si après ce tour d’horizon de l’API vous êtes toujours intéressé, vous pouvez vous lancer dans le Hands-On que j’ai réalisé pour le XKE. Vous trouverez le hands-on sous forme de projet Github ici : https://github.com/xebia-france/xke-metrics
Il vous suffira ensuite de lancer :
mvn jetty:run -DskipTest=true
Ouvrez votre navigateur sur http://localhost:8080/. Vous verrez alors les slides du hands-on. En cas de difficulté lors des exercices, vous aurez toujours la possibilité de regarder la branche solution du dépôt Git.
Cet exercice vous guidera à travers la mise en place de Metrics sur une API Rest autour du vin. Vous verrez comment utiliser les servlets, les différentes métriques, les HealthChecks pour finir par l’intégration Spring et les annotations.
N’hésitez pas à me remonter toute difficulté via des Bugs Github ou par commentaire ici-même.
Pour finir
Enfin, il existe une API standardisée pour gérer la problématique du monitoring qui, de plus, sait s’abstraire du système backend utilisé. Deux choses retiennent tout de même mon attention :
- Les métriques sont maintenues en mémoire, dans le registre, ce n’est donc pas un puit sans fond ;
- Pour Graphite, le reporting se fait à intervalle régulier en itérant sur toutes les métriques et non en flux tendu.
Au final, Metrics est une bonne API légère et facile à développer. Pourtant, si vous cherchez à libérer complètement le nombre de métriques en faisant du flux tendu, je conseillerais plutôt l’utilisation de StatsD.
Merci d’avoir suivi cet article de blog, vous pouvez maintenant reprendre une activité normale.
Ressources
- Documentation de Metrics
- Extension Spring
- Hands-On