Java tutorial
/** * Copyright (c) 2013 Ahmed Badran (a3badran). This content is released under the MIT License. See LICENCE.txt */ package org.a3badran.platform.logging.writer; import com.google.common.base.Strings; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.a3badran.platform.logging.RequestScope; import org.a3badran.platform.logging.writer.Writer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; import org.apache.commons.math.stat.descriptive.SynchronizedDescriptiveStatistics; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** * Metrics writer to track statistics about request. * * @author a3badran */ public class MetricsWriter implements Writer { private static final Log log = LogFactory.getLog(Writer.LOGGER); private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private String appName = "app"; private int sampleWindow = 100; private float sampleRate = 0.5f; private boolean recordSubscope = false; private final ConcurrentHashMap<String, AtomicLong> appTotalMetrics = new ConcurrentHashMap<String, AtomicLong>(); private final ConcurrentHashMap<String, AtomicLong> scopeTotalMetrics = new ConcurrentHashMap<String, AtomicLong>(); private final ConcurrentHashMap<String, DescriptiveStatistics> sampleMetrics = new ConcurrentHashMap<String, DescriptiveStatistics>(); private final ConcurrentHashMap<String, DescriptiveStatistics> sampleCounterMetrics = new ConcurrentHashMap<String, DescriptiveStatistics>(); private final Random random = new Random(System.currentTimeMillis()); //--------------------------------------------------------- // Constructor //--------------------------------------------------------- public MetricsWriter() { // empty } //--------------------------------------------------------- // Public Implementation //--------------------------------------------------------- @Override public void write(RequestScope scope) { // this method must not throw any exceptions try { updateMetrics(scope, appName, scope.getEndTime() - scope.getStartTime(), 1, true); if (recordSubscope && scope.getSubScopes() != null && !scope.getSubScopes().isEmpty()) { for (Map.Entry<String, RequestScope> entry : scope.getSubScopes().entrySet()) { RequestScope subScope = entry.getValue(); updateMetrics(subScope, appName + "." + scope.getName(), subScope.getTotalTime(), subScope.getCallCount(), false); } } } catch (RuntimeException e) { log.warn("Error updating metrics", e); } } /** * Example in json of what this will return: * * [ * app.getCustomer: { * 50p: 972, * 90p: 1515, * 99p: 1579, * avg: 798, * max: 1580, * min: 7, * sampleCount: 100, * totalCount: 795, * totalTime: 509371 * }, * app.getOrder: { * 50p: 2, * 90p: 601, * 99p: 601, * avg: 201, * max: 601, * min: 2, * sampleCount: 3, * totalCount: 3, * totalTime: 605 * } * ] * @return metrics for the given name, if null or empty, it returns all metrics * */ public Map<String, Map<String, Long>> getMetrics() { return getMetricsByName(null); } public Map<String, Map<String, Long>> getMetricsByName(String name) { Map<String, Map<String, Long>> groupedMetrics = new HashMap<String, Map<String, Long>>(); Map<String, Long> metrics = getAllMetrics(); for (Entry<String, Long> entry : metrics.entrySet()) { String key = entry.getKey(); String[] parts = key.split("\\."); if (parts.length < 2) { continue; } String newKey = key.substring(0, key.length() - (parts[parts.length - 1].length() + 1)); if (!Strings.isNullOrEmpty(name) && !newKey.equalsIgnoreCase(name)) { continue; } if (groupedMetrics.get(newKey) == null) { Map<String, Long> newValue = new TreeMap<String, Long>(); newValue.put(parts[parts.length - 1], entry.getValue()); groupedMetrics.put(newKey, newValue); } else { groupedMetrics.get(newKey).put(parts[parts.length - 1], entry.getValue()); } } return groupedMetrics; } public Map<String, Long> getAppTotalMetrics() { Map<String, Long> map = Maps.newHashMap(); for (Entry<String, AtomicLong> entry : appTotalMetrics.entrySet()) { map.put(entry.getKey(), entry.getValue().longValue()); } return map; } public void resetTotalMetrics() { this.scopeTotalMetrics.clear(); this.appTotalMetrics.clear(); } public void resetSampleMetrics() { this.sampleMetrics.clear(); } @Override public String toString() { return gson.toJson(getMetrics()); } //--------------------------------------------------------- // Private methods //--------------------------------------------------------- private Map<String, Long> getAllMetrics() { Map<String, Long> metrics = new HashMap<String, Long>(); for (Entry<String, DescriptiveStatistics> entry : sampleMetrics.entrySet()) { // create a copy to reduce locking String name = entry.getKey(); DescriptiveStatistics stats = entry.getValue().copy(); metrics.put(name + ".sampleCount", (long) stats.getN()); metrics.put(name + ".max", (long) stats.getMax()); metrics.put(name + ".min", (long) stats.getMin()); metrics.put(name + ".avg", (long) stats.getMean()); metrics.put(name + ".50p", (long) stats.getPercentile(50)); metrics.put(name + ".90p", (long) stats.getPercentile(90)); metrics.put(name + ".99p", (long) stats.getPercentile(99)); } for (Entry<String, DescriptiveStatistics> cEntry : sampleCounterMetrics.entrySet()) { // create a copy to reduce locking String cName = cEntry.getKey(); DescriptiveStatistics cStats = cEntry.getValue().copy(); metrics.put(cName + ".max", (long) cStats.getMax()); metrics.put(cName + ".min", (long) cStats.getMin()); metrics.put(cName + ".avg", (long) cStats.getMean()); metrics.put(cName + ".50p", (long) cStats.getPercentile(50)); metrics.put(cName + ".90p", (long) cStats.getPercentile(90)); metrics.put(cName + ".99p", (long) cStats.getPercentile(99)); } for (Entry<String, AtomicLong> entry : scopeTotalMetrics.entrySet()) { metrics.put(entry.getKey(), entry.getValue().longValue()); } for (Entry<String, AtomicLong> entry : appTotalMetrics.entrySet()) { metrics.put(entry.getKey(), entry.getValue().longValue()); } return metrics; } private void updateMetrics(RequestScope scope, String prefixName, long tt, long count, boolean updateCounters) { String name = Strings.isNullOrEmpty(prefixName) ? scope.getName() : prefixName + "." + scope.getName(); String metricTotalCount = name + ".totalCount"; scopeTotalMetrics.putIfAbsent(metricTotalCount, new AtomicLong(0)); scopeTotalMetrics.get(metricTotalCount).addAndGet(count); String serviceTotalCount = appName + ".totalCount"; appTotalMetrics.putIfAbsent(serviceTotalCount, new AtomicLong(0)); appTotalMetrics.get(serviceTotalCount).addAndGet(count); if (!Strings.isNullOrEmpty(scope.getError())) { String metricErrorCount = name + ".errorCount"; scopeTotalMetrics.putIfAbsent(metricErrorCount, new AtomicLong(0)); scopeTotalMetrics.get(metricErrorCount).addAndGet(1); String serviceErrorCount = appName + ".errorCount"; appTotalMetrics.putIfAbsent(serviceErrorCount, new AtomicLong(0)); appTotalMetrics.get(serviceErrorCount).addAndGet(1); } else if (!Strings.isNullOrEmpty(scope.getWarninge())) { String metricWarningCount = name + ".warningCount"; scopeTotalMetrics.putIfAbsent(metricWarningCount, new AtomicLong(0)); scopeTotalMetrics.get(metricWarningCount).addAndGet(1); String serviceWarningCount = appName + ".warningCount"; appTotalMetrics.putIfAbsent(serviceWarningCount, new AtomicLong(0)); appTotalMetrics.get(serviceWarningCount).addAndGet(1); } String metricTotalTime = name + ".totalTime"; scopeTotalMetrics.putIfAbsent(metricTotalTime, new AtomicLong(0)); scopeTotalMetrics.get(metricTotalTime).addAndGet(tt); // sample data if (random.nextFloat() <= sampleRate) { sampleMetrics.putIfAbsent(name, new SynchronizedDescriptiveStatistics(sampleWindow)); sampleMetrics.get(name).addValue(tt); // sample counters if (updateCounters == true && scope.getCounters() != null) { for (Map.Entry<String, AtomicLong> entry : scope.getCounters().entrySet()) { String counterName = String.format("%s.%s", name, entry.getKey()); sampleCounterMetrics.putIfAbsent(counterName, new SynchronizedDescriptiveStatistics(sampleWindow)); sampleCounterMetrics.get(counterName).addValue(entry.getValue().doubleValue()); } } } } //--------------------------------------------------------- // IoC //--------------------------------------------------------- public String getAppName() { return appName; } public void setAppName(String serviceName) { this.appName = serviceName; } public int getSampleWindow() { return sampleWindow; } public void setSampleWindow(int sampleWindow) { this.sampleWindow = sampleWindow; } public float getSampleRate() { return sampleRate; } public void setSampleRate(float sampleRate) { this.sampleRate = sampleRate; } public boolean isRecordSubScopes() { return recordSubscope; } public void setRecordSubScopes(boolean recordSubScopes) { this.recordSubscope = recordSubScopes; } }