查看原文
其他

Java性能监控神器MyPerf4J,开源免费!

小胡 爱编程爱技术 2023-12-13

每天给大家带来网站工具、安卓软件、iOS软件、开源社区项目等黑科技!

如果你们有什么好的建议,也可以在后台留言。

创作不易,希望大家给一点鼓励,把公众号设置为"星标",点下文章"在看",谢谢大家!目前佛系更新,望多多支持~

MyPerf4J

一个针对高并发、低延迟应用设计的高性能 Java 性能监控和统计工具。

🎯MyPerf4J

一个针对高并发、低延迟应用设计的简单、快速且无侵入的Java方法性能监控和统计工具。受 perf4j 和 TProfiler 启发而来。

perf4j开源项目地址https://github.com/perf4j/perf4j

TProfiler开源项目地址https://github.com/alibaba/TProfiler

MyPerf4J 具有以下几个优势:

  • 高性能: 单线程支持每秒 1600 万次 响应时间的记录,每次记录只花费 63 纳秒

  • 无侵入: 采用 JavaAgent 方式,对应用程序完全无侵入,无需修改应用代码

  • 低内存: 采用内存复用的方式,整个生命周期只产生极少的临时对象,不影响应用程序的 GC

  • 高实时: 支持秒级统计,最低统计粒度为 1 秒,并且是全量统计,不丢失任何一次记录

⛏️MyPerf4J 能做什么?

以下是 MyPerf4J 的最基本的几个需求:

  • 能统计出方法的 RPS、Avg、Min、Max、StdDev、TP90、TP95、TP99、TP999 等性能指标

  • 可配置

    • 可指定统计某些类、某些方法

    • 可指定不统计某些类、某些方法

  • 拥有极致的性能

    • 不影响应用的 GC

    • 不影响应用的 RT

  • 性能指标的处理可以定制化,例如:日志收集、上报给日志收集服务等

MyPerf4J 可以统计以下几个性能指标:

  • RPS: 每秒请求数
  • Count: 总请求数
  • RT: 方法响应时间
    • TP: Top 百分数 (Top Percentile)
    • TP90: 在一个时间段内(如 1 分钟),统计该方法每次调用所消耗的时间,并将这些时间按从小到大的顺序进行排序,取第 90% 的那个值作为 TP90 值;
    • Avg: 方法平均响应时间
    • Min: 方法最小响应时间
    • Max: 方法最大响应时间
    • StdDev: 方法响应时间的标准差
    • TP50, TP90, TP95, TP99, TP999, TP9999, TP99999, TP100

🧱MyPerf4J 整体架构

MyPerf4J 支持两种部署结构:

  • 3.x 

  • 3.x 及其之前 

各组件说明

组件说明
Java ApplicationMyPerf4J 的运行容器
MyPerf4JMetrics 收集和统计
Collector日志收集器
Storage存储平台
Dashboard可视化平台

各组件关系说明

  • MyPerf4J 定时把指定时间片内的统计数据写入日志文件

  • Collector 从日志文件中读取统计数据,并写入 Storage

  • Dashboard 从 Storage 中读取数据并展示

注意,MyPerf4J 项目 只提供 MyPerf4J 本身,其余组件需要用户自行选择。这样做的优点如下:
  • 保持 MyPerf4J 的精简

  • 健壮性,不论是 Collector、Storage 还是 Dashboard 宕掉都不影响 MyPerf4J 的数据采集,也不丢失采集到的数据

  • 多样性,Collector 可以是 Telegraf 也可以是 Filebeat;Storage 可以是 InfluxDB 也可以是 OpenTSDB; Dashboard 可以是 Grafana 也可以是 Chronograf

🔑️实现原理

1、数据采集

 MyPerf4J 目前采用 ASM 字节码修改框架在 JVM 加载类时修改 Java 方法的字节码:

  • 在方法的开头加入 long start = System.nanoTime();

  • 在方法的结尾加入 ProfilingAspect.profiling(start, methodId);,其中 methodId 为类加载时为每一个方法分配的唯一 ID

