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 boolean _is_table;
67 /**
68 * Converts a generic (OpenType) object to a number.
69 *
70 * Returns null if a conversion is not possible or not implemented.
71 */
72 private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
73 {
74 if (obj instanceof String)
75 {
76 String str = (String) obj;
78 try
79 {
80 if (ds_type == DataSource.TYPE_GAUGE)
81 return (new Double (str));
82 else
83 return (new Long (str));
84 }
85 catch (NumberFormatException e)
86 {
87 return (null);
88 }
89 }
90 else if (obj instanceof Byte)
91 {
92 return (new Byte ((Byte) obj));
93 }
94 else if (obj instanceof Short)
95 {
96 return (new Short ((Short) obj));
97 }
98 else if (obj instanceof Integer)
99 {
100 return (new Integer ((Integer) obj));
101 }
102 else if (obj instanceof Long)
103 {
104 return (new Long ((Long) obj));
105 }
106 else if (obj instanceof Float)
107 {
108 return (new Float ((Float) obj));
109 }
110 else if (obj instanceof Double)
111 {
112 return (new Double ((Double) obj));
113 }
114 else if (obj instanceof BigDecimal)
115 {
116 return (BigDecimal.ZERO.add ((BigDecimal) obj));
117 }
118 else if (obj instanceof BigInteger)
119 {
120 return (BigInteger.ZERO.add ((BigInteger) obj));
121 }
123 return (null);
124 } /* }}} Number genericObjectToNumber */
126 private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
127 {
128 List<Number> ret = new ArrayList<Number> ();
129 List<DataSource> dsrc = this._ds.getDataSources ();
131 assert (objects.size () == dsrc.size ());
133 for (int i = 0; i < objects.size (); i++)
134 {
135 Number n;
137 n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
138 if (n == null)
139 return (null);
140 ret.add (n);
141 }
143 return (ret);
144 } /* }}} List<Number> genericListToNumber */
146 private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
147 String key)
148 {
149 List<Object> objects = new ArrayList<Object> ();
151 for (int i = 0; i < cdlist.size (); i++)
152 {
153 CompositeData cd;
154 Object value;
156 cd = cdlist.get (i);
157 try
158 {
159 value = cd.get (key);
160 }
161 catch (InvalidKeyException e)
162 {
163 return (null);
164 }
165 objects.add (value);
166 }
168 return (genericListToNumber (objects));
169 } /* }}} List<Number> genericCompositeToNumber */
171 private void submitTable (List<Object> objects, ValueList vl) /* {{{ */
172 {
173 List<CompositeData> cdlist;
174 Set<String> keySet = null;
175 Iterator<String> keyIter;
177 cdlist = new ArrayList<CompositeData> ();
178 for (int i = 0; i < objects.size (); i++)
179 {
180 Object obj;
182 obj = objects.get (i);
183 if (obj instanceof CompositeData)
184 {
185 CompositeData cd;
187 cd = (CompositeData) obj;
189 if (i == 0)
190 keySet = cd.getCompositeType ().keySet ();
192 cdlist.add (cd);
193 }
194 else
195 {
196 Collectd.logError ("GenericJMXConfValue: At least one of the "
197 + "attributes was not of type `CompositeData', as required "
198 + "when table is set to `true'.");
199 return;
200 }
201 }
203 assert (keySet != null);
205 keyIter = keySet.iterator ();
206 while (keyIter.hasNext ())
207 {
208 String key;
209 List<Number> values;
211 key = keyIter.next ();
212 values = genericCompositeToNumber (cdlist, key);
213 if (values == null)
214 {
215 Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
216 + "numbers for key " + key + ". Most likely not all attributes "
217 + "have this key.");
218 continue;
219 }
221 if (this._instance_prefix == null)
222 vl.setTypeInstance (key);
223 else
224 vl.setTypeInstance (this._instance_prefix + key);
225 vl.setValues (values);
227 Collectd.dispatchValues (vl);
228 }
229 } /* }}} void submitTable */
231 private void submitScalar (List<Object> objects, ValueList vl) /* {{{ */
232 {
233 List<Number> values;
235 values = genericListToNumber (objects);
236 if (values == null)
237 {
238 Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
239 + "objects to numbers.");
240 return;
241 }
243 if (this._instance_prefix == null)
244 vl.setTypeInstance ("");
245 else
246 vl.setTypeInstance (this._instance_prefix);
247 vl.setValues (values);
249 Collectd.dispatchValues (vl);
250 } /* }}} void submitScalar */
252 private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
253 List<String> attrName)
254 {
255 String key;
256 Object value;
258 key = attrName.remove (0);
260 try
261 {
262 value = parent.get (key);
263 }
264 catch (InvalidKeyException e)
265 {
266 return (null);
267 }
269 if (attrName.size () == 0)
270 {
271 return (value);
272 }
273 else
274 {
275 if (value instanceof CompositeData)
276 return (queryAttributeRecursive ((CompositeData) value, attrName));
277 else
278 return (null);
279 }
280 } /* }}} queryAttributeRecursive */
282 private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
283 ObjectName objName, String attrName)
284 {
285 List<String> attrNameList;
286 String key;
287 Object value;
288 String[] attrNameArray;
290 attrNameList = new ArrayList<String> ();
292 attrNameArray = attrName.split ("\\.");
293 key = attrNameArray[0];
294 for (int i = 1; i < attrNameArray.length; i++)
295 attrNameList.add (attrNameArray[i]);
297 try
298 {
299 value = conn.getAttribute (objName, key);
300 }
301 catch (Exception e)
302 {
303 Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
304 + e);
305 return (null);
306 }
308 if (attrNameList.size () == 0)
309 {
310 return (value);
311 }
312 else
313 {
314 if (value instanceof CompositeData)
315 return (queryAttributeRecursive((CompositeData) value, attrNameList));
316 else if (value instanceof OpenType)
317 {
318 OpenType ot = (OpenType) value;
319 Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
320 + ot.getTypeName () + "\" is not yet implemented.");
321 return (null);
322 }
323 else
324 {
325 Collectd.logError ("GenericJMXConfValue: Received object of "
326 + "unknown class.");
327 return (null);
328 }
329 }
330 } /* }}} Object queryAttribute */
332 private String getConfigString (OConfigItem ci) /* {{{ */
333 {
334 List<OConfigValue> values;
335 OConfigValue v;
337 values = ci.getValues ();
338 if (values.size () != 1)
339 {
340 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
341 + " configuration option needs exactly one string argument.");
342 return (null);
343 }
345 v = values.get (0);
346 if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
347 {
348 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
349 + " configuration option needs exactly one string argument.");
350 return (null);
351 }
353 return (v.getString ());
354 } /* }}} String getConfigString */
356 private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
357 {
358 List<OConfigValue> values;
359 OConfigValue v;
360 Boolean b;
362 values = ci.getValues ();
363 if (values.size () != 1)
364 {
365 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
366 + " configuration option needs exactly one boolean argument.");
367 return (null);
368 }
370 v = values.get (0);
371 if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
372 {
373 Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
374 + " configuration option needs exactly one boolean argument.");
375 return (null);
376 }
378 return (new Boolean (v.getBoolean ()));
379 } /* }}} String getConfigBoolean */
381 /**
382 * Constructs a new value with the configured properties.
383 */
384 public GenericJMXConfValue (OConfigItem ci) /* {{{ */
385 throws IllegalArgumentException
386 {
387 List<OConfigItem> children;
388 Iterator<OConfigItem> iter;
390 this._ds_name = null;
391 this._ds = null;
392 this._attributes = new ArrayList<String> ();
393 this._instance_prefix = null;
394 this._is_table = false;
396 /*
397 * <Value>
398 * Type "memory"
399 * Table true|false
400 * Attribute "HeapMemoryUsage"
401 * Attribute "..."
402 * :
403 * # Type instance:
404 * InstancePrefix "heap-"
405 * </Value>
406 */
407 children = ci.getChildren ();
408 iter = children.iterator ();
409 while (iter.hasNext ())
410 {
411 OConfigItem child = iter.next ();
413 if (child.getKey ().equalsIgnoreCase ("Type"))
414 {
415 String tmp = getConfigString (child);
416 if (tmp != null)
417 this._ds_name = tmp;
418 }
419 else if (child.getKey ().equalsIgnoreCase ("Table"))
420 {
421 Boolean tmp = getConfigBoolean (child);
422 if (tmp != null)
423 this._is_table = tmp.booleanValue ();
424 }
425 else if (child.getKey ().equalsIgnoreCase ("Attribute"))
426 {
427 String tmp = getConfigString (child);
428 if (tmp != null)
429 this._attributes.add (tmp);
430 }
431 else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
432 {
433 String tmp = getConfigString (child);
434 if (tmp != null)
435 this._instance_prefix = tmp;
436 }
437 else
438 throw (new IllegalArgumentException ("Unknown option: "
439 + child.getKey ()));
440 }
442 if (this._ds_name == null)
443 throw (new IllegalArgumentException ("No data set was defined."));
444 else if (this._attributes.size () == 0)
445 throw (new IllegalArgumentException ("No attribute was defined."));
446 } /* }}} GenericJMXConfValue (OConfigItem ci) */
448 /**
449 * Query values via JMX according to the object's configuration and dispatch
450 * them to collectd.
451 *
452 * @param conn Connection to the MBeanServer.
453 * @param objName Object name of the MBean to query.
454 * @param pd Preset naming components. The members host, plugin and
455 * plugin instance will be used.
456 */
457 public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
458 PluginData pd)
459 {
460 ValueList vl;
461 List<DataSource> dsrc;
462 List<Object> values;
464 if (this._ds == null)
465 {
466 this._ds = Collectd.getDS (this._ds_name);
467 if (this._ds == null)
468 {
469 Collectd.logError ("GenericJMXConfValue: Unknown type: "
470 + this._ds_name);
471 return;
472 }
473 }
475 dsrc = this._ds.getDataSources ();
476 if (dsrc.size () != this._attributes.size ())
477 {
478 Collectd.logError ("GenericJMXConfValue.query: The data set "
479 + this._ds_name + " has " + this._ds.getDataSources ().size ()
480 + " data sources, but there were " + this._attributes.size ()
481 + " attributes configured. This doesn't match!");
482 this._ds = null;
483 return;
484 }
486 vl = new ValueList (pd);
487 vl.setType (this._ds_name);
488 vl.setTypeInstance (this._instance_prefix);
490 values = new ArrayList<Object> ();
492 assert (dsrc.size () == this._attributes.size ());
493 for (int i = 0; i < this._attributes.size (); i++)
494 {
495 Object v;
497 v = queryAttribute (conn, objName, this._attributes.get (i));
498 if (v == null)
499 {
500 Collectd.logError ("GenericJMXConfValue.query: "
501 + "Querying attribute " + this._attributes.get (i) + " failed.");
502 return;
503 }
505 values.add (v);
506 }
508 if (this._is_table)
509 submitTable (values, vl);
510 else
511 submitScalar (values, vl);
512 } /* }}} void query */
513 }
515 /* vim: set sw=2 sts=2 et fdm=marker : */