diff --git a/README.md b/README.md index 4fc75c9..a90b300 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,41 @@ [![HitCount](http://hits.dwyl.io/blinkfox/stalker.svg)](http://hits.dwyl.io/blinkfox/stalker) [![Build Status](https://secure.travis-ci.org/blinkfox/stalker.svg)](https://travis-ci.org/blinkfox/stalker) [![GitHub license](https://img.shields.io/github/license/blinkfox/stalker.svg)](https://github.com/blinkfox/stalker/blob/master/LICENSE) [![codecov](https://codecov.io/gh/blinkfox/stalker/branch/master/graph/badge.svg)](https://codecov.io/gh/blinkfox/stalker) ![Java Version](https://img.shields.io/badge/Java-%3E%3D%208-blue.svg) -[中文介绍](https://github.com/blinkfox/stalker/blob/master/README_CN.md) +> 这是一个简单的用来对 Java 代码做性能评估的工具库。 -> A small library for performance evaluation of Java code. +## 一、特性 -## Features +- 轻量级(jar包仅`49kb`) +- 支持对性能的多种统计纬度 +- API简单易用,易于集成或扩展 -- Lightweight (jar package is only '28kb') -- API are simple and easy to use. -- Easy integration or expansion +## 二、快速集成 -## Maven integration +### 1. Maven ```xml com.blinkfox stalker - 1.1.1 + 1.2.0 ``` -## API introduction and use +### 2. Gradle -### Prepare +```bash +compile 'com.blinkfox:stalker:1.2.0' +``` + +## 三、API 介绍和使用 -Before doing performance testing on Java methods, prepare the test service class and methods to be tested: +### 预先准备 + +在对Java方法做性能测试之前,先准备好待测试的类和方法: ```java /** - * A class used to measure (test only) the time-consuming execution of methods in this class. + * 用于测量(仅测试使用)该类中的方法的执行耗时的类. * * @author blinkfox on 2019-02-03. */ @@ -38,27 +44,27 @@ Before doing performance testing on Java methods, prepare the test service class public class MyTestService { /** - * Test Method 1, the simulation of the business code takes 2~5 ms, - * and there will be a 1% chance of executing the exception. + * 测试方法1,模拟业务代码耗时 2~5 ms,且会有约 1% 的几率执行异常. */ public void hello() { + // 模拟运行时抛出异常. if (new Random().nextInt(100) == 5) { throw new MyServiceException("My Service Exception."); } + // 模拟运行占用约 2~5 ms 的时间. this.sleep(2L + new Random().nextInt(3)); } /** - * Test Method 2, the simulation business code runs for about 2 ms. + * 测试方法2,模拟业务代码运行占用约 2 ms 的时间. */ public void fastHello() { this.sleep(2L); } /** - * When this thread calls this method, - * it sleeps for the specified time and is used to simulate the time-consuming business. + * 本线程调用该方法时,睡眠指定时间,用来模拟业务耗时. * * @param time 时间 */ @@ -74,11 +80,11 @@ public class MyTestService { } ``` -### Stalker +### Stalker 类 -#### 1. Simplest example +#### 1. 最简示例 -The following code will warm up to `5` times, then formally execute `10` times in a single thread, and then calculate the statistics of the running results and output them: +以下代码将会预热`5`次,然后在单线程下正式执行`10`次,从而将运行结果计算统计并输出出来: ```java public static void main(String[] args) { @@ -86,7 +92,7 @@ public static void main(String[] args) { } ``` -The above results will default to the console output: +以上结果将默认在控制台输出: ```bash +------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -98,21 +104,20 @@ The above results will default to the console output: +---+----------+-------+---------+---------+------------+----------+---------+---------+---------+---------+---------------------+---------------------+ ``` -You can also get statistical results: +也可以获取到统计结果: ```java -// Get running statistics. -Measurement[] measurements = mStalker.runStatis(() -> new MyTestService().hello()); +// 获取运行的统计结果. +MeasureResult[] measureResults = mStalker.runStatis(() -> new MyTestService().hello()); -// Get the MeasureOutput result specified in the running Options. -// The default is the string content of the ASCII table output in the console log. -// It can be multiple results, so return the collection. +// 获取运行的 Options 中指定的 MeasureOutput 结果,默认是控制台中输出的 ASCII 表格的字符串内容, +// 你可以实现 MeasureOutput 接口,来实现自定义的结果返回,可以是多个结果,所以返回集合. List measurements = mStalker.run(() -> new MyTestService().hello()); ``` -#### 2. More complete example +#### 2. 更全示例 -The following code indicates that the two methods `hello()` and `fastHello()` will preheat `10` times, in the `1000` threads `200` concurrent, each time executing `5` times: +以下代码表示,两个方法`hello()`和`fastHello()`将会预热`10`次,在`1000`个线程`200`个并发下,每次执行`5`次: ```java Stalker.run(Options.of(1000, 200).warmups(10).runs(5), @@ -120,7 +125,7 @@ Stalker.run(Options.of(1000, 200).warmups(10).runs(5), () -> new MyTestService().fastHello()); ``` -The above results will default to the console output: +以上结果将默认在控制台输出: ```bash +-------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -133,43 +138,156 @@ The above results will default to the console output: +---+-----------+-------+---------+---------+------------+---------+---------+---------+----------+---------+---------------------+---------------------+ ``` -Explanation of results: +结果说明: -- `Costs`: Total time spent on actual official runs -- `Total`: total number of official runs -- `Success`: number of successful runs -- `Failure`: number of failed runs -- `Throughput`: Throughput of official runs -- `Sum`: the value after each time-consuming summation of the run -- `Avg`: arithmetic mean of all running time-consuming results -- `Min`: the minimum of all running time-consuming results -- `Max`: the maximum of all running time-consuming results -- `StdDev`: standard deviation of all running time-consuming results -- `95% LowerConfidence`: minimum boundary value for 95% confidence interval -- `95% LowerConfidence`: maximum boundary value for 95% confidence interval +- `Costs`: 实际正式运行所消耗的总时间 +- `Total`: 正式运行的总次数 +- `Success`: 正式运行的成功次数 +- `Failure`: 正式运行的失败次数 +- `Throughput`: 正式运行的吞吐量 +- `Sum`: 每次运行的耗时结果求和之后的值 +- `Avg`: 所有运行耗时结果的算术平均数 +- `Min`: 所有运行耗时结果中最小值 +- `Max`: 所有运行耗时结果中最大值 +- `StdDev`: 所有运行耗时结果的标准方差 +- `95% LowerConfidence`: 95%置信区间的最小边界值 +- `95% LowerConfidence`: 95%置信区间的最大边界值 -#### 3. Main methods +#### 3. submit 异步执行 -- `void run(Runnable... runnables)`: Perform performance measurement evaluation on several code to be executed. -- `void run(Options options, Runnable... runnables)`: Performance measurement evaluation of several code to be executed by custom `Options`. +Stalker 中的 `run` 方法默认是同步执行的,如果你的性能测试任务耗时比较久,可以直接调用 `submit` 来异步提交性能测试任务,`submit` 将返回 `StalkerFuture` 的实例,后续你就可以通过 `StalkerFuture` 实时获取任务执行情况或取消执行中的任务等。 -### Options +下面是在 20 个线程、5 并发下的异步执行情况: -Options indicates option parameters when making performance measurements. +```java +@Test +public void submitWithSlowMethod() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.of("SlowTest", 20, 5, 1), + () -> new MyTestService().slowHello()); + Assert.assertNotNull(stalkerFuture); + + while (!stalkerFuture.isDone()) { + List results = stalkerFuture.get(); + Assert.assertNotNull(results.get(0)); + Thread.sleep(50L); + } -#### The main properties are as follows + log.info("任务已完成,获取最后的执行结果."); + stalkerFuture.get(); +} +``` + +执行将得到如下类似结果: -- `name`: name. -- `threads`: The number of threads that are executed. The default is 1. -- `concurrens`: The number of concurrent executions under formal multithreading. The default is 1. -- `warmups`: The number of warm ups under single thread, the default is 5. -- `runs`: The number of times each thread is executed, the default is 10. -- `printErrorLog`: Whether to print the error log, the default is false. -- `outputs`: The measurement results are output in a variety of ways (collections). The default is output to the console, which can be customized to implement the `MeasureOutput` interface. +```bash ++-------------------------------------------------------------------------------------------------------------------------------------+ +| duration: 2 s, concurrens: 4, warmups:5, runs: 1, printErrorLog: false | ++---+-------+-------+---------+---------+------------+------+------+------+------+--------+---------------------+---------------------+ +| | Costs | Total | Success | Failure | Throughput | Sum | Avg | Min | Max | StdDev | 95% LowerConfidence | 95% UpperConfidence | ++---+-------+-------+---------+---------+------------+------+------+------+------+--------+---------------------+---------------------+ +| 1 | 0 ns | 0 | 0 | 0 | 0.00 | 0 ns | 0 ns | 0 ns | 0 ns | 0 ns | 0 ns | 0 ns | ++---+-------+-------+---------+---------+------------+------+------+------+------+--------+---------------------+---------------------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| duration: 2 s, concurrens: 4, warmups:5, runs: 1, printErrorLog: false | ++---+--------+-------+---------+---------+------------+--------+----------+------+-----------+----------+---------------------+---------------------+ +| | Costs | Total | Success | Failure | Throughput | Sum | Avg | Min | Max | StdDev | 95% LowerConfidence | 95% UpperConfidence | ++---+--------+-------+---------+---------+------------+--------+----------+------+-----------+----------+---------------------+---------------------+ +| 1 | 1.03 s | 69 | 66 | 3 | 66.86 | 3.92 s | 56.76 ms | 0 ns | 102.38 ms | 24.13 ms | 51.06 ms | 62.45 ms | ++---+--------+-------+---------+---------+------------+--------+----------+------+-----------+----------+---------------------+---------------------+ + +[main] INFO com.blinkfox.stalker.test.StalkerTest - 任务已完成,获取最后的执行结果,并移除任务记录. ++--------------------------------------------------------------------------------------------------------------------------------------------------+ +| duration: 2 s, concurrens: 4, warmups:5, runs: 1, printErrorLog: false | ++---+--------+-------+---------+---------+------------+--------+---------+------+-----------+----------+---------------------+---------------------+ +| | Costs | Total | Success | Failure | Throughput | Sum | Avg | Min | Max | StdDev | 95% LowerConfidence | 95% UpperConfidence | ++---+--------+-------+---------+---------+------------+--------+---------+------+-----------+----------+---------------------+---------------------+ +| 1 | 2.03 s | 138 | 132 | 6 | 68.03 | 7.89 s | 57.2 ms | 0 ns | 102.38 ms | 24.18 ms | 53.17 ms | 61.24 ms | ++---+--------+-------+---------+---------+------------+--------+---------+------+-----------+----------+---------------------+---------------------+ +``` -#### Main methods +#### 4. 执行指定的时间 -Here are a few overloaded methods for constructing an `Options` instance: +你也可以在 `run` 或者 `submit` 方法中通过 `options` 参数设置运行指定的时间,当达到指定的结束时间点时,将自动停止执行中的性能测试任务。 + +下面是运行 `15` 秒,5 个绝对并发的代码示例: + +```java +@Test +public void submitWithDuration() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.ofDurationSeconds(15, 5), + () -> new MyTestService().slowHello()); + + // 判断任务是否完成,没完成,则休眠 5 秒 直到完成为止. + while (!stalkerFuture.isDone()) { + Thread.sleep(5000L); + } + + log.info("任务已完成,获取最终的执行结果信息."); + stalkerFuture.get(); +} +``` + +执行之后将获得如下类似结果: + +```bash ++----------------------------------------------------------------------------------------------------------------------------------------------------+ +| duration: 15 s, concurrens: 5, warmups:5, runs: 1, printErrorLog: false | ++---+--------+-------+---------+---------+------------+----------+----------+------+-----------+---------+---------------------+---------------------+ +| | Costs | Total | Success | Failure | Throughput | Sum | Avg | Min | Max | StdDev | 95% LowerConfidence | 95% UpperConfidence | ++---+--------+-------+---------+---------+------------+----------+----------+------+-----------+---------+---------------------+---------------------+ +| 1 | 15.0 s | 1241 | 1199 | 42 | 82.71 | 1.24 min | 59.95 ms | 0 ns | 103.94 ms | 22.5 ms | 58.69 ms | 61.2 ms | ++---+--------+-------+---------+---------+------------+----------+----------+------+-----------+---------+---------------------+---------------------+ +``` + +#### 5. 停止正在运行中的任务 + +你也可以在获取到 `StalkerFuture` 对象后,停止正在运行中的任务。代码示例如下: + +```java +@Test +public void submitWithStop() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.of("StopTest", 20, 5, 1), + () -> new MyTestService().slowHello()); + + Thread.sleep(50L); + List results = stalkerFuture.get(); + Assert.assertNotNull(results.get(0)); + // 调用 cancel 取消任务. + stalkerFuture.cancel(); + + stalkerFuture.get(); + log.info("任务已停止,获取最后的执行结果."); +} +``` + +#### 6. 主要方法 + +- `List run(Runnable... runnables)`: 对若干个要执行的代码做性能测量评估,并返回输出结果信息. +- `List run(Options options, Runnable... runnables)`: 通过自定义的`Options`对若干个要执行的代码做性能测量评估,并返回输出结果信息. +- `MeasureResult[] runStatis(Options options, Runnable... runnables)`: 对若干个要执行的代码做性能测量评估,并返回多个基础测量统计结果信息. +- `StalkerFuture submit(Runnable task)`: 对要执行的代码做性能测量评估,并返回异步获取结果信息的 `Future`. +- `StalkerFuture submit(Options options, Runnable task)`: 通过自定义的`Options`对若干个要执行的代码做性能测量评估,并返回异步获取结果信息的 `Future`. + +### Options类 + +Options 表示做性能测量时的选项参数 + +#### 1. 主要属性如下 + +- `name`: 选项参数的名称 +- `threads`: 正式执行的线程数,默认为 `1`。 +- `concurrens`: 正式多线程下执行的并发数,默认为 `1`。 +- `warmups`: 单线程下的预热次数,默认 `5`。 +- `runs`: 每个线程正式执行的次数,默认 `10`。 +- `printErrorLog`: 是否打印错误日志,默认 `false`。 +- `outputs`: 将测量结果通过多种方式(集合)输出出来,默认为输出到控制台,可自定义实现 `MeasureOutput` 接口。 +- `duration`: `v1.2.0` 版本新增,表示运行的持续时间。 +- `scheduledUpdater`:`v1.2.0`版本新增,在调用 `submit` 方法时会默认开启,用于定时更新统计数据的定时更新器。 + +#### 2. 主要方法 + +以下是构造`Options`实例的若干重载方法: - `Options of(String name)` - `Options of(int runs)` @@ -177,23 +295,51 @@ Here are a few overloaded methods for constructing an `Options` instance: - `Options of(int threads, int concurrens)` - `Options of(String name, int threads, int concurrens)` - `Options of(String name, int threads, int concurrens, int runs)` - -Other methods: - -- `boolean valid()`: Check whether the parameters related to Options are legal. -- `Options named(String name)`: Set the name property of the Options instance -- `Options threads(int threads)`: Set the threads property of the Options instance -- `Options concurrens(int concurrens)`: Set the concurrens property of the Options instance -- `Options warmups(int warmups)`: Set the warmups property of the Options instance -- `Options runs(int runs)`: Set the runs property of the Options instance -- `Options printErrorLog(boolean printErrorLog)`: Set the printErrorLog property of the Options instance -- `Options outputs(MeasureOutput... measureOutputs)`: Set the outputs property of the Options instance - -### Assert - -The Assert is mainly used for assertion use. - -#### Demo +- `Options ofDuration(long amount, TimeUnit timeUnit)` +- `Options ofDuration(long amount, TimeUnit timeUnit, int concurrens)` +- `Options ofDurationSeconds(long amount, int concurrens)` +- `Options ofDurationMinutes(long amount, int concurrens)` +- `Options ofDurationHours(long amount, int concurrens)` +- `Options ofDurationDays(long amount, int concurrens)` + +其他方法: + +- `boolean valid()`: 校验 Options 相关参数是否合法 +- `Options named(String name)`: 设置 Options 实例的 name 属性 +- `Options threads(int threads)`: 设置 Options 实例的 threads 属性 +- `Options concurrens(int concurrens)`: 设置 Options 实例的 concurrens 属性 +- `Options warmups(int warmups)`: 设置 Options 实例的 warmups 属性 +- `Options runs(int runs)`: 设置 Options 实例的 runs 属性 +- `Options printErrorLog(boolean printErrorLog)`: 设置 Options 实例的 printErrorLog 属性 +- `Options outputs(MeasureOutput... measureOutputs)`: 自定义设置 Options 实例的 MeasureOutput 输出通道 +- `Options duration(long amount, TimeUnit timeUnit)`: 设置任务持续运行的时间 +- `Options enableScheduledUpdater()`: 默认的定时统计数据更新任务的配置选项,默认是 `10` 秒 +- `Options enableScheduledUpdater(long delay, TimeUnit timeUnit)`: 设置默认的定时统计数据更新任务的配置选项 +- `Options enableScheduledUpdater(long initialDelay, long delay, TimeUnit timeUnit)`: 设置默认的定时统计数据更新任务的配置选项 + +### StalkerFuture 类 + +- `void run()`: 执行可运行的方法,通常你不需要再去手动执行了。 +- `boolean cancel()`: 取消正在运行中的任务. +- `boolean cancel(boolean mayInterruptIfRunning)` 取消正在运行中的任务. +- `boolean isCancelled()`: 是否已经取消了执行中的性能测试任务。 +- `boolean isDone()`: 是否已经执行完成. +- `boolean isDoneSuccessfully()`: 是否是正常执行完成的. +- `List get()`: 实时获取任务的执行结果,该方法不会阻塞任务执行. +- `List get(long timeout, TimeUnit unit)`: 实时获取任务的执行结果,该方法不会阻塞任务执行. +- `MeasureResult getMeasureResult()`: 获取基础的测量统计结果信息. +- `long getCosts()`: 获取任务最终完成时实际所消耗的总的纳秒时间数. +- `long getTotal()`: 获取当前已经运行的总次数. +- `long getSuccess()`: 获取到当前时的运行成功的次数. +- `long getFailure()`: 获取当前运行失败的次数. +- `long getStartNanoTime()`: 获取任务开始运行时的纳秒时间戳. +- `long getEndNanoTime()`: 获取任务结束运行时的纳秒时间戳,如果任务还未结束,该值将是 `0`. + +### Assert类 + +Assert类主要用来做断言使用。 + +#### 示例 ```java Assert.assertFaster(Options.of(), @@ -201,18 +347,21 @@ Assert.assertFaster(Options.of(), () -> new MyTestService().hello()); ``` -## License +## 四、许可证 -This [stalker](https://github.com/blinkfox/stalker) library is open sourced under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). +本 [stalker](https://github.com/blinkfox/stalker) 类库遵守 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 许可证。 -## Changelog +## 五、变更日志 -- v1.1.1 New statistical index of throughput (2020-05-20) - - New statistical index of throughput; -- v1.1.0 Added the function of getting return results after running (2020-05-14) - - Added the ability to output results in `MeasureOutput`, and the default run method will also return its results; -  - Added `runStatis` method, you can get the original statistical result data; -- v1.0.1 Fix the limitation problem when too many threads created (2019-09-14) - - Fixed where thread creation failed after a certain number of thread pools failed; -- v1.0.0 Milestone version (2019-02-08) - - Completed the basic functionality required for benchmark performance testing; +- v1.2.0 新增了异步性能评估和大量的代码重构 (2020-06-07) + - 新增了异步提交任务作性能评估; + - 重构了大量代码,部分方法或类与之前的版本不兼容; +- v1.1.1 新增了吞吐量的统计指标 (2020-05-20) + - 新增了吞吐量的统计指标; +- v1.1.0 新增了运行后可以获取返回结果的功能 (2020-05-14) + - 新增了 `MeasureOutput` 中可输出结果的功能,且默认的 run 方法,也会返回其结果; + - 新增了 `runStatis` 方法,可以拿到原始的统计结果数据; +- v1.0.1 修复线程创建过多时的限制问题 (2019-09-14) + - 修复了线程池超过一定数量后的线程创建失败的问题; +- v1.0.0 里程碑正式版 (2019-02-08) + - 完成了基准性能测试所需的基础功能; diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 9254e27..0000000 --- a/README_CN.md +++ /dev/null @@ -1,216 +0,0 @@ -# stalker - -[![HitCount](http://hits.dwyl.io/blinkfox/stalker.svg)](http://hits.dwyl.io/blinkfox/stalker) [![Build Status](https://secure.travis-ci.org/blinkfox/stalker.svg)](https://travis-ci.org/blinkfox/stalker) [![GitHub license](https://img.shields.io/github/license/blinkfox/stalker.svg)](https://github.com/blinkfox/stalker/blob/master/LICENSE) [![codecov](https://codecov.io/gh/blinkfox/stalker/branch/master/graph/badge.svg)](https://codecov.io/gh/blinkfox/stalker) ![Java Version](https://img.shields.io/badge/Java-%3E%3D%208-blue.svg) - -[English Document](https://github.com/blinkfox/stalker/blob/master/README.md) - -> 这是一个简单的用来对Java代码做性能评估的工具库。 - -## 特性 - -- 轻量级(jar包仅`28kb`) -- API简单易用 -- 易于集成或扩展 - -## Maven集成 - -```xml - - com.blinkfox - stalker - 1.1.1 - -``` - -## API 介绍和使用 - -### 预先准备 - -在对Java方法做性能测试之前,先准备好待测试的类和方法: - -```java -/** - * 用于测量(仅测试使用)该类中的方法的执行耗时的类. - * - * @author blinkfox on 2019-02-03. - */ -@Slf4j -public class MyTestService { - - /** - * 测试方法1,模拟业务代码耗时 2~5 ms,且会有约 1% 的几率执行异常. - */ - public void hello() { - // 模拟运行时抛出异常. - if (new Random().nextInt(100) == 5) { - throw new MyServiceException("My Service Exception."); - } - - // 模拟运行占用约 2~5 ms 的时间. - this.sleep(2L + new Random().nextInt(3)); - } - - /** - * 测试方法2,模拟业务代码运行占用约 2 ms 的时间. - */ - public void fastHello() { - this.sleep(2L); - } - - /** - * 本线程调用该方法时,睡眠指定时间,用来模拟业务耗时. - * - * @param time 时间 - */ - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - log.info("InterruptedException", e); - Thread.currentThread().interrupt(); - } - } - -} -``` - -### Stalker类 - -#### 1. 最简示例 - -以下代码将会预热`5`次,然后在单线程下正式执行`10`次,从而将运行结果计算统计并输出出来: - -```java -public static void main(String[] args) { - Stalker.run(() -> new MyTestService().hello()); -} -``` - -以上结果将默认在控制台输出: - -```bash -+------------------------------------------------------------------------------------------------------------------------------------------------------+ -| threads: 1, concurrens: 1, warmups:5, runs: 10, printErrorLog: false | -+---+----------+-------+---------+---------+------------+----------+---------+---------+---------+---------+---------------------+---------------------+ -| | Costs | Total | Success | Failure | Throughput | Sum | Avg | Min | Max | StdDev | 95% LowerConfidence | 95% UpperConfidence | -+---+----------+-------+---------+---------+------------+----------+---------+---------+---------+---------+---------------------+---------------------+ -| 1 | 40.52 ms | 10 | 10 | 0 | 246.76 | 40.43 ms | 4.04 ms | 2.24 ms | 7.74 ms | 1.56 ms | 3.07 ms | 5.01 ms | -+---+----------+-------+---------+---------+------------+----------+---------+---------+---------+---------+---------------------+---------------------+ -``` - -也可以获取到统计结果: - -```java -// 获取运行的统计结果. -Measurement[] measurements = mStalker.runStatis(() -> new MyTestService().hello()); - -// 获取运行的 Options 中指定的 MeasureOutput 结果,默认是控制台中输出的 ASCII 表格的字符串内容,可以是多个结果,所以返回集合. -List measurements = mStalker.run(() -> new MyTestService().hello()); -``` - -#### 2. 更全示例 - -以下代码表示,两个方法`hello()`和`fastHello()`将会预热`10`次,在`1000`个线程`200`个并发下,每次执行`5`次: - -```java -Stalker.run(Options.of(1000, 200).warmups(10).runs(5), - () -> new MyTestService().hello(), - () -> new MyTestService().fastHello()); -``` - -以上结果将默认在控制台输出: - -```bash -+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| threads: 1000, concurrens: 200, warmups:10, runs: 5, printErrorLog: false | -+---+-----------+-------+---------+---------+------------+---------+---------+---------+----------+---------+---------------------+---------------------+ -| | Costs | Total | Success | Failure | Throughput | Sum | Avg | Min | Max | StdDev | 95% LowerConfidence | 95% UpperConfidence | -+---+-----------+-------+---------+---------+------------+---------+---------+---------+----------+---------+---------------------+---------------------+ -| 1 | 668.93 ms | 5000 | 4955 | 45 | 7474.57 | 17.22 s | 3.48 ms | 2.01 ms | 11.66 ms | 1.14 ms | 3.44 ms | 3.51 ms | -| 2 | 348.85 ms | 5000 | 5000 | 0 | 14332.69 | 11.23 s | 2.25 ms | 2.01 ms | 3.32 ms | 0.19 ms | 2.24 ms | 2.25 ms | -+---+-----------+-------+---------+---------+------------+---------+---------+---------+----------+---------+---------------------+---------------------+ -``` - -结果说明: - -- `Costs`: 实际正式运行所消耗的总时间 -- `Total`: 正式运行的总次数 -- `Success`: 正式运行的成功次数 -- `Failure`: 正式运行的失败次数 -- `Throughput`: 正式运行的吞吐量 -- `Sum`: 每次运行的耗时结果求和之后的值 -- `Avg`: 所有运行耗时结果的算术平均数 -- `Min`: 所有运行耗时结果中最小值 -- `Max`: 所有运行耗时结果中最大值 -- `StdDev`: 所有运行耗时结果的标准方差 -- `95% LowerConfidence`: 95%置信区间的最小边界值 -- `95% LowerConfidence`: 95%置信区间的最大边界值 - -#### 3. 主要方法 - -- `void run(Runnable... runnables)`: 对若干个要执行的代码做性能测量评估. -- `void run(Options options, Runnable... runnables)`: 通过自定义的`Options`对若干个要执行的代码做性能测量评估. - -### Options类 - -Options 表示做性能测量时的选项参数 - -#### 主要属性如下 - -- `name`: 选项参数的名称 -- `threads`: 正式执行的线程数,默认为 `1`。 -- `concurrens`: 正式多线程下执行的并发数,默认为 `1`。 -- `warmups`: 单线程下的预热次数,默认 `5`。 -- `runs`: 每个线程正式执行的次数,默认 `10`。 -- `printErrorLog`: 是否打印错误日志,默认 `false`。 -- `outputs`: 将测量结果通过多种方式(集合)输出出来,默认为输出到控制台,可自定义实现 `MeasureOutput` 接口。 - -#### 主要方法 - -以下是构造`Options`实例的若干重载方法: - -- `Options of(String name)` -- `Options of(int runs)` -- `Options of(String name, int runs)` -- `Options of(int threads, int concurrens)` -- `Options of(String name, int threads, int concurrens)` -- `Options of(String name, int threads, int concurrens, int runs)` - -其他方法: - -- `boolean valid()`: 校验 Options 相关参数是否合法 -- `Options named(String name)`: 设置 Options 实例的 name 属性 -- `Options threads(int threads)`: 设置 Options 实例的 threads 属性 -- `Options concurrens(int concurrens)`: 设置 Options 实例的 concurrens 属性 -- `Options warmups(int warmups)`: 设置 Options 实例的 warmups 属性 -- `Options runs(int runs)`: 设置 Options 实例的 runs 属性 -- `Options printErrorLog(boolean printErrorLog)`: 设置 Options 实例的 printErrorLog 属性 -- `Options outputs(MeasureOutput... measureOutputs)`: 自定义设置 Options 实例的 MeasureOutput 输出通道 - -### Assert类 - -Assert类主要用来做断言使用。 - -#### 示例 - -```java -Assert.assertFaster(Options.of(), - () -> new MyTestService().fastHello(), - () -> new MyTestService().hello()); -``` - -## 许可证 - -本 [stalker](https://github.com/blinkfox/stalker) 类库遵守 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 许可证。 - -## 变更日志 - -- v1.1.1 新增了吞吐量的统计指标 (2020-05-20) - - 新增了吞吐量的统计指标; -- v1.1.0 新增了运行后可以获取返回结果的功能 (2020-05-14) - - 新增了 `MeasureOutput` 中可输出结果的功能,且默认的 run 方法,也会返回其结果; - - 新增了 `runStatis` 方法,可以拿到原始的统计结果数据; -- v1.0.1 修复线程创建过多时的限制问题 (2019-09-14) - - 修复了线程池超过一定数量后的线程创建失败的问题; -- v1.0.0 里程碑正式版 (2019-02-08) - - 完成了基准性能测试所需的基础功能; diff --git a/pom.xml b/pom.xml index 3a63d8b..e41ea09 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.blinkfox stalker - 1.1.1 + 1.2.0 jar stalker diff --git a/src/main/java/com/blinkfox/stalker/Assert.java b/src/main/java/com/blinkfox/stalker/Assert.java index 4c06299..c507e28 100644 --- a/src/main/java/com/blinkfox/stalker/Assert.java +++ b/src/main/java/com/blinkfox/stalker/Assert.java @@ -29,26 +29,9 @@ public void fail() { */ public void assertFaster(Options options, Runnable runnable1, Runnable runnable2) { MeasureRunnerContext runnerContext = new MeasureRunnerContext(options); - if (calcSum(runnerContext.run(runnable1).getEachMeasures()) - >= calcSum(runnerContext.run(runnable2).getEachMeasures())) { + if (runnerContext.run(runnable1).getSum() >= runnerContext.run(runnable2).getSum()) { fail(); } } - /** - * 计算long数组中的总和. - * - * @param arr long数组 - * @return 总和 - */ - private long calcSum(long[] arr) { - int sum = 0; - if (arr != null && arr.length > 0) { - for (long n : arr) { - sum += n; - } - } - return sum; - } - } diff --git a/src/main/java/com/blinkfox/stalker/Stalker.java b/src/main/java/com/blinkfox/stalker/Stalker.java index b00bf70..66c3fd1 100644 --- a/src/main/java/com/blinkfox/stalker/Stalker.java +++ b/src/main/java/com/blinkfox/stalker/Stalker.java @@ -2,8 +2,8 @@ import com.blinkfox.stalker.config.Options; import com.blinkfox.stalker.output.MeasureOutputContext; -import com.blinkfox.stalker.result.MeasurementCollector; -import com.blinkfox.stalker.result.bean.Measurement; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.result.StalkerFuture; import com.blinkfox.stalker.runner.MeasureRunnerContext; import java.util.List; import lombok.experimental.UtilityClass; @@ -17,6 +17,40 @@ @UtilityClass public class Stalker { + /** + * 使用默认选项参数来提交可运行的异步测量任务,并立即返回此次异步任务的 {@link StalkerFuture} 实例. + * + * @param task 任务 + * @return {@link StalkerFuture} 对象实例 + * @author blinkfox on 2020-06-03 + * @since v1.2.0 + */ + public StalkerFuture submit(Runnable task) { + return submit(Options.of(), task); + } + + /** + * 提交可运行的异步测量任务,并立即返回此次异步任务的 {@link StalkerFuture} 实例. + * + *

异步提交任务时,将默认额外开启定时更新统计数据的定时任务.

+ * + * @param options 选项参数 + * @param task 任务 + * @return {@link StalkerFuture} 对象实例 + * @author blinkfox on 2020-06-03 + * @since v1.2.0 + */ + public StalkerFuture submit(Options options, Runnable task) { + if (options == null || task == null) { + throw new IllegalArgumentException("options or runnables is null (or empty)!"); + } + options.valid(); + + // 异步提交任务时,将默认额外开启定时更新统计数据的定时任务. + options.enableScheduledUpdater(); + return MeasureRunnerContext.submit(options, task); + } + /** * 测量要执行的代码的性能评估. * @@ -42,21 +76,21 @@ public List run(Options options, Runnable... runnables) { * 测量要执行的各个代码的性能并输出统计数据的结果数组. * * @param options 参数选项 - * @param runnables runnable + * @param runnables 可运行的任务 * @return 各个运行结果统计数据的数组 * @author blinkfox on 2020-05-14 * @since v1.1.0 */ - public Measurement[] runStatis(Options options, Runnable... runnables) { + public MeasureResult[] runStatis(Options options, Runnable... runnables) { int len; if (options == null || runnables == null || (len = runnables.length) == 0) { - throw new IllegalArgumentException("options or runnables is null (or empty)!"); + throw new IllegalArgumentException("【Stalker 参数异常】options or runnables is null (or empty)!"); } // 循环遍历测量各个 Runnable 实例的性能结果,然后将各个结果存放到数组中,最后统一输出出来. - Measurement[] measurements = new Measurement[len]; + MeasureResult[] measurements = new MeasureResult[len]; for (int i = 0; i < len; i++) { - measurements[i] = new MeasurementCollector().collect(new MeasureRunnerContext(options).run(runnables[i])); + measurements[i] = new MeasureRunnerContext(options).run(runnables[i]); } return measurements; } diff --git a/src/main/java/com/blinkfox/stalker/config/Options.java b/src/main/java/com/blinkfox/stalker/config/Options.java index b2c49a5..4a694fa 100644 --- a/src/main/java/com/blinkfox/stalker/config/Options.java +++ b/src/main/java/com/blinkfox/stalker/config/Options.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; import lombok.Getter; /** @@ -40,6 +41,13 @@ public class Options { */ private int runs; + /** + * 运行的持续时间. + * + * @since v1.2.0 + */ + private RunDuration duration; + /** * 是否打印出执行错误(异常运行)的日志,默认是false. */ @@ -55,6 +63,11 @@ public class Options { */ private String message; + /** + * 用于定时更新统计数据的定时更新器,通常在调用 {@code Stalker.submit} 的异步执行任务时才设置并开启此配置项,默认是空值. + */ + private ScheduledUpdater scheduledUpdater; + /** * 根据'执行次数'来构建Options实例. * @@ -120,6 +133,7 @@ public static Options of(int threads, int concurrens) { Options options = of(); options.threads = threads; options.concurrens = concurrens; + options.runs = 1; return options; } @@ -152,6 +166,92 @@ public static Options of(String name, int threads, int concurrens, int runs) { return options; } + /** + * 根据'持续时间的量'、'持续时间的单位'来构建 Options 实例. + * + * @param amount 运行持续时间的量 + * @param timeUnit 运行持续时间的单位 + * @return Options实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public static Options ofDuration(long amount, TimeUnit timeUnit) { + Options options = of(1, 1); + options.runs = 1; + options.duration = RunDuration.of(amount, timeUnit); + return options; + } + + /** + * 根据'持续时间的量'、'持续时间的单位'和'并发数'来构建 Options 实例. + * + * @param amount 运行持续时间的量 + * @param timeUnit 运行持续时间的单位 + * @param concurrens 并发数 + * @return Options实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public static Options ofDuration(long amount, TimeUnit timeUnit, int concurrens) { + Options options = of(1, 1); + options.concurrens = concurrens; + options.runs = 1; + options.duration = RunDuration.of(amount, timeUnit); + return options; + } + + /** + * 根据'持续秒数的量'和'并发数'来构建 Options 实例. + * + * @param amount 运行持续秒数的量 + * @param concurrens 并发数 + * @return Options实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public static Options ofDurationSeconds(long amount, int concurrens) { + return ofDuration(amount, TimeUnit.SECONDS, concurrens); + } + + /** + * 根据'持续分钟数的量'和'并发数'来构建 Options 实例. + * + * @param amount 运行持续分钟数的量 + * @param concurrens 并发数 + * @return Options实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public static Options ofDurationMinutes(long amount, int concurrens) { + return ofDuration(amount, TimeUnit.MINUTES, concurrens); + } + + /** + * 根据'持续小时数的量'和'并发数'来构建 Options 实例. + * + * @param amount 运行持续小时数的量 + * @param concurrens 并发数 + * @return Options实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public static Options ofDurationHours(long amount, int concurrens) { + return ofDuration(amount, TimeUnit.HOURS, concurrens); + } + + /** + * 根据'持续天数的量'和'并发数'来构建 Options 实例. + * + * @param amount 运行持续小时数的量 + * @param concurrens 并发数 + * @return Options实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public static Options ofDurationDays(long amount, int concurrens) { + return ofDuration(amount, TimeUnit.DAYS, concurrens); + } + /** * 校验需要进行测量的 Options 选项参数是否合法,如果不合法,则抛出异常. */ @@ -227,13 +327,27 @@ public Options warmups(int warmups) { * 设置执行次数的 runs 的属性值. * * @param runs 运行次数 - * @return Options实例 + * @return Options 实例 */ public Options runs(int runs) { this.runs = runs; return this; } + /** + * 设置运行的持续时间. + * + * @param amount 持续时间的量 + * @param timeUnit 持续时间的单位 + * @return Options 实例 + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ + public Options duration(long amount, TimeUnit timeUnit) { + this.duration = RunDuration.of(amount, timeUnit); + return this; + } + /** * 设置是否打印运行错误的日志的 printErrorLog 的属性值. * @@ -271,4 +385,41 @@ public Options outputs(List outputs) { return this; } + /** + * 设置默认的定时统计数据更新任务的配置选项,默认是 10 秒. + * + * @return 本 {@link Options} 实例 + */ + public Options enableScheduledUpdater() { + ScheduledUpdater updater = StalkerConfigManager.getInstance().getDefaultScheduledUpdater(); + this.scheduledUpdater = ScheduledUpdater.of(true, updater.getInitialDelay(), + updater.getDelay(), updater.getTimeUnit()); + return this; + } + + /** + * 设置默认的定时统计数据更新任务的配置选项. + * + * @param delay 时间间隔 + * @param timeUnit 时间单位 + * @return 本 {@link Options} 实例 + */ + public Options enableScheduledUpdater(long delay, TimeUnit timeUnit) { + this.scheduledUpdater = ScheduledUpdater.of(delay, timeUnit); + return this; + } + + /** + * 设置默认的定时统计数据更新任务的配置选项. + * + * @param initialDelay 第一次的延迟执行时间 + * @param delay 时间间隔 + * @param timeUnit 时间单位 + * @return 本 {@link Options} 实例 + */ + public Options enableScheduledUpdater(long initialDelay, long delay, TimeUnit timeUnit) { + this.scheduledUpdater = ScheduledUpdater.of(true, initialDelay, delay, timeUnit); + return this; + } + } diff --git a/src/main/java/com/blinkfox/stalker/config/RunDuration.java b/src/main/java/com/blinkfox/stalker/config/RunDuration.java new file mode 100644 index 0000000..49c068e --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/config/RunDuration.java @@ -0,0 +1,150 @@ +package com.blinkfox.stalker.config; + +import com.blinkfox.stalker.kit.StrKit; +import java.util.concurrent.TimeUnit; +import lombok.Getter; + +/** + * 程序运行的持续时间实体类. + * + * @author blinkfox on 2020-05-31. + * @since v1.2.0 + */ +public class RunDuration { + + /** + * 运行的持续时间量. + */ + @Getter + private final long amount; + + /** + * 运行的持续时间单位. + */ + @Getter + private final TimeUnit timeUnit; + + /** + * 构造方法. + * + * @param amount 持续时间的量 + * @param timeUnit 持续时间的单位 + */ + private RunDuration(long amount, TimeUnit timeUnit) { + this.amount = amount; + this.timeUnit = timeUnit; + } + + /** + * 构造运行持续时间的 {@link RunDuration} 实例. + * + * @param amount 持续时间的量 + * @param timeUnit 持续时间的单位 + * @return {@link RunDuration} 实例 + */ + public static RunDuration of(long amount, TimeUnit timeUnit) { + checkParams(amount, timeUnit); + return new RunDuration(amount, timeUnit); + } + + /** + * 构造运行持续指定【秒数】的 {@link RunDuration} 实例. + * + * @param amount 持续时间的量 + * @return {@link RunDuration} 实例 + */ + public static RunDuration ofSeconds(long amount) { + checkAmount(amount); + return new RunDuration(amount, TimeUnit.SECONDS); + } + + /** + * 构造运行持续指定【分钟数】的 {@link RunDuration} 实例. + * + * @param amount 持续时间的量 + * @return {@link RunDuration} 实例 + */ + public static RunDuration ofMinutes(long amount) { + checkAmount(amount); + return new RunDuration(amount, TimeUnit.MINUTES); + } + + /** + * 构造运行持续指定【小时数】的 {@link RunDuration} 实例. + * + * @param amount 持续时间的量 + * @return {@link RunDuration} 实例 + */ + public static RunDuration ofHours(long amount) { + checkAmount(amount); + return new RunDuration(amount, TimeUnit.HOURS); + } + + /** + * 构造运行持续指定【天数】的 {@link RunDuration} 实例. + * + * @param amount 持续时间的量 + * @return {@link RunDuration} 实例 + */ + public static RunDuration ofDays(long amount) { + checkAmount(amount); + return new RunDuration(amount, TimeUnit.DAYS); + } + + /** + * 根据开始纳秒时间和持续时间计算出期望的结束纳秒时间. + * + * @param startNanoTime 开始纳秒时间 + * @return 结束纳秒时间 + */ + public long getEndNanoTime(long startNanoTime) { + switch (this.timeUnit) { + case NANOSECONDS : + return startNanoTime + amount; + case MICROSECONDS : + return startNanoTime + amount * 1000L; + case MILLISECONDS : + return startNanoTime + amount * 1000_000L; + case SECONDS: + return startNanoTime + amount * 1000_000_000L; + case MINUTES: + return startNanoTime + amount * 60_000_000_000L; + case HOURS: + return startNanoTime + amount * 3600_000_000_000L; + case DAYS: + return startNanoTime + amount * 86400_000_000_000L; + default : + return startNanoTime; + } + } + + private static void checkParams(long amount, TimeUnit timeUnit) { + checkAmount(amount); + if (timeUnit == null) { + throw new IllegalArgumentException("【Stalker 无效参数异常】运行的最小持续时间单位不能为空【null】."); + } + if (timeUnit == TimeUnit.NANOSECONDS + || timeUnit == TimeUnit.MICROSECONDS + || timeUnit == TimeUnit.MILLISECONDS) { + throw new IllegalArgumentException("【Stalker 无效参数异常】运行的最小持续时间单位至少是【秒】," + + "不能是【纳秒】、【微秒】或者【毫秒】,获取到的值是:【" + timeUnit.name() + "】."); + } + } + + private static void checkAmount(long amount) { + if (amount <= 0) { + throw new IllegalArgumentException("【Stalker 无效参数异常】运行的续时间必须是正整数,获取到的值是:【" + amount + "】."); + } + } + + /** + * 将对象转换为字符串. + * + * @return 字符串 + */ + @Override + public String toString() { + return StrKit.convertTimeUnit(this.amount, this.timeUnit); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/config/ScheduledUpdater.java b/src/main/java/com/blinkfox/stalker/config/ScheduledUpdater.java new file mode 100644 index 0000000..39e4449 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/config/ScheduledUpdater.java @@ -0,0 +1,176 @@ +package com.blinkfox.stalker.config; + +import java.util.concurrent.TimeUnit; +import lombok.Getter; + +/** + * 用于定时更新统计数据的配置类. + * + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ +@Getter +public class ScheduledUpdater { + + /** + * 是否启用定时更新统计数据的任务,默认为 {@code false}. + */ + private boolean enabled; + + /** + * 初始化延迟执行的时间间隔. + */ + private final long initialDelay; + + /** + * 每次执行的时间间隔. + */ + private final long delay; + + /** + * 时间间隔的单位. + */ + private final TimeUnit timeUnit; + + /** + * 私有默认构造方法. + * + * @param enabled 是否启用 + * @param initialDelay 初始延迟执行时间 + * @param delay 定时执行的时间间隔 + * @param timeUnit 时间单位 + */ + private ScheduledUpdater(boolean enabled, long initialDelay, long delay, TimeUnit timeUnit) { + this.enabled = enabled; + this.initialDelay = initialDelay; + this.delay = delay; + this.timeUnit = timeUnit; + } + + /** + * 通过 4 个参数构造 {@link ScheduledUpdater} 实例的构造方法. + * + * @param enabled 是否启用 + * @param initialDelay 初始延迟执行时间 + * @param delay 定时执行的时间间隔 + * @param timeUnit 时间单位 + * @return {@link ScheduledUpdater} 实例 + */ + public static ScheduledUpdater of(boolean enabled, long initialDelay, long delay, TimeUnit timeUnit) { + checkDelay(initialDelay); + checkParams(delay, timeUnit); + return new ScheduledUpdater(enabled, initialDelay, delay, timeUnit); + } + + /** + * 构造 {@link ScheduledUpdater} 实例的构造方法. + * + * @param delay 时间间隔数据,延迟时间的值也跟这个值默认保持一致 + * @param timeUnit 时间单位 + * @return {@link ScheduledUpdater} 实例 + */ + public static ScheduledUpdater of(long delay, TimeUnit timeUnit) { + checkParams(delay, timeUnit); + return new ScheduledUpdater(true, delay, delay, timeUnit); + } + + /** + * 根据时间间隔参数构和默认"秒"作为参数来构造 {@link ScheduledUpdater} 实例的构造方法. + * + * @param delay 时间间隔数据,延迟时间的值也跟这个值默认保持一致 + * @return {@link ScheduledUpdater} 实例 + */ + public static ScheduledUpdater ofSeconds(long delay) { + checkDelay(delay); + return new ScheduledUpdater(true, delay, delay, TimeUnit.SECONDS); + } + + /** + * 根据时间间隔参数构和默认"分钟"作为参数来构造 {@link ScheduledUpdater} 实例的构造方法. + * + * @param delay 时间间隔数据,延迟时间的值也跟这个值默认保持一致 + * @return {@link ScheduledUpdater} 实例 + */ + public static ScheduledUpdater ofMinutes(long delay) { + checkDelay(delay); + return new ScheduledUpdater(true, delay, delay, TimeUnit.MINUTES); + } + + /** + * 构造启用和其他默认参数 {@link ScheduledUpdater} 实例的构造方法. + * + * @return {@link ScheduledUpdater} 实例 + */ + public static ScheduledUpdater ofEnable() { + return new ScheduledUpdater(true, 5, 5, TimeUnit.SECONDS); + } + + /** + * 构造禁用和其他默认参数 {@link ScheduledUpdater} 实例的构造方法. + * + * @return {@link ScheduledUpdater} 实例 + */ + public static ScheduledUpdater ofDisable() { + return new ScheduledUpdater(false, 5, 5, TimeUnit.SECONDS); + } + + /** + * 启用定时任务. + * + * @return 当前 {@link ScheduledUpdater} 实例对象 + */ + public ScheduledUpdater enable() { + this.enabled = true; + return this; + } + + /** + * 禁用定时任务. + * + * @return 当前 {@link ScheduledUpdater} 实例对象 + */ + public ScheduledUpdater disable() { + this.enabled = false; + return this; + } + + /** + * 校验属性信息是否合法. + */ + public void valid() { + checkDelay(initialDelay); + checkParams(delay, timeUnit); + } + + /** + * 检查延迟时间或时间间隔参数是否合法. + * + * @param delay 时间间隔 + */ + private static void checkDelay(long delay) { + if (delay <= 0) { + throw new IllegalArgumentException("【Stalker 无效参数异常】延迟更新统计数据的时间必须大于 0,获取到的值是:【" + delay + "】."); + } + } + + /** + * 检查时间间隔或时间单位参数是否合法. + * + * @param delay 时间间隔 + * @param timeUnit 时间单位 + */ + private static void checkParams(long delay, TimeUnit timeUnit) { + checkDelay(delay); + if (timeUnit == null) { + throw new IllegalArgumentException("【Stalker 无效参数异常】运行的时间间隔的单位不能为空【null】."); + } + + if (timeUnit == TimeUnit.NANOSECONDS + || timeUnit == TimeUnit.MICROSECONDS + || timeUnit == TimeUnit.MILLISECONDS) { + throw new IllegalArgumentException("【Stalker 无效参数异常】运行的时间间隔的单位至少是【秒】," + + "不能是【纳秒】、【微秒】或者【毫秒】,获取到的值是:【" + timeUnit.name() + "】."); + } + } + +} diff --git a/src/main/java/com/blinkfox/stalker/config/StalkerConfigManager.java b/src/main/java/com/blinkfox/stalker/config/StalkerConfigManager.java index da15085..5960184 100644 --- a/src/main/java/com/blinkfox/stalker/config/StalkerConfigManager.java +++ b/src/main/java/com/blinkfox/stalker/config/StalkerConfigManager.java @@ -1,6 +1,7 @@ package com.blinkfox.stalker.config; import com.blinkfox.stalker.output.OutputConsole; +import lombok.Getter; /** * Stalker 单例的全局配置管理类. @@ -18,8 +19,15 @@ public final class StalkerConfigManager { /** * 全局默认的选项参数. */ + @Getter private Options defaultOptions; + /** + * 全局默认的定时更新统计数据的相关参数. + */ + @Getter + private ScheduledUpdater defaultScheduledUpdater; + /** * 私有构造方法,构造默认的选项参数数据. */ @@ -31,6 +39,8 @@ private StalkerConfigManager() { .runs(10) .printErrorLog(false) .outputs(new OutputConsole()); + + this.defaultScheduledUpdater = ScheduledUpdater.ofSeconds(10).disable(); } /** @@ -43,12 +53,14 @@ public static StalkerConfigManager getInstance() { } /** - * 获取默认的选项参数. + * 重新加载指定的选项参数 options 对象作为默认的 options 对象. + *

重载传入的options前,对options的参数合法性做校验.

* - * @return 选项参数 + * @param options 选项参数 */ - public Options getDefaultOptions() { - return this.defaultOptions; + public synchronized void reLoadOptions(Options options) { + options.valid(); + this.defaultOptions = options; } /** @@ -56,10 +68,13 @@ public Options getDefaultOptions() { *

重载传入的options前,对options的参数合法性做校验.

* * @param options 选项参数 + * @param scheduledUpdater 用于定时更新测量的统计数据更新器配置参数 */ - public synchronized void reLoadOptions(Options options) { + public synchronized void reLoadOptions(Options options, ScheduledUpdater scheduledUpdater) { options.valid(); + scheduledUpdater.valid(); this.defaultOptions = options; + this.defaultScheduledUpdater = scheduledUpdater; } } diff --git a/src/main/java/com/blinkfox/stalker/exception/StalkerException.java b/src/main/java/com/blinkfox/stalker/exception/StalkerException.java new file mode 100644 index 0000000..494f2c4 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/exception/StalkerException.java @@ -0,0 +1,32 @@ +package com.blinkfox.stalker.exception; + +/** + * Stalker 运行过程中相关的运行时异常. + * + * @author blinkfox on 2020-05-23. + * @since v1.2.0 + */ +public class StalkerException extends RuntimeException { + + private static final long serialVersionUID = 8452522047738865868L; + + /** + * 异常实例的构造方法. + * + * @param s 异常描述信息 + */ + public StalkerException(String s) { + super(s); + } + + /** + * 附带异常实例的构造方法. + * + * @param s 异常描述信息 + * @param e 异常实例 + */ + public StalkerException(String s, Throwable e) { + super(s, e); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/kit/ConcurrentHashSet.java b/src/main/java/com/blinkfox/stalker/kit/ConcurrentHashSet.java new file mode 100644 index 0000000..d0577f3 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/kit/ConcurrentHashSet.java @@ -0,0 +1,114 @@ +package com.blinkfox.stalker.kit; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 支持高并发操作的 HashSet 集合,内部使用 {@link ConcurrentHashMap} 实现. + * + * @author blinkfox on 2020-05-30. + * @since v1.2.0 + */ +public class ConcurrentHashSet extends AbstractSet implements Set, java.io.Serializable { + + private static final long serialVersionUID = 8473385025738451022L; + + /** + * 恒定的不可变对象,每个 key 都引用此对象,以节省空间. + */ + private static final Object PRESENT = new Object(); + + private final ConcurrentMap map; + + /** + * 默认构造方法. + */ + public ConcurrentHashSet() { + this.map = new ConcurrentHashMap<>(); + } + + /** + * 基于初始容量的构造方法. + * + * @param capacity 初始容量 + */ + public ConcurrentHashSet(int capacity) { + this.map = new ConcurrentHashMap<>(capacity); + } + + /** + * 返回此集合中元素的迭代器. + * + * @return 迭代器 + * @see java.util.ConcurrentModificationException + */ + @Override + public Iterator iterator() { + return this.map.keySet().iterator(); + } + + /** + * 返回此集合中的元素个数. + * + * @return 元素个数 + */ + @Override + public int size() { + return this.map.size(); + } + + /** + * 判断此集合是否为空. + * + * @return 如果为空就返回 {@code true} + */ + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + /** + * 判断此集合中是否包含指定的元素. + * + * @param o 元素 + * @return 布尔值 + */ + @Override + public boolean contains(Object o) { + return this.map.containsKey(o); + } + + /** + * 如果此元素在集合中不存在,就向此集合中添加元素. + * + * @param e 要添加的元素 + * @return 如果此集合中还没有包含此元素就返回 {@code true} + */ + @Override + public boolean add(E e) { + return this.map.put(e, PRESENT) == null; + } + + /** + * 如果此集合中包含某个元素,就将该元素从集合中移除. + * + * @param o 要移除的对象 + * @return 如果包含了该元素,就返回 {@code true}. + */ + @Override + public boolean remove(Object o) { + return this.map.remove(o) == PRESENT; + } + + /** + * 删除此集合中的所有元素,之后集合将为空. + */ + @Override + public void clear() { + this.map.clear(); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/kit/MathKit.java b/src/main/java/com/blinkfox/stalker/kit/MathKit.java index c6ce8dc..74503de 100644 --- a/src/main/java/com/blinkfox/stalker/kit/MathKit.java +++ b/src/main/java/com/blinkfox/stalker/kit/MathKit.java @@ -6,7 +6,7 @@ * 数学计算的相关工具类. * * @author blinkfox on 2020-05-15. - * @since v1.0.0 + * @since v1.1.1 */ @UtilityClass public class MathKit { @@ -21,7 +21,7 @@ public class MathKit { * @return 吞吐率 */ public double calcThroughput(long count, long costs) { - return count / ((double) costs / 1e9); + return costs == 0 ? 0.0d : count / ((double) costs / 1e9); } } diff --git a/src/main/java/com/blinkfox/stalker/kit/RadixKit.java b/src/main/java/com/blinkfox/stalker/kit/RadixKit.java new file mode 100644 index 0000000..39704a9 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/kit/RadixKit.java @@ -0,0 +1,86 @@ +package com.blinkfox.stalker.kit; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * 进制处理工具类. + * + * @author blinkfox on 2020-05-27. + * @since v1.2.0 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class RadixKit { + + /** + * 62 进制数需要的 char 数组字符表, + * 为了保证生成的各个进制数的字符串保证 ASCII 的大小顺序,Radix 类生成的进制顺序是先数字,大写字母,再小写字母的顺序. + */ + private static final char[] DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; + + /** + * 支持的最大进制数. + */ + static final int RADIX_62 = DIGITS.length; + + /** + * 默认的 10 进制常量. + */ + private static final int RADIX_10 = 10; + + /** + * 将长整型数值转换为指定的进制数(最大支持 62 进制,字母数字已经用尽). + * + * @param i 待转换数字 + * @param radix 进制数 + * @return 转换后的字符串 + */ + public static String toString(long i, int radix) { + if (radix < Character.MIN_RADIX || radix > RADIX_62) { + radix = RADIX_10; + } + + if (radix == RADIX_10) { + return Long.toString(i); + } + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + final int size = 65; + int charPos = 64; + char[] buf = new char[size]; + + while (i <= -radix) { + buf[charPos--] = DIGITS[(int) (-(i % radix))]; + i = i / radix; + } + buf[charPos] = DIGITS[(int) (-i)]; + + if (negative) { + buf[--charPos] = '-'; + } + + return new String(buf, charPos, (size - charPos)); + } + + /** + * 根据对应的整数和移位数求得对应的字符串值. + * + * @param val 整数 + * @param digits 移位数 + * @return 字符串值 + */ + public static String digits(long val, int digits) { + long hi = 1L << (digits * 4); + return toString(hi | (val & (hi - 1)), RADIX_62).substring(1); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/kit/StrKit.java b/src/main/java/com/blinkfox/stalker/kit/StrKit.java index 9058ac9..bee1153 100644 --- a/src/main/java/com/blinkfox/stalker/kit/StrKit.java +++ b/src/main/java/com/blinkfox/stalker/kit/StrKit.java @@ -2,7 +2,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import lombok.experimental.UtilityClass; +import org.slf4j.helpers.MessageFormatter; /** * 字符串操作工具类. @@ -28,6 +31,8 @@ public boolean isEmpty(String s) { * * @param objects 不定参数对象 * @return 字符串 + * @author blinkfox on 2020-05-23. + * @since v1.2.0 */ public String join(Object... objects) { if (objects != null && objects.length > 0) { @@ -37,10 +42,20 @@ public String join(Object... objects) { } return sb.toString(); } - return ""; } + /** + * 使用 Slf4j 中的字符串格式化方式来格式化字符串. + * + * @param pattern 待格式化的字符串 + * @param args 参数 + * @return 格式化后的字符串 + */ + public String format(String pattern, Object... args) { + return pattern == null ? "" : MessageFormatter.arrayFormat(pattern, args).getMessage(); + } + /** * 将纳秒的时间转为与其最贴近的时间单位. * @@ -64,6 +79,34 @@ public String convertTime(Number n) { } } + /** + * 将纳秒的时间转为与其最贴近的时间单位. + * + * @param amount 数量 + * @param timeUnit 时间单位 + * @return 其他单位的时间字符串 + */ + public String convertTimeUnit(long amount, TimeUnit timeUnit) { + switch (timeUnit) { + case NANOSECONDS : + return amount + " ns"; + case MICROSECONDS : + return amount + " μs"; + case MILLISECONDS : + return amount + " ms"; + case SECONDS: + return amount + " s"; + case MINUTES: + return amount + " m"; + case HOURS: + return amount + " h"; + case DAYS: + return amount + " d"; + default : + return String.valueOf(amount); + } + } + /** * 根据double的值和单位,拼接其对应的四舍五入的字符串值. * @@ -85,4 +128,20 @@ public String roundToString(double d) { return BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_UP).toString(); } + /** + * 获取 {@code 62} 进制的长度为 19 位长度的 {@code UUID} 字符串. + * + * @return {@code 62} 进制位的 {@code UUID} + * @author blinkfox on 2020-05-27. + * @since v1.2.0 + */ + public String get62RadixUuid() { + UUID uuid = UUID.randomUUID(); + return join(RadixKit.digits(uuid.getMostSignificantBits() >> 32, 8), + RadixKit.digits(uuid.getMostSignificantBits() >> 16, 4), + RadixKit.digits(uuid.getMostSignificantBits(), 4), + RadixKit.digits(uuid.getLeastSignificantBits() >> 48, 4), + RadixKit.digits(uuid.getLeastSignificantBits(), 12)); + } + } \ No newline at end of file diff --git a/src/main/java/com/blinkfox/stalker/output/MeasureOutput.java b/src/main/java/com/blinkfox/stalker/output/MeasureOutput.java index 48ee20b..2d7816d 100644 --- a/src/main/java/com/blinkfox/stalker/output/MeasureOutput.java +++ b/src/main/java/com/blinkfox/stalker/output/MeasureOutput.java @@ -1,7 +1,7 @@ package com.blinkfox.stalker.output; import com.blinkfox.stalker.config.Options; -import com.blinkfox.stalker.result.bean.Measurement; +import com.blinkfox.stalker.result.MeasureResult; /** * 将最终的测量统计结果输出出来. @@ -15,9 +15,9 @@ public interface MeasureOutput { * 将测量的相关参数和统计结果等信息输出出来. * * @param options 测量的选项参数 - * @param measurements 多种测量结果 + * @param measureResults 多个测量统计结果的不定集合 * @return 输出结果 */ - Object output(Options options, Measurement... measurements); + Object output(Options options, MeasureResult... measureResults); } diff --git a/src/main/java/com/blinkfox/stalker/output/MeasureOutputContext.java b/src/main/java/com/blinkfox/stalker/output/MeasureOutputContext.java index 6c79eac..184c91e 100644 --- a/src/main/java/com/blinkfox/stalker/output/MeasureOutputContext.java +++ b/src/main/java/com/blinkfox/stalker/output/MeasureOutputContext.java @@ -1,7 +1,7 @@ package com.blinkfox.stalker.output; import com.blinkfox.stalker.config.Options; -import com.blinkfox.stalker.result.bean.Measurement; +import com.blinkfox.stalker.result.MeasureResult; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,10 +20,10 @@ public final class MeasureOutputContext { * 将测量的相关参数和统计结果等信息输出出来. * * @param options 测量的选项参数 - * @param measurements 多种测量结果 + * @param measureResults 多个测量统计结果的不定集合 * @return 各个输出结果的集合,v1.1.0 新增的返回结果 */ - public List output(Options options, Measurement... measurements) { + public List output(Options options, MeasureResult... measureResults) { // 如果没有指定任何输出形式,则默认将结果输出到控制台中. List outputs = options.getOutputs(); if (outputs == null || outputs.isEmpty()) { @@ -33,7 +33,7 @@ public List output(Options options, Measurement... measurements) { // 如果有多种输出形式,就遍历得到结果,并将各个结果存入到 List 集合中. List results = new ArrayList<>(); - outputs.forEach(measureOutput -> results.add(measureOutput.output(options, measurements))); + outputs.forEach(measureOutput -> results.add(measureOutput.output(options, measureResults))); return results; } diff --git a/src/main/java/com/blinkfox/stalker/output/OutputConsole.java b/src/main/java/com/blinkfox/stalker/output/OutputConsole.java index daf3848..60e2407 100644 --- a/src/main/java/com/blinkfox/stalker/output/OutputConsole.java +++ b/src/main/java/com/blinkfox/stalker/output/OutputConsole.java @@ -3,8 +3,7 @@ import com.blinkfox.minitable.MiniTable; import com.blinkfox.stalker.config.Options; import com.blinkfox.stalker.kit.StrKit; -import com.blinkfox.stalker.result.bean.EasyReadResult; -import com.blinkfox.stalker.result.bean.Measurement; +import com.blinkfox.stalker.result.MeasureResult; import java.util.Arrays; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -37,12 +36,12 @@ public class OutputConsole implements MeasureOutput { * 将测量的相关参数和统计结果等信息输出出来. * * @param options 测量的选项参数 - * @param measurements 多种测量结果 + * @param measureResults 多个测量统计结果的不定集合 */ @Override - public Object output(Options options, Measurement... measurements) { + public Object output(Options options, MeasureResult... measureResults) { // 渲染并打印结果. - String result = this.getRenderResult(options, measurements); + String result = this.getRenderResult(options, measureResults); log.warn("\n{}", result); return result; } @@ -50,30 +49,39 @@ public Object output(Options options, Measurement... measurements) { /** * 获取最终需要渲染的结果. * + * @param options 测量运行的相关选项参数 + * @param measureResults 多个测量统计结果的不定集合 * @return 结果字符串 */ - private String getRenderResult(Options options, Measurement... measurements) { - if (options == null || measurements == null) { - throw new IllegalArgumentException("options or measureStatisResult is null."); + private String getRenderResult(Options options, MeasureResult... measureResults) { + if (options == null || measureResults == null) { + throw new IllegalArgumentException("options or measureResult is null."); } // 根据options的值, 拼接title. - String title = StrKit.join("threads: ", options.getThreads(), - ", concurrens: ", options.getConcurrens(), ", warmups:", options.getWarmups(), - ", runs: ", options.getRuns(), ", printErrorLog: ", options.isPrintErrorLog()); + String title; + if (options.getDuration() != null) { + title = StrKit.join("duration: ", options.getDuration().toString(), + ", concurrens: ", options.getConcurrens(), ", warmups:", options.getWarmups(), + ", runs: ", options.getRuns(), ", printErrorLog: ", options.isPrintErrorLog()); + } else { + title = StrKit.join("threads: ", options.getThreads(), + ", concurrens: ", options.getConcurrens(), ", warmups:", options.getWarmups(), + ", runs: ", options.getRuns(), ", printErrorLog: ", options.isPrintErrorLog()); + } String name = options.getName(); title = StrKit.isEmpty(name) ? title : StrKit.join("name: ", name, ", ", title); // 拼接各个测量结果的字符串表头和内容. MiniTable table = new MiniTable(title).addHeaders(HEADERS); - for (int i = 0, len = measurements.length; i < len; i++) { - EasyReadResult result = measurements[i].getEasyReadResult(); - table.addDatas(i + 1, - result.getCosts(), result.getTotal(), result.getSuccess(), result.getFailure(), - result.getThroughput(), result.getSum(), result.getAvg(), result.getMin(), result.getMax(), - result.getStdDev(), result.getLowerConfidence(), result.getUpperConfidence()); + for (int i = 0, len = measureResults.length; i < len; i++) { + MeasureResult result = measureResults[i]; + table.addDatas(i + 1, result.getEasyReadCosts(), + result.getTotal(), result.getSuccess(), result.getFailure(), result.getEasyReadThroughput(), + result.getEasyReadSum(), result.getEasyReadAvg(), result.getEasyReadMin(), result.getEasyReadMax(), + result.getEasyReadStdDev(), result.getEasyReadLowerConfidence(), + result.getEasyReadUpperConfidence()); } - return table.render(); } diff --git a/src/main/java/com/blinkfox/stalker/result/MeasureResult.java b/src/main/java/com/blinkfox/stalker/result/MeasureResult.java new file mode 100644 index 0000000..d13d6d0 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/result/MeasureResult.java @@ -0,0 +1,186 @@ +package com.blinkfox.stalker.result; + +import com.blinkfox.stalker.kit.StrKit; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * 对测量的耗时时间等信息做统计分析后的测量统计结果实体类. + * + * @author blinkfox on 2019-01-05. + * @see MeasureStatistician + * @since v1.2.0 + */ +@Getter +@Setter +@Accessors(chain = true) +public class MeasureResult { + + /** + * 测量代码在执行过程中所消耗的总耗时,单位为纳秒(ns). + */ + protected long costs; + + /** + * 总次数. + */ + protected long total; + + /** + * 测量过程中执行成功的次数. + */ + protected long success; + + /** + * 测量过程中执行失败的次数. + */ + protected long failure; + + /** + * 吞吐率,指单位时间内(每秒)的执行总次数,即:{@code throughput = total / (costs / 10^9)}. + * + * @since v1.1.1 + */ + protected double throughput; + + /** + * 总耗时. + */ + protected long sum; + + /** + * 平均耗时. + */ + protected long avg; + + /** + * 最小耗时. + */ + protected long min; + + /** + * 最大耗时. + */ + protected long max; + + /** + * 标准差. + */ + protected double stdDev; + + /** + * 95%置信区间下限. + */ + protected double lowerConfidence; + + /** + * 95%置信区间上限. + */ + protected double upperConfidence; + + /** + * 获取易于人阅读的实际任务运行总时间字符串. + * + * @return 实际任务运行总时间字符串 + */ + public String getEasyReadCosts() { + return StrKit.convertTime(this.costs); + } + + /** + * 获取易于人阅读的吞吐量字符串. + * + * @return 吞吐量字符串 + */ + public String getEasyReadThroughput() { + return StrKit.roundToString(this.throughput); + } + + /** + * 获取易于人阅读的每次测量所花费时间总和的字符串. + * + * @return 花费时间总和的字符串 + */ + public String getEasyReadSum() { + return StrKit.convertTime(this.sum); + } + + /** + * 获取易于人阅读的每次测量所花费的平均时间总和的字符串. + * + * @return 花费的平均时间的字符串 + */ + public String getEasyReadAvg() { + return StrKit.convertTime(this.avg); + } + + /** + * 获取易于人阅读的测量所花费的最小时间的字符串. + * + * @return 所花费的最小时间的字符串 + */ + public String getEasyReadMin() { + return StrKit.convertTime(this.min); + } + + /** + * 获取易于人阅读的测量所花费的最大时间的字符串. + * + * @return 所花费的最大时间的字符串 + */ + public String getEasyReadMax() { + return StrKit.convertTime(this.max); + } + + /** + * 获取易于人阅读的测量所花费的时间标准差的字符串. + * + * @return 所花费的时间标准差的字符串 + */ + public String getEasyReadStdDev() { + return StrKit.convertTime(this.stdDev); + } + + /** + * 获取易于人阅读的测量所花费时间的 95% 置信区间下界的时间字符串. + * + * @return 95% 置信区间下界的时间字符串 + */ + public String getEasyReadLowerConfidence() { + return StrKit.convertTime(this.lowerConfidence); + } + + /** + * 获取易于人阅读的测量所花费时间的 95% 置信区间上界的字符串. + * + * @return 95% 置信区间上界的时间字符串 + */ + public String getEasyReadUpperConfidence() { + return StrKit.convertTime(this.upperConfidence); + } + + /** + * 将对象转换为字符串. + * + * @return 字符串 + */ + @Override + public String toString() { + return StrKit.join("MeasureResult = {", + ", costs = ", this.getEasyReadCosts(), + ", total = ", this.getTotal(), + ", success = ", this.getSuccess(), + ", failure = ", this.getFailure(), + ", throughput = ", this.getEasyReadThroughput(), + ", sum = ", this.getEasyReadSum(), + ", avg = ", this.getEasyReadAvg(), + ", min = ", this.getEasyReadMin(), + ", max = ", this.getEasyReadMax(), + ", stdDev = ", this.getEasyReadStdDev(), + ", lowerConfidence = ", this.getEasyReadLowerConfidence(), + ", upperConfidence = ", this.getEasyReadUpperConfidence(), + "}."); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/result/MeasureStatistician.java b/src/main/java/com/blinkfox/stalker/result/MeasureStatistician.java new file mode 100644 index 0000000..614d4fa --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/result/MeasureStatistician.java @@ -0,0 +1,131 @@ +package com.blinkfox.stalker.result; + +import com.blinkfox.stalker.kit.MathKit; +import java.util.ArrayList; +import java.util.List; + +/** + * 针对测量出的消耗时间数据进行统计的统计器类,一些通用属性信息集成自 {@link MeasureResult}. + * + * @author blinkfox on 2020-06-05. + * @see MeasureResult + * @since v1.2.0 + */ +public class MeasureStatistician extends MeasureResult { + + /** + * 95% 置信区间的 Z 值. + */ + private static final double Z = 1.96; + + /** + * 用来表示存放每次花费时间的数组容量的阈值常量,默认 10 万. + */ + private static final int THRESHOLD = 100_000; + + /** + * 用来记录每次测量出的待测量方法的消耗时间的集合,单位为纳秒(ns), + * 当集合中的数量超过设定的阈值时,就会清空本集合中的数据,防止内存移除. + */ + private final List eachCosts = new ArrayList<>(3325); + + /** + * 方差和,该值表示各个值与平均值的差的平方和. + * + * @since v1.2.0 + */ + private double varSum; + + /** + * 更新最新的统计数据. + * + *

为了防止多个线程更新数据时的线程安全问题,这里加了写锁.

+ * + * @param currSuccess 当前累计的成功运行次数 + * @param currFailure 当前累计的失败运行次数 + * @param currCosts 当前累计的总的运行时间 + * @param currEachCosts 从上次更新到本次更新期间的每次运行次数的花费时间 + */ + public void update(long currSuccess, long currFailure, long currCosts, List currEachCosts) { + // 对基础统计数据进行赋值. + super.success = currSuccess; + super.failure = currFailure; + super.total = super.success + super.failure; + super.costs = currCosts; + super.throughput = MathKit.calcThroughput(super.total, super.costs); + + // 遍历求得所有测量值的和,最大值,最小值和平均值. + for (Long cost : currEachCosts) { + eachCosts.add(cost); + super.sum += cost; + if (super.min > cost) { + super.min = cost; + } + if (super.max < cost) { + super.max = cost; + } + } + super.avg = super.sum / super.total; + + // 计算方差所需的当前所有数据的平方差之和. + double currVarSum = this.varSum; + for (long measure : eachCosts) { + currVarSum += Math.pow(measure - (double) super.avg, 2); + } + + // 分别计算出标准差和95%的置信区间半径, + // 由于数据超过阈值之后,之前的每次花费时间的值会清空,而新的花费时间的平均值发生了变化, + // 因此,计算的总的平法差之和并不准确,从而导致标准差也不准确,但是在大数据情况时,这些误差可以容忍. + super.stdDev = Math.sqrt(currVarSum / super.total); + double radius = (Z * super.stdDev) / Math.sqrt(super.total); + this.lowerConfidence = super.avg - radius; + this.upperConfidence = super.avg + radius; + + // 当数据量超过阈值之后,为了防止后续程序继续运行时内存溢出,就清空 eachCosts 集合中的数据. + // 并记录当前累计的平法差之和,便于以后再累计,计算方差时使用. + if (this.eachCosts.size() > THRESHOLD) { + this.eachCosts.clear(); + this.varSum = currVarSum; + } + } + + /** + * 更新最新的统计数据. + * + *

为了防止多个线程更新数据时的线程安全问题,这里加了写锁.

+ * + * @param currSuccess 当前累计的成功运行次数 + * @param currFailure 当前累计的失败运行次数 + * @param currCosts 当前累计的总的运行时间 + * @param currEachCosts 从上次更新到本次更新期间的每次运行次数的花费时间 + * @return {@link MeasureResult} 测量出的统计结果信息 + */ + public MeasureResult updateAndGet(long currSuccess, long currFailure, long currCosts, List currEachCosts) { + this.update(currSuccess, currFailure, currCosts, currEachCosts); + return this.get(); + } + + /** + * 读取最新的统计结果信息. + * + *

这里先使用乐观读的方式读取数据,如果读取期间数据发生了变化,就采用悲观读的方式来再次读取最新的数据到新的结果变量中.

+ * + * @return 统计结果信息 + */ + public MeasureResult get() { + return new MeasureResult() + .setCosts(super.costs) + .setTotal(super.total) + .setSuccess(super.success) + .setFailure(super.failure) + .setThroughput(super.throughput) + .setSum(super.sum) + .setAvg(super.avg) + .setMin(super.min) + .setMax(super.max) + .setStdDev(super.stdDev) + .setLowerConfidence(super.lowerConfidence) + .setUpperConfidence(super.upperConfidence); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/result/MeasurementCollector.java b/src/main/java/com/blinkfox/stalker/result/MeasurementCollector.java deleted file mode 100644 index 53019c0..0000000 --- a/src/main/java/com/blinkfox/stalker/result/MeasurementCollector.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.blinkfox.stalker.result; - -import com.blinkfox.stalker.kit.StrKit; -import com.blinkfox.stalker.result.bean.EasyReadResult; -import com.blinkfox.stalker.result.bean.Measurement; -import com.blinkfox.stalker.result.bean.OverallResult; -import com.blinkfox.stalker.result.bean.StatisResult; -import com.blinkfox.stalker.result.statis.DefaultMeasureStatis; - -/** - * 测量结果信息的收集器类,即将测量结果、统计结果等收集、整合起来. - * - * @author blinkfox on 2019-01-22. - * @since v1.0.0 - */ -public final class MeasurementCollector { - - /** - * 根据正式测量的总体结果信息,统计、收集整理出更全面的测量结果信息. - * - * @param overallResult 总体结果 - * @return 测量总结果 - */ - public Measurement collect(OverallResult overallResult) { - Measurement measurement = new Measurement(overallResult); - StatisResult statisResult = new DefaultMeasureStatis().statis(overallResult); - measurement.setStatisResult(statisResult); - measurement.setEasyReadResult(this.buildEasyReadResult(overallResult, statisResult)); - return measurement; - } - - /** - * 由于测量结果单位均为纳秒,这里需要构建使人易读的测量结果信息 EasyReadResult 对象. - * - * @param overallResult 总体结果 - * @param statisResult 统计结果 - * @return 易读的结果 - */ - private EasyReadResult buildEasyReadResult(OverallResult overallResult, StatisResult statisResult) { - EasyReadResult easyReadResult = new EasyReadResult(); - easyReadResult.setCosts(StrKit.convertTime(overallResult.getCosts())); - easyReadResult.setTotal(overallResult.getTotal()); - easyReadResult.setSuccess(overallResult.getSuccess()); - easyReadResult.setFailure(overallResult.getFailure()); - easyReadResult.setThroughput(StrKit.roundToString(overallResult.getThroughput())); - - easyReadResult.setSum(StrKit.convertTime(statisResult.getSum())); - easyReadResult.setAvg(StrKit.convertTime(statisResult.getAvg())); - easyReadResult.setMin(StrKit.convertTime(statisResult.getMin())); - easyReadResult.setMax(StrKit.convertTime(statisResult.getMax())); - easyReadResult.setStdDev(StrKit.convertTime(statisResult.getStdDev())); - easyReadResult.setLowerConfidence(StrKit.convertTime(statisResult.getLowerConfidence())); - easyReadResult.setUpperConfidence(StrKit.convertTime(statisResult.getUpperConfidence())); - return easyReadResult; - } - -} diff --git a/src/main/java/com/blinkfox/stalker/result/StalkerFuture.java b/src/main/java/com/blinkfox/stalker/result/StalkerFuture.java new file mode 100644 index 0000000..36b7b00 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/result/StalkerFuture.java @@ -0,0 +1,293 @@ +package com.blinkfox.stalker.result; + +import com.blinkfox.stalker.config.Options; +import com.blinkfox.stalker.config.ScheduledUpdater; +import com.blinkfox.stalker.kit.StrKit; +import com.blinkfox.stalker.output.MeasureOutputContext; +import com.blinkfox.stalker.runner.MeasureRunner; +import com.blinkfox.stalker.runner.executor.StalkerExecutors; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Stalker 中异步任务的 Future 结果对象. + * + * @author blinkfox on 2020-06-02. + * @since v1.2.0 + */ +@Slf4j +public class StalkerFuture implements RunnableFuture> { + + /** + * 用于异步提交任务的线程池. + */ + private static final ExecutorService executor = + StalkerExecutors.newThreadExecutor(4, 16, "stalker-future-thread"); + + /** + * 用于异步定时更新统计数据的线程池. + */ + private ScheduledExecutorService scheduledUpdateExecutor; + + /** + * 可运行任务的选项参数信息. + */ + private final Options options; + + /** + * 可运行的任务. + */ + private final Runnable runnable; + + /** + * 任务运行的 {@link MeasureRunner} 实例. + */ + @Getter + private final MeasureRunner measureRunner; + + /** + * 用于 StalkerFuture 内部识别和控制任务运行状态的 {@link CompletableFuture} 对象. + */ + private CompletableFuture runFuture; + + /** + * 定时更新同步数据的 {@link ScheduledFuture} 对象. + */ + private ScheduledFuture scheduledUpdateFuture; + + /** + * 构造方法. + * + * @param options 运行任务的选项参数 + * @param runnable 可运行实例 + * @param measureRunner 运行测量器 + */ + public StalkerFuture(Options options, Runnable runnable, MeasureRunner measureRunner) { + this.options = options; + this.runnable = runnable; + this.measureRunner = measureRunner; + + // 如果启用了定时更新统计数据的任务,就构造定时任务线程池,并开启异步定时获取统计数据的任务. + ScheduledUpdater scheduledUpdater = options.getScheduledUpdater(); + if (scheduledUpdater != null && scheduledUpdater.isEnabled()) { + this.scheduledUpdateExecutor = StalkerExecutors.newScheduledThreadPool(1, "scheduled-update-thread"); + + final long delay = scheduledUpdater.getDelay(); + final TimeUnit timeUnit = scheduledUpdater.getTimeUnit(); + this.scheduledUpdateFuture = this.scheduledUpdateExecutor.scheduleWithFixedDelay(() -> { + if (log.isDebugEnabled()) { + log.debug("【Stalker 提示】开始了每隔【{}】执行一次定时更新统计数据的定时任务.", StrKit.convertTimeUnit(delay, timeUnit)); + } + this.measureRunner.getMeasureResult(); + }, + scheduledUpdater.getInitialDelay(), delay, timeUnit); + } + } + + /** + * 执行此可运行的方法. + *

注意,此次使用双重检查锁机制,使得该对象的任务只会被运行一次.

+ */ + @Override + public void run() { + if (this.runFuture != null) { + return; + } + + synchronized (this) { + if (this.runFuture == null) { + // 开始异步运行测量任务. + this.runFuture = CompletableFuture.runAsync( + () -> this.measureRunner.run(this.options, this.runnable), executor); + + // 当任务完成之后,如果有其他异步任务没完成或关闭,就关闭相关的异步任务. + this.runFuture.whenComplete((a, e) -> stopFutures()); + } + } + } + + /** + * 立即取消正在执行的测量任务,并立即关闭运行中的任务线程池. + * + * @return 正常情况下返回 {@code true},如果期间发生异常将返回 {@code false} + */ + public boolean cancel() { + return this.cancel(true); + } + + /** + * 立即取消正在执行的测量任务,并关闭运行中的任务线程池. + * + * @param mayInterruptIfRunning 该参数将始终是 {@code true},即取消任务时不管是否执行完了相关的任务都会立即取消任务. + * @return 正常情况下返回 {@code true},如果期间发生异常将返回 {@code false} + */ + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (this.isCancelled()) { + return true; + } + + // 使用布尔值记录,核心任务是否运行完成. + boolean flag = true; + try { + this.measureRunner.stop(); + } catch (Exception e) { + log.error("【Stalker 错误提示】取消正在执行中的测量任务时发生异常!", e); + flag = false; + } + + // 需要将本 Future 中的相关任务或线程也停止. + this.stopFutures(); + return flag; + } + + /** + * 如果某些任务还没完成或者没关闭,就停止相关的任务信息. + */ + private void stopFutures() { + // 立即停止当前异步测量线程任务. + if (this.runFuture != null && !this.runFuture.isDone()) { + this.runFuture.cancel(true); + } + + // 如果线程池未关闭,就关闭线程池,注意,这里不要理解关闭和立即终止正在运行中的任务,防止最后的统计数据更新异常. + if (this.scheduledUpdateExecutor != null && !this.scheduledUpdateExecutor.isShutdown()) { + this.scheduledUpdateExecutor.shutdown(); + } + if (this.scheduledUpdateFuture != null && !this.scheduledUpdateFuture.isDone()) { + this.scheduledUpdateFuture.cancel(false); + } + log.debug("【Stalker 提示】已关闭停止了相关的线程池或异步任务."); + } + + /** + * 获取测量任务在完成之前是否已经被取消. + * 如果任务是正常完成的,将返回 {@code false},如果任务是在完成之前手动取消的,将返回 {@code true}. + * + * @return 布尔值 + */ + @Override + public boolean isCancelled() { + return this.measureRunner.isCancelled(); + } + + /** + * 获取测量任务是否已经执行结束. + * 不管任务是正常结束还是手动取消,只要任务结束了,都将返回 {@code true},如果任务还在执行中,将返回 {@code false}. + * + * @return 布尔值 + */ + @Override + public boolean isDone() { + return this.measureRunner.isCompleted(); + } + + /** + * 获取测量任务是否已经正常执行完毕. + * 只有当任务正常执行完毕时才返回 {@code true},如果任务还在执行中或者被手动取消将返回 {@code false}. + * + * @return 布尔值 + */ + public boolean isDoneSuccessfully() { + return this.measureRunner.isCompleted() && !this.measureRunner.isCancelled(); + } + + /** + * 实时获取任务的执行结果. + * 请注意,该方法获取结果时是“非阻塞的”,每次都能获取到正在执行中的任务进度结果,即时任务被取消也能获取到取消时的最终结果信息。 + * 所以,你不应该调用此方法来阻塞等待执行结果. + * + * @return {@code Options.getOutputs()} 中定义多种的输出通道结果 + */ + @Override + public List get() { + return new MeasureOutputContext().output(this.options, this.getMeasureResult()); + } + + /** + * 实时获取任务的执行结果. + * 请注意,该方法同 {@link #get()} 语义相同,获取结果时是“非阻塞的”,每次都能获取到正在执行中的任务进度结果, + * 即时任务被取消也能获取到取消时的最终结果信息。 + * + *

所以,你不应该调用此方法来阻塞等待执行结果.

+ * + * @param timeout 超时时间 + * @param unit 超时时间单位 + * @return {@code Options.getOutputs()} 中定义多种的输出通道结果 + */ + @Override + public List get(long timeout, TimeUnit unit) { + return this.get(); + } + + /** + * 实时获取任务的测量头统计结果. + * + * @return {@link MeasureResult} 结果 + */ + public MeasureResult getMeasureResult() { + return this.measureRunner.getMeasureResult(); + } + + /** + * 获取任务最终完成时实际所消耗的总的纳秒时间数. + * + * @return 实际所消耗的总的纳秒时间数 + */ + public long getCosts() { + return this.measureRunner.getCosts(); + } + + /** + * 获取当前已经运行的总次数. + * + * @return 运行总次数 + */ + public long getTotal() { + return this.measureRunner.getTotal(); + } + + /** + * 获取到当前时的运行成功的次数. + * + * @return 运行成功的次数 + */ + public long getSuccess() { + return this.measureRunner.getSuccess(); + } + + /** + * 获取当前运行失败的次数. + * + * @return 运行失败的次数 + */ + public long getFailure() { + return this.measureRunner.getFailure(); + } + + /** + * 获取任务开始运行时的纳秒时间戳. + * + * @return 开始运行时的纳秒时间戳 + */ + public long getStartNanoTime() { + return this.measureRunner.getStartNanoTime(); + } + + /** + * 获取任务结束运行时的纳秒时间戳,如果任务还未结束,该值将是 {@code 0}. + * + * @return 结束时纳秒时间戳MeasureStatistician + */ + public long getEndNanoTime() { + return this.measureRunner.getEndNanoTime(); + } + +} diff --git a/src/main/java/com/blinkfox/stalker/result/bean/EasyReadResult.java b/src/main/java/com/blinkfox/stalker/result/bean/EasyReadResult.java deleted file mode 100644 index 3868c78..0000000 --- a/src/main/java/com/blinkfox/stalker/result/bean/EasyReadResult.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.blinkfox.stalker.result.bean; - -import lombok.Getter; -import lombok.Setter; - -/** - * 对测量的整体结果和统计结果等信息的时间单位做了易读性处理后的结果实体类. - * - * @author blinkfox on 2019-01-21. - * @since v1.0.0 - */ -@Getter -@Setter -public class EasyReadResult { - - /** - * 正式测量期间消耗的总时间. - */ - private String costs; - - /** - * 正式测量执行的总次数. - */ - private long total; - - /** - * 正式测量执行成功的次数. - */ - private long success; - - /** - * 正式测量执行失败的次数. - */ - private long failure; - - /** - * 吞吐率,指单位时间内(每秒)的执行总次数. - * - * @since v1.1.1 - */ - private String throughput; - - /** - * 正式执行的所有结果的总耗时. - */ - private String sum; - - /** - * 正式执行的所有结果的平均耗时. - */ - private String avg; - - /** - * 最小耗时. - */ - private String min; - - /** - * 最大耗时. - */ - private String max; - - /** - * 标准差. - */ - private String stdDev; - - /** - * 95%置信区间下限. - */ - private String lowerConfidence; - - /** - * 95%置信区间上限. - */ - private String upperConfidence; - -} diff --git a/src/main/java/com/blinkfox/stalker/result/bean/Measurement.java b/src/main/java/com/blinkfox/stalker/result/bean/Measurement.java deleted file mode 100644 index 286813c..0000000 --- a/src/main/java/com/blinkfox/stalker/result/bean/Measurement.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.blinkfox.stalker.result.bean; - -import lombok.Getter; -import lombok.Setter; - -/** - * 正式测量结果的实体类. - * - * @author blinkfox on 2019-01-22. - * @since v1.0.0 - */ -@Getter -@Setter -public class Measurement { - - /** - * 正式测量的整体结果信息. - */ - private OverallResult overallResult; - - /** - * 正式测量的统计结果信息. - */ - private StatisResult statisResult; - - /** - * 正式测量结果和统计结果信息转换成的易读的结果信息. - */ - private EasyReadResult easyReadResult; - - /** - * 构造方法. - * - * @param overallResult 整体测量结果 - */ - public Measurement(OverallResult overallResult) { - this.overallResult = overallResult; - } - -} diff --git a/src/main/java/com/blinkfox/stalker/result/bean/OverallResult.java b/src/main/java/com/blinkfox/stalker/result/bean/OverallResult.java deleted file mode 100644 index 4647adc..0000000 --- a/src/main/java/com/blinkfox/stalker/result/bean/OverallResult.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.blinkfox.stalker.result.bean; - -import com.blinkfox.stalker.kit.StrKit; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; - -/** - * 待测量方法的耗时测量的整体结果实体类. - * - * @author blinkfox on 2019-01-09. - * @since v1.0.0 - */ -@Getter -@Setter -@Accessors(chain = true) -public class OverallResult { - - /** - * 每次成功测量出的待测量方法的测量时间的集合,单位为纳秒(ns). - */ - private long[] eachMeasures; - - /** - * 测量代码在执行过程中所消耗的总耗时,单位为纳秒(ns). - */ - private long costs; - - /** - * 测量过程中执行的总次数. - */ - private long total; - - /** - * 测量过程中执行成功的次数. - */ - private long success; - - /** - * 测量过程中执行失败的次数. - */ - private long failure; - - /** - * 吞吐率,指单位时间内(每秒)的执行总次数,即:{@code throughput = total / (costs / 10^9)}. - * - * @since v1.1.1 - */ - private double throughput; - - /** - * 将该对象转换字符串. - * - * @return 字符串 - */ - @Override - public String toString() { - return StrKit.join("Measurement = {", - "costs = ", StrKit.convertTime(this.costs), - ", total = ", this.total, - ", success = ", this.success, - ", failure = ", this.failure, - ", throughput = ", this.throughput, - "}"); - } - -} diff --git a/src/main/java/com/blinkfox/stalker/result/bean/StatisResult.java b/src/main/java/com/blinkfox/stalker/result/bean/StatisResult.java deleted file mode 100644 index 85eccbd..0000000 --- a/src/main/java/com/blinkfox/stalker/result/bean/StatisResult.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.blinkfox.stalker.result.bean; - -import com.blinkfox.stalker.kit.StrKit; -import lombok.Getter; -import lombok.Setter; - -/** - * 对测量的耗时时间等信息做统计分析后的统计结果实体类. - * - * @author blinkfox on 2019-01-05. - * @since v1.0.0 - */ -@Getter -@Setter -public class StatisResult { - - /** - * 总耗时. - */ - private long sum; - - /** - * 平均耗时. - */ - private long avg; - - /** - * 最小耗时. - */ - private long min; - - /** - * 最大耗时. - */ - private long max; - - /** - * 标准差. - */ - private double stdDev; - - /** - * 95%置信区间下限. - */ - private double lowerConfidence; - - /** - * 95%置信区间上限. - */ - private double upperConfidence; - - @Override - public String toString() { - return StrKit.join("MeasureStatisResult = {", - ", sum = ", StrKit.convertTime(this.sum), - ", avg = ", StrKit.convertTime(this.avg), - ", min = ", StrKit.convertTime(this.min), - ", max = ", StrKit.convertTime(this.max), - ", stdDev = ", StrKit.convertTime(this.stdDev), - ", lowerConfidence = ", StrKit.convertTime(this.lowerConfidence), - ", upperConfidence = ", StrKit.convertTime(this.upperConfidence), - "}."); - } - -} diff --git a/src/main/java/com/blinkfox/stalker/result/statis/DefaultMeasureStatis.java b/src/main/java/com/blinkfox/stalker/result/statis/DefaultMeasureStatis.java deleted file mode 100644 index eba2dab..0000000 --- a/src/main/java/com/blinkfox/stalker/result/statis/DefaultMeasureStatis.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.blinkfox.stalker.result.statis; - -import com.blinkfox.stalker.result.bean.OverallResult; -import com.blinkfox.stalker.result.bean.StatisResult; - -/** - * 默认的测量结果统计实现. - * - * @author blinkfox on 2019-01-10. - * @since v1.0.0 - */ -public class DefaultMeasureStatis implements MeasureStatis { - - /** - * 95%置信区间的 Z 值. - */ - private static final double Z = 1.96; - - /** - * 将测量结果数据做统计分析,得出性能结果数据. - * - * @param overallResult 测量结果数据 - * @return 性能结果数据 - */ - @Override - public StatisResult statis(OverallResult overallResult) { - StatisResult statisResult = new StatisResult(); - long[] eachMeasures = overallResult.getEachMeasures(); - if (eachMeasures == null || eachMeasures.length == 0) { - return statisResult; - } - - // 计算出测量结果的相关统计结果. - return this.buildOtherResult(this.buildBaseResult(statisResult, eachMeasures), eachMeasures); - } - - /** - * 构建基础统计结果信息. - * - * @param statisResult 性能结果对象 - * @param eachMeasures 每次测量值的数组 - * @return 性能结果对象 - */ - private StatisResult buildBaseResult(StatisResult statisResult, long[] eachMeasures) { - long sum = 0; - long min = eachMeasures[0]; - long max = min; - - // 遍历求得所有测量值的和,最大值,最小值. - for (long measure : eachMeasures) { - sum += measure; - if (min > measure) { - min = measure; - } - if (max < measure) { - max = measure; - } - } - - statisResult.setMin(min); - statisResult.setMax(max); - statisResult.setSum(sum); - statisResult.setAvg(sum / eachMeasures.length); - return statisResult; - } - - /** - * 构建其它统计结果信息. - * - * @param statisResult 性能结果对象 - * @param eachMeasures 每次测量值的数组 - * @return 性能结果对象 - */ - private StatisResult buildOtherResult(StatisResult statisResult, long[] eachMeasures) { - // 计算方差所需的平方和. - double s = 0; - long avg = statisResult.getAvg(); - for (long measure : eachMeasures) { - s += Math.pow(measure - (double) avg, 2); - } - - // 分别计算出标准差和95%的置信区间半径. - long n = eachMeasures.length; - double stdDev = Math.sqrt(s / n); - double radius = (Z * stdDev) / Math.sqrt(n); - - statisResult.setStdDev(stdDev); - statisResult.setLowerConfidence(avg - radius); - statisResult.setUpperConfidence(avg + radius); - return statisResult; - } - -} diff --git a/src/main/java/com/blinkfox/stalker/result/statis/MeasureStatis.java b/src/main/java/com/blinkfox/stalker/result/statis/MeasureStatis.java deleted file mode 100644 index d828c54..0000000 --- a/src/main/java/com/blinkfox/stalker/result/statis/MeasureStatis.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.blinkfox.stalker.result.statis; - -import com.blinkfox.stalker.result.bean.OverallResult; -import com.blinkfox.stalker.result.bean.StatisResult; - -/** - * 测量结果统计接口. - * - * @author blinkfox on 2019-01-10. - * @since v1.0.0 - */ -public interface MeasureStatis { - - /** - * 将测量结果数据做统计分析,得出性能结果数据. - * - * @param overallResult 测量结果数据 - * @return 性能结果数据 - */ - StatisResult statis(OverallResult overallResult); - -} \ No newline at end of file diff --git a/src/main/java/com/blinkfox/stalker/runner/AbstractMeasureRunner.java b/src/main/java/com/blinkfox/stalker/runner/AbstractMeasureRunner.java new file mode 100644 index 0000000..2d04c5b --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/runner/AbstractMeasureRunner.java @@ -0,0 +1,232 @@ +package com.blinkfox.stalker.runner; + +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.result.MeasureStatistician; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * 用于测量待执行方法耗时情况等信息的抽象运行器,实现了 {@link MeasureRunner} 接口. + * + * @author blinkfox on 2020-05-24. + * @see MeasureRunner + * @see SimpleMeasureRunner + * @see SimpleScheduledMeasureRunner + * @see ConcurrentMeasureRunner + * @see ConcurrentScheduledMeasureRunner + * @since v1.2.0 + */ +@Slf4j +public abstract class AbstractMeasureRunner implements MeasureRunner { + + /** + * 线程池. + */ + protected ExecutorService executorService; + + /** + * 测量统计器的实例对象. + */ + private final MeasureStatistician measureStatistician; + + private final Lock statisLock; + + /** + * 每次'成功'测量出的待测量方法的耗时时间,单位为纳秒({@code ns}). + */ + protected Queue eachMeasures; + + /** + * 测量过程中执行成功的次数. + */ + protected LongAdder success; + + /** + * 测量过程中执行失败的次数. + */ + protected LongAdder failure; + + /** + * 是否已经运行完成. + */ + protected AtomicBoolean completed; + + /** + * 是否已经被取消. + */ + protected AtomicBoolean canceled; + + /** + * 运行开始时的纳秒时间戳,单位为纳秒({@code ns}). + */ + @Getter + protected long startNanoTime; + + /** + * 运行结束时的纳秒时间戳,单位为纳秒({@code ns}). + */ + @Getter + protected long endNanoTime; + + /** + * 用于记录以前总共读取了 eachMeasures 中的数据总量,即总的偏移量. + */ + private long beforeTotalCount; + + /** + * 公共的抽象父构造方法. + */ + public AbstractMeasureRunner() { + this.measureStatistician = new MeasureStatistician(); + this.statisLock = new ReentrantLock(); + this.eachMeasures = new ConcurrentLinkedQueue<>(); + this.success = new LongAdder(); + this.failure = new LongAdder(); + this.completed = new AtomicBoolean(false); + this.canceled = new AtomicBoolean(false); + } + + /** + * 获取当前测量任务已经运行的总花费时间. + * + * @return 运行总花费时间 + */ + public long getCosts() { + return this.completed.get() && this.getTotal() <= this.beforeTotalCount && eachMeasures.isEmpty() + ? this.endNanoTime - this.startNanoTime + : this.startNanoTime == 0 ? 0 : System.nanoTime() - this.startNanoTime; + } + + /** + * 获取任务运行成功的数量. + * + * @return 成功数量 + */ + @Override + public long getTotal() { + return this.success.longValue() + this.failure.longValue(); + } + + /** + * 获取任务运行成功的数量. + * + * @return 成功数量 + */ + public long getSuccess() { + return this.success.longValue(); + } + + /** + * 获取任务运行失败的数量. + * + * @return 失败数量 + */ + public long getFailure() { + return this.failure.longValue(); + } + + /** + * 判断当前任务是否已经执行完成. + * + * @return 是否执行完成的布尔值 + */ + @Override + public boolean isCompleted() { + return this.completed.get(); + } + + /** + * 判断当前任务是否已经被取消. + * + * @return 是否被取消的布尔值 + */ + @Override + public boolean isCancelled() { + return this.canceled.get(); + } + + /** + * 如果结束时间的值是 0,那么就设置结束时的纳秒时间. + * + * @param endNanoTime 结束纳秒时间. + */ + public void setEndNanoTimeIfEmpty(long endNanoTime) { + if (this.endNanoTime == 0) { + this.endNanoTime = endNanoTime; + } + } + + /** + * 更新并获取统计结果信息数据. + * + *

如果任务已经完成,就直接返回最终的测试结果数据即可,否则加锁获取正在运行中的任务的统计数据.

+ * + * @return 统计结果信息 + */ + @Override + public MeasureResult getMeasureResult() { + return this.completed.get() && this.getTotal() <= this.beforeTotalCount && eachMeasures.isEmpty() + ? this.measureStatistician.get() + : this.getRunningMeasureResult(); + } + + /** + * 获取正在运行中的任务的数据的统计结果信息. + * + *

由于可能会有两个或多个线程去更新和获取统计数据,这里须要加锁来获取正在运行中的任务的统计数据.

+ * + * @return 统计结果信息 + */ + private MeasureResult getRunningMeasureResult() { + try { + // 读取时加锁. + statisLock.lockInterruptibly(); + + // 计算出运行消耗的总时间,如果已经结束了,就直接使用结束时间戳减去开始时间戳. + final long currCosts = this.completed.get() + ? this.endNanoTime - this.startNanoTime + : this.startNanoTime == 0 ? 0 : System.nanoTime() - this.startNanoTime; + + // 获取到截至到当前时间的正确运行次数数、错误运行次数,消耗的时间和每次的运行时间等数据. + final long currFailure = this.getFailure(); + final long currSuccess = this.getSuccess(); + final long currTotal = currSuccess + currFailure; + int newCount = (int) (currTotal - this.beforeTotalCount); + if (newCount <= 0) { + return this.measureStatistician.get(); + } + + // 截取复制出最新的测量耗时信息,这里从队列中"出队读取" len 个最新的测量耗时数据. + final List currEachCosts = new ArrayList<>(newCount); + for (int i = 0; i < newCount; ++i) { + Long cost = this.eachMeasures.poll(); + if (cost != null) { + currEachCosts.add(cost); + } + } + this.beforeTotalCount = currTotal; + + // 更新并获取最新的统计数据信息. + return measureStatistician.updateAndGet(currSuccess, currFailure, currCosts, currEachCosts); + } catch (InterruptedException e) { + log.error("【Stalker 错误提示】获取运行中任务的统计结果数据线程被中断!", e); + Thread.currentThread().interrupt(); + return this.measureStatistician.get(); + } catch (Exception e) { + log.error("【Stalker 错误提示】获取运行中任务的统计结果数据时出错,将直接返回之前的数据.", e); + return this.measureStatistician.get(); + } finally { + statisLock.unlock(); + } + } + +} diff --git a/src/main/java/com/blinkfox/stalker/runner/ConcurrentMeasureRunner.java b/src/main/java/com/blinkfox/stalker/runner/ConcurrentMeasureRunner.java index 9b5bad5..c8ae4dc 100644 --- a/src/main/java/com/blinkfox/stalker/runner/ConcurrentMeasureRunner.java +++ b/src/main/java/com/blinkfox/stalker/runner/ConcurrentMeasureRunner.java @@ -1,15 +1,14 @@ package com.blinkfox.stalker.runner; import com.blinkfox.stalker.config.Options; -import com.blinkfox.stalker.kit.MathKit; -import com.blinkfox.stalker.result.bean.OverallResult; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; +import com.blinkfox.stalker.kit.ConcurrentHashSet; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.runner.executor.StalkerExecutors; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicLong; import lombok.extern.slf4j.Slf4j; /** @@ -19,29 +18,12 @@ * @since v1.0.0 */ @Slf4j -public class ConcurrentMeasureRunner implements MeasureRunner { - - private static final int N_1024 = 1024; - - /** - * 每次'成功'测量出的待测量方法的耗时时间,单位为纳秒(ns). - */ - private final Queue eachMeasures; +public class ConcurrentMeasureRunner extends AbstractMeasureRunner { /** - * 测量过程中执行的总次数. + * 用于存放正在运行中的 Future 线程,便于在手动"停止"运行时,能取消正在执行中的任务. */ - private final AtomicLong total; - - /** - * 测量过程中执行成功的次数. - */ - private final AtomicLong success; - - /** - * 测量过程中执行失败的次数. - */ - private final AtomicLong failure; + protected final Set> runningFutures; /** * 构造方法. @@ -49,10 +31,8 @@ public class ConcurrentMeasureRunner implements MeasureRunner { *

这个类中的属性,需要支持高并发写入.

*/ public ConcurrentMeasureRunner() { - this.eachMeasures = new ConcurrentLinkedQueue<>(); - this.total = new AtomicLong(0); - this.success = new AtomicLong(0); - this.failure = new AtomicLong(0); + super(); + this.runningFutures = new ConcurrentHashSet<>(); } /** @@ -60,40 +40,51 @@ public ConcurrentMeasureRunner() { * * @param options 运行的配置选项实例 * @param runnable 可运行实例 - * @return 测量结果 + * @return 测量统计结果 */ @Override - public OverallResult run(Options options, Runnable runnable) { + public MeasureResult run(Options options, Runnable runnable) { int threads = options.getThreads(); int concurrens = options.getConcurrens(); int runs = options.getRuns(); boolean printErrorLog = options.isPrintErrorLog(); // 初始化存储的集合、线程池、并发工具类中的对象实例等. - Semaphore semaphore = new Semaphore(Math.min(concurrens, threads)); - CountDownLatch countDownLatch = new CountDownLatch(threads); - ExecutorService executorService = Executors.newFixedThreadPool(Math.min(threads, N_1024)); - final long start = System.nanoTime(); + Semaphore semaphore = new Semaphore(concurrens); + CountDownLatch countLatch = new CountDownLatch(threads); + super.executorService = StalkerExecutors.newFixedThreadExecutor(threads, "concurrent-measure-thread"); + super.startNanoTime = System.nanoTime(); // 在多线程下控制线程并发量,与循环搭配来一起执行和测量. for (int i = 0; i < threads; i++) { - executorService.submit(() -> { - try { - semaphore.acquire(); + try { + semaphore.acquire(); + // 如果线程池已经关闭,就直接返回结果. + if (super.executorService.isShutdown()) { + return super.getMeasureResult(); + } + + final CompletableFuture future = CompletableFuture.runAsync(() -> { this.loopMeasure(runs, printErrorLog, runnable); semaphore.release(); - } catch (InterruptedException e) { - log.error("测量方法耗时信息在多线程下出错!", e); - Thread.currentThread().interrupt(); - } finally { - countDownLatch.countDown(); - } - }); + countLatch.countDown(); + }, super.executorService); + + // 将 future 添加到正在运行的 Future 信息集合中,并在 future 完成时,异步移除已经完成了的 future. + runningFutures.add(future); + future.whenCompleteAsync((a, b) -> runningFutures.remove(future)); + } catch (InterruptedException e) { + log.error("【Stalker 错误提示】在多线程并发情况下测量任务执行的耗时信息的线程已被中断!", e); + Thread.currentThread().interrupt(); + } } - // 等待所有线程执行完毕,并关闭线程池,最后将结果封装成实体信息. - this.awaitAndShutdown(countDownLatch, executorService); - return this.buildMeasurement(System.nanoTime() - start); + // 等待所有线程执行完毕,记录是否完成和完成时间,并关闭线程池等资源,最后将结果封装成实体信息返回. + this.await(countLatch); + super.setEndNanoTimeIfEmpty(System.nanoTime()); + super.completed.compareAndSet(false, true); + StalkerExecutors.shutdown(this.executorService); + return super.getMeasureResult(); } /** @@ -103,17 +94,16 @@ public OverallResult run(Options options, Runnable runnable) { * @param printErrorLog 是否打印输出错误日志 * @param runnable 可执行实例 */ - private void loopMeasure(int runs, boolean printErrorLog, final Runnable runnable) { + protected void loopMeasure(int runs, boolean printErrorLog, final Runnable runnable) { for (int j = 0; j < runs; j++) { - this.total.incrementAndGet(); try { long eachStart = System.nanoTime(); runnable.run(); - this.eachMeasures.add(System.nanoTime() - eachStart); - this.success.incrementAndGet(); + super.eachMeasures.offer(System.nanoTime() - eachStart); + super.success.increment(); } catch (Exception e) { // 如果待测量的方法,执行错误则失败数 +1,且根据选项参数来判断是否打印异常错误日志. - this.failure.incrementAndGet(); + super.failure.increment(); if (printErrorLog) { log.error("测量方法耗时信息在多线程下出错!", e); } @@ -122,44 +112,52 @@ private void loopMeasure(int runs, boolean printErrorLog, final Runnable runnabl } /** - * 等待所有线程执行完毕,并最终关闭线程池. + * 停止相关的运行测量任务. * - * @param countDownLatch countDownLatch实例 - * @param executorService 线程池 + *

注意:如果任务未完成,则立即停止线程池,但是还不能停止正在运行中的若干任务线程, + * 暂时还没想到一个更好的、高性能的停止所有运行中的任务的方法.

+ * + * @author blinkfox on 2020-05-25. + * @since v1.2.0 */ - private void awaitAndShutdown(CountDownLatch countDownLatch, ExecutorService executorService) { - try { - countDownLatch.await(); - } catch (InterruptedException e) { - log.error("在多线程下等待测量结果结束时出错!", e); - Thread.currentThread().interrupt(); - } finally { - executorService.shutdown(); + @Override + public void stop() { + if (!isCompleted()) { + super.setEndNanoTimeIfEmpty(System.nanoTime()); + super.completed.compareAndSet(false, true); + super.canceled.compareAndSet(false, true); + + // 停止时直接关闭线程池. + StalkerExecutors.shutdownNow(this.executorService); + + // 迭代删除正在运行中的 Future,并取消正在运行中的任务. + Iterator> futureIterator = this.runningFutures.iterator(); + while (futureIterator.hasNext()) { + CompletableFuture future = futureIterator.next(); + this.runningFutures.remove(future); + if (!future.isDone()) { + future.cancel(true); + } + } } } /** - * 构造测量的结果信息的 Measurement 对象. + * 等待所有线程执行完毕,并最终关闭线程池. * - * @param costs 消耗的总耗时,单位是纳秒 - * @return Measurement对象 + * @param countLatch 计数锁 + * @author blinkfox on 2020-05-25. + * @since v1.2.0 */ - private OverallResult buildMeasurement(long costs) { - // 将队列转数组. - int len = this.eachMeasures.size(); - long[] measures = new long[len]; - for (int i = 0; i < len; i++) { - measures[i] = eachMeasures.remove(); + private void await(CountDownLatch countLatch) { + try { + if (countLatch != null) { + countLatch.await(); + } + } catch (InterruptedException e) { + log.error("【Stalker 错误提示】在并发执行下等待任务执行结束时出错!", e); + Thread.currentThread().interrupt(); } - - long totalCount = this.total.get(); - return new OverallResult() - .setEachMeasures(measures) - .setCosts(costs) - .setTotal(totalCount) - .setSuccess(this.success.get()) - .setFailure(this.failure.get()) - .setThroughput(MathKit.calcThroughput(totalCount, costs)); } } diff --git a/src/main/java/com/blinkfox/stalker/runner/ConcurrentScheduledMeasureRunner.java b/src/main/java/com/blinkfox/stalker/runner/ConcurrentScheduledMeasureRunner.java new file mode 100644 index 0000000..6ed9cfa --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/runner/ConcurrentScheduledMeasureRunner.java @@ -0,0 +1,122 @@ +package com.blinkfox.stalker.runner; + +import com.blinkfox.stalker.config.Options; +import com.blinkfox.stalker.config.RunDuration; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.runner.executor.StalkerExecutors; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import lombok.extern.slf4j.Slf4j; + +/** + * 继承自 {@link ConcurrentMeasureRunner},在多线程并发情况下的运行指定的持续时间的测量运行器. + * + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ +@Slf4j +public class ConcurrentScheduledMeasureRunner extends ConcurrentMeasureRunner { + + /** + * 用于异步定时调度任务的线程池. + */ + private final ScheduledExecutorService scheduledExecutorService; + + /** + * 执行中的测量任务的 {@link Future} 实例. + * + * @since v1.2.0 + */ + protected Future scheduledFuture; + + /** + * 构造方法. + * + *

这个类中的属性,需要支持高并发写入.

+ */ + public ConcurrentScheduledMeasureRunner() { + super(); + this.scheduledExecutorService = StalkerExecutors.newScheduledThreadPool(1, "concurrent-scheduled-thread"); + super.executorService = StalkerExecutors.newFixedThreadExecutor( + StalkerExecutors.MAX_POOL_SIZE, "concurrent-measure-thread"); + } + + /** + * 持续并发的执行指定时间的 runnable 方法,并将执行成功与否、耗时结果等信息存入到 OverallResult 实体对象中. + * + * @param options 运行的配置选项实例 + * @param runnable 可运行实例 + * @return 测量统计结果 + */ + @Override + public MeasureResult run(Options options, Runnable runnable) { + int concurrens = options.getConcurrens(); + int runs = options.getRuns(); + boolean printErrorLog = options.isPrintErrorLog(); + + // 初始化存储的集合、线程池、并发工具类中的对象实例等. + final Semaphore semaphore = new Semaphore(concurrens); + + // 到指定的持续时间之后,就取消执行中的任务,并关闭线程池, + // 注意,由于是定时任务,所以“是否取消”也设置为 false,用于区分是否是人为取消了任务,只有人为取消的才是 true. + final RunDuration duration = options.getDuration(); + this.scheduledFuture = this.scheduledExecutorService.schedule(() -> { + this.stop(); + super.canceled.compareAndSet(true, false); + }, duration.getAmount(), duration.getTimeUnit()); + + super.startNanoTime = System.nanoTime(); + long expectEndNanoTime = duration.getEndNanoTime(super.startNanoTime); + + while (true) { + try { + semaphore.acquire(); + if (super.executorService.isShutdown()) { + return super.getMeasureResult(); + } + + // 如果当前时间大于了期望的结束时间,就跳出 while 循环. + if (System.nanoTime() > expectEndNanoTime) { + break; + } + final CompletableFuture future = CompletableFuture.runAsync(() -> { + this.loopMeasure(runs, printErrorLog, runnable); + semaphore.release(); + }, super.executorService); + + // 将 future 添加到正在运行的 Future 信息集合中,并在 future 完成时,异步移除已经完成了的 future. + runningFutures.add(future); + future.whenCompleteAsync((a, b) -> runningFutures.remove(future)); + } catch (InterruptedException e) { + log.error("【Stalker 错误提示】在多线程并发情况下测量任务执行的耗时信息的线程已被中断!", e); + Thread.currentThread().interrupt(); + } + } + + // 等待所有线程执行完毕,记录是否完成和完成时间,并关闭线程池等资源,最后将结果封装成实体信息返回. + super.setEndNanoTimeIfEmpty(System.nanoTime()); + super.completed.compareAndSet(false, true); + StalkerExecutors.shutdown(this.executorService, this.scheduledExecutorService); + if (!this.scheduledFuture.isDone()) { + this.scheduledFuture.cancel(true); + } + return super.getMeasureResult(); + } + + /** + * 停止相关的运行测量任务. + */ + @Override + public void stop() { + super.stop(); + + // 关闭定时任务线程池和取消对应的定时任务. + StalkerExecutors.shutdown(this.scheduledExecutorService); + if (this.scheduledFuture != null && !this.scheduledFuture.isDone()) { + this.scheduledFuture.cancel(true); + } + } + +} diff --git a/src/main/java/com/blinkfox/stalker/runner/MeasureRunner.java b/src/main/java/com/blinkfox/stalker/runner/MeasureRunner.java index 78f3171..79a8700 100644 --- a/src/main/java/com/blinkfox/stalker/runner/MeasureRunner.java +++ b/src/main/java/com/blinkfox/stalker/runner/MeasureRunner.java @@ -1,7 +1,7 @@ package com.blinkfox.stalker.runner; import com.blinkfox.stalker.config.Options; -import com.blinkfox.stalker.result.bean.OverallResult; +import com.blinkfox.stalker.result.MeasureResult; /** * 用于测量待执行方法耗时情况等信息的运行器接口. @@ -16,8 +16,97 @@ public interface MeasureRunner { * * @param options 运行的配置选项实例 * @param runnable 可运行实例 - * @return 测量结果 + * @return 测量的统计结果 */ - OverallResult run(Options options, Runnable runnable); + MeasureResult run(Options options, Runnable runnable); + + /** + * 判断当前任务是否已经执行完成. + * + * @return 是否执行完成的布尔值 + * @author blinkfox on 2020-05-23. + * @since v1.2.0 + */ + boolean isCompleted(); + + /** + * 判断当前任务是否已经被取消. + * + * @return 是否被取消的布尔值 + * @author blinkfox on 2020-06-02. + * @since v1.2.0 + */ + boolean isCancelled(); + + /** + * 获取当前已经运行的总次数. + * + * @return 运行总次数 + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ + long getTotal(); + + /** + * 获取当前测量任务已经运行的总花费时间. + * + * @return 运行总花费时间 + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ + long getCosts(); + + /** + * 获取到当前时的运行成功的次数. + * + * @return 运行成功的次数 + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ + long getSuccess(); + + /** + * 获取当前运行失败的次数. + * + * @return 运行失败的次数 + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ + long getFailure(); + + /** + * 获取任务开始运行时的纳秒时间戳. + * + * @return 开始运行时间 + * @author blinkfox on 2020-05-23. + * @since v1.2.0 + */ + long getStartNanoTime(); + + /** + * 获取任务结束运行时的纳秒时间戳. + * + * @return 结束运行时间 + * @author blinkfox on 2020-05-23. + * @since v1.2.0 + */ + long getEndNanoTime(); + + /** + * 停止相关的运行测量任务. + * + * @author blinkfox on 2020-06-03. + * @since v1.2.0 + */ + void stop(); + + /** + * 获取运行中的任务的测量统计结果信息. + * + * @return 统计结果信息 + * @author blinkfox on 2020-06-05. + * @since v1.2.0 + */ + MeasureResult getMeasureResult(); } diff --git a/src/main/java/com/blinkfox/stalker/runner/MeasureRunnerContext.java b/src/main/java/com/blinkfox/stalker/runner/MeasureRunnerContext.java index 3d3ef40..e80a80a 100644 --- a/src/main/java/com/blinkfox/stalker/runner/MeasureRunnerContext.java +++ b/src/main/java/com/blinkfox/stalker/runner/MeasureRunnerContext.java @@ -2,7 +2,8 @@ import com.blinkfox.stalker.config.Options; import com.blinkfox.stalker.kit.StrKit; -import com.blinkfox.stalker.result.bean.OverallResult; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.result.StalkerFuture; import lombok.extern.slf4j.Slf4j; /** @@ -35,7 +36,7 @@ public MeasureRunnerContext(Options options) { * @param options 参数选项 * @param runnable runnable */ - private void warmup(Options options, Runnable runnable) { + private static void warmup(Options options, Runnable runnable) { final boolean printErrorLog = options.isPrintErrorLog(); log.debug("【stalker 提示】预热开始..."); long start = System.nanoTime(); @@ -61,13 +62,50 @@ private void warmup(Options options, Runnable runnable) { * 检查Options参数是否合法,并进行预热准备,然后执行 runnable 方法,并将执行结果的耗时纳秒(ns)值存入到集合中. * * @param runnable 可运行实例 - * @return 运行的测量结果 + * @return 运行的测量统计结果信息 */ - public OverallResult run(Runnable runnable) { - this.warmup(options, runnable); - return options.getThreads() > 1 && options.getConcurrens() > 1 - ? new ConcurrentMeasureRunner().run(options, runnable) - : new SimpleMeasureRunner().run(options, runnable); + public MeasureResult run(Runnable runnable) { + warmup(options, runnable); + if (options.getDuration() != null) { + return options.getConcurrens() > 1 + ? new ConcurrentScheduledMeasureRunner().run(options, runnable) + : new SimpleScheduledMeasureRunner().run(options, runnable); + } else { + return options.getConcurrens() > 1 + ? new ConcurrentMeasureRunner().run(options, runnable) + : new SimpleMeasureRunner().run(options, runnable); + } + } + + /** + * 检查Options参数是否合法,并进行预热准备,然后执行 runnable 方法,并将执行结果的耗时纳秒(ns)值存入到集合中. + * + * @param options 运行的选项参数 + * @param runnable 可运行实例 + * @return 此次运行的会话 ID + * @author blinkfox on 2020-05-23. + * @since v1.2.0 + */ + public static StalkerFuture submit(final Options options, final Runnable runnable) { + // 预热运行. + warmup(options, runnable); + + // 获取对应的 measureRunner,并将 measureRunner 存储到 map 中,并异步执行任务. + MeasureRunner measureRunner; + if (options.getDuration() != null) { + measureRunner = options.getConcurrens() > 1 + ? new ConcurrentScheduledMeasureRunner() + : new SimpleScheduledMeasureRunner(); + } else { + measureRunner = options.getConcurrens() > 1 + ? new ConcurrentMeasureRunner() + : new SimpleMeasureRunner(); + } + + // 构造 StalkerFuture 对象,并开始运行任务. + StalkerFuture stalkerFuture = new StalkerFuture(options, runnable, measureRunner); + stalkerFuture.run(); + return stalkerFuture; } } diff --git a/src/main/java/com/blinkfox/stalker/runner/SimpleMeasureRunner.java b/src/main/java/com/blinkfox/stalker/runner/SimpleMeasureRunner.java index f6f7572..91bd198 100644 --- a/src/main/java/com/blinkfox/stalker/runner/SimpleMeasureRunner.java +++ b/src/main/java/com/blinkfox/stalker/runner/SimpleMeasureRunner.java @@ -1,8 +1,10 @@ package com.blinkfox.stalker.runner; import com.blinkfox.stalker.config.Options; -import com.blinkfox.stalker.kit.MathKit; -import com.blinkfox.stalker.result.bean.OverallResult; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.runner.executor.StalkerExecutors; +import java.util.concurrent.CancellationException; +import java.util.concurrent.Future; import lombok.extern.slf4j.Slf4j; /** @@ -12,76 +14,91 @@ * @since v1.0.0 */ @Slf4j -public class SimpleMeasureRunner implements MeasureRunner { +public class SimpleMeasureRunner extends AbstractMeasureRunner { /** - * 每次'成功'测量出的待测量方法的耗时时间,单位为纳秒(ns). - */ - private long[] eachMeasures; - - /** - * 测量过程中执行的总次数. - */ - private long total; - - /** - * 测量过程中执行成功的次数. + * 执行中的测量任务的 {@link Future} 实例. + * + * @since v1.2.0 */ - private long success; + protected Future measureFuture; /** - * 测量过程中执行失败的次数. + * 构造方法. */ - private long failure; + public SimpleMeasureRunner() { + super(); + } /** * 执行 runnable 方法,并将执行成功与否、耗时结果等信息存入到 OverallResult 实体对象中. - *

这里由于是单线程的重写方法,不再需要`new Thread`了,直接调用`runnable.run()`即可.

* * @param options 运行的配置选项实例 * @param runnable 可运行实例 - * @return 测量结果 + * @return 测量统计结果 */ @Override - public OverallResult run(Options options, Runnable runnable) { + public MeasureResult run(Options options, Runnable runnable) { boolean printErrorLog = options.isPrintErrorLog(); int totalCount = options.getThreads() * options.getRuns(); - this.eachMeasures = new long[totalCount]; - final long start = System.nanoTime(); + super.executorService = StalkerExecutors.newSingleThreadExecutor("simple-measure-thread"); + super.startNanoTime = System.nanoTime(); - // 单线程循环执行 runs 次. - for (int i = 0; i < totalCount; ++i) { - this.total++; - try { - long eachStart = System.nanoTime(); - runnable.run(); - this.eachMeasures[i] = System.nanoTime() - eachStart; - this.success++; - } catch (RuntimeException e) { - this.failure++; - if (printErrorLog) { - log.error("【stalker 错误】测量方法耗时信息出错!", e); + // 由于并发数是 1,直接单线程循环执行 (runs * threads) 次即可, + // 将执行的相关任务以 Future 的形式来执行,便于程序动态取消任务或判断任务执行情况等. + this.measureFuture = executorService.submit(() -> { + for (int i = 0; i < totalCount; ++i) { + try { + // 开始执行测量任务,记录开始时间、执行次数等. + long eachStart = System.nanoTime(); + runnable.run(); + super.eachMeasures.offer(System.nanoTime() - eachStart); + super.success.increment(); + } catch (Exception e) { + super.failure.increment(); + if (printErrorLog) { + log.error("【stalker 错误】测量方法耗时信息出错!", e); + } } } + }); + + // 阻塞调用要执行的测量任务,达到等待任务结束的目的. + try { + this.measureFuture.get(); + } catch (CancellationException e) { + log.info("【Stalker 提示】已取消或完成测量任务."); + } catch (Exception e) { + log.error("【Stalker 错误】执行测量任务发生错误!", e); } - return this.buildMeasurement(System.nanoTime() - start); + // 等待所有线程执行完毕,并关闭线程池,最后将结果封装成实体信息. + super.setEndNanoTimeIfEmpty(System.nanoTime()); + super.completed.compareAndSet(false, true); + StalkerExecutors.shutdown(super.executorService); + return super.getMeasureResult(); } /** - * 构造测量的结果信息的 Measurement 对象. + * 停止相关的运行测量任务. * - * @param costs 消耗的总耗时,单位是纳秒 - * @return Measurement对象 + * @author blinkfox on 2020-05-25. + * @since v1.2.0 */ - private OverallResult buildMeasurement(long costs) { - return new OverallResult() - .setEachMeasures(this.eachMeasures) - .setCosts(costs) - .setTotal(this.total) - .setSuccess(this.success) - .setFailure(this.failure) - .setThroughput(MathKit.calcThroughput(this.total, costs)); + @Override + public void stop() { + if (!isCompleted()) { + super.setEndNanoTimeIfEmpty(System.nanoTime()); + super.completed.compareAndSet(false, true); + super.canceled.compareAndSet(false, true); + + // 立即关闭线程池. + StalkerExecutors.shutdownNow(super.executorService); + // 取消正在执行中的任务. + if (this.measureFuture != null && !this.measureFuture.isDone()) { + this.measureFuture.cancel(true); + } + } } } diff --git a/src/main/java/com/blinkfox/stalker/runner/SimpleScheduledMeasureRunner.java b/src/main/java/com/blinkfox/stalker/runner/SimpleScheduledMeasureRunner.java new file mode 100644 index 0000000..00bf53f --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/runner/SimpleScheduledMeasureRunner.java @@ -0,0 +1,113 @@ +package com.blinkfox.stalker.runner; + +import com.blinkfox.stalker.config.Options; +import com.blinkfox.stalker.config.RunDuration; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.runner.executor.StalkerExecutors; +import java.util.concurrent.CancellationException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import lombok.extern.slf4j.Slf4j; + +/** + * 继承自 {@link SimpleMeasureRunner},在单线程情况下的运行指定的持续时间的测量运行器. + * + * @author blinkfox on 2020-06-01. + * @since v1.2.0 + */ +@Slf4j +public class SimpleScheduledMeasureRunner extends SimpleMeasureRunner { + + /** + * 用于异步定时调度任务的线程池. + */ + private final ScheduledExecutorService scheduledExecutorService; + + /** + * 执行中的测量任务的 {@link Future} 实例. + * + * @since v1.2.0 + */ + protected Future scheduledFuture; + + /** + * 构造方法. + */ + public SimpleScheduledMeasureRunner() { + super(); + this.scheduledExecutorService = StalkerExecutors.newScheduledThreadPool(1, "simple-scheduled-thread"); + } + + /** + * 持续执行指定时间的 runnable 方法,并将执行成功与否、耗时结果等信息存入到 {@link MeasureResult} 实体对象中. + * + * @param options 运行的配置选项实例 + * @param runnable 可运行实例 + * @return 测量统计结果 + */ + @Override + public MeasureResult run(Options options, Runnable runnable) { + boolean printErrorLog = options.isPrintErrorLog(); + super.executorService = StalkerExecutors.newSingleThreadExecutor("simple-scheduled-measure-thread"); + super.startNanoTime = System.nanoTime(); + + // 将单线程中执行的任务放在 while 循环中,一直执行下去. + super.measureFuture = executorService.submit(() -> { + while (true) { + try { + // 开始执行测量任务,记录开始时间、执行次数等. + long eachStart = System.nanoTime(); + runnable.run(); + super.eachMeasures.offer(System.nanoTime() - eachStart); + super.success.increment(); + } catch (Exception e) { + super.failure.increment(); + if (printErrorLog) { + log.error("【stalker 错误】测量方法耗时信息出错!", e); + } + } + } + }); + + // 到指定的持续时间之后,就取消执行中的任务,并关闭线程池. + // 注意,由于是定时任务,所以“是否取消”也设置为 false,用于区分是否是人为取消了任务,只有人为取消的才是 true. + final RunDuration duration = options.getDuration(); + this.scheduledFuture = this.scheduledExecutorService.schedule(() -> { + this.stop(); + super.canceled.compareAndSet(true, false); + }, duration.getAmount(), duration.getTimeUnit()); + + // 阻塞调用要执行的测量任务,达到阻塞等待任务结束的目的. + try { + this.measureFuture.get(); + } catch (CancellationException e) { + log.info("【Stalker 提示】已取消或完成指定运行时间的测量任务."); + } catch (Exception e) { + log.error("【Stalker 错误】执行测量任务发生错误!", e); + } + + // 如果没有设置相关的结束信息资源,就设置,没有关闭相关的资源就进行关闭. + super.setEndNanoTimeIfEmpty(System.nanoTime()); + super.completed.compareAndSet(false, true); + StalkerExecutors.shutdown(super.executorService, this.scheduledExecutorService); + return super.getMeasureResult(); + } + + /** + * 停止相关的运行测量任务. + * + * @author blinkfox on 2020-05-25. + * @since v1.2.0 + */ + @Override + public void stop() { + super.stop(); + + // 关闭定时任务线程池和取消对应的定时任务. + StalkerExecutors.shutdown(this.scheduledExecutorService); + if (this.scheduledFuture != null && !this.scheduledFuture.isDone()) { + this.scheduledFuture.cancel(true); + } + } + +} diff --git a/src/main/java/com/blinkfox/stalker/runner/executor/StalkerExecutors.java b/src/main/java/com/blinkfox/stalker/runner/executor/StalkerExecutors.java new file mode 100644 index 0000000..0013b72 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/runner/executor/StalkerExecutors.java @@ -0,0 +1,102 @@ +package com.blinkfox.stalker.runner.executor; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * Stalker 中使用到的线程池执行器工具类. + * + * @author blinkfox on 2020-06-02. + * @since v1.2.0 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class StalkerExecutors { + + private static final int MAX_QUEUE_SIZE = 524280; + + public static final int MAX_POOL_SIZE = 1024; + + /** + * 根据线程名称创建新的单线程线程池. + * + * @param threadName 线程名称 + * @return 线程池 + */ + public static ThreadPoolExecutor newSingleThreadExecutor(String threadName) { + return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(2), r -> new Thread(r, threadName), new StalkerRejectedHandler()); + } + + /** + * 根据线程名称创建固定数量线程的线程池,最大线程数最多 1024 个. + * + * @param corePoolSize 核心线程数 + * @param threadName 线程名称 + * @return 线程池 + */ + public static ThreadPoolExecutor newFixedThreadExecutor(int corePoolSize, String threadName) { + int fixedPoolSize = Math.min(corePoolSize, MAX_POOL_SIZE); + return new ThreadPoolExecutor(fixedPoolSize, fixedPoolSize, 0L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), r -> new Thread(r, threadName), + new StalkerRejectedHandler()); + } + + /** + * 根据线程名称创建固定数量线程的线程池,最大线程数最多 1024 个. + * + * @param corePoolSize 核心线程数 + * @param maxPoolSize 最大线程数 + * @param threadName 线程名称 + * @return 线程池 + */ + public static ThreadPoolExecutor newThreadExecutor(int corePoolSize, int maxPoolSize, String threadName) { + return new ThreadPoolExecutor(corePoolSize, maxPoolSize, 30L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), r -> new Thread(r, threadName), + new StalkerRejectedHandler()); + } + + /** + * 根据线程名称创建新的可调度定时任务的程线程池. + * + * @param corePoolSize 核心线程数 + * @param threadName 线程名称 + * @return 线程池 + */ + public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, String threadName) { + return new ScheduledThreadPoolExecutor(corePoolSize, + r -> new Thread(r, threadName), new StalkerRejectedHandler()); + } + + /** + * 等待所有线程执行完毕,并最终关闭这若干个线程池集合. + * + * @param executorServices 若干个待关闭的线程池执行器集合 + */ + public static void shutdown(ExecutorService... executorServices) { + for (ExecutorService executorService : executorServices) { + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + } + } + } + + /** + * 立即安静的关闭若干个线程池集合. + * + * @param executorServices 若干个待立即关闭的线程池执行器集合 + */ + public static void shutdownNow(ExecutorService... executorServices) { + for (ExecutorService executorService : executorServices) { + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdownNow(); + } + } + } + +} diff --git a/src/main/java/com/blinkfox/stalker/runner/executor/StalkerRejectedHandler.java b/src/main/java/com/blinkfox/stalker/runner/executor/StalkerRejectedHandler.java new file mode 100644 index 0000000..22a1f21 --- /dev/null +++ b/src/main/java/com/blinkfox/stalker/runner/executor/StalkerRejectedHandler.java @@ -0,0 +1,27 @@ +package com.blinkfox.stalker.runner.executor; + +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import lombok.extern.slf4j.Slf4j; + +/** + * Stalker 中线程池队列满了之后的拒绝执行策略. + * + * @author blinkfox on 2020-06-02. + * @since v1.2.0 + */ +@Slf4j +public class StalkerRejectedHandler implements RejectedExecutionHandler { + + /** + * 当线程池队列满了之后,将拒绝接收新的任务,即放弃任务并打印出 {@code warn} 级别的日志基于警示. + * + * @param r 可运行任务 + * @param executor 线程池执行器 + */ + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + log.warn("【Stalker 警示】线程池队列任务已满,将拒绝接收新的执行任务,建议你调低运行的【并发数】。"); + } + +} diff --git a/src/test/java/com/blinkfox/stalker/config/ScheduledUpdaterTest.java b/src/test/java/com/blinkfox/stalker/config/ScheduledUpdaterTest.java new file mode 100644 index 0000000..50d1cd0 --- /dev/null +++ b/src/test/java/com/blinkfox/stalker/config/ScheduledUpdaterTest.java @@ -0,0 +1,85 @@ +package com.blinkfox.stalker.config; + +import java.util.concurrent.TimeUnit; +import org.junit.Assert; +import org.junit.Test; + +/** + * {@link ScheduledUpdater} 的单元测试类. + * + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ +public class ScheduledUpdaterTest { + + @Test + public void of() { + ScheduledUpdater scheduledUpdater = ScheduledUpdater.of(true, 2, 3, TimeUnit.SECONDS); + Assert.assertTrue(scheduledUpdater.isEnabled()); + Assert.assertEquals(2, scheduledUpdater.getInitialDelay()); + Assert.assertEquals(3, scheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.SECONDS, scheduledUpdater.getTimeUnit()); + } + + @Test + public void ofEnable() { + ScheduledUpdater scheduledUpdater = ScheduledUpdater.ofEnable(); + Assert.assertTrue(scheduledUpdater.isEnabled()); + Assert.assertEquals(5, scheduledUpdater.getInitialDelay()); + Assert.assertEquals(5, scheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.SECONDS, scheduledUpdater.getTimeUnit()); + } + + @Test + public void ofDisable() { + ScheduledUpdater scheduledUpdater = ScheduledUpdater.ofDisable(); + Assert.assertFalse(scheduledUpdater.isEnabled()); + Assert.assertEquals(5, scheduledUpdater.getInitialDelay()); + Assert.assertEquals(5, scheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.SECONDS, scheduledUpdater.getTimeUnit()); + } + + @Test + public void of2() { + ScheduledUpdater scheduledUpdater = ScheduledUpdater.of(3, TimeUnit.MINUTES); + Assert.assertTrue(scheduledUpdater.isEnabled()); + Assert.assertEquals(3, scheduledUpdater.getInitialDelay()); + Assert.assertEquals(3, scheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.MINUTES, scheduledUpdater.getTimeUnit()); + } + + @Test + public void of3() { + ScheduledUpdater scheduledUpdater = ScheduledUpdater.ofSeconds(7); + Assert.assertTrue(scheduledUpdater.isEnabled()); + Assert.assertEquals(7, scheduledUpdater.getInitialDelay()); + Assert.assertEquals(7, scheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.SECONDS, scheduledUpdater.getTimeUnit()); + } + + @Test + public void of4() { + ScheduledUpdater scheduledUpdater = ScheduledUpdater.ofMinutes(2); + Assert.assertTrue(scheduledUpdater.isEnabled()); + Assert.assertEquals(2, scheduledUpdater.getInitialDelay()); + Assert.assertEquals(2, scheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.MINUTES, scheduledUpdater.getTimeUnit()); + + scheduledUpdater.disable(); + Assert.assertFalse(scheduledUpdater.isEnabled()); + + scheduledUpdater.enable(); + Assert.assertTrue(scheduledUpdater.isEnabled()); + } + + @Test(expected = IllegalArgumentException.class) + public void checkDelay() { + ScheduledUpdater.ofMinutes(0); + } + + @Test(expected = IllegalArgumentException.class) + public void checkParams() { + ScheduledUpdater.of(1, TimeUnit.MILLISECONDS); + } + +} \ No newline at end of file diff --git a/src/test/java/com/blinkfox/stalker/test/StalkerTest.java b/src/test/java/com/blinkfox/stalker/test/StalkerTest.java index 2a1f3d2..b81593a 100644 --- a/src/test/java/com/blinkfox/stalker/test/StalkerTest.java +++ b/src/test/java/com/blinkfox/stalker/test/StalkerTest.java @@ -2,7 +2,12 @@ import com.blinkfox.stalker.Stalker; import com.blinkfox.stalker.config.Options; +import com.blinkfox.stalker.result.MeasureResult; +import com.blinkfox.stalker.result.StalkerFuture; import com.blinkfox.stalker.test.prepare.MyTestService; +import java.util.List; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; @@ -12,6 +17,7 @@ * @author blinkfox on 2019-02-03. * @since v1.0.0 */ +@Slf4j public class StalkerTest { /** @@ -30,6 +36,30 @@ public void run() { Stalker.run(Options.of(100, 20), () -> new MyTestService().hello()); } + /** + * 测试有 duration 选项参数时的执行情况. + */ + @Test + public void runWithDuration() { + Stalker.run(Options.ofDuration(1, TimeUnit.SECONDS), () -> new MyTestService().hello()); + } + + /** + * 测试有 duration 选项参数时的执行情况. + */ + @Test + public void runWithDurationConcurrent() { + Stalker.run(Options.ofDurationSeconds(1, 3), () -> new MyTestService().hello()); + } + + /** + * 测试有 duration 选项参数时的执行情况. + */ + @Test(expected = IllegalArgumentException.class) + public void runWithDurationException() { + Stalker.run(Options.ofDuration(2, TimeUnit.MILLISECONDS), () -> new MyTestService().hello()); + } + /** * 测试简单无并发的执行情况. */ @@ -92,4 +122,140 @@ public void runWithConcurrentException() { () -> new MyTestService().helloException()); } + /** + * 测试慢方法的执行情况. + */ + @Test + public void runWithSlowMethod() { + Stalker.run(Options.of("SlowTest", 20, 5, 1), + () -> new MyTestService().slowHello()); + } + + /** + * 测试没有Options选项参数时的执行情况. + */ + @Test + public void submit() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(() -> new MyTestService().hello()); + Assert.assertNotNull(stalkerFuture); + + while (!stalkerFuture.isDone()) { + List results = stalkerFuture.get(); + Assert.assertNotNull(results.get(0)); + Thread.sleep(2L); + } + + log.info("任务已完成,获取最后的执行结果,并移除任务记录."); + stalkerFuture.get(); + } + + /** + * 测试慢方法的执行情况. + */ + @Test + public void submitWithSlowMethod() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.of("SlowTest", 20, 5), + () -> new MyTestService().slowHello()); + Assert.assertNotNull(stalkerFuture); + + while (!stalkerFuture.isDone()) { + List results = stalkerFuture.get(); + Assert.assertNotNull(results.get(0)); + Thread.sleep(50L); + } + + log.info("任务已完成,获取最后的执行结果."); + stalkerFuture.get(); + } + + /** + * 测试慢方法的执行情况. + */ + @Test + public void submitWithSlowMethodDuration() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.ofDurationSeconds(2, 4), + () -> new MyTestService().slowHello()); + Assert.assertNotNull(stalkerFuture); + Assert.assertEquals(0, stalkerFuture.getEndNanoTime()); + + while (!stalkerFuture.isDone()) { + List results = stalkerFuture.get(); + Assert.assertNotNull(results.get(0)); + Thread.sleep(1000L); + } + + log.info("任务已完成,获取最后的执行结果,并移除任务记录."); + stalkerFuture.get(); + Assert.assertTrue(stalkerFuture.getStartNanoTime() > 0); + Assert.assertTrue(stalkerFuture.isDoneSuccessfully()); + Assert.assertEquals(stalkerFuture.getTotal(), stalkerFuture.getSuccess() + stalkerFuture.getFailure()); + Assert.assertTrue(stalkerFuture.getCosts() > 0); + } + + /** + * 测试 queryMeasurement 方法. + */ + @Test + public void queryMeasureResult() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(() -> new MyTestService().hello()); + Assert.assertNotNull(stalkerFuture); + + while (!stalkerFuture.isDone()) { + MeasureResult measureResult = stalkerFuture.getMeasureResult(); + Assert.assertNotNull(measureResult); + Thread.sleep(5L); + } + + Assert.assertFalse(stalkerFuture.isCancelled()); + Assert.assertNotNull(stalkerFuture.getMeasureResult()); + } + + /** + * 测试慢方法的执行情况. + */ + @Test + public void submitWithStop() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.of("StopTest", 20, 5, 1), + () -> new MyTestService().slowHello()); + Assert.assertNotNull(stalkerFuture); + + Thread.sleep(50L); + List results = stalkerFuture.get(); + Assert.assertNotNull(results.get(0)); + boolean isCancelled = stalkerFuture.cancel(); + if (isCancelled) { + log.info("任务已停止,获取停止前的执行结果."); + } else { + log.info("任务停止失败."); + } + + stalkerFuture.get(); + Thread.sleep(100L); + + log.info("任务已停止,再次获取最后的执行结果,判断内容是否一致."); + stalkerFuture.get(); + } + + /** + * 测试慢方法的执行情况. + */ + @Test + public void submitWithDuration() throws InterruptedException { + StalkerFuture stalkerFuture = Stalker.submit(Options.ofDurationSeconds(15, 5), + () -> new MyTestService().slowHello()); + Assert.assertNotNull(stalkerFuture); + Assert.assertEquals(0, stalkerFuture.getEndNanoTime()); + + while (!stalkerFuture.isDone()) { + Thread.sleep(5000L); + } + + log.info("任务已完成,获取最终的执行结果信息."); + stalkerFuture.get(); + Assert.assertTrue(stalkerFuture.getStartNanoTime() > 0); + Assert.assertTrue(stalkerFuture.isDoneSuccessfully()); + Assert.assertEquals(stalkerFuture.getTotal(), stalkerFuture.getSuccess() + stalkerFuture.getFailure()); + Assert.assertTrue(stalkerFuture.getCosts() > 0); + } + } diff --git a/src/test/java/com/blinkfox/stalker/test/config/StalkerConfigManagerTest.java b/src/test/java/com/blinkfox/stalker/test/config/StalkerConfigManagerTest.java index a2ea098..1717bac 100644 --- a/src/test/java/com/blinkfox/stalker/test/config/StalkerConfigManagerTest.java +++ b/src/test/java/com/blinkfox/stalker/test/config/StalkerConfigManagerTest.java @@ -1,7 +1,9 @@ package com.blinkfox.stalker.test.config; import com.blinkfox.stalker.config.Options; +import com.blinkfox.stalker.config.ScheduledUpdater; import com.blinkfox.stalker.config.StalkerConfigManager; +import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; @@ -19,11 +21,31 @@ public void testConfigOptions() { Assert.assertNotNull(stalkerConfigManager); Options options = stalkerConfigManager.getDefaultOptions(); - Assert.assertEquals(10, options.getRuns()); + Assert.assertEquals(1, options.getRuns()); options.named("test"); stalkerConfigManager.reLoadOptions(options); Assert.assertEquals("test", options.getName()); } + /** + * 测试加载获取 {@link ScheduledUpdater} 的配置参数信息. + * + * @author blinkfox on 2020-06-06. + * @since v1.2.0 + */ + @Test + public void testConfigOptions2() { + StalkerConfigManager stalkerConfigManager = StalkerConfigManager.getInstance(); + + Options options = stalkerConfigManager.getDefaultOptions(); + options.runs(1); + stalkerConfigManager.reLoadOptions(options, ScheduledUpdater.ofMinutes(2)); + Assert.assertEquals(1, options.getRuns()); + + ScheduledUpdater defaultScheduledUpdater = stalkerConfigManager.getDefaultScheduledUpdater(); + Assert.assertEquals(2, defaultScheduledUpdater.getDelay()); + Assert.assertEquals(TimeUnit.MINUTES, defaultScheduledUpdater.getTimeUnit()); + } + } \ No newline at end of file diff --git a/src/test/java/com/blinkfox/stalker/kit/MathKitTest.java b/src/test/java/com/blinkfox/stalker/test/kit/MathKitTest.java similarity index 83% rename from src/test/java/com/blinkfox/stalker/kit/MathKitTest.java rename to src/test/java/com/blinkfox/stalker/test/kit/MathKitTest.java index f848f18..9d9842c 100644 --- a/src/test/java/com/blinkfox/stalker/kit/MathKitTest.java +++ b/src/test/java/com/blinkfox/stalker/test/kit/MathKitTest.java @@ -1,5 +1,6 @@ -package com.blinkfox.stalker.kit; +package com.blinkfox.stalker.test.kit; +import com.blinkfox.stalker.kit.MathKit; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/com/blinkfox/stalker/test/kit/StrKitTest.java b/src/test/java/com/blinkfox/stalker/test/kit/StrKitTest.java index 3d0f357..28659cd 100644 --- a/src/test/java/com/blinkfox/stalker/test/kit/StrKitTest.java +++ b/src/test/java/com/blinkfox/stalker/test/kit/StrKitTest.java @@ -60,4 +60,12 @@ public void getRoundString() { Assert.assertEquals("27.00", StrKit.roundToString(26.998)); } + /** + * 测试获取 UUID. + */ + @Test + public void get62RadixUuid() { + Assert.assertEquals(19, StrKit.get62RadixUuid().length()); + } + } \ No newline at end of file diff --git a/src/test/java/com/blinkfox/stalker/test/output/MeasureOutputContextTest.java b/src/test/java/com/blinkfox/stalker/test/output/MeasureOutputContextTest.java index 3a620d5..9ce9e7c 100644 --- a/src/test/java/com/blinkfox/stalker/test/output/MeasureOutputContextTest.java +++ b/src/test/java/com/blinkfox/stalker/test/output/MeasureOutputContextTest.java @@ -3,8 +3,7 @@ import com.blinkfox.stalker.config.Options; import com.blinkfox.stalker.output.MeasureOutput; import com.blinkfox.stalker.output.MeasureOutputContext; -import com.blinkfox.stalker.result.bean.Measurement; -import com.blinkfox.stalker.result.bean.OverallResult; +import com.blinkfox.stalker.result.MeasureResult; import java.util.ArrayList; import java.util.List; import org.junit.Test; @@ -23,10 +22,8 @@ public class MeasureOutputContextTest { @Test public void outputWithoutOutputs() { List outputs = null; - Measurement measurement = new Measurement(new OverallResult()); - - new MeasureOutputContext().output(Options.of().outputs(outputs), measurement); - new MeasureOutputContext().output(Options.of().outputs(new ArrayList<>()), measurement); + new MeasureOutputContext().output(Options.of().outputs(outputs), new MeasureResult()); + new MeasureOutputContext().output(Options.of().outputs(new ArrayList<>()), new MeasureResult()); } } diff --git a/src/test/java/com/blinkfox/stalker/test/output/OutputConsoleTest.java b/src/test/java/com/blinkfox/stalker/test/output/OutputConsoleTest.java index 26f31ce..4da30e3 100644 --- a/src/test/java/com/blinkfox/stalker/test/output/OutputConsoleTest.java +++ b/src/test/java/com/blinkfox/stalker/test/output/OutputConsoleTest.java @@ -2,8 +2,7 @@ import com.blinkfox.stalker.config.Options; import com.blinkfox.stalker.output.OutputConsole; -import com.blinkfox.stalker.result.bean.Measurement; -import com.blinkfox.stalker.result.bean.OverallResult; +import com.blinkfox.stalker.result.MeasureResult; import org.junit.Test; /** @@ -19,7 +18,7 @@ public class OutputConsoleTest { */ @Test(expected = IllegalArgumentException.class) public void outputWithNullOptions() { - new OutputConsole().output(null, new Measurement(new OverallResult())); + new OutputConsole().output(null, new MeasureResult()); } /** diff --git a/src/test/java/com/blinkfox/stalker/test/prepare/MyTestService.java b/src/test/java/com/blinkfox/stalker/test/prepare/MyTestService.java index 72611a5..106e01d 100644 --- a/src/test/java/com/blinkfox/stalker/test/prepare/MyTestService.java +++ b/src/test/java/com/blinkfox/stalker/test/prepare/MyTestService.java @@ -13,7 +13,7 @@ public class MyTestService { /** - * 测试方法1,模拟业务代码耗时 2~5 ms,且会有约 5% 的几率执行异常. + * 测试方法1,模拟业务代码耗时 2~5 ms,且会有约 1% 的几率执行异常. */ public void hello() { // 模拟运行抛出异常. @@ -29,7 +29,20 @@ public void hello() { * 测试方法2,模拟业务代码耗时 2 ms. */ public void fastHello() { - this.sleep(2L); + this.sleep(1L); + } + + /** + * 测试方法3,模拟业务代码耗时 20~100 ms,且会有约 3% 的几率执行异常. + */ + public void slowHello() { + // 模拟运行抛出异常. + if (new Random().nextInt(100) < 3) { + throw new MyServiceException("My Service Exception."); + } + + // 模拟运行占用约 20~100 ms 的时间. + this.sleep(20L + new Random().nextInt(80)); } /** @@ -41,8 +54,7 @@ private void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { - log.info("InterruptedException", e); - Thread.currentThread().interrupt(); + log.error("【Stalker 提示】本方法的执行已中断,异常简述为:【{}】.", e.getMessage()); } } diff --git a/src/test/java/com/blinkfox/stalker/test/result/bean/MeasureResultTest.java b/src/test/java/com/blinkfox/stalker/test/result/bean/MeasureResultTest.java new file mode 100644 index 0000000..1d3ac1c --- /dev/null +++ b/src/test/java/com/blinkfox/stalker/test/result/bean/MeasureResultTest.java @@ -0,0 +1,22 @@ +package com.blinkfox.stalker.test.result.bean; + +import com.blinkfox.stalker.result.MeasureResult; +import org.junit.Assert; +import org.junit.Test; + +/** + * {@link MeasureResult} 的单元测试类. + * + * @author blinkfox on 2019-02-04. + * @since v1.0.0 + */ +public class MeasureResultTest { + + @Test + public void testToString() { + MeasureResult measureResult = new MeasureResult(); + measureResult.setMax(30); + Assert.assertNotNull(measureResult.toString()); + } + +} diff --git a/src/test/java/com/blinkfox/stalker/test/result/bean/MeasurementTest.java b/src/test/java/com/blinkfox/stalker/test/result/bean/MeasurementTest.java deleted file mode 100644 index 0ce3132..0000000 --- a/src/test/java/com/blinkfox/stalker/test/result/bean/MeasurementTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.blinkfox.stalker.test.result.bean; - -import com.blinkfox.stalker.result.bean.Measurement; -import com.blinkfox.stalker.result.bean.OverallResult; -import com.blinkfox.stalker.result.bean.StatisResult; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * MeasurementTest. - * - * @author blinkfox on 2019-02-04. - * @since v1.0.0 - */ -public class MeasurementTest { - - private static Measurement measurement; - - /** - * 初始化. - */ - @BeforeClass - public static void init() { - measurement = new Measurement(new OverallResult()); - measurement.setStatisResult(new StatisResult()); - } - - @Test - public void getOverallResult() { - Assert.assertNotNull(measurement.getOverallResult()); - } - - @Test - public void getStatisResult() { - Assert.assertNotNull(measurement.getStatisResult()); - } - -} diff --git a/src/test/java/com/blinkfox/stalker/test/result/bean/OverallResultTest.java b/src/test/java/com/blinkfox/stalker/test/result/bean/OverallResultTest.java deleted file mode 100644 index a25c4da..0000000 --- a/src/test/java/com/blinkfox/stalker/test/result/bean/OverallResultTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.blinkfox.stalker.test.result.bean; - -import com.blinkfox.stalker.result.bean.OverallResult; -import org.junit.Assert; -import org.junit.Test; - -/** - * OverallResultTest. - * - * @author blinkfox on 2019-02-04. - * @since v1.0.0 - */ -public class OverallResultTest { - - @Test - public void testToString() { - Assert.assertNotNull(new OverallResult() - .setEachMeasures(new long[] {20, 10}) - .setCosts(40) - .setTotal(5) - .setSuccess(5) - .setFailure(0).toString()); - } - -} diff --git a/src/test/java/com/blinkfox/stalker/test/result/bean/StatisResultTest.java b/src/test/java/com/blinkfox/stalker/test/result/bean/StatisResultTest.java deleted file mode 100644 index dc83dfd..0000000 --- a/src/test/java/com/blinkfox/stalker/test/result/bean/StatisResultTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.blinkfox.stalker.test.result.bean; - -import com.blinkfox.stalker.result.bean.StatisResult; -import org.junit.Assert; -import org.junit.Test; - -/** - * StatisResultTest. - * - * @author blinkfox on 2019-02-04. - * @since v1.0.0 - */ -public class StatisResultTest { - - @Test - public void testToString() { - StatisResult statisResult = new StatisResult(); - statisResult.setMax(30); - Assert.assertNotNull(statisResult.toString()); - } - -} diff --git a/src/test/java/com/blinkfox/stalker/test/result/statis/DefaultMeasureStatisTest.java b/src/test/java/com/blinkfox/stalker/test/result/statis/DefaultMeasureStatisTest.java deleted file mode 100644 index 64325a2..0000000 --- a/src/test/java/com/blinkfox/stalker/test/result/statis/DefaultMeasureStatisTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.blinkfox.stalker.test.result.statis; - -import com.blinkfox.stalker.result.bean.OverallResult; -import com.blinkfox.stalker.result.statis.DefaultMeasureStatis; -import org.junit.Assert; -import org.junit.Test; - -/** - * DefaultMeasureStatisTest. - * - * @author blinkfox on 2019-02-04. - * @since v1.0.0 - */ -public class DefaultMeasureStatisTest { - - @Test - public void statis() { - OverallResult overallResult = new OverallResult(); - Assert.assertEquals(0L, new DefaultMeasureStatis().statis(overallResult).getSum()); - - overallResult.setEachMeasures(new long[] {}); - Assert.assertEquals(0L, new DefaultMeasureStatis().statis(overallResult).getSum()); - } - -}