#524 复活Metrics模块

This commit is contained in:
calvin.xiao
2016-12-29 12:12:31 +08:00
parent 50f76a2e82
commit dd613bccfa
26 changed files with 2180 additions and 9 deletions
+2 -7
View File
@@ -1,15 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.springside</groupId>
<artifactId>springside-parent</artifactId>
<version>5.0.0-SNAPSHOT</version>
<relativePath>../modules/parent/</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>io.springside.examples</groupId>
<artifactId>springside-examples</artifactId>
<version>5.0.0-SNAPSHOT</version>
<name>Springside :: Examples</name>
<packaging>pom</packaging>
+74
View File
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.springside</groupId>
<artifactId>springside-modules</artifactId>
<version>5.0.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>springside-metrics</artifactId>
<packaging>jar</packaging>
<name>Springside :: Module :: Metrics</name>
<!-- 尽量少的引入依赖包,方便单独重用本模块 -->
<dependencies>
<!-- LOG -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- TEST -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- source attach plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- enforcer, 规则统一定义在parent -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,14 @@
/*******************************************************************************
* Copyright (c) 2005, 2017 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
/**
* 不同于定时计算并汇报的Reporter, Exporter以JMX/RESTFul等方式,暴露所有提供Metrics的实时查询接口.
*
* 但Exporter不会触发Metric计算,只会读取由ReporterScheduler产生的快照值
*/
public interface Exporter {
}
@@ -0,0 +1,194 @@
/*******************************************************************************
* Copyright (c) 2005, 2017 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.Timer;
/**
* 注册中心, 用户创建Metrics的入口.
* 支持多线程并发的取得或创建metrics.
*/
public class MetricRegistry {
public static final MetricRegistry INSTANCE = new MetricRegistry();
private Double[] defaultPcts = new Double[] {};
//从get的性能考虑,没有使用ConcurrentSkipListMap而是仍然使用ConcurrentHashMap.
private ConcurrentMap<String, Gauge> gauges = new ConcurrentHashMap<String, Gauge>();
private ConcurrentMap<String, Counter> counters = new ConcurrentHashMap<String, Counter>();
private ConcurrentMap<String, Histogram> histograms = new ConcurrentHashMap<String, Histogram>();
private ConcurrentMap<String, Timer> timers = new ConcurrentHashMap<String, Timer>();
private List<MetricRegistryListener> listeners = new ArrayList<MetricRegistryListener>();
/**
* 格式化以"."分割的Metrics Name的辅助函数.
*/
public static String name(String name, String... subNames) {
final StringBuilder builder = new StringBuilder(name);
if (subNames != null) {
for (String s : subNames) {
if ((s != null) && !s.isEmpty()) {
builder.append('.').append(s);
}
}
}
return builder.toString();
}
/**
* 在注册中心注册Gauge.
*/
public void registerGauge(String name, Gauge gauge) {
gauges.put(name, gauge);
}
/**
* 在注册中心获取或创建Counter.
*/
public Counter counter(String name) {
if (counters.containsKey(name)) {
return counters.get(name);
} else {
Counter counter = new Counter();
return register(counters, name, counter);
}
}
/**
* 在注册中心获取或创建Histogram, 并设置所需的百分比计算.
*/
public Histogram histogram(String name, Double... pcts) {
if (histograms.containsKey(name)) {
return histograms.get(name);
} else {
Histogram histogram = new Histogram(((pcts != null) && (pcts.length > 0)) ? pcts : defaultPcts);
return register(histograms, name, histogram);
}
}
/**
* 在注册中心获取或创建Timer, 并设置所需的百分比计算.
*/
public Timer timer(String name, Double... pcts) {
if (timers.containsKey(name)) {
return timers.get(name);
} else {
Timer timer = new Timer(((pcts != null) && (pcts.length > 0) ? pcts : defaultPcts));
return register(timers, name, timer);
}
}
/**
* 快速清理注册表中全部Metrics.
*/
public void clearAll() {
for (MetricRegistryListener listener : listeners) {
for (String key : gauges.keySet()) {
listener.onGaugeRemoved(key);
}
for (String key : counters.keySet()) {
listener.onCounterRemoved(key);
}
for (String key : histograms.keySet()) {
listener.onHistogramRemoved(key);
}
for (String key : timers.keySet()) {
listener.onTimerRemoved(key);
}
}
gauges.clear();
counters.clear();
histograms.clear();
timers.clear();
}
private <T> T register(ConcurrentMap<String, T> metrics, String name, T newMetric) {
T existingMetric = metrics.putIfAbsent(name, newMetric);
if (existingMetric != null) {
return existingMetric;
} else {
notifyNewMetric(name, newMetric);
return newMetric;
}
}
/**
* 通知Listener有新Metrics加入
*/
private void notifyNewMetric(String name, Object newMetric) {
for (MetricRegistryListener listener : listeners) {
if (newMetric instanceof Gauge) {
listener.onGaugeAdded(name, (Gauge) newMetric);
}
if (newMetric instanceof Counter) {
listener.onCounterAdded(name, (Counter) newMetric);
}
if (newMetric instanceof Histogram) {
listener.onHistogramAdded(name, (Histogram) newMetric);
}
if (newMetric instanceof Timer) {
listener.onTimerAdded(name, (Timer) newMetric);
}
}
}
/**
* 返回所有Gauge, 无序.
*/
public Map<String, Gauge> getGauges() {
return gauges;
}
/**
* 返回所有Counter, 无序.
*/
public Map<String, Counter> getCounters() {
return counters;
}
/**
* 返回所有Histogram, 无序.
*/
public Map<String, Histogram> getHistograms() {
return histograms;
}
/**
* 返回所有Timer, 无序.
*/
public Map<String, Timer> getTimers() {
return timers;
}
/**
* 重新设置默认的百分比设置.
*/
public void setDefaultPcts(Double[] defaultPcts) {
this.defaultPcts = defaultPcts;
}
/**
* Exporter需要将自己加为Listener.
*/
public void addListener(MetricRegistryListener listener) {
listeners.add(listener);
}
}
@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2005, 2017 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.Timer;
/**
* Registry中Metrics有注册或注销变化时的通知接口
*/
public interface MetricRegistryListener {
void onGaugeAdded(String name, Gauge gauge);
void onCounterAdded(String name, Counter counter);
void onHistogramAdded(String name, Histogram histogram);
void onTimerAdded(String name, Timer timer);
void onGaugeRemoved(String name);
void onCounterRemoved(String name);
void onHistogramRemoved(String name);
void onTimerRemoved(String name);
}
@@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (c) 2005, 2017 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.Timer;
/**
* Reporter定时任务, 定期计算所有Metrics, 并调用所有Reporter进行汇报.
*
* 由用户负责reporter的初始化及定时管理任务的启停.
*/
public class ReportScheduler {
private static final String SCHEDULER_THREAD_NAME = "metrics-reporter";
private static Logger logger = LoggerFactory.getLogger(ReportScheduler.class);
private MetricRegistry metricRegistry;
private List<Reporter> reporters;
private ScheduledExecutorService executor;
private volatile boolean started;
public ReportScheduler(MetricRegistry metricRegistry, Reporter... reporters) {
this(metricRegistry, Arrays.asList(reporters));
}
public ReportScheduler(MetricRegistry metricRegistry, List<Reporter> reporters) {
this.metricRegistry = metricRegistry;
this.reporters = reporters;
this.executor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(SCHEDULER_THREAD_NAME));
}
public void addReporter(Reporter reporter) {
reporters.add(reporter);
}
public void removeReporter(Reporter reporter) {
reporters.remove(reporter);
}
/**
* 启动定时任务.
*/
public void start(long period, TimeUnit unit) {
if (started) {
throw new IllegalStateException("Scheduler had been started before");
}
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
report();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}, period, period, unit);
started = true;
logger.info("metric reporters started.");
}
/**
* 启动停止任务.
*/
public void stop() {
if (!started) {
throw new IllegalStateException("Scheduler hadn't been started before");
}
executor.shutdownNow();
try {
if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
logger.info("metric reporters stopped.");
} else {
logger.info("metric reporters can't stop in 5 seconds, force stopped.");
}
started = false;
} catch (InterruptedException ignored) {
// do nothing
}
}
/**
* 定期计算所有Metrics, 并调用所有Reporter进行汇报.
*/
public void report() {
// 取出所有Metrics, 未按名称排序.
Map<String, Gauge> gaugeMap = metricRegistry.getGauges();
Map<String, Counter> counterMap = metricRegistry.getCounters();
Map<String, Histogram> histogramMap = metricRegistry.getHistograms();
Map<String, Timer> timerMap = metricRegistry.getTimers();
// 调度每个Metrics的caculateMetrics()方法
// 计算单位时间内的metrics值, 存入该Metrics的Snapshot中,并清零原始数据
for (Gauge gauge : gaugeMap.values()) {
gauge.calculateMetric();
}
for (Counter counter : counterMap.values()) {
counter.calculateMetric();
}
for (Histogram histogram : histogramMap.values()) {
histogram.calculateMetric();
}
for (Timer timer : timerMap.values()) {
timer.calculateMetric();
}
// 调度所有Reporters 输出 metrics值
for (Reporter reporter : reporters) {
reporter.report(gaugeMap, counterMap, histogramMap, timerMap);
}
}
/**
* 给线程命名的线程工厂类,为了减少项目依赖,没有直接使用Guava里的实现。
*/
private static class NamedThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private NamedThreadFactory(String name) {
final SecurityManager s = System.getSecurityManager();
this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
this.namePrefix = name;
}
@Override
public Thread newThread(Runnable r) {
final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(true);
return t;
}
}
}
@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright (c) 2005, 2017 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import java.util.Map;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.Timer;
/**
* Reporter的公共接口, 被ReportScheduler定时调用输出报告.
*/
public interface Reporter {
void report(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms,
Map<String, Timer> timers);
}
@@ -0,0 +1,362 @@
package org.springside.modules.metrics.exporter;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springside.modules.metrics.Exporter;
import org.springside.modules.metrics.MetricRegistry;
import org.springside.modules.metrics.MetricRegistryListener;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.Timer;
/**
* 以JMX形式,暴露所有Metrics.
*
* 为每一个Metrics注册一个MBean, 实现MetricRegistryListener接口感知Metrics的变化.
*
* TODO: 实现一个将所有Metrics暴露成一个总的JSON字符串的选项.
*/
public class JmxExporter implements Exporter, MetricRegistryListener {
private static Logger logger = LoggerFactory.getLogger(JmxExporter.class);
private MBeanServer mBeanServer;
private MetricRegistry registry;
private String domain;
private final Map<ObjectName, ObjectName> registered;
public JmxExporter(String domain, MetricRegistry registry) {
this.mBeanServer = ManagementFactory.getPlatformMBeanServer();
this.registry = registry;
this.domain = domain;
this.registered = new ConcurrentHashMap<ObjectName, ObjectName>();
registry.addListener(this);
}
public void initMBeans() {
Map<String, Gauge> gauges = registry.getGauges();
for (Entry<String, Gauge> entry : gauges.entrySet()) {
onGaugeAdded(entry.getKey(), entry.getValue());
}
Map<String, Counter> counters = registry.getCounters();
for (Entry<String, Counter> entry : counters.entrySet()) {
onCounterAdded(entry.getKey(), entry.getValue());
}
Map<String, Histogram> histograms = registry.getHistograms();
for (Entry<String, Histogram> entry : histograms.entrySet()) {
onHistogramAdded(entry.getKey(), entry.getValue());
}
Map<String, Timer> timers = registry.getTimers();
for (Entry<String, Timer> entry : timers.entrySet()) {
onTimerAdded(entry.getKey(), entry.getValue());
}
}
public void destroyMBeans() {
Map<String, Gauge> gauges = registry.getGauges();
for (String key : gauges.keySet()) {
onGaugeRemoved(key);
}
Map<String, Counter> counters = registry.getCounters();
for (String key : counters.keySet()) {
onCounterRemoved(key);
}
Map<String, Histogram> histograms = registry.getHistograms();
for (String key : histograms.keySet()) {
onHistogramRemoved(key);
}
Map<String, Timer> timers = registry.getTimers();
for (String key : timers.keySet()) {
onTimerRemoved(key);
}
}
private void registerMBean(Object mBean, ObjectName objectName) {
try {
ObjectInstance objectInstance = mBeanServer.registerMBean(mBean, objectName);
if (objectInstance != null) {
// the websphere mbeanserver rewrites the objectname to include cell, node & server info
// make sure we capture the new objectName for unregistration
registered.put(objectName, objectInstance.getObjectName());
} else {
registered.put(objectName, objectName);
}
} catch (InstanceAlreadyExistsException e) {
logger.debug("Unable to register:" + objectName, e);
} catch (JMException e) {
logger.warn("Unable to register:" + objectName, e);
}
}
private void unregisterMBean(ObjectName originalObjectName) {
ObjectName storedObjectName = registered.remove(originalObjectName);
try {
if (storedObjectName != null) {
mBeanServer.unregisterMBean(storedObjectName);
} else {
mBeanServer.unregisterMBean(originalObjectName);
}
} catch (InstanceNotFoundException e) {
logger.debug("Unable to unregister:" + originalObjectName, e);
} catch (MBeanRegistrationException e) {
logger.warn("Unable to unregister:" + originalObjectName, e);
}
}
private ObjectName createName(String type, String name) {
try {
return new ObjectName(this.domain, "name", name);
} catch (MalformedObjectNameException e) {
try {
return new ObjectName(this.domain, "name", ObjectName.quote(name));
} catch (MalformedObjectNameException e1) {
logger.warn("Unable to register {} {}", type, name, e1);
throw new RuntimeException(e1);
}
}
}
@Override
public void onGaugeAdded(String name, Gauge gauge) {
final ObjectName objectName = createName("gauges", name);
registerMBean(new JmxGauge(gauge, objectName), objectName);
}
@Override
public void onCounterAdded(String name, Counter counter) {
final ObjectName objectName = createName("counters", name);
registerMBean(new JmxCounter(counter, objectName), objectName);
}
@Override
public void onHistogramAdded(String name, Histogram histogram) {
final ObjectName objectName = createName("histograms", name);
registerMBean(new JmxHistogram(histogram, objectName), objectName);
}
@Override
public void onTimerAdded(String name, Timer timer) {
final ObjectName objectName = createName("timers", name);
registerMBean(new JmxTimer(timer, objectName), objectName);
}
@Override
public void onGaugeRemoved(String name) {
final ObjectName objectName = createName("guages", name);
unregisterMBean(objectName);
}
@Override
public void onCounterRemoved(String name) {
final ObjectName objectName = createName("counters", name);
unregisterMBean(objectName);
}
@Override
public void onHistogramRemoved(String name) {
final ObjectName objectName = createName("histograms", name);
unregisterMBean(objectName);
}
@Override
public void onTimerRemoved(String name) {
final ObjectName objectName = createName("timers", name);
unregisterMBean(objectName);
}
public interface MetricMBean {
ObjectName objectName();
}
public interface JmxGaugeMBean extends MetricMBean {
Number getValue();
}
public interface JmxCounterMBean extends MetricMBean {
long getLatestCount();
long getLatestRate();
long getTotalCount();
long getMeanRate();
}
public interface JmxHistogramMBean extends MetricMBean {
long getMin();
long getMax();
double getMean();
// TODO: add pcts
}
public interface JmxTimerMBean extends MetricMBean {
long getLatestCount();
long getLatestRate();
long getTotalCount();
long getMeanRate();
long getMinLatency();
long getMaxLatency();
double getMeanLatency();
// TODO: add pcts
}
private abstract static class AbstractBean implements MetricMBean {
private final ObjectName objectName;
AbstractBean(ObjectName objectName) {
this.objectName = objectName;
}
@Override
public ObjectName objectName() {
return objectName;
}
}
private static class JmxGauge extends AbstractBean implements JmxGaugeMBean {
private final Gauge metric;
public JmxGauge(Gauge gauge, ObjectName objectName) {
super(objectName);
this.metric = gauge;
}
@Override
public Number getValue() {
return metric.latestMetric;
}
}
private static class JmxCounter extends AbstractBean implements JmxCounterMBean {
private final Counter metric;
private JmxCounter(Counter metric, ObjectName objectName) {
super(objectName);
this.metric = metric;
}
@Override
public long getLatestCount() {
return metric.latestMetric.latestCount;
}
@Override
public long getLatestRate() {
return metric.latestMetric.latestRate;
}
@Override
public long getTotalCount() {
return metric.latestMetric.totalCount;
}
@Override
public long getMeanRate() {
return metric.latestMetric.meanRate;
}
}
private static class JmxHistogram extends AbstractBean implements JmxHistogramMBean {
private final Histogram metric;
private JmxHistogram(Histogram metric, ObjectName objectName) {
super(objectName);
this.metric = metric;
}
@Override
public long getMin() {
return metric.latestMetric.min;
}
@Override
public long getMax() {
return metric.latestMetric.max;
}
@Override
public double getMean() {
return metric.latestMetric.mean;
}
}
private static class JmxTimer extends AbstractBean implements JmxTimerMBean {
private final Timer metric;
private JmxTimer(Timer metric, ObjectName objectName) {
super(objectName);
this.metric = metric;
}
@Override
public long getLatestCount() {
return metric.latestMetric.counterMetric.latestCount;
}
@Override
public long getLatestRate() {
return metric.latestMetric.counterMetric.latestRate;
}
@Override
public long getTotalCount() {
return metric.latestMetric.counterMetric.totalCount;
}
@Override
public long getMeanRate() {
return metric.latestMetric.counterMetric.meanRate;
}
@Override
public long getMinLatency() {
return metric.latestMetric.histogramMetric.min;
}
@Override
public long getMaxLatency() {
return metric.latestMetric.histogramMetric.max;
}
@Override
public double getMeanLatency() {
return metric.latestMetric.histogramMetric.mean;
}
}
}
@@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.metric;
import java.util.concurrent.atomic.AtomicLong;
import org.springside.modules.metrics.utils.Clock;
/**
* Counter类型, 主要用于计算QPS.
*
* 报告Report间隔时间内的Counter变化值及平均QPS,以及服务从启动到现在的Counter总值和平均QPS.
*/
public class Counter {
public static Clock clock = Clock.DEFAULT;
public CounterMetric latestMetric; // Snapshot值
private AtomicLong counter; // 统计周期内的计数器
private long startTime; // 启动时间
private long lastReportTime; // 上一次报告的时间
private long totalCount; // 从启动到上一次报告周期的总调用次数
public Counter() {
reset();
}
public void inc() {
counter.incrementAndGet();
}
public void inc(long n) {
counter.addAndGet(n);
}
public void dec() {
counter.decrementAndGet();
}
public void dec(long n) {
counter.addAndGet(-n);
}
/**
* 计算单位时间内的metrics值, 存入该Metrics的Snapshot中,并清零原始数据.
*/
public CounterMetric calculateMetric() {
long latestCount = counter.getAndSet(0);
long currentTime = clock.getCurrentTime();
CounterMetric metric = new CounterMetric();
totalCount += latestCount;
long totalElapsed = currentTime - startTime;
metric.meanRate = (totalCount * 1000) / totalElapsed;
metric.latestCount = latestCount;
metric.totalCount = totalCount;
long elapsed = currentTime - lastReportTime;
if (elapsed > 0) {
metric.latestRate = (latestCount * 1000) / elapsed;
}
lastReportTime = currentTime;
latestMetric = metric;
return metric;
}
public void reset() {
latestMetric = new CounterMetric();
counter = new AtomicLong(0);
totalCount = 0L;
startTime = clock.getCurrentTime();
lastReportTime = startTime;
}
@Override
public String toString() {
return "Counter [latestMetric=" + latestMetric + ", counter=" + counter + ", totalCount=" + totalCount
+ ", startTime=" + startTime + ", lastReportTime=" + lastReportTime + "]";
}
}
@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.metric;
/**
* Counter的统计值
*/
public class CounterMetric {
public long totalCount; //从启动到目前到调用次数
public long meanRate; //从启动到目前到平均QPS
public long latestCount;//最后一个报告周期的调用次数
public long latestRate; //最后一个报告周期的QPS
@Override
public String toString() {
return "CounterMetric [totalCount=" + totalCount + ", meanRate=" + meanRate + ", latestCount=" + latestCount
+ ", latestRate=" + latestRate + "]";
}
}
@@ -0,0 +1,26 @@
/*------------------------------------------------------------------------------
* COPYRIGHT Ericsson 2014
*
* The copyright to the computer program(s) herein is the property of
* Ericsson Inc. The programs may be used and/or copied only with written
* permission from Ericsson Inc. or in accordance with the terms and
* conditions stipulated in the agreement/contract under which the
* program(s) have been supplied.
*----------------------------------------------------------------------------*/
package org.springside.modules.metrics.metric;
public abstract class Gauge {
public Number latestMetric;
public void calculateMetric() {
latestMetric = getValue();
}
protected abstract Number getValue();
@Override
public String toString() {
return "Gauge [latestMetric=" + latestMetric + "]";
}
}
@@ -0,0 +1,118 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.metric;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Histogram数据类型, 主要用于计算Latency.
*
* 报告Report间隔时间内数值的最小/最大,平均值,以及某个百分比的请求数都小于的值.
*/
public class Histogram {
public HistogramMetric latestMetric;// Snapshot值
private Double[] pcts; //配置需要计算的百分位
private LinkedList<Long> measurements; //统计周期内的原始数据
public Histogram(Double... pcts) {
this.pcts = pcts;
measurements = new LinkedList<Long>();
latestMetric = createEmptyMetric();
}
public void update(long value) {
synchronized (measurements) {
measurements.add(value);
}
}
/**
* 计算单位时间内的metrics值, 存入该Metrics的Snapshot中,并清零原始数据.
*/
public HistogramMetric calculateMetric() {
// 快照当前的数据,在计算时不阻塞新的metrics update.
List<Long> snapshotList = measurements;
measurements = new LinkedList<Long>();
if (snapshotList.isEmpty()) {
return createEmptyMetric();
}
HistogramMetric metric = new HistogramMetric();
int count = snapshotList.size();
double sum = 0;
if ((pcts != null) && (pcts.length > 0)) {
// 按数值大小排序,以快速支持百分比过滤
Collections.sort(snapshotList);
metric.min = snapshotList.get(0);
metric.max = snapshotList.get(count - 1);
for (long value : snapshotList) {
sum += value;
}
for (Double pct : pcts) {
metric.pcts.put(pct, getPercent(snapshotList, count, pct));
}
} else {
// 不排序的算法,因为不需要支持百分比过滤
metric.min = snapshotList.get(0);
metric.max = snapshotList.get(0);
for (long value : snapshotList) {
if (value < metric.min) {
metric.min = value;
}
if (value > metric.max) {
metric.max = value;
}
sum += value;
}
}
metric.mean = sum / count;
latestMetric = metric;
return metric;
}
private HistogramMetric createEmptyMetric() {
HistogramMetric metric = new HistogramMetric();
for (Double pct : pcts) {
metric.pcts.put(pct, 0L);
}
return metric;
}
private Long getPercent(List<Long> snapshotList, int count, double pct) {
final double pos = (pct * (count + 1)) / 100;
if (pos < 1) {
return snapshotList.get(0);
}
if (pos >= count) {
return snapshotList.get(count - 1);
}
return snapshotList.get((int) pos - 1);
}
@Override
public String toString() {
return "Histogram [latestMetric=" + latestMetric + ", measurements=" + measurements + ", pcts="
+ Arrays.toString(pcts) + "]";
}
}
@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.metric;
import java.util.Map;
import java.util.TreeMap;
/**
* Histogram的统计值
*/
public class HistogramMetric {
public long min; //最小
public long max; //最大
public double mean; //平均
public Map<Double, Long> pcts = new TreeMap<Double, Long>(); //百分位数
@Override
public String toString() {
return "HistogramMetric [min=" + min + ", max=" + max + ", mean=" + mean + ", pcts=" + pcts + "]";
}
}
@@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.metric;
import org.springside.modules.metrics.utils.Clock;
/**
* Timer类型,兼具Counter和Histogram的简便写法.
*
* 有两种用法:
* 1. 使用timer
*
* <pre>
* TimerContext timerContext = timer.start();
* ...
* timerContext.stop();
* </pre>
*
* 2. 自行计算,更吝啬一点
*
* <pre>
* long start = System.currentTimeMillis();
* ....
* timer.update(start);
* </pre>
*/
public class Timer {
public static Clock clock = Clock.DEFAULT;
public TimerMetric latestMetric; //snapshot值
private Counter counter;
private Histogram histogram;
public Timer(Double... pcts) {
counter = new Counter();
histogram = new Histogram(pcts);
latestMetric = new TimerMetric();
}
public void update(long start) {
histogram.update(clock.getCurrentTime() - start);
counter.inc();
}
public TimerContext start() {
return new TimerContext(this, clock.getCurrentTime());
}
private void stopTimer(long startTime) {
long elapsed = clock.getCurrentTime() - startTime;
histogram.update(elapsed);
counter.inc();
}
/**
* 计算单位时间内的metrics值, 存入该Metrics的Snapshot中,并清零原始数据.
*/
public TimerMetric calculateMetric() {
TimerMetric metric = new TimerMetric();
metric.counterMetric = counter.calculateMetric();
metric.histogramMetric = histogram.calculateMetric();
latestMetric = metric;
return metric;
}
@Override
public String toString() {
return "Timer [latestMetric=" + latestMetric + ", counter=" + counter + ", histogram=" + histogram + "]";
}
/**
* 保存某一次请求的初始时间与Timer实例.
*/
public static class TimerContext {
private final Timer timer;
private final long startTime;
private TimerContext(Timer timer, long startTime) {
this.timer = timer;
this.startTime = startTime;
}
public void stop() {
timer.stopTimer(startTime);
}
}
}
@@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.metric;
/**
* Timer的统计值
*/
public class TimerMetric {
public CounterMetric counterMetric;
public HistogramMetric histogramMetric;
@Override
public String toString() {
return "TimerMetric [counterMetric=" + counterMetric + ", histogramMetric=" + histogramMetric + "]";
}
}
@@ -0,0 +1,126 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.reporter;
import java.io.PrintStream;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.springside.modules.metrics.Reporter;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.CounterMetric;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.HistogramMetric;
import org.springside.modules.metrics.metric.Timer;
import org.springside.modules.metrics.metric.TimerMetric;
/**
* 将Metrics打印到Console.
*/
public class ConsoleReporter implements Reporter {
private static final int CONSOLE_WIDTH = 80;
private PrintStream output = System.out;
@Override
public void report(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms,
Map<String, Timer> timers) {
printWithBanner(new Date().toString(), '=');
output.println();
if (!gauges.isEmpty()) {
printWithBanner("-- Gaugues", '-');
for (Map.Entry<String, Gauge> entry : getSortedMetrics(gauges).entrySet()) {
output.println(entry.getKey());
printGauge(entry.getValue());
}
output.println();
}
if (!counters.isEmpty()) {
printWithBanner("-- Counters", '-');
for (Map.Entry<String, Counter> entry : getSortedMetrics(counters).entrySet()) {
output.println(entry.getKey());
printCounter(entry.getValue().latestMetric);
}
output.println();
}
if (!histograms.isEmpty()) {
printWithBanner("-- Histograms", '-');
for (Map.Entry<String, Histogram> entry : getSortedMetrics(histograms).entrySet()) {
output.println(entry.getKey());
printHistogram(entry.getValue().latestMetric);
}
output.println();
}
if (!timers.isEmpty()) {
printWithBanner("-- Timers", '-');
for (Map.Entry<String, Timer> entry : getSortedMetrics(timers).entrySet()) {
output.println(entry.getKey());
printTimer(entry.getValue().latestMetric);
}
output.println();
}
}
private void printWithBanner(String s, char c) {
output.print(s);
output.print(' ');
for (int i = 0; i < (CONSOLE_WIDTH - s.length() - 1); i++) {
output.print(c);
}
output.println();
}
private void printGauge(Gauge gauge) {
output.printf(" value = %s%n", gauge.latestMetric);
}
private void printCounter(CounterMetric counter) {
output.printf(" latest count = %d%n", counter.latestCount);
output.printf(" total count = %d%n", counter.totalCount);
output.printf(" latest rate = %d%n", counter.latestRate);
output.printf(" mean rate = %d%n", counter.meanRate);
}
private void printHistogram(HistogramMetric histogram) {
output.printf(" min = %d%n", histogram.min);
output.printf(" max = %d%n", histogram.max);
output.printf(" mean = %2.2f%n", histogram.mean);
for (Entry<Double, Long> pct : histogram.pcts.entrySet()) {
output.printf(" %2.2f%% <= %d %n", pct.getKey(), pct.getValue());
}
}
private void printTimer(TimerMetric timer) {
output.printf(" latest count = %d%n", timer.counterMetric.latestCount);
output.printf(" total count = %d%n", timer.counterMetric.totalCount);
output.printf(" latest rate = %d%n", timer.counterMetric.latestRate);
output.printf(" mean rate = %d%n", timer.counterMetric.meanRate);
output.printf(" min latency = %d ms%n", timer.histogramMetric.min);
output.printf(" max latency = %d ms%n", timer.histogramMetric.max);
output.printf(" mean latency = %2.2f ms%n", timer.histogramMetric.mean);
for (Entry<Double, Long> pct : timer.histogramMetric.pcts.entrySet()) {
output.printf(" %2.2f%% latency <= %d ms%n", pct.getKey(), pct.getValue());
}
}
/**
* 返回按metrics name排序的Map.
*
* 从get的性能考虑,没有使用ConcurrentSkipListMap而是仍然使用ConcurrentHashMap,因此每次报告时需要用TreeMap重新排序.
*/
private <T> SortedMap<String, T> getSortedMetrics(Map<String, T> metrics) {
return new TreeMap<String, T>(metrics);
}
}
@@ -0,0 +1,219 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.reporter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import javax.net.SocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springside.modules.metrics.MetricRegistry;
import org.springside.modules.metrics.Reporter;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.CounterMetric;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.HistogramMetric;
import org.springside.modules.metrics.metric.Timer;
import org.springside.modules.metrics.metric.TimerMetric;
/**
* 输出到Graphite.
*/
public class GraphiteReporter implements Reporter {
public static final String DEFAULT_PREFIX = "metrics";
private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static Logger logger = LoggerFactory.getLogger(GraphiteReporter.class);
private String prefix;
private InetSocketAddress address;
private SocketFactory socketFactory;
private Socket socket;
private Writer writer;
// use to only print connection error message once.
private GraphiteConnStatus graphiteConnStatus = GraphiteConnStatus.CONN_OK;
public GraphiteReporter(InetSocketAddress address) {
this(address, DEFAULT_PREFIX);
}
public GraphiteReporter(InetSocketAddress address, String prefix) {
this.prefix = prefix;
this.address = address;
this.socketFactory = SocketFactory.getDefault();
}
@Override
public void report(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms,
Map<String, Timer> timers) {
try {
connect();
long timestamp = System.currentTimeMillis() / 1000;
for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
reportGauge(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
for (Map.Entry<String, Counter> entry : counters.entrySet()) {
reportCounter(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
reportHistogram(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
for (Map.Entry<String, Timer> entry : timers.entrySet()) {
reportTimer(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
flush();
onConnSuccess();
} catch (IOException e) {
onConnFail(e);
} finally {
try {
close();
} catch (IOException e) {
logger.warn("Error disconnecting from Graphite", e);
}
}
}
private void reportGauge(String name, Number gauge, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "gauge"), format(gauge), timestamp);
}
private void reportCounter(String name, CounterMetric counter, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "count"), format(counter.latestCount), timestamp);
}
private void reportHistogram(String name, HistogramMetric histogram, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "min"), format(histogram.min), timestamp);
send(MetricRegistry.name(prefix, name, "max"), format(histogram.max), timestamp);
send(MetricRegistry.name(prefix, name, "mean"), format(histogram.mean), timestamp);
for (Entry<Double, Long> pct : histogram.pcts.entrySet()) {
send(MetricRegistry.name(prefix, name, format(pct.getKey()).replace('.', '_')), format(pct.getValue()),
timestamp);
}
}
private void reportTimer(String name, TimerMetric timer, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "count"), format(timer.counterMetric.latestCount), timestamp);
send(MetricRegistry.name(prefix, name, "min"), format(timer.histogramMetric.min), timestamp);
send(MetricRegistry.name(prefix, name, "max"), format(timer.histogramMetric.max), timestamp);
send(MetricRegistry.name(prefix, name, "mean"), format(timer.histogramMetric.mean), timestamp);
for (Entry<Double, Long> pct : timer.histogramMetric.pcts.entrySet()) {
send(MetricRegistry.name(prefix, name, format(pct.getKey()).replace('.', '_')), format(pct.getValue()),
timestamp);
}
}
private void connect() throws IllegalStateException, IOException {
if (socket != null) {
throw new IllegalStateException("Already connected");
}
this.socket = socketFactory.createSocket(address.getAddress(), address.getPort());
this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8));
}
private void send(String name, String value, long timestamp) throws IOException {
try {
writer.write(sanitize(name));
writer.write(' ');
writer.write(sanitize(value));
writer.write(' ');
writer.write(format(timestamp));
writer.write('\n');
} catch (IOException e) {
throw e;
}
}
private void flush() throws IOException {
writer.flush();
}
private void close() throws IOException {
if (writer != null) {
writer.flush();
}
if (socket != null) {
socket.close();
}
this.socket = null;
this.writer = null;
}
private String format(long n) {
return Long.toString(n);
}
private String format(double v) {
return String.format(Locale.US, "%2.2f", v);
}
private String format(Number o) {
if (o instanceof Float) {
return format(((Float) o).doubleValue());
} else if (o instanceof Double) {
return format(((Double) o).doubleValue());
} else if (o instanceof Short) {
return format(((Short) o).longValue());
} else if (o instanceof Integer) {
return format(((Integer) o).longValue());
} else if (o instanceof Long) {
return format(((Long) o).longValue());
}
return null;
}
private String sanitize(String s) {
return WHITESPACE.matcher(s).replaceAll("-");
}
private void onConnFail(Exception exception) {
if (graphiteConnStatus != GraphiteConnStatus.CONN_NOK) {
logger.warn("Unable to report to Graphite", exception);
graphiteConnStatus = GraphiteConnStatus.CONN_NOK;
}
}
private void onConnSuccess() {
if (graphiteConnStatus != GraphiteConnStatus.CONN_OK) {
logger.info("Graphite connection is recovered.");
graphiteConnStatus = GraphiteConnStatus.CONN_OK;
}
}
private enum GraphiteConnStatus {
CONN_OK, CONN_NOK
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
@@ -0,0 +1,105 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.reporter;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springside.modules.metrics.Reporter;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.CounterMetric;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.HistogramMetric;
import org.springside.modules.metrics.metric.Timer;
import org.springside.modules.metrics.metric.TimerMetric;
/**
* 使用Slf4j以INFO级别将Metrics打印到日志.
*
* 默认logger name是"metrics",可在构造函数中设定.用户需要在日志的配置文件中对该logger进行正确配置.
*
* TODO:
* 1.输出为JSON的选项
* 2.日志字符串的性能调优
*/
public class Slf4jReporter implements Reporter {
private static final String DEFAULT_LOGGER_NAME = "metrics";
private Logger reportLogger;
public Slf4jReporter() {
this(DEFAULT_LOGGER_NAME);
}
public Slf4jReporter(String loggerName) {
reportLogger = LoggerFactory.getLogger(loggerName);
}
@Override
public void report(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms,
Map<String, Timer> timers) {
for (Entry<String, Gauge> entry : getSortedMetrics(gauges).entrySet()) {
logGauge(entry.getKey(), entry.getValue().latestMetric);
}
for (Entry<String, Counter> entry : getSortedMetrics(counters).entrySet()) {
logCounter(entry.getKey(), entry.getValue().latestMetric);
}
for (Entry<String, Histogram> entry : getSortedMetrics(histograms).entrySet()) {
logHistogram(entry.getKey(), entry.getValue().latestMetric);
}
for (Entry<String, Timer> entry : getSortedMetrics(timers).entrySet()) {
logTimer(entry.getKey(), entry.getValue().latestMetric);
}
}
private void logGauge(String name, Number gauge) {
reportLogger.info("type=GAUGE, name={}, value={}", name, gauge);
}
private void logCounter(String name, CounterMetric counter) {
reportLogger.info("type=COUNTER, name={}, totalCount={}, meanRate={}, latestRate={}", name, counter.totalCount,
counter.meanRate, counter.latestRate);
}
private void logHistogram(String name, HistogramMetric histogram) {
reportLogger.info("type=HISTOGRAM, name={}, min={}, max={}, mean={}{}", name, histogram.min, histogram.max,
histogram.mean, buildPcts(histogram.pcts));
}
private void logTimer(String name, TimerMetric timer) {
reportLogger
.info("type=TIMER, name={}, totalCount={}, meanRate={}, latestRate={}, minLatency={}ms, maxLatency={}ms, meanLatency={}ms{}",
name, timer.counterMetric.totalCount, timer.counterMetric.meanRate,
timer.counterMetric.latestRate, timer.histogramMetric.min, timer.histogramMetric.max,
timer.histogramMetric.mean, buildPcts(timer.histogramMetric.pcts));
}
private String buildPcts(Map<Double, Long> pcts) {
StringBuilder builder = new StringBuilder();
for (Entry<Double, Long> entry : pcts.entrySet()) {
builder.append(", ").append(entry.getKey()).append("%<=").append(entry.getValue()).append("ms");
}
return builder.toString();
}
/**
* 返回按metrics name排序的Map.
*
*/
private <T> SortedMap<String, T> getSortedMetrics(Map<String, T> metrics) {
return new TreeMap<String, T>(metrics);
}
}
@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.utils;
/**
* 日期提供者,使用它而不是直接取得系统时间,方便测试。
*
* @author calvin
*/
public interface Clock {
static final Clock DEFAULT = new DefaultClock();
long getCurrentTime();
/**
* 默认时间提供者,返回当前的时间,线程安全。
*/
public static class DefaultClock implements Clock {
@Override
public long getCurrentTime() {
return System.currentTimeMillis();
}
}
/**
* 可配置的时间提供者,用于测试.
*/
public static class MockClock implements Clock {
private long time;
public MockClock() {
this(0);
}
public MockClock(long time) {
this.time = time;
}
@Override
public long getCurrentTime() {
return time;
}
/**
* 重新设置时间。
*/
public void update(long newTime) {
this.time = newTime;
}
/**
* 增加时间戳.
*/
public void increaseTime(int millis) {
time += millis;
}
/**
* 滚动时间.
*/
public void decreaseTime(int millis) {
time -= millis;
}
}
}
@@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import static org.assertj.core.api.Assertions.*;
import org.junit.Before;
import org.junit.Test;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.CounterMetric;
import org.springside.modules.metrics.utils.Clock.MockClock;
public class CounterTest {
private MockClock clock = new MockClock();
@Before
public void setup() {
Counter.clock = clock;
}
@Test
public void normal() {
Counter counter = new Counter();
counter.inc(10);
counter.inc(20);
counter.inc(30);
clock.increaseTime(1000);
CounterMetric metric = counter.calculateMetric();
assertThat(metric.totalCount).isEqualTo(60);
assertThat(metric.meanRate).isEqualTo(60);
assertThat(metric.latestCount).isEqualTo(60);
assertThat(metric.latestRate).isEqualTo(60);
counter.inc(20);
clock.increaseTime(1000);
metric = counter.calculateMetric();
assertThat(metric.totalCount).isEqualTo(80);
assertThat(metric.meanRate).isEqualTo(40);
assertThat(metric.latestCount).isEqualTo(20);
assertThat(metric.latestRate).isEqualTo(20);
}
@Test
public void incAndDec() {
Counter counter = new Counter();
counter.inc(20);
counter.inc();
counter.inc();
counter.dec(10);
counter.dec();
clock.increaseTime(1000);
CounterMetric metric = counter.calculateMetric();
assertThat(metric.totalCount).isEqualTo(11);
}
@Test
public void empty() {
Counter counter = new Counter();
clock.increaseTime(1000);
CounterMetric metric = counter.calculateMetric();
assertThat(metric.totalCount).isEqualTo(0);
assertThat(metric.latestRate).isEqualTo(0);
}
}
@@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.HistogramMetric;
public class HistogramTest {
@Test
public void normal() {
Histogram histogram = new Histogram(90d, 95d);
for (int i = 1; i <= 100; i++) {
histogram.update(i);
}
HistogramMetric metric = histogram.calculateMetric();
assertThat(metric.min).isEqualTo(1);
assertThat(metric.max).isEqualTo(100);
assertThat(metric.mean).isEqualTo(50.5);
assertThat(metric.pcts.get(90d)).isEqualTo(90);
assertThat(metric.pcts.get(95d)).isEqualTo(95);
for (int i = 1; i <= 100; i++) {
histogram.update(i * 2);
}
metric = histogram.calculateMetric();
assertThat(metric.min).isEqualTo(2);
assertThat(metric.max).isEqualTo(200);
assertThat(metric.mean).isEqualTo(101);
assertThat(metric.pcts.get(90d)).isEqualTo(180);
assertThat(metric.pcts.get(95d)).isEqualTo(190);
}
@Test
public void fewData() {
Histogram histogram = new Histogram(90d, 95d);
histogram.update(1);
HistogramMetric metric = histogram.calculateMetric();
assertThat(metric.pcts.get(90d)).isEqualTo(1);
assertThat(metric.pcts.get(95d)).isEqualTo(1);
for (int i = 1; i <= 3; i++) {
histogram.update(i);
}
metric = histogram.calculateMetric();
assertThat(metric.min).isEqualTo(1);
assertThat(metric.max).isEqualTo(3);
assertThat(metric.mean).isEqualTo(2);
assertThat(metric.pcts.get(90d)).isEqualTo(3);
assertThat(metric.pcts.get(95d)).isEqualTo(3);
}
@Test
public void emptyMesures() {
Histogram histogram = new Histogram(90d, 95d);
HistogramMetric metric = histogram.calculateMetric();
assertThat(metric.min).isZero();
assertThat(metric.max).isZero();
assertThat(metric.mean).isZero();
assertThat(metric.pcts.get(90d)).isZero();
}
@Test()
public void emptyPcts() {
Histogram histogram = new Histogram();
for (int i = 1; i <= 3; i++) {
histogram.update(i);
}
HistogramMetric metric = histogram.calculateMetric();
assertThat(metric.max).isEqualTo(3);
assertThat(metric.pcts).isEmpty();
assertThat(metric.pcts.get(90d)).isNull();
}
}
@@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import static org.assertj.core.api.Assertions.*;
import java.util.Map;
import org.junit.Test;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.HistogramMetric;
import org.springside.modules.metrics.metric.Timer;
public class MetricRegistryTest {
@Test
public void counter() {
MetricRegistry metricRegistry = new MetricRegistry();
Counter counter = metricRegistry.counter(MetricRegistry.name("UserService", "getUser.counter"));
assertThat(counter).isNotNull();
Map<String, Counter> counters = metricRegistry.getCounters();
Counter counter2 = counters.get("UserService.getUser.counter");
assertThat(counter2).isNotNull().isSameAs(counter);
Counter counter3 = metricRegistry.counter(MetricRegistry.name("UserService", "getUser.counter"));
assertThat(counter3).isNotNull().isSameAs(counter);
}
@Test
public void histogram() {
MetricRegistry metricRegistry = new MetricRegistry();
Histogram histogram = metricRegistry.histogram(MetricRegistry.name("UserService", "getUser.latency"));
assertThat(histogram).isNotNull();
Map<String, Histogram> histograms = metricRegistry.getHistograms();
Histogram histogram2 = histograms.get("UserService.getUser.latency");
assertThat(histogram2).isNotNull().isSameAs(histogram);
Histogram histogram3 = metricRegistry.histogram(MetricRegistry.name("UserService", "getUser.latency"));
assertThat(histogram3).isNotNull().isSameAs(histogram);
}
@Test
public void execution() {
MetricRegistry metricRegistry = new MetricRegistry();
Timer execution = metricRegistry.timer(MetricRegistry.name("UserService", "getUser.execution"));
assertThat(execution).isNotNull();
Map<String, Timer> executions = metricRegistry.getTimers();
Timer execution2 = executions.get("UserService.getUser.execution");
assertThat(execution2).isNotNull().isSameAs(execution);
Timer execution3 = metricRegistry.timer(MetricRegistry.name("UserService", "getUser.execution"));
assertThat(execution3).isNotNull().isSameAs(execution);
}
@Test
public void defaultPcts() {
MetricRegistry metricRegistry = new MetricRegistry();
// set pcts 60,70
Histogram histogram = metricRegistry.histogram(MetricRegistry.name("UserService", "getUser.histogram.setPcts"),
60d, 70d);
for (int i = 1; i <= 100; i++) {
histogram.update(i);
}
HistogramMetric metric = histogram.calculateMetric();
assertThat(metric.pcts.get(60d)).isEqualTo(60);
assertThat(metric.pcts.get(70d)).isEqualTo(70);
// new default 50
metricRegistry.setDefaultPcts(new Double[] { 50d });
Histogram histogramWithNewDefaultPcts = metricRegistry.histogram(MetricRegistry.name("UserService",
"getUser.histogram.newDefault"));
for (int i = 1; i <= 100; i++) {
histogramWithNewDefaultPcts.update(i);
}
metric = histogramWithNewDefaultPcts.calculateMetric();
assertThat(metric.pcts.get(50d)).isEqualTo(50);
assertThat(metric.pcts.get(90d)).isNull();
}
}
@@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import org.junit.Ignore;
import org.junit.Test;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.Timer;
import org.springside.modules.metrics.metric.Timer.TimerContext;
import org.springside.modules.metrics.reporter.ConsoleReporter;
import org.springside.modules.metrics.reporter.GraphiteReporter;
import org.springside.modules.metrics.reporter.Slf4jReporter;
import org.springside.modules.metrics.utils.Clock.MockClock;
public class ReporterTest {
@Test
public void consoleReporter() {
runReport(new ConsoleReporter());
}
@Test
public void slf4jReporter() {
runReport(new Slf4jReporter());
}
@Test
@Ignore("manual test")
public void graphiteReporter() {
runReport(new GraphiteReporter(new InetSocketAddress("localhost", 2003)));
}
@Test
public void schedulerStartStop() throws InterruptedException {
ReportScheduler scheduler = new ReportScheduler(new MetricRegistry(), new ConsoleReporter());
scheduler.start(1, TimeUnit.SECONDS);
Thread.sleep(2000);
scheduler.stop();
}
private void runReport(Reporter reporter) {
MetricRegistry metricRegistry = new MetricRegistry();
MockClock clock = new MockClock();
Counter.clock = clock;
Timer.clock = clock;
// counter
Counter counter = metricRegistry.counter(MetricRegistry.name("UserService", "getUser.counter"));
counter.inc(4);
Counter counter2 = metricRegistry.counter(MetricRegistry.name("UserService", "setUser.counter"));
counter2.inc(6);
clock.increaseTime(1000);
// histogram
Histogram histogram = metricRegistry.histogram(MetricRegistry.name("UserService", "getUser.latency"));
for (int i = 1; i <= 100; i++) {
histogram.update(i);
}
Histogram histogram2 = metricRegistry.histogram(MetricRegistry.name("UserService", "setUser.latency"));
for (int i = 1; i <= 100; i++) {
histogram2.update(i * 2);
}
// timer
Timer timer = metricRegistry.timer(MetricRegistry.name("UserService", "getUser.timer"));
for (int i = 1; i <= 10; i++) {
TimerContext timerContext = timer.start();
clock.increaseTime(25);
timerContext.stop();
}
Timer timer2 = metricRegistry.timer(MetricRegistry.name("UserService", "setUser.timer"));
for (int i = 1; i <= 10; i++) {
TimerContext timerContext = timer2.start();
clock.increaseTime(75);
timerContext.stop();
}
// totally 2 seconds past
ReportScheduler scheduler = new ReportScheduler(metricRegistry, reporter);
scheduler.report();
}
}
@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.Timer;
import org.springside.modules.metrics.metric.TimerMetric;
import org.springside.modules.metrics.metric.Timer.TimerContext;
import org.springside.modules.metrics.utils.Clock.MockClock;
public class TimerTest {
@Test
public void normal() {
MockClock clock = new MockClock();
Timer.clock = clock;
Counter.clock = clock;
Timer timer = new Timer(new Double[] { 90d });
TimerContext timerContext = timer.start();
clock.increaseTime(200);
timerContext.stop();
TimerContext timer2 = timer.start();
clock.increaseTime(300);
timer2.stop();
TimerMetric metric = timer.calculateMetric();
assertThat(metric.counterMetric.totalCount).isEqualTo(2);
assertThat(metric.counterMetric.meanRate).isEqualTo(4);
assertThat(metric.counterMetric.latestCount).isEqualTo(2);
assertThat(metric.counterMetric.latestRate).isEqualTo(4);
assertThat(metric.histogramMetric.min).isEqualTo(200);
assertThat(metric.histogramMetric.mean).isEqualTo(250);
assertThat(metric.histogramMetric.pcts.get(90d)).isEqualTo(300);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springside.modules" level="INFO"/>
<logger name="metrics" level="INFO"/>
<root level="WARN">
<appender-ref ref="console"/>
</root>
</configuration>
+3 -2
View File
@@ -8,9 +8,9 @@
<name>Springside :: Module</name>
<packaging>pom</packaging>
<properties>
<spring.version>4.1.8.RELEASE</spring.version>
<spring.version>4.1.8.RELEASE</spring.version>
<springside.version>5.0.0-SNAPSHOT</springside.version>
<guava.version>18.0</guava.version>
<guava.version>18.0</guava.version>
<commons-lang3.version>3.4</commons-lang3.version>
<commons-codec.version>1.10</commons-codec.version>
<commons-io.version>2.4</commons-io.version>
@@ -34,5 +34,6 @@
<modules>
<module>utils</module>
<module>core</module>
<module>metrics</module>
</modules>
</project>