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