All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.metrics.reporters.jmx.JmxMetricsReporter Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.solr.metrics.reporters.jmx;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metered;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricRegistryListener;
import com.codahale.metrics.Reporter;
import com.codahale.metrics.Timer;
import com.codahale.metrics.jmx.DefaultObjectNameFactory;
import com.codahale.metrics.jmx.ObjectNameFactory;
import java.io.Closeable;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.management.Attribute;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.QueryExp;
import org.apache.solr.metrics.MetricsMap;
import org.apache.solr.metrics.SolrMetricManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is a modified copy of Dropwizard's {@link com.codahale.metrics.jmx.JmxReporter} and classes
 * that it internally uses, with a few important differences:
 *
 * 
    *
  • this class knows that it can directly use {@link MetricsMap} as a dynamic MBean. *
  • this class allows us to "tag" MBean instances so that we can later unregister only * instances registered with the same tag. *
  • this class processes all metrics already existing in the registry at the time when reporter * is started. *
*/ public class JmxMetricsReporter implements Reporter, Closeable { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); public static final String INSTANCE_TAG = "_instanceTag"; public static Builder forRegistry(MetricRegistry registry) { return new Builder(registry); } /** Builder for the {@link JmxMetricsReporter} class. */ public static class Builder { private final MetricRegistry registry; private MBeanServer mBeanServer; private TimeUnit rateUnit; private TimeUnit durationUnit; private ObjectNameFactory objectNameFactory; private MetricFilter filter = MetricFilter.ALL; private String domain; private String tag; private Builder(MetricRegistry registry) { this.registry = registry; this.rateUnit = TimeUnit.SECONDS; this.durationUnit = TimeUnit.MILLISECONDS; this.domain = "metrics"; this.objectNameFactory = new DefaultObjectNameFactory(); } /** * Register MBeans with the given {@link MBeanServer}. * * @param mBeanServer an {@link MBeanServer} * @return {@code this} */ public Builder registerWith(MBeanServer mBeanServer) { this.mBeanServer = mBeanServer; return this; } /** * Convert rates to the given time unit. * * @param rateUnit a unit of time * @return {@code this} */ public Builder convertRatesTo(TimeUnit rateUnit) { this.rateUnit = rateUnit; return this; } public Builder createsObjectNamesWith(ObjectNameFactory onFactory) { if (onFactory == null) { throw new IllegalArgumentException("null objectNameFactory"); } this.objectNameFactory = onFactory; return this; } /** * Convert durations to the given time unit. * * @param durationUnit a unit of time * @return {@code this} */ public Builder convertDurationsTo(TimeUnit durationUnit) { this.durationUnit = durationUnit; return this; } /** * Only report metrics which match the given filter. * * @param filter a {@link MetricFilter} * @return {@code this} */ public Builder filter(MetricFilter filter) { this.filter = filter; return this; } public Builder inDomain(String domain) { this.domain = domain; return this; } public Builder withTag(String tag) { this.tag = tag; return this; } public JmxMetricsReporter build() { if (tag == null) { tag = Integer.toHexString(this.hashCode()); } return new JmxMetricsReporter( mBeanServer, domain, registry, filter, rateUnit, durationUnit, objectNameFactory, tag); } } // MBean interfaces and base classes public interface MetricMBean { ObjectName objectName(); // this strange-looking method name is used for producing "_instanceTag" attribute name String get_instanceTag(); } private abstract static class AbstractBean implements MetricMBean { private final ObjectName objectName; private final String instanceTag; AbstractBean(ObjectName objectName, String instanceTag) { this.objectName = objectName; this.instanceTag = instanceTag; } @Override public String get_instanceTag() { return instanceTag; } @Override public ObjectName objectName() { return objectName; } } public interface JmxGaugeMBean extends MetricMBean { Object getValue(); } private static class JmxGauge extends AbstractBean implements JmxGaugeMBean { private final Gauge metric; private JmxGauge(Gauge metric, ObjectName objectName, String tag) { super(objectName, tag); this.metric = metric; } @Override public Object getValue() { return metric.getValue(); } } public interface JmxCounterMBean extends MetricMBean { long getCount(); } private static class JmxCounter extends AbstractBean implements JmxCounterMBean { private final Counter metric; private JmxCounter(Counter metric, ObjectName objectName, String tag) { super(objectName, tag); this.metric = metric; } @Override public long getCount() { return metric.getCount(); } } public interface JmxHistogramMBean extends MetricMBean { long getCount(); long getMin(); long getMax(); double getMean(); double getStdDev(); double get50thPercentile(); double get75thPercentile(); double get95thPercentile(); double get98thPercentile(); double get99thPercentile(); double get999thPercentile(); long[] values(); long getSnapshotSize(); } private static class JmxHistogram extends AbstractBean implements JmxHistogramMBean { private final Histogram metric; private JmxHistogram(Histogram metric, ObjectName objectName, String tag) { super(objectName, tag); this.metric = metric; } @Override public double get50thPercentile() { return metric.getSnapshot().getMedian(); } @Override public long getCount() { return metric.getCount(); } @Override public long getMin() { return metric.getSnapshot().getMin(); } @Override public long getMax() { return metric.getSnapshot().getMax(); } @Override public double getMean() { return metric.getSnapshot().getMean(); } @Override public double getStdDev() { return metric.getSnapshot().getStdDev(); } @Override public double get75thPercentile() { return metric.getSnapshot().get75thPercentile(); } @Override public double get95thPercentile() { return metric.getSnapshot().get95thPercentile(); } @Override public double get98thPercentile() { return metric.getSnapshot().get98thPercentile(); } @Override public double get99thPercentile() { return metric.getSnapshot().get99thPercentile(); } @Override public double get999thPercentile() { return metric.getSnapshot().get999thPercentile(); } @Override public long[] values() { return metric.getSnapshot().getValues(); } @Override public long getSnapshotSize() { return metric.getSnapshot().size(); } } public interface JmxMeterMBean extends MetricMBean { long getCount(); double getMeanRate(); double getOneMinuteRate(); double getFiveMinuteRate(); double getFifteenMinuteRate(); String getRateUnit(); } private static class JmxMeter extends AbstractBean implements JmxMeterMBean { private final Metered metric; private final double rateFactor; private final String rateUnit; private JmxMeter(Metered metric, ObjectName objectName, TimeUnit rateUnit, String tag) { super(objectName, tag); this.metric = metric; this.rateFactor = rateUnit.toSeconds(1); this.rateUnit = ("events/" + calculateRateUnit(rateUnit)).intern(); } @Override public long getCount() { return metric.getCount(); } @Override public double getMeanRate() { return metric.getMeanRate() * rateFactor; } @Override public double getOneMinuteRate() { return metric.getOneMinuteRate() * rateFactor; } @Override public double getFiveMinuteRate() { return metric.getFiveMinuteRate() * rateFactor; } @Override public double getFifteenMinuteRate() { return metric.getFifteenMinuteRate() * rateFactor; } @Override public String getRateUnit() { return rateUnit; } private String calculateRateUnit(TimeUnit unit) { final String s = unit.toString().toLowerCase(Locale.US); return s.substring(0, s.length() - 1); } } public interface JmxTimerMBean extends JmxMeterMBean { double getMin(); double getMax(); double getMean(); double getStdDev(); double get50thPercentile(); double get75thPercentile(); double get95thPercentile(); double get98thPercentile(); double get99thPercentile(); double get999thPercentile(); long[] values(); String getDurationUnit(); } private static class JmxTimer extends JmxMeter implements JmxTimerMBean { private final Timer metric; private final double durationFactor; private final String durationUnit; private JmxTimer( Timer metric, ObjectName objectName, TimeUnit rateUnit, TimeUnit durationUnit, String tag) { super(metric, objectName, rateUnit, tag); this.metric = metric; this.durationFactor = 1.0 / durationUnit.toNanos(1); this.durationUnit = durationUnit.toString().toLowerCase(Locale.US); } @Override public double get50thPercentile() { return metric.getSnapshot().getMedian() * durationFactor; } @Override public double getMin() { return metric.getSnapshot().getMin() * durationFactor; } @Override public double getMax() { return metric.getSnapshot().getMax() * durationFactor; } @Override public double getMean() { return metric.getSnapshot().getMean() * durationFactor; } @Override public double getStdDev() { return metric.getSnapshot().getStdDev() * durationFactor; } @Override public double get75thPercentile() { return metric.getSnapshot().get75thPercentile() * durationFactor; } @Override public double get95thPercentile() { return metric.getSnapshot().get95thPercentile() * durationFactor; } @Override public double get98thPercentile() { return metric.getSnapshot().get98thPercentile() * durationFactor; } @Override public double get99thPercentile() { return metric.getSnapshot().get99thPercentile() * durationFactor; } @Override public double get999thPercentile() { return metric.getSnapshot().get999thPercentile() * durationFactor; } @Override public long[] values() { return metric.getSnapshot().getValues(); } @Override public String getDurationUnit() { return durationUnit; } } private static class JmxListener implements MetricRegistryListener { private final String name; private final MBeanServer mBeanServer; private final MetricFilter filter; private final TimeUnit rateUnit; private final TimeUnit durationUnit; private final Map registered; private final ObjectNameFactory objectNameFactory; private final String tag; private final QueryExp exp; private JmxListener( MBeanServer mBeanServer, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit, ObjectNameFactory objectNameFactory, String tag) { this.mBeanServer = mBeanServer; this.name = name; this.filter = filter; this.rateUnit = rateUnit; this.durationUnit = durationUnit; this.registered = new ConcurrentHashMap<>(); this.objectNameFactory = objectNameFactory; this.tag = tag; this.exp = Query.eq(Query.attr(INSTANCE_TAG), Query.value(tag)); } private void registerMBean(Object mBean, ObjectName objectName) throws InstanceAlreadyExistsException, JMException { // remove previous bean if exists if (mBeanServer.isRegistered(objectName)) { if (log.isDebugEnabled()) { Set objects = mBeanServer.queryMBeans(objectName, null); if (log.isDebugEnabled()) { log.debug( "## removing existing {} bean(s) for {}, current tag={}:", objects.size(), objectName.getCanonicalName(), tag); } for (ObjectInstance inst : objects) { if (log.isDebugEnabled()) { log.debug( "## - tag={}", mBeanServer.getAttribute(inst.getObjectName(), INSTANCE_TAG)); } } } mBeanServer.unregisterMBean(objectName); } ObjectInstance objectInstance = mBeanServer.registerMBean(mBean, objectName); if (objectInstance != null) { // the websphere mbeanserver rewrites the objectname to include // cell, node & server info // make sure we capture the new objectName for unregistration registered.put(objectName, objectInstance.getObjectName()); } else { registered.put(objectName, objectName); } if (log.isDebugEnabled()) { log.debug( "## registered {}, tag={}", objectInstance.getObjectName().getCanonicalName(), tag); } } private void unregisterMBean(ObjectName originalObjectName) throws InstanceNotFoundException, MBeanRegistrationException { ObjectName objectName = registered.remove(originalObjectName); if (objectName == null) { objectName = originalObjectName; } Set objects = mBeanServer.queryMBeans(objectName, exp); for (ObjectInstance o : objects) { if (log.isDebugEnabled()) { log.debug("## Unregistered {}, tag={}", o.getObjectName().getCanonicalName(), tag); } mBeanServer.unregisterMBean(o.getObjectName()); } } @Override public void onGaugeAdded(String name, Gauge gauge) { try { if (filter.matches(name, gauge)) { final ObjectName objectName = createName("gauges", name); if (gauge instanceof SolrMetricManager.GaugeWrapper && ((SolrMetricManager.GaugeWrapper) gauge).getGauge() instanceof MetricsMap) { MetricsMap mm = (MetricsMap) ((SolrMetricManager.GaugeWrapper) gauge).getGauge(); mm.setAttribute(new Attribute(INSTANCE_TAG, tag)); // don't wrap it in a JmxGauge, it already supports all necessary JMX attributes registerMBean(mm, objectName); } else { registerMBean(new JmxGauge(gauge, objectName, tag), objectName); } } } catch (InstanceAlreadyExistsException e) { log.debug("Unable to register gauge", e); } catch (JMException e) { log.warn("Unable to register gauge", e); } } @Override public void onGaugeRemoved(String name) { try { final ObjectName objectName = createName("gauges", name); unregisterMBean(objectName); } catch (InstanceNotFoundException e) { log.debug("Unable to unregister gauge", e); } catch (MBeanRegistrationException e) { log.warn("Unable to unregister gauge", e); } } @Override public void onCounterAdded(String name, Counter counter) { try { if (filter.matches(name, counter)) { final ObjectName objectName = createName("counters", name); registerMBean(new JmxCounter(counter, objectName, tag), objectName); } } catch (InstanceAlreadyExistsException e) { log.debug("Unable to register counter", e); } catch (JMException e) { log.warn("Unable to register counter", e); } } @Override public void onCounterRemoved(String name) { try { final ObjectName objectName = createName("counters", name); unregisterMBean(objectName); } catch (InstanceNotFoundException e) { log.debug("Unable to unregister counter", e); } catch (MBeanRegistrationException e) { log.warn("Unable to unregister counter", e); } } @Override public void onHistogramAdded(String name, Histogram histogram) { try { if (filter.matches(name, histogram)) { final ObjectName objectName = createName("histograms", name); registerMBean(new JmxHistogram(histogram, objectName, tag), objectName); } } catch (InstanceAlreadyExistsException e) { log.debug("Unable to register histogram", e); } catch (JMException e) { log.warn("Unable to register histogram", e); } } @Override public void onHistogramRemoved(String name) { try { final ObjectName objectName = createName("histograms", name); unregisterMBean(objectName); } catch (InstanceNotFoundException e) { log.debug("Unable to unregister histogram", e); } catch (MBeanRegistrationException e) { log.warn("Unable to unregister histogram", e); } } @Override public void onMeterAdded(String name, Meter meter) { try { if (filter.matches(name, meter)) { final ObjectName objectName = createName("meters", name); registerMBean(new JmxMeter(meter, objectName, rateUnit, tag), objectName); } } catch (InstanceAlreadyExistsException e) { log.debug("Unable to register meter", e); } catch (JMException e) { log.warn("Unable to register meter", e); } } @Override public void onMeterRemoved(String name) { try { final ObjectName objectName = createName("meters", name); unregisterMBean(objectName); } catch (InstanceNotFoundException e) { log.debug("Unable to unregister meter", e); } catch (MBeanRegistrationException e) { log.warn("Unable to unregister meter", e); } } @Override public void onTimerAdded(String name, Timer timer) { try { if (filter.matches(name, timer)) { final ObjectName objectName = createName("timers", name); registerMBean(new JmxTimer(timer, objectName, rateUnit, durationUnit, tag), objectName); } } catch (InstanceAlreadyExistsException e) { log.debug("Unable to register timer", e); } catch (JMException e) { log.warn("Unable to register timer", e); } } @Override public void onTimerRemoved(String name) { try { final ObjectName objectName = createName("timers", name); unregisterMBean(objectName); } catch (InstanceNotFoundException e) { log.debug("Unable to unregister timer", e); } catch (MBeanRegistrationException e) { log.warn("Unable to unregister timer", e); } } private ObjectName createName(String type, String name) { return objectNameFactory.createName(type, this.name, name); } void unregisterAll() { for (ObjectName name : registered.keySet()) { try { unregisterMBean(name); } catch (InstanceNotFoundException e) { log.debug("Unable to unregister metric", e); } catch (MBeanRegistrationException e) { log.warn("Unable to unregister metric", e); } } registered.clear(); } } private final MetricRegistry registry; private final JmxListener listener; private JmxMetricsReporter( MBeanServer mBeanServer, String domain, MetricRegistry registry, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit, ObjectNameFactory objectNameFactory, String tag) { this.registry = registry; this.listener = new JmxListener( mBeanServer, domain, filter, rateUnit, durationUnit, objectNameFactory, tag); } public void start() { registry.addListener(listener); // process existing metrics Map metrics = new HashMap<>(registry.getMetrics()); metrics.forEach( (k, v) -> { if (v instanceof Counter) { listener.onCounterAdded(k, (Counter) v); } else if (v instanceof Meter) { listener.onMeterAdded(k, (Meter) v); } else if (v instanceof Histogram) { listener.onHistogramAdded(k, (Histogram) v); } else if (v instanceof Timer) { listener.onTimerAdded(k, (Timer) v); } else if (v instanceof Gauge) { listener.onGaugeAdded(k, (Gauge) v); } else { log.warn("Unknown metric type {} for metric '{}', ignoring", v.getClass().getName(), k); } }); } @Override public void close() { registry.removeListener(listener); listener.unregisterAll(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy