torsdag 4 oktober 2012

JMX over HTTP with Jolokia and javascript

Using a combination of jolokia and jquery (with jstree) it is quite easy to create a jconsole like M-Bean handler application in javascript.

The basic idea is to search all M-Beans using jolokia and to create a navigation tree using jstree. When selecting a leaf node the corresponding M-Bean attributes and operations are presented.

Implementation

The AgenServlet from jolokia was added to a servletcontainer (tomcat) in web.xml

<servlet>
      <servlet-name>jolokia</servlet-name>
      <servlet-class>org.jolokia.http.AgentServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
      <servlet-name> jolokia </servlet-name>
      <url-pattern>/jolokia/*</url-pattern>
</servlet-mapping>

By using the javascipt API for jolokia to search for M-Beans it is possible to present a jconsole like tree structure using jstree.

NOTE: the keyorder:"constructionTime" is a patch to jolokia making it possible to get the M-Bean keys in construction order instead of cannonical. This feature makes the tree look like jconsole but is currently not available in jolokia (just omit it).

Disclaimer: I'm obviously not a javascript developer, sorry for the coding style :)

<div id="nav" class="jstree"></div>
<script type="text/javascript">
function extractDomainMap(mbeans) {
   var domains = {};
   for (var i = 0; i < mbeans.length; i++) {
      var mbean = mbeans[i];
      var domainEnd = mbean.indexOf(":");
      var domainName = mbean.substring(0, domainEnd);
      var domain = domains[domainName];
      if (domain == null) {
         domain = {"name": domainName, "entries": {}};
         domains[domainName] = domain;
      }
      var container = domain.entries;
      var tokens = mbean.substring(domainEnd + 1).split(",");
      for (var ii = 0; ii < tokens.length; ii++) {
         var token = tokens[ii];
         var typeEnd = token.indexOf("=");
         var name = token.substring(typeEnd + 1);
         var item = container[name];
         if (item == null) {
            item = {"name": name, "entries": []};
            container[name] = item;
         }
         if (ii == (tokens.length - 1)) {
            item.mbean = mbean;
         }
         container = item.entries;
      }
   }
   return domains;
}

function createJsTreeDataRecursive(entries, children) {
   var i = 0;
   for (var itemName in entries) {
      if (entries.hasOwnProperty(itemName)) {
         var item = entries[itemName];
         var node = {"data": item.name, "children": []};
         if (item.mbean != null) {
            node.metadata = {"mbean": item.mbean};
         }
         children[i] = node;
         createJsTreeDataRecursive(item.entries, node.children);
         i++;
      }
   }
}

function createJsTreeData(domains) {
   var data = {"data" : []};
   var i = 0;
   for (var domainName in domains) {
      if (domains.hasOwnProperty(domainName)) {
         var domain = domains[domainName];
         var node = {"data": domain.name, "children": []};
         data.data[i] = node;
         createJsTreeDataRecursive(domain.entries, node.children);
         i++;
      }
   }
   return data;
}

function searchAll() {
   var response = new Jolokia({"url": "jolokia"}).request(
     {type: "search", mbean:"*:*", keyorder:"constructionTime"}, 
     {method: "post"});
   response.value.sort();
   return createJsTreeData(extractDomainMap(response.value));
}

var data = searchAll();
$("#nav")
   .jstree ({
      "json_data" : data,
      "themes" : {
         "theme" : "classic",
         "dots" : false
      },
      "core" : {
         "animation" : 50
      },
         "plugins" : ["themes", "classic", "json_data","ui"]
   })
   .bind("select_node.jstree", function (event, data) {
      var mbean = data.rslt.obj.data("mbean");
      if (mbean != null) {
         listMbean(mbean);
      }
   });
</script>

The listMBean function is invoked when an leaf node is selected. It is quite easy to create a jquery tablesorter of all the attributes, operations and notifications using for example the cool javascript template framework handlebars. Here is the code that creates a table of the M-Bean attributes

<div id="attributes"></div>
<script id="attributes-template" type="text/x-handlebars-template">
<table id="attributes-table" class="tablesorter">
   <thead>
      <th>Name</th>
      <th>Value</th>
   </thead>
   <tbody>
   {{#attributes}}
      <tr>
         <td>{{name}}</td>
         <td id='{{name}}'>
         {{#if value.rw}}
            <input class="attr-input" id='attr-input-{{name}}' name='{{name}}' type='text' value=''>
            <input class="attr-submit" id='attr-submit-{{name}}' name='Save' type='submit' value='Save' onclick="updateAttribute('{{name}}')">
         {{/if}}
         </td>
      </tr>
   {{/attributes}}
   </tbody>
</table>
</script>

<script type="text/javascript">
var currentMbean;
function listMbean(mbean) {
   var path = mbean.replace(new RegExp("/", 'g'), "!/");
   path = path.replace(":", "/");
   var meta = new Jolokia({"url": "jolokia"}).request(
     {"type": "list", "path": path}, 
     {method: "post"});
   currentMbean = mbean;
   currentMeta = meta;
   $("#mbeanInfo").html("<h2 id='mbeanName'>" + mbean + "</h2><p>" + meta.value.desc + "</p>");
   var attributes = { 'attributes' : [] };
   for (var attr in meta.value.attr) {
      if (meta.value.attr.hasOwnProperty(attr)) {
         attributes['attributes'].push({
            'name' : attr,
            'value' : meta.value.attr[attr]
         });
      }
   }
   var source = $("#attributes-template").html();
   var template = Handlebars.compile(source);
   $("#attributes").html(template(attributes));
   $("#attributes-table").tablesorter({widgets: ['zebra']});
   // inject the values
   var values = new Jolokia({"url": "jolokia"}).request(
     {"type": "read", "mbean": mbean}, 
     {method: "post"});
   for (var attr in values.value) {
      if (values.value.hasOwnProperty(attr)) {
         var value = values.value[attr];
         if (meta.value.attr[attr].rw) {
            $("#attr-input-" + attr).val(value);
         } else {
            $("#" + attr).html(value);
         }
      }
   }
}


function updateAttribute(name) {
   var mbean = currentMbean;
   var value = $("#attr-input-" + name).val();
    new Jolokia({"url": "jolokia"}).request(
     {"type": "write", "mbean": mbean, "attribute": name, "value": value}, 
     {method: "post"});
   listMbean(mbean);
}
</script>

Similar tables can be created for the M-Bean operations and notifications.



4 kommentarer:

  1. After some experiments with Jolokia in the browser I also wanted to "port" jconsole to JavaScript.

    I'm glad someone did it already with such great results!

    SvaraRadera
    Svar
    1. The javascript libraries make to easy. What you end up doing is just converting the data from Jolokia to match the required structure.

      Radera
  2. Any chance you can provide a download link for this entire example? This would make a great starting point for me. I would really appreciate it.

    SvaraRadera
  3. Especially helpful would be what pictured in the screenshot - the ability to execute operations of the MBean.

    SvaraRadera