Commit 710423d1 authored by Dave Syer's avatar Dave Syer Committed by Andy Wilkinson

Add JmxMetricWriter for exporting metric values to MBeans

User can add a @Bean of type JmxMetricWriter and get all values
automatically exported in a form that is usable in jconsole
or jvisualvm.
parent 5edc404a
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.jmx;
import java.util.Hashtable;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.jmx.export.naming.KeyNamingStrategy;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
import org.springframework.util.StringUtils;
/**
* MBean naming strategy for metric keys. A metric name of
* <code>counter.foo.bar.spam</code> translates to an object name with
* <code>type=counter</code>, <code>name=foo</code> and <code>value=bar.spam</code>. This
* results in a more or less pleasing view with no tweaks in jconsole or jvisualvm. The
* domain is copied from the input key and the type in the input key is discarded.
*
* @author Dave Syer
*/
public class DefaultMetricNamingStrategy implements ObjectNamingStrategy {
private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy();
@Override
public ObjectName getObjectName(Object managedBean, String beanKey)
throws MalformedObjectNameException {
ObjectName objectName = this.namingStrategy.getObjectName(managedBean, beanKey);
String domain = objectName.getDomain();
Hashtable<String, String> table = new Hashtable<String, String>(
objectName.getKeyPropertyList());
String name = objectName.getKeyProperty("name");
if (name != null) {
table.remove("name");
String[] parts = StringUtils.delimitedListToStringArray(name, ".");
table.put("type", parts[0]);
if (parts.length > 1) {
table.put(parts.length > 2 ? "name" : "value", parts[1]);
}
if (parts.length > 2) {
table.put("value",
name.substring(parts[0].length() + parts[1].length() + 2));
}
}
return new ObjectName(domain, table);
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.jmx;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.management.MalformedObjectNameException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
/**
* A {@link MetricWriter} for MBeans. Each metric is registered as an individual MBean, so
* (for instance) it can be graphed and monitored. The object names are provided by an
* {@link ObjectNamingStrategy}, where the default is a
* {@link DefaultMetricNamingStrategy} which provides <code>type</code>, <code>name</code>
* and <code>value</code> keys by splitting up the metric name on periods.
*
* @author Dave Syer
*/
@ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
public class JmxMetricWriter implements MetricWriter {
private static Log logger = LogFactory.getLog(JmxMetricWriter.class);
private final ConcurrentMap<String, MetricValue> values = new ConcurrentHashMap<String, MetricValue>();
private final MBeanExporter exporter;
private ObjectNamingStrategy namingStrategy = new DefaultMetricNamingStrategy();
private String domain = "org.springframework.metrics";
public JmxMetricWriter(MBeanExporter exporter) {
this.exporter = exporter;
}
public void setNamingStrategy(ObjectNamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
public void setDomain(String domain) {
this.domain = domain;
}
@ManagedOperation
public void increment(String name, long value) {
increment(new Delta<Long>(name, value));
}
@Override
public void increment(Delta<?> delta) {
MetricValue counter = getValue(delta.getName());
counter.increment(delta.getValue().longValue());
}
@ManagedOperation
public void set(String name, double value) {
set(new Metric<Double>(name, value));
}
@Override
public void set(Metric<?> value) {
MetricValue metric = getValue(value.getName());
metric.setValue(value.getValue().doubleValue());
}
@Override
@ManagedOperation
public void reset(String name) {
MetricValue value = this.values.remove(name);
if (value != null) {
try {
// We can unregister the MBean, but if this writer is on the end of an
// Exporter the chances are it will be re-registered almost immediately.
this.exporter.unregisterManagedResource(this.namingStrategy
.getObjectName(value, getKey(name)));
}
catch (MalformedObjectNameException e) {
logger.warn("Could not unregister MBean for " + name);
}
}
}
private MetricValue getValue(String name) {
if (!this.values.containsKey(name)) {
this.values.putIfAbsent(name, new MetricValue());
MetricValue value = this.values.get(name);
try {
this.exporter.registerManagedResource(value,
this.namingStrategy.getObjectName(value, getKey(name)));
}
catch (Exception e) {
// Could not register mbean, maybe just a race condition
}
}
return this.values.get(name);
}
private String getKey(String name) {
return String.format(this.domain + ":type=MetricValue,name=%s", name);
}
@ManagedResource
public static class MetricValue {
private double value;
private long lastUpdated = 0;
public void setValue(double value) {
if (this.value != value) {
this.lastUpdated = System.currentTimeMillis();
}
this.value = value;
}
public void increment(long value) {
this.lastUpdated = System.currentTimeMillis();
this.value += value;
}
@ManagedAttribute
public double getValue() {
return this.value;
}
@ManagedAttribute
public Date getLastUpdated() {
return new Date(this.lastUpdated);
}
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Metrics integration with JMX.
*/
package org.springframework.boot.actuate.metrics.jmx;
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.jmx;
import javax.management.ObjectName;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Dave Syer
*/
public class DefaultMetricNamingStrategyTests {
private DefaultMetricNamingStrategy strategy = new DefaultMetricNamingStrategy();
@Test
public void simpleName() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
}
@Test
public void onePeriod() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo.bar");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("value"));
}
@Test
public void twoPeriods() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo.bar.spam");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("name"));
assertEquals("Wrong name: " + name, "spam", name.getKeyProperty("value"));
}
@Test
public void threePeriods() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo.bar.spam.bucket");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("name"));
assertEquals("Wrong name: " + name, "spam.bucket", name.getKeyProperty("value"));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment