0a2abc3393c5d4fa2c9e6c7b8f4a14ab283497d7
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 */
22 package org.collectd.java;
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;
30 import java.math.BigDecimal;
31 import java.math.BigInteger;
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;
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;
47 /**
48 * Representation of a <value /> block and query functionality.
49 *
50 * This class represents a <value /> 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;
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;
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 }
124 return (null);
125 } /* }}} Number genericObjectToNumber */
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 ();
137 assert (objects.size () == dsrc.size ());
139 for (int i = 0; i < objects.size (); i++)
140 {
141 Number n;
143 n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
144 if (n == null)
145 return (null);
146 ret.add (n);
147 }
149 return (ret);
150 } /* }}} List<Number> genericListToNumber */
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> ();
165 for (int i = 0; i < cdlist.size (); i++)
166 {
167 CompositeData cd;
168 Object value;
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 }
182 return (genericListToNumber (objects));
183 } /* }}} List<Number> genericCompositeToNumber */
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;
192 cdlist = new ArrayList<CompositeData> ();
193 for (int i = 0; i < objects.size (); i++)
194 {
195 Object obj;
197 obj = objects.get (i);
198 if (obj instanceof CompositeData)
199 {
200 CompositeData cd;
202 cd = (CompositeData) obj;
204 if (i == 0)
205 keySet = cd.getCompositeType ().keySet ();
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 }
218 assert (keySet != null);
220 keyIter = keySet.iterator ();
221 while (keyIter.hasNext ())
222 {
223 String key;
224 List<Number> values;
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 }
236 if (instancePrefix == null)
237 vl.setTypeInstance (key);
238 else
239 vl.setTypeInstance (instancePrefix + key);
240 vl.setValues (values);
242 Collectd.dispatchValues (vl);
243 }
244 } /* }}} void submitTable */
246 private void submitScalar (List<Object> objects, ValueList vl, /* {{{ */
247 String instancePrefix)
248 {
249 List<Number> values;
251 values = genericListToNumber (objects);
252 if (values == null)
253 {
254 Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
255 + "objects to numbers.");
256 return;
257 }
259 if (instancePrefix == null)
260 vl.setTypeInstance ("");
261 else
262 vl.setTypeInstance (instancePrefix);
263 vl.setValues (values);
265 Collectd.dispatchValues (vl);
266 } /* }}} void submitScalar */
268 private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
269 List<String> attrName)
270 {
271 String key;
272 Object value;
274 key = attrName.remove (0);
276 try
277 {
278 value = parent.get (key);
279 }
280 catch (InvalidKeyException e)
281 {
282 return (null);
283 }
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 */
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;
306 attrNameList = new ArrayList<String> ();
308 attrNameArray = attrName.split ("\\.");
309 key = attrNameArray[0];
310 for (int i = 1; i < attrNameArray.length; i++)
311 attrNameList.add (attrNameArray[i]);
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,
322 /* parameters */ null, /* signature */ null);
323 }
324 }
325 catch (Exception e)
326 {
327 Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
328 + e);
329 return (null);
330 }
332 if (attrNameList.size () == 0)
333 {
334 return (value);
335 }
336 else
337 {
338 if (value instanceof CompositeData)
339 return (queryAttributeRecursive((CompositeData) value, attrNameList));
340 else if (value instanceof OpenType)
341 {
342 OpenType ot = (OpenType) value;
343 Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
344 + ot.getTypeName () + "\" is not yet implemented.");
345 return (null);
346 }
347 else
348 {
349 Collectd.logError ("GenericJMXConfValue: Received object of "
350 + "unknown class.");
351 return (null);
352 }
353 }
354 } /* }}} Object queryAttribute */
356 private String join (String separator, List<String> list) /* {{{ */
357 {
358 StringBuffer sb;
360 sb = new StringBuffer ();
362 for (int i = 0; i < list.size (); i++)
363 {
364 if (i > 0)
365 sb.append ("-");
366 sb.append (list.get (i));
367 }
369 return (sb.toString ());
370 } /* }}} String join */
372 private String getConfigString (OConfigItem ci) /* {{{ */
373 {
374 List<OConfigValue> values;
375 OConfigValue v;
377 values = ci.getValues ();
378 if (values.size () != 1)
379 {
380 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
381 + " configuration option needs exactly one string argument.");
382 return (null);
383 }
385 v = values.get (0);
386 if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
387 {
388 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
389 + " configuration option needs exactly one string argument.");
390 return (null);
391 }
393 return (v.getString ());
394 } /* }}} String getConfigString */
396 private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
397 {
398 List<OConfigValue> values;
399 OConfigValue v;
400 Boolean b;
402 values = ci.getValues ();
403 if (values.size () != 1)
404 {
405 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
406 + " configuration option needs exactly one boolean argument.");
407 return (null);
408 }
410 v = values.get (0);
411 if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
412 {
413 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
414 + " configuration option needs exactly one boolean argument.");
415 return (null);
416 }
418 return (new Boolean (v.getBoolean ()));
419 } /* }}} String getConfigBoolean */
421 /**
422 * Constructs a new value with the configured properties.
423 */
424 public GenericJMXConfValue (OConfigItem ci) /* {{{ */
425 throws IllegalArgumentException
426 {
427 List<OConfigItem> children;
428 Iterator<OConfigItem> iter;
430 this._ds_name = null;
431 this._ds = null;
432 this._attributes = new ArrayList<String> ();
433 this._instance_prefix = null;
434 this._instance_from = new ArrayList<String> ();
435 this._is_table = false;
437 /*
438 * <Value>
439 * Type "memory"
440 * Table true|false
441 * Attribute "HeapMemoryUsage"
442 * Attribute "..."
443 * :
444 * # Type instance:
445 * InstancePrefix "heap-"
446 * </Value>
447 */
448 children = ci.getChildren ();
449 iter = children.iterator ();
450 while (iter.hasNext ())
451 {
452 OConfigItem child = iter.next ();
454 if (child.getKey ().equalsIgnoreCase ("Type"))
455 {
456 String tmp = getConfigString (child);
457 if (tmp != null)
458 this._ds_name = tmp;
459 }
460 else if (child.getKey ().equalsIgnoreCase ("Table"))
461 {
462 Boolean tmp = getConfigBoolean (child);
463 if (tmp != null)
464 this._is_table = tmp.booleanValue ();
465 }
466 else if (child.getKey ().equalsIgnoreCase ("Attribute"))
467 {
468 String tmp = getConfigString (child);
469 if (tmp != null)
470 this._attributes.add (tmp);
471 }
472 else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
473 {
474 String tmp = getConfigString (child);
475 if (tmp != null)
476 this._instance_prefix = tmp;
477 }
478 else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
479 {
480 String tmp = getConfigString (child);
481 if (tmp != null)
482 this._instance_from.add (tmp);
483 }
484 else
485 throw (new IllegalArgumentException ("Unknown option: "
486 + child.getKey ()));
487 }
489 if (this._ds_name == null)
490 throw (new IllegalArgumentException ("No data set was defined."));
491 else if (this._attributes.size () == 0)
492 throw (new IllegalArgumentException ("No attribute was defined."));
493 } /* }}} GenericJMXConfValue (OConfigItem ci) */
495 /**
496 * Query values via JMX according to the object's configuration and dispatch
497 * them to collectd.
498 *
499 * @param conn Connection to the MBeanServer.
500 * @param objName Object name of the MBean to query.
501 * @param pd Preset naming components. The members host, plugin and
502 * plugin instance will be used.
503 */
504 public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
505 PluginData pd)
506 {
507 ValueList vl;
508 List<DataSource> dsrc;
509 List<Object> values;
510 List<String> instanceList;
511 String instancePrefix;
513 if (this._ds == null)
514 {
515 this._ds = Collectd.getDS (this._ds_name);
516 if (this._ds == null)
517 {
518 Collectd.logError ("GenericJMXConfValue: Unknown type: "
519 + this._ds_name);
520 return;
521 }
522 }
524 dsrc = this._ds.getDataSources ();
525 if (dsrc.size () != this._attributes.size ())
526 {
527 Collectd.logError ("GenericJMXConfValue.query: The data set "
528 + this._ds_name + " has " + this._ds.getDataSources ().size ()
529 + " data sources, but there were " + this._attributes.size ()
530 + " attributes configured. This doesn't match!");
531 this._ds = null;
532 return;
533 }
535 vl = new ValueList (pd);
536 vl.setType (this._ds_name);
538 /*
539 * Build the instnace prefix from the fixed string prefix and the
540 * properties of the objName.
541 */
542 instanceList = new ArrayList<String> ();
543 for (int i = 0; i < this._instance_from.size (); i++)
544 {
545 String propertyName;
546 String propertyValue;
548 propertyName = this._instance_from.get (i);
549 propertyValue = objName.getKeyProperty (propertyName);
550 if (propertyValue == null)
551 {
552 Collectd.logError ("GenericJMXConfMBean: "
553 + "No such property in object name: " + propertyName);
554 }
555 else
556 {
557 instanceList.add (propertyValue);
558 }
559 }
561 if (this._instance_prefix != null)
562 instancePrefix = new String (this._instance_prefix
563 + join ("-", instanceList));
564 else
565 instancePrefix = join ("-", instanceList);
567 /*
568 * Build a list of `Object's which is then passed to `submitTable' and
569 * `submitScalar'.
570 */
571 values = new ArrayList<Object> ();
572 assert (dsrc.size () == this._attributes.size ());
573 for (int i = 0; i < this._attributes.size (); i++)
574 {
575 Object v;
577 v = queryAttribute (conn, objName, this._attributes.get (i));
578 if (v == null)
579 {
580 Collectd.logError ("GenericJMXConfValue.query: "
581 + "Querying attribute " + this._attributes.get (i) + " failed.");
582 return;
583 }
585 values.add (v);
586 }
588 if (this._is_table)
589 submitTable (values, vl, instancePrefix);
590 else
591 submitScalar (values, vl, instancePrefix);
592 } /* }}} void query */
593 } /* class GenericJMXConfValue */
595 /* vim: set sw=2 sts=2 et fdm=marker : */