Skip to content

Instantly share code, notes, and snippets.

@aleung
Created November 26, 2013 09:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aleung/7655478 to your computer and use it in GitHub Desktop.
Save aleung/7655478 to your computer and use it in GitHub Desktop.
Invocation metric bases on codahale (Yammer) Metrics. 这是在Messaging性能测试时使用的metrics,记录了代码块调用的throughput(TPS,latency)和active count(并发数),数据每分钟写入csv格式文件。 用try + finally的方式对调用进行拦截检测,代码有侵入性。当时是测试使用,这样写起来最快。
public interface ActiveCountMetric {
void inc();
void dec();
}
public interface ExecutionMetric {
void begin();
void end();
}
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();
}
}
}
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();
}
}
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();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment