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

org.apache.solr.util.stats.MetricUtils 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.util.stats;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.InstrumentedExecutorService;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.PlatformManagedObject;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.solr.common.ConditionalKeyMapWriter;
import org.apache.solr.common.IteratorWriter;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.metrics.AggregateMetric;
import org.apache.solr.metrics.SolrMetricManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Metrics specific utility functions. */
public class MetricUtils {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  public static final String METRIC_NAME = "metric";
  public static final String VALUE = "value";
  public static final String VALUES = "values";

  static final String MS = "_ms";

  static final String MIN = "min";
  static final String MIN_MS = MIN + MS;
  static final String MAX = "max";
  static final String MAX_MS = MAX + MS;
  static final String MEAN = "mean";
  static final String MEAN_MS = MEAN + MS;
  static final String MEDIAN = "median";
  static final String MEDIAN_MS = MEDIAN + MS;
  static final String STDDEV = "stddev";
  static final String STDDEV_MS = STDDEV + MS;
  static final String SUM = "sum";
  static final String P75 = "p75";
  static final String P75_MS = P75 + MS;
  static final String P95 = "p95";
  static final String P95_MS = P95 + MS;
  static final String P99 = "p99";
  static final String P99_MS = P99 + MS;
  static final String P999 = "p999";
  static final String P999_MS = P999 + MS;

  public static final Predicate ALL_PROPERTIES = (name) -> true;

  /**
   * Adds metrics from a Timer to a NamedList, using well-known back-compat names.
   *
   * @param lst The NamedList to add the metrics data to
   * @param timer The Timer to extract the metrics from
   */
  public static void addMetrics(NamedList lst, Timer timer) {
    Snapshot snapshot = timer.getSnapshot();
    lst.add("avgRequestsPerSecond", timer.getMeanRate());
    lst.add("5minRateRequestsPerSecond", timer.getFiveMinuteRate());
    lst.add("15minRateRequestsPerSecond", timer.getFifteenMinuteRate());
    lst.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
    lst.add("medianRequestTime", nsToMs(snapshot.getMedian()));
    lst.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
    lst.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
    lst.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
    lst.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
  }

  /**
   * Converts a double representing nanoseconds to a double representing milliseconds.
   *
   * @param ns the amount of time in nanoseconds
   * @return the amount of time in milliseconds
   */
  public static double nsToMs(double ns) {
    return ns / TimeUnit.MILLISECONDS.toNanos(1);
  }

  /**
   * Provides a representation of the given metric registry as {@link SolrInputDocument}-s. Only
   * those metrics are converted which match at least one of the given MetricFilter instances.
   *
   * @param registry the {@link MetricRegistry} to be converted
   * @param shouldMatchFilters a list of {@link MetricFilter} instances. A metric must match any
   *     one of the filters from this list to be included in the output
   * @param mustMatchFilter a {@link MetricFilter}. A metric must match this filter to be
   *     included in the output.
   * @param propertyFilter limit what properties of a metric are returned
   * @param skipHistograms discard any {@link Histogram}-s and histogram parts of {@link Timer}-s.
   * @param skipAggregateValues discard internal values of {@link AggregateMetric}-s.
   * @param compact use compact representation for counters and gauges.
   * @param metadata optional metadata. If not null and not empty then this map will be added under
   *     a {@code _metadata_} key.
   * @param consumer consumer that accepts produced {@link SolrInputDocument}-s
   */
  public static void toSolrInputDocuments(
      MetricRegistry registry,
      List shouldMatchFilters,
      MetricFilter mustMatchFilter,
      Predicate propertyFilter,
      boolean skipHistograms,
      boolean skipAggregateValues,
      boolean compact,
      Map metadata,
      Consumer consumer) {
    boolean addMetadata = metadata != null && !metadata.isEmpty();
    toMaps(
        registry,
        shouldMatchFilters,
        mustMatchFilter,
        propertyFilter,
        skipHistograms,
        skipAggregateValues,
        compact,
        false,
        (k, v) -> {
          SolrInputDocument doc = new SolrInputDocument();
          doc.setField(METRIC_NAME, k);
          toSolrInputDocument(null, doc, v);
          if (addMetadata) {
            toSolrInputDocument(null, doc, metadata);
          }
          consumer.accept(doc);
        });
  }

