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