| /* |
| * 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 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 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 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; |
| |
| /** |
| * This filter can limit what properties of a metric are returned. |
| * @deprecated use {@link Predicate} instead. |
| */ |
| @Deprecated() |
| public interface PropertyFilter { |
| PropertyFilter ALL = (name) -> true; |
| |
| /** |
| * Return only properties that match. |
| * @param name property name |
| * @return true if this property should be returned, false otherwise. |
| */ |
| boolean accept(String name); |
| |
| static Predicate<CharSequence> toPredicate(PropertyFilter filter) { |
| return (name) -> filter.accept(name.toString()); |
| } |
| } |
| |
| public static final Predicate<CharSequence> 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<Object> 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 <em>any one</em> of the filters from this list to be |
| * included in the output |
| * @param mustMatchFilter a {@link MetricFilter}. |
| * A metric <em>must</em> 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 |
| * @deprecated use {@link #toSolrInputDocuments(MetricRegistry, List, MetricFilter, Predicate, boolean, boolean, boolean, Map, Consumer)} instead. |
| */ |
| @Deprecated() |
| public static void toSolrInputDocuments(MetricRegistry registry, List<MetricFilter> shouldMatchFilters, |
| MetricFilter mustMatchFilter, PropertyFilter propertyFilter, boolean skipHistograms, |
| boolean skipAggregateValues, boolean compact, |
| Map<String, Object> metadata, Consumer<SolrInputDocument> consumer) { |
| toSolrInputDocuments(registry, shouldMatchFilters, mustMatchFilter, |
| PropertyFilter.toPredicate(propertyFilter), skipHistograms, |
| skipAggregateValues, compact, metadata, consumer); |
| } |
| /** |
| * 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 <em>any one</em> of the filters from this list to be |
| * included in the output |
| * @param mustMatchFilter a {@link MetricFilter}. |
| * A metric <em>must</em> 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<MetricFilter> shouldMatchFilters, |
| MetricFilter mustMatchFilter, Predicate<CharSequence> propertyFilter, boolean skipHistograms, |
| boolean skipAggregateValues, boolean compact, |
| Map<String, Object> metadata, Consumer<SolrInputDocument> 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<Object, Object> 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) { |
| @SuppressWarnings({"unchecked"}) |
| MapWriter writer = (MapWriter) o; |
| writer._forEachEntry(consumer); |
| } else if (o instanceof Map) { |
| @SuppressWarnings({"unchecked"}) |
| Map<String, Object> map = (Map<String, Object>) o; |
| for (Map.Entry<String, Object> entry : map.entrySet()) { |
| consumer.accept(entry.getKey(), entry.getValue()); |
| } |
| } else if (o instanceof IteratorWriter) { |
| @SuppressWarnings({"unchecked"}) |
| IteratorWriter writer = (IteratorWriter) o; |
| final String name = prefix != null ? prefix : "value"; |
| try { |
| writer.writeIter(new IteratorWriter.ItemWriter() { |
| @Override |
| public IteratorWriter.ItemWriter add(Object o) throws IOException { |
| 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 |
| * @deprecated use {@link #toMaps(MetricRegistry, List, MetricFilter, Predicate, boolean, boolean, boolean, boolean, BiConsumer)} instead. |
| */ |
| @Deprecated() |
| public static void toMaps(MetricRegistry registry, List<MetricFilter> shouldMatchFilters, |
| MetricFilter mustMatchFilter, PropertyFilter propertyFilter, |
| boolean skipHistograms, boolean skipAggregateValues, |
| boolean compact, boolean simple, |
| BiConsumer<String, Object> consumer) { |
| toMaps(registry, shouldMatchFilters, mustMatchFilter, |
| PropertyFilter.toPredicate(propertyFilter), skipHistograms, |
| skipAggregateValues, compact, simple, consumer); |
| } |
| /** |
| * 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<MetricFilter> shouldMatchFilters, |
| MetricFilter mustMatchFilter, Predicate<CharSequence> propertyFilter, |
| boolean skipHistograms, boolean skipAggregateValues, |
| boolean compact, boolean simple, |
| BiConsumer<String, Object> consumer) { |
| final Map<String, Metric> metrics = registry.getMetrics(); |
| final SortedSet<String> 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<String, Object> convertMetrics(MetricRegistry registry, Collection<String> names) { |
| final Map<String, Object> 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 <code>compact==false</code>) 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<String> names, |
| boolean skipHistograms, boolean skipAggregateValues, |
| boolean compact, boolean simple, |
| BiConsumer<String, Object> consumer) { |
| final Map<String, Metric> metrics = registry.getMetrics(); |
| names.stream() |
| .forEach(n -> { |
| Metric metric = metrics.get(n); |
| convertMetric(n, metric, PropertyFilter.ALL, 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 |
| * @deprecated use {@link #convertMetric(String, Metric, Predicate, boolean, boolean, boolean, boolean, String, BiConsumer)} instead. |
| */ |
| @Deprecated() |
| public static void convertMetric(String n, Metric metric, PropertyFilter propertyFilter, boolean skipHistograms, boolean skipAggregateValues, |
| boolean compact, boolean simple, String separator, BiConsumer<String, Object> consumer) { |
| convertMetric(n, metric, PropertyFilter.toPredicate(propertyFilter), |
| skipHistograms, skipAggregateValues, compact, simple, separator, 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<CharSequence> propertyFilter, boolean skipHistograms, boolean skipAggregateValues, |
| boolean compact, boolean simple, String separator, BiConsumer<String, Object> consumer) { |
| if (metric instanceof Counter) { |
| Counter counter = (Counter) metric; |
| convertCounter(n, counter, propertyFilter, compact, consumer); |
| } else if (metric instanceof Gauge) { |
| @SuppressWarnings({"rawtypes"}) |
| 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<CharSequence> propertyFilter, |
| boolean skipAggregateValues, boolean simple, String separator, BiConsumer<String, Object> consumer) { |
| if (simple) { |
| if (propertyFilter.test(MEAN)) { |
| consumer.accept(name + separator + MEAN, metric.getMean()); |
| } |
| } else { |
| MapWriter writer = ew -> { |
| BiConsumer<String, Object> 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<CharSequence> propertyFilter, |
| boolean simple, String separator, BiConsumer<String, Object> 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<CharSequence> propertyFilter, boolean ms) { |
| BiConsumer<String, Object> filter = (k, v) -> { |
| if (propertyFilter.test(k)) { |
| ew.putNoEx(k, v); |
| } |
| }; |
| filter.accept((ms ? MIN_MS: MIN), nsToMs(ms, snapshot.getMin())); |
| filter.accept((ms ? MAX_MS: MAX), nsToMs(ms, 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 |
| * @deprecated use {@link #convertTimer(String, Timer, Predicate, boolean, boolean, String, BiConsumer)} instead. |
| */ |
| @Deprecated() |
| public static void convertTimer(String name, Timer timer, PropertyFilter propertyFilter, boolean skipHistograms, |
| boolean simple, String separator, BiConsumer<String, Object> consumer) { |
| convertTimer(name, timer, PropertyFilter.toPredicate(propertyFilter), |
| skipHistograms, simple, separator, consumer); |
| } |
| /** |
| * 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<CharSequence> propertyFilter, boolean skipHistograms, |
| boolean simple, String separator, BiConsumer<String, Object> consumer) { |
| if (simple) { |
| String prop = "meanRate"; |
| if (propertyFilter.test(prop)) { |
| consumer.accept(name + separator + prop, timer.getMeanRate()); |
| } |
| } else { |
| MapWriter writer = ew -> { |
| BiConsumer<String,Object> 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<CharSequence> propertyFilter, boolean simple, String separator, BiConsumer<String, Object> consumer) { |
| if (simple) { |
| if (propertyFilter.test("count")) { |
| consumer.accept(name + separator + "count", meter.getCount()); |
| } |
| } else { |
| MapWriter writer = ew -> { |
| BiConsumer<String, Object> 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<CharSequence> propertyFilter, boolean simple, boolean compact, |
| String separator, BiConsumer<String, Object> 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, |
| @SuppressWarnings({"rawtypes"})Gauge gauge, |
| Predicate<CharSequence> propertyFilter, boolean simple, boolean compact, |
| String separator, BiConsumer<String, Object> 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<CharSequence> propertyFilter, boolean compact, BiConsumer<String, Object> 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.getMetricNames() != null) { |
| info.getMetricNames().add(MetricRegistry.name(scope, "submitted")); |
| info.getMetricNames().add(MetricRegistry.name(scope, "running")); |
| info.getMetricNames().add(MetricRegistry.name(scope, "completed")); |
| info.getMetricNames().add(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 <T> formal type |
| */ |
| public static <T extends PlatformManagedObject> void addMXBeanMetrics(T obj, Class<? extends T> intf, |
| String prefix, BiConsumer<String, Metric> 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 <T> formal type |
| */ |
| public static <T extends PlatformManagedObject> void addMXBeanMetrics(T obj, String[] interfaces, |
| String prefix, BiConsumer<String, Metric> consumer) { |
| for (String clazz : interfaces) { |
| try { |
| final Class<? extends PlatformManagedObject> intf = Class.forName(clazz) |
| .asSubclass(PlatformManagedObject.class); |
| MetricUtils.addMXBeanMetrics(obj, intf, null, consumer); |
| } catch (ClassNotFoundException e) { |
| // ignore |
| } |
| } |
| } |
| } |