  /**
   * Fill in a SolrInputDocument with values from a converted metric, recursively.
   *
   * @param prefix prefix to add to generated field names, or null if none.
   * @param doc document to fill
   * @param o an instance of converted metric, either a Map or a flat Object
   */
  static void toSolrInputDocument(String prefix, SolrInputDocument doc, Object o) {
    final BiConsumer consumer =
        (k, v) -> {
          if ((v instanceof Map) || (v instanceof MapWriter) || (v instanceof IteratorWriter)) {
            toSolrInputDocument(k.toString(), doc, v);
          } else {
            String key = prefix != null ? prefix + "." + k : k.toString();
            doc.addField(key, v);
          }
        };
    if (o instanceof MapWriter) {
      MapWriter writer = (MapWriter) o;
      writer._forEachEntry(consumer);
    } else if (o instanceof Map) {
      @SuppressWarnings({"unchecked"})
      Map map = (Map) o;
      for (Map.Entry entry : map.entrySet()) {
        consumer.accept(entry.getKey(), entry.getValue());
      }
    } else if (o instanceof IteratorWriter) {
      IteratorWriter writer = (IteratorWriter) o;
      final String name = prefix != null ? prefix : "value";
      try {
        writer.writeIter(
            new IteratorWriter.ItemWriter() {
              @Override
              public IteratorWriter.ItemWriter add(Object o) {
                consumer.accept(name, o);
                return this;
              }
            });
      } catch (IOException e) {
        throw new RuntimeException("this should never happen", e);
      }
    } else {
      String key = prefix != null ? prefix : VALUE;
      doc.addField(key, o);
    }
  }

  /**
   * Convert selected metrics to maps or to flattened objects.
   *
   * @param registry source of metrics
   * @param shouldMatchFilters metrics must match any of these filters
   * @param mustMatchFilter metrics must match this filter
   * @param propertyFilter limit what properties of a metric are returned
   * @param skipHistograms discard any {@link Histogram}-s and histogram parts of {@link Timer}-s.
   * @param skipAggregateValues discard internal values of {@link AggregateMetric}-s.
   * @param compact use compact representation for counters and gauges.
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  public static void toMaps(
      MetricRegistry registry,
      List shouldMatchFilters,
      MetricFilter mustMatchFilter,
      Predicate propertyFilter,
      boolean skipHistograms,
      boolean skipAggregateValues,
      boolean compact,
      boolean simple,
      BiConsumer consumer) {
    final Map metrics = registry.getMetrics();
    final SortedSet names = registry.getNames();
    names.stream()
        .filter(
            s ->
                shouldMatchFilters.stream()
                    .anyMatch(metricFilter -> metricFilter.matches(s, metrics.get(s))))
        .filter(s -> mustMatchFilter.matches(s, metrics.get(s)))
        .forEach(
            n -> {
              Metric metric = metrics.get(n);
              convertMetric(
                  n,
                  metric,
                  propertyFilter,
                  skipHistograms,
                  skipAggregateValues,
                  compact,
                  simple,
                  ".",
                  consumer);
            });
  }

  /**
   * Convert selected metrics from a registry into a map, with metrics in a compact AND simple
   * format.
   *
   * @param registry registry
   * @param names metric names
   * @return map where keys are metric names (if they were present in the registry) and values are
   *     converted metrics in simplified format.
   */
  public static Map convertMetrics(
      MetricRegistry registry, Collection names) {
    final Map metrics = new HashMap<>();
    convertMetrics(registry, names, false, true, true, true, (k, v) -> metrics.put(k, v));
    return metrics;
  }

