Showing posts with label jfreechart. Show all posts
Showing posts with label jfreechart. Show all posts

Tuesday, February 16, 2010

A Groovy DSL - JMX Reporting

In previous blog example, using JFreeChart, JMX and SwingBuilder we came up with dashboard type utility. With this post we will explore how to write small DSL which will allow to report on JMX using reporting charts. The DSL will using Groovy's SwingBuilder to draw JFree Chart to report on various MBeans. This type DSL can be used to write small scripts to monitor behavioral aspect of application server (or any JMX based application).

Simple DSL
First we will chart out how we want our domain language to look like. DSL syntax can be done multiple ways. It is necessary that you play with the syntax to come up with one that works for the scenario. For our example, here is the first cut that we will use.

Essentially, the script reports processing time of all web modules defined on (in this example - JBoss) application server using Bar Chart.

jmx {
    server "service:jmx:rmi://localhost/jndi/rmi://localhost:1090/jmxconnector" {
      query "jboss.web:*" {
        findAll "j2eeType=WebModule" {
          chart{
            chartType="Bar"
            attributes={m-> [m.processingTime, m.path]}
            labels=["Time per Webapp", "Webapp", "Time"]
            options=[false, true, true]
            windowTitle="JBoss Module Processing Time"
            width=1200
            height=700
            refreshRate=5000
            show()
          }
        }
      }
    }
  }

Other Use cases
Target users for this DSL are system administrators who can write simple scripts to monitor and/or report on various aspects of app server instances graphically.

Some other use cases for this type of DSL
  • Compare Processing Time of Web Application
  • Compare Load Time of Servlets
  • Compare Response Time
  • Compare memory usage
  • Compare total requests
DSL Engine
With Groovy there are multiple ways to write domain specific language as described in groovy documentation here

We will use nested closure approach. More information about nested closure here.

Here we use ExpandoMetaClass and series of Delegate classes to handle each closure. In the sample script above read each node (eg. jmx, server, query) as a dynamic method call with one or two arguments. For example jmx has a string argument followed by closure argument. String argument is we just store in instance variable. For closure argument we delegate the handling to separate Delegate Class. We follow the same pattern for the rest of the nested nodes.

First up Engine
This is the main class for the DSL. It gets the file passed as command args and pass it to GroovyShell to create Script object. It uses ExpandoMetaClass to dynamically add methods/closure. It add adds jmx closure and sets properties for handling closure.

class JmxReportingDslEngine {
 
  static main(String[] args){
    if(args.length != 1)
    {
      println("Usage: JmxReportingDslEngine <ScriptFileName>")
    }
    runEngine(new File(args[0]))
  }
 
  static void runEngine(File dsl){
    Script dslScript = new GroovyShell().parse(dsl.text)
    dslScript.metaClass = createExpandoMetaClass(dslScript.class, {
      ExpandoMetaClass emc ->
        emc.jmx = {
          Closure cl ->
            cl.delegate = new JmxClosureDelegate()
            cl.resolveStrategy = Closure.DELEGATE_FIRST
            cl()
        }
    })
    dslScript.run()
   }

  static ExpandoMetaClass createExpandoMetaClass(Class clazz, Closure cl){
    ExpandoMetaClass emc = new ExpandoMetaClass(clazz, false)
    cl(emc)
    emc.initialize()
    return emc
  }
}

Delegates


Next up we write series of Delegate Class responsible for handling each node of the language.

JMXClosureDelegate
This handles closure passed to jmxtag. It instantiates MBeanServerConnection and passes the reference down to the delegate chain.

