Merge branch 'ym/target_set_add_meta' of github.com:ymettier/collectd into target_set...
[collectd.git] / bindings / java / org / collectd / java / GenericJMXConfValue.java
1 /**
2  * collectd - bindings/java/org/collectd/java/GenericJMXConfValue.java
3  * Copyright (C) 2009       Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  */
26
27 package org.collectd.java;
28
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Collection;
32 import java.util.Set;
33 import java.util.Iterator;
34 import java.util.ArrayList;
35
36 import java.math.BigDecimal;
37 import java.math.BigInteger;
38
39 import javax.management.MBeanServerConnection;
40 import javax.management.ObjectName;
41 import javax.management.openmbean.OpenType;
42 import javax.management.openmbean.CompositeData;
43 import javax.management.openmbean.TabularData;
44 import javax.management.openmbean.InvalidKeyException;
45
46 import org.collectd.api.Collectd;
47 import org.collectd.api.DataSet;
48 import org.collectd.api.DataSource;
49 import org.collectd.api.ValueList;
50 import org.collectd.api.PluginData;
51 import org.collectd.api.OConfigValue;
52 import org.collectd.api.OConfigItem;
53
54 /**
55  * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
56  *
57  * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
58  * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
59  * construct an object of this class.
60  *
61  * The object can then be asked to query data from JMX and dispatch it to
62  * collectd.
63  *
64  * @see GenericJMXConfMBean
65  */
66 class GenericJMXConfValue
67 {
68   private String _ds_name;
69   private DataSet _ds;
70   private List<String> _attributes;
71   private String _instance_prefix;
72   private List<String> _instance_from;
73   private String _plugin_name;
74   private boolean _is_table;
75
76   /**
77    * Converts a generic (OpenType) object to a number.
78    *
79    * Returns null if a conversion is not possible or not implemented.
80    */
81   private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
82   {
83     if (obj instanceof String)
84     {
85       String str = (String) obj;
86       
87       try
88       {
89         if (ds_type == DataSource.TYPE_GAUGE)
90           return (new Double (str));
91         else
92           return (new Long (str));
93       }
94       catch (NumberFormatException e)
95       {
96         return (null);
97       }
98     }
99     else if (obj instanceof Byte)
100     {
101       return (new Byte ((Byte) obj));
102     }
103     else if (obj instanceof Short)
104     {
105       return (new Short ((Short) obj));
106     }
107     else if (obj instanceof Integer)
108     {
109       return (new Integer ((Integer) obj));
110     }
111     else if (obj instanceof Long)
112     {
113       return (new Long ((Long) obj));
114     }
115     else if (obj instanceof Float)
116     {
117       return (new Float ((Float) obj));
118     }
119     else if (obj instanceof Double)
120     {
121       return (new Double ((Double) obj));
122     }
123     else if (obj instanceof BigDecimal)
124     {
125       return (BigDecimal.ZERO.add ((BigDecimal) obj));
126     }
127     else if (obj instanceof BigInteger)
128     {
129       return (BigInteger.ZERO.add ((BigInteger) obj));
130     }
131
132     return (null);
133   } /* }}} Number genericObjectToNumber */
134
135   /**
136    * Converts a generic list to a list of numbers.
137    *
138    * Returns null if one or more objects could not be converted.
139    */
140   private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
141   {
142     List<Number> ret = new ArrayList<Number> ();
143     List<DataSource> dsrc = this._ds.getDataSources ();
144
145     assert (objects.size () == dsrc.size ());
146
147     for (int i = 0; i < objects.size (); i++)
148     {
149       Number n;
150
151       n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
152       if (n == null)
153         return (null);
154       ret.add (n);
155     }
156
157     return (ret);
158   } /* }}} List<Number> genericListToNumber */
159
160   /**
161    * Converts a list of CompositeData to a list of numbers.
162    *
163    * From each <em>CompositeData </em> the key <em>key</em> is received and all
164    * those values are converted to a number. If one of the
165    * <em>CompositeData</em> doesn't have the specified key or one returned
166    * object cannot converted to a number then the function will return null.
167    */
168   private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
169       String key)
170   {
171     List<Object> objects = new ArrayList<Object> ();
172
173     for (int i = 0; i < cdlist.size (); i++)
174     {
175       CompositeData cd;
176       Object value;
177
178       cd = cdlist.get (i);
179       try
180       {
181         value = cd.get (key);
182       }
183       catch (InvalidKeyException e)
184       {
185         return (null);
186       }
187       objects.add (value);
188     }
189
190     return (genericListToNumber (objects));
191   } /* }}} List<Number> genericCompositeToNumber */
192
193   private void submitTable (List<Object> objects, ValueList vl, /* {{{ */
194       String instancePrefix)
195   {
196     List<CompositeData> cdlist;
197     Set<String> keySet = null;
198     Iterator<String> keyIter;
199
200     cdlist = new ArrayList<CompositeData> ();
201     for (int i = 0; i < objects.size (); i++)
202     {
203       Object obj;
204
205       obj = objects.get (i);
206       if (obj instanceof CompositeData)
207       {
208         CompositeData cd;
209
210         cd = (CompositeData) obj;
211
212         if (i == 0)
213           keySet = cd.getCompositeType ().keySet ();
214
215         cdlist.add (cd);
216       }
217       else
218       {
219         Collectd.logError ("GenericJMXConfValue: At least one of the "
220             + "attributes was not of type `CompositeData', as required "
221             + "when table is set to `true'.");
222         return;
223       }
224     }
225
226     assert (keySet != null);
227
228     keyIter = keySet.iterator ();
229     while (keyIter.hasNext ())
230     {
231       String key;
232       List<Number> values;
233
234       key = keyIter.next ();
235       values = genericCompositeToNumber (cdlist, key);
236       if (values == null)
237       {
238         Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
239             + "numbers for key " + key + ". Most likely not all attributes "
240             + "have this key.");
241         continue;
242       }
243
244       if (instancePrefix == null)
245         vl.setTypeInstance (key);
246       else
247         vl.setTypeInstance (instancePrefix + key);
248       vl.setValues (values);
249
250       Collectd.dispatchValues (vl);
251     }
252   } /* }}} void submitTable */
253
254   private void submitScalar (List<Object> objects, ValueList vl, /* {{{ */
255       String instancePrefix)
256   {
257     List<Number> values;
258
259     values = genericListToNumber (objects);
260     if (values == null)
261     {
262       Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
263           + "objects to numbers.");
264       return;
265     }
266
267     if (instancePrefix == null)
268       vl.setTypeInstance ("");
269     else
270       vl.setTypeInstance (instancePrefix);
271     vl.setValues (values);
272
273     Collectd.dispatchValues (vl);
274   } /* }}} void submitScalar */
275
276   private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
277       List<String> attrName)
278   {
279     String key;
280     Object value;
281
282     key = attrName.remove (0);
283
284     try
285     {
286       value = parent.get (key);
287     }
288     catch (InvalidKeyException e)
289     {
290       return (null);
291     }
292
293     if (attrName.size () == 0)
294     {
295       return (value);
296     }
297     else
298     {
299       if (value instanceof CompositeData)
300         return (queryAttributeRecursive ((CompositeData) value, attrName));
301       else if (value instanceof TabularData)
302         return (queryAttributeRecursive ((TabularData) value, attrName));
303       else
304         return (null);
305     }
306   } /* }}} queryAttributeRecursive */
307
308   private Object queryAttributeRecursive (TabularData parent, /* {{{ */
309       List<String> attrName)
310   {
311     String key;
312     Object value = null;
313
314     key = attrName.remove (0);
315
316     TabularData tabularData = (TabularData) parent;
317     Collection<CompositeData> table =
318         (Collection<CompositeData>)tabularData.values();
319     for (CompositeData compositeData : table)
320     {
321       if (key.equals(compositeData.get("key")))
322       {
323         value = compositeData.get("value");
324       }
325     }
326     if (null == value)
327     {
328       return (null);
329     }
330
331     if (attrName.size () == 0)
332     {
333       return (value);
334     }
335     else
336     {
337       if (value instanceof CompositeData)
338         return (queryAttributeRecursive ((CompositeData) value, attrName));
339       else if (value instanceof TabularData)
340         return (queryAttributeRecursive ((TabularData) value, attrName));
341       else
342         return (null);
343     }
344   } /* }}} queryAttributeRecursive */
345
346   private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
347       ObjectName objName, String attrName)
348   {
349     List<String> attrNameList;
350     String key;
351     Object value;
352     String[] attrNameArray;
353
354     attrNameList = new ArrayList<String> ();
355
356     attrNameArray = attrName.split ("\\.");
357     key = attrNameArray[0];
358     for (int i = 1; i < attrNameArray.length; i++)
359       attrNameList.add (attrNameArray[i]);
360
361     try
362     {
363       try
364       {
365         value = conn.getAttribute (objName, key);
366       }
367       catch (javax.management.AttributeNotFoundException e)
368       {
369         value = conn.invoke (objName, key, /* args = */ null, /* types = */ null);
370       }
371     }
372     catch (Exception e)
373     {
374       Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
375           + e);
376       return (null);
377     }
378
379     if (attrNameList.size () == 0)
380     {
381       return (value);
382     }
383     else
384     {
385       if (value instanceof CompositeData)
386         return (queryAttributeRecursive((CompositeData) value, attrNameList));
387       else if (value instanceof TabularData)
388         return (queryAttributeRecursive((TabularData) value, attrNameList));
389       else if (value instanceof OpenType)
390       {
391         OpenType ot = (OpenType) value;
392         Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
393             + ot.getTypeName () + "\" is not yet implemented.");
394         return (null);
395       }
396       else
397       {
398         Collectd.logError ("GenericJMXConfValue: Received object of "
399             + "unknown class. " + attrName + " " + ((value == null)?"null":value.getClass().getName()));
400         return (null);
401       }
402     }
403   } /* }}} Object queryAttribute */
404
405   private String join (String separator, List<String> list) /* {{{ */
406   {
407     StringBuffer sb;
408
409     sb = new StringBuffer ();
410
411     for (int i = 0; i < list.size (); i++)
412     {
413       if (i > 0)
414         sb.append ("-");
415       sb.append (list.get (i));
416     }
417
418     return (sb.toString ());
419   } /* }}} String join */
420
421   private String getConfigString (OConfigItem ci) /* {{{ */
422   {
423     List<OConfigValue> values;
424     OConfigValue v;
425
426     values = ci.getValues ();
427     if (values.size () != 1)
428     {
429       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
430           + " configuration option needs exactly one string argument.");
431       return (null);
432     }
433
434     v = values.get (0);
435     if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
436     {
437       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
438           + " configuration option needs exactly one string argument.");
439       return (null);
440     }
441
442     return (v.getString ());
443   } /* }}} String getConfigString */
444
445   private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
446   {
447     List<OConfigValue> values;
448     OConfigValue v;
449     Boolean b;
450
451     values = ci.getValues ();
452     if (values.size () != 1)
453     {
454       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
455           + " configuration option needs exactly one boolean argument.");
456       return (null);
457     }
458
459     v = values.get (0);
460     if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
461     {
462       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
463           + " configuration option needs exactly one boolean argument.");
464       return (null);
465     }
466
467     return (new Boolean (v.getBoolean ()));
468   } /* }}} String getConfigBoolean */
469
470   /**
471    * Constructs a new value with the configured properties.
472    */
473   public GenericJMXConfValue (OConfigItem ci) /* {{{ */
474     throws IllegalArgumentException
475   {
476     List<OConfigItem> children;
477     Iterator<OConfigItem> iter;
478
479     this._ds_name = null;
480     this._ds = null;
481     this._attributes = new ArrayList<String> ();
482     this._instance_prefix = null;
483     this._instance_from = new ArrayList<String> ();
484     this._plugin_name = null;
485     this._is_table = false;
486
487     /*
488      * <Value>
489      *   Type "memory"
490      *   Table true|false
491      *   Attribute "HeapMemoryUsage"
492      *   Attribute "..."
493      *   :
494      *   # Type instance:
495      *   InstancePrefix "heap-"
496      * </Value>
497      */
498     children = ci.getChildren ();
499     iter = children.iterator ();
500     while (iter.hasNext ())
501     {
502       OConfigItem child = iter.next ();
503
504       if (child.getKey ().equalsIgnoreCase ("Type"))
505       {
506         String tmp = getConfigString (child);
507         if (tmp != null)
508           this._ds_name = tmp;
509       }
510       else if (child.getKey ().equalsIgnoreCase ("Table"))
511       {
512         Boolean tmp = getConfigBoolean (child);
513         if (tmp != null)
514           this._is_table = tmp.booleanValue ();
515       }
516       else if (child.getKey ().equalsIgnoreCase ("Attribute"))
517       {
518         String tmp = getConfigString (child);
519         if (tmp != null)
520           this._attributes.add (tmp);
521       }
522       else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
523       {
524         String tmp = getConfigString (child);
525         if (tmp != null)
526           this._instance_prefix = tmp;
527       }
528       else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
529       {
530         String tmp = getConfigString (child);
531         if (tmp != null)
532           this._instance_from.add (tmp);
533       }
534       else if (child.getKey ().equalsIgnoreCase ("PluginName"))
535       {
536         String tmp = getConfigString (child);
537         if (tmp != null)
538           this._plugin_name = tmp;
539       }
540       else
541         throw (new IllegalArgumentException ("Unknown option: "
542               + child.getKey ()));
543     }
544
545     if (this._ds_name == null)
546       throw (new IllegalArgumentException ("No data set was defined."));
547     else if (this._attributes.size () == 0)
548       throw (new IllegalArgumentException ("No attribute was defined."));
549   } /* }}} GenericJMXConfValue (OConfigItem ci) */
550
551   /**
552    * Query values via JMX according to the object's configuration and dispatch
553    * them to collectd.
554    *
555    * @param conn    Connection to the MBeanServer.
556    * @param objName Object name of the MBean to query.
557    * @param pd      Preset naming components. The members host, plugin and
558    *                plugin instance will be used.
559    */
560   public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
561       PluginData pd)
562   {
563     ValueList vl;
564     List<DataSource> dsrc;
565     List<Object> values;
566     List<String> instanceList;
567     String instancePrefix;
568
569     if (this._ds == null)
570     {
571       this._ds = Collectd.getDS (this._ds_name);
572       if (this._ds == null)
573       {
574         Collectd.logError ("GenericJMXConfValue: Unknown type: "
575             + this._ds_name);
576         return;
577       }
578     }
579
580     dsrc = this._ds.getDataSources ();
581     if (dsrc.size () != this._attributes.size ())
582     {
583       Collectd.logError ("GenericJMXConfValue.query: The data set "
584           + this._ds_name + " has " + this._ds.getDataSources ().size ()
585           + " data sources, but there were " + this._attributes.size ()
586           + " attributes configured. This doesn't match!");
587       this._ds = null;
588       return;
589     }
590
591     vl = new ValueList (pd);
592     vl.setType (this._ds_name);
593     if (this._plugin_name != null)
594     {
595       vl.setPlugin (this._plugin_name);
596     }
597
598     /*
599      * Build the instnace prefix from the fixed string prefix and the
600      * properties of the objName.
601      */
602     instanceList = new ArrayList<String> ();
603     for (int i = 0; i < this._instance_from.size (); i++)
604     {
605       String propertyName;
606       String propertyValue;
607
608       propertyName = this._instance_from.get (i);
609       propertyValue = objName.getKeyProperty (propertyName);
610       if (propertyValue == null)
611       {
612         Collectd.logError ("GenericJMXConfMBean: "
613             + "No such property in object name: " + propertyName);
614       }
615       else
616       {
617         instanceList.add (propertyValue);
618       }
619     }
620
621     if (this._instance_prefix != null)
622       instancePrefix = new String (this._instance_prefix
623           + join ("-", instanceList));
624     else
625       instancePrefix = join ("-", instanceList);
626
627     /*
628      * Build a list of `Object's which is then passed to `submitTable' and
629      * `submitScalar'.
630      */
631     values = new ArrayList<Object> ();
632     assert (dsrc.size () == this._attributes.size ());
633     for (int i = 0; i < this._attributes.size (); i++)
634     {
635       Object v;
636
637       v = queryAttribute (conn, objName, this._attributes.get (i));
638       if (v == null)
639       {
640         Collectd.logError ("GenericJMXConfValue.query: "
641             + "Querying attribute " + this._attributes.get (i) + " failed.");
642         return;
643       }
644
645       values.add (v);
646     }
647
648     if (this._is_table)
649       submitTable (values, vl, instancePrefix);
650     else
651       submitScalar (values, vl, instancePrefix);
652   } /* }}} void query */
653 } /* class GenericJMXConfValue */
654
655 /* vim: set sw=2 sts=2 et fdm=marker : */