/* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.btrace; /** * This class is a simple implementation of DynamicMBean that exposes * a BTrace class as a MBean. The static fields annotated with @Property * are exposed as MBean attributes. * * @author A. Sundararajan */ import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.Property; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.Descriptor; import javax.management.DynamicMBean; import javax.management.InvalidAttributeValueException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.modelmbean.DescriptorSupport; import javax.management.openmbean.ArrayType; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; /** * This is a simple DynamicMBean implementation that exposes the static * fields of BTrace class as attributes. The fields exposed should be * annotated as {@linkplain Property}. * * @author A. Sundararajan */ public class BTraceMBean implements DynamicMBean { private final Class clazz; private final Map<String, Field> attributes; private final String beanName; private MBeanInfo cachedBeanInfo; public BTraceMBean(Class clazz) { this.clazz = clazz; this.attributes = getJMXAttributes(clazz); this.beanName = getBeanName(clazz); } @Override public synchronized Object getAttribute(String name) throws AttributeNotFoundException { Field field = attributes.get(name); if (field == null) { throw new AttributeNotFoundException("No such property: " + name); } return getFieldValue(field); } @Override public synchronized void setAttribute(Attribute attribute) throws InvalidAttributeValueException, MBeanException, AttributeNotFoundException { throw new MBeanException(new RuntimeException("BTrace attributes are read-only")); } @Override public synchronized AttributeList getAttributes(String[] names) { AttributeList list = new AttributeList(); for (String name : names) { Field field = attributes.get(name); Object value = null; if (field != null) { value = getFieldValue(field); } if (value != null) { list.add(new Attribute(name, value)); } } return list; } @Override public synchronized AttributeList setAttributes(AttributeList list) { // we don't support attribute sets -- return an empty list. return new AttributeList(); } @Override public Object invoke(String name, Object[] args, String[] sig) throws MBeanException, ReflectionException { throw new ReflectionException(new NoSuchMethodException(name)); } @Override public synchronized MBeanInfo getMBeanInfo() { if (cachedBeanInfo != null) { return cachedBeanInfo; } SortedSet<String> names = new TreeSet<>(); for (String name : attributes.keySet()) { names.add((String) name); } MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[names.size()]; Iterator<String> it = names.iterator(); for (int i = 0; i < attrs.length; i++) { String name = it.next(); Field field = attributes.get(name); Property attr = field.getAnnotation(Property.class); String description = attr.description(); if (description.isEmpty()) { description = name; } Descriptor descriptor = new DescriptorSupport(); OpenType ot = OpenTypeUtils.typeToOpenType(field.getGenericType()); if (ot != null) { descriptor.setField("openType", ot); } attrs[i] = new MBeanAttributeInfo( name, field.getType().getName(), description, true, // isReadable false, // isWritable false, // isIs descriptor); } BTrace info = (BTrace) clazz.getAnnotation(BTrace.class); String description = info.description(); if (description.isEmpty()) { description = "BTrace MBean : " + beanName; } cachedBeanInfo = new MBeanInfo( beanName, description, attrs, null, // constructors null, null); // notifications return cachedBeanInfo; } public static void registerMBean(Class clazz) { if (isMBean(clazz)) { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); BTraceMBean bean = new BTraceMBean(clazz); try { ObjectName on = new ObjectName("btrace:name=" + bean.beanName); if (server.isRegistered(on)) { server.unregisterMBean(on); } server.registerMBean(bean, on); } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } } // internals only below this point private static String getBeanName(Class clazz) { BTrace info = (BTrace) clazz.getAnnotation(BTrace.class); String beanName = info.name(); if (beanName.isEmpty()) { beanName = clazz.getName(); } return beanName; } public static boolean isMBean(Class clazz) { // if atleast one field is annotated as @Property, we create MBean for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(Property.class)) { return true; } } return false; } private static Object getFieldValue(Field field) { try { Object value = field.get(null); OpenType ot = OpenTypeUtils.typeToOpenType(field.getGenericType()); if (ot != null) { return OpenTypeUtils.convertToOpenTypeValue(ot, value); } else { // no conversion attempted! return value; } } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } private static Map<String, Field> getJMXAttributes(Class clazz) { try { Map<String, Field> fields = new HashMap<>(); for (Field field : clazz.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers()) && field.isAnnotationPresent(Property.class)) { Property attr = field.getAnnotation(Property.class); if (attr != null) { field.setAccessible(true); String attrName = attr.name(); if (attrName.isEmpty()) { attrName = field.getName(); // remove BTRACE_FIELD_PREFIX ("$") from field name if (attrName.startsWith("$")) { attrName = attrName.substring(1); } } fields.put(attrName, field); } } } return fields; } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } private static class OpenTypeUtils { private static final Map<Class, OpenType> classToOpenTypes = new HashMap<>(); static { classToOpenTypes.put(Byte.TYPE, SimpleType.BYTE); classToOpenTypes.put(Byte.class, SimpleType.BYTE); classToOpenTypes.put(Short.TYPE, SimpleType.SHORT); classToOpenTypes.put(Short.class, SimpleType.SHORT); classToOpenTypes.put(Integer.TYPE, SimpleType.INTEGER); classToOpenTypes.put(Integer.class, SimpleType.INTEGER); classToOpenTypes.put(Long.TYPE, SimpleType.LONG); classToOpenTypes.put(Long.class, SimpleType.LONG); classToOpenTypes.put(Float.TYPE, SimpleType.FLOAT); classToOpenTypes.put(Float.class, SimpleType.FLOAT); classToOpenTypes.put(Double.TYPE, SimpleType.DOUBLE); classToOpenTypes.put(Double.class, SimpleType.DOUBLE); classToOpenTypes.put(Boolean.TYPE, SimpleType.BOOLEAN); classToOpenTypes.put(Boolean.class, SimpleType.BOOLEAN); classToOpenTypes.put(Character.TYPE, SimpleType.CHARACTER); classToOpenTypes.put(Character.class, SimpleType.CHARACTER); classToOpenTypes.put(AtomicInteger.class, SimpleType.INTEGER); classToOpenTypes.put(AtomicLong.class, SimpleType.LONG); classToOpenTypes.put(BigInteger.class, SimpleType.BIGINTEGER); classToOpenTypes.put(BigDecimal.class, SimpleType.BIGDECIMAL); classToOpenTypes.put(String.class, SimpleType.STRING); classToOpenTypes.put(ObjectName.class, SimpleType.OBJECTNAME); classToOpenTypes.put(Date.class, SimpleType.DATE); } private static OpenType typeToOpenType(Type t) { try { // FIXME: This is highly incomplete, revisit... // just enough to get Maps for now. if (t instanceof Class) { Class c = (Class) t; if (Profiler.class.isAssignableFrom(c)) { CompositeType record = new CompositeType("Record", "Profiler record", new String[]{"block", "invocations", "selfTime.total", "selfTime.percent", "selfTime.avg", "selfTime.max", "selfTime.min", "wallTime.total", "wallTime.percent", "wallTime.avg", "wallTime.max", "wallTime.min" }, new String[]{"block", "invocations", "selfTime.total", "selfTime.percent", "selfTime.avg", "selfTime.max", "selfTime.min", "wallTime.total", "wallTime.percent", "wallTime.avg", "wallTime.max", "wallTime.min" }, new OpenType[]{ typeToOpenType(String.class), typeToOpenType(Long.class), typeToOpenType(Long.class), typeToOpenType(Double.class), typeToOpenType(Long.class), typeToOpenType(Long.class), typeToOpenType(Long.class), typeToOpenType(Long.class), typeToOpenType(Double.class), typeToOpenType(Long.class), typeToOpenType(Long.class), typeToOpenType(Long.class) }); CompositeType recordEntry = new CompositeType("Record Entry", "Record map entry", new String[]{"key", "value"}, new String[]{"key", "value"}, new OpenType[]{ typeToOpenType(String.class), record } ); CompositeType snapshot = new CompositeType("Snapshot", "Profiler snapshot", new String[]{"startTime", "lastRefresh", "interval", "data"}, new String[]{"startTime", "lastRefresh", "interval", "data"}, new OpenType[]{ typeToOpenType(Long.class), typeToOpenType(Long.class), typeToOpenType(Long.class), new ArrayType(1, recordEntry) } ); return snapshot; } else { return classToOpenTypes.get(c); } } else if (t instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) t; Type rawType = pt.getRawType(); if (rawType instanceof Class) { Class rt = (Class) rawType; Type[] argTypes = pt.getActualTypeArguments(); if (Map.class.isAssignableFrom(rt)) { OpenType keyType = typeToOpenType(argTypes[0]); OpenType valueType = typeToOpenType(argTypes[1]); if (keyType != null && valueType != null) { CompositeType rowType = new CompositeType("Map", "Map of data", new String[]{"key", "value"}, new String[]{"key", "value"}, new OpenType[]{keyType, valueType}); return new ArrayType(1, rowType); } } } } } catch (OpenDataException ode) { ode.printStackTrace(); } // nothing seems working... return null; } private static Object convertToOpenTypeValue(OpenType ot, Object value) { if (ot instanceof SimpleType) { if (value instanceof AtomicInteger) { return ((AtomicInteger) value).get(); } else if (value instanceof AtomicLong) { return ((AtomicLong) value).get(); } else { return value; } } else if (ot instanceof CompositeType) { // System.err.println("!!! converting val of type " + value.getClass().getName()); // System.err.println("!!! is profiler: " + (value instanceof Profiler)); // System.err.println("!!! is mbean value provider: " + (value instanceof Profiler.MBeanValueProvider)); if (value instanceof Profiler && value instanceof Profiler.MBeanValueProvider) { CompositeType ct = (CompositeType) ot; Profiler.MBeanValueProvider p = (Profiler.MBeanValueProvider)value; Profiler.Snapshot snapshot = p.getMBeanValue(); // System.err.println("!!! snapshot == null :: " + (snapshot == null)); if (snapshot == null) { // System.err.println("!!! NULL snapshot"); try { return new CompositeDataSupport(ct, new String[]{"startTime", "lastRefresh", "interval", "data"}, new Object[]{ convertToOpenTypeValue(ct.getType("startTime"), ((Profiler) p).START_TIME), convertToOpenTypeValue(ct.getType("lastRefresh"), -1L), convertToOpenTypeValue(ct.getType("interval"), 0L), new CompositeData[0] }); } catch (OpenDataException e) { e.printStackTrace(); return null; } } // System.err.println("!!! Snapshot length: " + snapshot.total.length); CompositeData[] total = new CompositeData[snapshot.total.length]; long divider = snapshot.timeInterval * 1000000; // converting ms to ns divider int index = 0; for (Profiler.Record r : snapshot.total) { try { // System.err.println("!!! adding record: " + r); CompositeType at = (CompositeType)((ArrayType)ct.getType("data")).getElementOpenType(); CompositeType rt = (CompositeType)at.getType("value"); CompositeData recordData = new CompositeDataSupport(rt, new String[]{"block", "invocations", "selfTime.total", "selfTime.percent", "selfTime.avg", "selfTime.max", "selfTime.min", "wallTime.total", "wallTime.percent", "wallTime.avg", "wallTime.max", "wallTime.min"}, new Object[]{r.blockName, r.invocations, r.selfTime, (double)(snapshot.timeInterval > 0 ? ((double)r.selfTime / (double)divider) * 100 : 0), r.selfTime / r.invocations, r.selfTimeMax, r.selfTimeMin == Long.MAX_VALUE ? 0 : r.selfTimeMin, r.wallTime, (double)(snapshot.timeInterval > 0 ? ((double)r.wallTime / (double)divider) * 100 : 0), r.wallTime / r.invocations, r.wallTimeMax, r.wallTimeMin}); total[index] = new CompositeDataSupport(at, new String[]{"key", "value"}, new Object[]{r.blockName, recordData} ); } catch (OpenDataException ode) { ode.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } index++; } CompositeData snapshotData = null; try { // System.err.println("!!! creating snapshot data"); snapshotData = new CompositeDataSupport(ct, new String[]{"startTime", "lastRefresh", "interval", "data"}, new Object[]{ convertToOpenTypeValue(ct.getType("startTime"), ((Profiler)p).START_TIME), convertToOpenTypeValue(ct.getType("lastRefresh"), snapshot.timeStamp), convertToOpenTypeValue(ct.getType("interval"), snapshot.timeInterval), total }); } catch (OpenDataException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } // System.err.println("!!! data: " + snapshot); return snapshotData; } } else if (ot instanceof ArrayType) { ArrayType at = (ArrayType) ot; OpenType et = at.getElementOpenType(); if (value instanceof Map && et instanceof CompositeType) { CompositeType ct = (CompositeType) et; Map<Object, Object> map = new HashMap((Map<Object, Object>) value); CompositeData[] array = new CompositeData[map.size()]; OpenType keyType = ct.getType("key"); OpenType valueType = ct.getType("value"); int index = 0; for (Map.Entry<Object, Object> entry : map.entrySet()) { Map<String, Object> row = new HashMap<>(); row.put("key", convertToOpenTypeValue(keyType, entry.getKey())); row.put("value", convertToOpenTypeValue(valueType, entry.getValue())); try { array[index] = new CompositeDataSupport(ct, row); } catch (OpenDataException ode) { ode.printStackTrace(); } index++; } return array; } } return value; } } }