metrics是一个JAVA的度量工具包,可以方便的服务进行监控和统计。目前最为流行的metrics库来自Coda Hale的dropwizard/metrics,这个库被多个知名的开源项目使用。
metrics的简单使用可以参考这里,官方文档
MetricRegistry
是metrics的核心类,是用来注册多个度量工具的地方。源码如下:
public class MetricRegistry implements MetricSet {
...
private final ConcurrentMap metrics;
private final List listeners;
/**
* Creates a new {@link MetricRegistry}.
*/
public MetricRegistry() {
this.metrics = buildMap();
this.listeners = new CopyOnWriteArrayList<>();
}
...
}
其中metrics是一个线性安全的map,key是度量类的名字,value就是具体的度量类。listeners是MetricRegistryListener的一个List,在现在版本上有两个具体的实现一个是JmxListener一个MetricRegistryListenerTest,但是MetricRegistryListener我工作中并没有涉及所有具体的源码我没有看,从名字以及MetricRegistry类上的方法可以猜出来是通过观察者模式实现的监听功能类。
Gauges接口是最简单的度量指标,只有一个简单的返回值
public interface Gauge<T> extends Metric {
T getValue();
}
在包里也有3个实现了Gauge接口的抽象类RatioGauge、CachedGauge、DerivativeGauge。
[转]例如,我们想衡量一个待处理队列中任务的个数,代码如下
public class GaugeTest {
public static Queue q = new LinkedList();
public static void main(String[] args) throws InterruptedException {
MetricRegistry registry = new MetricRegistry();
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(1, TimeUnit.SECONDS);
registry.register(MetricRegistry.name(GaugeTest.class, "queue", "size"),
new Gauge() {
public Integer getValue() {
return q.size();
}
});
while(true){
Thread.sleep(1000);
q.add("Job-xxx");
}
}
}
Counter 是一个简单的计数器,源码如下:
public class Counter implements Metric, Counting {
private final LongAdder count;
public Counter() {
this.count = new LongAdder();
}
public void inc() {
inc(1);
}
public void inc(long n) {
count.add(n);
}
public void dec() {
dec(1);
}
public void dec(long n) {
count.add(-n);
}
@Override
public long getCount() {
return count.sum();
}
}
可以看出Counter只是封装了LongAdder,实现了Counting接口,返回数值。
[转]我们可以使用如下的方法,使得获得队列大小更加高效。
public class CounterTest {
public static Queue q = new LinkedBlockingQueue();
public static Counter pendingJobs;
public static Random random = new Random();
public static void addJob(String job) {
pendingJobs.inc();
q.offer(job);
}
public static String takeJob() {
pendingJobs.dec();
return q.poll();
}
public static void main(String[] args) throws InterruptedException {
MetricRegistry registry = new MetricRegistry();
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(1, TimeUnit.SECONDS);
pendingJobs = registry.counter(MetricRegistry.name(Queue.class,"pending-jobs","size"));
int num = 1;
while(true){
Thread.sleep(200);
if (random.nextDouble() > 0.7){
String job = takeJob();
System.out.println("take job : "+job);
}else{
String job = "Job-"+num;
addJob(job);
System.out.println("add job : "+job);
}
num++;
}
}
}
Meter度量一系列事件发生的速率(rate),例如TPS。Meters会统计最近1分钟,5分钟,15分钟,还有全部时间的速率。
public class MeterTest {
public static Random random = new Random();
public static void request(Meter meter){
System.out.println("request");
meter.mark();
}
public static void request(Meter meter, int n){
while(n > 0){
request(meter);
n--;
}
}
public static void main(String[] args) throws InterruptedException {
MetricRegistry registry = new MetricRegistry();
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(1, TimeUnit.SECONDS);
Meter meterTps = registry.meter(MetricRegistry.name(MeterTest.class,"request","tps"));
while(true){
request(meterTps,random.nextInt(5));
Thread.sleep(1000);
}
}
}
Histogram统计数据的分布情况。比如最小值,最大值,中间值,还有中位数,75百分位, 90百分位, 95百分位, 98百分位, 99百分位, 和 99.9百分位的值(percentiles)。
看下源码:
public class Histogram implements Metric, Sampling, Counting {
private final Reservoir reservoir;
private final LongAdder count;
public Histogram(Reservoir reservoir) {
this.reservoir = reservoir;
this.count = new LongAdder();
}
public void update(int value) {
update((long) value);
}
public void update(long value) {
count.increment();
reservoir.update(value);
}
@Override
public long getCount() {
return count.sum();
}
@Override
public Snapshot getSnapshot() {
return reservoir.getSnapshot();
}
}
可以看到这里因为计算需要用到分布所以需要缓冲下统计区间的数据,Reservoir就是用来对数据进行缓存的。根据功能的不同目前有ExponentiallyDecayingReservoir、SlidingTimeWindowArrayReservoir、SlidingTimeWindowReservoir、SlidingWindowReservoir、UniformReservoir五个。这些类根据各自的需求将数据缓存到Snapshot,Snapshot又分为UniformSnapshot、WeightedSnapshot。
UniformSnapshot源码如下:
public class UniformSnapshot extends Snapshot {
private final long[] values;
public UniformSnapshot(Collection values) {
final Object[] copy = values.toArray();
this.values = new long[copy.length];
for (int i = 0; i < copy.length; i++) {
this.values[i] = (Long) copy[i];
}
Arrays.sort(this.values);
}
public UniformSnapshot(long[] values) {
this.values = Arrays.copyOf(values, values.length);
Arrays.sort(this.values);
}
@Override
public double getValue(double quantile) {
if (quantile < 0.0 || quantile > 1.0 || Double.isNaN(quantile)) {
throw new IllegalArgumentException(quantile + " is not in [0..1]");
}
if (values.length == 0) {
return 0.0;
}
final double pos = quantile * (values.length + 1);
final int index = (int) pos;
if (index < 1) {
return values[0];
}
if (index >= values.length) {
return values[values.length - 1];
}
final double lower = values[index - 1];
final double upper = values[index];
return lower + (pos - floor(pos)) * (upper - lower);
}
@Override
public int size() {
return values.length;
}
@Override
public long[] getValues() {
return Arrays.copyOf(values, values.length);
}
@Override
public long getMax() {
if (values.length == 0) {
return 0;
}
return values[values.length - 1];
}
@Override
public long getMin() {
if (values.length == 0) {
return 0;
}
return values[0];
}
@Override
public double getMean() {
if (values.length == 0) {
return 0;
}
double sum = 0;
for (long value : values) {
sum += value;
}
return sum / values.length;
}
@Override
public double getStdDev() {
// two-pass algorithm for variance, avoids numeric overflow
if (values.length <= 1) {
return 0;
}
final double mean = getMean();
double sum = 0;
for (long value : values) {
final double diff = value - mean;
sum += diff * diff;
}
final double variance = sum / (values.length - 1);
return Math.sqrt(variance);
}
}
很简单就是通过一个数组排序,利用quantile计算index直接取得分布。虽然简单但是疑问不懂在超大数据量的时候性能怎么样?
Histogram具体使用举个栗子:
public class HistogramTest {
public static Random random = new Random();
public static void main(String[] args) throws InterruptedException {
MetricRegistry registry = new MetricRegistry();
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(1, TimeUnit.SECONDS);
Histogram histogram = new Histogram(new ExponentiallyDecayingReservoir());
registry.register(MetricRegistry.name(HistogramTest.class, "request", "histogram"), histogram);
while(true){
Thread.sleep(1000);
histogram.update(random.nextInt(100000));
}
}
Timer其实是 Histogram 和 Meter 的结合, histogram 某部分代码/调用的耗时, meter统计TPS。
public class TimerTest {
public static Random random = new Random();
public static void main(String[] args) throws InterruptedException {
MetricRegistry registry = new MetricRegistry();
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(1, TimeUnit.SECONDS);
Timer timer = registry.timer(MetricRegistry.name(TimerTest.class,"get-latency"));
Timer.Context ctx;
while(true){
ctx = timer.time();
Thread.sleep(random.nextInt(1000));
ctx.stop();
}
}
}
上面简单的简绍如何使用5种metric度量类,掺杂的简绍了一点源码。可以看出目前这个工具主要用于实时的数据统计。