SkyWalking jvm指标采集与存储

1. 概述

本文主要分享SkyWalking JVM 指标的收集与存储。大体流程如下:

  • Agent 每秒定时收集 JVM 指标到缓冲队列。
  • Agent 每秒定时将缓冲队列的 JVM 指标发送到 Collector 。
  • Collector 接收到 JVM 指标,异步批量存储到存储器( 例如,ES )。

目前 JVM 指标包括四个维度

  • CPU
  • Memory
  • MemoryPool
  • GC

SkyWalking UI 界面如下:

[这里是图片001]

2. Agent 收集 JVM 指标

2.1 JVMService

[这里是代码001],实现 BootService 、Runnable 接口,JVM 指标服务,负责将 JVM 指标收集并发送给 Collector 。代码如下:

  • queue属性,收集指标队列。
  • collectMetricFuture属性,收集指标定时任务。
  • sendMetricFuture属性,发送指标定时任务。
  • sender属性,发送器。

[这里是代码006]方法,初始化queuesender属性,并将自己添加到 GRPCChannelManager ,从而监听与 Collector 的连接状态。

[这里是代码009]方法,创建两个定时任务:

  • 第 88 至 90 行:创建收集指标定时任务,0 秒延迟,1 秒间隔,调用JVMService#run()方法。
  • 第 92 至 94 行:创建发送指标定时任务,0 秒延迟,1 秒间隔,调用Sender#run()方法。

2.1.1 定时收集

[这里是代码012]方法,代码如下:

  • 第 110 至 111 行:应用实例注册后,才收集 JVM 指标。
  • 第 116 至 122 行:创建 JVMMetric 对象。
    • 第 118 行:调用CPUProvider#getCpuMetric()方法,获得 GC 指标。
    • 第 119 行:调用MemoryProvider#getMemoryMetricList()方法,获得 Memory 指标。
    • 第 120 行:调用MemoryPoolProvider#getMemoryPoolMetricList()方法,获得 MemoryPool 指标。
    • 第 121 行:调用GCProvider#getGCList()方法,获得 GC 指标。
  • 第 125 至 128 行:提交 JVMMetric 对象到收集指标队列。

2.1.2 定时发送

[这里是代码017],实现 Runnable 、GRPCChannelListener 接口,JVM 指标发送器。代码如下:

  • status属性,连接状态。
  • stub属性,阻塞Stub 。
  • [这里是代码020]方法,当连接成功时,创建阻塞Stub 。
  • [这里是代码021]方法,代码如下:
    • 第 148 至 151 行:应用实例注册后,并且连接中,才发送 JVM 指标。
    • 第 153 至 155 行:调用#drainTo(Collection)方法,从队列移除所有 JVMMetric 到buffer数组。
    • 第 157 至 162 行:使用 Stub ,批量发送到 Collector 。

2.2 CPU

[这里是代码024],CPU 提供者,提供[这里是代码025]方法,采集 CPU 指标,如下图所示:[这里是图片002]

  • usagePercent:JVM 进程占用 CPU 百分比。
  • 第 51 行:调用CPUMetricAccessor#getCPUMetric()方法,获得 CPU 指标。

CPUProvider 构造方法中,初始化cpuMetricAccessor数量,代码如下:

  • 第 37 行:调用[这里是代码029]方法,获得 CPU 数量。
  • 第 40 至 42 行:创建 SunCpuAccessor 对象。
  • 第 44 至 46 行:发生异常,说明不支持,创建 NoSupportedCPUAccessor 对象。
  • 为什么需要使用ClassLoader#loadClass(className)方法呢?因为 SkyWalking Agent 是通过 JavaAgent 机制,实际未引入,所以通过该方式加载类。

2.2.1 CPUMetricAccessor

[这里是代码031],CPU 指标访问器抽象类。代码如下:

  • lastCPUTimeNs属性,获得进程占用 CPU 时长,单位:纳秒。
  • lastSampleTimeNs属性,最后采样时间,单位:纳秒。
  • cpuCoreNum属性,CPU 数量。
  • [这里是代码035]方法,初始化lastCPUTimeNslastSampleTimeNs
  • [这里是代码038]抽象方法,获得 CPU 占用时间,由子类完成。
  • [这里是代码039]方法,获得 CPU 指标。放在和 SunCpuAccessor 一起分享。这里我先记得,JVM 进程占用 CPU 率的计算公式:进程 CPU 占用总时间 / ( 进程启动总时间 * CPU 数量)

