V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
linyimin520812
V2EX  ›  程序员

分享一种 Spring 应用启动过程采样的方法

  •  1
     
  •   linyimin520812 · 249 天前 · 1918 次点击
    这是一个创建于 249 天前的主题,其中的信息可能已经有所发展或是发生改变。

    spring-startup-analyzer采集 Spring 应用启动过程数据,生成交互式分析报告(HTML),用于分析 Spring 应用启动卡点,支持 Spring Bean 异步初始化,减少优化 Spring 应用启动时间。

    其中 Wall Clock 采样火焰图是基于async-profiler的,async-profiler 只支持 linux 和 mac ,问题在于 windows 没有 POSIX 信号的概念,也没有类似 Linux 中 perf_event_open 的 API 。为了在 windows 下也能生成火焰图,使用了一种简单的方法,采样结果与 async-profiler 相比虽然会存在一些偏差Safepoint bias problem,但是基本上也能用。

    思路很简单:

    1.首先获取线程名为 main 的线程(主线程)

    List<Thread> sampledThreads = new ArrayList<>(ThreadUtils.findThreads(thread -> StringUtils.equals("main", thread.getName())));
    

    2.起一个周期调度任务进行采样,采样结果放到一个LinkedBlockingQueue

    LinkedBlockingQueue<StackTraceElement[]> STACK_TRACE_QUEUE = new LinkedBlockingQueue<>();
    
    ScheduledExecutorService SAMPLE_SCHEDULER = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
    
    SAMPLE_SCHEDULER.scheduleAtFixedRate(() -> {
    
        sampledThreads = getTargetThreads();
    
        for (Thread thread : sampledThreads) {
            STACK_TRACE_QUEUE.add(thread.getStackTrace());
        }
    }, 0, 5, TimeUnit.MILLISECONDS);
    
    

    3.起一个处理线程将队列中的采样结果放到 Map 中

    while (true) {
    
        try {
        	StackTraceElement[] traces = STACK_TRACE_QUEUE.poll(5, TimeUnit.SECONDS);
            if (traces == null || traces.length == 0) {
            	continue;
            }
            List<StackTraceElement> elements = Arrays.asList(traces);
            Collections.reverse(elements);
            String trace = elements.stream().map(element -> element.getClassName() + "." + element.getMethodName()).collect(Collectors.joining(";"));
            TRACE_MAP.put(trace, TRACE_MAP.getOrDefault(trace, 0) + 1);
        } catch (InterruptedException ignored) {
        }
    
        if (stop && STACK_TRACE_QUEUE.isEmpty()) {
        	break;
        }
    }
    
    

    将采样和处理两个逻辑通过阻塞队列分开可以在一定程度上保证采样周期的稳定性。

    将采样到的调用栈样本集进行整合后,需输出如下所示的文本格式。每一行代表一“类“调用栈,空格左边是调用栈的方法名排列,以分号分割,左栈底右栈顶,空格右边是该样本出现的次数。这样就可以进行可视化输出 svg 了,可视化的逻辑参考了 async-profiler 的实现,这里就不在具体描述了。

    base_func;func1;func2;func3 10
    base_func;funca;funcb 15
    

    最后再推荐一下项目,如果感兴趣欢迎 star, 提 PR

    • 项目地址: https://github.com/linyimin0812/spring-startup-analyzer

    • 项目描述:

      采集 Spring 应用启动过程数据——Spring Bean 初始化详情信息,支持初始化耗时/beanName 搜索、Spring Bean 初始化时序图方法调用次数及耗时统计(支持自定义方法)、应用未加载的 jar 包(帮助 fatjar 瘦身)及应用启动过程线程 wall clock 火焰图,并生成交互式分析报告(HTML),用于分析 Spring 应用启动卡点,支持 Spring Bean 异步初始化,减少优化 Spring 应用启动时间。

    • 亮点:

      • 无侵入:以 javaagent 形式接入,无需修改应用代码
      • 类隔离:自定义类加载器,与应用完全隔离
      • 数据详细:应用启动数据整合成交互式报表,供开发人员分析
      • 支持自定义扩展
    • 截图:

      • Spring Bean 初始化详情 Spring Bean Initialization

      • Spring Bean 初始化时序图 Spring Bean Timeline

      • 方法调用次数、耗时统计(支持自定义方法) Details of Method Invoke

      • 应用未加载的 jar 包(帮助 fatjar 瘦身) Unused Jars

      • 应用启动过程线程 wall clock 火焰图(支持指定线程名称,不指定则采集全部线程) Flame Gragh

    第 1 条附言  ·  249 天前
    大家如果有更好的实现方式,欢迎提 PR
    第 2 条附言  ·  249 天前
    目前觉得能力还是有点单一,后续想添加自动诊断的能力,有想法的欢迎一起参与
    5 条回复    2023-08-09 09:07:03 +08:00
    lyxeno
        1
    lyxeno  
       249 天前   ❤️ 1
    用了一下,蛮不错的,已 star
    xubeiyou
        2
    xubeiyou  
       249 天前   ❤️ 1
    这个感觉不错的样子! STAR 了 破坏了队形 本来是 666 的 变成 667 了
    chengyiqun
        3
    chengyiqun  
       249 天前
    感觉不错, mark 了
    banzhe0421
        4
    banzhe0421  
       249 天前
    简单跑了一下,感觉不错,后续有时间看一下代码
    258
        5
    258  
       248 天前
    感觉不错,
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2559 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 11:52 · PVG 19:52 · LAX 04:52 · JFK 07:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.