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-json提供了json格式的序列化。
第三方庫
- 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); } }