  /**
   * Convert selected metrics from a registry into maps (when compact==false) or
   * flattened objects.
   *
   * @param registry registry
   * @param names metric names
   * @param skipHistograms discard any {@link Histogram}-s and histogram parts of {@link Timer}-s.
   * @param skipAggregateValues discard internal values of {@link AggregateMetric}-s.
   * @param compact use compact representation for counters and gauges.
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  public static void convertMetrics(
      MetricRegistry registry,
      Collection names,
      boolean skipHistograms,
      boolean skipAggregateValues,
      boolean compact,
      boolean simple,
      BiConsumer consumer) {
    final Map metrics = registry.getMetrics();
    names.stream()
        .forEach(
            n -> {
              Metric metric = metrics.get(n);
              convertMetric(
                  n,
                  metric,
                  (s) -> true,
                  skipHistograms,
                  skipAggregateValues,
                  compact,
                  simple,
                  ".",
                  consumer);
            });
  }

  /**
   * Convert a single instance of metric into a map or flattened object.
   *
   * @param n metric name
   * @param metric metric instance
   * @param propertyFilter limit what properties of a metric are returned
   * @param skipHistograms discard any {@link Histogram}-s and histogram parts of {@link Timer}-s.
   * @param skipAggregateValues discard internal values of {@link AggregateMetric}-s.
   * @param compact use compact representation for counters and gauges.
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  public static void convertMetric(
      String n,
      Metric metric,
      Predicate propertyFilter,
      boolean skipHistograms,
      boolean skipAggregateValues,
      boolean compact,
      boolean simple,
      String separator,
      BiConsumer consumer) {
    if (metric instanceof Counter) {
      Counter counter = (Counter) metric;
      convertCounter(n, counter, propertyFilter, compact, consumer);
    } else if (metric instanceof Gauge) {
      Gauge gauge = (Gauge) metric;
      // unwrap if needed
      if (gauge instanceof SolrMetricManager.GaugeWrapper) {
        gauge = ((SolrMetricManager.GaugeWrapper) gauge).getGauge();
      }
      try {
        if (gauge instanceof MapWriter) {
          convertMapWriter(
              n, (MapWriter) gauge, propertyFilter, simple, compact, separator, consumer);
        } else {
          convertGauge(n, gauge, propertyFilter, simple, compact, separator, consumer);
        }
      } catch (InternalError ie) {
        if (n.startsWith("memory.") && ie.getMessage().contains("Memory Pool not found")) {
          log.warn("Error converting gauge '{}', possible JDK bug: SOLR-10362", n, ie);
          consumer.accept(n, null);
        } else {
          throw ie;
        }
      }
    } else if (metric instanceof Meter) {
      Meter meter = (Meter) metric;
      convertMeter(n, meter, propertyFilter, simple, separator, consumer);
    } else if (metric instanceof Timer) {
      Timer timer = (Timer) metric;
      convertTimer(n, timer, propertyFilter, skipHistograms, simple, separator, consumer);
    } else if (metric instanceof Histogram) {
      if (!skipHistograms) {
        Histogram histogram = (Histogram) metric;
        convertHistogram(n, histogram, propertyFilter, simple, separator, consumer);
      }
    } else if (metric instanceof AggregateMetric) {
      convertAggregateMetric(
          n,
          (AggregateMetric) metric,
          propertyFilter,
          skipAggregateValues,
          simple,
          separator,
          consumer);
    }
  }

  /**
   * Convert an instance of {@link AggregateMetric}.
   *
   * @param name metric name
   * @param metric an instance of {@link AggregateMetric}
   * @param propertyFilter limit what properties of a metric are returned
   * @param skipAggregateValues discard internal values of {@link AggregateMetric}-s.
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  static void convertAggregateMetric(
      String name,
      AggregateMetric metric,
      Predicate propertyFilter,
      boolean skipAggregateValues,
      boolean simple,
      String separator,
      BiConsumer consumer) {
    if (simple) {
      if (propertyFilter.test(MEAN)) {
        consumer.accept(name + separator + MEAN, metric.getMean());
      }
    } else {
      MapWriter writer =
          ew -> {
            BiConsumer filter =
                (k, v) -> {
                  if (propertyFilter.test(k)) {
                    ew.putNoEx(k, v);
                  }
                };
            filter.accept("count", metric.size());
            filter.accept(MAX, metric.getMax());
            filter.accept(MIN, metric.getMin());
            filter.accept(MEAN, metric.getMean());
            filter.accept(STDDEV, metric.getStdDev());
            filter.accept(SUM, metric.getSum());
            if (!(metric.isEmpty() || skipAggregateValues)) {
              ew.putNoEx(
                  VALUES,
                  (MapWriter)
                      ew1 -> {
                        metric
                            .getValues()
                            .forEach(
                                (k, v) -> {
                                  ew1.putNoEx(
                                      k,
                                      (MapWriter)
                                          ew2 -> {
                                            ew2.putNoEx("value", v.value);
                                            ew2.putNoEx("updateCount", v.updateCount.get());
                                          });
                                });
                      });
            }
          };
      if (writer._size() > 0) {
        consumer.accept(name, writer);
      }
    }
  }

  /**
   * Convert an instance of {@link Histogram}. NOTE: it's assumed that histogram contains non-time
   * based values that don't require unit conversion.
   *
   * @param name metric name
   * @param histogram an instance of {@link Histogram}
   * @param propertyFilter limit what properties of a metric are returned
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  static void convertHistogram(
      String name,
      Histogram histogram,
      Predicate propertyFilter,
      boolean simple,
      String separator,
      BiConsumer consumer) {
    Snapshot snapshot = histogram.getSnapshot();
    if (simple) {
      if (propertyFilter.test(MEAN)) {
        consumer.accept(name + separator + MEAN, snapshot.getMean());
      }
    } else {
      MapWriter writer =
          ew -> {
            String prop = "count";
            if (propertyFilter.test(prop)) {
              ew.putNoEx(prop, histogram.getCount());
            }
            // non-time based values
            addSnapshot(ew, snapshot, propertyFilter, false);
          };
      consumer.accept(name, writer);
    }
  }

  // optionally convert ns to ms
  static double nsToMs(boolean convert, double value) {
    if (convert) {
      return nsToMs(value);
    } else {
      return value;
    }
  }

  // some snapshots represent time in ns, other snapshots represent raw values (eg. chunk size)
  static void addSnapshot(
      MapWriter.EntryWriter ew,
      Snapshot snapshot,
      Predicate propertyFilter,
      boolean ms) {
    BiConsumer filter =
        (k, v) -> {
          if (propertyFilter.test(k)) {
            ew.putNoEx(k, v);
          }
        };
    filter.accept((ms ? MIN_MS : MIN), nsToMs(ms, (double) snapshot.getMin()));
    filter.accept((ms ? MAX_MS : MAX), nsToMs(ms, (double) snapshot.getMax()));
    filter.accept((ms ? MEAN_MS : MEAN), nsToMs(ms, snapshot.getMean()));
    filter.accept((ms ? MEDIAN_MS : MEDIAN), nsToMs(ms, snapshot.getMedian()));
    filter.accept((ms ? STDDEV_MS : STDDEV), nsToMs(ms, snapshot.getStdDev()));
    filter.accept((ms ? P75_MS : P75), nsToMs(ms, snapshot.get75thPercentile()));
    filter.accept((ms ? P95_MS : P95), nsToMs(ms, snapshot.get95thPercentile()));
    filter.accept((ms ? P99_MS : P99), nsToMs(ms, snapshot.get99thPercentile()));
    filter.accept((ms ? P999_MS : P999), nsToMs(ms, snapshot.get999thPercentile()));
  }

  /**
   * Convert a {@link Timer} to a map.
   *
   * @param name metric name
   * @param timer timer instance
   * @param propertyFilter limit what properties of a metric are returned
   * @param skipHistograms if true then discard the histogram part of the timer.
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  public static void convertTimer(
      String name,
      Timer timer,
      Predicate propertyFilter,
      boolean skipHistograms,
      boolean simple,
      String separator,
      BiConsumer consumer) {
    if (simple) {
      String prop = "meanRate";
      if (propertyFilter.test(prop)) {
        consumer.accept(name + separator + prop, timer.getMeanRate());
      }
    } else {
      MapWriter writer =
          ew -> {
            BiConsumer filter =
                (k, v) -> {
                  if (propertyFilter.test(k)) {
                    ew.putNoEx(k, v);
                  }
                };
            filter.accept("count", timer.getCount());
            filter.accept("meanRate", timer.getMeanRate());
            filter.accept("1minRate", timer.getOneMinuteRate());
            filter.accept("5minRate", timer.getFiveMinuteRate());
            filter.accept("15minRate", timer.getFifteenMinuteRate());
            if (!skipHistograms) {
              // time-based values in nanoseconds
              addSnapshot(ew, timer.getSnapshot(), propertyFilter, true);
            }
          };
      if (writer._size() > 0) {
        consumer.accept(name, writer);
      }
    }
  }

  /**
   * Convert a {@link Meter} to a map.
   *
   * @param name metric name
   * @param meter meter instance
   * @param propertyFilter limit what properties of a metric are returned
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param consumer consumer that accepts produced objects
   */
  static void convertMeter(
      String name,
      Meter meter,
      Predicate propertyFilter,
      boolean simple,
      String separator,
      BiConsumer consumer) {
    if (simple) {
      if (propertyFilter.test("count")) {
        consumer.accept(name + separator + "count", meter.getCount());
      }
    } else {
      MapWriter writer =
          ew -> {
            BiConsumer filter =
                (k, v) -> {
                  if (propertyFilter.test(k)) {
                    ew.putNoEx(k, v);
                  }
                };
            filter.accept("count", meter.getCount());
            filter.accept("meanRate", meter.getMeanRate());
            filter.accept("1minRate", meter.getOneMinuteRate());
            filter.accept("5minRate", meter.getFiveMinuteRate());
            filter.accept("15minRate", meter.getFifteenMinuteRate());
          };
      if (writer._size() > 0) {
        consumer.accept(name, writer);
      }
    }
  }

