package org.stagemonitor.core.metrics;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metered;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.core.Stagemonitor;
import org.stagemonitor.core.metrics.metrics2.InfluxDbReporter;
import org.stagemonitor.core.metrics.metrics2.Metric2Registry;
import org.stagemonitor.core.metrics.metrics2.MetricName;
import org.stagemonitor.core.metrics.metrics2.ScheduledMetrics2Reporter;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.stagemonitor.core.metrics.metrics2.MetricName.name;
public class SortedTableLogReporter extends ScheduledMetrics2Reporter {
private static final int CONSOLE_WIDTH = 80;
private static final MetricName reportingTimeMetricName = name("reporting_time").tag("reporter", "log").build();
private final Locale locale;
private final Logger log;
/**
* Returns a new {@link SortedTableLogReporter.Builder} for {@link SortedTableLogReporter}.
*
* @param registry the registry to report
* @return a {@link SortedTableLogReporter.Builder} instance for a {@link SortedTableLogReporter}
*/
public static Builder forRegistry(Metric2Registry registry) {
return new Builder(registry);
}
/**
* A builder for {@link SortedTableLogReporter} instances. Defaults to using the
* default locale and time zone, writing to {@code System.out}, converting
* rates to events/second, converting durations to milliseconds, and not
* filtering metrics.
*/
public static class Builder extends ScheduledMetrics2Reporter.Builder<SortedTableLogReporter, Builder> {
private Logger log = LoggerFactory.getLogger("metrics");
private Locale locale = Locale.getDefault();
private Builder(Metric2Registry registry) {
super(registry, "stagemonitor-log-reporter");
}
/**
* Log to the given {@link Logger}.
*
* @param log a {@link Logger} instance.
* @return {@code this}
*/
public Builder log(Logger log) {
this.log = log;
return this;
}
/**
* Format numbers for the given {@link java.util.Locale}.
*
* @param locale a {@link java.util.Locale}
* @return {@code this}
*/
public Builder formattedFor(Locale locale) {
this.locale = locale;
return this;
}
public SortedTableLogReporter build() {
return new SortedTableLogReporter(this);
}
}
private SortedTableLogReporter(Builder builder) {
super(builder);
this.log = builder.log;
this.locale = builder.locale;
}
@Override
public void reportMetrics(Map<MetricName, Gauge> gauges,
Map<MetricName, Counter> counters,
Map<MetricName, Histogram> histograms,
Map<MetricName, Meter> meters,
Map<MetricName, Timer> timers) {
final Timer.Context time = Stagemonitor.getMetric2Registry().timer(reportingTimeMetricName).time();
StringBuilder sb = new StringBuilder(1000);
printWithBanner("Metrics", '=', sb);
sb.append('\n');
try {
logGauges(gauges, sb);
logCounters(counters, sb);
logHistograms(histograms, sb);
logMeters(meters, sb);
logTimers(timers, sb);
sb.append('\n');
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
log.info(sb.toString());
time.stop();
}
}
private void logGauges(Map<MetricName, Gauge> gauges, StringBuilder sb) {
if (!gauges.isEmpty()) {
printWithBanner("-- Gauges", '-', sb);
int maxLength = getMaxLengthOfKeys(gauges);
sb.append(String.format("%-" + maxLength + "s | value\n", "name"));
final Map<MetricName, Gauge> sortedGauges = sortByValue(gauges, new Comparator<Gauge>() {
@Override
public int compare(Gauge o1, Gauge o2) {
Object value2 = o2.getValue();
if (value2 == null) {
value2 = "";
}
Object value1 = o1.getValue();
if (value1 == null) {
value1 = "";
}
return value2.toString().compareTo(value1.toString());
}
});
for (Map.Entry<MetricName, Gauge> entry : sortedGauges.entrySet()) {
printGauge(InfluxDbReporter.getInfluxDbLineProtocolString(entry.getKey()), entry.getValue(), maxLength, sb);
}
sb.append('\n');
}
}
private void logCounters(Map<MetricName, Counter> counters, StringBuilder sb) {
if (!counters.isEmpty()) {
printWithBanner("-- Counters", '-', sb);
int maxLength = getMaxLengthOfKeys(counters);
sb.append(String.format("%-" + maxLength + "s | count\n", "name"));
Map<MetricName, Counter> sortedCounters = sortByValue(counters, new Comparator<Counter>() {
@Override
public int compare(Counter o1, Counter o2) {
return ((Long) o2.getCount()).compareTo(o1.getCount());
}
});
for (Map.Entry<MetricName, Counter> entry : sortedCounters.entrySet()) {
printCounter(InfluxDbReporter.getInfluxDbLineProtocolString(entry.getKey()), entry.getValue(), maxLength, sb);
}
sb.append('\n');
}
}
private void logHistograms(Map<MetricName, Histogram> histograms, StringBuilder sb) {
if (!histograms.isEmpty()) {
printWithBanner("-- Histograms", '-', sb);
int maxLength = getMaxLengthOfKeys(histograms);
sb.append(String.format("%-" + maxLength + "s | count | mean | min | max | stddev | p50 | p75 | p95 | p98 | p99 | p999 |\n", "name"));
Map<MetricName, Histogram> sortedHistograms = sortByValue(histograms, new Comparator<Histogram>() {
@Override
public int compare(Histogram o1, Histogram o2) {
return Double.compare(o2.getSnapshot().getMean(), o1.getSnapshot().getMean());
}
});
for (Map.Entry<MetricName, Histogram> entry : sortedHistograms.entrySet()) {
printHistogram(InfluxDbReporter.getInfluxDbLineProtocolString(entry.getKey()), entry.getValue(), maxLength, sb);
}
sb.append('\n');
}
}
private void logMeters(Map<MetricName, Meter> meters, StringBuilder sb) {
if (!meters.isEmpty()) {
printWithBanner("-- Meters", '-', sb);
int maxLength = getMaxLengthOfKeys(meters);
sb.append(String.format("%-" + maxLength + "s | count | mean_rate | m1_rate | m5_rate | m15_rate | rate_unit | duration_unit\n", "name"));
Map<MetricName, Meter> sortedMeters = sortByValue(meters, new Comparator<Meter>() {
@Override
public int compare(Meter o1, Meter o2) {
return ((Long) o2.getCount()).compareTo(o1.getCount());
}
});
for (Map.Entry<MetricName, Meter> entry : sortedMeters.entrySet()) {
printMeter(InfluxDbReporter.getInfluxDbLineProtocolString(entry.getKey()), entry.getValue(), maxLength, sb);
}
sb.append('\n');
}
}
private void logTimers(Map<MetricName, Timer> timers, StringBuilder sb) {
if (!timers.isEmpty()) {
printWithBanner("-- Timers", '-', sb);
int maxLength = getMaxLengthOfKeys(timers);
sb.append(String.format("%-" + maxLength + "s | count | mean | min | max | stddev | p50 | p75 | p95 | p98 | p99 | p999 | mean_rate | m1_rate | m5_rate | m15_rate | rate_unit | duration_unit\n", "name"));
Map<MetricName, Timer> sortedTimers = sortByValue(timers, new Comparator<Timer>() {
public int compare(Timer o1, Timer o2) {
return Double.compare(o2.getSnapshot().getMean(), o1.getSnapshot().getMean());
}
});
for (Map.Entry<MetricName, Timer> entry : sortedTimers.entrySet()) {
printTimer(InfluxDbReporter.getInfluxDbLineProtocolString(entry.getKey()), entry.getValue(), maxLength, sb);
}
sb.append('\n');
}
}
private static <K, V> Map<K, V> sortByValue(Map<K, V> map, final Comparator<V> valueComparator) {
List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
return valueComparator.compare(o1.getValue(), o2.getValue());
}
});
Map<K, V> result = new LinkedHashMap<K, V>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
private static int getMaxLengthOfKeys(Map<MetricName, ?> map) {
int maxLength = -1;
for (MetricName n : map.keySet()) {
if (InfluxDbReporter.getInfluxDbLineProtocolString(n).length() > maxLength) {
maxLength = InfluxDbReporter.getInfluxDbLineProtocolString(n).length();
}
}
return maxLength;
}
private void printGauge(String name, Gauge gauge, int maxNameLength, StringBuilder sb) {
sb.append(String.format("%" + maxNameLength + "s | ", name));
sb.append(gauge.getValue()).append('\n');
}
private void printCounter(String name, Counter counter, int maxNameLength, StringBuilder sb) {
sb.append(String.format("%" + maxNameLength + "s | ", name));
sb.append(counter.getCount()).append('\n');
}
private void printMeter(String name, Meter meter, int maxNameLength, StringBuilder sb) {
sb.append(String.format("%" + maxNameLength + "s | ", name));
sb.append(formatCount(meter.getCount()));
printMetered(meter, sb);
sb.append('\n');
}
private void printMetered(Metered metered, StringBuilder sb) {
printDouble(convertRate(metered.getMeanRate()), sb);
printDouble(convertRate(metered.getOneMinuteRate()), sb);
printDouble(convertRate(metered.getFiveMinuteRate()), sb);
printDouble(convertRate(metered.getFifteenMinuteRate()), sb);
sb.append(String.format("%-13s | ", getRateUnit()));
sb.append(getDurationUnit());
}
private void printHistogram(String name, Histogram histogram, int maxNameLength, StringBuilder sb) {
sb.append(String.format("%" + maxNameLength + "s | ", name));
sb.append(formatCount(histogram.getCount()));
printHistogramSnapshot(histogram.getSnapshot(), sb);
sb.append('\n');
}
private void printTimerSnapshot(Snapshot snapshot, StringBuilder sb) {
printDouble(convertDuration(snapshot.getMean()), sb);
printDouble(convertDuration(snapshot.getMin()), sb);
printDouble(convertDuration(snapshot.getMax()), sb);
printDouble(convertDuration(snapshot.getStdDev()), sb);
printDouble(convertDuration(snapshot.getMedian()), sb);
printDouble(convertDuration(snapshot.get75thPercentile()), sb);
printDouble(convertDuration(snapshot.get95thPercentile()), sb);
printDouble(convertDuration(snapshot.get98thPercentile()), sb);
printDouble(convertDuration(snapshot.get99thPercentile()), sb);
printDouble(convertDuration(snapshot.get999thPercentile()), sb);
}
private void printHistogramSnapshot(Snapshot snapshot, StringBuilder sb) {
printDouble(snapshot.getMean(), sb);
printDouble(snapshot.getMin(), sb);
printDouble(snapshot.getMax(), sb);
printDouble(snapshot.getStdDev(), sb);
printDouble(snapshot.getMedian(), sb);
printDouble(snapshot.get75thPercentile(), sb);
printDouble(snapshot.get95thPercentile(), sb);
printDouble(snapshot.get98thPercentile(), sb);
printDouble(snapshot.get99thPercentile(), sb);
printDouble(snapshot.get999thPercentile(), sb);
}
private void printTimer(String name, Timer timer, int maxNameLength, StringBuilder sb) {
final Snapshot snapshot = timer.getSnapshot();
sb.append(String.format("%" + maxNameLength + "s | ", name));
sb.append(formatCount(timer.getCount()));
printTimerSnapshot(snapshot, sb);
printMetered(timer, sb);
sb.append('\n');
}
private String formatCount(long count) {
return String.format(locale, "%,9d | ", count);
}
public void printDouble(double d, StringBuilder sb) {
sb.append(String.format(locale, "%,9.2f | ", d));
}
private void printWithBanner(String s, char c, StringBuilder sb) {
sb.append(s);
sb.append(' ');
for (int i = 0; i < (CONSOLE_WIDTH - s.length() - 1); i++) {
sb.append(c);
}
sb.append('\n');
}
}