Lors d’une récente mise en place d’un projet basé sur Scala et Akka, la problématique de collecte de métriques s’est posée. Notre choix s’est porté sur la bibliothèque Metrics. Si vous souhaitez plus d’informations sur cette dernière, un article traitant le sujet se trouve à cette adresse.
De base, Metrics ne fournit pas de support pour Scala. Nous vous proposons une configuration afin de pouvoir l’intégrer facilement.
1. Configuration de Metrics avec Scala
Metrics ne fournit pas d’intégration avec Scala. Heureusement pour nous, une bibliothèque propose une API Scala.
Voici la configuration SBT :
libraryDependencies += "nl.grons" %% "metrics-scala" % "3.0.5_a2.3"
et Maven :
<dependency> <groupId>nl.grons</groupId> <artifactId>metrics-scala_2.10</artifactId> <version>3.0.5_a2.3</version> </dependency>
Petite précision : cette version est prévue pour les versions 2.10 de Scala et 2.3.0 de Akka. Pour les versions précédentes, un tableau est disponible sur le github du projet.
Nous devons ensuite déclarer un MetricRegistry :
object MetricsConfig { val metricRegistry = new MetricRegistry() metricRegistry.register("jvm.buffers", new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer())) metricRegistry.register("jvm.gc", new GarbageCollectorMetricSet()) metricRegistry.register("jvm.memory", new MemoryUsageGaugeSet()) metricRegistry.register("jvm.threads", new ThreadStatesGaugeSet()) val graphite: Graphite = new Graphite(new InetSocketAddress(graphiteHost, graphitePort)) def graphiteReporter = GraphiteReporter.forRegistry(metricRegistry) .prefixedWith(graphitePrefix) .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) .filter(MetricFilter.ALL) .build(graphite) .start(graphitePeriod, TimeUnit.MINUTES) } trait Instrumented extends InstrumentedBuilder { val metricRegistry = MetricsConfig.metricRegistry }
Dans cette configuration, nous envoyons les données collectées par Metrics dans Graphite. Nous devons aussi déclarer un trait utilisé par la bibliothèque avec notre metricRegistry.
2. Metrics avec les Actors
En premier lieu nous allons créer un acteur générique :
trait ActorNG extends Actor with ActorLogging { def receiveActor: Receive def receive: Receive = { case value => receiveActor(value) } }
Pour rappel, Actor.Receive est une fonction partielle d’où la mise en place de cette encapsulation dans le trait. Pour plus de détail sur les fonctions partielles, je vous invite à consulter cet article.
Ensuite nous écrivons notre acteur qui se charge d’appliquer un timer sur la fonction receive. Pour cela nous devons hériter des traits ActorNG et Instrumented précédemment définis :
trait MetricsActor extends ActorNG with Instrumented { override def receive: Receive = { case value => val loading = metrics.timer("message-actor") loading.time { super.receive(value) } } }
Selon les besoins, nous pouvons ajouter d’autres métriques (counter, gauge, histogram, etc.).
Dorénavant lorsque nous utilisons les acteurs, nous devons hériter du trait MetricsActor et redéfinir la fonction receiveActor :
class MyActor extends MetricsActor { override def receiveActor: Receive = { // Your treatment } }
Avec cette solution tous vos acteurs vous remonteront les métriques qui feront la joie des équipes de développement et de production.