/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * 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.linkedin.pinot.common.metrics; import java.util.Collection; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import com.yammer.metrics.core.Metered; import com.yammer.metrics.core.MetricName; import com.yammer.metrics.core.MetricProcessor; import com.yammer.metrics.core.Stoppable; /** * Aggregated Meter Stats to consolidate metrics from across consolidated meters. We can have multi-level * aggregation with this class. One example is * You have a JVM wide aggregated metric for ClientStats and a peer specific clientStats * ( client objects connection to the same server) and individual metrics. * * We have refreshMs that will throttle the aggregation frequency to 1 minute (by default). The refresh happens * in the same thread which called the metric method. * */ public class AggregatedMeter<T extends Metered & Stoppable> implements Metered, Stoppable { public static final long SECONDS_IN_ONE_MIN = 60; public static final long SECONDS_IN_FIVE_MIN = SECONDS_IN_ONE_MIN * 5; public static final long SECONDS_IN_FIFTEEN_MIN = SECONDS_IN_ONE_MIN * 15; // Container of inner meters private final List<T> _meters = new CopyOnWriteArrayList<T>(); private static final long DEFAULT_REFRESH_MS = 60 * 1000; // 1 minute // Refresh Delay config private final long _refreshMs; // Last Refreshed timestamp private volatile long _lastRefreshedTime; // Count of entries private volatile long _count; // Rates private volatile double _oneMinRate; private volatile double _fiveMinRate; private volatile double _fifteenMinRate; private volatile double _meanRate; public AggregatedMeter() { _refreshMs = DEFAULT_REFRESH_MS; } public AggregatedMeter(long refreshMs) { _refreshMs = refreshMs; } /** * Add Collection of metrics to be aggregated * @return this instance */ public AggregatedMeter<T> addAll(Collection<T> meters) { _meters.addAll(meters); return this; } /** * Add a metric to be aggregated * @return this instance */ public AggregatedMeter<T> add(T meter) { _meters.add(meter); return this; } /** * Remove a metric which was already added * @return true if the metric was present in the list */ public boolean remove(T meter) { return _meters.remove(meter); } @Override public void stop() { for (T m : _meters) m.stop(); } @Override public <T2> void processWith(MetricProcessor<T2> processor, MetricName name, T2 context) throws Exception { for (T m : _meters) { m.processWith(processor, name, context); } } /** * Check elapsed time since last refresh and only refresh if time difference is * greater than threshold. */ private void refreshIfElapsed() { long currentTime = System.currentTimeMillis(); if (currentTime - _lastRefreshedTime > _refreshMs && !_meters.isEmpty()) { refresh(); _lastRefreshedTime = currentTime; } } public void refresh() { // Refresh 1 min long oneMinSum = 0; long fiveMinSum = 0; long fifteenMinSum = 0; long meanSum = 0; int count = _meters.size(); _count = 0; for (T m : _meters) { oneMinSum += m.oneMinuteRate() * SECONDS_IN_ONE_MIN; fiveMinSum += m.fiveMinuteRate() * SECONDS_IN_FIVE_MIN; fifteenMinSum += m.fifteenMinuteRate() * SECONDS_IN_FIFTEEN_MIN; meanSum += m.meanRate() * m.count(); _count += m.count(); } _oneMinRate = oneMinSum / (count * SECONDS_IN_ONE_MIN * 1.0); _fiveMinRate = fiveMinSum / (count * SECONDS_IN_FIVE_MIN * 1.0); _fifteenMinRate = fifteenMinSum / (count * SECONDS_IN_FIFTEEN_MIN * 1.0); _meanRate = meanSum / _count; } @Override public TimeUnit rateUnit() { if (_meters.isEmpty()) return null; return _meters.get(0).rateUnit(); } @Override public String eventType() { if (_meters.isEmpty()) return null; return _meters.get(0).eventType(); } @Override public long count() { refreshIfElapsed(); return _count; } @Override public double fifteenMinuteRate() { refreshIfElapsed(); return _fifteenMinRate; } @Override public double fiveMinuteRate() { refreshIfElapsed(); return _fiveMinRate; } @Override public double meanRate() { refreshIfElapsed(); return _meanRate; } @Override public double oneMinuteRate() { refreshIfElapsed(); return _oneMinRate; } }