All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eleme.openapi.sdk.media.trace.Reporter Maven / Gradle / Ivy

There is a newer version: 1.30.71
Show newest version
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