/*
 * 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.cassandra.metrics;

import java.lang.reflect.Method;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import com.codahale.metrics.*;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.cassandra.utils.MBeanWrapper;

Makes integrating 3.0 metrics API with 2.0.

The 3.0 API comes with poor JMX integration

/** * Makes integrating 3.0 metrics API with 2.0. * <p> * The 3.0 API comes with poor JMX integration * </p> */
public class CassandraMetricsRegistry extends MetricRegistry { public static final CassandraMetricsRegistry Metrics = new CassandraMetricsRegistry(); private final MBeanWrapper mBeanServer = MBeanWrapper.instance; private CassandraMetricsRegistry() { super(); } public Counter counter(MetricName name) { Counter counter = counter(name.getMetricName()); registerMBean(counter, name.getMBeanName()); return counter; } public Counter counter(MetricName name, MetricName alias) { Counter counter = counter(name); registerAlias(name, alias); return counter; } public Meter meter(MetricName name) { Meter meter = meter(name.getMetricName()); registerMBean(meter, name.getMBeanName()); return meter; } public Meter meter(MetricName name, MetricName alias) { Meter meter = meter(name); registerAlias(name, alias); return meter; } public Histogram histogram(MetricName name, boolean considerZeroes) { Histogram histogram = register(name, new ClearableHistogram(new DecayingEstimatedHistogramReservoir(considerZeroes))); registerMBean(histogram, name.getMBeanName()); return histogram; } public Histogram histogram(MetricName name, MetricName alias, boolean considerZeroes) { Histogram histogram = histogram(name, considerZeroes); registerAlias(name, alias); return histogram; } public Timer timer(MetricName name) { Timer timer = register(name, new Timer(new DecayingEstimatedHistogramReservoir())); registerMBean(timer, name.getMBeanName()); return timer; } public Timer timer(MetricName name, MetricName alias) { Timer timer = timer(name); registerAlias(name, alias); return timer; } public <T extends Metric> T register(MetricName name, T metric) { try { register(name.getMetricName(), metric); registerMBean(metric, name.getMBeanName()); return metric; } catch (IllegalArgumentException e) { Metric existing = Metrics.getMetrics().get(name.getMetricName()); return (T)existing; } } public <T extends Metric> T register(MetricName name, MetricName aliasName, T metric) { T ret = register(name, metric); registerAlias(name, aliasName); return ret; } public boolean remove(MetricName name) { boolean removed = remove(name.getMetricName()); try { mBeanServer.unregisterMBean(name.getMBeanName()); } catch (Exception ignore) {} return removed; } public boolean remove(MetricName name, MetricName alias) { if (remove(name)) { removeAlias(alias); return true; } return false; } public void registerMBean(Metric metric, ObjectName name) { AbstractBean mbean; if (metric instanceof Gauge) { mbean = new JmxGauge((Gauge<?>) metric, name); } else if (metric instanceof Counter) { mbean = new JmxCounter((Counter) metric, name); } else if (metric instanceof Histogram) { mbean = new JmxHistogram((Histogram) metric, name); } else if (metric instanceof Meter) { mbean = new JmxMeter((Meter) metric, name, TimeUnit.SECONDS); } else if (metric instanceof Timer) { mbean = new JmxTimer((Timer) metric, name, TimeUnit.SECONDS, TimeUnit.MICROSECONDS); } else { throw new IllegalArgumentException("Unknown metric type: " + metric.getClass()); } try { mBeanServer.registerMBean(mbean, name); } catch (Exception ignored) {} } private void registerAlias(MetricName existingName, MetricName aliasName) { Metric existing = Metrics.getMetrics().get(existingName.getMetricName()); assert existing != null : existingName + " not registered"; registerMBean(existing, aliasName.getMBeanName()); } private void removeAlias(MetricName name) { try { MBeanWrapper.instance.unregisterMBean(name.getMBeanName()); } catch (Exception ignored) {} }
Strips a single final '$' from input
Params:
  • s – String to strip
Returns:a string with one less '$' at end
/** * Strips a single final '$' from input * * @param s String to strip * @return a string with one less '$' at end */
private static String withoutFinalDollar(String s) { int l = s.length(); return (l!=0 && '$' == s.charAt(l-1))?s.substring(0,l-1):s; } public interface MetricMBean { ObjectName objectName(); } private abstract static class AbstractBean implements MetricMBean { private final ObjectName objectName; AbstractBean(ObjectName objectName) { this.objectName = objectName; } @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) { super(objectName); this.metric = metric; } @Override public Object getValue() { return metric.getValue(); } } 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(); } private static class JmxHistogram extends AbstractBean implements JmxHistogramMBean { private final Histogram metric; private JmxHistogram(Histogram metric, ObjectName objectName) { super(objectName); 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(); } } 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) { super(objectName); this.metric = metric; } @Override public long getCount() { return metric.getCount(); } } 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) { super(objectName); this.metric = metric; this.rateFactor = rateUnit.toSeconds(1); this.rateUnit = "events/" + calculateRateUnit(rateUnit); } @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(); } 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) { super(metric, objectName, rateUnit); 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; } }
A value class encapsulating a metric's owning class and name.
/** * A value class encapsulating a metric's owning class and name. */
public static class MetricName implements Comparable<MetricName> { private final String group; private final String type; private final String name; private final String scope; private final String mBeanName;
Creates a new MetricName without a scope.
Params:
/** * Creates a new {@link MetricName} without a scope. * * @param klass the {@link Class} to which the {@link Metric} belongs * @param name the name of the {@link Metric} */
public MetricName(Class<?> klass, String name) { this(klass, name, null); }
Creates a new MetricName without a scope.
Params:
  • group – the group to which the Metric belongs
  • type – the type to which the Metric belongs
  • name – the name of the Metric
/** * Creates a new {@link MetricName} without a scope. * * @param group the group to which the {@link Metric} belongs * @param type the type to which the {@link Metric} belongs * @param name the name of the {@link Metric} */
public MetricName(String group, String type, String name) { this(group, type, name, null); }
Creates a new MetricName without a scope.
Params:
/** * Creates a new {@link MetricName} without a scope. * * @param klass the {@link Class} to which the {@link Metric} belongs * @param name the name of the {@link Metric} * @param scope the scope of the {@link Metric} */
public MetricName(Class<?> klass, String name, String scope) { this(klass.getPackage() == null ? "" : klass.getPackage().getName(), withoutFinalDollar(klass.getSimpleName()), name, scope); }
Creates a new MetricName without a scope.
Params:
  • group – the group to which the Metric belongs
  • type – the type to which the Metric belongs
  • name – the name of the Metric
  • scope – the scope of the Metric
/** * Creates a new {@link MetricName} without a scope. * * @param group the group to which the {@link Metric} belongs * @param type the type to which the {@link Metric} belongs * @param name the name of the {@link Metric} * @param scope the scope of the {@link Metric} */
public MetricName(String group, String type, String name, String scope) { this(group, type, name, scope, createMBeanName(group, type, name, scope)); }
Creates a new MetricName without a scope.
Params:
  • group – the group to which the Metric belongs
  • type – the type to which the Metric belongs
  • name – the name of the Metric
  • scope – the scope of the Metric
  • mBeanName – the 'ObjectName', represented as a string, to use when registering the MBean.
/** * Creates a new {@link MetricName} without a scope. * * @param group the group to which the {@link Metric} belongs * @param type the type to which the {@link Metric} belongs * @param name the name of the {@link Metric} * @param scope the scope of the {@link Metric} * @param mBeanName the 'ObjectName', represented as a string, to use when registering the * MBean. */
public MetricName(String group, String type, String name, String scope, String mBeanName) { if (group == null || type == null) { throw new IllegalArgumentException("Both group and type need to be specified"); } if (name == null) { throw new IllegalArgumentException("Name needs to be specified"); } this.group = group; this.type = type; this.name = name; this.scope = scope; this.mBeanName = mBeanName; }
Returns the group to which the Metric belongs. For class-based metrics, this will be the package name of the Class to which the Metric belongs.
Returns:the group to which the Metric belongs
/** * Returns the group to which the {@link Metric} belongs. For class-based metrics, this will be * the package name of the {@link Class} to which the {@link Metric} belongs. * * @return the group to which the {@link Metric} belongs */
public String getGroup() { return group; }
Returns the type to which the Metric belongs. For class-based metrics, this will be the simple class name of the Class to which the Metric belongs.
Returns:the type to which the Metric belongs
/** * Returns the type to which the {@link Metric} belongs. For class-based metrics, this will be * the simple class name of the {@link Class} to which the {@link Metric} belongs. * * @return the type to which the {@link Metric} belongs */
public String getType() { return type; }
Returns the name of the Metric.
Returns:the name of the Metric
/** * Returns the name of the {@link Metric}. * * @return the name of the {@link Metric} */
public String getName() { return name; } public String getMetricName() { return MetricRegistry.name(group, type, name, scope); }
Returns the scope of the Metric.
Returns:the scope of the Metric
/** * Returns the scope of the {@link Metric}. * * @return the scope of the {@link Metric} */
public String getScope() { return scope; }
Returns true if the Metric has a scope, false otherwise.
Returns:true if the Metric has a scope
/** * Returns {@code true} if the {@link Metric} has a scope, {@code false} otherwise. * * @return {@code true} if the {@link Metric} has a scope */
public boolean hasScope() { return scope != null; }
Returns the MBean name for the Metric identified by this metric name.
Returns:the MBean name
/** * Returns the MBean name for the {@link Metric} identified by this metric name. * * @return the MBean name */
public ObjectName getMBeanName() { String mname = mBeanName; if (mname == null) mname = getMetricName(); try { return new ObjectName(mname); } catch (MalformedObjectNameException e) { try { return new ObjectName(ObjectName.quote(mname)); } catch (MalformedObjectNameException e1) { throw new RuntimeException(e1); } } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final MetricName that = (MetricName) o; return mBeanName.equals(that.mBeanName); } @Override public int hashCode() { return mBeanName.hashCode(); } @Override public String toString() { return mBeanName; } @Override public int compareTo(MetricName o) { return mBeanName.compareTo(o.mBeanName); } private static String createMBeanName(String group, String type, String name, String scope) { final StringBuilder nameBuilder = new StringBuilder(); nameBuilder.append(ObjectName.quote(group)); nameBuilder.append(":type="); nameBuilder.append(ObjectName.quote(type)); if (scope != null) { nameBuilder.append(",scope="); nameBuilder.append(ObjectName.quote(scope)); } if (name.length() > 0) { nameBuilder.append(",name="); nameBuilder.append(ObjectName.quote(name)); } return nameBuilder.toString(); }
If the group is empty, use the package name of the given class. Otherwise use group
Params:
  • group – The group to use by default
  • klass – The class being tracked
Returns:a group for the metric
/** * If the group is empty, use the package name of the given class. Otherwise use group * * @param group The group to use by default * @param klass The class being tracked * @return a group for the metric */
public static String chooseGroup(String group, Class<?> klass) { if (group == null || group.isEmpty()) { group = klass.getPackage() == null ? "" : klass.getPackage().getName(); } return group; }
If the type is empty, use the simple name of the given class. Otherwise use type
Params:
  • type – The type to use by default
  • klass – The class being tracked
Returns:a type for the metric
/** * If the type is empty, use the simple name of the given class. Otherwise use type * * @param type The type to use by default * @param klass The class being tracked * @return a type for the metric */
public static String chooseType(String type, Class<?> klass) { if (type == null || type.isEmpty()) { type = withoutFinalDollar(klass.getSimpleName()); } return type; }
If name is empty, use the name of the given method. Otherwise use name
Params:
  • name – The name to use by default
  • method – The method being tracked
Returns:a name for the metric
/** * If name is empty, use the name of the given method. Otherwise use name * * @param name The name to use by default * @param method The method being tracked * @return a name for the metric */
public static String chooseName(String name, Method method) { if (name == null || name.isEmpty()) { name = method.getName(); } return name; } } }