  static void convertMapWriter(
      String name,
      MapWriter metric,
      Predicate propertyFilter,
      boolean simple,
      boolean compact,
      String separator,
      BiConsumer consumer) {
    ConditionalKeyMapWriter filteredMetric = new ConditionalKeyMapWriter(metric, propertyFilter);
    if (compact || simple) {
      if (simple) {
        filteredMetric._forEachEntry((k, v) -> consumer.accept(name + separator + k, v));
      } else {
        if (filteredMetric._size() > 0) {
          consumer.accept(name, filteredMetric);
        }
      }
    } else {
      if (filteredMetric._size() > 0) {
        consumer.accept(name, (MapWriter) ew -> ew.putNoEx("value", filteredMetric));
      }
    }
  }

  /**
   * Convert a {@link Gauge}.
   *
   * @param name metric name
   * @param gauge gauge instance
   * @param propertyFilter limit what properties of a metric are returned
   * @param simple use simplified representation for complex metrics - instead of a (name, map) only
   *     the selected (name "." key, value) pairs will be produced.
   * @param compact if true then only return {@link Gauge#getValue()}. If false then return a map
   *     with a "value" field.
   * @param consumer consumer that accepts produced objects
   */
  static void convertGauge(
      String name,
      Gauge gauge,
      Predicate propertyFilter,
      boolean simple,
      boolean compact,
      String separator,
      BiConsumer consumer) {
    if (compact || simple) {
      Object o = gauge.getValue();
      if (o instanceof Map) {
        if (simple) {
          for (Map.Entry entry : ((Map) o).entrySet()) {
            String prop = entry.getKey().toString();
            if (propertyFilter.test(prop)) {
              consumer.accept(name + separator + prop, entry.getValue());
            }
          }
        } else {
          boolean notEmpty =
              ((Map) o)
                  .entrySet().stream()
                      .anyMatch(entry -> propertyFilter.test(entry.getKey().toString()));
          MapWriter writer =
              ew -> {
                for (Map.Entry entry : ((Map) o).entrySet()) {
                  String prop = entry.getKey().toString();
                  if (propertyFilter.test(prop)) {
                    ew.putNoEx(prop, entry.getValue());
                  }
                }
              };
          if (notEmpty) {
            consumer.accept(name, writer);
          }
        }
      } else {
        consumer.accept(name, o);
      }
    } else {
      Object o = gauge.getValue();
      if (o instanceof Map) {
        boolean notEmpty =
            ((Map) o)
                .entrySet().stream()
                    .anyMatch(entry -> propertyFilter.test(entry.getKey().toString()));
        if (notEmpty) {
          consumer.accept(
              name,
              (MapWriter)
                  ew -> {
                    ew.putNoEx(
                        "value",
                        (MapWriter)
                            ew1 -> {
                              for (Map.Entry entry : ((Map) o).entrySet()) {
                                String prop = entry.getKey().toString();
                                if (propertyFilter.test(prop)) {
                                  ew1.put(prop, entry.getValue());
                                }
                              }
                            });
                  });
        }
      } else {
        if (propertyFilter.test("value")) {
          consumer.accept(name, (MapWriter) ew -> ew.putNoEx("value", o));
        }
      }
    }
  }

