blob: e6e09ddec3be6e1f83ff536aeaa47b500625034f [file] [log] [blame]
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.monitor.simple;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ConfigUtils;
import com.alibaba.dubbo.common.utils.NamedThreadFactory;
import com.alibaba.dubbo.common.utils.NetUtils;
import com.alibaba.dubbo.monitor.MonitorService;
/**
* SimpleMonitorService
*
* @author william.liangf
*/
public class SimpleMonitorService implements MonitorService {
private static final Logger logger = LoggerFactory.getLogger(SimpleMonitorService.class);
private static final String[] types = {SUCCESS, FAILURE, ELAPSED, CONCURRENT, MAX_ELAPSED, MAX_CONCURRENT};
private static final String POISON_PROTOCOL = "poison";
// 定时任务执行器
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboMonitorTimer", true));
// 图表绘制定时器
private final ScheduledFuture<?> chartFuture;
private final Thread writeThread;
private final BlockingQueue<URL> queue;
private String statisticsDirectory = "statistics";
private String chartsDirectory = "charts";
private volatile boolean running = true;
private static SimpleMonitorService INSTANCE = null;
public static SimpleMonitorService getInstance() {
return INSTANCE;
}
public String getStatisticsDirectory() {
return statisticsDirectory;
}
public void setStatisticsDirectory(String statistics) {
if (statistics != null) {
this.statisticsDirectory = statistics;
}
}
public String getChartsDirectory() {
return chartsDirectory;
}
public void setChartsDirectory(String charts) {
if (charts != null) {
this.chartsDirectory = charts;
}
}
public SimpleMonitorService() {
queue = new LinkedBlockingQueue<URL>(Integer.parseInt(ConfigUtils.getProperty("dubbo.monitor.queue", "100000")));
writeThread = new Thread(new Runnable() {
public void run() {
while (running) {
try {
write(); // 记录统计日志
} catch (Throwable t) { // 防御性容错
logger.error("Unexpected error occur at write stat log, cause: " + t.getMessage(), t);
try {
Thread.sleep(5000); // 失败延迟
} catch (Throwable t2) {
}
}
}
}
});
writeThread.setDaemon(true);
writeThread.setName("DubboMonitorAsyncWriteLogThread");
writeThread.start();
chartFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
draw(); // 绘制图表
} catch (Throwable t) { // 防御性容错
logger.error("Unexpected error occur at draw stat chart, cause: " + t.getMessage(), t);
}
}
}, 1, 300, TimeUnit.SECONDS);
INSTANCE = this;
}
public void close() {
try {
running = false;
queue.offer(new URL(POISON_PROTOCOL, NetUtils.LOCALHOST, 0));
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
chartFuture.cancel(true);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
private void write() throws Exception {
URL statistics = queue.take();
if (POISON_PROTOCOL.equals(statistics.getProtocol())) {
return;
}
String timestamp = statistics.getParameter(Constants.TIMESTAMP_KEY);
Date now;
if (timestamp == null || timestamp.length() == 0) {
now = new Date();
} else if (timestamp.length() == "yyyyMMddHHmmss".length()) {
now = new SimpleDateFormat("yyyyMMddHHmmss").parse(timestamp);
} else {
now = new Date(Long.parseLong(timestamp));
}
String day = new SimpleDateFormat("yyyyMMdd").format(now);
SimpleDateFormat format = new SimpleDateFormat("HHmm");
for (String key : types) {
try {
String type;
String consumer;
String provider;
if (statistics.hasParameter(PROVIDER)) {
type = PROVIDER;
consumer = statistics.getHost();
provider = statistics.getParameter(PROVIDER);
int i = provider.indexOf(':');
if (i > 0) {
provider = provider.substring(0, i);
}
} else {
type = CONSUMER;
consumer = statistics.getParameter(CONSUMER);
int i = consumer.indexOf(':');
if (i > 0) {
consumer = consumer.substring(0, i);
}
provider = statistics.getHost();
}
String filename = statisticsDirectory
+ "/" + day
+ "/" + statistics.getServiceInterface()
+ "/" + statistics.getParameter(METHOD)
+ "/" + consumer
+ "/" + provider
+ "/" + type + "." + key;
File file = new File(filename);
File dir = file.getParentFile();
if (dir != null && ! dir.exists()) {
dir.mkdirs();
}
FileWriter writer = new FileWriter(file, true);
try {
writer.write(format.format(now) + " " + statistics.getParameter(key, 0) + "\n");
writer.flush();
} finally {
writer.close();
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}
private void draw() {
File rootDir = new File(statisticsDirectory);
if (! rootDir.exists()) {
return;
}
File[] dateDirs = rootDir.listFiles();
for (File dateDir : dateDirs) {
File[] serviceDirs = dateDir.listFiles();
for (File serviceDir : serviceDirs) {
File[] methodDirs = serviceDir.listFiles();
for (File methodDir : methodDirs) {
String methodUri = chartsDirectory + "/" + dateDir.getName() + "/" + serviceDir.getName() + "/" + methodDir.getName();
File successFile = new File(methodUri + "/" + SUCCESS + ".png");
long successModified = successFile.lastModified();
boolean successChanged = false;
Map<String, long[]> successData = new HashMap<String, long[]>();
double[] successSummary = new double[4];
File elapsedFile = new File(methodUri + "/" + ELAPSED + ".png");
long elapsedModified = elapsedFile.lastModified();
boolean elapsedChanged = false;
Map<String, long[]> elapsedData = new HashMap<String, long[]>();
double[] elapsedSummary = new double[4];
long elapsedMax = 0;
File[] consumerDirs = methodDir.listFiles();
for (File consumerDir : consumerDirs) {
File[] providerDirs = consumerDir.listFiles();
for (File providerDir : providerDirs) {
File consumerSuccessFile = new File(providerDir, CONSUMER + "." + SUCCESS);
File providerSuccessFile = new File(providerDir, PROVIDER + "." + SUCCESS);
appendData(new File[] {consumerSuccessFile, providerSuccessFile}, successData, successSummary);
if (consumerSuccessFile.lastModified() > successModified
|| providerSuccessFile.lastModified() > successModified) {
successChanged = true;
}
File consumerElapsedFile = new File(providerDir, CONSUMER + "." + ELAPSED);
File providerElapsedFile = new File(providerDir, PROVIDER + "." + ELAPSED);
appendData(new File[] {consumerElapsedFile, providerElapsedFile}, elapsedData, elapsedSummary);
elapsedMax = Math.max(elapsedMax, CountUtils.max(new File(providerDir, CONSUMER + "." + MAX_ELAPSED)));
elapsedMax = Math.max(elapsedMax, CountUtils.max(new File(providerDir, PROVIDER + "." + MAX_ELAPSED)));
if (consumerElapsedFile.lastModified() > elapsedModified
|| providerElapsedFile.lastModified() > elapsedModified) {
elapsedChanged = true;
}
}
}
if (elapsedChanged) {
divData(elapsedData, successData);
elapsedSummary[0] = elapsedMax;
elapsedSummary[1] = -1;
elapsedSummary[2] = successSummary[3] == 0 ? 0 : elapsedSummary[3] / successSummary[3];
elapsedSummary[3] = -1;
createChart("ms/t", serviceDir.getName(), methodDir.getName(), dateDir.getName(), new String[] {CONSUMER, PROVIDER}, elapsedData, elapsedSummary, elapsedFile.getAbsolutePath());
}
if (successChanged) {
divData(successData, 60);
successSummary[0] = successSummary[0] / 60;
successSummary[1] = successSummary[1] / 60;
successSummary[2] = successSummary[2] / 60;
createChart("t/s", serviceDir.getName(), methodDir.getName(), dateDir.getName(), new String[] {CONSUMER, PROVIDER}, successData, successSummary, successFile.getAbsolutePath());
}
}
}
}
}
private void divData(Map<String, long[]> successMap, long unit) {
for (long[] success : successMap.values()) {
for (int i = 0; i < success.length; i ++) {
success[i] = success[i] / unit;
}
}
}
private void divData(Map<String, long[]> elapsedMap, Map<String, long[]> successMap) {
for (Map.Entry<String, long[]> entry : elapsedMap.entrySet()) {
long[] elapsed = entry.getValue();
long[] success = successMap.get(entry.getKey());
for (int i = 0; i < elapsed.length; i ++) {
elapsed[i] = success[i] == 0 ? 0 : elapsed[i] / success[i];
}
}
}
private void appendData(File[] files, Map<String, long[]> data, double[] summary) {
for (int i = 0; i < files.length; i ++) {
File file = files[i];
if (! file.exists()) {
continue;
}
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
try {
int sum = 0;
int cnt = 0;
String line;
while ((line = reader.readLine()) != null) {
int index = line.indexOf(" ");
if (index > 0) {
String key = line.substring(0, index).trim();
long value = Long.parseLong(line.substring(index + 1).trim());
long[] values = data.get(key);
if (values == null) {
values = new long[files.length];
data.put(key, values);
}
values[i] += value;
summary[0] = Math.max(summary[0], values[i]);
summary[1] = summary[1] == 0 ? values[i] : Math.min(summary[1], values[i]);
sum += value;
cnt ++;
}
}
if (i == 0) {
summary[3] += sum;
summary[2] = (summary[2] + (sum / cnt)) / 2;
}
} finally {
reader.close();
}
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
private static void createChart(String key, String service, String method, String date, String[] types, Map<String, long[]> data, double[] summary, String path) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmm");
DecimalFormat numberFormat = new DecimalFormat("###,##0.##");
TimeSeriesCollection xydataset = new TimeSeriesCollection();
for (int i = 0; i < types.length; i ++) {
String type = types[i];
TimeSeries timeseries = new TimeSeries(type);
for (Map.Entry<String, long[]> entry : data.entrySet()) {
try {
timeseries.add(new Minute(dateFormat.parse(date + entry.getKey())), entry.getValue()[i]);
} catch (ParseException e) {
logger.error(e.getMessage(), e);
}
}
xydataset.addSeries(timeseries);
}
JFreeChart jfreechart = ChartFactory.createTimeSeriesChart(
"max: " + numberFormat.format(summary[0]) + (summary[1] >=0 ? " min: " + numberFormat.format(summary[1]) : "")
+ " avg: " + numberFormat.format(summary[2]) + (summary[3] >=0 ? " sum: " + numberFormat.format(summary[3]) : ""),
toDisplayService(service) + " " + method + " " + toDisplayDate(date), key, xydataset, true, true, false);
jfreechart.setBackgroundPaint(Color.WHITE);
XYPlot xyplot = (XYPlot) jfreechart.getPlot();
xyplot.setBackgroundPaint(Color.WHITE);
xyplot.setDomainGridlinePaint(Color.GRAY);
xyplot.setRangeGridlinePaint(Color.GRAY);
xyplot.setDomainGridlinesVisible(true);
xyplot.setRangeGridlinesVisible(true);
DateAxis dateaxis = (DateAxis) xyplot.getDomainAxis();
dateaxis.setDateFormatOverride(new SimpleDateFormat("HH:mm"));
BufferedImage image = jfreechart.createBufferedImage(600, 300);
try {
if (logger.isInfoEnabled()) {
logger.info("write chart: " + path);
}
File methodChartFile = new File(path);
File methodChartDir = methodChartFile.getParentFile();
if (methodChartDir != null && ! methodChartDir.exists()) {
methodChartDir.mkdirs();
}
FileOutputStream output = new FileOutputStream(methodChartFile);
try {
ImageIO.write(image, "png", output);
output.flush();
} finally {
output.close();
}
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
private static String toDisplayService(String service) {
int i = service.lastIndexOf('.');
if (i >= 0) {
return service.substring(i + 1);
}
return service;
}
private static String toDisplayDate(String date) {
try {
return new SimpleDateFormat("yyyy-MM-dd").format(new SimpleDateFormat("yyyyMMdd").parse(date));
} catch (ParseException e) {
return date;
}
}
public void count(URL statistics) {
queue.offer(statistics);
if (logger.isInfoEnabled()) {
logger.info("collect statistics: " + statistics);
}
}
}