class JmxClosureDelegate {
//To avoid using "," between String and Closure argument
  def methodMissing(String name, args) {
    return [name, args[0]]
  }
  void server(param){
    def (serverUrl, cl) = param
    def server = JMXConnectorFactory.connect(new JmxUrl(serverUrl),env).MBeanServerConnection
    cl.delegate = new JmxServerClosureDelegate(server)
    cl.resolveStrategy = Closure.DELEGATE_FIRST
    cl()
  }

JMXServerClosureDelegate

This delegate handles closure passed to server tag.

class JmxServerClosureDelegate {
  def server
  JmxServerClosureDelegate(server){
    this.server = server
  }
  def methodMissing(String name, args) {
   return [name, args[0]]
  }
  void query(param){
    def (objectName, cl) = param
    def query = new ObjectName(objectName)
    String[] allNames = server.queryNames(query, null)
    cl.delegate = new JmxQueryClosureDelegate(allNames, server)
    cl.resolveStrategy = Closure.DELEGATE_FIRST
    cl()
  }
}
JmxQueryClosureDelegate
and so on...

class JmxQueryClosureDelegate {
  def allNames
  def server
  JmxQueryClosureDelegate(allNames, server){
    this.allNames = allNames
    this.server = server
  }
  def methodMissing(String name, args) {
    return [name, args[0]]
  }
  void findAll(param){
    def (filter, cl) = param
    def modules = allNames.findAll{ name ->
          name.contains(filter)
      }.collect{ new GroovyMBean(server, it) }
    cl.delegate = new JmxFindAllClosureDelegate(modules)
    cl.resolveStrategy = Closure.DELEGATE_FIRST
    cl()
  }
}
JmxFindAllClosureDelegate

class JmxFindAllClosureDelegate {
  def modules
  JmxFindAllClosureDelegate(modules){
    this.modules = modules
  }
  void chart(Closure cl){
    cl.delegate = new ChartDelegate(modules)
    cl.resolveStrategy = Closure.DELEGATE_FIRST
    cl()
  }
}
ChartDelegate
This class
  • Creates the dataset from the MBean values
  • Creates the chart with the dataset values
  • Creates the external frame using SwingBuilder

class ChartDelegate {

