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