2、数据存储

 MyPerf4J 采用 数组(timingArr) + Map(timingMap) 的方式把方法的响应时间存储到 Recorder中:
  • 在 timingArr 中,数组下标代表方法的响应时间,而下标对应的元素值代表该响应时间出现的次数,例如从上图中可以看出,timingArr 中记录了 100次 0ms 的响应,0次 1ms 的响应,80次 2ms 的响应 和 20次 3ms 的响应

  • 在 timingMap 中,Map 的 key 代表方法的响应时间,而 key 对应的 value 代表该响应时间出现的次数,例如,从上图中可以看出,timingMap 中记录了 120次 1010ms 的响应,102次 1025ms 的响应,95次 1041ms 的响应,1次 2094ms 的响应。

3、MyPerf4J 内存占用分析

假设你有 1024 个方法需要监控,并且 timingArr 长度为 1000,timingMap 大小为 128,Recorders 转盘的数量为 3
  • 每个 timingArr 占用 1000 * 4B = 4KB 的空间

  • 每个 timingMap 占用的空间大约为 128 * (96B + 4B) = 12.5KB,其中,每个 Map.Node 占用 96B
综上所述,监控这 1024 个方法只需要占用 3 * 1024 * (4KB + 12.5KB) = 49.5MB,并且这 49.5MB 的对象常驻在内存中,除了 timingMap 扩容和缩容时会分配少量内存外,所有的对象在 MyPerf4J 的整个生命周期中只分配一次!支持 弹性内存管理 后,MyPerf4J 可以降低数十倍的内存占用!
4、数据处理

 为简化处理流程,以 timingArr 为例进行说明:

  • 第一步,通过遍历一次 timingArr 可以计算出所有响应时间及其出现的次数,把这份数据存入 sortedRecords 中,在 sortedRecords 偶数下标代表响应时间,奇数下标代表次数,以上图为例,0ms 出现 100次,2ms 出现 80次,3ms 出现 20次。

  • 第二步,把 sortedRecords 按逻辑展开,即可快速计算出 TP50、TP90 和 TP99 等指标,以上图为例,把 sortedRecords 按逻辑展开,可以得到连续存放的 100 个 0、80 个 2 和 20 个 3,一共 200 个响应时间,那么 TP50 对应于下标为 200 * 50% = 100 的值,也就是 2ms。

5、内存管理

5.1 内存复用

MyPerf4J 采用 Recorder 来记录方法的响应时间,采用 Recorders 存放同一个时间片内的所有 Recorder,采用 List<Recorders> 存放所有的 Recorders 并且维护一个 curIndex 指向当前时间片的 Recorders;所有的 Recorder 和 Recorders 均在应用启动时就分配好,当当前时间片结束时,curIndex 便指向 List<Recorders> 中下一个 Recorders 作为下一个时间片的 Recorders,并计算出当前时间片的性能指标,如此往复。

5.2 弹性内存管理

MyPerf4J 自 2.8.0 开始支持弹性内存管理,MyPerf4J 在应用启动时,会根据应用上一次运行期间的方法响应时间的分布计算出本次运行所需分配的内存,可以有效的减小每个 Recorder 的 timingArr 的大小,既可以大幅减少 MyPerf4J 的内存占用,也可以提高性能指标的计算速度!

🚢项目集成MyPerf4J

MyPerf4J 采用 JavaAgent 配置方式,透明化接入应用,对应用代码完全没有侵入。

1、下载

  • 下载并解压 MyPerf4J-ASM.zip

  • 阅读解压出的 README 文件

  • 修改解压出的 MyPerf4J.properties 配置文件中 app_namemetrics.log.xxx、 filter.packages.include 的配置值

2、配置模板文件MyPerf4J.properties如下:

