View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.hadoop.hbase.util;
18  
19  import java.io.BufferedWriter;
20  import java.io.Closeable;
21  import java.io.IOException;
22  import java.io.OutputStreamWriter;
23  import java.io.PrintWriter;
24  import java.io.StringWriter;
25  import java.lang.management.ManagementFactory;
26  import java.lang.reflect.Array;
27  import java.nio.charset.StandardCharsets;
28  import java.util.Iterator;
29  import java.util.Set;
30  
31  import javax.management.AttributeNotFoundException;
32  import javax.management.InstanceNotFoundException;
33  import javax.management.IntrospectionException;
34  import javax.management.MBeanAttributeInfo;
35  import javax.management.MBeanException;
36  import javax.management.MBeanInfo;
37  import javax.management.MBeanServer;
38  import javax.management.MalformedObjectNameException;
39  import javax.management.ObjectName;
40  import javax.management.ReflectionException;
41  import javax.management.RuntimeErrorException;
42  import javax.management.RuntimeMBeanException;
43  import javax.management.openmbean.CompositeData;
44  import javax.management.openmbean.CompositeType;
45  import javax.management.openmbean.TabularData;
46  
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  import org.codehaus.jackson.JsonFactory;
50  import org.codehaus.jackson.JsonGenerationException;
51  import org.codehaus.jackson.JsonGenerator;
52  
53  /**
54   * Utility for doing JSON and MBeans.
55   */
56  public class JSONBean {
57    private static final Log LOG = LogFactory.getLog(JSONBean.class);
58    private final JsonFactory jsonFactory;
59  
60    public JSONBean() {
61      this.jsonFactory = new JsonFactory();
62    }
63  
64    /**
65     * Use dumping out mbeans as JSON.
66     */
67    public interface Writer extends Closeable {
68      void write(final String key, final String value) throws JsonGenerationException, IOException;
69      int write(final MBeanServer mBeanServer, ObjectName qry, String attribute,
70          final boolean description) throws IOException;
71      void flush() throws IOException;
72    }
73  
74    public Writer open(final PrintWriter writer) throws IOException {
75      final JsonGenerator jg = jsonFactory.createJsonGenerator(writer);
76      jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
77      jg.useDefaultPrettyPrinter();
78      jg.writeStartObject();
79      return new Writer() {
80        @Override
81        public void flush() throws IOException {
82          jg.flush();
83        }
84  
85        @Override
86        public void close() throws IOException {
87          jg.close();
88        }
89  
90        @Override
91        public void write(String key, String value) throws JsonGenerationException, IOException {
92          jg.writeStringField(key, value);
93        }
94  
95        @Override
96        public int write(MBeanServer mBeanServer, ObjectName qry, String attribute,
97            boolean description)
98        throws IOException {
99          return JSONBean.write(jg, mBeanServer, qry, attribute, description);
100       }
101     };
102   }
103 
104   /**
105    * @param mBeanServer
106    * @param qry
107    * @param attribute
108    * @param description
109    * @return Return non-zero if failed to find bean. 0
110    * @throws IOException
111    */
112   private static int write(final JsonGenerator jg,
113       final MBeanServer mBeanServer, ObjectName qry, String attribute,
114       final boolean description)
115   throws IOException {
116     LOG.trace("Listing beans for "+qry);
117     Set<ObjectName> names = null;
118     names = mBeanServer.queryNames(qry, null);
119     jg.writeArrayFieldStart("beans");
120     Iterator<ObjectName> it = names.iterator();
121     while (it.hasNext()) {
122       ObjectName oname = it.next();
123       MBeanInfo minfo;
124       String code = "";
125       String descriptionStr = null;
126       Object attributeinfo = null;
127       try {
128         minfo = mBeanServer.getMBeanInfo(oname);
129         code = minfo.getClassName();
130         if (description) descriptionStr = minfo.getDescription();
131         String prs = "";
132         try {
133           if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) {
134             prs = "modelerType";
135             code = (String) mBeanServer.getAttribute(oname, prs);
136           }
137           if (attribute != null) {
138             prs = attribute;
139             attributeinfo = mBeanServer.getAttribute(oname, prs);
140           }
141         } catch (RuntimeMBeanException e) {
142          // UnsupportedOperationExceptions happen in the normal course of business,
143          // so no need to log them as errors all the time.
144          if (e.getCause() instanceof UnsupportedOperationException) {
145            if (LOG.isTraceEnabled()) {
146              LOG.trace("Getting attribute " + prs + " of " + oname + " threw " + e);
147            }
148          } else {
149            LOG.error("Getting attribute " + prs + " of " + oname + " threw an exception", e);
150          }
151          return 0;
152         } catch (AttributeNotFoundException e) {
153           // If the modelerType attribute was not found, the class name is used
154           // instead.
155           LOG.error("getting attribute " + prs + " of " + oname
156               + " threw an exception", e);
157         } catch (MBeanException e) {
158           // The code inside the attribute getter threw an exception so log it,
159           // and fall back on the class name
160           LOG.error("getting attribute " + prs + " of " + oname
161               + " threw an exception", e);
162         } catch (RuntimeException e) {
163           // For some reason even with an MBeanException available to them
164           // Runtime exceptionscan still find their way through, so treat them
165           // the same as MBeanException
166           LOG.error("getting attribute " + prs + " of " + oname
167               + " threw an exception", e);
168         } catch (ReflectionException e) {
169           // This happens when the code inside the JMX bean (setter?? from the
170           // java docs) threw an exception, so log it and fall back on the
171           // class name
172           LOG.error("getting attribute " + prs + " of " + oname
173               + " threw an exception", e);
174         }
175       } catch (InstanceNotFoundException e) {
176         //Ignored for some reason the bean was not found so don't output it
177         continue;
178       } catch (IntrospectionException e) {
179         // This is an internal error, something odd happened with reflection so
180         // log it and don't output the bean.
181         LOG.error("Problem while trying to process JMX query: " + qry
182             + " with MBean " + oname, e);
183         continue;
184       } catch (ReflectionException e) {
185         // This happens when the code inside the JMX bean threw an exception, so
186         // log it and don't output the bean.
187         LOG.error("Problem while trying to process JMX query: " + qry
188             + " with MBean " + oname, e);
189         continue;
190       }
191 
192       jg.writeStartObject();
193       jg.writeStringField("name", oname.toString());
194       if (description && descriptionStr != null && descriptionStr.length() > 0) {
195         jg.writeStringField("description", descriptionStr);
196       }
197       jg.writeStringField("modelerType", code);
198       if (attribute != null && attributeinfo == null) {
199         jg.writeStringField("result", "ERROR");
200         jg.writeStringField("message", "No attribute with name " + attribute + " was found.");
201         jg.writeEndObject();
202         jg.writeEndArray();
203         jg.close();
204         return -1;
205       }
206 
207       if (attribute != null) {
208         writeAttribute(jg, attribute, descriptionStr, attributeinfo);
209       } else {
210         MBeanAttributeInfo[] attrs = minfo.getAttributes();
211         for (int i = 0; i < attrs.length; i++) {
212           writeAttribute(jg, mBeanServer, oname, description, attrs[i]);
213         }
214       }
215       jg.writeEndObject();
216     }
217     jg.writeEndArray();
218     return 0;
219   }
220 
221   private static void writeAttribute(final JsonGenerator jg,
222       final MBeanServer mBeanServer, ObjectName oname,
223       final boolean description, final MBeanAttributeInfo attr)
224   throws IOException {
225     if (!attr.isReadable()) {
226       return;
227     }
228     String attName = attr.getName();
229     if ("modelerType".equals(attName)) {
230       return;
231     }
232     if (attName.indexOf("=") >= 0 || attName.indexOf(":") >= 0 || attName.indexOf(" ") >= 0) {
233       return;
234     }
235     String descriptionStr = description? attr.getDescription(): null;
236     Object value = null;
237     try {
238       value = mBeanServer.getAttribute(oname, attName);
239     } catch (RuntimeMBeanException e) {
240       // UnsupportedOperationExceptions happen in the normal course of business,
241       // so no need to log them as errors all the time.
242       if (e.getCause() instanceof UnsupportedOperationException) {
243         if (LOG.isTraceEnabled()) {
244           LOG.trace("Getting attribute " + attName + " of " + oname + " threw " + e);
245         }
246       } else {
247         LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e);
248       }
249       return;
250     } catch (RuntimeErrorException e) {
251       // RuntimeErrorException happens when an unexpected failure occurs in getAttribute
252       // for example https://issues.apache.org/jira/browse/DAEMON-120
253       LOG.debug("getting attribute "+attName+" of "+oname+" threw an exception", e);
254       return;
255     } catch (AttributeNotFoundException e) {
256       //Ignored the attribute was not found, which should never happen because the bean
257       //just told us that it has this attribute, but if this happens just don't output
258       //the attribute.
259       return;
260     } catch (MBeanException e) {
261       //The code inside the attribute getter threw an exception so log it, and
262       // skip outputting the attribute
263       LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e);
264       return;
265     } catch (RuntimeException e) {
266       //For some reason even with an MBeanException available to them Runtime exceptions
267       //can still find their way through, so treat them the same as MBeanException
268       LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e);
269       return;
270     } catch (ReflectionException e) {
271       //This happens when the code inside the JMX bean (setter?? from the java docs)
272       //threw an exception, so log it and skip outputting the attribute
273       LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e);
274       return;
275     } catch (InstanceNotFoundException e) {
276       //Ignored the mbean itself was not found, which should never happen because we
277       //just accessed it (perhaps something unregistered in-between) but if this
278       //happens just don't output the attribute.
279       return;
280     }
281 
282     writeAttribute(jg, attName, descriptionStr, value);
283   }
284 
285   private static void writeAttribute(JsonGenerator jg, String attName, final String descriptionStr,
286       Object value)
287   throws IOException {
288     boolean description = false;
289     if (descriptionStr != null && descriptionStr.length() > 0 && !attName.equals(descriptionStr)) {
290       description = true;
291       jg.writeFieldName(attName);
292       jg.writeStartObject();
293       jg.writeFieldName("description");
294       jg.writeString(descriptionStr);
295       jg.writeFieldName("value");
296       writeObject(jg, description, value);
297       jg.writeEndObject();
298     } else {
299       jg.writeFieldName(attName);
300       writeObject(jg, description, value);
301     }
302   }
303 
304   private static void writeObject(final JsonGenerator jg, final boolean description, Object value)
305   throws IOException {
306     if(value == null) {
307       jg.writeNull();
308     } else {
309       Class<?> c = value.getClass();
310       if (c.isArray()) {
311         jg.writeStartArray();
312         int len = Array.getLength(value);
313         for (int j = 0; j < len; j++) {
314           Object item = Array.get(value, j);
315           writeObject(jg, description, item);
316         }
317         jg.writeEndArray();
318       } else if(value instanceof Number) {
319         Number n = (Number)value;
320         double doubleValue = n.doubleValue();
321         if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
322           jg.writeString(n.toString());
323         } else {
324           jg.writeNumber(n.toString());
325         }
326       } else if(value instanceof Boolean) {
327         Boolean b = (Boolean)value;
328         jg.writeBoolean(b);
329       } else if(value instanceof CompositeData) {
330         CompositeData cds = (CompositeData)value;
331         CompositeType comp = cds.getCompositeType();
332         Set<String> keys = comp.keySet();
333         jg.writeStartObject();
334         for (String key: keys) {
335           writeAttribute(jg, key, null, cds.get(key));
336         }
337         jg.writeEndObject();
338       } else if(value instanceof TabularData) {
339         TabularData tds = (TabularData)value;
340         jg.writeStartArray();
341         for(Object entry : tds.values()) {
342           writeObject(jg, description, entry);
343         }
344         jg.writeEndArray();
345       } else {
346         jg.writeString(value.toString());
347       }
348     }
349   }
350 
351   /**
352    * Dump out a subset of regionserver mbeans only, not all of them, as json on System.out.
353    * @throws MalformedObjectNameException
354    * @throws IOException
355    */
356   public static String dumpRegionServerMetrics() throws MalformedObjectNameException, IOException {
357     StringWriter sw = new StringWriter(1024 * 100); // Guess this size
358     try (PrintWriter writer = new PrintWriter(sw)) {
359       JSONBean dumper = new JSONBean();
360       try (JSONBean.Writer jsonBeanWriter = dumper.open(writer)) {
361         MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
362         jsonBeanWriter.write(mbeanServer,
363           new ObjectName("java.lang:type=Memory"), null, false);
364         jsonBeanWriter.write(mbeanServer,
365           new ObjectName("Hadoop:service=HBase,name=RegionServer,sub=IPC"), null, false);
366         jsonBeanWriter.write(mbeanServer,
367           new ObjectName("Hadoop:service=HBase,name=RegionServer,sub=Replication"), null, false);
368         jsonBeanWriter.write(mbeanServer,
369           new ObjectName("Hadoop:service=HBase,name=RegionServer,sub=Server"), null, false);
370       }
371     }
372     sw.close();
373     return sw.toString();
374   }
375 
376   /**
377    * Dump out all registered mbeans as json on System.out.
378    * @throws IOException
379    * @throws MalformedObjectNameException
380    */
381   public static void dumpAllBeans() throws IOException, MalformedObjectNameException {
382     try (PrintWriter writer = new PrintWriter(new BufferedWriter(
383         new OutputStreamWriter(System.out, StandardCharsets.UTF_8)))) {
384       JSONBean dumper = new JSONBean();
385       try (JSONBean.Writer jsonBeanWriter = dumper.open(writer)) {
386         MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
387         jsonBeanWriter.write(mbeanServer, new ObjectName("*:*"), null, false);
388       }
389     }
390   }
391 
392   public static void main(String[] args) throws IOException, MalformedObjectNameException {
393     String str = dumpRegionServerMetrics();
394     System.out.println(str);
395   }
396 }