1. Overview
1.概述
Performance testing is an activity often pushed towards the end stages of the software development cycle. We usually rely on Java profilers to help troubleshoot performance issues.
性能测试是一项经常被推到软件开发周期最后阶段的活动。我们通常依靠Java profilers来帮助排查性能问题。
In this tutorial, we’ll go through the Simple Performance Framework for Java (SPF4J). It provides us APIs that can be added to our code. As a result, we can make performance monitoring an integral part of our component.
在本教程中,我们将了解Java的简单性能框架(SPF4J)。它为我们提供了可以添加到我们代码中的API。因此,我们可以使性能监控成为我们组件的一个组成部分。
2. Basic Concepts of Metrics Capture and Visualization
2.指标采集和可视化的基本概念
Before we start, let’s try to understand the concepts of metrics capture and visualization using a simple example.
在我们开始之前,让我们试着用一个简单的例子来理解指标捕获和可视化的概念。
Let’s imagine that we’re interested in monitoring the downloads of a newly launched app in an app store. For the sake of learning, let’s think of doing this experiment manually.
让我们想象一下,我们对监测一个新推出的应用在应用商店中的下载情况感兴趣。为了便于学习,让我们考虑手动做这个实验。
2.1. Capturing the Metrics
2.1.捕获指标
First, we need to decide on what needs to be measured. The metric we’re interested is downloads/min. Therefore, we’ll measure the number of downloads.
首先,我们需要决定需要测量的内容。我们感兴趣的指标是下载量/分钟。因此,我们将测量下载次数。
Second, how frequently do we need to take the measurements? Let’s decide “once per minute”.
第二,我们需要多长时间进行一次测量?让我们决定 “每分钟一次”。
Finally, how long should we monitor? Let’s decide “for one hour”.
最后,我们应该监测多长时间?让我们决定 “一个小时”。
With these rules in place, we’re ready to conduct the experiment. Once the experiment is over, we can see the results:
有了这些规则,我们就可以进行实验了。一旦实验结束,我们就可以看到结果了。
Time Cumulative Downloads Downloads/min
----------------------------------------------
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
The first two columns – time and cumulative downloads – are direct values we observe. The third column, downloads/min, is a derived value calculated as the difference between current and previous cumulative download values. This gives us the actual number of downloads during that time period.
前两栏–时间和累计下载–是我们观察到的直接数值。第三列,downloads/min,是一个衍生值,计算为当前和以前的cumulative download值之间的差异。这给我们提供了该时间段内的实际下载量。
2.2. Visualizing the Metrics
2.2.衡量标准的可视化
Let’s plot a simple linear graph of time vs downloads/min.
让我们绘制一个时间与下载量/min的简单线性图。
We can see that there are some peaks indicating a large number of downloads that happened on a few occasions. Due to the linear scale used for downloads axis, the lower values appear as a straight line.
我们可以看到,有一些峰值表明有大量的下载发生在几个场合。由于下载量轴使用的是线性比例,较低的数值显示为一条直线。
Let’s change downloads axis to use a logarithmic scale (base 10) and plot a log/linear graph.
让我们把downloads轴改为使用对数刻度(基数为10),并绘制一个对数/线性图。
Now we actually start seeing the lower values. And they are closer towards 100 (+/-). Notice that the linear graph indicated an average of 703 as it included the peaks also.
现在我们实际上开始看到较低的数值。而且它们更接近于100(+/-)。请注意,线性图显示的平均值为703,因为它也包括了峰值。
If we were to exclude the peaks as aberrations, we can conclude from our experiment using the log/linear graph:
如果我们将这些峰值排除在畸变之外,我们可以用对数/线性图从我们的实验中得出结论。
- the average downloads/min is in the order of 100s
3. Performance Monitoring of a Function Call
3.一个函数调用的性能监测
Having understood how to capture a simple metric and analyze it from the previous example, let’s now apply it on a simple Java method — isPrimeNumber:
从前面的例子中了解了如何捕获一个简单的指标并对其进行分析,现在让我们把它应用于一个简单的Java方法–isPrimeNumber。
private static boolean isPrimeNumber(long number) {
for (long i = 2; i <= number / 2; i++) {
if (number % i == 0)
return false;
}
return true;
}
Using SPF4J, there are two ways to capture metrics. Let’s explore them in the next section.
使用SPF4J,有两种方法来捕获指标。让我们在下一节中探讨它们。
4. Setup and Configuration
4.设置和配置
4.1. Maven Setup
4.1.Maven设置
SPF4J provides us many different libraries for different purposes, but we only need a few for our simple example.
SPF4J为我们提供了许多不同用途的库,但对于我们这个简单的例子,我们只需要几个。
The core library is spf4j-core, which provides us most of the necessary features.
核心库是spf4j-core,它为我们提供了大部分必要的功能。
Let’s add this as a Maven dependency:
让我们把它作为一个Maven依赖项加入。
<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-core</artifactId>
<version>8.6.10</version>
</dependency>
There is a more well-suited library for performance monitoring — spf4j-aspects, which uses AspectJ.
有一个更适合于性能监控的库–spf4j-aspects,它使用AspectJ。
We’ll explore this in our example, so let’s add this too:
我们将在我们的例子中探讨这个问题,所以我们也来添加这个。
<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-aspects</artifactId>
<version>8.6.10</version>
</dependency>
And finally, SPF4J also comes with a simple UI that is quite useful for data visualization, so let’s add spf4j-ui as well:
最后,SPF4J还附带了一个简单的UI,对于数据的可视化相当有用,所以我们也来添加spf4j-ui。
<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-ui</artifactId>
<version>8.6.10</version>
</dependency>
4.2. Configuration of Output Files
4.2.输出文件的配置
SPF4J framework writes data into a time-series-database (TSDB) and can optionally also write to a text file.
SPF4J框架将数据写入一个时间序列数据库(TSDB),也可以选择写入一个文本文件。
Let’s configure both of them and set a system property spf4j.perf.ms.config:
让我们来配置它们两个,并设置一个系统属性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. Recorders and Sources
4.3.记录器和来源
The SPF4J framework’s core capability is to record, aggregate, and save metrics, so that there is no post-processing needed when analyzing it. It does so by using the MeasurementRecorder and MeasurementRecorderSource classes.
SPF4J框架的核心能力是记录、聚合和保存指标,这样在分析时就不需要进行后期处理。它通过使用MeasurementRecorder和MeasurementRecorderSource类来实现。
These two classes provide two different ways to record a metric. The key difference is that MeasurementRecorder can be invoked from anywhere, whereas MeasurementRecorderSource is used only with annotations.
这两个类提供了两种不同的方式来记录一个度量。关键的区别在于,MeasurementRecorder 可以从任何地方调用,而MeasurementRecorderSource 仅用于注释。
The framework provides us a RecorderFactory class to create instances of recorder and recorder source classes for different types of aggregations:
该框架为我们提供了一个RecorderFactory 类,用于为不同类型的聚合创建记录器和记录器源类的实例。
- createScalableQuantizedRecorder() and createScalableQuantizedRecorderSource()
- createScalableCountingRecorder() and createScalableCountingRecorderSource()
- createScalableMinMaxAvgRecorder() and createScalableMinMaxAvgRecorderSource()
- createDirectRecorder() and createDirectRecorderSource()
For our example, let’s choose scalable quantized aggregation.
对于我们的例子,让我们选择可扩展的量化聚合。
4.4. Creating a Recorder
4.4.创建一个录音机
First, let’s create a helper method to create an instance of MeasurementRecorder:
首先,让我们创建一个辅助方法来创建一个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);
}
Let’s look at the different settings:
让我们来看看不同的设置。
- unitOfMeasurement – the unit value being measured – for a performance monitoring scenario, it is generally a unit of time
- sampleTimeMillis – the time period for taking measurements – or in other words, how often to take measurements
- factor – the base of the logarithmic scale used for plotting the measured value
- lowerMagnitude – the minimum value on the logarithmic scale – for log base 10, lowerMagnitude = 0 means 10 to power 0 = 1
- higherMagnitude – the maximum value on the logarithmic scale – for log base 10, higherMagnitude = 4 means 10 to power 4 = 10,000
- quantasPerMagnitude – number of sections within a magnitude – if a magnitude ranges from 1,000 to 10,000, then quantasPerMagnitude = 10 means the range will be divided into 10 sub-ranges
We can see that the values can be changed as per our need. So, it might be a good idea to create separate MeasurementRecorder instances for different measurements.
我们可以看到,这些值可以根据我们的需要来改变。因此,为不同的测量创建单独的MeasurementRecorder实例可能是个好主意。
4.5. Creating a Source
4.5.创建一个源
Next, let’s create an instance of MeasurementRecorderSource using another helper method:
接下来,让我们使用另一个辅助方法创建一个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);
}
}
Notice that we’ve used the same values for settings as previously.
请注意,我们使用了与之前相同的设置值。
4.6. Creating a Configuration Class
4.6.创建一个配置类
Let’s now create a handy Spf4jConfig class and put all the above methods inside it:
现在让我们创建一个方便的Spf4jConfig类,并把上述所有方法放在里面。
public class Spf4jConfig {
public static void initialize() {
//...
}
public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
//...
}
public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
//...
}
}
4.7. Configuring aop.xml
4.7.配置aop.xml
SPF4J provides us the option to annotate methods on which to do performance measurement and monitoring. It uses the AspectJ library, which allows adding additional behavior needed for performance monitoring to existing code without modification of the code itself.
SPF4J为我们提供了注释方法的选项,以便对其进行性能测量和监控。它使用AspectJ库,该库允许在不修改代码本身的情况下向现有代码添加性能监控所需的额外行为。
Let’s weave our class and aspect using load-time weaver and put aop.xml under a META-INF folder:
让我们使用加载时编织器来编织我们的类和方面,并将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. Using MeasurementRecorder
5.使用MeasurementRecorder
Let’s now see how to use the MeasurementRecorder to record the performance metrics of our test function.
现在让我们看看如何使用MeasurementRecorder来记录我们测试函数的性能指标。
5.1. Recording the Metrics
5.1.记录指标
Let’s generate 100 random numbers and invoke the prime check method in a loop. Prior to this, let’s call our Spf4jConfig class to do the initialization and to create an instance of MeasureRecorder class. Using this instance, let’s call the record() method to save the individual time taken for 100 isPrimeNumber() calls:
让我们生成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. Running the Code
5.2.运行代码
We’re now ready to test the performance of our simple function isPrimeNumber().
我们现在准备测试一下我们的简单函数isPrimeNumber()的性能。
Let’s run the code and see the results:
让我们运行代码,看看结果。
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. Viewing the Results
5.3.查看结果
Let’s launch the SPF4J UI by running the command from the project folder:
让我们通过在项目文件夹中运行命令来启动SPF4J用户界面。
java -jar target/dependency-jars/spf4j-ui-8.6.9.jar
This will bring up a desktop UI application. Then, from the menu let’s choose File > Open. After that, let’s use the browse window to locate the spf4j-performance-monitoring.tsdb2 file and open it.
这将带出一个桌面UI应用程序。然后,从菜单中让我们选择文件 >打开。之后,让我们使用浏览窗口找到spf4j-performance-monitoring.tsdb2文件并打开它。
We can now see a new window open up with a tree view containing our file name and a child item. Let’s click on the child item and then click on the Plot button above it.
现在我们可以看到一个新的窗口打开了,里面有一个树状视图,包含我们的文件名和一个子项目。让我们点击子项目,然后点击上面的Plot按钮。
This will generate a series of graphs.
这将产生一系列的图表。
The first graph, measurement distribution, is a variation of the log-linear graph we saw earlier. This graph additionally shows a heatmap based on the count.
第一个图,测量分布,是我们之前看到的对数线性图的一个变种。这张图另外显示了一个基于计数的热图。
The second graph shows aggregated data like min, max, and average:
第二张图显示了最小、最大和平均等汇总的数据。
And the last graph shows the count of measurements vs time:
最后一张图显示了测量的数量与时间的关系。
6. Using MeasurementRecorderSource
6.使用MeasurementRecorderSource
In the previous section, we had to write extra code around our functionality to record the measurements. In this section, let’s use another approach to avoid this.
在上一节中,我们不得不围绕我们的功能编写额外的代码来记录测量结果。在本节中,让我们用另一种方法来避免这种情况。
6.1. Recording the Metrics
6.1.记录指标
First, we’ll remove the extra code added for capturing and recording metrics:
首先,我们将删除为捕捉和记录指标而添加的额外代码。
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);
}
Instead of all that boilerplate, next, let’s annotate the isPrimeNumber() method using @PerformanceMonitor:
接下来,让我们使用@PerformanceMonitor对isPrimeNumber()方法进行注释,而不是所有这些模板。
@PerformanceMonitor(
warnThresholdMillis = 1,
errorThresholdMillis = 100,
recorderSource = Spf4jConfig.RecorderSourceForIsPrimeNumber.class)
private static boolean isPrimeNumber(long number) {
//...
}
Let’s look at the different settings:
让我们来看看不同的设置。
- warnThresholdMillis – maximum time allowed for the method to run without a warning message
- errorThresholdMillis – maximum time allowed for the method to run without an error message
- recorderSource – an instance of MeasurementRecorderSource
6.2. Running the Code
6.2.运行代码
Let’s do a Maven build first and then execute the code by passing a Java agent:
我们先进行Maven构建,然后通过Java代理来执行代码。
java -javaagent:target/dependency-jars/aspectjweaver-1.8.13.jar -jar target/spf4j-aspects-app.jar
We see the results:
我们看到了结果。
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]
...
We can see that SPF4J framework logs the time taken for every method call. And whenever it exceeds the errorThresholdMillis value of 100 ms, it logs it as an error. The argument passed to the method is also logged.
我们可以看到,SPF4J框架记录了每个方法调用的时间。只要超过errorThresholdMillis值100ms,就会被记录为错误。传递给方法的参数也会被记录下来。
6.3. Viewing the Results
6.3.查看结果
We can view the results the same way as we did earlier using the SPF4J UI so we can refer to the previous section.
我们可以像之前使用SPF4J用户界面一样查看结果,所以我们可以参考之前的章节。
7. Conclusion
7.结语
In this article, we talked about the basic concepts of capturing and visualizing metrics.
在这篇文章中,我们谈到了捕获和可视化指标的基本概念。
We then understood the performance monitoring capabilities of SPF4J framework with the help of a simple example. We also used the built-in UI tool to visualize the data.
然后,我们在一个简单的例子的帮助下了解了SPF4J框架的性能监控能力。我们还使用了内置的UI工具来实现数据的可视化。
As always, the examples from this article are available over on GitHub.
一如既往,本文中的例子可以在GitHub上找到。