/** * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * 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 com.netflix.spectator.spark; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricFilter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.ScheduledReporter; import com.codahale.metrics.Timer; import com.netflix.spectator.api.DistributionSummary; import com.netflix.spectator.api.Id; import com.netflix.spectator.api.Registry; import com.netflix.spectator.api.Spectator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.SortedMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; /** * Reporter for mapping data in a metrics3 registry to spectator. */ public final class SpectatorReporter extends ScheduledReporter { private static final Logger LOGGER = LoggerFactory.getLogger(SpectatorReporter.class); /** * Return a builder for creating a spectator reported based on {@code registry}. */ public static Builder forRegistry(MetricRegistry registry) { return new Builder(registry); } /** * Builder for configuring the spectator reporter. */ public static final class Builder { private final MetricRegistry registry; private Registry spectatorRegistry; private NameFunction nameFunction = new NameFunction() { @Override public Id apply(String name) { return spectatorRegistry.createId(name); } }; private ValueFunction valueFunction = (name, v) -> v; /** * By default none should match. Note the '$' sign is regex end of line so it cannot be * followed by characters and thus the pattern will not match anything. */ private Pattern gaugeCounters = Pattern.compile("$NONE"); /** Create a new instance. */ Builder(MetricRegistry registry) { this.registry = registry; } /** Set the spectator registry to use. */ public Builder withSpectatorRegistry(Registry r) { spectatorRegistry = r; return this; } /** Set the name mapping function to use. */ public Builder withNameFunction(NameFunction f) { nameFunction = f; return this; } /** Set the value mapping function to use. */ public Builder withValueFunction(ValueFunction f) { valueFunction = f; return this; } /** Set the regex for matching names of gauges that should be treated as counters. */ public Builder withGaugeCounters(Pattern pattern) { gaugeCounters = pattern; return this; } /** Create a new instance of the reporter. */ public SpectatorReporter build() { if (spectatorRegistry == null) { spectatorRegistry = Spectator.globalRegistry(); } return new SpectatorReporter( registry, spectatorRegistry, nameFunction, valueFunction, gaugeCounters); } } private final Registry spectatorRegistry; private final NameFunction nameFunction; private final ValueFunction valueFunction; private final Pattern gaugeCounters; private final ConcurrentHashMap<String, AtomicLong> previousValues = new ConcurrentHashMap<>(); /** Create a new instance. */ SpectatorReporter( MetricRegistry metricRegistry, Registry spectatorRegistry, NameFunction nameFunction, ValueFunction valueFunction, Pattern gaugeCounters) { super(metricRegistry, "spectator", // name MetricFilter.ALL, // filter TimeUnit.SECONDS, // rateUnit TimeUnit.SECONDS); // durationUnit this.spectatorRegistry = spectatorRegistry; this.nameFunction = nameFunction; this.valueFunction = valueFunction; this.gaugeCounters = gaugeCounters; } @SuppressWarnings("PMD.NPathComplexity") @Override public void report( SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) { LOGGER.debug("gauges {}, counters {}, histograms {}, meters {}, timers {}", gauges.size(), counters.size(), histograms.size(), meters.size(), timers.size()); for (Map.Entry<String, Gauge> entry : gauges.entrySet()) { final Object obj = entry.getValue().getValue(); if (obj instanceof Number) { if (gaugeCounters.matcher(entry.getKey()).matches()) { final long v = ((Number) obj).longValue(); updateCounter(entry.getKey(), v); } else { final double v = ((Number) obj).doubleValue(); setGaugeValue(entry.getKey(), v); } } } for (Map.Entry<String, Counter> entry : counters.entrySet()) { if (gaugeCounters.matcher(entry.getKey()).matches()) { updateCounter(entry.getKey(), entry.getValue().getCount()); } else { setGaugeValue(entry.getKey(), entry.getValue().getCount()); } } for (Map.Entry<String, Histogram> entry : histograms.entrySet()) { final Id id = nameFunction.apply(entry.getKey()); if (id != null) { final DistributionSummary sHisto = spectatorRegistry.distributionSummary(id); final Histogram mHisto = entry.getValue(); final long[] vs = mHisto.getSnapshot().getValues(); for (long v : vs) { sHisto.record(v); } } } for (Map.Entry<String, Meter> entry : meters.entrySet()) { updateCounter(entry.getKey(), entry.getValue().getCount()); } for (Map.Entry<String, Timer> entry : timers.entrySet()) { final Id id = nameFunction.apply(entry.getKey()); if (id != null) { final com.netflix.spectator.api.Timer sTimer = spectatorRegistry.timer(id); final Timer mTimer = entry.getValue(); final long[] vs = mTimer.getSnapshot().getValues(); for (long v : vs) { sTimer.record(v, TimeUnit.NANOSECONDS); } } } } private void setGaugeValue(String name, double v) { Id id = nameFunction.apply(name); if (id != null) { final double cv = valueFunction.convert(name, v); LOGGER.debug("setting gauge {} to {}", name, cv); spectatorRegistry.gauge(id).set(cv); } } private long getAndSetPrevious(String name, long newValue) { AtomicLong prev = previousValues.get(name); if (prev == null) { AtomicLong tmp = new AtomicLong(0L); prev = previousValues.putIfAbsent(name, tmp); prev = (prev == null) ? tmp : prev; } return prev.getAndSet(newValue); } private void updateCounter(String k, long curr) { final long prev = getAndSetPrevious(k, curr); final Id id = nameFunction.apply(k); if (id != null) { spectatorRegistry.counter(id).increment(curr - prev); } } }