All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
xitrum.metrics.MetricsAction.scala Maven / Gradle / Ivy
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
{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
)))
}
}
}