Metrics可以為你的代碼的運作提供無與倫比的洞察力。作為一款監控名額的度量類庫,它提供了很多子產品可以為第三方庫或者應用提供輔助統計資訊, 比如Jetty, Logback, Log4j, Apache HttpClient, Ehcache, JDBI, Jersey, 它還可以将度量資料發送給Ganglia和Graphite以提供圖形化的監控。
Metrics提供了Gauge、Counter、Meter、Histogram、Timer等度量工具類以及Health Check功能。
引用Metric庫
将metrics-core加入到maven pom.xml中:
<dependencies>
<dependency>
<groupId>com.codahale.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${metrics.version}</version>
</dependency>
</dependencies>
将
metrics.version
設定為metrics最新的版本。
現在你可以在你的程式代碼中加入一些度量了。
Registry
Metric的中心部件是
MetricRegistry
。 它是程式中所有度量metric的容器。讓我們接着在代碼中加入一行:
final MetricRegistry metrics = new MetricRegistry();
Gauge (儀表)
Gauge
代表一個度量的即時值。 當你開汽車的時候, 目前速度是Gauge值。 你測體溫的時候, 體溫計的刻度是一個Gauge值。 當你的程式運作的時候, 記憶體使用量和CPU占用率都可以通過Gauge值來度量。
比如我們可以檢視一個隊列目前的size。
public class QueueManager {
private final Queue queue;
public QueueManager(MetricRegistry metrics, String name) {
this.queue = new Queue();
metrics.register(MetricRegistry.name(QueueManager.class, name, "size"),
new Gauge<Integer>() {
@Override
public Integer getValue() {
return queue.size();
}
});
}
}
registry
中每一個
metric
都有唯一的名字。 它可以是以.連接配接的字元串。 如"things.count" 和 "com.colobu.Thing.latency"。
MetricRegistry
提供了一個靜态的輔助方法用來生成這個名字:
MetricRegistry.name(QueueManager.class, "jobs", "size")
生成的name為
com.colobu.QueueManager.jobs.size
。
實際程式設計中對于隊列或者類似隊列的資料結構,你不會簡單的度量queue.size(), 因為在java.util和java.util.concurrent包中大部分的queue的#size是O(n),這意味的調用此方法會有性能的問題, 更深一步,可能會有lock的問題。
RatioGauge可以計算兩個Gauge的比值。 Meter和Timer可以參考下面的代碼建立。下面的代碼用來計算計算命中率 (hit/call)。
public class CacheHitRatio extends RatioGauge {
private final Meter hits;
private final Timer calls;
public CacheHitRatio(Meter hits, Timer calls) {
this.hits = hits;
this.calls = calls;
}
@Override
public Ratio getValue() {
return Ratio.of(hits.oneMinuteRate(),
calls.oneMinuteRate());
}
}
CachedGauge可以緩存耗時的測量。DerivativeGauge可以引用另外一個Gauage。
Counter (計數器)
Counter
是一個
AtomicLong
執行個體, 可以增加或者減少值。 例如,可以用它來計數隊列中加入的Job的總數。
private final Counter pendingJobs = metrics.counter(name(QueueManager.class, "pending-jobs"));
public void addJob(Job job) {
pendingJobs.inc();
queue.offer(job);
}
public Job takeJob() {
pendingJobs.dec();
return queue.take();
}
和上面Gauage不同,這裡我們使用的是metrics.counter方法而不是metrics.register方法。 使用metrics.counter更簡單。
Meter ()
Meter
用來計算事件的速率。 例如 request per second。 還可以提供1分鐘,5分鐘,15分鐘不斷更新的平均速率。
private final Meter requests = metrics.meter(name(RequestHandler.class, "requests"));
public void handleRequest(Request request, Response response) {
requests.mark();
// etc
}
Histogram (直方圖)
Histogram
可以為資料流提供統計資料。 除了最大值,最小值,平均值外,它還可以測量 中值(median),百分比比如XX%這樣的Quantile資料 。
private final Histogram responseSizes = metrics.histogram(name(RequestHandler.class, "response-sizes");
public void handleRequest(Request request, Response response) {
// etc
responseSizes.update(response.getContent().length);
}
這個例子用來統計response的位元組數。
Metrics提供了一批的Reservoir實作,非常有用。例如SlidingTimeWindowReservoir 用來統計最新N個秒(或其它時間單元)的資料。
Timer (計時器)
Timer
用來測量一段代碼被調用的速率和用時。
private final Timer responses = metrics.timer(name(RequestHandler.class, "responses"));
public String handleRequest(Request request, Response response) {
final Timer.Context context = responses.time();
try {
// etc;
return "OK";
} finally {
context.stop();
}
}
這段代碼用來計算中間的代碼用時以及request的速率。
Health Check (健康檢查)
Metric
還提供了服務健康檢查能力, 由
metrics-healthchecks
子產品提供。
先建立一個
HealthCheckRegistry
執行個體。
final HealthCheckRegistry healthChecks = new HealthCheckRegistry();
再實作一個
HealthCheck
子類, 用來檢查資料庫的狀态。
public class DatabaseHealthCheck extends HealthCheck {
private final Database database;
public DatabaseHealthCheck(Database database) {
this.database = database;
}
@Override
public HealthCheck.Result check() throws Exception {
if (database.isConnected()) {
return HealthCheck.Result.healthy();
} else {
return HealthCheck.Result.unhealthy("Cannot connect to " + database.getUrl());
}
}
}
注冊一下。
healthChecks.register("mysql", new DatabaseHealthCheck(database));
最後運作健康檢查并檢視檢查結果。
final Map<String, HealthCheck.Result> results = healthChecks.runHealthChecks();
for (Entry<String, HealthCheck.Result> entry : results.entrySet()) {
if (entry.getValue().isHealthy()) {
System.out.println(entry.getKey() + " is healthy");
} else {
System.err.println(entry.getKey() + " is UNHEALTHY: " + entry.getValue().getMessage());
final Throwable e = entry.getValue().getError();
if (e != null) {
e.printStackTrace();
}
}
}
Metric
内置一個ThreadDeadlockHealthCheck, 它使用java内置的線程死鎖檢查方法來檢查程式中是否有死鎖。
JMX報表
通過JMX報告Metric。
final JmxReporter reporter = JmxReporter.forRegistry(registry).build();
reporter.start();
一旦啟動, 所有registry中注冊的metric都可以通過JConsole或者VisualVM檢視 (通過MBean插件)。
HTTP報表
Metric也提供了一個servlet (AdminServlet)提供JSON風格的報表。它還提供了單一功能的servlet (MetricsServlet, HealthCheckServlet, ThreadDumpServlet, PingServlet)。
你需要在pom.xml加入metrics-servlets。
<dependency>
<groupId>com.codahale.metrics</groupId>
<artifactId>metrics-servlets</artifactId>
<version>${metrics.version}</version>
</dependency>
其它報表
除了JMX和HTTP, metric還提供其它報表。
- STDOUT, using ConsoleReporter from metrics-core
final ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(1, TimeUnit.MINUTES);
- CSV files, using CsvReporter from metrics-core
final CsvReporter reporter = CsvReporter.forRegistry(registry)
.formatFor(Locale.US)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(new File("~/projects/data/"));
reporter.start(1, TimeUnit.SECONDS);
- SLF4J loggers, using Slf4jReporter from metrics-core
final Slf4jReporter reporter = Slf4jReporter.forRegistry(registry)
.outputTo(LoggerFactory.getLogger("com.example.metrics"))
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(1, TimeUnit.MINUTES);
- Ganglia, using GangliaReporter from metrics-ganglia
final GMetric ganglia = new GMetric("ganglia.example.com", 8649, UDPAddressingMode.MULTICAST, 1);
final GangliaReporter reporter = GangliaReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(ganglia);
reporter.start(1, TimeUnit.MINUTES);
- Graphite, using GraphiteReporter from metrics-graphite
MetricSet
可以将一組Metric組織成一組便于重用。
final Graphite graphite = new Graphite(new InetSocketAddress("graphite.example.com", 2003));
final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
.prefixedWith("web1.example.com")
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.filter(MetricFilter.ALL)
.build(graphite);
reporter.start(1, TimeUnit.MINUTES);
一些子產品
- metrics-json提供了json格式的序列化。
- 以及為其它庫提供度量的能力
- metrics-ehcache
- metrics-httpclient
- metrics-jdbi
- metrics-jersey
- metrics-jetty
- metrics-log4j
- metrics-logback
- metrics-jvm
- metrics-servlet 注意不是metrics-servlets
第三方庫
- metrics-librato 提供Librato Metrics報表
- Metrics Spring Integration 提供了Spring的內建
- sematext-metrics-reporter 提供了SPM報表.
- wicket-metrics提供Wicket應用.
- metrics-guice 提供Guice內建.
- metrics-scala 提供了為Scala優化的API.
這裡重點介紹一下Metrics for Spring
Metrics for Spring
這個庫為Spring增加了Metric庫, 提供基于XML或者注解方式。
- 可以使用注解建立metric和代理類。 @Timed, @Metered, @ExceptionMetered, @Counted
- 為注解了 @Gauge 和 @CachedGauge的bean注冊Gauge
- 為@Metric注解的字段自動裝配
- 注冊HealthCheck
- 通過XML配置産生報表
- 通過XML注冊metric和metric組
你需要在pom.xml加入
<dependency>
<groupId>com.ryantenney.metrics</groupId>
<artifactId>metrics-spring</artifactId>
<version>3.0.1</version>
</dependency>
基本用法
- XML風格的配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:metrics="http://www.ryantenney.com/schema/metrics"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.ryantenney.com/schema/metrics
http://www.ryantenney.com/schema/metrics/metrics-3.0.xsd">
<!-- Registry should be defined in only one context XML file -->
<metrics:metric-registry id="metrics" />
<!-- annotation-driven must be included in all context files -->
<metrics:annotation-driven metric-registry="metrics" />
<!-- (Optional) Registry should be defined in only one context XML file -->
<metrics:reporter type="console" metric-registry="metrics" period="1m" />
<!-- (Optional) The metrics in this example require the metrics-jvm jar-->
<metrics:register metric-registry="metrics">
<bean metrics:name="jvm.gc" class="com.codahale.metrics.jvm.GarbageCollectorMetricSet" />
<bean metrics:name="jvm.memory" class="com.codahale.metrics.jvm.MemoryUsageGaugeSet" />
<bean metrics:name="jvm.thread-states" class="com.codahale.metrics.jvm.ThreadStatesGaugeSet" />
<bean metrics:name="jvm.fd.usage" class="com.codahale.metrics.jvm.FileDescriptorRatioGauge" />
</metrics:register>
<!-- Beans and other Spring config -->
</beans>
- java注解的方式
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
@Configuration
@EnableMetrics
public class SpringConfiguringClass extends MetricsConfigurerAdapter {
@Override
public void configureReporters(MetricRegistry metricRegistry) {
ConsoleReporter
.forRegistry(metricRegistry)
.build()
.start(1, TimeUnit.MINUTES);
}
}