使用 JfrUnit 断言 JDK 飞行记录器事件

331 阅读3分钟

使用 JfrUnit 断言 JDK 飞行记录器事件

Gunnar Morling Red Hat 的开源软件工程师 介绍了 JfrUnit,这是一种新的测试实用程序,可用于检测 性能回归 JUnit Spock 框架的 。 解释性能测试结果(例如响应时间)可能很困难,因为可能存在由其他因素(例如其他进程或网络)而不是应用程序本身引起的回归。 JfrUnit 可用于通过测量内存分配、IO、数据库查询或其他特定于应用程序的元素来测试应用程序的性能。

JDK Flight Recorder (JFR) 从正在运行的应用程序收集事件,这些事件可用于诊断或分析应用程序。 这些事件几乎可以是任何事件,从内存分配到垃圾回收。 可以直接从命令行使用该工具,但它通常与 一起使用 JDK Mission Control ,后者提供可与 JFR 结合使用的 GUI 和各种插件。 JfrUnit 可以创建断言来验证来自应用程序的 JFR 事件。

JfrUnit 支持 OpenJDK 16 ,依赖项在 Maven Central 上可用:

<依赖> 
<groupId>org.moditect.jfrunit</groupId> 
<artifactId>jfrunit</artifactId> 
<version>1.0.0.Alpha1</version> 
<范围>测试</范围> 
</依赖> 

实现 JUnit 测试开始于添加 **@JfrEventTest**单元测试类的注解,除非测试用 **@QuarkusTest**注释作为 Quarkus 测试框架自动与 JFR 记录交互。 测试使用 **@EnableEvent**用于收集特定事件的注解,例如垃圾收集事件。 执行程序逻辑后, **jfrEvents.awaitEvents()**在使用断言验证事件是否发生之前,方法等待来自 JVM 或应用程序的任何 JFR 事件:

@JfrEventTest 
公共  类  GarbageCollectionTest { 
    public  JfrEvents  jfrEvents  =  new  JfrEvents (); 

    @测试 
    @EnableEvent ( "jdk.GarbageCollection" ) 
    public  void  testGarbageCollectionEvent () 抛出  异常 { 
        系统 。 GC (); 

        jfrEvents 。 等待事件 (); 

        assertThat ( jfrEvents )。 包含 ( 事件 ( “jdk.GarbageCollection” )); 
    } 
} 

或者,可以使用 Spock 框架编写相同的测试:

类  GarbageCollectionSpec  扩展  规范 { 
    JfrEvents  jfrEvents  =  new  JfrEvents () 

    @EnableEvent ( 'jdk.GarbageCollection' ) 
    def  '包含垃圾回收 Jfr 事件' () { 
        当 : 
        系统 。 GC () 

        然后 : 
        jfrEvents [ 'jdk.GarbageCollection' ] 
    } 
} 

除了验证事件是否发生外,还可以验证事件的详细信息,例如事件的持续时间 **Thread.sleep()**方法:

@测试 
@EnableEvent ( "jdk.ThreadSleep" ) 
public  void  testThreadSleepEvent () 抛出  异常 { 
    线程 。 睡眠 ( 42 ); 

    jfrEvents 。 等待事件 (); 

    assertThat ( jfrEvents ) 
. 包含 ( 事件 ( “jdk.ThreadSleep” ) 
. with ( "time" , Duration . of Millis ( 42 ))); 
} 

JfrUnit 允许创建更复杂的场景。 考虑以下示例,该示例收集内存分配事件并在断言内存分配介于特定值之间之前对它们求和:

@测试 
@EnableEvent ( "jdk.ObjectAllocationInNewTLAB" ) 
@EnableEvent ( "jdk.ObjectAllocationOutsideTLAB" ) 
public  void  testAllocationEvent () 抛出  异常 { 
    字符串  名称  =  线程 线程 。 当前线程 ()。 获取名称 (); 

    // 创建对象的应用逻辑 

    jfrEvents 。 等待事件 (); 
    长  总和  =  jfrEvents 。 过滤器 ( this :: isObjectAllocationEvent ) 
. 过滤器 ( 事件  - >  事件 。 getThread 。() getJavaName 。() 的equalsthreadName )) 
. mapToLong ( 这个 :: getAllocationSize ) 
. 总和 (); 

    断言 ( 总和 )。 isLessThan ( 43_000_000 ); 
    断言 ( 总和 )。 isGreaterThan ( 42_000_000 ); 
} 

私有  布尔  isObjectAllocationEvent ( RecordedEvent  re ) { 
    字符串  名称  =  re 。 获取事件类型 ()。 获取名称 (); 
    返回  名称 。 等于 ( "jdk.ObjectAllocationInNewTLAB" ) || 
            名字 。 等于 ( “jdk.ObjectAllocationOutsideTLAB” ); 
} 

私有  长  getAllocationSizeRecordedEvent  recordedEvent ){ 
    返回  记录事件 。 获取事件类型 ()。 获取名称 () 
. 等于 ( “jdk.ObjectAllocationInNewTLAB” ) ? 
            记录事件 。 getLong ( "tlabSize" ) : 
            记录事件 。 getLong ( "allocationSize" ); 
} 

也可以使用通配符“*”启用多个事件,例如, **@EnableEvent("jdk.ObjectAllocation\*")**可用于激活所有 ObjectAllocation 事件。

要重置收集的事件, **jfrEvents.reset()**方法可用于确保仅在 **reset()**方法收集。 例如,当运行多次迭代并断言每次迭代的结果时:

for ( int  i  =  0 ; i  <  ITERATIONS ; i ++ ) { 
    // 应用逻辑 

    jfrEvents 。 等待事件 (); 


    // 断言 

    jfrEvents 。 重置 (); 
} 

诸如 Hibernate 之类的框架本身不会发出事件,但在这些情况下, JMC 代理 可以使用 来创建事件。 使用 JMC 代理,可以生成 SQL 查询事件,然后可以使用这些事件来断言进入数据库的 SQL 查询的(数量)。 这在 的 使用 JfrUnit 连续性能回归测试中进行了演示 会话 ,该示例可在 JfrUnit 的示例中找到