From 822ed03826f7136ed48fefffd8a6a1f16432338f Mon Sep 17 00:00:00 2001 From: Jennifer Hickey Date: Tue, 19 May 2009 21:02:43 +0000 Subject: [PATCH] SPR-5256 --- .../AnnotationJmxAttributeSource.java | 15 +++ .../jmx/export/annotation/ManagedMetric.java | 42 ++++++ .../AbstractReflectiveMBeanInfoAssembler.java | 19 +++ .../assembler/MetadataMBeanInfoAssembler.java | 72 ++++++++-- .../export/metadata/JmxAttributeSource.java | 14 ++ .../jmx/export/metadata/ManagedMetric.java | 124 ++++++++++++++++++ .../jmx/support/MetricType.java | 20 +++ .../export/annotation/AnnotationTestBean.java | 14 +- .../AbstractMetadataAssemblerTests.java | 42 +++++- 9 files changed, 349 insertions(+), 13 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/ManagedMetric.java create mode 100644 org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/ManagedMetric.java create mode 100644 org.springframework.context/src/main/java/org/springframework/jmx/support/MetricType.java diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java b/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java index f03f94a520d..66570285784 100644 --- a/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java +++ b/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java @@ -26,6 +26,7 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jmx.export.metadata.InvalidMetadataException; import org.springframework.jmx.export.metadata.JmxAttributeSource; import org.springframework.jmx.export.metadata.ManagedAttribute; +import org.springframework.jmx.export.metadata.ManagedMetric; import org.springframework.jmx.export.metadata.ManagedNotification; import org.springframework.jmx.export.metadata.ManagedOperation; import org.springframework.jmx.export.metadata.ManagedOperationParameter; @@ -41,6 +42,7 @@ import org.springframework.util.StringUtils; * * @author Rob Harrop * @author Juergen Hoeller + * @author Jennifer Hickey * @since 1.2 * @see org.springframework.jmx.export.annotation.ManagedResource * @see org.springframework.jmx.export.annotation.ManagedAttribute @@ -75,6 +77,19 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource { } return managedAttribute; } + + @Override + public ManagedMetric getManagedMetric(Method method) + throws InvalidMetadataException { + org.springframework.jmx.export.annotation.ManagedMetric ann = + AnnotationUtils.getAnnotation(method, org.springframework.jmx.export.annotation.ManagedMetric.class); + if (ann == null) { + return null; + } + ManagedMetric managedMetric = new ManagedMetric(); + AnnotationBeanUtils.copyPropertiesToBean(ann, managedMetric); + return managedMetric; + } public ManagedOperation getManagedOperation(Method method) throws InvalidMetadataException { PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/ManagedMetric.java b/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/ManagedMetric.java new file mode 100644 index 00000000000..0267fc5f540 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/jmx/export/annotation/ManagedMetric.java @@ -0,0 +1,42 @@ +package org.springframework.jmx.export.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.jmx.support.MetricType; + +/** + * JDK 1.5+ method-level annotation that indicates to expose a given bean + * property as JMX attribute, with added Descriptor properties to indicate that + * it is a metric. Only valid when used on a JavaBean getter. + * + * + * @author Jennifer Hickey + * @since 3.0 + * @see org.springframework.jmx.export.metadata.ManagedMetric + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ManagedMetric { + + String category() default ""; + + int currencyTimeLimit() default -1; + + String description() default ""; + + String displayName() default ""; + + MetricType metricType() default MetricType.GAUGE; + + int persistPeriod() default -1; + + String persistPolicy() default ""; + + String unit() default ""; + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java b/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java index 44b795dda38..10a7178b185 100644 --- a/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java +++ b/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java @@ -143,7 +143,26 @@ public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBean * Constant identifier for the persistName field in a JMX {@link Descriptor}. */ protected static final String FIELD_PERSIST_NAME = "persistName"; + + /** + * Constant identifier for the displayName field in a JMX {@link Descriptor}. + */ + protected static final String FIELD_DISPLAY_NAME = "displayName"; + + /** + * Constant identifier for the units field in a JMX {@link Descriptor}. + */ + protected static final String FIELD_UNITS = "units"; + /** + * Constant identifier for the metricType field in a JMX {@link Descriptor}. + */ + protected static final String FIELD_METRIC_TYPE = "metricType"; + + /** + * Constant identifier for the custom metricCategory field in a JMX {@link Descriptor}. + */ + protected static final String FIELD_METRIC_CATEGORY = "metricCategory"; /** * Default value for the JMX field "currencyTimeLimit". diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java b/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java index 85bceb70d6f..91573d90d27 100644 --- a/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java +++ b/org.springframework.context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java @@ -30,10 +30,12 @@ import org.springframework.jmx.export.metadata.InvalidMetadataException; import org.springframework.jmx.export.metadata.JmxAttributeSource; import org.springframework.jmx.export.metadata.JmxMetadataUtils; import org.springframework.jmx.export.metadata.ManagedAttribute; +import org.springframework.jmx.export.metadata.ManagedMetric; import org.springframework.jmx.export.metadata.ManagedNotification; import org.springframework.jmx.export.metadata.ManagedOperation; import org.springframework.jmx.export.metadata.ManagedOperationParameter; import org.springframework.jmx.export.metadata.ManagedResource; +import org.springframework.jmx.support.MetricType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -51,6 +53,7 @@ import org.springframework.util.StringUtils; * * @author Rob Harrop * @author Juergen Hoeller + * @author Jennifer Hickey * @since 1.2 * @see #setAttributeSource * @see org.springframework.jmx.export.metadata.AttributesJmxAttributeSource @@ -60,8 +63,8 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem implements AutodetectCapableMBeanInfoAssembler, InitializingBean { private JmxAttributeSource attributeSource; - - + + /** * Create a new MetadataMBeanInfoAssembler which needs to be * configured through the {@link #setAttributeSource} method. @@ -129,7 +132,7 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem */ @Override protected boolean includeReadAttribute(Method method, String beanKey) { - return hasManagedAttribute(method); + return hasManagedAttribute(method) || hasManagedMetric(method); } /** @@ -166,6 +169,13 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem private boolean hasManagedAttribute(Method method) { return (this.attributeSource.getManagedAttribute(method) != null); } + + /** + * Checks to see if the given Method has the ManagedMetric attribute. + */ + private boolean hasManagedMetric(Method method) { + return (this.attributeSource.getManagedMetric(method) != null); + } /** * Checks to see if the given Method has the ManagedOperation attribute. @@ -207,6 +217,12 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem else if (setter != null && StringUtils.hasText(setter.getDescription())) { return setter.getDescription(); } + + ManagedMetric metric = (readMethod != null) ? this.attributeSource.getManagedMetric(readMethod) : null; + if(metric != null && StringUtils.hasText(metric.getDescription())) { + return metric.getDescription(); + } + return propertyDescriptor.getDisplayName(); } @@ -222,6 +238,10 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem if (ma != null && StringUtils.hasText(ma.getDescription())) { return ma.getDescription(); } + ManagedMetric metric = this.attributeSource.getManagedMetric(method); + if (metric != null && StringUtils.hasText(metric.getDescription())) { + return metric.getDescription(); + } return method.getName(); } else { @@ -314,18 +334,24 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem } /** - * Adds descriptor fields from the ManagedAttribute attribute - * to the attribute descriptor. Specifically, adds the currencyTimeLimit, - * default, persistPolicy and persistPeriod - * descriptor fields if they are present in the metadata. + * Adds descriptor fields from the ManagedAttribute attribute or the ManagedMetric attribute + * to the attribute descriptor. */ @Override protected void populateAttributeDescriptor(Descriptor desc, Method getter, Method setter, String beanKey) { - ManagedAttribute gma = + if(getter != null && hasManagedMetric(getter)) { + populateMetricDescriptor(desc, this.attributeSource.getManagedMetric(getter)); + } + else { + ManagedAttribute gma = (getter == null) ? ManagedAttribute.EMPTY : this.attributeSource.getManagedAttribute(getter); - ManagedAttribute sma = + ManagedAttribute sma = (setter == null) ? ManagedAttribute.EMPTY : this.attributeSource.getManagedAttribute(setter); - + populateAttributeDescriptor(desc,gma,sma); + } + } + + private void populateAttributeDescriptor(Descriptor desc, ManagedAttribute gma, ManagedAttribute sma) { applyCurrencyTimeLimit(desc, resolveIntDescriptor(gma.getCurrencyTimeLimit(), sma.getCurrencyTimeLimit())); Object defaultValue = resolveObjectDescriptor(gma.getDefaultValue(), sma.getDefaultValue()); @@ -340,6 +366,32 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem desc.setField(FIELD_PERSIST_PERIOD, Integer.toString(persistPeriod)); } } + + private void populateMetricDescriptor(Descriptor desc, ManagedMetric metric) { + applyCurrencyTimeLimit(desc, metric.getCurrencyTimeLimit()); + + if (StringUtils.hasLength(metric.getPersistPolicy())) { + desc.setField(FIELD_PERSIST_POLICY, metric.getPersistPolicy()); + } + if (metric.getPersistPeriod() >= 0) { + desc.setField(FIELD_PERSIST_PERIOD, Integer.toString(metric.getPersistPeriod())); + } + + if (StringUtils.hasLength(metric.getDisplayName())) { + desc.setField(FIELD_DISPLAY_NAME, metric.getDisplayName()); + } + + if(StringUtils.hasLength(metric.getUnit())) { + desc.setField(FIELD_UNITS, metric.getUnit()); + } + + if(StringUtils.hasLength(metric.getCategory())) { + desc.setField(FIELD_METRIC_CATEGORY, metric.getCategory()); + } + + String metricType = (metric.getMetricType() == null) ? MetricType.GAUGE.toString() : metric.getMetricType().toString(); + desc.setField(FIELD_METRIC_TYPE, metricType); + } /** * Adds descriptor fields from the ManagedAttribute attribute diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/JmxAttributeSource.java b/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/JmxAttributeSource.java index b5165454762..95177bb84ac 100644 --- a/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/JmxAttributeSource.java +++ b/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/JmxAttributeSource.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; * read source-level metadata from a managed resource's class. * * @author Rob Harrop + * @author Jennifer Hickey * @since 1.2 * @see org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler#setAttributeSource * @see org.springframework.jmx.export.MBeanExporter#setAssembler @@ -48,6 +49,16 @@ public interface JmxAttributeSource { * @throws InvalidMetadataException in case of invalid attributes */ ManagedAttribute getManagedAttribute(Method method) throws InvalidMetadataException; + + /** + * Implementations should return an instance of ManagedMetric + * if the supplied Method has the corresponding metadata. + * Otherwise should return null. + * @param method the method to read the attribute data from + * @return the metric, or null if not found + * @throws InvalidMetadataException in case of invalid attributes + */ + ManagedMetric getManagedMetric(Method method) throws InvalidMetadataException; /** * Implementations should return an instance of ManagedOperation @@ -78,4 +89,7 @@ public interface JmxAttributeSource { * @throws InvalidMetadataException in the case of invalid metadata */ ManagedNotification[] getManagedNotifications(Class clazz) throws InvalidMetadataException; + + + } diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/ManagedMetric.java b/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/ManagedMetric.java new file mode 100644 index 00000000000..1f5f9e0506b --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/jmx/export/metadata/ManagedMetric.java @@ -0,0 +1,124 @@ +package org.springframework.jmx.export.metadata; + +import org.springframework.jmx.support.MetricType; + +/** + * Metadata that indicates to expose a given bean property as a JMX attribute, + * with additional descriptor properties that indicate that the attribute is a + * metric. Only valid when used on a JavaBean getter. + * + * @author Jennifer Hickey + * @since 3.0 + * @see org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler + */ +public class ManagedMetric extends AbstractJmxAttribute { + + private String category = ""; + + private String displayName = ""; + + private MetricType metricType = MetricType.GAUGE; + + private int persistPeriod = -1; + + private String persistPolicy = ""; + + private String unit = ""; + + /** + * + *@return The category of this metric (ex. throughput, performance, utilization) + */ + public String getCategory() { + return category; + } + + /** + * + * @return A display name for this metric + */ + public String getDisplayName() { + return displayName; + } + + /** + * + * @return A description of how this metric's values change over time + */ + public MetricType getMetricType() { + return metricType; + } + + /** + * + * @return The persist period for this metric + */ + public int getPersistPeriod() { + return persistPeriod; + } + + /** + * + * @return The persist policy for this metric + */ + public String getPersistPolicy() { + return persistPolicy; + } + + /** + * + * @return The expected unit of measurement values + */ + public String getUnit() { + return unit; + } + + /** + * + * @param category The category of this metric (ex. throughput, performance, utilization) + */ + public void setCategory(String category) { + this.category = category; + } + + /** + * + * @param displayName A display name for this metric + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + /** + * + * @param metricType A description of how this metric's values change over time + */ + public void setMetricType(MetricType metricType) { + this.metricType = metricType; + } + + /** + * + * @param persistPeriod The persist period for this metric + */ + public void setPersistPeriod(int persistPeriod) { + this.persistPeriod = persistPeriod; + } + + /** + * + * @param persistPolicy The persist policy for this metric + */ + public void setPersistPolicy(String persistPolicy) { + this.persistPolicy = persistPolicy; + } + + /** + * + * @param unit The expected unit of measurement values + */ + public void setUnit(String unit) { + this.unit = unit; + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/jmx/support/MetricType.java b/org.springframework.context/src/main/java/org/springframework/jmx/support/MetricType.java new file mode 100644 index 00000000000..28e47a70be4 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/jmx/support/MetricType.java @@ -0,0 +1,20 @@ +package org.springframework.jmx.support; + + +/** + * Represents how the measurement values of a ManagedMetric will change over time + * @author Jennifer Hickey + * @since 3.0 + * + */ +public enum MetricType { + /** + * The measurement values may go up or down over time + */ + GAUGE, + + /** + * The measurement values will always increase + */ + COUNTER +} diff --git a/org.springframework.context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java b/org.springframework.context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java index 7837975015d..0b93f17e3b8 100644 --- a/org.springframework.context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java +++ b/org.springframework.context/src/test/java/org/springframework/jmx/export/annotation/AnnotationTestBean.java @@ -17,6 +17,7 @@ package org.springframework.jmx.export.annotation; import org.springframework.jmx.IJmxTestBean; +import org.springframework.jmx.support.MetricType; import org.springframework.stereotype.Service; /** @@ -65,7 +66,7 @@ public class AnnotationTestBean implements IJmxTestBean { public String getName() { return name; } - + @ManagedAttribute(description = "The Nick Name Attribute") public void setNickName(String nickName) { this.nickName = nickName; @@ -97,5 +98,16 @@ public class AnnotationTestBean implements IJmxTestBean { public void dontExposeMe() { throw new RuntimeException(); } + + @ManagedMetric(description="The QueueSize metric", currencyTimeLimit = 20, persistPolicy="OnUpdate", persistPeriod=300, + category="utilization", metricType = MetricType.COUNTER, displayName="Queue Size", unit="messages") + public long getQueueSize() { + return 100l; + } + + @ManagedMetric + public int getCacheEntries() { + return 3; + } } diff --git a/org.springframework.context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java b/org.springframework.context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java index 4e14013c938..a73ecd95851 100644 --- a/org.springframework.context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java @@ -40,6 +40,10 @@ import test.interceptor.NopInterceptor; * @author Chris Beams */ public abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemblerTests { + + protected static final String QUEUE_SIZE_METRIC = "QueueSize"; + + protected static final String CACHE_ENTRIES_METRIC = "CacheEntries"; public void testDescription() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -165,15 +169,49 @@ public abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble assertTrue("Not included in autodetection", assembler.includeBean(proxy.getClass(), "some bean name")); } + + public void testMetricDescription() throws Exception { + ModelMBeanInfo inf = getMBeanInfoFromAssembler(); + ModelMBeanAttributeInfo metric = inf.getAttribute(QUEUE_SIZE_METRIC); + ModelMBeanOperationInfo operation = inf.getOperation("getQueueSize"); + assertEquals("The description for the queue size metric is incorrect", + "The QueueSize metric", metric.getDescription()); + assertEquals("The description for the getter operation of the queue size metric is incorrect", + "The QueueSize metric", operation.getDescription()); + } + + public void testMetricDescriptor() throws Exception { + ModelMBeanInfo info = getMBeanInfoFromAssembler(); + Descriptor desc = info.getAttribute(QUEUE_SIZE_METRIC).getDescriptor(); + assertEquals("Currency Time Limit should be 20", "20", desc.getFieldValue("currencyTimeLimit")); + assertEquals("Persist Policy should be OnUpdate", "OnUpdate", desc.getFieldValue("persistPolicy")); + assertEquals("Persist Period should be 300", "300", desc.getFieldValue("persistPeriod")); + assertEquals("Unit should be messages", "messages",desc.getFieldValue("units")); + assertEquals("Display Name should be Queue Size", "Queue Size",desc.getFieldValue("displayName")); + assertEquals("Metric Type should be COUNTER", "COUNTER",desc.getFieldValue("metricType")); + assertEquals("Metric Category should be utilization", "utilization",desc.getFieldValue("metricCategory")); + } + + public void testMetricDescriptorDefaults() throws Exception { + ModelMBeanInfo info = getMBeanInfoFromAssembler(); + Descriptor desc = info.getAttribute(CACHE_ENTRIES_METRIC).getDescriptor(); + assertNull("Currency Time Limit should not be populated", desc.getFieldValue("currencyTimeLimit")); + assertNull("Persist Policy should not be populated", desc.getFieldValue("persistPolicy")); + assertNull("Persist Period should not be populated", desc.getFieldValue("persistPeriod")); + assertNull("Unit should not be populated", desc.getFieldValue("units")); + assertEquals("Display Name should be populated by default via JMX", CACHE_ENTRIES_METRIC,desc.getFieldValue("displayName")); + assertEquals("Metric Type should be GAUGE", "GAUGE",desc.getFieldValue("metricType")); + assertNull("Metric Category should not be populated", desc.getFieldValue("metricCategory")); + } protected abstract String getObjectName(); protected int getExpectedAttributeCount() { - return 4; + return 6; } protected int getExpectedOperationCount() { - return 7; + return 9; } protected IJmxTestBean createJmxTestBean() {