├── .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 | }
--------------------------------------------------------------------------------