9fb0fc2eda9fd175038e755e7c1260b4456c160a
[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       try
316       {
317         value = conn.getAttribute (objName, key);
318       }
319       catch (javax.management.AttributeNotFoundException e)
320       {
321         value = conn.invoke (objName, key, /* args = */ null, /* types = */ null);
322       }
323     }
324     catch (Exception e)
325     {
326       Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
327           + e);
328       return (null);
329     }
330
331     if (attrNameList.size () == 0)
332     {
333       return (value);
334     }
335     else
336     {
337       if (value instanceof CompositeData)
338         return (queryAttributeRecursive((CompositeData) value, attrNameList));
339       else if (value instanceof OpenType)
340       {
341         OpenType ot = (OpenType) value;
342         Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
343             + ot.getTypeName () + "\" is not yet implemented.");
344         return (null);
345       }
346       else
347       {
348         Collectd.logError ("GenericJMXConfValue: Received object of "
349             + "unknown class.");
350         return (null);
351       }
352     }
353   } /* }}} Object queryAttribute */
354
355   private String join (String separator, List<String> list) /* {{{ */
356   {
357     StringBuffer sb;
358
359     sb = new StringBuffer ();
360
361     for (int i = 0; i < list.size (); i++)
362     {
363       if (i > 0)
364         sb.append ("-");
365       sb.append (list.get (i));
366     }
367
368     return (sb.toString ());
369   } /* }}} String join */
370
371   private String getConfigString (OConfigItem ci) /* {{{ */
372   {
373     List<OConfigValue> values;
374     OConfigValue v;
375
376     values = ci.getValues ();
377     if (values.size () != 1)
378     {
379       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
380           + " configuration option needs exactly one string argument.");
381       return (null);
382     }
383
384     v = values.get (0);
385     if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
386     {
387       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
388           + " configuration option needs exactly one string argument.");
389       return (null);
390     }
391
392     return (v.getString ());
393   } /* }}} String getConfigString */
394
395   private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
396   {
397     List<OConfigValue> values;
398     OConfigValue v;
399     Boolean b;
400
401     values = ci.getValues ();
402     if (values.size () != 1)
403     {
404       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
405           + " configuration option needs exactly one boolean argument.");
406       return (null);
407     }
408
409     v = values.get (0);
410     if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
411     {
412       Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
413           + " configuration option needs exactly one boolean argument.");
414       return (null);
415     }
416
417     return (new Boolean (v.getBoolean ()));
418   } /* }}} String getConfigBoolean */
419
420   /**
421    * Constructs a new value with the configured properties.
422    */
423   public GenericJMXConfValue (OConfigItem ci) /* {{{ */
424     throws IllegalArgumentException
425   {
426     List<OConfigItem> children;
427     Iterator<OConfigItem> iter;
428
429     this._ds_name = null;
430     this._ds = null;
431     this._attributes = new ArrayList<String> ();
432     this._instance_prefix = null;
433     this._instance_from = new ArrayList<String> ();
434     this._is_table = false;
435
436     /*
437      * <Value>
438      *   Type "memory"
439      *   Table true|false
440      *   Attribute "HeapMemoryUsage"
441      *   Attribute "..."
442      *   :
443      *   # Type instance:
444      *   InstancePrefix "heap-"
445      * </Value>
446      */
447     children = ci.getChildren ();
448     iter = children.iterator ();
449     while (iter.hasNext ())
450     {
451       OConfigItem child = iter.next ();
452
453       if (child.getKey ().equalsIgnoreCase ("Type"))
454       {
455         String tmp = getConfigString (child);
456         if (tmp != null)
457           this._ds_name = tmp;
458       }
459       else if (child.getKey ().equalsIgnoreCase ("Table"))
460       {
461         Boolean tmp = getConfigBoolean (child);
462         if (tmp != null)
463           this._is_table = tmp.booleanValue ();
464       }
465       else if (child.getKey ().equalsIgnoreCase ("Attribute"))
466       {
467         String tmp = getConfigString (child);
468         if (tmp != null)
469           this._attributes.add (tmp);
470       }
471       else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
472       {
473         String tmp = getConfigString (child);
474         if (tmp != null)
475           this._instance_prefix = tmp;
476       }
477       else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
478       {
479         String tmp = getConfigString (child);
480         if (tmp != null)
481           this._instance_from.add (tmp);
482       }
483       else
484         throw (new IllegalArgumentException ("Unknown option: "
485               + child.getKey ()));
486     }
487
488     if (this._ds_name == null)
489       throw (new IllegalArgumentException ("No data set was defined."));
490     else if (this._attributes.size () == 0)
491       throw (new IllegalArgumentException ("No attribute was defined."));
492   } /* }}} GenericJMXConfValue (OConfigItem ci) */
493
494   /**
495    * Query values via JMX according to the object's configuration and dispatch
496    * them to collectd.
497    *
498    * @param conn    Connection to the MBeanServer.
499    * @param objName Object name of the MBean to query.
500    * @param pd      Preset naming components. The members host, plugin and
501    *                plugin instance will be used.
502    */
503   public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
504       PluginData pd)
505   {
506     ValueList vl;
507     List<DataSource> dsrc;
508     List<Object> values;
509     List<String> instanceList;
510     String instancePrefix;
511
512     if (this._ds == null)
513     {
514       this._ds = Collectd.getDS (this._ds_name);
515       if (this._ds == null)
516       {
517         Collectd.logError ("GenericJMXConfValue: Unknown type: "
518             + this._ds_name);
519         return;
520       }
521     }
522
523     dsrc = this._ds.getDataSources ();
524     if (dsrc.size () != this._attributes.size ())
525     {
526       Collectd.logError ("GenericJMXConfValue.query: The data set "
527           + this._ds_name + " has " + this._ds.getDataSources ().size ()
528           + " data sources, but there were " + this._attributes.size ()
529           + " attributes configured. This doesn't match!");
530       this._ds = null;
531       return;
532     }
533
534     vl = new ValueList (pd);
535     vl.setType (this._ds_name);
536
537     /*
538      * Build the instnace prefix from the fixed string prefix and the
539      * properties of the objName.
540      */
541     instanceList = new ArrayList<String> ();
542     for (int i = 0; i < this._instance_from.size (); i++)
543     {
544       String propertyName;
545       String propertyValue;
546
547       propertyName = this._instance_from.get (i);
548       propertyValue = objName.getKeyProperty (propertyName);
549       if (propertyValue == null)
550       {
551         Collectd.logError ("GenericJMXConfMBean: "
552             + "No such property in object name: " + propertyName);
553       }
554       else
555       {
556         instanceList.add (propertyValue);
557       }
558     }
559
560     if (this._instance_prefix != null)
561       instancePrefix = new String (this._instance_prefix
562           + join ("-", instanceList));
563     else
564       instancePrefix = join ("-", instanceList);
565
566     /*
567      * Build a list of `Object's which is then passed to `submitTable' and
568      * `submitScalar'.
569      */
570     values = new ArrayList<Object> ();
571     assert (dsrc.size () == this._attributes.size ());
572     for (int i = 0; i < this._attributes.size (); i++)
573     {
574       Object v;
575
576       v = queryAttribute (conn, objName, this._attributes.get (i));
577       if (v == null)
578       {
579         Collectd.logError ("GenericJMXConfValue.query: "
580             + "Querying attribute " + this._attributes.get (i) + " failed.");
581         return;
582       }
583
584       values.add (v);
585     }
586
587     if (this._is_table)
588       submitTable (values, vl, instancePrefix);
589     else
590       submitScalar (values, vl, instancePrefix);
591   } /* }}} void query */
592 } /* class GenericJMXConfValue */
593
594 /* vim: set sw=2 sts=2 et fdm=marker : */