#524 复活Metrics模块
This commit is contained in:
+2
-7
@@ -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>
|
||||
|
||||
|
||||
Executable
+74
@@ -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 {
|
||||
}
|
||||
+194
@@ -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);
|
||||
}
|
||||
}
|
||||
+32
@@ -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);
|
||||
}
|
||||
+155
@@ -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);
|
||||
}
|
||||
+362
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
+22
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
+26
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
+118
@@ -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) + "]";
|
||||
}
|
||||
}
|
||||
+24
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
+92
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
Executable
+126
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+219
@@ -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;
|
||||
}
|
||||
}
|
||||
+105
@@ -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);
|
||||
}
|
||||
}
|
||||
+71
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -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);
|
||||
}
|
||||
}
|
||||
+90
@@ -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();
|
||||
}
|
||||
}
|
||||
+98
@@ -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();
|
||||
}
|
||||
}
|
||||
+89
@@ -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();
|
||||
}
|
||||
}
|
||||
+45
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user