java bindings: GenericJMX plugin: Implement the ‘InstanceFrom’ option for <value...
[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 java.math.BigDecimal;
31 import java.math.BigInteger;
32
33 import javax.management.MBeanServerConnection;
34 import javax.management.ObjectName;
35 import javax.management.openmbean.OpenType;
36 import javax.management.openmbean.CompositeData;
37 import javax.management.openmbean.InvalidKeyException;
38
39 import org.collectd.api.Collectd;
40 import org.collectd.api.DataSet;
41 import org.collectd.api.DataSource;
42 import org.collectd.api.ValueList;
43 import org.collectd.api.PluginData;
44 import org.collectd.api.OConfigValue;
45 import org.collectd.api.OConfigItem;
46
47 /**
48  * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
49  *
50  * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
51  * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
52  * construct an object of this class.
53  *
54  * The object can then be asked to query data from JMX and dispatch it to
55  * collectd.
56  *
57  * @see GenericJMXConfMBean
58  */
59 class GenericJMXConfValue
60 {
61   private String _ds_name;
62   private DataSet _ds;
63   private List<String> _attributes;
64   private String _instance_prefix;
65   private List<String> _instance_from;
66   private boolean _is_table;
67
68   /**
69    * Converts a generic (OpenType) object to a number.
70    *
71    * Returns null if a conversion is not possible or not implemented.
72    */
73   private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
74   {
75     if (obj instanceof String)
76     {
77       String str = (String) obj;
78       
79       try
80       {
81         if (ds_type == DataSource.TYPE_GAUGE)
82           return (new Double (str));
83         else
84           return (new Long (str));
85       }
86       catch (NumberFormatException e)
87       {
88         return (null);
89       }
90     }
91     else if (obj instanceof Byte)
92     {
93       return (new Byte ((Byte) obj));
94     }
95     else if (obj instanceof Short)
96     {
97       return (new Short ((Short) obj));
98     }
99     else if (obj instanceof Integer)
100     {
101       return (new Integer ((Integer) obj));
102     }
103     else if (obj instanceof Long)
104     {
105       return (new Long ((Long) obj));
106     }
107     else if (obj instanceof Float)
108     {
109       return (new Float ((Float) obj));
110     }
111     else if (obj instanceof Double)
112     {
113       return (new Double ((Double) obj));
114     }
115     else if (obj instanceof BigDecimal)
116     {
117       return (BigDecimal.ZERO.add ((BigDecimal) obj));
118     }
119     else if (obj instanceof BigInteger)
120     {
121       return (BigInteger.ZERO.add ((BigInteger) obj));
122     }
123
124     return (null);
125   } /* }}} Number genericObjectToNumber */
126
127   /**
128    * Converts a generic list to a list of numbers.
129    *
130    * Returns null if one or more objects could not be converted.
131    */
132   private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
133   {
134     List<Number> ret = new ArrayList<Number> ();
135     List<DataSource> dsrc = this._ds.getDataSources ();
136
137     assert (objects.size () == dsrc.size ());
138
139     for (int i = 0; i < objects.size (); i++)
140     {
141       Number n;
142
143       n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
144       if (n == null)
145         return (null);
146       ret.add (n);
147     }
148
149     return (ret);
150   } /* }}} List<Number> genericListToNumber */
151
152   /**
153    * Converts a list of CompositeData to a list of numbers.
154    *
155    * From each <em>CompositeData </em> the key <em>key</em> is received and all
156    * those values are converted to a number. If one of the
157    * <em>CompositeData</em> doesn't have the specified key or one returned
158    * object cannot converted to a number then the function will return null.
159    */
160   private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
161       String key)
162   {
163     List<Object> objects = new ArrayList<Object> ();
164
165     for (int i = 0; i < cdlist.size (); i++)
166     {
167       CompositeData cd;
168       Object value;
169
170       cd = cdlist.get (i);
171       try
172       {
173         value = cd.get (key);
174       }
175       catch (InvalidKeyException e)
176       {
177         return (null);
178       }
179       objects.add (value);
180     }
181
182     return (genericListToNumber (objects));
183   } /* }}} List<Number> genericCompositeToNumber */
184
185   private void submitTable (List<Object> objects, ValueList vl, /* {{{ */
186       String instancePrefix)
187   {
188     List<CompositeData> cdlist;
189     Set<String> keySet = null;
190     Iterator<String> keyIter;
191
192     cdlist = new ArrayList<CompositeData> ();
193     for (int i = 0; i < objects.size (); i++)
194     {
195       Object obj;
196
197       obj = objects.get (i);
198       if (obj instanceof CompositeData)
199       {
200         CompositeData cd;
201
202         cd = (CompositeData) obj;
203
204         if (i == 0)
205           keySet = cd.getCompositeType ().keySet ();
206
207         cdlist.add (cd);
208       }
209       else
210       {
211         Collectd.logError ("GenericJMXConfValue: At least one of the "
212             + "attributes was not of type `CompositeData', as required "
213             + "when table is set to `true'.");
214         return;
215       }
216     }
217
218     assert (keySet != null);
219
220     keyIter = keySet.iterator ();
221     while (keyIter.hasNext ())
222     {
223       String key;
224       List<Number> values;
225
226       key = keyIter.next ();
227       values = genericCompositeToNumber (cdlist, key);
228       if (values == null)
229       {
230         Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
231             + "numbers for key " + key + ". Most likely not all attributes "
232             + "have this key.");
233         continue;
234       }
235
236       if (instancePrefix == null)
237         vl.setTypeInstance (key);
238       else
239         vl.setTypeInstance (instancePrefix + key);
240       vl.setValues (values);
241
242       Collectd.dispatchValues (vl);
243     }
244   } /* }}} void submitTable */
245
246   private void submitScalar (List<Object> objects, ValueList vl, /* {{{ */
247       String instancePrefix)
248   {
249     List<Number> values;
250
251     values = genericListToNumber (objects);
252     if (values == null)
253     {
254       Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
255           + "objects to numbers.");
256       return;
257     }
258
259     if (instancePrefix == null)
260       vl.setTypeInstance ("");
261     else
262       vl.setTypeInstance (instancePrefix);
263     vl.setValues (values);
264
265     Collectd.dispatchValues (vl);
266   } /* }}} void submitScalar */
267
268   private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
269       List<String> attrName)
270   {
271     String key;
272     Object value;
273
274     key = attrName.remove (0);
275
276     try
277     {
278       value = parent.get (key);
279     }
280     catch (InvalidKeyException e)
281     {
282       return (null);
283     }
284
285     if (attrName.size () == 0)
286     {
287       return (value);
288     }
289     else
290     {
291       if (value instanceof CompositeData)
292         return (queryAttributeRecursive ((CompositeData) value, attrName));
293       else
294         return (null);
295     }
296   } /* }}} queryAttributeRecursive */
297
298   private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
299       ObjectName objName, String attrName)
300   {
301     List<String> attrNameList;
302     String key;
303     Object value;
304     String[] attrNameArray;
305
306     attrNameList = new ArrayList<String> ();
307
308     attrNameArray = attrName.split ("\\.");
309     key = attrNameArray[0];
310     for (int i = 1; i < attrNameArray.length; i++)
311       attrNameList.add (attrNameArray[i]);
312
313     try
314     {
315       value = conn.getAttribute (objName, key);
316     }
317     catch (Exception e)
318     {
319       Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
320           + e);
321       return (null);
322     }
323
324     if (attrNameList.size () == 0)
325     {
326       return (value);
327     }
328     else
329     {
330       if (value instanceof CompositeData)
331         return (queryAttributeRecursive((CompositeData) value, attrNameList));
332       else if (value instanceof OpenType)
333       {
334         OpenType ot = (OpenType) value;
335         Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
336             + ot.getTypeName () + "\" is not yet implemented.");
337         return (null);
338       }
339       else
340       {
341         Collectd.logError ("GenericJMXConfValue: Received object of "
342             + "unknown class.");
343         return (null);
344       }
345     }
346   } /* }}} Object queryAttribute */
347
348   private String join (String separator, List<String> list) /* {{{ */
349   {
350     StringBuffer sb;
351
352     sb = new StringBuffer ();
353
354     for (int i = 0; i < list.size (); i++)
355     {
356       if (i > 0)
357         sb.append ("-");
358       sb.append (list.get (i));
359     }
360
361     return (sb.toString ());
362   } /* }}} String join */
363
364   private String getConfigString (OConfigItem ci) /* {{{ */
365   {
366     List<OConfigValue> values;
367     OConfigValue v;
368
369     values = ci.getValues ();
370     if (values.size () != 1)
371     {
372       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
373           + " configuration option needs exactly one string argument.");
374       return (null);
375     }
376
377     v = values.get (0);
378     if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
379     {
380       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
381           + " configuration option needs exactly one string argument.");
382       return (null);
383     }
384
385     return (v.getString ());
386   } /* }}} String getConfigString */
387
388   private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
389   {
390     List<OConfigValue> values;
391     OConfigValue v;
392     Boolean b;
393
394     values = ci.getValues ();
395     if (values.size () != 1)
396     {
397       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
398           + " configuration option needs exactly one boolean argument.");
399       return (null);
400     }
401
402     v = values.get (0);
403     if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
404     {
405       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
406           + " configuration option needs exactly one boolean argument.");
407       return (null);
408     }
409
410     return (new Boolean (v.getBoolean ()));
411   } /* }}} String getConfigBoolean */
412
413   /**
414    * Constructs a new value with the configured properties.
415    */
416   public GenericJMXConfValue (OConfigItem ci) /* {{{ */
417     throws IllegalArgumentException
418   {
419     List<OConfigItem> children;
420     Iterator<OConfigItem> iter;
421
422     this._ds_name = null;
423     this._ds = null;
424     this._attributes = new ArrayList<String> ();
425     this._instance_prefix = null;
426     this._instance_from = new ArrayList<String> ();
427     this._is_table = false;
428
429     /*
430      * <Value>
431      *   Type "memory"
432      *   Table true|false
433      *   Attribute "HeapMemoryUsage"
434      *   Attribute "..."
435      *   :
436      *   # Type instance:
437      *   InstancePrefix "heap-"
438      * </Value>
439      */
440     children = ci.getChildren ();
441     iter = children.iterator ();
442     while (iter.hasNext ())
443     {
444       OConfigItem child = iter.next ();
445
446       if (child.getKey ().equalsIgnoreCase ("Type"))
447       {
448         String tmp = getConfigString (child);
449         if (tmp != null)
450           this._ds_name = tmp;
451       }
452       else if (child.getKey ().equalsIgnoreCase ("Table"))
453       {
454         Boolean tmp = getConfigBoolean (child);
455         if (tmp != null)
456           this._is_table = tmp.booleanValue ();
457       }
458       else if (child.getKey ().equalsIgnoreCase ("Attribute"))
459       {
460         String tmp = getConfigString (child);
461         if (tmp != null)
462           this._attributes.add (tmp);
463       }
464       else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
465       {
466         String tmp = getConfigString (child);
467         if (tmp != null)
468           this._instance_prefix = tmp;
469       }
470       else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
471       {
472         String tmp = getConfigString (child);
473         if (tmp != null)
474           this._instance_from.add (tmp);
475       }
476       else
477         throw (new IllegalArgumentException ("Unknown option: "
478               + child.getKey ()));
479     }
480
481     if (this._ds_name == null)
482       throw (new IllegalArgumentException ("No data set was defined."));
483     else if (this._attributes.size () == 0)
484       throw (new IllegalArgumentException ("No attribute was defined."));
485   } /* }}} GenericJMXConfValue (OConfigItem ci) */
486
487   /**
488    * Query values via JMX according to the object's configuration and dispatch
489    * them to collectd.
490    *
491    * @param conn    Connection to the MBeanServer.
492    * @param objName Object name of the MBean to query.
493    * @param pd      Preset naming components. The members host, plugin and
494    *                plugin instance will be used.
495    */
496   public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
497       PluginData pd)
498   {
499     ValueList vl;
500     List<DataSource> dsrc;
501     List<Object> values;
502     List<String> instanceList;
503     String instancePrefix;
504
505     if (this._ds == null)
506     {
507       this._ds = Collectd.getDS (this._ds_name);
508       if (this._ds == null)
509       {
510         Collectd.logError ("GenericJMXConfValue: Unknown type: "
511             + this._ds_name);
512         return;
513       }
514     }
515
516     dsrc = this._ds.getDataSources ();
517     if (dsrc.size () != this._attributes.size ())
518     {
519       Collectd.logError ("GenericJMXConfValue.query: The data set "
520           + this._ds_name + " has " + this._ds.getDataSources ().size ()
521           + " data sources, but there were " + this._attributes.size ()
522           + " attributes configured. This doesn't match!");
523       this._ds = null;
524       return;
525     }
526
527     vl = new ValueList (pd);
528     vl.setType (this._ds_name);
529
530     /*
531      * Build the instnace prefix from the fixed string prefix and the
532      * properties of the objName.
533      */
534     instanceList = new ArrayList<String> ();
535     for (int i = 0; i < this._instance_from.size (); i++)
536     {
537       String propertyName;
538       String propertyValue;
539
540       propertyName = this._instance_from.get (i);
541       propertyValue = objName.getKeyProperty (propertyName);
542       if (propertyValue == null)
543       {
544         Collectd.logError ("GenericJMXConfMBean: "
545             + "No such property in object name: " + propertyName);
546       }
547       else
548       {
549         instanceList.add (propertyValue);
550       }
551     }
552
553     if (this._instance_prefix != null)
554       instancePrefix = new String (this._instance_prefix
555           + join ("-", instanceList));
556     else
557       instancePrefix = join ("-", instanceList);
558
559     /*
560      * Build a list of `Object's which is then passed to `submitTable' and
561      * `submitScalar'.
562      */
563     values = new ArrayList<Object> ();
564     assert (dsrc.size () == this._attributes.size ());
565     for (int i = 0; i < this._attributes.size (); i++)
566     {
567       Object v;
568
569       v = queryAttribute (conn, objName, this._attributes.get (i));
570       if (v == null)
571       {
572         Collectd.logError ("GenericJMXConfValue.query: "
573             + "Querying attribute " + this._attributes.get (i) + " failed.");
574         return;
575       }
576
577       values.add (v);
578     }
579
580     if (this._is_table)
581       submitTable (values, vl, instancePrefix);
582     else
583       submitScalar (values, vl, instancePrefix);
584   } /* }}} void query */
585 } /* class GenericJMXConfValue */
586
587 /* vim: set sw=2 sts=2 et fdm=marker : */