# MyPerf4J 所有配置请参考:https://github.com/LinShunKang/MyPerf4J/wiki/%E9%85%8D%E7%BD%AE
# 配置监控应用的名称app_name = MyApp
# 是否开启 debug 模式,可配置为 true/falsedebug = true
################################################################################ HTTP Server Configuration ################################################################################
# 配置 HTTP Server 端口号# 格式为:首选端口,备选最小端口,备选最大端口http.server.port = 2048,2000,2040
# 配置 HTTP Server 最小工作线程数http.server.min_workers = 1
# 配置 HTTP Server 最大工作线程数http.server.max_workers = 2
# 配置 HTTP Server 排队数, worker 线程数达到最大时,接受排队的请求个数http.server.accept_count = 1024
################################################################################ Metrics Configuration ################################################################################
# 配置 MetricsExporter 类型# log.stdout: 以标准格式化结构输出到 stdout.log# log.standard: 以标准格式化结构输出到磁盘# log.influxdb: 以 InfluxDB LineProtocol 格式输出到磁盘# http.influxdb: 以 InfluxDB LineProtocol 格式发送至 InfluxDB servermetrics.exporter = log.stdout
# 配置各项监控指标日志的文件路径# 如果 metrics.exporter 配置为 log.influxdb,建议把所有的 metrics.log.* 路径配置成一样以方便 Telegraf 收集metrics.log.method = /data/logs/MyPerf4J/metrics.logmetrics.log.class_loading = /data/logs/MyPerf4J/metrics.logmetrics.log.gc = /data/logs/MyPerf4J/metrics.logmetrics.log.memory = /data/logs/MyPerf4J/metrics.logmetrics.log.buff_pool = /data/logs/MyPerf4J/metrics.logmetrics.log.thread = /data/logs/MyPerf4J/metrics.logmetrics.log.file_desc = /data/logs/MyPerf4J/metrics.logmetrics.log.compilation = /data/logs/MyPerf4J/metrics.log
# 配置日志文件滚动时间间隔,分别有 MINUTELY、HOURLY 和 DAILY 三个值metrics.log.rolling.time_unit = DAILY
# 配置历史日志文件保留个数metrics.log.reserve.count = 7
# 配置方法指标采集的时间片,单位为 ms,最小 1s,最大 600smetrics.time_slice.method = 10000
# 配置 JVM 指标采集的时间片,单位为ms,最小1s,最大600smetrics.time_slice.jvm = 10000
# 是否展示方法参数类型metrics.method.show_params = true
# 配置 Java 类的层级映射关系# 规则为:LevelA:[classNameExpA1,classNameExpA2];LevelB:[classNameExpB1,classNameExpB2];metrics.method.class_level_mapping = Filter:[*Filter];Handler:[*Handler];
################################################################################ InfluxDB Configuration ################################################################################
influxdb.version = 1.0influxdb.orgName = <yourOrgnation>influxdb.host = 127.0.0.1influxdb.port = 8086influxdb.database = <yourDatabase>
influxdb.username = admininfluxdb.password = admin123
# 配置超时时间,单位:msinfluxdb.conn_timeout = 3000influxdb.read_timeout = 5000
################################################################################ Filter Configuration ################################################################################
# 配置需要监控的package,可配置多个,用英文';'分隔# com.demo.p1 代表包含以 com.demo.p1 为前缀的所有包和类# [] 表示集合的概念:例如,com.demo.[p1,p2,p3] 代表包含以 com.demo.p1、com.demo.p2 和 com.demo.p3 为前缀的所有包和类,等价于 com.demo.p1;com.demo.p2;com.demo.p3# * 表示通配符:可以指代零个或多个字符,例如,com.*.demo.*filter.packages.include = cn.perf4j.demo;
# 配置不需要监控的package,可配置多个,用英文';'分隔filter.packages.exclude = cn.perf4j.demo.dao.DemoDAOImpl
# 配置不需要进行监控的方法名,每个方法名用英文 ';' 分隔#filter.methods.exclude = getId1 代表排除所有方法名为 getId1 的方法#filter.methods.exclude = DemoServiceImpl.getId1 代表排除类 DemoServiceImpl 中所有方法名为 getId1 的方法#filter.methods.exclude = DemoServiceImpl.getId1(long) 代表排除类 DemoServiceImpl 中方法签名为 getId1(long) 的方法filter.methods.exclude = equals;toString;hashCode;wait
# 配置是否排除私有方法,true/falsefilter.methods.exclude_private = true
# 配置需要排除的 ClassLoader,可配置多个ClassLoader,用英文';'分隔filter.class_loaders.exclude =
################################################################################ Recorder Configuration ################################################################################
# 配置 Recorder 模式,可配置为 accurate/roughrecorder.mode = ACCURATE
# 配置 Recorders 转盘的数量recorders.backup_count = 3
# 指定通用的方法执行时间阈值,单位为 msrecorder.size.timing_arr = 1500
# 指定一个时间片内,超过方法执行时间阈值的次数,仅在 recorder.mode = accurate 时有效recorder.size.timing_map = 128

