Browse Source
User can add a @Bean of type JmxMetricWriter and get all values automatically exported in a form that is usable in jconsole or jvisualvm.pull/2938/merge
4 changed files with 313 additions and 0 deletions
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,159 @@
@@ -0,0 +1,159 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* 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")); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue