Export JMX statistics via JSON REST endpoints using Jolokia.

This commit is contained in:
Mike Hearn 2016-03-09 15:43:50 +01:00
parent 975d569e55
commit a03352e6cd
7 changed files with 81 additions and 11 deletions

View File

@ -94,7 +94,9 @@ dependencies {
// Web stuff: for HTTP[S] servlets
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
compile "javax.servlet:javax.servlet-api:3.1.0"
compile "org.jolokia:jolokia-agent-war:2.0.0-M1"
compile "commons-fileupload:commons-fileupload:1.3.1"
// Unit testing helpers.

View File

@ -4,6 +4,34 @@ Node administration
When a node is running, it exposes an embedded web server that lets you monitor it, upload and download attachments,
access a REST API and so on.
Monitoring your node
--------------------
Like most Java servers, the node exports various useful metrics and management operations via the industry-standard
`JMX infrastructure <https://en.wikipedia.org/wiki/Java_Management_Extensions>`_. JMX is a standard _in-process_ API
for registering so-called _MBeans_ ... objects whose properties and methods are intended for server management. It does
not require any particular network protocol for export. So this data can be exported from the node in various ways:
some monitoring systems provide a "Java Agent", which is essentially a JVM plugin that finds all the MBeans and sends
them out to a statistics collector over the network. For those systems, follow the instructions provided by the vendor.
Sometimes though, you just want raw access to the data and operations itself. So nodes export them over HTTP on the
`/monitoring/json` HTTP endpoint, using a program called `Jolokia <https://jolokia.org/>`_. Jolokia defines the JSON
and REST formats for accessing MBeans, and provides client libraries to work with that protocol as well.
Here are a few ways to build dashboards and extract monitoring data for a node:
* `JMX2Graphite <https://github.com/logzio/jmx2graphite>`_ is a tool that can be pointed to /monitoring/json and will
scrape the statistics found there, then insert them into the Graphite monitoring tool on a regular basis. It runs
in Docker and can be started with a single command.
* `JMXTrans <https://github.com/jmxtrans/jmxtrans>`_ is another tool for Graphite, this time, it's got its own agent
(JVM plugin) which reads a custom config file and exports only the named data. It's more configurable than
JMX2Graphite and doesn't require a separate process, as the JVM will write directly to Graphite.
* *Java Mission Control* is a desktop app that can connect to a target JVM that has the right command line flags set
(or always, if running locally). You can explore what data is available, create graphs of those metrics, and invoke
management operations like forcing a garbage collection.
* Cloud metrics services like New Relic also understand JMX, typically, by providing their own agent that uploads the
data to their service on a regular schedule.
Uploading and downloading attachments
-------------------------------------

View File

@ -16,7 +16,9 @@ import core.node.servlets.AttachmentDownloadServlet
import core.node.servlets.DataUploadServlet
import core.utilities.loggerFor
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.webapp.WebAppContext
import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.nio.channels.FileLock
@ -58,11 +60,28 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration
private fun initWebServer(): Server {
val port = p2pAddr.port + 1 // TODO: Move this into the node config file.
val server = Server(port)
val handler = ServletContextHandler()
handler.setAttribute("node", this)
handler.addServlet(DataUploadServlet::class.java, "/upload/*")
handler.addServlet(AttachmentDownloadServlet::class.java, "/attachments/*")
server.handler = handler
val handlerCollection = HandlerCollection()
// Export JMX monitoring statistics and data over REST/JSON.
if (configuration.exportJMXto.split(',').contains("http")) {
handlerCollection.addHandler(WebAppContext().apply {
// Find the jolokia WAR file on the classpath.
contextPath = "/monitoring/json"
val classpath = System.getProperty("java.class.path").split(System.getProperty("path.separator"))
war = classpath.first { it.contains("jolokia-agent-war-2") && it.endsWith(".war") }
})
}
// Data upload and download to services (attachments, rates oracles etc)
handlerCollection.addHandler(ServletContextHandler().apply {
contextPath = "/"
setAttribute("storage", storage)
addServlet(DataUploadServlet::class.java, "/upload/*")
addServlet(AttachmentDownloadServlet::class.java, "/attachments/*")
})
server.handler = handlerCollection
server.start()
return server
}

View File

@ -9,9 +9,26 @@
package core.node
import java.util.*
import kotlin.reflect.declaredMemberProperties
interface NodeConfiguration {
val myLegalName: String
val exportJMXto: String
}
object DefaultConfiguration : NodeConfiguration {
override val myLegalName: String = "Vast Global MegaCorp"
override val exportJMXto: String = "" // can be "http" or empty
fun toProperties(): Properties {
val settings = DefaultConfiguration::class.declaredMemberProperties.map { it.name to it.get(this@DefaultConfiguration).toString() }
val p = Properties().apply {
for (setting in settings) {
setProperty(setting.first, setting.second)
}
}
return p
}
}
/**
@ -22,5 +39,6 @@ interface NodeConfiguration {
* editing the file is a must-have.
*/
class NodeConfigurationFromProperties(private val properties: Properties) : NodeConfiguration {
override val myLegalName: String by properties
}
override val myLegalName: String get() = properties.getProperty("myLegalName")
override val exportJMXto: String get() = properties.getProperty("exportJMXto")
}

View File

@ -70,6 +70,7 @@ fun main(args: Array<String>) {
val myNetAddr = ArtemisMessagingService.toHostAndPort(options.valueOf(networkAddressArg))
val config = object : NodeConfiguration {
override val myLegalName: String = "Rate fix demo node"
override val exportJMXto: String = "http"
}
val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, null).start() }

View File

@ -17,6 +17,7 @@ import core.crypto.SecureHash
import core.crypto.generateKeyPair
import core.messaging.LegallyIdentifiableNode
import core.messaging.SingleMessageRecipient
import core.node.DefaultConfiguration
import core.node.Node
import core.node.NodeConfiguration
import core.node.NodeConfigurationFromProperties
@ -299,12 +300,12 @@ private fun loadConfigFile(configFile: Path): NodeConfiguration {
askAdminToEditConfig(configFile)
}
val configProps = configFile.toFile().reader().use {
Properties().apply { load(it) }
val config = configFile.toFile().reader().use {
NodeConfigurationFromProperties(
Properties(DefaultConfiguration.toProperties()).apply { load(it) }
)
}
val config = NodeConfigurationFromProperties(configProps)
// Make sure admin did actually edit at least the legal name.
if (config.myLegalName == defaultLegalName)
askAdminToEditConfig(configFile)

View File

@ -95,6 +95,7 @@ class MockNetwork(private val threadPerNode: Boolean = false) {
Files.createDirectories(path.resolve("attachments"))
val config = object : NodeConfiguration {
override val myLegalName: String = "Mock Company $id"
override val exportJMXto: String = ""
}
val fac = factory ?: { p, n, n2, l -> MockNode(p, n, n2, l, id) }
val node = fac(path, config, this, withTimestamper).start()