/*******************************************************************************
* 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.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, new ArrayList<Reporter>());
for (Reporter reporter : reporters) {
this.addReporter(reporter);
}
}
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;
}
}
}