/*
* Copyright 2012-2017 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 javax.management.ObjectName;
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 name} and
* {@code value} keys by splitting up the metric name on periods.
*
* @author Dave Syer
* @since 1.3.0
*/
@ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
public class JmxMetricWriter implements MetricWriter {
private static final Log logger = LogFactory.getLog(JmxMetricWriter.class);
private final ConcurrentMap<String, MetricValue> values = new ConcurrentHashMap<>();
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<>(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<>(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(getName(name, value));
}
catch (MalformedObjectNameException ex) {
logger.warn("Could not unregister MBean for " + name);
}
}
}
private MetricValue getValue(String name) {
MetricValue value = this.values.get(name);
if (value == null) {
value = new MetricValue();
MetricValue oldValue = this.values.putIfAbsent(name, value);
if (oldValue != null) {
value = oldValue;
}
try {
this.exporter.registerManagedResource(value, getName(name, value));
}
catch (Exception ex) {
// Could not register mbean, maybe just a race condition
}
}
return value;
}
private ObjectName getName(String name, MetricValue value)
throws MalformedObjectNameException {
String key = String.format(this.domain + ":type=MetricValue,name=%s", name);
return this.namingStrategy.getObjectName(value, key);
}
/**
* A single metric value.
*/
@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);
}
}
}