All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xitrum.metrics.MetricsAction.scala Maven / Gradle / Ivy

There is a newer version: 3.28.18
Show newest version
package xitrum.metrics

import akka.actor.{Actor, ActorRef, Props, Terminated}
import akka.cluster.{Cluster, NodeMetrics}
import akka.cluster.ClusterEvent.{ClusterMetricsChanged, CurrentClusterState}
import akka.cluster.StandardMetrics.{Cpu, HeapMemory}

import io.netty.handler.codec.http.HttpResponseStatus
import glokka.Registry

import xitrum.{FutureAction, Config, SockJsAction, SockJsText}
import xitrum.annotation.{GET, Last, SOCKJS}
import xitrum.util.SeriDeseri
import xitrum.view.{DocType, Js}

/** JMX metrics collector for single node application. */
class MetricsCollector(localPublisher: ActorRef) extends Actor {
  def receive = {
    case Collect => localPublisher ! MetricsManager.jmx.sample()
    case _ =>
  }
}

/** JMX metrics collector for clustered node application. */
class ClusterMetricsCollector(localPublisher: ActorRef) extends Actor {
  override def preStart =
    Cluster(context.system).subscribe(self, classOf[ClusterMetricsChanged])

  override def postStop =
    Cluster(context.system).unsubscribe(self)

  def receive = {
    case m @ ClusterMetricsChanged(clusterMetrics) =>
      if (localPublisher != null) localPublisher ! m

    case UnSubscribe =>
      Cluster(context.system).unsubscribe(self)

    case _ =>
  }
}

/**
 * Example: XitrumMetricsChannel
 */
class MetricsPublisher extends Actor {
  private var clients                                = Seq[ActorRef]()
  private var lastPublished                          = System.currentTimeMillis()
  private var cachedNodeMetrics:    NodeMetrics      = _
  private var cachedClusterMetrics: Set[NodeMetrics] = _
  private var cachedRegistryAsJson: String           = _

  def receive = {
    case ClusterMetricsChanged(clusterMetrics) =>
      cachedClusterMetrics = clusterMetrics

    case nodeMetrics: NodeMetrics =>
      cachedNodeMetrics = nodeMetrics

    case Publish(registryAsJson) =>
      cachedRegistryAsJson = registryAsJson
      clients.foreach { client =>
        client ! Publish(registryAsJson)
        if (cachedNodeMetrics != null) client ! cachedNodeMetrics
        // Ignore if clusterd metricsManager sent duplicated requests
        if (System.currentTimeMillis - lastPublished > Config.xitrum.metrics.get.collectActorInterval.toMillis) {
          if (cachedClusterMetrics != null) client ! cachedClusterMetrics.toList
          lastPublished = System.currentTimeMillis
        }
      }

    case Pull =>
      sender ! Publish(MetricsManager.registryAsJson)
      if (cachedNodeMetrics != null)    sender ! cachedNodeMetrics
      if (cachedClusterMetrics != null) sender ! cachedClusterMetrics.toList

    case Subscribe =>
      clients = clients :+ sender
      context.watch(sender)

    case UnSubscribe =>
      clients =  clients.filterNot(_ == sender)
      context.unwatch(sender)

    case Terminated(client) =>
      clients = clients.filterNot(_ == client)

    case _ =>
  }
}

/** Javascript fragment for establish metrics JSON sockJS channel. */
trait MetricsViewer extends FutureAction {
  def jsAddMetricsNameSpace(namespace: String = null) {
    jsAddToView(s"""
(function (namespace) {
  var ns  = namespace || window;
  var url = '${sockJsUrl[XitrumMetricsChannel]}';
  var pullTimer;
  var initMetricsChannel = function(onMessageFunc) {
    var socket;
    socket = new SockJS(url);
    socket.onopen = function(event) {
      socket.send('${MetricsManager.metrics.apiKey}');
      pullTimer = setInterval(function() { socket.send('pull') }, 2000);
    };
    socket.onclose = function(e) {
      clearInterval(pullTimer);
      // Reconnect
      setTimeout(function() { initMetricsChannel(onMessageFunc) }, 2000);
    };
    socket.onmessage = function(e) { onMessageFunc(e.data); }
  };
  ns.initMetricsChannel = initMetricsChannel;
})($namespace);
"""
    )
  }
}

/**
 * Default metrics viewer page.
 * This page could be overwritten in user application with any style.
 */
@Last
@GET("xitrum/metrics/viewer")
class XitrumMetricsViewer extends FutureAction with MetricsViewer {
  beforeFilter {
    val apiKey  = param("api_key")
    if (apiKey != MetricsManager.metrics.apiKey) {
      response.setStatus(HttpResponseStatus.UNAUTHORIZED)
      respondHtml(

Unauthorized

) } } lazy val html = DocType.html5( {xitrumCss} {jsDefaults} Xitrum Default Metrics Viewer

Xitrum Default Metrics Viewer

NodeMetrics Status

NodeMetrics(HeapMemory)
TimeNodeCommitted(MB)Used(MB)Max(MB)

NodeMetrics(CPU)
TimeNodeProcessorsLoad Average

Application Metrics Status

Histograms
NodeKeyCountMin(ms)Max(ms)Mean(ms)
{jsForView} ) lazy val focusHtml = DocType.html5( {xitrumCss} {jsDefaults} Xitrum Default Metrics Viewer

{jsForView} ) def execute() { jsAddMetricsNameSpace("window") paramo("focusAction") match { case Some(key) => jsAddToView("initMetricsChannel(channelOnMessageWithKey('"+ key +"'));") respondHtml(focusHtml) case None => jsAddToView("initMetricsChannel(channelOnMessage);") respondHtml(html) } } } /** SockJS channel for metrics JSON. */ @SOCKJS("xitrum/metrics/channel") class XitrumMetricsChannel extends SockJsAction with PublisherLookUp { def execute() { checkAPIKey() } private def checkAPIKey() { context.become { case SockJsText(text) if (text == MetricsManager.metrics.apiKey) => lookUpPublisher() case SockJsText(key) => respondSockJsText("Wrong apikey") respondSockJsClose() case ignore => log.warn("Unexpected message: " + ignore) } } override def doWithPublisher(publisher: ActorRef) { publisher ! Subscribe context.watch(publisher) context.become { case msg @ (first::rest) => msg.foreach { nodeMetrics => sendHeapMemory(nodeMetrics.asInstanceOf[NodeMetrics]) sendCpu(nodeMetrics.asInstanceOf[NodeMetrics]) } case nodeMetrics: NodeMetrics => sendHeapMemory(nodeMetrics) sendCpu(nodeMetrics) case Publish(registryAsJson) => respondSockJsText(registryAsJson) case SockJsText(text) => if (text == "pull" ) publisher ! Pull case Terminated(publisher) => lookUpPublisher() case _ => } } private def sendHeapMemory(nodeMetrics:NodeMetrics) { nodeMetrics match { case HeapMemory(address, timestamp, used, committed, max) => respondSockJsText(SeriDeseri.toJson(Map( "TYPE" -> "heapMemory", "SYSTEM" -> address.system, "HOST" -> address.host, "PORT" -> address.port, "HASH" -> address.hashCode, "TIMESTAMP" -> timestamp, "USED" -> used, "COMMITTED" -> committed, "MAX" -> max ))) } } private def sendCpu(nodeMetrics:NodeMetrics):Unit = { nodeMetrics match { case Cpu(address, timestamp, Some(systemLoadAverage), cpuCombined, processors) => respondSockJsText(SeriDeseri.toJson(Map( "TYPE" -> "cpu", "SYSTEM" -> address.system, "HOST" -> address.host, "PORT" -> address.port, "HASH" -> address.hashCode, "TIMESTAMP" -> timestamp, "SYSTEMLOADAVERAGE" -> systemLoadAverage, "CPUCOMBINED" -> cpuCombined, "PROCESSORS" -> processors ))) } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy