性能监控: SPF4J介绍

1. 整体介绍

性能测试是一项在软件生命开发周期中老是被置于最后一环的活动。咱们常常依靠 Java profilers 去帮助发现性能问题。java

在这篇文章中,咱们将会学习关于 Java 的简单性能测试框架 - SPF4J。它提供了能够加在咱们代码中的 API。所以,咱们能够将 性能监视变为咱们组件的一部分git

2. 度量捕获和可视化的基本概念

在咱们开始以前,让咱们用一个简单的例子来理解度量捕获和可视化的基本概念。github

让咱们想象一下:咱们正关注于一款新发布的 App 在应用商店的下载量,出于学习的目的,让咱们手工的作这件事情。数据库

2.1. 捕捉度量

首先咱们须要决定要测量什么,咱们感兴趣的是每分钟下载量。 所以*,* 咱们将会测量下载量的数量。bash

第二,咱们多久须要执行一次测量?让咱们 “每分钟测量一次”吧。app

最后,咱们应该监控多长时间?让咱们 “监控一小时吧”。框架

有了以上的这些规则,咱们就能够实施这个实验了。当实验完成的时候,咱们能够看到如下的结果:dom

时间    累积下载量                   每分钟下载数
----------------------------------------------
T       497                     0  
T+1     624                     127
T+2     676                     52
...     
T+14    19347                   17390
T+15    19427                   80
...  
T+22    27195                   7350
...  
T+41    41321                   11885
...   
T+60    43395                   40
复制代码

前两列-时间累积下载数– 咱们能够很直观的看到这些值。第三列,每分钟下载量,是一个由当前和以前累积下载量的差额计算出来的间接值。咱们看到了那个时间段的实际下载数。maven

2.2. 可视化度量

让咱们绘制一个关于 时间 vs每分钟下载量的线形图。函数

file

咱们能够看到,有一些峰值代表大量的下载发生在一些场合。由于使用线性比例做为下载量轴,因此较低的值以直线出现。

让咱们用以 10 为底的对数做为下载量轴的标度,并绘制一个对数/线性图。

file

如今咱们就能够看到那些更低的值了。这些值在 100 上下浮动。注意,线性图中的平均值为 703 ,由于它也包含峰值。

若是咱们将峰值从图像中移除,咱们可使用对数/线形图从咱们的实验中获得结论:

  • 每分钟下载量的平均值在 100 左右

3. 函数调用的性能监视

在理解了如何捕获一个简单的度量并从前面的例子中分析以后,让咱们如今将它应用于一个简单的 Java 方法 - 是不是素数

private static boolean isPrimeNumber(long number) {
    for (long i = 2; i <= number / 2; i++) {
        if (number % i == 0)
            return false;
    }
    return true;
}
复制代码

使用 SPF4J,有两种方法能够捕获指标。让咱们在下一节中探讨它们。

4. 设置和配置

4.1. Maven 设置

SPF4J 为不一样的性能测试提供了许多不一样的库,但咱们只须要一些简单的例子。

核心库是 [spf4j-core](search.maven.org/search?q=g:… AND a:spf4j-core),它为咱们提供了大部分必要的功能。

让咱们将其添加到 Maven 依赖:

<dependency>
    <groupId>org.spf4j</groupId>
    <artifactId>spf4j-core</artifactId>
    <version>8.6.10</version>
</dependency>
复制代码

有一个更适合性能监控的库 - *spf4j-aspects,*它使用的是 AspectJ

咱们将在咱们的示例中探讨这一点,因此咱们也添加它:

<dependency>
    <groupId>org.spf4j</groupId>
    <artifactId>spf4j-aspects</artifactId>
    <version>8.6.10</version>
</dependency>
复制代码

最后,SPF4J 还带有一个对数据可视化很是有用的简单 UI,因此让咱们添加 [spf4j-ui](search.maven.org/search?q=g:… AND a:spf4j-ui) 以下:

<dependency>
    <groupId>org.spf4j</groupId>
    <artifactId>spf4j-ui</artifactId>
    <version>8.6.10</version>
</dependency>
复制代码

4.2. 输出文件的配置

SPF4J 框架将数据写入时间序列数据库(TSDB),也能够选择写入文本文件。

让咱们配置它们并设置系统属性 spf4j.perf.ms.config

public static void initialize() {
  String tsDbFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.tsdb2";
  String tsTextFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.txt";
  LOGGER.info("\nTime Series DB (TSDB) : {}\nTime Series text file : {}",tsDbFile,tsTextFile);
  System.setProperty("spf4j.perf.ms.config""TSDB@" + tsDbFile + "," + "TSDB_TXT@" + tsTextFile);
}
复制代码

4.3. 记录器和来源

SPF4J 框架的核心功能是记录,聚合和保存指标,以便在分析时不须要进行后置处理。它经过使用MeasurementRecorderMeasurementRecorderSource 类来实现。

这两个类提供了两种记录度量的方法。关键的区别在于 MeasurementRecorder 能够从任何地方调用,而MeasurementRecorderSource 仅用于注解。

该框架为咱们提供了一个 RecorderFactory 工厂类,用于为不一样类型的聚合建立记录器和记录器源类的实例:

  • createScalableQuantizedRecorder()createScalableQuantizedRecorderSource()
  • createScalableCountingRecorder()createScalableCountingRecorderSource()
  • createScalableMinMaxAvgRecorder()createScalableMinMaxAvgRecorderSource()
  • createDirectRecorder()createDirectRecorderSource()

对于咱们的示例,让咱们选择可扩展的量化聚合。

4.4. 建立记录器

首先,让咱们建立一个辅助方法来建立 MeasurementRecorder 的实例:

public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
    String unitOfMeasurement = "ms";
    int sampleTimeMillis = 1_000;
    int factor = 10;
    int lowerMagnitude = 0;
    int higherMagnitude = 4;
    int quantasPerMagnitude = 10;

    return RecorderFactory.createScalableQuantizedRecorder(
      forWhat,unitOfMeasurement,sampleTimeMillis,factor,lowerMagnitude,
      higherMagnitude,quantasPerMagnitude);
}
复制代码

咱们来看看不一样的参数意思:

  • unitOfMeasurement – 被测量的单位价值 - 对于性能检测方案,它一般是一个时间单位
  • sampleTimeMillis – 进行测量的时间段 - 换句话说,进行测量的频率
  • factor – 用于绘制测量值的对数标度的底数
  • lowerMagnitude – 对数刻度的最小值 - 对于底数 10,lowerMagnitude = 0 表示 10 的 0 次幂 = 1
  • higherMagnitude – 对数刻度上的最大值 - 对于底数 10,higherMagnitude = 4 表示 10 的 4 次幂 = 10,000
  • quantasPerMagnitude – 幅度范围内的部分数量 - 若是幅度范围从 1,000 到 10,000,则 quantasPerMagnitude = 10 表示范围将被划分为 10 个子范围

咱们能够看到能够根据须要更改值。所以,为不一样的测量建立单独的 MeasurementRecorder 实例多是个好主意。

4.5. 建立资源

接下来,让咱们使用另外一个辅助方法建立 MeasurementRecorderSource 的实例:

public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
    public static final MeasurementRecorderSource INSTANCE;
    static {
        Object forWhat = App.class + " isPrimeNumber";
        String unitOfMeasurement = "ms";
        int sampleTimeMillis = 1_000;
        int factor = 10;
        int lowerMagnitude = 0;
        int higherMagnitude = 4;
        int quantasPerMagnitude = 10;
        INSTANCE = RecorderFactory.createScalableQuantizedRecorderSource(
          forWhat,unitOfMeasurement,sampleTimeMillis,factor,
          lowerMagnitude,higherMagnitude,quantasPerMagnitude);
    }
}
复制代码

请注意,咱们使用了与以前相同的设置值。

4.6. 建立配置类

如今让咱们建立一个 Spf4jConfig 类并将全部上述方法放入其中:

public class Spf4jConfig {
    public static void initialize() {
        //...
    }

    public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
        //...
    }

    public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
        //...
    }
}
复制代码

4.7. 配置 aop.xml

SPF4J 为咱们提供了基于注解的性能测量和监控方法的选项。它使用 AspectJ 库,它容许在不修改代码自己的状况下向现有代码添加性能监视所需的其余行为。

让咱们使用 load-time weaver 编织咱们的类和切面,并将 aop.xml 放在 META-INF 文件夹下:

<aspectj>
    <aspects>
        <aspect name="org.spf4j.perf.aspects.PerformanceMonitorAspect" />
    </aspects>
    <weaver options="-verbose">
        <include within="com..*" />
        <include within="org.spf4j.perf.aspects.PerformanceMonitorAspect" />
    </weaver>
</aspectj>
复制代码

5. 使用 MeasurementRecorder

如今让咱们看看如何使用 MeasurementRecorder 来记录测试功能的性能指标。

5.1. 记录指标

让咱们生成 100 个随机数并在循环中调用是否为素数方法。在此以前,让咱们调用咱们的 Spf4jConfig 类来进行初始化并建立 MeasureRecorder 类的实例。使用此实例,让咱们调用 record() 方法来保存调用 100 次 isPrimeNumber() 所需的时间:

Spf4jConfig.initialize();
MeasurementRecorder measurementRecorder = Spf4jConfig
  .getMeasurementRecorder(App.class + " isPrimeNumber");
Random random = new Random();
for (int i = 0; i < 100; i++) {
    long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
    long startTime = System.currentTimeMillis();
    boolean isPrime = isPrimeNumber(numberToCheck);
    measurementRecorder.record(System.currentTimeMillis() - startTime);
    LOGGER.info("{}. {} is prime? {}",i + 1,numberToCheck,isPrime);
}
复制代码

5.2. 运行代码

咱们已经准备好测试咱们的简单函数 isPrimeNumber() 的性能。

让咱们运行代码并查看结果:

Time Series DB (TSDB) : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.txt
1. 406704834 is prime? false
...
9. 507639059 is prime? true
...
20. 557385397 is prime? true
...
26. 152042771 is prime? true
...
100. 841159884 is prime? false
复制代码

5.3. 查看结果

让咱们在项目文件夹下经过运行命令来启动 SPF4J UI:

java -jar target/dependency-jars/spf4j-ui-8.6.9.jar
复制代码

这将打开桌面UI应用程序。而后,从菜单中选择 File> Open 。以后,让咱们使用浏览窗口找到 spf4j-performance-monitoring.tsdb2 文件并打开它。

咱们如今能够看到一个新窗口,其中有一个包含咱们的文件名和子项目的树状图。让咱们点击子项目,而后点击它上面的 Plot按钮。

这会生成一系列图表。

第一个图表测量分布是咱们以前看到的对数线性图的变体。该图还显示了基于计数的热图。

file

第二个图表显示聚合数据,如最小值,最大值和平均值:

file

最后一张图显示了测量次数与时间的关系:

file

6. 使用 MeasurementRecorderSource

在上一节中,咱们必须围绕咱们的功能编写额外的代码来记录测量值。在本节中,让咱们使用另外一种方法来避免这种状况。

6.1. 记录指标

首先,咱们将删除为捕获和记录指标而添加的额外代码:

Spf4jConfig.initialize();
Random random = new Random();
for (int i = 0; i < 50; i++) {
    long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
    isPrimeNumber(numberToCheck);
}
复制代码

接下来,让咱们用 @PerformanceMonitor 来注解 isprimenumber() 方法,而不是全部的样板文件:

@PerformanceMonitor(
  warnThresholdMillis = 1,
  errorThresholdMillis = 100,
  recorderSource = Spf4jConfig.RecorderSourceForIsPrimeNumber.class)
private static boolean isPrimeNumber(long number) {
    //...
}
复制代码

让咱们看看这些参数:

  • warnThresholdMillis – 容许该方法在没有警告消息的状况下运行的最长时间
  • errorThresholdMillis – 容许该方法在没有错误消息的状况下运行的最长时间
  • recorderSourceMeasurementRecorderSource 的一个实例

6.2. 运行代码

让咱们先作一个 Maven 构建,而后经过传递 Java 代理来执行代码:

java -javaagent:target/dependency-jars/aspectjweaver-1.8.13.jar -jar target/spf4j-aspects-app.jar
复制代码

看下结果:

Time Series DB (TSDB) : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.txt

[DEBUG] Execution time 0 ms for execution(App.isPrimeNumber(..)),arguments [555031768]
...
[ERROR] Execution time  2826 ms for execution(App.isPrimeNumber(..)) exceeds error threshold of 100 ms,arguments [464032213]
...
复制代码

咱们能够看到 SPF4J 框架记录了每次方法调用所花费的时间。只要它超过 errorThresholdMillis 值100毫秒,它就会将其记录为错误。传递给该方法的参数也会被记录。

6.3. 查看结果

咱们可使用与以前使用 SPF4J UI 相同的方式查看结果,所以咱们能够参考上一节。

7. 结论

在本文中,咱们讨论了捕获和可视化指标的基本概念。而后,咱们借助一个简单的例子了解了 SPF4J 框架的性能监控功能。咱们还使用内置的 UI 工具来可视化数据。与往常同样,本文中的示例都可用 over on GitHub

原文:www.baeldung.com/spf4j

做者:Mohan Sundararaju

译者:KeepGoingPawn

相关文章
相关标签/搜索