/* * 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; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.facebook.logging.Logger; import com.facebook.logging.LoggerImpl; /** * This was written to resemble some older libraries. You may find * more functionality in StatsUtil.java. * * See com.facebook.fb303.stats.HistoryManager for more info on how * this works. * * The parameters "shortName" refer to a stat name such as "queries" * or "cpu_load", and "fullName" refer to the longer * "queries.sum.600", "cpu_load.avg.3600". */ /** * Each stat's name corresponds to a single underlying counter, but * each counter can be exported for different uses (ExportTypes). For * details see com.facebook.fb303.stats.HistoryManager. */ public class StatsManager implements HistoryManager { private static Logger logger = LoggerImpl.getLogger(StatsManager.class); private ConcurrentHashMap<String, Integer> typeMap; // todo: handle mutliple shortName/types private ConcurrentHashMap<String, MultiWindowGauge> counterMap; /** * Creates a StatsMgr instance with default settings for the internal * ConcurrentHashMaps. */ public StatsManager() { this(16, 0.75f, 16); } /** * Creates a StatsMgr instance with the specified initial capacity and * concurrency level for the ConcurrentHashMaps used internally. * * @param initialNumKeys: expected initial number of keys * @param loadFactor: load factor of the internal hash maps * @param concurrencyLevel: estimated number of concurrently updating threads */ public StatsManager(int initialNumKeys, float loadFactor, int concurrencyLevel) { logger.trace("StatsMgr Created"); this.typeMap = new ConcurrentHashMap<String, Integer>(initialNumKeys, loadFactor, concurrencyLevel); this.counterMap = new ConcurrentHashMap<String, MultiWindowGauge>(initialNumKeys, loadFactor, concurrencyLevel); } private void ensureStat(String shortName) { if (counterMap.containsKey(shortName)) { return; } MultiWindowGauge mwg = new MultiWindowGauge(); MultiWindowGauge wasGauge = counterMap.putIfAbsent(shortName, mwg); if (wasGauge == null) { if (logger.isDebugEnabled()) { logger.debug("Created stat " + shortName); } } else { if (logger.isTraceEnabled()) { logger.trace("almost accidentally created stat" + shortName + " twice. phew..."); } } } private void ensureType(String shortName, ExportType etype) { boolean hasType = typeMap.containsKey(shortName); if (!hasType) { Integer prior = typeMap.putIfAbsent(shortName, etype.value()); if (prior == null) { return; } // some other thread got there first } Integer bitmask = ExportType.NONE.value(); boolean done; Integer newValue; int tries = 0; do { bitmask = typeMap.get(shortName); newValue = bitmask | etype.value(); if (bitmask == newValue) { break; } done = typeMap.replace(shortName, bitmask, newValue); tries++; } while (!done); if (logger.isTraceEnabled()) { logger.trace("Updated type for " + shortName + ", added " + etype + ", was " + bitmask + ", now " + newValue + " (after " + tries + " tries)"); } return; } @Override public void addStatExportType(String shortName, ExportType etype) { if (shortName == null) { logger.error("Null value passed as key"); return; } if (etype == null) { logger.error("Null value passed as exportType"); return; } ensureStat(shortName); ensureType(shortName, etype); } @Override public void addStatValue(String shortName, long delta) { if (!counterMap.containsKey(shortName)) { addStatExportType(shortName, ExportType.AVG); } MultiWindowGauge stat = counterMap.get(shortName); stat.add(delta); } // fb303 public long getCounter(String fullName) { // todo: convert to a more intelligent table/map version int lastDot = fullName.lastIndexOf('.'); if (lastDot <= 0) { throw new IllegalArgumentException("Stat name argument '" + fullName + "' not found"); } int preLastDot = -1; String shortName = null; String ending = null; String ending2 = null; preLastDot = fullName.lastIndexOf('.', lastDot - 1); ending = fullName.substring(lastDot); if (preLastDot > 0) { shortName = fullName.substring(0, preLastDot); ending2 = fullName.substring(preLastDot); } else { shortName = fullName.substring(0, lastDot); } try { if (ending.equals(".60")) { if (ending2.equals(".sum.60")) { return counterMap.get(shortName).getMinuteSum(); } if (ending2.equals(".avg.60")) { return counterMap.get(shortName).getMinuteAvg(); } if (ending2.equals(".rate.60")) { return counterMap.get(shortName).getMinuteRate(); } if (ending2.equals(".count.60")) { return counterMap.get(shortName).getMinuteSamples(); } } if (ending.equals(".600")) { if (ending2.equals(".sum.600")) { return counterMap.get(shortName).getTenMinuteSum(); } if (ending2.equals(".avg.600")) { return counterMap.get(shortName).getTenMinuteAvg(); } if (ending2.equals(".rate.600")) { return counterMap.get(shortName).getTenMinuteRate(); } if (ending2.equals(".count.600")) { return counterMap.get(shortName).getTenMinuteSamples(); } } if (ending.equals(".3600")) { if (ending2.equals(".sum.3600")) { return counterMap.get(shortName).getHourSum(); } if (ending2.equals(".avg.3600")) { return counterMap.get(shortName).getHourAvg(); } if (ending2.equals(".rate.3600")) { return counterMap.get(shortName).getHourRate(); } if (ending2.equals(".count.3600")) { return counterMap.get(shortName).getHourSamples(); } } if (fullName.endsWith(".sum")) { return counterMap.get(shortName).getAllTimeSum(); } if (fullName.endsWith(".avg")) { return counterMap.get(shortName).getAllTimeAvg(); } if (fullName.endsWith(".rate")) { return counterMap.get(shortName).getAllTimeRate(); } if (fullName.endsWith(".count")) { return counterMap.get(shortName).getAllTimeSamples(); } } catch (Exception e) { throw new IllegalArgumentException("Stat name '" + shortName + "' not found for '" + ending2 + "' or '" + ending + "'"); } throw new IllegalArgumentException("Stat name argument '" + fullName + "' not found"); } /** * Returns a new map of results and any missing keys will be missing * from output map. * * fb303-support */ public Map<String, Long> getSelectedCounters(List<String> keys) { Map<String, Long> result = new HashMap<String, Long>(); for (String key : keys) { try { result.put(key, getCounter(key)); } catch (IllegalArgumentException e) { // okay, result will be missing for this } } return result; } /** * fb303-support * * @param fullName: key with the .sum.60 parts, etc */ public boolean hasCounter(String fullName) { try { getCounter(fullName); return true; } catch (IllegalArgumentException e) { return false; } } /** * Returns true if the shortName has been added already. * * @param shortName: The stat name used with addStatExportType does * NOT have the .sum.60 parts, etc */ public boolean containsKey(String shortName) { return counterMap.containsKey(shortName); } /** * fb303-support */ public Map<String, Long> getCounters() { Map<String, Long> result = new HashMap<String, Long>(); String fullname; long value; Set<String> typeKeys = typeMap.keySet(); for (String name : typeKeys) { int bitmask = typeMap.get(name); MultiWindowGauge stat = counterMap.get(name); for (ExportType type : ExportType.values()) { if ((bitmask & type.value()) == 0) { continue; } // minute fullname = String.format("%s.%s.60", name, type); switch (type) { case SUM: value = stat.getMinuteSum(); break; case COUNT: value = stat.getMinuteSamples(); break; case AVG: value = stat.getMinuteAvg(); break; case RATE: value = stat.getMinuteRate(); break; default: throw new IndexOutOfBoundsException("Bad type"); } result.put(fullname, value); // 10 min fullname = String.format("%s.%s.600", name, type); switch (type) { case SUM: value = stat.getTenMinuteSum(); break; case COUNT: value = stat.getTenMinuteSamples(); break; case AVG: value = stat.getTenMinuteAvg(); break; case RATE: value = stat.getTenMinuteRate(); break; default: throw new IndexOutOfBoundsException("Bad type"); } result.put(fullname, value); // hour fullname = String.format("%s.%s.3600", name, type); switch (type) { case SUM: value = stat.getHourSum(); break; case COUNT: value = stat.getHourSamples(); break; case AVG: value = stat.getHourAvg(); break; case RATE: value = stat.getHourRate(); break; default: throw new IndexOutOfBoundsException("Bad type"); } result.put(fullname, value); // all time fullname = String.format("%s.%s", name, type); switch (type) { case SUM: value = stat.getAllTimeSum(); break; case COUNT: value = stat.getAllTimeSamples(); break; case AVG: value = stat.getAllTimeAvg(); break; case RATE: value = stat.getAllTimeRate(); break; default: throw new IndexOutOfBoundsException("Bad type"); } result.put(fullname, value); } // for all export types } // for all window stat names return result; } }