package com.arpnetworking.utils; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import org.joda.time.DateTime; import play.Logger; import play.Logger.ALogger; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Query logs * * @author barp */ public class Counter { private static final ALogger QUERY_LOG = Logger.of("query-log"); private HashMap<String, ArrayList<Long>> counters = new HashMap<>(); private HashMap<String, ArrayList<Long>> timers = new HashMap<>(); private HashMap<String, ArrayList<Double>> gauges = new HashMap<>(); private HashMap<String, Long> startedTimers = new HashMap<String, Long>(); private HashMap<String, String> annotations = new HashMap<String, String>(); private Boolean hasDumped = false; private Boolean dumpLocked = false; private static final DecimalFormat timestampFormat = new DecimalFormat("##.000"); public Counter() { annotations.put("initTimestamp", timestampFormat.format(Double.valueOf(DateTime.now().getMillis()) / 1000)); } public Long increment(String counterName, Long count) { ArrayList<Long> vals = initCounterKey(counterName); long currentValue = vals.get(vals.size() - 1); currentValue += count; vals.set(vals.size() - 1, currentValue); return currentValue; } public Long increment(String counterName) { return increment(counterName, 1l); } public Long decrement(String counterName, Long count) { return increment(counterName, count * -1); } public Long decrement(String counterName) { return decrement(counterName, 1l); } public void recordGauge(String gaugeName, double value) { ArrayList<Double> values = initGauge(gaugeName); values.add(value); } private ArrayList<Double> initGauge(final String gaugeName) { ArrayList<Double> values = gauges.get(gaugeName); if (values == null) { values = new ArrayList<>(); gauges.put(gaugeName, values); } return values; } public void startTimer(String timerName) { initTimerKey(timerName); if (startedTimers.containsKey(timerName)) { throw new IllegalArgumentException("Cannot start a timer that is already started"); } startedTimers.put(timerName, System.nanoTime()); } public void stopTimer(String timerName) { if (!startedTimers.containsKey(timerName)) { throw new IllegalArgumentException("Cannot stop a timer that has not been started"); } Long now = System.nanoTime(); Long startTime = startedTimers.get(timerName); startedTimers.remove(timerName); Long elapsed = (now - startTime) / 1000; ArrayList<Long> times = timers.get(timerName); times.add(elapsed); } private ArrayList<Long> initCounterKey(String counterName) { ArrayList<Long> values = counters.get(counterName); if (values == null) { values = new ArrayList<>(); values.add(0L); counters.put(counterName, values); } return values; } private void initTimerKey(String timerName) { if (!timers.containsKey(timerName)) { timers.put(timerName, new ArrayList<Long>()); } } public void annotate(String key, String annotation) { annotations.put(key, annotation); } private String dumpCounters() { JsonNodeFactory factory = JsonNodeFactory.instance; ObjectNode ret = new ObjectNode(factory); //version number ObjectNode annotationsNode = new ObjectNode(factory); //Write the init and final timestamps first to be nice to splunk if (annotations.containsKey("initTimestamp")) { annotationsNode.put("initTimestamp", annotations.get("initTimestamp")); annotations.remove("initTimestamp"); } if (annotations.containsKey("finalTimestamp")) { annotationsNode.put("finalTimestamp", annotations.get("finalTimestamp")); annotations.remove("finalTimestamp"); } for (Map.Entry<String, String> annotation : annotations.entrySet()) { annotationsNode.put(annotation.getKey(), annotation.getValue()); } ret.put("annotations", annotationsNode); ret.put("version", "2c"); ret.put("counters", buildLongValuesNode(counters)); ret.put("timers", buildLongValuesNode(timers)); ret.put("gauges", buildDoubleValuesNode(gauges)); return ret.toString(); } private ObjectNode buildDoubleValuesNode(final HashMap<String, ArrayList<Double>> map) { ObjectNode countersNode = JsonNodeFactory.instance.objectNode(); for (Map.Entry<String, ArrayList<Double>> counterSet : map.entrySet()) { ArrayNode counterEntries = JsonNodeFactory.instance.arrayNode(); for (Double timerEntry : counterSet.getValue()) { counterEntries.add(timerEntry); } countersNode.put(counterSet.getKey(), counterEntries); } return countersNode; } private ObjectNode buildLongValuesNode(final HashMap<String, ArrayList<Long>> map) { ObjectNode countersNode = JsonNodeFactory.instance.objectNode(); for (Map.Entry<String, ArrayList<Long>> counterSet : map.entrySet()) { ArrayNode counterEntries = JsonNodeFactory.instance.arrayNode(); for (Long timerEntry : counterSet.getValue()) { counterEntries.add(timerEntry); } countersNode.put(counterSet.getKey(), counterEntries); } return countersNode; } public void lockSave() { dumpLocked = true; } public void unlockSave() { dumpLocked = false; } public void saveCounters() { if (!hasDumped && !dumpLocked) { hasDumped = true; annotations.put("finalTimestamp", timestampFormat.format((double) DateTime.now().getMillis() / 1000)); QUERY_LOG.info(dumpCounters()); } } }