eleme.openapi.sdk.media.trace.Reporter Maven / Gradle / Ivy
package eleme.openapi.sdk.media.trace;
import eleme.openapi.sdk.media.MediaConfiguration;
import eleme.openapi.sdk.media.__Version__;
import eleme.openapi.sdk.media.utils.IpUtils;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static eleme.openapi.sdk.utils.StringUtils.isEmpty;
/**
* 不保证不丢数据,尽可能上报
* Created by huamulou on 15/12/9.
*/
public class Reporter {
/**
* 默认会对上传结果汇报,用于上传性能跟踪和优化
*/
private volatile boolean close = false; //是否暂时关闭跟踪, 可以恢复
private volatile boolean shutdown = false; //是否关闭整个类, 无法恢复
private Timer timer; //定时任务, 定时触发汇报线程
private Thread worker; //汇报线程
private volatile long lastLaunchTime; //只有单线程修改, 上次汇报线程启动的时间
private final byte[] lock; //汇报线程锁
private Queue queue; //需要汇报的队列
private ReportClient reportService; //汇报的http服务
private DeviceId deviceId; //机器id, 用于uploadId
private String reportStartTime; //本类开始的时间, 用于uploadId的一部分
private AtomicLong reportSequence; //上传序号, 用于uploadId
private boolean inited = false; //reporter是否已经初始化
private boolean needDebugLog = false; //是否开启debuglog
private CountDownLatch workerExitCountDownLatch; //用于graceful地退出
private static final String DEFAULT_DEVICE_ID_FILE_NAME = "ams_device"; //机器id存储的文件名
private Lock queueLock = new ReentrantLock();
private volatile int queueSize = 0;
private static Reporter instance = new Reporter();
public static Reporter sharedInstance() {
if (!instance.inited) {
synchronized (instance) {
if (!instance.inited) {
instance.init();
}
}
}
return instance;
}
private String getLocalPath() {
String path;
URL base = getClass().getClassLoader().getResource("");
if (base == null) {
base = getClass().getClassLoader().getResource("/");
}
if (base == null) {
path = System.getProperty("user.dir");
} else {
path = base.getPath();
}
path = path.endsWith(File.separator) ? path : path + File.separator;
D("device id path is " + path);
return path;
}
private Reporter() {
this.deviceId = new DeviceId(getLocalPath() + DEFAULT_DEVICE_ID_FILE_NAME);
this.timer = new Timer("ams-report-timer");
this.lock = new byte[1];
Random random = new Random();
this.queue = new ConcurrentLinkedQueue();
this.lastLaunchTime = 0;
this.reportStartTime = Numbers.toString(Long.parseLong(System.currentTimeMillis() / 1000 + "" + random.nextInt(100000)), Numbers.MAX_RADIX);
this.reportSequence = new AtomicLong(0);
this.workerExitCountDownLatch = new CountDownLatch(1);
this.reportService = new ReportClient();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
shutdown();
}
}));
}
public void init() {
this.timer.schedule(new TimerTask() {
@Override
public void run() {
if (!shutdown) { //如果close, 照样触发. close的时候只要不接消息就好了.
D("try trigger notify");
if (lastLaunchTime == 0) {
D("try trigger notify success");
notifyAllWaiters();
} else {
if (System.currentTimeMillis() - lastLaunchTime > 15 * 1000) {
D("try trigger notify success");
notifyAllWaiters();
}
}
}
}
}, 10 * 1000, 5 * 1000);
this.createAndStartDispatchThread();
this.inited = true;
}
public void shutdown() {
D("try shutdown gracefully");
this.shutdown = true;
try {
this.notifyAllWaiters();
this.workerExitCountDownLatch.await(10, TimeUnit.SECONDS);
D("shutdown gracefully success");
} catch (InterruptedException e) {
//
D("interrupt at shutdown", e);
}
this.timer.cancel();
D("timer cancel success");
}
private static class WorkerThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
final Reporter reporter;
public WorkerThreadExceptionHandler(final Reporter reporter) {
this.reporter = reporter;
}
@Override
public void uncaughtException(final Thread t, final Throwable e) {
if (!this.reporter.isShutdown())
this.reporter.createAndStartDispatchThread();
}
}
private void createAndStartDispatchThread() {
this.worker = createDispatchThread();
this.worker.setName("ams-report-worker");
this.worker.setUncaughtExceptionHandler(new WorkerThreadExceptionHandler(this));
this.worker.start();
}
protected Thread createDispatchThread() {
return new Thread(new Runnable() {
@Override
public void run() {
while (true) {
D("entering running loop.");
if (shutdown && queue.isEmpty()) {
D("break by shutdown or queue size empty.");//没有同步, 可能丢数据
break;
}
try {
waitOnLock();
if (System.currentTimeMillis() - lastLaunchTime < 5 * 1000 && !shutdown) {
continue;
}
lastLaunchTime = System.currentTimeMillis();
while (true && !queue.isEmpty()) {
D("send report loop.");
int count = 0;
List reportList = new ArrayList();
while (!queue.isEmpty() && count < 100) { //每次发送不超过100的报告
count++;
String report = getReport();
if (report == null) {
break;
}
reportList.add(report);
}
try {
if (!reportList.isEmpty()) {
D("send {0} reports.", count);
reportService.report(reportList);
}
} catch (Exception e) {
D("exception happened when send report. message: " + e.getMessage(), e);
if (shutdown) {
D("break by shutdown and exception");
break;
} else {
for (String report : reportList) {
addReport(report);
}
D("sleep 1 minute before retry");
Thread.sleep(1 * 60 * 1000); //线程在睡觉的时候可能导致提交的数据丢失 TODO
}
}
}
} catch (InterruptedException e) {
//如果interrupt,忽略
D("running loop interrupted", e);
}
}
workerExitCountDownLatch.countDown();
}
});
}
public void setTraceOn(boolean traceOn) {
D("set trace on: " + traceOn);
close = !traceOn;
}
public enum ArgsFactor {
CODE("code"), MESSAGE("message"), UPLOAD_SIZE("uploadSize"), TOTAL_TIME("totalTime"),
DNS("dns"), OPEN_CONN("openConn"), FIRST_PACKET_ARRIVE("firstPacketArrive");
String desc;
ArgsFactor(String desc) {
this.desc = desc;
}
}
public enum ReportFactor {
VERSION,
OS,
OS_VERSION,
SDK_TYPE,
SDK_VERSION,
SESSION_ID,
RECORD_DATE,
EVENT_ID,
LOCAL_IP,
REQUEST_ID,
APPKEY,
ARGS
}
public enum OP {
UPLOAD("upload"), BLOCK_INIT("blockInit"), BLOCK_UPLOAD("blockUpload"), BLOCK_COMPLETE("blockComplete"), BLOCK_CANCEL("blockCancel"),
DNS("dns"), OPEN_CONN("openConn"), FIRST_PACKET_ARRIVE("firstPacketArrive");
public String desc;
OP(String desc) {
this.desc = desc;
}
}
/**
* 每次记录都要先start,这样同时对线程的脏数据进行清理
*/
public void start() {
if (close || shutdown) return;
Profiler.clear();
Profiler.setStarted(true);
}
public void addKV(String key, String value) {
if (isEmpty(key) || isEmpty(value)) {
return;
} else {
Profiler.getKVMap().put(key, value);
}
}
/**
* 对Profiler方法的封装 start
* @param op op
*/
public void enter(OP op) {
if (close || shutdown) return;
if (!Profiler.getStarted()) return;
Profiler.Factor factor = Profiler.getParameter(op.name());
factor = factor == null ? new Profiler.Factor(-1) : factor;
factor.setStart(System.currentTimeMillis());
Profiler.setParameter(op.name(), factor);
D("start trace of op {0}", op.name());
}
public void release(OP op) {
if (close || shutdown) return;
if (!Profiler.getStarted()) return;
Profiler.Factor factor = Profiler.getParameter(op.name());
if (factor != null)
factor.setEnd(System.currentTimeMillis());
}
private String getAk() {
if (MediaConfiguration.getLastConfiguration() == null) {
return null;
}
return MediaConfiguration.getLastConfiguration().getAk();
}
public void finish(String requestId, String uploadId, String message, String code, long uploadSize) {
if (close || shutdown) return;
D("finish trace");
ReportStore reportNew = new ReportStore();
reportNew.addFactor(ReportFactor.SDK_VERSION, __Version__.SDK_VERSION);
reportNew.addFactor(ReportFactor.SDK_TYPE, __Version__.SDK_TYPE);
reportNew.addFactor(ReportFactor.OS, System.getProperty("os.name"));
reportNew.addFactor(ReportFactor.OS_VERSION, System.getProperty("os.version"));
reportNew.addFactor(ReportFactor.APPKEY, getAk());
reportNew.addFactor(ReportFactor.RECORD_DATE, System.currentTimeMillis());
reportNew.addFactor(ReportFactor.LOCAL_IP, IpUtils.getIpAddress());
reportNew.addFactor(ReportFactor.REQUEST_ID, requestId);
reportNew.addFactor(ReportFactor.SESSION_ID, uploadId);
reportNew.addFactor(ReportFactor.VERSION, "1.0");
reportNew.addArgs(ArgsFactor.CODE, code);
reportNew.addArgs(ArgsFactor.MESSAGE, message);
reportNew.addArgs(ArgsFactor.UPLOAD_SIZE, uploadSize);
if (Profiler.getMap() != null) {
for (Map.Entry e : Profiler.getMap().entrySet()) {
if (e.getKey() == null || e.getValue() == null) continue;
OP op = OP.valueOf(e.getKey());
switch (op) {
case UPLOAD:
reportNew.addArgs(ArgsFactor.TOTAL_TIME, e.getValue().getInterval());
reportNew.addFactor(ReportFactor.EVENT_ID, op.desc);
break;
case BLOCK_INIT:
reportNew.addArgs(ArgsFactor.TOTAL_TIME, e.getValue().getInterval());
reportNew.addFactor(ReportFactor.EVENT_ID, op.desc);
break;
case BLOCK_UPLOAD:
reportNew.addArgs(ArgsFactor.TOTAL_TIME, e.getValue().getInterval());
reportNew.addFactor(ReportFactor.EVENT_ID, op.desc);
break;
case BLOCK_COMPLETE:
reportNew.addArgs(ArgsFactor.TOTAL_TIME, e.getValue().getInterval());
reportNew.addFactor(ReportFactor.EVENT_ID, op.desc);
break;
case BLOCK_CANCEL:
reportNew.addArgs(ArgsFactor.TOTAL_TIME, e.getValue().getInterval());
reportNew.addFactor(ReportFactor.EVENT_ID, op.desc);
break;
default:
reportNew.addArgs(op.desc, e.getValue().getInterval());
break;
}
}
}
if (Profiler.getKVMap() != null) {
for (Map.Entry e : Profiler.getKVMap().entrySet()) {
reportNew.addArgs(e.getKey(), e.getValue());
}
}
D("add report: {0}", reportNew.getReport());
Profiler.clear();
this.addReport(reportNew.getReport());
}
public String getSessionId() {
this.reportSequence.incrementAndGet();
return this.deviceId.getValue() + "-" + this.reportStartTime + "-" + reportSequence.get();
}
private void addReport(String report) {
if (close || shutdown) return;
if (report == null) return;
//size方法需要遍历,速度比较慢
queueLock.lock();
try {
if (queueSize > 10240) {
return; //如果内存累积过多,直接丢弃
}
boolean addResult = queue.add(report);
if (addResult)
queueSize++;
if (queueSize > 100) {
notifyAllWaiters();
}
} finally {
queueLock.unlock();
}
}
private String getReport() {
queueLock.lock();
try {
String report = queue.poll();
if (report != null) {
queueSize--;
}
return report;
} finally {
queueLock.unlock();
}
}
private void waitOnLock() throws InterruptedException {
synchronized (lock) {
lock.wait();
}
}
private void notifyAllWaiters() {
synchronized (lock) {
lock.notifyAll();
}
}
private void D(String pattern, Object... args) {
}
private void D(String arg) {
}
private void D(String arg, Throwable t) {
}
public boolean isClose() {
return close;
}
public void setClose(boolean close) {
this.close = close;
}
public boolean isShutdown() {
return shutdown;
}
public void setShutdown(boolean shutdown) {
this.shutdown = shutdown;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy