aleung
11/26/2013 - 9:12 AM

Invocation metric bases on codahale (Yammer) Metrics. 这是在Messaging性能测试时使用的metrics,记录了代码块调用的throughput(TPS,latency)和active count(并发数),数据每分

Invocation metric bases on codahale (Yammer) Metrics.

这是在Messaging性能测试时使用的metrics,记录了代码块调用的throughput(TPS,latency)和active count(并发数),数据每分钟写入csv格式文件。

用try + finally的方式对调用进行拦截检测,代码有侵入性。当时是测试使用,这样写起来最快。

    private String httpSend(String url, PostMethod method, HttpClient httpClient, HostConfiguration hostConfiguration)
            throws IOException {
        ExecutionMetric metric = GlobalMetricRegistry.getOrCreateInvocationMetric("Messaging",
                GlobalMetricRegistry.name(this.getClass(), "httpSend", url));
        metric.begin();
        try {
            int aStatusCode = send(httpClient, hostConfiguration, method);
            if (aStatusCode != HttpStatus.SC_OK) {
                if ((logger != null) && logger.isWarnEnabled()) {
                    logger.warn("HTTP error, object will not be read");
                }
                throw new RetryableException("HTTP Error: " + aStatusCode + " ("
                        + HttpStatus.getStatusText(aStatusCode) + ")");
            }
            return method.getResponseBodyAsString();
        } finally {
            metric.end();
        }
    }
package leoliang.foundation.metrics;

import java.io.File;
import java.util.concurrent.TimeUnit;

import com.codahale.metrics.CsvReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.ScheduledReporter;
import com.codahale.metrics.SharedMetricRegistries;

import leoliang.lifecycle.WSFApplicationLifecycleIF;

public class MetricsApplicationLifecycle implements WSFApplicationLifecycleIF {

    private ScheduledReporter reporter;

    @Override
    public void startup() {
        // FIXME: tmp solution, hard coded to "Messaging"
        MetricRegistry registry = SharedMetricRegistries.getOrCreate("Messaging");
        reporter = CsvReporter.forRegistry(registry)
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS).build(new File("/var/exposure/metrics/"));
        reporter.start(1, TimeUnit.MINUTES);
    }

    @Override
    public void shutdown() {
        reporter.stop();
    }
}
package leoliang.foundation.metrics;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.SlidingTimeWindowReservoir;
import com.codahale.metrics.Timer;

public class GlobalMetricRegistry {

    interface Creator {
        Metric create();
    }

    public static String name(Class<?> klass, String... names) {
        return replaceInvalidChars(MetricRegistry.name(klass, names));
    }

    public static String name(String name, String... names) {
        return replaceInvalidChars(MetricRegistry.name(name, names));
    }

    private static String replaceInvalidChars(String name) {
        return name.replaceAll("[^A-Za-z0-9\\.]", "_");
    }


    /**
     * InvocationMetric is a combination of ThroughputMetric and ActiveCountMetric
     * 
     * @param registryName
     * @param metricNamePrefix
     * @return
     */
    public static ExecutionMetric getOrCreateInvocationMetric(String registryName, String metricNamePrefix) {
        if (!isEnabled(registryName, metricNamePrefix))
            return new DummyExecutionMetric();

        return new InvocationMetricImpl(getOrCreateActiveCountMetric(registryName, metricNamePrefix),
                getOrCreateThroughputMetric(registryName, metricNamePrefix));
    }

    /**
     * ThroughputMetric measures the throughput and the distribution of execution time in last one minute. The
     * throughput is counted when execution ends.
     * 
     * @param registryName
     * @param metricNamePrefix
     * @return
     */
    public static ExecutionMetric getOrCreateThroughputMetric(String registryName, String metricNamePrefix) {
        if (!isEnabled(registryName, metricNamePrefix))
            return new DummyExecutionMetric();

        Metric metric = getOrCreate(registryName, metricNamePrefix + ".throughput", new TimerCreator());
        return new ThroughputMetricImpl((Timer) metric);
    }

