java bindings: GenericJMX: This first prototype version seems to do something.
[collectd.git] / bindings / java / org / collectd / java / GenericJMXConfValue.java
1 /*
2  * collectd/java - org/collectd/java/GenericJMXConfValue.java
3  * Copyright (C) 2009  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  */
21
22 package org.collectd.java;
23
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.Iterator;
28 import java.util.ArrayList;
29
30 import javax.management.MBeanServerConnection;
31 import javax.management.ObjectName;
32 import javax.management.openmbean.CompositeData;
33 import javax.management.openmbean.InvalidKeyException;
34
35 import org.collectd.api.Collectd;
36 import org.collectd.api.DataSet;
37 import org.collectd.api.DataSource;
38 import org.collectd.api.ValueList;
39 import org.collectd.api.PluginData;
40 import org.collectd.api.OConfigValue;
41 import org.collectd.api.OConfigItem;
42
43 /**
44  * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
45  *
46  * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
47  * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
48  * construct an object of this class.
49  *
50  * The object can then be asked to query data from JMX and dispatch it to
51  * collectd.
52  *
53  * @see GenericJMXConfMBean
54  */
55 class GenericJMXConfValue
56 {
57   private String ds_name;
58   private DataSet _ds;
59   private List<String> _attributes;
60   private String _instance_prefix;
61   private boolean _is_table;
62
63   private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
64   {
65     if (obj instanceof String)
66     {
67       String str = (String) obj;
68       
69       try
70       {
71         if (ds_type == DataSource.TYPE_GAUGE)
72           return (new Double (str));
73         else
74           return (new Long (str));
75       }
76       catch (NumberFormatException e)
77       {
78         return (null);
79       }
80     }
81     else if (obj instanceof Integer)
82     {
83       return (new Integer ((Integer) obj));
84     }
85     else if (obj instanceof Long)
86     {
87       return (new Long ((Long) obj));
88     }
89     else if (obj instanceof Double)
90     {
91       return (new Double ((Double) obj));
92     }
93
94     return (null);
95   } /* }}} Number genericObjectToNumber */
96
97   private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
98   {
99     List<Number> ret = new ArrayList<Number> ();
100     List<DataSource> dsrc = this._ds.getDataSources ();
101
102     assert (objects.size () == dsrc.size ());
103
104     for (int i = 0; i < objects.size (); i++)
105     {
106       Number n;
107
108       n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
109       if (n == null)
110         return (null);
111       ret.add (n);
112     }
113
114     return (ret);
115   } /* }}} List<Number> genericListToNumber */
116
117   private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
118       String key)
119   {
120     List<Object> objects = new ArrayList<Object> ();
121
122     for (int i = 0; i < cdlist.size (); i++)
123     {
124       CompositeData cd;
125       Object value;
126
127       cd = cdlist.get (i);
128       try
129       {
130         value = cd.get (key);
131       }
132       catch (InvalidKeyException e)
133       {
134         return (null);
135       }
136       objects.add (value);
137     }
138
139     return (genericListToNumber (objects));
140   } /* }}} List<Number> genericCompositeToNumber */
141
142   private void submitTable (List<Object> objects, ValueList vl) /* {{{ */
143   {
144     List<CompositeData> cdlist;
145     Set<String> keySet = null;
146     Iterator<String> keyIter;
147
148     cdlist = new ArrayList<CompositeData> ();
149     for (int i = 0; i < objects.size (); i++)
150     {
151       Object obj;
152
153       obj = objects.get (i);
154       if (obj instanceof CompositeData)
155       {
156         CompositeData cd;
157
158         cd = (CompositeData) obj;
159
160         if (i == 0)
161           keySet = cd.getCompositeType ().keySet ();
162
163         cdlist.add (cd);
164       }
165       else
166       {
167         Collectd.logError ("GenericJMXConfValue: At least one of the "
168             + "attributes was not of type `CompositeData', as required "
169             + "when table is set to `true'.");
170         return;
171       }
172     }
173
174     assert (keySet != null);
175
176     keyIter = keySet.iterator ();
177     while (keyIter.hasNext ())
178     {
179       String key;
180       List<Number> values;
181
182       key = keyIter.next ();
183       values = genericCompositeToNumber (cdlist, key);
184       if (values == null)
185       {
186         Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
187             + "numbers for key " + key + ". Most likely not all attributes "
188             + "have this key.");
189         continue;
190       }
191
192       if (this._instance_prefix == null)
193         vl.setTypeInstance (key);
194       else
195         vl.setTypeInstance (this._instance_prefix + key);
196       vl.setValues (values);
197
198       Collectd.dispatchValues (vl);
199     }
200   } /* }}} void submitTable */
201
202   private void submitScalar (List<Object> objects, ValueList vl) /* {{{ */
203   {
204     List<Number> values;
205
206     values = genericListToNumber (objects);
207     if (values == null)
208     {
209       Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
210           + "objects to numbers.");
211       return;
212     }
213
214     if (this._instance_prefix == null)
215       vl.setTypeInstance ("");
216     else
217       vl.setTypeInstance (this._instance_prefix);
218     vl.setValues (values);
219
220     Collectd.dispatchValues (vl);
221   } /* }}} void submitScalar */
222
223   private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
224       List<String> attrName)
225   {
226     String key;
227     Object value;
228
229     key = attrName.remove (0);
230
231     try
232     {
233       value = parent.get (key);
234     }
235     catch (InvalidKeyException e)
236     {
237       return (null);
238     }
239
240     if (attrName.size () == 0)
241     {
242       return (value);
243     }
244     else
245     {
246       if (value instanceof CompositeData)
247         return (queryAttributeRecursive ((CompositeData) value, attrName));
248       else
249         return (null);
250     }
251   } /* }}} queryAttributeRecursive */
252
253   private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
254       ObjectName objName, String attrName)
255   {
256     List<String> attrNameList;
257     String key;
258     Object value;
259     String[] attrNameArray;
260
261     attrNameList = new ArrayList<String> ();
262
263     attrNameArray = attrName.split ("\\.");
264     key = attrNameArray[0];
265     for (int i = 1; i < attrNameArray.length; i++)
266       attrNameList.add (attrNameArray[i]);
267
268     try
269     {
270       value = conn.getAttribute (objName, key);
271     }
272     catch (Exception e)
273     {
274       Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
275           + e);
276       return (null);
277     }
278
279     if (attrNameList.size () == 0)
280     {
281       return (value);
282     }
283     else
284     {
285       if (value instanceof CompositeData)
286         return (queryAttributeRecursive ((CompositeData) value, attrNameList));
287       else
288         return (null);
289     }
290   } /* }}} Object queryAttribute */
291
292   private String getConfigString (OConfigItem ci) /* {{{ */
293   {
294     List<OConfigValue> values;
295     OConfigValue v;
296
297     values = ci.getValues ();
298     if (values.size () != 1)
299     {
300       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
301           + " configuration option needs exactly one string argument.");
302       return (null);
303     }
304
305     v = values.get (0);
306     if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
307     {
308       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
309           + " configuration option needs exactly one string argument.");
310       return (null);
311     }
312
313     return (v.getString ());
314   } /* }}} String getConfigString */
315
316   private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
317   {
318     List<OConfigValue> values;
319     OConfigValue v;
320     Boolean b;
321
322     values = ci.getValues ();
323     if (values.size () != 1)
324     {
325       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
326           + " configuration option needs exactly one boolean argument.");
327       return (null);
328     }
329
330     v = values.get (0);
331     if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
332     {
333       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
334           + " configuration option needs exactly one boolean argument.");
335       return (null);
336     }
337
338     return (new Boolean (v.getBoolean ()));
339   } /* }}} String getConfigBoolean */
340
341   /**
342    * Constructs a new value with the configured properties.
343    */
344   public GenericJMXConfValue (OConfigItem ci) /* {{{ */
345     throws IllegalArgumentException
346   {
347     List<OConfigItem> children;
348     Iterator<OConfigItem> iter;
349
350     this.ds_name = null;
351     this._ds = null;
352     this._attributes = new ArrayList<String> ();
353     this._instance_prefix = null;
354     this._is_table = false;
355
356     /*
357      * <Value>
358      *   Type "memory"
359      *   Table true|false
360      *   Attribute "HeapMemoryUsage"
361      *   Attribute "..."
362      *   :
363      *   # Type instance:
364      *   InstancePrefix "heap-"
365      * </Value>
366      */
367     children = ci.getChildren ();
368     iter = children.iterator ();
369     while (iter.hasNext ())
370     {
371       OConfigItem child = iter.next ();
372
373       if (child.getKey ().equalsIgnoreCase ("Type"))
374       {
375         String tmp = getConfigString (child);
376         if (tmp != null)
377           this.ds_name = tmp;
378       }
379       else if (child.getKey ().equalsIgnoreCase ("Table"))
380       {
381         Boolean tmp = getConfigBoolean (child);
382         if (tmp != null)
383           this._is_table = tmp.booleanValue ();
384       }
385       else if (child.getKey ().equalsIgnoreCase ("Attribute"))
386       {
387         String tmp = getConfigString (child);
388         if (tmp != null)
389           this._attributes.add (tmp);
390       }
391       else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
392       {
393         String tmp = getConfigString (child);
394         if (tmp != null)
395           this._instance_prefix = tmp;
396       }
397       else
398         throw (new IllegalArgumentException ("Unknown option: "
399               + child.getKey ()));
400     }
401
402     if (this.ds_name == null)
403       throw (new IllegalArgumentException ("No data set was defined."));
404     else if (this._attributes.size () == 0)
405       throw (new IllegalArgumentException ("No attribute was defined."));
406   } /* }}} GenericJMXConfValue (OConfigItem ci) */
407
408   /**
409    * Query values via JMX according to the object's configuration and dispatch
410    * them to collectd.
411    *
412    * @param conn Connection to the MBeanServer.
413    * @param objName Object name of the MBean to query.
414    * @param pd      Preset naming components. The members host, plugin and
415    *                plugin instance will be used.
416    */
417   public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
418       PluginData pd)
419   {
420     ValueList vl;
421     List<DataSource> dsrc;
422     List<Object> values;
423
424     if (this._ds == null)
425     {
426       this._ds = Collectd.getDS (this.ds_name);
427       if (this._ds == null)
428       {
429         Collectd.logError ("GenericJMXConfValue: Unknown type: "
430             + this.ds_name);
431         return;
432       }
433     }
434
435     dsrc = this._ds.getDataSources ();
436     if (dsrc.size () != this._attributes.size ())
437     {
438       Collectd.logError ("GenericJMXConfValue.query: The data set "
439           + ds_name + " has " + this._ds.getDataSources ().size ()
440           + " data sources, but there were " + this._attributes.size ()
441           + " attributes configured. This doesn't match!");
442       this._ds = null;
443       return;
444     }
445
446     vl = new ValueList (pd);
447     vl.setType (this.ds_name);
448     vl.setTypeInstance (this._instance_prefix);
449
450     values = new ArrayList<Object> ();
451
452     assert (dsrc.size () == this._attributes.size ());
453     for (int i = 0; i < this._attributes.size (); i++)
454     {
455       Object v;
456
457       v = queryAttribute (conn, objName, this._attributes.get (i));
458       if (v == null)
459       {
460         Collectd.logError ("GenericJMXConfValue.query: "
461             + "Querying attribute " + this._attributes.get (i) + " failed.");
462         return;
463       }
464
465       values.add (v);
466     }
467
468     if (this._is_table)
469       submitTable (values, vl);
470     else
471       submitScalar (values, vl);
472   } /* }}} void query */
473 }
474
475 /* vim: set sw=2 sts=2 et fdm=marker : */