/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 gobblin.metrics.reporter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.SortedMap; import java.util.TimeZone; import com.typesafe.config.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.Clock; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; import com.google.common.collect.Maps; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.io.Closer; import gobblin.configuration.ConfigurationKeys; import gobblin.util.ConfigUtils; public class OutputStreamReporter extends ConfiguredScheduledReporter { private static final String TAGS_SECTION = "-- Tags"; private static final String GAUGES_SECTION = "-- Gauges"; private static final String COUNTERS_SECTION = "-- Counters"; private static final String HISTOGRAMS_SECTION = "-- Histograms"; private static final String METERS_SECTION = "-- Meters"; private static final String TIMERS_SECTION = "-- Times"; private static final Logger LOGGER = LoggerFactory.getLogger(OutputStreamReporter.class); public static class Factory { public static BuilderImpl newBuilder() { return new BuilderImpl(); } } public static class BuilderImpl extends Builder<BuilderImpl> { @Override protected BuilderImpl self() { return this; } } /** * A builder for {@link OutputStreamReporter} 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 abstract class Builder<T extends ConfiguredScheduledReporter.Builder<T>> extends ConfiguredScheduledReporter.Builder<T> { protected PrintStream output; protected Locale locale; protected Clock clock; protected TimeZone timeZone; protected Builder() { this.name = "OutputStreamReporter"; this.output = System.out; this.locale = Locale.getDefault(); this.clock = Clock.defaultClock(); this.timeZone = TimeZone.getDefault(); } protected abstract T self(); /** * Write to the given {@link PrintStream}. * * @param output a {@link PrintStream} instance. * @return {@code this} */ public T outputTo(PrintStream output) { this.output = output; return self(); } /** * Write to the given {@link java.io.OutputStream}. * * @param stream 2 {@link java.io.OutputStream} instance * @return {@code this} */ public T outputTo(OutputStream stream) { try { this.output = new PrintStream(stream, false, Charsets.UTF_8.toString()); } catch(UnsupportedEncodingException exception) { LOGGER.error("Unsupported encoding in OutputStreamReporter. This is an error with the code itself.", exception); throw new RuntimeException(exception); } return self(); } /** * Format numbers for the given {@link Locale}. * * @param locale a {@link Locale} * @return {@code this} */ public T formattedFor(Locale locale) { this.locale = locale; return self(); } /** * Use the given {@link Clock} instance for the time. * * @param clock a {@link Clock} instance * @return {@code this} */ public T withClock(Clock clock) { this.clock = clock; return self(); } /** * Use the given {@link TimeZone} for the time. * * @param timeZone a {@link TimeZone} * @return {@code this} */ public T formattedFor(TimeZone timeZone) { this.timeZone = timeZone; return self(); } /** * Builds a {@link OutputStreamReporter} with the given properties. * * @return a {@link OutputStreamReporter} */ public OutputStreamReporter build(Properties props) { return new OutputStreamReporter(this, ConfigUtils.propertiesToConfig(props, Optional.of(ConfigurationKeys.METRICS_CONFIGURATIONS_PREFIX))); } } private static final int CONSOLE_WIDTH = 80; private final PrintStream output; private final Locale locale; private final Clock clock; private final DateFormat dateFormat; private final ByteArrayOutputStream outputBuffer; private final PrintStream outputBufferPrintStream; private final Closer closer; private OutputStreamReporter(Builder<?> builder, Config config) { super(builder, config); this.closer = Closer.create(); this.output = this.closer.register(builder.output); this.locale = builder.locale; this.clock = builder.clock; this.dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); this.dateFormat.setTimeZone(builder.timeZone); this.outputBuffer = new ByteArrayOutputStream(); try { this.outputBufferPrintStream = this.closer.register(new PrintStream(this.outputBuffer, false, Charsets.UTF_8.toString())); } catch (UnsupportedEncodingException re) { throw new RuntimeException("This should never happen.", re); } } @Override public void close() throws IOException { try { this.closer.close(); } catch (IOException exception) { LOGGER.warn("Failed to close output streams."); } finally { super.close(); } } @SuppressWarnings("rawtypes") @Override protected synchronized void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers, Map<String, Object> tags) { this.outputBuffer.reset(); final String dateTime = dateFormat.format(new Date(clock.getTime())); printWithBanner(dateTime, '='); this.outputBufferPrintStream.println(); Map<String, Object> allTags = Maps.newHashMap(); allTags.putAll(tags); allTags.putAll(this.tags); if (!allTags.isEmpty()) { printWithBanner(TAGS_SECTION, '-'); for (Map.Entry<String, Object> entry : allTags.entrySet()) { this.outputBufferPrintStream.println(String.format("%s=%s", entry.getKey(), entry.getValue())); } this.outputBufferPrintStream.println(); } if (!gauges.isEmpty()) { printWithBanner(GAUGES_SECTION, '-'); for (Map.Entry<String, Gauge> entry : gauges.entrySet()) { this.outputBufferPrintStream.println(entry.getKey()); printGauge(entry); } this.outputBufferPrintStream.println(); } if (!counters.isEmpty()) { printWithBanner(COUNTERS_SECTION, '-'); for (Map.Entry<String, Counter> entry : counters.entrySet()) { this.outputBufferPrintStream.println(entry.getKey()); printCounter(entry); } this.outputBufferPrintStream.println(); } if (!histograms.isEmpty()) { printWithBanner(HISTOGRAMS_SECTION, '-'); for (Map.Entry<String, Histogram> entry : histograms.entrySet()) { this.outputBufferPrintStream.println(entry.getKey()); printHistogram(entry.getValue()); } this.outputBufferPrintStream.println(); } if (!meters.isEmpty()) { printWithBanner(METERS_SECTION, '-'); for (Map.Entry<String, Meter> entry : meters.entrySet()) { this.outputBufferPrintStream.println(entry.getKey()); printMeter(entry.getValue()); } this.outputBufferPrintStream.println(); } if (!timers.isEmpty()) { printWithBanner(TIMERS_SECTION, '-'); for (Map.Entry<String, Timer> entry : timers.entrySet()) { this.outputBufferPrintStream.println(entry.getKey()); printTimer(entry.getValue()); } this.outputBufferPrintStream.println(); } this.outputBufferPrintStream.println(); this.outputBufferPrintStream.flush(); try { this.outputBuffer.writeTo(this.output); } catch (IOException exception) { LOGGER.warn("Failed to write metric report to output stream."); } } private void printMeter(Meter meter) { this.outputBufferPrintStream.printf(locale, " count = %d%n", meter.getCount()); this.outputBufferPrintStream.printf(locale, " mean rate = %2.2f events/%s%n", convertRate(meter.getMeanRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " 1-minute rate = %2.2f events/%s%n", convertRate(meter.getOneMinuteRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " 5-minute rate = %2.2f events/%s%n", convertRate(meter.getFiveMinuteRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " 15-minute rate = %2.2f events/%s%n", convertRate(meter.getFifteenMinuteRate()), getRateUnit()); } private void printCounter(Map.Entry<String, Counter> entry) { this.outputBufferPrintStream.printf(locale, " count = %d%n", entry.getValue().getCount()); } private void printGauge(Map.Entry<String, Gauge> entry) { this.outputBufferPrintStream.printf(locale, " value = %s%n", entry.getValue().getValue()); } private void printHistogram(Histogram histogram) { this.outputBufferPrintStream.printf(locale, " count = %d%n", histogram.getCount()); Snapshot snapshot = histogram.getSnapshot(); this.outputBufferPrintStream.printf(locale, " min = %d%n", snapshot.getMin()); this.outputBufferPrintStream.printf(locale, " max = %d%n", snapshot.getMax()); this.outputBufferPrintStream.printf(locale, " mean = %2.2f%n", snapshot.getMean()); this.outputBufferPrintStream.printf(locale, " stddev = %2.2f%n", snapshot.getStdDev()); this.outputBufferPrintStream.printf(locale, " median = %2.2f%n", snapshot.getMedian()); this.outputBufferPrintStream.printf(locale, " 75%% <= %2.2f%n", snapshot.get75thPercentile()); this.outputBufferPrintStream.printf(locale, " 95%% <= %2.2f%n", snapshot.get95thPercentile()); this.outputBufferPrintStream.printf(locale, " 98%% <= %2.2f%n", snapshot.get98thPercentile()); this.outputBufferPrintStream.printf(locale, " 99%% <= %2.2f%n", snapshot.get99thPercentile()); this.outputBufferPrintStream.printf(locale, " 99.9%% <= %2.2f%n", snapshot.get999thPercentile()); } private void printTimer(Timer timer) { final Snapshot snapshot = timer.getSnapshot(); this.outputBufferPrintStream.printf(locale, " count = %d%n", timer.getCount()); this.outputBufferPrintStream.printf(locale, " mean rate = %2.2f calls/%s%n", convertRate(timer.getMeanRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " 1-minute rate = %2.2f calls/%s%n", convertRate(timer.getOneMinuteRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " 5-minute rate = %2.2f calls/%s%n", convertRate(timer.getFiveMinuteRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " 15-minute rate = %2.2f calls/%s%n", convertRate(timer.getFifteenMinuteRate()), getRateUnit()); this.outputBufferPrintStream.printf(locale, " min = %2.2f %s%n", convertDuration(snapshot.getMin()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " max = %2.2f %s%n", convertDuration(snapshot.getMax()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " mean = %2.2f %s%n", convertDuration(snapshot.getMean()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " stddev = %2.2f %s%n", convertDuration(snapshot.getStdDev()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " median = %2.2f %s%n", convertDuration(snapshot.getMedian()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " 75%% <= %2.2f %s%n", convertDuration(snapshot.get75thPercentile()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " 95%% <= %2.2f %s%n", convertDuration(snapshot.get95thPercentile()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " 98%% <= %2.2f %s%n", convertDuration(snapshot.get98thPercentile()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " 99%% <= %2.2f %s%n", convertDuration(snapshot.get99thPercentile()), getDurationUnit()); this.outputBufferPrintStream.printf(locale, " 99.9%% <= %2.2f %s%n", convertDuration(snapshot.get999thPercentile()), getDurationUnit()); } private void printWithBanner(String s, char c) { this.outputBufferPrintStream.print(s); this.outputBufferPrintStream.print(' '); for (int i = 0; i < (CONSOLE_WIDTH - s.length() - 1); i++) { this.outputBufferPrintStream.print(c); } this.outputBufferPrintStream.println(); } }