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