  /**
   * Convert a {@link Counter}
   *
   * @param counter counter instance
   * @param propertyFilter limit what properties of a metric are returned
   * @param compact if true then only return {@link Counter#getCount()}. If false then return a map
   *     with a "count" field.
   */
  static void convertCounter(
      String name,
      Counter counter,
      Predicate propertyFilter,
      boolean compact,
      BiConsumer consumer) {
    if (compact) {
      consumer.accept(name, counter.getCount());
    } else {
      if (propertyFilter.test("count")) {
        consumer.accept(name, (MapWriter) ew -> ew.putNoEx("count", counter.getCount()));
      }
    }
  }

  /** Returns an instrumented wrapper over the given executor service. */
  public static ExecutorService instrumentedExecutorService(
      ExecutorService delegate, SolrInfoBean info, MetricRegistry metricRegistry, String scope) {
    if (info != null && info.getSolrMetricsContext() != null) {
      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "submitted"));
      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "running"));
      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "completed"));
      info.getSolrMetricsContext().registerMetricName(MetricRegistry.name(scope, "duration"));
    }
    return new InstrumentedExecutorService(delegate, metricRegistry, scope);
  }

  /**
   * Creates a set of metrics (gauges) that correspond to available bean properties for the provided
   * MXBean.
   *
   * @param obj an instance of MXBean
   * @param intf MXBean interface, one of {@link PlatformManagedObject}-s
   * @param consumer consumer for created names and metrics
   * @param  formal type
   */
  public static  void addMXBeanMetrics(
      T obj, Class intf, String prefix, BiConsumer consumer) {
    if (intf.isInstance(obj)) {
      BeanInfo beanInfo;
      try {
        beanInfo =
            Introspector.getBeanInfo(intf, intf.getSuperclass(), Introspector.IGNORE_ALL_BEANINFO);
      } catch (IntrospectionException e) {
        log.warn("Unable to fetch properties of MXBean {}", obj.getClass().getName());
        return;
      }
      for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) {
        final String name = desc.getName();
        // test if it works at all
        try {
          desc.getReadMethod().invoke(obj);
          // worked - consume it
          final Gauge gauge =
              () -> {
                try {
                  return desc.getReadMethod().invoke(obj);
                } catch (InvocationTargetException ite) {
                  // ignore (some properties throw UOE)
                  return null;
                } catch (IllegalAccessException e) {
                  return null;
                }
              };
          String metricName = MetricRegistry.name(prefix, name);
          consumer.accept(metricName, gauge);
        } catch (Exception e) {
          // didn't work, skip it...
        }
      }
    }
  }

  /**
   * These are well-known implementations of {@link java.lang.management.OperatingSystemMXBean}.
   * Some of them provide additional useful properties beyond those declared by the interface.
   */
  public static String[] OS_MXBEAN_CLASSES =
      new String[] {
        OperatingSystemMXBean.class.getName(),
        "com.sun.management.OperatingSystemMXBean",
        "com.sun.management.UnixOperatingSystemMXBean",
        "com.ibm.lang.management.OperatingSystemMXBean"
      };

  /**
   * Creates a set of metrics (gauges) that correspond to available bean properties for the provided
   * MXBean.
   *
   * @param obj an instance of MXBean
   * @param interfaces interfaces that it may implement. Each interface will be tried in turn, and
   *     only if it exists and if it contains unique properties then they will be added as metrics.
   * @param prefix optional prefix for metric names
   * @param consumer consumer for created names and metrics
   * @param  formal type
   */
  public static  void addMXBeanMetrics(
      T obj, String[] interfaces, String prefix, BiConsumer consumer) {
    for (String clazz : interfaces) {
      try {
        final Class intf =
            Class.forName(clazz).asSubclass(PlatformManagedObject.class);
        MetricUtils.addMXBeanMetrics(obj, intf, null, consumer);
      } catch (ClassNotFoundException e) {
        // ignore
      }
    }
  }
}