3、修改JVM启动参数

在 JVM 启动参数里加上以下两个参数

  • -javaagent:/path/to/MyPerf4J-ASM.jar

  • -DMyPerf4JPropFile=/path/to/MyPerf4J.properties

形如:java -javaagent:/path/to/MyPerf4J-ASM.jar -DMyPerf4JPropFile=/path/to/MyPerf4J.properties -jar yourApp.jar

注意:如果您使用 JDK9 及其之上的版本,请额外添加 --add-opens java.base/java.lang=ALL-UNNAMED

4、运行项目

启动应用,监控日志输出到 /path/to/log/method_metrics.log:

MyPerf4J Method Metrics [2020-01-01 12:49:57, 2020-01-01 12:49:58]

Method[6]                            Type        Level  TimePercent      RPS  Avg(ms)  Min(ms)  Max(ms)    StdDev    Count     TP50     TP90     TP95     TP99    TP999   TP9999

DemoServiceImpl.getId2(long)      General      Service      322.50%     6524     0.49        0        1     0.50      6524        0        1        1        1        1        1

DemoServiceImpl.getId3(long)      General      Service      296.10%     4350     0.68        0        1     0.47      4350        1        1        1        1        1        1

DemoServiceImpl.getId4(long)      General      Service      164.60%     2176     0.76        0        1     0.43      2176        1        1        1        1        1        1

DemoServiceImpl.getId1(long)      General      Service        0.00%     8704     0.00        0        0     0.00      8704        0        0        0        0        0        0

DemoDAO.getId1(long)         DynamicProxy          DAO        0.00%     2176     0.00        0        0     0.00      2176        0        0        0        0        0        0

DemoDAO.getId2()             DynamicProxy          DAO        0.00%     2176     0.00        0        0     0.00      2176        0        0        0        0        0        0


5、卸载

在 JVM 启动参数中去掉以下两个参数,重启即可卸载此工具。

  • -javaagent:/path/to/MyPerf4J-ASM.jar

  • -DMyPerf4JPropFile=/path/to/MyPerf4J.properties

6、自行构建MyPerf4J-ASM.jar

  • git clone git@github.com:LinShunKang/MyPerf4J.git

  • mvn clean package

MyPerf4J-ASM-${MyPerf4J-version}.jar 在 MyPerf4J-ASM/target/ 目录下

✨结语

最后附上项目地址:

github地址:https://github.com/LinShunKang/MyPerf4J

gitee地址:https://gitee.com/mirrors/MyPerf4J

中文文档:https://github.com/LinShunKang/MyPerf4J/wiki/Chinese-Doc

资料获取


关注【爱编程爱技术】公众号,
获取 MyPerf4J-ASM复关键字“myperf4j
获取ASM4 使用指南回复关键字“asm”
您可能喜欢:

十款好用的开源软件,功能强悍,完全免费!

VeryCapture,一款堪称最强的截图、录制、识别工具!人人必备!

阿里巴巴开源项目Dubbo

Springboot内置Tomcat配置参数调优

Tomcat启动流程

零拷贝详解

Windows12来了,居然是初中生写的?

监控你的网络流量工具sniffnet

真人版《海贼王》震撼来袭!

sms4j让发送短信变的更简单

如何解决Netty粘包分包

干翻算法!118K stars 霸榜GitHub!

CodeGeeX:Tab一下,AI自动生成代码

Java8的Stream流


继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存