io.micrometer.core.instrument.binder.commonspool2.CommonsObjectPool2Metrics Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micrometer-core Show documentation
Show all versions of micrometer-core Show documentation
Core module of Micrometer containing instrumentation API and implementation
/*
* Copyright 2020 VMware, Inc.
*
* 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
*
* https://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 io.micrometer.core.instrument.binder.commonspool2;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.binder.BaseUnits;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.util.NamedThreadFactory;
import io.micrometer.core.lang.NonNull;
import io.micrometer.core.lang.Nullable;
import io.micrometer.core.util.internal.logging.InternalLogger;
import io.micrometer.core.util.internal.logging.InternalLoggerFactory;
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.ToDoubleFunction;
import static java.util.Collections.emptyList;
/**
* Apache Commons Pool 2.x metrics collected from metrics exposed via the MBeanServer.
* Metrics are exposed for each object pool.
*
* @author Chao Chang
* @since 1.6.0
*/
public class CommonsObjectPool2Metrics implements MeterBinder, AutoCloseable {
private static final InternalLogger log = InternalLoggerFactory.getInstance(CommonsObjectPool2Metrics.class);
private static final String JMX_DOMAIN = "org.apache.commons.pool2";
private static final String METRIC_NAME_PREFIX = "commons.pool2.";
private static final String[] TYPES = new String[] { "GenericObjectPool", "GenericKeyedObjectPool" };
private final ExecutorService executor = Executors
.newSingleThreadExecutor(new NamedThreadFactory("commons-pool-metrics-updater"));
private final MBeanServer mBeanServer;
private final Iterable tags;
private final List notificationListenerCleanUpRunnables = new CopyOnWriteArrayList<>();
public CommonsObjectPool2Metrics() {
this(emptyList());
}
public CommonsObjectPool2Metrics(Iterable tags) {
this(getMBeanServer(), tags);
}
public CommonsObjectPool2Metrics(MBeanServer mBeanServer, Iterable tags) {
this.mBeanServer = mBeanServer;
this.tags = tags;
}
private static MBeanServer getMBeanServer() {
List mBeanServers = MBeanServerFactory.findMBeanServer(null);
if (!mBeanServers.isEmpty()) {
return mBeanServers.get(0);
}
return ManagementFactory.getPlatformMBeanServer();
}
@Override
public void bindTo(@NonNull MeterRegistry registry) {
for (String type : TYPES) {
registerMetricsEventually(type, (o, tags) -> {
registerGaugeForObject(registry, o, "NumIdle", "num.idle", tags,
"The number of instances currently idle in this pool", BaseUnits.OBJECTS);
registerGaugeForObject(registry, o, "NumWaiters", "num.waiters", tags,
"The estimate of the number of threads currently blocked waiting for an object from the pool",
BaseUnits.THREADS);
registerFunctionCounterForObject(registry, o, "CreatedCount", "created", tags,
"The total number of objects created for this pool over the lifetime of the pool",
BaseUnits.OBJECTS);
registerFunctionCounterForObject(registry, o, "BorrowedCount", "borrowed", tags,
"The total number of objects successfully borrowed from this pool over the lifetime of the pool",
BaseUnits.OBJECTS);
registerFunctionCounterForObject(registry, o, "ReturnedCount", "returned", tags,
"The total number of objects returned to this pool over the lifetime of the pool",
BaseUnits.OBJECTS);
registerFunctionCounterForObject(registry, o, "DestroyedCount", "destroyed", tags,
"The total number of objects destroyed by this pool over the lifetime of the pool",
BaseUnits.OBJECTS);
registerFunctionCounterForObject(registry, o, "DestroyedByEvictorCount", "destroyed.by.evictor", tags,
"The total number of objects destroyed by the evictor associated with this pool over the lifetime of the pool",
BaseUnits.OBJECTS);
registerFunctionCounterForObject(registry, o, "DestroyedByBorrowValidationCount",
"destroyed.by.borrow.validation", tags,
"The total number of objects destroyed by this pool as a result of failing validation during borrowObject() over the lifetime of the pool",
BaseUnits.OBJECTS);
registerTimeGaugeForObject(registry, o, "MaxBorrowWaitTimeMillis", "max.borrow.wait", tags,
"The maximum time a thread has waited to borrow objects from the pool");
registerTimeGaugeForObject(registry, o, "MeanActiveTimeMillis", "mean.active", tags,
"The mean time objects are active");
registerTimeGaugeForObject(registry, o, "MeanIdleTimeMillis", "mean.idle", tags,
"The mean time objects are idle");
registerTimeGaugeForObject(registry, o, "MeanBorrowWaitTimeMillis", "mean.borrow.wait", tags,
"The mean time threads wait to borrow an object");
});
}
}
private Iterable nameTag(ObjectName name, String type)
throws AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException {
Tags tags = Tags.of("name", name.getKeyProperty("name"), "type", type);
if (Objects.equals(type, "GenericObjectPool")) {
// for GenericObjectPool, we want to include the name and factoryType as tags
String factoryType = mBeanServer.getAttribute(name, "FactoryType").toString();
tags = Tags.concat(tags, "factoryType", factoryType);
}
return tags;
}
private void registerMetricsEventually(String type, BiConsumer perObject) {
try {
Set objs = mBeanServer.queryNames(new ObjectName(JMX_DOMAIN + ":type=" + type + ",*"), null);
for (ObjectName o : objs) {
Iterable nameTags = emptyList();
try {
nameTags = nameTag(o, type);
}
catch (Exception e) {
log.error("exception in determining name tag", e);
}
perObject.accept(o, Tags.concat(tags, nameTags));
}
}
catch (MalformedObjectNameException e) {
throw new RuntimeException("Error registering commons pool2 based metrics", e);
}
registerNotificationListener(type, perObject);
}
/**
* This notification listener should remain indefinitely since new pools can be added
* at any time.
* @param type The pool type to listen for.
* @param perObject Metric registration handler when a new MBean is created.
*/
private void registerNotificationListener(String type, BiConsumer perObject) {
NotificationListener notificationListener =
// in notification listener, we cannot get attributes for the registered
// object,
// so we do it later time in a separate thread.
(notification, handback) -> {
executor.execute(() -> {
MBeanServerNotification mbs = (MBeanServerNotification) notification;
ObjectName o = mbs.getMBeanName();
Iterable nameTags = emptyList();
int maxTries = 3;
for (int i = 0; i < maxTries; i++) {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
try {
nameTags = nameTag(o, type);
break;
}
catch (AttributeNotFoundException | MBeanException | ReflectionException
| InstanceNotFoundException e) {
if (i == maxTries - 1) {
log.error("can not set name tag", e);
}
}
}
perObject.accept(o, Tags.concat(tags, nameTags));
});
};
NotificationFilter filter = (NotificationFilter) notification -> {
if (!MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(notification.getType()))
return false;
ObjectName obj = ((MBeanServerNotification) notification).getMBeanName();
return obj.getDomain().equals(JMX_DOMAIN) && obj.getKeyProperty("type").equals(type);
};
try {
mBeanServer.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, notificationListener, filter, null);
notificationListenerCleanUpRunnables.add(() -> {
try {
mBeanServer.removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME, notificationListener);
}
catch (InstanceNotFoundException | ListenerNotFoundException ignore) {
}
});
}
catch (InstanceNotFoundException ignore) {
// unable to register MBean listener
}
}
@Override
public void close() {
notificationListenerCleanUpRunnables.forEach(Runnable::run);
executor.shutdown();
}
private void registerGaugeForObject(MeterRegistry registry, ObjectName o, String jmxMetricName, String meterName,
Tags allTags, String description, @Nullable String baseUnit) {
final AtomicReference gauge = new AtomicReference<>();
gauge.set(Gauge
.builder(METRIC_NAME_PREFIX + meterName, mBeanServer,
getJmxAttribute(registry, gauge, o, jmxMetricName))
.description(description).baseUnit(baseUnit).tags(allTags).register(registry));
}
private void registerFunctionCounterForObject(MeterRegistry registry, ObjectName o, String jmxMetricName,
String meterName, Tags allTags, String description, @Nullable String baseUnit) {
final AtomicReference counter = new AtomicReference<>();
counter.set(FunctionCounter
.builder(METRIC_NAME_PREFIX + meterName, mBeanServer,
getJmxAttribute(registry, counter, o, jmxMetricName))
.description(description).baseUnit(baseUnit).tags(allTags).register(registry));
}
private void registerTimeGaugeForObject(MeterRegistry registry, ObjectName o, String jmxMetricName,
String meterName, Tags allTags, String description) {
final AtomicReference timeGauge = new AtomicReference<>();
timeGauge.set(TimeGauge
.builder(METRIC_NAME_PREFIX + meterName, mBeanServer, TimeUnit.MILLISECONDS,
getJmxAttribute(registry, timeGauge, o, jmxMetricName))
.description(description).tags(allTags).register(registry));
}
private ToDoubleFunction getJmxAttribute(MeterRegistry registry,
AtomicReference meter, ObjectName o, String jmxMetricName) {
return s -> safeDouble(() -> {
if (!s.isRegistered(o)) {
registry.remove(meter.get());
}
return s.getAttribute(o, jmxMetricName);
});
}
private double safeDouble(Callable