/* * Copyright (C) 2012 Facebook, 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.facebook.stats.mx; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import com.facebook.logging.Logger; import com.facebook.logging.LoggerImpl; import com.facebook.stats.MultiWindowDistribution; import com.facebook.stats.MultiWindowRate; import com.facebook.stats.MultiWindowSpread; public class Stats implements StatsReader, StatsCollector { private static final Logger LOG = LoggerImpl.getClassLogger(); private static final String ERROR_FLAG = "--ERROR--"; private static final long ERROR_VALUE = -1; private final String prefix; private final ConcurrentMap<String, Callable<String>> attributes = new ConcurrentHashMap<>(); // generic counters; anything here will have sum/rate for 1m/10m/60m/all-time private final ConcurrentMap<String, MultiWindowRate> rates = new ConcurrentHashMap<>(); private final ConcurrentMap<String, MultiWindowRate> sums = new ConcurrentHashMap<>(); private final ConcurrentMap<String, LongCounter> counters = new ConcurrentHashMap<>(); private final ConcurrentMap<String, MultiWindowSpread> spreads = new ConcurrentHashMap<>(); private final ConcurrentMap<String, MultiWindowDistribution> distributions = new ConcurrentHashMap<>(); public Stats(String prefix) { this.prefix = prefix; } public Stats() { this(""); } private MultiWindowRate getMultiWindowRate( String key, ConcurrentMap<String, MultiWindowRate> map ) { MultiWindowRate rate = map.get(key); if (rate == null) { rate = new MultiWindowRate(); MultiWindowRate existingRate = map.putIfAbsent(key, rate); if (existingRate != null) { rate = existingRate; } } return rate; } @Override public void exportCounters(Map<String, Long> counterMap) { for (Map.Entry<String, MultiWindowRate> entry : rates.entrySet()) { StatsUtil.addRateAndSumToCounters( prefix + entry.getKey(), entry.getValue(), counterMap ); } for (Map.Entry<String, MultiWindowRate> entry : sums.entrySet()) { StatsUtil.addSumToCounters( prefix + entry.getKey(), entry.getValue(), counterMap ); } for (Map.Entry<String, MultiWindowSpread> entry : spreads.entrySet()) { StatsUtil.addSpreadToCounters( prefix + entry.getKey(), entry.getValue(), counterMap ); } for (Map.Entry<String, MultiWindowDistribution> entry : distributions.entrySet()) { StatsUtil.addQuantileToCounters( prefix + entry.getKey(), entry.getValue(), counterMap ); } Long duplicate = null; for (Map.Entry<String, LongCounter> entry : counters.entrySet()) { duplicate = counterMap.put(prefix + entry.getKey(), entry.getValue().get()); if (duplicate != null) { LOG.warn("Duplicate counter(3) : %s, Ignoring old value %s", prefix + entry.getKey(), duplicate); } } } @Override public MultiWindowRate getRate(StatType statType) { return getRate(statType.getKey()); } @Override public MultiWindowRate getRate(String key) { return getMultiWindowRate(key, rates); } @Override public void incrementRate(StatType type, long delta) { getMultiWindowRate(type.getKey(), rates).add(delta); } @Override public void incrementRate(String key, long delta) { getMultiWindowRate(key, rates).add(delta); } @Override public MultiWindowRate getSum(StatType statType) { return getSum(statType.getKey()); } @Override public MultiWindowRate getSum(String key) { return getMultiWindowRate(key, sums); } @Override public void incrementSum(StatType type, long delta) { getMultiWindowRate(type.getKey(), sums).add(delta); } @Override public void incrementSum(String key, long delta) { getMultiWindowRate(key, sums).add(delta); } @Override public void incrementCounter(StatType key, long delta) { internalIncrementCounter(key.getKey(), delta); } @Override public void incrementCounter(String key, long delta) { internalIncrementCounter(key, delta); } private void internalIncrementCounter(String key, long delta) { LongCounter counter = counters.get(key); if (counter == null) { counter = new AtomicLongCounter(); LongCounter existingCounter = counters.putIfAbsent(key, counter); if (existingCounter != null) { counter = existingCounter; } } counter.update(delta); } @Override @Deprecated public long setCounter(StatType statType, long value) { return StatsUtil.setCounterValue(statType.getKey(), value, this); } @Override @Deprecated public long setCounter(String key, long value) { return StatsUtil.setCounterValue(key, value, this); } public long resetCounter(StatType key) { return internalResetCounter(key.getKey()); } @Override public long resetCounter(String key) { return internalResetCounter(key); } private long internalResetCounter(String key) { LongCounter counter = counters.remove(key); return counter == null ? 0 : counter.get(); } public void incrementSpread(StatType type, long value) { getMultiWindowSpread(type.getKey()).add(value); } @Override public void incrementSpread(String key, long value) { getMultiWindowSpread(key).add(value); } @Override public void updateDistribution(StatType type, long value) { getMultiWindowDistribution(type.getKey()).add(value); } @Override public void updateDistribution(String key, long value) { getMultiWindowDistribution(key).add(value); } @Override public long getCounter(StatType key) { return internalGetCounter(key.getKey()); } @Override public long getCounter(String key) { return internalGetCounter(key); } @Override public MultiWindowSpread getSpread(StatType key) { return getMultiWindowSpread(key.getKey()); } @Override public MultiWindowSpread getSpread(String key) { return getMultiWindowSpread(key); } @Override public MultiWindowDistribution getDistribution(StatType key) { return getMultiWindowDistribution(key.getKey()); } @Override public MultiWindowDistribution getDistribution(String key) { return getMultiWindowDistribution(key); } /** * Sets the dynamic counter if a counter with the specified key doesn't already exist * * @param key the key for the dynamic counter * @param valueProducer the generator value for this counter * * @return true if the counter was added. False, if a counter with the specified key exists * already */ public boolean addDynamicCounter(String key, Callable<Long> valueProducer) { return null == counters.putIfAbsent(key, new CallableLongCounter(key, valueProducer)); } /** * Removes a counter with the specified key * * @param key the key for the counter * @return true if a counter with the specified key existed and was removed, false otherwise. */ public boolean removeCounter(String key) { return counters.remove(key) != null; } private long internalGetCounter(String key) { LongCounter counter = counters.get(key); return counter == null ? 0 : counter.get(); } @Override public String getAttribute(StatType key) { return internalGetAttribute(key.getKey()); } @Override public void setAttribute(StatType key, String value) { internalSetAttribute(key.getKey(), new StringProducer(value)); } @Override public void setAttribute(String key, String value) { internalSetAttribute(key, new StringProducer(value)); } @Override public void setAttribute(StatType key, Callable<String> valueProducer) { internalSetAttribute(key.getKey(), valueProducer); } @Override public void setAttribute(String key, Callable<String> valueProducer) { internalSetAttribute(key, valueProducer); } /** * Removes an attribute with the specified key * * @param key the key for the attribute * @return true if an attribute with the specified key existed and was removed, false otherwise. */ public boolean removeAttribute(String key) { return attributes.remove(key) != null; } private void internalSetAttribute(String key, Callable<String> valueProducer) { try { attributes.put(key, valueProducer); } catch (Exception e) { LOG.error("error in producer for key %s", key, e); } } @Override public String getAttribute(String key) { return internalGetAttribute(key); } @Deprecated @Override public Callable<Long> getDynamicCounter(StatType key) { final LongCounter longCounter = counters.get(key.getKey()); return new Callable<Long>() { @Override public Long call() throws Exception { return longCounter.get(); } }; } @Deprecated @Override public Callable<Long> getDynamicCounter(String key) { final LongCounter longCounter = counters.get(key); return new Callable<Long>() { @Override public Long call() throws Exception { return longCounter.get(); } }; } private String internalGetAttribute(String key) { try { Callable<String> callable = attributes.get(key); return callable == null ? null : callable.call(); } catch (Exception e) { LOG.error("error producing value for key %s", key, e); return ERROR_FLAG; } } @Override public Map<String, String> getAttributes() { return materializeAttributes(); } private Map<String, String> materializeAttributes() { Map<String, String> materializedAttributes = new HashMap<String, String>(); for (Map.Entry<String, Callable<String>> entry : attributes.entrySet()) { try { materializedAttributes.put(entry.getKey(), entry.getValue().call()); } catch (Exception e) { materializedAttributes.put(entry.getKey(), ERROR_FLAG); LOG.error("error producing value for key %s", entry.getKey(), e); } } for (Map.Entry<String, MultiWindowDistribution> entry : distributions.entrySet()) { StatsUtil.addHistogramToExportedValues( entry.getKey(), entry.getValue(), materializedAttributes ); } return materializedAttributes; } private MultiWindowSpread getMultiWindowSpread(String key) { MultiWindowSpread spread = spreads.get(key); if (spread == null) { spread = new MultiWindowSpread(); MultiWindowSpread existingSpreads = spreads.putIfAbsent(key, spread); if (existingSpreads != null) { spread = existingSpreads; } } return spread; } private MultiWindowDistribution getMultiWindowDistribution(String key) { MultiWindowDistribution distribution = distributions.get(key); if (distribution == null) { distribution = new MultiWindowDistribution(); MultiWindowDistribution existing = distributions.putIfAbsent(key, distribution); if (existing != null) { distribution = existing; } } return distribution; } private static class StringProducer implements Callable<String> { private final String value; private StringProducer(String value) { this.value = value; } @Override public String call() throws Exception { return value; } } private interface LongCounter { void update(long delta); long get(); } private static class AtomicLongCounter implements LongCounter { private final AtomicLong value = new AtomicLong(0); @Override public void update(long delta) { value.addAndGet(delta); } @Override public long get() { return value.get(); } } private static class CallableLongCounter implements LongCounter { private final String key; private Callable<Long> longCallable; private CallableLongCounter(String key, Callable<Long> longCallable) { this.key = key; this.longCallable = longCallable; } @Override public void update(long delta) { //no-op } @Override public long get() { try { return longCallable.call(); } catch (Exception e) { LOG.debug("Exception when generating dynamic counter value for %s", key, e); return ERROR_VALUE; } } } }