  def modules
  def chartType
  def attributes
  def labels
  def options
  def windowTitle
  def width
  def height
  def refreshRate
  def orientation = "VERTICAL"
  def dataset
  ChartDelegate(modules){
    this.modules = modules
  }
  void show(){
    switch(chartType){
      case "Bar": drawBarChart(); break;
     //TODO:Add more chart types
      default: break;
    }
  }
  void drawBarChart(){
    calculateData()
    def chart = ChartFactory.createBarChart(*labels, dataset, Orientation."${orientation}", *options)
    def swing = new SwingBuilder()
    def frame = swing.frame(title:windowTitle, defaultCloseOperation:WC.EXIT_ON_CLOSE){
      panel(id:'canvas') {rigidArea(width:width, height:height)}
    }
    while(true){
      calculateData()
      chart.fireChartChanged()
      frame.pack()
      frame.show()
      chart.draw(swing.canvas.graphics, swing.canvas.bounds)
      sleep(refreshRate)
    }
  }
  void calculateData(){
    def newDataset = new DefaultCategoryDataset()
    modules.each{ m ->
      def dsCall = attributes.call(m)
      newDataset.addValue dsCall[0], 0, dsCall[1]
    }
    this.dataset = newDataset
  }
}
Output
Let's run and look at sample output...

web-app-processing-time

Click here for bigger images

Here is another script for Servlet Load Time

This one uses some intermediate groovy knowledge with attributes param taking a closure. It allows user to perform transformation on the MBean parameters. Below in the script it performs operates on long objectName attribute to get servlet name parameter.

jmx {
    server "service:jmx:rmi://localhost/jndi/rmi://localhost:1090/jmxconnector" {
      query "jboss.web:*" {
        findAll "j2eeType=Servlet" {
            chart{
              chartType="Bar"
              attributes={m-> [m.loadTime, m.objectName.find("name=([^,]*)"){it[1]}]}
              labels=["Load Time per Servlet", "Servlet", "Time"]
              options=[false, true, true]
              windowTitle="JBoss Servlet Processing Time"
              width=1200
              height=700
              orientation="HORIZONTAL"
              refreshRate=5000
              show()
            }
        }
      }
    }
}
and here is the output...

servlet-load-time

Click here for bigger image

Further you can,
  • Add different type of chart support to the DSL - Bar, XY, trending, etc
  • Use/Extend to work with different application server and/or application

Complete source code can be found at http://github.com/kartikshah/jmx-dsl
Blogged with the Flock Browser

Saturday, June 20, 2009

JMX, Groovy, JFreeChart and Swing

Recently, I worked on creating JMX based dashboard application showing Weblogic instances' information. During the exercise I came across JMX and Groovy examples

Using JMX, Groovy, Swing and JFreeChart, I came up with a dashboard application which looks like this.

Memory-Usage

It show memory usage on two instances. It automatically updates every 10 seconds and redraws chart.

Surprisingly (or not so surprisingly), code required to be written to perform this activity is minimal. Specifically thanks to Groovy's SwingBuilder and JMX libraries. Groovy makes the task of using 4 different technologies really easy and fun.

import org.jfree.chart.ChartFactory
import javax.swing.WindowConstants as WC
import javax.management.remote.*
import javax.naming.Context;

/**
* WeblogicJMXReporter monitors Memory value for given node
* @author kartik.shah
*/
public class WeblogicJMXReporter
{

static void main(String[] args)
{
def env = [:]
env["java.naming.factory.initial"] = "weblogic.jndi.WLInitialContextFactory"
env[Context.SECURITY_PRINCIPAL] = "system"
env[Context.SECURITY_CREDENTIALS] = "*******" //Password is always stars
env[JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES] = "weblogic.management.remote"
def jmxServiceURL = new JMXServiceURL("http", "10.50.120.110", 19400,"/jndi/weblogic.management.mbeanservers.domainruntime")
def server = JMXConnectorFactory.connect(jmxServiceURL, env).MBeanServerConnection

def chart1Map = getChart("node1_svr", server);
def piedata1 = chart1Map."pieData"
def chart1 = chart1Map."Chart"
def jvmInfo1 = chart1Map."MBean"

def chart2Map = getChart("node2_svr", server);
def piedata2 = chart2Map."pieData"
def chart2 = chart2Map."Chart"
def jvmInfo2 = chart2Map."MBean"

def swing = new groovy.swing.SwingBuilder()

def frame = swing.frame(title:'Weblogic INT Memory Usage', defaultCloseOperation:WC.EXIT_ON_CLOSE,
size:[800,600], locationRelativeTo: null) {
borderLayout()
panel(id:'canvas') { rigidArea(width:700, height:250) }
}

def bounds1 = new java.awt.Rectangle(0,0, 350,250).bounds
def bounds2 = new java.awt.Rectangle(351,0, 350, 250).bounds
while(true)
{
recalculatePieData(piedata1, jvmInfo1)
recalculatePieData(piedata2, jvmInfo2)

chart1.fireChartChanged()
chart2.fireChartChanged()

frame.pack()
frame.show()
chart1.draw(swing.canvas.graphics, bounds1)
chart2.draw(swing.canvas.graphics, bounds2)
sleep(10000)
}
}

static getChart(nodeName, server)
{
def jvmInfo = new GroovyMBean(server, 'com.bea:Location='+nodeName+',Name='+nodeName+',ServerRuntime='+nodeName+',Type=JVMRuntime')
def piedata = new org.jfree.data.general.DefaultPieDataset()
piedata.setValue "Free", jvmInfo.HeapFreeCurrent
piedata.setValue "Used", jvmInfo.HeapSizeMax - jvmInfo.HeapFreeCurrent
def options = [true, true, true]
def chart = ChartFactory.createPieChart(nodeName, piedata, *options)
chart.backgroundPaint = java.awt.Color.white

["MBean":jvmInfo, "pieData":piedata, "Chart":chart]
}

static recalculatePieData(piedata, jvmInfo)
{
piedata.setValue "Free", jvmInfo.HeapFreeCurrent
piedata.setValue "Used", jvmInfo.HeapSizeMax - jvmInfo.HeapFreeCurrent
println piedata.getValue("Free")
println piedata.getValue("Used")

}
}
For purpose of this blog and keeping it simple, I kept it all in one groovy class.
Blogged with the Flock Browser