CPUMetricAccessor 有两个子类,实际上文我们已经看到它的创建:

  • SunCpuAccessor ,基于 SUN 提供的方法,获取 CPU 指标访问器。
  • NoSupportedCPUAccessor,不支持的 CPU 指标访问器。因此,使用该类的情况下,获取不到具体的进程 CPU 占用率。

SunCpuAccessor 构造方法,代码如下:

  • 第 32 行:设置 CPU 数量。

  • 第 33 行:获得 OperatingSystemMXBean 对象。通过该对象,在[这里是代码041]实现方法,调用[这里是代码042]方法,获得 JVM 进程占用 CPU 总时长。

    long getProcessCpuTime()

    Returns the CPU time used by the process on which the Java virtual machine is running in nanoseconds. The returned value is of nanoseconds precision but not necessarily nanoseconds accuracy. This method returns -1 if the the platform does not support this operation.

    Returns:
    the CPU time used by the process in nanoseconds, or -1 if this operation is not supported.

  • 第 34 行:调用#init()方法,初始化lastCPUTimeNslastSampleTimeNs

[这里是代码046]方法,获得 CPU 指标。代码如下:

  • 第 58 至 59 行:获得 JVM 进程占用 CPU 总时长。
  • 第 64 行:now - lastSampleTimeNs,获得 JVM 进程启动总时长。
  • 这里为什么相减呢?因为 CPUMetricAccessor 不是在 JVM 启动时就进行计算,通过相减,解决偏差。
  • 第 63 至 64 行:计算 JVM 进程占用 CPU 率。

2.3 Memory

[这里是代码048],Memory 提供者,提供[这里是代码049]方法,采集 Memory 指标,如下图所示:[这里是图片003]

2.4 MemoryPool

[这里是代码055],MemoryPool 提供者,提供[这里是代码056]方法,采集 MemoryPool 指标数组,如下图:[这里是图片004]

  • 推荐阅读文章:
  • type:内存区域类型。MemoryPool 和 Memory 的差别在于拆分的维度不同,如下图:[这里是图片005]
  • init:初始化的内存数量。
  • max:最大的内存数量。
  • used:已使用的内存数量。
  • committed:可以使用的内存数量。

MemoryPoolProvider 构造方法,代码如下:

  • 第 38 行:获得 MemoryPoolMXBean 数组。每个 MemoryPoolMXBean 对象,代表上面的一个区域类型。
  • 第 39 至 46 行:循环 MemoryPoolMXBean 数组,调用[这里是代码062]方法,找到对应的 GC 算法,创建对应的 MemoryPoolMetricAccessor 对象。
  • 第 47 至 49 行:未找到匹配的 GC 算法,创建 UnknownMemoryPool 对象。

2.4.1 MemoryPoolMetricAccessor

[这里是代码063],MemoryPool 指标访问器接口

MemoryPoolMetricAccessor 子类如下图:[这里是图片006]

2.4.2 MemoryPoolModule

[这里是代码066],实现 MemoryPoolMetricAccessor 接口,MemoryPool 指标访问器抽象类。不同 GC 算法之间,内存区域命名不同,通过如下六个方法抽象,分别对应不同内存区域,形成映射关系,屏蔽差异:

[这里是代码073]实现方法,代码如下:

  • 第 44 行:循环每个内存区域,收集每个 MemoryPool 指标。
  • 第 47 至 62 行:调用[这里是代码074]方法,逐个内存区域名字判断,获得对应的内存区域类型。
  • 第 65 至 71 行:创建 MemoryUsage 对象,并添加到结果数组。

2.5 GC

整体实现类似「2.4 MemoryPool」

[这里是代码075],GC 提供者,提供[这里是代码076]方法,采集 GC 指标数组,如下图:[这里是图片007]

  • phrase:生代类型,包括新生代、老生代。
  • count:总回收次数。
  • time:总回收占用时间。

GCProvider 构造方法,代码如下:

  • 第 38 行:获得 GarbageCollectorMXBean 数组。
  • 第 39 至 46 行:循环 MemoryPoolMXBean 数组,调用[这里是代码080]方法,找到对应的 GC 算法,创建对应的 GCMetricAccessor 对象。
  • 第 47 至 49 行:未找到匹配的 GC 算法,创建 UnknowGC 对象。

