package com.chinaweal.aiccs.schedule;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 首先第一次采样，获取所有线程的 CPU 时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime())
 * 然后睡眠等待一个间隔时间（默认为 200ms，可以通过-i指定间隔时间）
 * 再次第二次采样，获取所有线程的 CPU 时间，对比两次采样数据，计算出每个线程的增量 CPU 时间
 * 线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间 * 100%
 */
@Slf4j
//@Configuration
public class ThreadCpuSchedule {

    public static int topNum = 10;//默认输出前10的排行
    //获取当前应用
    private static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

    @Scheduled(cron = "${scheduling.cron.threadCpu}")
    public void runTop() {
        log.info("==================开始输出CPU前" + topNum + "线程排行，时间：" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "==================");
        List<MyThreadInfo> myThreadInfos = null;
        try {
            myThreadInfos = listRankThread(2000);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        int i = 0;
        if (myThreadInfos == null) {
            return;
        }
        for (MyThreadInfo myThreadInfo : myThreadInfos) {
            i++;
            if (i > topNum) {//指定输出多少个排行
                break;
            }
            log.info(myThreadInfo.toString());
        }
        log.info("==================结束输出线程排行，时间：" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "==================");
    }

    /**
     * 通过日期范围+CPU使用率，输出相关线程，并且按照CPU排行高低来排
     */
    public static List<ThreadInfoTrace> listTopText(float leastCpuUsage, LocalDateTime startTime, LocalDateTime endTime) throws IOException {
        List<ThreadInfoTrace> list = new ArrayList<>();

        //获取日志归档的记录
//        List<File> files = listFile(new File("D:\\desktop\\版本系统排查\\9898\\20231207\\查询"), "aicscr-threadTop", startTime, endTime);
        List<File> files = listFile(new File("D:\\desktop\\版本系统排查\\9898\\20231221\\2023-12"), "aicscr-threadTop", startTime, endTime);

        //遍历文件内容
        for (File file : files) {
            List<String> strings = FileUtils.readLines(file);
            LocalDateTime time = null;//当前CPU时间
            float cpuUsage = 0;//当前CPU时间
            String stackTrace = "";//当前栈
            for (String lineStr : strings) {
                //判断获取第一行的时间日期
                if (lineStr.startsWith("==================开")) {
                    //截取时间
                    int i = lineStr.indexOf("时间：");
                    time = LocalDateTime.parse(lineStr.substring(i + 3, i + 22), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                    continue;
                }

                //当这一行是CPU的
                if (lineStr.contains("cpuUsage=")) {
                    int s = lineStr.indexOf("cpuUsage=");
                    int e = lineStr.indexOf("%,");
                    cpuUsage = Float.parseFloat(lineStr.substring(s + 9, e));
                    if (cpuUsage >= leastCpuUsage) {
                        //CPU占比满足，重置上一个的栈字符
                        stackTrace = "";
                    } else {
                        cpuUsage = 0;
                    }
                }
                if (cpuUsage != 0) {
                    //拼接栈内容
                    stackTrace = stackTrace + "\n" + lineStr;
                }

                //最后一行了，栈内容
                if (time != null && cpuUsage != 0 && StringUtils.isBlank(lineStr)) {
                    list.add(new ThreadInfoTrace(time, cpuUsage, stackTrace));
                }
            }
        }

        list.sort((l1, l2) -> {
            if ((l2.getCpuUsage() - l1.getCpuUsage()) > 0) {
                return 1;
            } else if (l2.getCpuUsage() == l1.getCpuUsage()) {
                return 0;
            } else {
                return -1;
            }
        });
        for (int i = 0; i < list.size(); i++) {
            ThreadInfoTrace threadInfoTrace = list.get(i);
            threadInfoTrace.setCpuRank(i + 1);
        }
        return list;
    }

    public static void main(String[] args) throws IOException {
        LocalDateTime start = LocalDateTime.parse("2023-11-28 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        LocalDateTime end = LocalDateTime.parse("2023-11-29 23:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        List<ThreadInfoTrace> threadInfoTraces = listTopText(50f, start, end);
        for (ThreadInfoTrace threadInfoTrace : threadInfoTraces) {

        }

    }

    private static List<File> listFile(File dir, String containName, LocalDateTime startTime, LocalDateTime endTime) {
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                list.addAll(listFile(file, containName, startTime, endTime));
            } else if (file.getName().contains(containName)) {
                //判断是否是否符合
                long l = file.lastModified();
                LocalDateTime fileTime = Instant.ofEpochMilli(l).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
                if (startTime.isBefore(fileTime) && endTime.isAfter(fileTime)) {
                    list.add(file);
                }

            }
        }
        return list;
    }


    public static List<MyThreadInfo> listTop(Integer topNum) {
        log.info("==================主动触发开始输出CPU前" + topNum + "线程排行，时间：" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "==================");
        List<MyThreadInfo> myThreadInfos = null;
        try {
            myThreadInfos = listRankThread(2000);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        int i = 0;

        for (MyThreadInfo myThreadInfo : myThreadInfos) {
            i++;
            if (i > topNum) {//指定输出多少个排行
                break;
            }
            log.info(myThreadInfo.toString());
        }
        log.info("==================主动触发结束输出线程排行，时间：" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "==================");
        return myThreadInfos.subList(0, topNum);
    }

    /**
     * 按CPU使用率排名
     * @param interval 两个线程等待时间间隔，单位毫秒
     */
    public static List<MyThreadInfo> listRankThread(long interval) throws InterruptedException {
        Map<Long, MyThreadInfo> firsts = getCurrentThreads(true, true);//第一次线程情况

        Thread.sleep(interval);

        Map<Long, MyThreadInfo> seconds = getCurrentThreads(true, true);//第二次线程情况
        List<MyThreadInfo> list = new ArrayList();
        //合并成交集，计算增量时间
        for (Map.Entry<Long, MyThreadInfo> entry : seconds.entrySet()) {
            Long threadId = entry.getKey();
            MyThreadInfo start = firsts.get(threadId);
            MyThreadInfo end = entry.getValue();
            if (start == null) {
                //第一次执行时，还没有生成的线程则直接取消
                continue;
            }
            long deltaTime = end.getCpuTotalTime() - start.getCpuTotalTime();//线程增量 CPU 时间 ns
            float cpuRate = (float) deltaTime / interval * 100; //线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间（毫秒单位） * 100%


            //优先输出RUNNABLE的线程栈
            if (!"RUNNABLE".equals(end.getThreadState()) && "RUNNABLE".equals(start.getThreadState())) {
                start.setDeltaTime(deltaTime);//单位毫秒
                start.setCpuRate(cpuRate);
                list.add(start);
            } else {
                end.setDeltaTime(deltaTime);//单位毫秒
                end.setCpuRate(cpuRate);
                list.add(end);
            }

        }
        //计算cpu排名
        list.sort((l1, l2) -> {
            if ((l2.getCpuRate() - l1.getCpuRate()) > 0) {
                return 1;
            } else if (l2.getCpuRate() == l1.getCpuRate()) {
                return 0;
            } else {
                return -1;
            }
        });
        for (int i = 0; i < list.size(); i++) {
            MyThreadInfo myThreadInfo = list.get(i);
            myThreadInfo.setCpuRank(i + 1);
        }
        return list;
    }

    public static Map<Long, MyThreadInfo> getCurrentThreads(boolean lockedMonitors, boolean lockedSynchronizers) {
        Map<Long, MyThreadInfo> myThreadInfos = new HashMap<>();

        long[] threadIds = threadMXBean.getAllThreadIds();
        ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds, lockedMonitors, lockedSynchronizers);

        for (ThreadInfo threadInfo : threadInfos) {

            MyThreadInfo myThreadInfo = new MyThreadInfo();
            myThreadInfo.setThreadId(threadInfo.getThreadId());
            myThreadInfo.setThreadName(threadInfo.getThreadName());
            myThreadInfo.setThreadState(threadInfo.getThreadState().name());
            myThreadInfo.setCpuTotalTime(threadMXBean.getThreadCpuTime(threadInfo.getThreadId()) / 1000000);//线程的总共 CPU 时间，单位毫秒
            String stackTrace = "";
            for (StackTraceElement element : threadInfo.getStackTrace()) {
                stackTrace += element + "\n";
            }
            myThreadInfo.setStackTrace(stackTrace);

            myThreadInfos.put(myThreadInfo.getThreadId(), myThreadInfo);
        }

        return myThreadInfos;
    }

    public static class MyThreadInfo {
        long threadId;//线程ID
        String threadName;//线程名称
        long deltaTime;//CPU耗时
        long cpuTotalTime;//线程CPU总耗时
        float cpuRate;//CPU使用率
        long cpuRank;//CPU使用排名
        String threadState;//线程状态
        String stackTrace;//堆栈跟踪信息

        public long getThreadId() {
            return threadId;
        }

        public void setThreadId(long threadId) {
            this.threadId = threadId;
        }

        public String getThreadName() {
            return threadName;
        }

        public void setThreadName(String threadName) {
            this.threadName = threadName;
        }

        public long getCpuRank() {
            return cpuRank;
        }

        public void setCpuRank(long cpuRank) {
            this.cpuRank = cpuRank;
        }

        public double getCpuRate() {
            return cpuRate;
        }

        public void setCpuRate(float cpuRate) {
            this.cpuRate = cpuRate;
        }

        public String getThreadState() {
            return threadState;
        }

        public void setThreadState(String threadState) {
            this.threadState = threadState;
        }

        public String getStackTrace() {
            return stackTrace;
        }

        public void setStackTrace(String stackTrace) {
            this.stackTrace = stackTrace;
        }

        public long getDeltaTime() {
            return deltaTime;
        }

        public void setDeltaTime(long deltaTime) {
            this.deltaTime = deltaTime;
        }

        public long getCpuTotalTime() {
            return cpuTotalTime;
        }

        public void setCpuTotalTime(long cpuTotalTime) {
            this.cpuTotalTime = cpuTotalTime;
        }

        @Override
        public String toString() {
            return cpuRank + ".'" + threadName + "',ID=" + threadId + ",state=" + threadState + ",cpuUsage=" + cpuRate + "%" + ",deltaTime=" + deltaTime + "ms" + ",totalTime=" + cpuTotalTime + "ms\n" + stackTrace;
        }
    }

    public static class ThreadInfoTrace {
        private LocalDateTime time;
        private float cpuUsage;

        long cpuRank;//CPU使用排名

        private String stackTrace;

        public ThreadInfoTrace(LocalDateTime time, float cpuUsage, String stackTrace) {
            this.time = time;
            this.cpuUsage = cpuUsage;
            this.stackTrace = stackTrace;
        }

        public LocalDateTime getTime() {
            return time;
        }

        public void setTime(LocalDateTime time) {
            this.time = time;
        }

        public float getCpuUsage() {
            return cpuUsage;
        }

        public void setCpuUsage(float cpuUsage) {
            this.cpuUsage = cpuUsage;
        }

        public String getStackTrace() {
            return stackTrace;
        }

        public void setStackTrace(String stackTrace) {
            this.stackTrace = stackTrace;
        }

        public long getCpuRank() {
            return cpuRank;
        }

        public void setCpuRank(long cpuRank) {
            this.cpuRank = cpuRank;
        }

        @Override
        public String toString() {
            return cpuRank + "、" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(time) + ", " + cpuUsage + "%" + stackTrace;
        }
    }

}