    /**
     * ActiveCountMetric counts the number of items, and also keep track of the max and min value in last one minute.
     * <p>
     * Two metrics will be created: <i>[metricNamePrefix].now</i> and <i>[metricNamePrefix].history_1min</i>
     * </p>
     * 
     * @param registryName
     * @param metricNamePrefix
     * @return
     */
    public static ActiveCountMetric getOrCreateActiveCountMetric(String registryName, String metricNamePrefix) {
        if (!isEnabled(registryName, metricNamePrefix))
            return new DummyActiveCountMetric();

        Metric counter = getOrCreate(registryName, metricNamePrefix + ".active.now", new CounterCreator());
        Metric histogram = getOrCreate(registryName, metricNamePrefix + ".active.history_1min", new HistogramCreator());
        return new ActiveCountMetricImpl((Counter) counter, (Histogram) histogram);
    }


    private static boolean isEnabled(String registryName, String metricNamePrefix) {
        // TODO: make it configurable
        return Logger.getLogger("metrics").isLoggable(Level.INFO);
    }


    private static Metric getOrCreate(String registryName, String metricName, Creator creator) {
        MetricRegistry registry = SharedMetricRegistries.getOrCreate(registryName);
        Metric metric = registry.getMetrics().get(metricName);
        if (metric == null) {
            try {
                registry.register(metricName, creator.create());
            } catch (IllegalArgumentException e) {
                // ignore if metric already exists
            }
            metric = registry.getMetrics().get(metricName);
        }
        return metric;
    }

    private static class TimerCreator implements Creator {
        @Override
        public Metric create() {
            return new Timer(new SlidingTimeWindowReservoir(1, TimeUnit.MINUTES));
        }
    }

    private static class CounterCreator implements Creator {
        @Override
        public Metric create() {
            return new Counter();
        }
    }

    private static class HistogramCreator implements Creator {
        @Override
        public Metric create() {
            return new Histogram(new SlidingTimeWindowReservoir(1, TimeUnit.MINUTES));
        }
    }

    private static class ThroughputMetricImpl implements ExecutionMetric {
        private Timer timer;
        private Timer.Context context;

        private ThroughputMetricImpl(Timer timer) {
            this.timer = timer;
        }

        @Override
        public void begin() {
            context = timer.time();
        }

        @Override
        public void end() {
            context.close();
            context = null; // force to thrown exception on next call to end() without begin()
        }
    }

    private static class DummyActiveCountMetric implements ActiveCountMetric {

        @Override
        public void inc() {
            // do nothing
        }

        @Override
        public void dec() {
            // do nothing
        }
    }

    private static class ActiveCountMetricImpl implements ActiveCountMetric {
        private final Counter counter;
        private final Histogram histogram;

        public ActiveCountMetricImpl(Counter counter, Histogram histogram) {
            this.counter = counter;
            this.histogram = histogram;
        }

        @Override
        public void inc() {
            counter.inc();
            updateHistory();
        }

        private void updateHistory() {
            // no lock, it isn't important to get precise value
            histogram.update(counter.getCount());
        }

        @Override
        public void dec() {
            counter.dec();
            updateHistory();
        }
    }

    private static class DummyExecutionMetric implements ExecutionMetric {

        @Override
        public void begin() {
            // do nothing
        }

        @Override
        public void end() {
            // do nothing
        }
    }
    private static class InvocationMetricImpl implements ExecutionMetric {

        private final ActiveCountMetric counterMetric;
        private final ExecutionMetric throughputMetric;

        private InvocationMetricImpl(ActiveCountMetric counterMetric, ExecutionMetric throughputMetric) {
            this.counterMetric = counterMetric;
            this.throughputMetric = throughputMetric;
        }

        @Override
        public void begin() {
            counterMetric.inc();
            throughputMetric.begin();
        }

        @Override
        public void end() {
            counterMetric.dec();
            throughputMetric.end();
        }
    }

}
public interface ExecutionMetric {
    void begin();
    void end();
}
public interface ActiveCountMetric {
    void inc();
    void dec();
}