2.5.1 GCMetricAccessor

[这里是代码081],GC 指标访问器接口

GCMetricAccessor 子类如下图:[这里是图片008]

  • UnknowGC,未知的 GC 指标访问器实现类。每次[这里是代码083]方法,返回 GC 指标数组,但是每个指标元素是无具体数据的。

2.5.2 GCModule

[这里是代码084],实现 GCMetricAccessor 接口,GC 指标访问器抽象类。不同 GC 算法之间,生代命名不同,通过如下两个方法抽象,分别对应两个生代,形成映射关系,屏蔽差异:

[这里是代码087]实现方法,代码如下:

  • 第 44 行:循环 GarbageCollectorMXBean 数组,收集每个 GC 指标。
  • 第 47 至 62 行:获得生代类型。
  • 第 65 至 71 行:创建 GC 对象,并添加到结果数组。

3. Collector 存储 JVM 指标

3.1 JVMMetricsServiceHandler

我们先来看看 API 的定义,[这里是代码088],如下图所示:

[这里是图片009]

[这里是代码089], 代码如下:

  • 第 60 行:循环接收到的 JVMMetric数组
  • 第 62 行:调用[这里是代码090]方法,发送心跳,记录应用实例的最后心跳时间。因为目前 SkyWaling 主要用于 JVM 平台,通过每秒的 JVM 指标收集的同时,记录应用实例的最后心跳时间。
  • 第 62 行:调用[这里是代码091]方法,处理 CPU 数据。
  • 第 64 行:调用[这里是代码092]方法,处理 Memory 数据。
  • 第 66 行:调用[这里是代码093]方法,处理 Memory Pool 数据。
  • 第 68 行:调用[这里是代码094]方法,处理 GC 数据。
  • 第 73 至 74 行:全部处理完成,返回成功。

上述的#sendToXXX()方法,内部每个对应调用一个如下图 Service 提供的方法:[这里是图片010]

  • 每个 Service 的实现,对应一个数据实体和一个 Graph 对象,通过流式处理,最终存储到存储器( 例如 ES ) ,流程如下图:
  • 具体的实现代码,我们放在下面的数据实体一起分享。

3.2 CPU

[这里是代码096],CPU 指标。

3.3 Memory

[这里是代码106],Memory 指标。

  • [这里是代码107], MemoryMetric 表(memory_metric)。字段如下:
    • instance_id:应用实例编号。
    • isHeap:是否堆内内存。
    • init:初始化的内存数量。
    • max:最大的内存数量。
    • used:已使用的内存数量。
    • committed:可以使用的内存数量。
    • time_bucket:时间。
  • [这里是代码116],MemoryMetric 的 EsDAO 。
  • 在 ES 存储例子如下图:[这里是图片012]
  • [这里是代码117],Memory 指标服务,调用 MemoryMetric 对应的[这里是代码118]对象,流式处理,最终 MemoryMetric 保存到存储器。
  • [这里是代码119], Memory 指标批量存储 Worker 。

3.4 MemoryPool

[这里是代码120],MemoryPool 指标。

  • [这里是代码121], MemoryPool 表(memory_pool_metric)。字段如下:
    • instance_id:应用实例编号。
    • pool_type:内存区域类型。
    • init:初始化的内存数量。
    • max:最大的内存数量。
    • used:已使用的内存数量。
    • committed:可以使用的内存数量。
    • time_bucket:时间。
  • [这里是代码130],MemoryPoolMetric 的 EsDAO 。
  • 在 ES 存储例子如下图:[这里是图片013]
  • [这里是代码131],MemoryPoolMetric 指标服务,调用 MemoryPoolMetric 对应的[这里是代码132]对象,流式处理,最终 MemoryPoolMetric 保存到存储器。
  • [这里是代码133], MemoryPool 指标批量存储 Worker 。

3.5 GC

[这里是代码134],GC 指标。

4. 心跳

Collector 在接收到 GC 指标上传后,调用[这里是代码146]方法,发送心跳,记录应用实例的最后心跳时间。因为目前 SkyWaling 主要用于 JVM 平台,通过每秒的 JVM 指标收集的同时,记录应用实例的最后心跳时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值