├── .gitignore ├── README.md ├── contrib ├── jmx2munin.cfg │ └── cassandra │ │ └── nodes_in_cluster └── jmx2munin.sh ├── pom.xml └── src └── main └── java └── org └── vafer └── jmx ├── Enums.java ├── Filter.java ├── ListOutput.java ├── NoFilter.java ├── Output.java ├── Query.java ├── Value.java └── munin ├── Munin.java ├── MuninAttributesFilter.java └── MuninOutput.java /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .classpath 3 | .project 4 | .fatjar 5 | target 6 | eclipse 7 | old 8 | bin 9 | .idea 10 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jmx2munin 2 | 3 | The [jmx2munin](http://github.com/tcurdt/jmx2munin) project exposes JMX MBean attributes to [Munin](http://munin-monitoring.org/). 4 | Some of it's features: 5 | 6 | * strictly complies to the plugin format 7 | * exposes composite types like Lists, Maps, Set as useful as possible 8 | * String values can be mapped to numbers 9 | 10 | # How to use 11 | 12 | This is what the Munin script will call. So you should test this first. Of course with your parameters. This example expose all Cassandra information to Munin. 13 | 14 | java -jar jmx2munin.jar \ 15 | -url service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi \ 16 | -query "org.apache.cassandra.*:*" 17 | 18 | The "url" parameters specifies the JMX URL, the query selects the MBeans (and optionally also the attributes) to expose. 19 | 20 | java -jar jmx2munin.jar \ 21 | -url service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi \ 22 | -query "org.apache.cassandra.*:*" \ 23 | -attribute org_apache_cassandra_db_storageservice_livenodes_size 24 | 25 | The script that does the actual interaction with munin you can find in the contrib section. It's the one you should link in the your Munin plugin directory. 26 | 27 | :/etc/munin/plugins$ ls -la cassandra_* 28 | lrwxrwxrwx 1 root root 37 2011-04-07 19:58 cassandra_nodes_in_cluster -> /usr/share/munin/plugins/jmx2munin.sh 29 | 30 | In the plugin conf you point to the correct configuration 31 | 32 | [cassandra_*] 33 | env.url service:jmx:rmi:///jndi/rmi://127.0.0.1:7199/jmxrmi 34 | env.query org.apache.cassandra.*:* 35 | 36 | [cassandra_nodes_in_cluster] 37 | env.config cassandra/nodes_in_cluster 38 | 39 | A possible configuration could look like this 40 | 41 | graph_title Number of Nodes in Cluster 42 | graph_vlabel org_apache_cassandra_db_storageservice_livenodes_size 43 | org_apache_cassandra_db_storageservice_livenodes_size.label number of nodes 44 | 45 | The script will extract the attributes from the config and caches the JMX results to reduce the load when showing many values. For testing you can run it manually just like munin would. 46 | 47 | MUNIN_LIBDIR='/usr/share/munin' 48 | config='cassandra/nodes_in_cluster' 49 | query='org.apache.cassandra.*:*' 50 | /usr/share/munin/plugins/jmx2munin.sh 51 | 52 | # More advanced 53 | 54 | Sometimes it can be useful to track String values by mapping them into an enum as they really describe states. To find this possible candidates you can call: 55 | 56 | java -jar jmx2munin.jar \ 57 | -url service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi \ 58 | -query "org.apache.cassandra.*:*" \ 59 | -list 60 | 61 | It should output a list of possible candidates. This can now be turned into a enum configuration file: 62 | 63 | [org.apache.cassandra.db.StorageService:OperationMode] 64 | 0 = ^Normal 65 | 1 = ^Client 66 | 2 = ^Joining 67 | 3 = ^Bootstrapping 68 | 4 = ^Leaving 69 | 5 = ^Decommissioned 70 | 6 = ^Starting drain 71 | 7 = ^Node is drained 72 | 73 | Which you then can provide: 74 | 75 | java -jar jmx2munin.jar \ 76 | -url service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi \ 77 | -query "org.apache.cassandra.*:*" \ 78 | -enums /path/to/enums.cfg 79 | 80 | Now matching values get replaced by their numerical representation. On the left needs to be a unique number on the right side is a regular expression. If a string cannot be matched according to the spec "U" for "undefined" will be returned. 81 | 82 | # License 83 | 84 | Licensed under the Apache License, Version 2.0 (the "License") 85 | You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 86 | -------------------------------------------------------------------------------- /contrib/jmx2munin.cfg/cassandra/nodes_in_cluster: -------------------------------------------------------------------------------- 1 | graph_title Number of Nodes in Cluster 2 | graph_vlabel org_apache_cassandra_db_storageservice_livenodes_size 3 | org_apache_cassandra_db_storageservice_livenodes_size.label number of nodes 4 | -------------------------------------------------------------------------------- /contrib/jmx2munin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # [cassandra_nodes_in_cluster] 3 | # env.url service:jmx:rmi:///jndi/rmi://127.0.0.1:7199/jmxrmi 4 | # env.query org.apache.cassandra.*:* 5 | # env.config cassandra/nodes_in_cluster 6 | # sets the 'config' and 'query' and 'url' variables for this script 7 | 8 | if [ -z "$MUNIN_LIBDIR" ]; then 9 | MUNIN_LIBDIR="`dirname $(dirname "$0")`" 10 | fi 11 | 12 | if [ -f "$MUNIN_LIBDIR/plugins/plugin.sh" ]; then 13 | . $MUNIN_LIBDIR/plugins/plugin.sh 14 | fi 15 | 16 | if [ "$1" = "autoconf" ]; then 17 | echo yes 18 | exit 0 19 | fi 20 | 21 | if [ -z "$url" ]; then 22 | # this is very common so make it a default 23 | url="service:jmx:rmi:///jndi/rmi://127.0.0.1:7199/jmxrmi" 24 | fi 25 | 26 | [ -z "$config" ] && config="${0#*_}" 27 | 28 | if [ -z "$config" -o -z "$query" -o -z "$url" ]; then 29 | echo "Configuration needs attributes config, query and optinally url" 30 | exit 1 31 | fi 32 | 33 | JMX2MUNIN_DIR="$MUNIN_LIBDIR/plugins" 34 | CONFIG="$JMX2MUNIN_DIR/jmx2munin.cfg/$config" 35 | 36 | if [ "$1" = "config" ]; then 37 | cat "$CONFIG" 38 | exit 0 39 | fi 40 | 41 | JAR="$MUNIN_LIBDIR/jmx2munin.jar" 42 | CACHED="${MUNIN_STATEFILE}" 43 | 44 | if test ! -f $CACHED || test `find "$CACHED" -mmin +2`; then 45 | 46 | java -jar "$JAR" \ 47 | -url "$url" \ 48 | -query "$query" \ 49 | $ATTRIBUTES \ 50 | > $CACHED 51 | 52 | echo "cached.value `date +%s`" >> $CACHED 53 | fi 54 | 55 | ATTRIBUTES=`awk '/\.label/ { gsub(/\.label/,""); print $1 }' $CONFIG` 56 | 57 | if [ -z "$ATTRIBUTES" ]; then 58 | echo "Could not find any *.label lines in $CONFIG" 59 | exit 1 60 | fi 61 | 62 | for ATTRIBUTE in $ATTRIBUTES; do 63 | grep "$ATTRIBUTE\." $CACHED 64 | done 65 | 66 | exit 0 67 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.vafer 6 | jmx2munin 7 | jmx2munin 8 | 1.1 9 | 10 | Munin plugin to access JMX information 11 | 12 | http://github.com/tcurdt/jmx2munin 13 | 14 | 15 | 16 | tcurdt 17 | Torsten Curdt 18 | tcurdt at vafer.org 19 | +1 20 | 21 | 22 | 23 | 24 | 25 | Apache License 2 26 | http://www.apache.org/licenses/LICENSE-2.0.txt 27 | 28 | 29 | 30 | 31 | scm:git:git://github.com:tcurdt/jmx2munin.git 32 | scm:git:git://github.com:tcurdt/jmx2munin.git 33 | http://github.com/tcurdt/jmx2munin/tree/master 34 | 35 | 36 | 37 | 38 | com.beust 39 | jcommander 40 | 1.20 41 | 42 | 43 | 44 | junit 45 | junit 46 | 4.13.1 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-compiler-plugin 56 | 2.3.2 57 | 58 | 1.6 59 | 1.6 60 | UTF-8 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-surefire-plugin 66 | 2.11 67 | 68 | never 69 | 70 | **/*TestCase.java 71 | 72 | 73 | **/Abstract* 74 | 75 | true 76 | false 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-source-plugin 82 | 2.1 83 | 84 | true 85 | 86 | 87 | 88 | create-source-jar 89 | 90 | jar-no-fork 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-shade-plugin 98 | 1.4 99 | 100 | 101 | package 102 | 103 | shade 104 | 105 | 106 | false 107 | 108 | 109 | com.beust:jcommander 110 | 111 | 112 | 113 | 114 | org.vafer.jmx.munin.Munin 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/Enums.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | import java.util.TreeMap; 10 | import java.util.regex.Pattern; 11 | 12 | import javax.management.ObjectName; 13 | 14 | public final class Enums { 15 | 16 | private TreeMap> sections = new TreeMap>(); 17 | 18 | public boolean load(String filePath) throws IOException { 19 | BufferedReader input = null; 20 | LinkedHashMap section = new LinkedHashMap(); 21 | try { 22 | input = new BufferedReader(new InputStreamReader(new FileInputStream(filePath))); 23 | String line; 24 | int linenr = 0; 25 | while((line = input.readLine()) != null) { 26 | linenr += 1; 27 | line = line.trim(); 28 | if (line.startsWith("#")) { 29 | continue; 30 | } 31 | if (line.startsWith("[") && line.endsWith("]")) { 32 | // new section 33 | String id = line.substring(1, line.length() - 1); 34 | section = new LinkedHashMap(); 35 | sections.put(id, section); 36 | } else { 37 | String[] pair = line.split("="); 38 | if (pair.length == 2) { 39 | Integer number = Integer.parseInt(pair[0].trim()); 40 | Pattern pattern = Pattern.compile(pair[1].trim()); 41 | if (section.put(number, pattern) != null) { 42 | System.err.println("Line " + linenr + ": previous definitions of " + number); 43 | } 44 | } 45 | } 46 | } 47 | } finally { 48 | if (input != null) { 49 | input.close(); 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | public static String id(ObjectName beanName, String attributeName) { 56 | StringBuilder sb = new StringBuilder(); 57 | sb.append(beanName.getDomain()); 58 | sb.append('.'); 59 | sb.append(beanName.getKeyProperty("type")); 60 | sb.append(':'); 61 | sb.append(attributeName); 62 | return sb.toString(); 63 | } 64 | 65 | public Number resolve(String id, String value) { 66 | LinkedHashMap section = sections.get(id); 67 | if (section == null) { 68 | return null; 69 | } 70 | for(Map.Entry entry : section.entrySet()) { 71 | if (entry.getValue().matcher(value).matches()) { 72 | return entry.getKey(); 73 | } 74 | } 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/Filter.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | 3 | import javax.management.ObjectName; 4 | 5 | public interface Filter { 6 | 7 | public boolean include(ObjectName bean, String attribute); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/ListOutput.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | import javax.management.ObjectName; 7 | 8 | public final class ListOutput implements Output { 9 | 10 | private final Set seen = new HashSet(); 11 | 12 | public void output(ObjectName beanName, String attributeName, Object value) { 13 | Value.flatten(beanName, attributeName, value, new Value.Listener() { 14 | public void value(ObjectName beanName, String attributeName, String value) { 15 | final String id = Enums.id(beanName, attributeName); 16 | if (!seen.contains(id)) { 17 | System.out.println("[" + id + "]"); 18 | seen.add(id); 19 | } 20 | } 21 | public void value(ObjectName beanName, String attributeName, Number value) { 22 | } 23 | }); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/NoFilter.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | 3 | import javax.management.ObjectName; 4 | 5 | public final class NoFilter implements Filter { 6 | 7 | public boolean include(ObjectName bean, String attribute) { 8 | return true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/Output.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | 3 | import javax.management.ObjectName; 4 | 5 | public interface Output { 6 | 7 | public void output(ObjectName beanName, String attributeName, Object value); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/Query.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | 3 | import java.io.IOException; 4 | import java.util.Collection; 5 | 6 | import javax.management.AttributeNotFoundException; 7 | import javax.management.InstanceNotFoundException; 8 | import javax.management.IntrospectionException; 9 | import javax.management.MBeanAttributeInfo; 10 | import javax.management.MBeanException; 11 | import javax.management.MBeanInfo; 12 | import javax.management.MBeanServerConnection; 13 | import javax.management.MalformedObjectNameException; 14 | import javax.management.ObjectInstance; 15 | import javax.management.ObjectName; 16 | import javax.management.ReflectionException; 17 | import javax.management.remote.JMXConnector; 18 | import javax.management.remote.JMXConnectorFactory; 19 | import javax.management.remote.JMXServiceURL; 20 | 21 | public final class Query { 22 | 23 | public void run(String url, String expression, Filter filter, Output output) throws IOException, MalformedObjectNameException, InstanceNotFoundException, ReflectionException, IntrospectionException, AttributeNotFoundException, MBeanException { 24 | JMXConnector connector = null; 25 | try { 26 | connector = JMXConnectorFactory.connect(new JMXServiceURL(url)); 27 | MBeanServerConnection connection = connector.getMBeanServerConnection(); 28 | final Collection mbeans = connection.queryMBeans(new ObjectName(expression), null); 29 | 30 | for(ObjectInstance mbean : mbeans) { 31 | final ObjectName mbeanName = mbean.getObjectName(); 32 | final MBeanInfo mbeanInfo = connection.getMBeanInfo(mbeanName); 33 | final MBeanAttributeInfo[] attributes = mbeanInfo.getAttributes(); 34 | for (final MBeanAttributeInfo attribute : attributes) { 35 | if (attribute.isReadable()) { 36 | if (filter.include(mbeanName, attribute.getName())) { 37 | final String attributeName = attribute.getName(); 38 | try { 39 | output.output( 40 | mbean.getObjectName(), 41 | attributeName, 42 | connection.getAttribute(mbeanName, attributeName) 43 | ); 44 | } catch(Exception e) { 45 | // System.err.println("Failed to read " + mbeanName + "." + attributeName); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } finally { 52 | if (connector != null) { 53 | connector.close(); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/Value.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx; 2 | import java.util.List; 3 | import java.util.Map; 4 | import java.util.Set; 5 | import java.util.TreeSet; 6 | 7 | import javax.management.ObjectName; 8 | import javax.management.openmbean.CompositeData; 9 | import javax.management.openmbean.CompositeType; 10 | 11 | public final class Value { 12 | 13 | public interface Listener { 14 | public void value(ObjectName beanName, String attributeName, String value); 15 | public void value(ObjectName beanName, String attributeName, Number value); 16 | } 17 | 18 | public static void flatten(ObjectName beanName, String attributeName, Object value, Listener listener) { 19 | if (value instanceof Number) { 20 | 21 | listener.value(beanName, attributeName, (Number) value); 22 | 23 | } else if (value instanceof String) { 24 | 25 | listener.value(beanName, attributeName, (String) value); 26 | 27 | } else if (value instanceof Set) { 28 | 29 | final Set set = (Set) value; 30 | flatten(beanName, attributeName + ".size", set.size(), listener); 31 | for(Object entry : set) { 32 | flatten(beanName, attributeName + "[" + entry + "]", 1, listener); 33 | } 34 | 35 | } else if (value instanceof List) { 36 | 37 | final List list = (List)value; 38 | listener.value(beanName, attributeName + ".size", list.size()); 39 | for(int i = 0; i map = (Map) value; 46 | listener.value(beanName, attributeName + ".size", map.size()); 47 | for(Map.Entry entry : map.entrySet()) { 48 | flatten(beanName, attributeName + "[" + entry.getKey() + "]", entry.getValue(), listener); 49 | } 50 | 51 | } else if (value instanceof CompositeData) { 52 | 53 | CompositeData composite = (CompositeData) value; 54 | CompositeType type = composite.getCompositeType(); 55 | TreeSet keysSet = new TreeSet(type.keySet()); 56 | for(String key : keysSet) { 57 | flatten(beanName, attributeName + "[" + key + "]", composite.get(key), listener); 58 | } 59 | 60 | } else { 61 | // System.err.println("Failed to convert " + beanName + "." + attributeName); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/munin/Munin.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx.munin; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Locale; 6 | 7 | import org.vafer.jmx.*; 8 | 9 | import com.beust.jcommander.JCommander; 10 | import com.beust.jcommander.Parameter; 11 | 12 | public final class Munin { 13 | 14 | @Parameter(names = "-list", description = "show as list") 15 | private boolean list; 16 | 17 | @Parameter(names = "-url", description = "jmx url", required = true) 18 | private String url; 19 | 20 | @Parameter(names = "-query", description = "query expression", required = true) 21 | private List queries = new ArrayList(); 22 | 23 | @Parameter(names = "-enums", description = "file string to enum config") 24 | private String enumsPath; 25 | 26 | @Parameter(names = "-attribute", description = "attributes to return") 27 | private List attributes = new ArrayList(); 28 | 29 | private void run() throws Exception { 30 | final Filter filter; 31 | if (attributes == null || attributes.isEmpty()) { 32 | filter = new NoFilter(); 33 | } else { 34 | filter = new MuninAttributesFilter(attributes); 35 | } 36 | 37 | final Enums enums = new Enums(); 38 | if (enumsPath != null) { 39 | enums.load(enumsPath); 40 | } 41 | 42 | final Output output; 43 | if (list) { 44 | output = new ListOutput(); 45 | } else { 46 | output = new MuninOutput(enums); 47 | } 48 | 49 | for(String query : queries) { 50 | new Query().run(url, query, filter, output); 51 | } 52 | } 53 | 54 | public static void main(String[] args) throws Exception { 55 | Munin m = new Munin(); 56 | 57 | JCommander cli = new JCommander(m); 58 | try { 59 | cli.parse(args); 60 | } catch(Exception e) { 61 | cli.usage(); 62 | System.exit(1); 63 | } 64 | 65 | m.run(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/munin/MuninAttributesFilter.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx.munin; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | 6 | import javax.management.ObjectName; 7 | 8 | import org.vafer.jmx.Filter; 9 | 10 | public final class MuninAttributesFilter implements Filter { 11 | 12 | private final HashSet attributes = new HashSet(); 13 | 14 | public MuninAttributesFilter(List pAttributes) { 15 | for (String attribute : pAttributes) { 16 | attributes.add(attribute.trim().replaceAll("_size$", "")); 17 | } 18 | } 19 | 20 | public boolean include(ObjectName bean, String attribute) { 21 | return attributes.contains(MuninOutput.attributeName(bean, attribute)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/vafer/jmx/munin/MuninOutput.java: -------------------------------------------------------------------------------- 1 | package org.vafer.jmx.munin; 2 | 3 | import java.text.NumberFormat; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Hashtable; 7 | import java.util.Locale; 8 | 9 | import javax.management.ObjectName; 10 | 11 | import org.vafer.jmx.Enums; 12 | import org.vafer.jmx.Output; 13 | import org.vafer.jmx.Value; 14 | 15 | public final class MuninOutput implements Output { 16 | 17 | private final Enums enums; 18 | 19 | public MuninOutput(Enums enums) { 20 | this.enums = enums; 21 | } 22 | 23 | public static String attributeName(ObjectName bean, String attribute) { 24 | StringBuilder sb = new StringBuilder(); 25 | sb.append(fieldname(beanString(bean))); 26 | sb.append('_'); 27 | sb.append(fieldname(attribute)); 28 | return sb.toString().toLowerCase(Locale.US); 29 | } 30 | 31 | private static String fieldname(String s) { 32 | return s.replaceAll("[^A-Za-z0-9]", "_").replaceAll("_+", "_").replaceAll("_$", ""); 33 | } 34 | 35 | private static String beanString(ObjectName beanName) { 36 | StringBuilder sb = new StringBuilder(); 37 | sb.append(beanName.getDomain()); 38 | 39 | Hashtable properties = beanName.getKeyPropertyList(); 40 | 41 | String keyspace = "keyspace"; 42 | if (properties.containsKey(keyspace)) { 43 | sb.append('.'); 44 | sb.append(properties.get(keyspace)); 45 | properties.remove(keyspace); 46 | } 47 | 48 | String type = "type"; 49 | if (properties.containsKey(type)) { 50 | sb.append('.'); 51 | sb.append(properties.get(type)); 52 | properties.remove(type); 53 | } 54 | 55 | ArrayList keys = new ArrayList(properties.keySet()); 56 | Collections.sort(keys); 57 | 58 | for(String key : keys) { 59 | sb.append('.'); 60 | sb.append(properties.get(key)); 61 | } 62 | 63 | return sb.toString(); 64 | // return beanName.getCanonicalName(); 65 | } 66 | 67 | public void output(ObjectName beanName, String attributeName, Object value) { 68 | Value.flatten(beanName, attributeName, value, new Value.Listener() { 69 | public void value(ObjectName beanName, String attributeName, String value) { 70 | final Number v = enums.resolve(Enums.id(beanName, attributeName), value); 71 | if (v != null) { 72 | value(beanName, attributeName, v); 73 | } else { 74 | value(beanName, attributeName, Double.NaN); 75 | } 76 | } 77 | public void value(ObjectName beanName, String attributeName, Number value) { 78 | final String v; 79 | 80 | if (Double.isNaN(value.doubleValue())) { 81 | v = "U"; 82 | } else { 83 | final NumberFormat f = NumberFormat.getInstance(); 84 | f.setMaximumFractionDigits(2); 85 | f.setGroupingUsed(false); 86 | v = f.format(value); 87 | } 88 | 89 | System.out.println(attributeName(beanName, attributeName) + ".value " + v); 90 | } 91 | }); 92 | } 93 | } --------------------------------------------------------------------------------