1. 概要

パフォーマンステストは、ソフトウェア開発サイクルの最終段階に向けて推進されることが多いアクティビティです。 通常、パフォーマンスの問題のトラブルシューティングを支援するために、Javaプロファイラーに依存しています。

このチュートリアルでは、Java(SPF4J)のシンプルパフォーマンスフレームワークについて説明します。 コードに追加できるAPIを提供します。 その結果、パフォーマンス監視をコンポーネントの不可欠な部分にすることができます。

2. メトリックのキャプチャと視覚化の基本概念

始める前に、簡単な例を使用して、メトリックのキャプチャと視覚化の概念を理解してみましょう。

アプリストアで新しくリリースされたアプリのダウンロードを監視することに関心があると想像してみてください。 学習のために、この実験を手動で行うことを考えてみましょう。

2.1. 指標の取得

まず、何を測定する必要があるかを決定する必要があります。 関心のある指標はダウンロード/分。 したがって ダウンロード数を測定します。

次に、どのくらいの頻度で測定を行う必要がありますか? 「1分に1回」を決めましょう。

最後に、どのくらいの期間監視する必要がありますか? 「1時間」を決めましょう。

これらのルールが整ったら、実験を行う準備が整います。 実験が終了すると、結果を確認できます。

Time	Cumulative Downloads	Downloads/min
----------------------------------------------
T       497                     0  
T+1     624                     127
T+2     676                     52
...     
T+14    19347                   17390
T+15    19427                   80
...  
T+22    27195                   7350
...  
T+41    41321                   11885
...   
T+60    43395                   40

最初の2つの列(timecumulativedownloads )は、私たちが観察する直接的な値です。 3番目の列downloads/ min は、現在と以前の累積ダウンロード値の差として計算された派生値です。 これにより、その期間中の実際のダウンロード数がわかります。

2.2. 指標の視覚化

時間ダウンロード/分の単純な線グラフをプロットしてみましょう。

いくつかのピークがいくつかの機会に発生した多数のダウンロードを示していることがわかります。 ダウンロード軸に使用される線形スケールのため、低い値は直線として表示されます。

downloads 軸を変更して、対数スケール(基数10)を使用し、対数/線形グラフをプロットしてみましょう。

これで、実際には低い値が表示され始めます。 そして、それらは100(+/-)に近づいています。 線形グラフは、ピークも含まれているため、703の平均を示していることに注意してください。

ピークを収差として除外する場合、対数/線形グラフを使用して実験から結論付けることができます。

  • 平均ダウンロード/分は100秒のオーダーです

3. 関数呼び出しのパフォーマンス監視

前の例から単純なメトリックをキャプチャして分析する方法を理解したので、それを単純なJavaメソッドに適用してみましょう— isPrimeNumber

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

SPF4Jを使用して、メトリックをキャプチャする2つの方法があります。 次のセクションでそれらを調べてみましょう。

4. セットアップと構成

4.1. Mavenのセットアップ

SPF4Jは、さまざまな目的のためにさまざまなライブラリを提供しますが、簡単な例ではいくつかしか必要ありません。

コアライブラリはspf4j-coreで、必要な機能のほとんどを提供します。

これをMaven依存関係として追加しましょう:

<dependency>
    <groupId>org.spf4j</groupId>
    <artifactId>spf4j-core</artifactId>
    <version>8.6.10</version>
</dependency>

パフォーマンス監視に適したライブラリがあります— spf4j-aspects、AspectJを使用する

この例ではこれについて説明しますので、これも追加しましょう。

<dependency>
    <groupId>org.spf4j</groupId>
    <artifactId>spf4j-aspects</artifactId>
    <version>8.6.10</version>
</dependency>

そして最後に、SPF4Jにはデータの視覚化に非常に役立つシンプルなUIも付属しているので、spf4j-uiも追加しましょう。

<dependency>
    <groupId>org.spf4j</groupId>
    <artifactId>spf4j-ui</artifactId>
    <version>8.6.10</version>
</dependency>

4.2. 出力ファイルの構成

SPF4Jフレームワークは、データを時系列データベース(TSDB)に書き込み、オプションでテキストファイルに書き込むこともできます。

両方を構成し、システムプロパティspf4j.perf.ms.configを設定しましょう。

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

4.3. レコーダーとソース

SPF4Jフレームワークのコア機能は、メトリックを記録、集約、および保存することであるため、分析時に後処理は必要ありません。 これは、MeasurementRecorderおよびMeasurementRecorderSourceクラスを使用して行われます。

これらの2つのクラスは、メトリックを記録するための2つの異なる方法を提供します。 主な違いは、 MeasurementRecorderはどこからでも呼び出すことができるのに対し、MeasurementRecorderSourceはアノテーションでのみ使用されることです。

このフレームワークは、 RecorderFactory クラスを提供して、さまざまなタイプの集計用のレコーダーおよびレコーダーソースクラスのインスタンスを作成します。

  • createScalableQuantizedRecorder()および createScalableQuantizedRecorderSource()
  • createScalableCountingRecorder()および createScalableCountingRecorderSource()
  • createScalableMinMaxAvgRecorder()および createScalableMinMaxAvgRecorderSource()
  • createDirectRecorder()および createDirectRecorderSource()

この例では、スケーラブルな量子化された集計を選択しましょう。

4.4. レコーダーの作成

まず、MeasurementRecorderのインスタンスを作成するためのヘルパーメソッドを作成しましょう。

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

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

さまざまな設定を見てみましょう。

  • unitOfMeasurement –測定される単位値–パフォーマンス監視シナリオの場合、通常は時間の単位です。
  • sampleTimeMillis –測定を行う期間–つまり、測定を行う頻度
  • factor –測定値のプロットに使用される対数スケールのベース
  • lowerMagnitude –対数スケールの最小値–対数ベース10の場合、 lowerMagnitude = 0は、10の0の累乗を意味します=1
  • higherMagnitude –対数スケールの最大値–対数ベース10の場合、 higherMagnitude = 4は、10の4の累乗を意味します= 10,000
  • quantasPerMagnitude –マグニチュード内のセクション数–マグニチュードが1,000から10,000の範囲の場合、 quantasPerMagnitude = 10は、範囲が10のサブ範囲に分割されることを意味します

必要に応じて値を変更できることがわかります。 したがって、異なる測定値に対して個別のMeasurementRecorderインスタンスを作成することをお勧めします。

4.5. ソースの作成

次に、別のヘルパーメソッドを使用してMeasurementRecorderSourceのインスタンスを作成しましょう。

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

以前と同じ値を設定に使用していることに注意してください。

4.6. 構成クラスの作成

次に、便利な Spf4jConfig クラスを作成し、その中に上記のすべてのメソッドを配置しましょう。

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

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

    public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
        //...
    }
}

4.7. aop.xmlの構成

SPF4Jは、パフォーマンスの測定と監視を行うメソッドに注釈を付けるオプションを提供します。 AspectJ ライブラリを使用します。これにより、コード自体を変更することなく、パフォーマンスの監視に必要な動作を既存のコードに追加できます。

ロード時ウィーバーを使用してクラスとアスペクトを織り、aop.xmlMETA-INFフォルダーの下に置きましょう。

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

5. MeasurementRecorderを使用する

MeasurementRecorder を使用して、テスト関数のパフォーマンスメトリックを記録する方法を見てみましょう。

5.1. 指標の記録

100個の乱数を生成し、ループで素数チェックメソッドを呼び出しましょう。 その前に、 Spf4jConfig クラスを呼び出して初期化を行い、MeasureRecorderクラスのインスタンスを作成しましょう。 このインスタンスを使用して、 record()メソッドを呼び出して、100回の isPrimeNumber()呼び出しにかかる個々の時間を節約しましょう。

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

5.2. コードの実行

これで、単純な関数 isPrimeNumber ()のパフォーマンスをテストする準備が整いました。

コードを実行して結果を見てみましょう。

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

5.3. 結果の表示

プロジェクトフォルダからコマンドを実行して、SPF4JUIを起動しましょう。

java -jar target/dependency-jars/spf4j-ui-8.6.9.jar

これにより、デスクトップUIアプリケーションが表示されます。 次に、メニューからファイル>開くを選択します。 その後、参照ウィンドウを使用してspf4j-performance-monitoring.tsdb2ファイルを見つけて開きます。

これで、ファイル名と子アイテムを含むツリービューで新しいウィンドウが開くのを確認できます。 子アイテムをクリックしてから、その上のプロットボタンをクリックしてみましょう。

これにより、一連のグラフが生成されます。

最初のグラフ測定分布は、前に見た対数線形グラフのバリエーションです。 このグラフは、カウントに基づくヒートマップを追加で示しています。

2番目のグラフは、最小、最大、平均などの集計データを示しています。

そして最後のグラフは、時間に対する測定の数を示しています。

6. MeasurementRecorderSourceを使用する

前のセクションでは、測定値を記録するために、機能の周りに追加のコードを記述する必要がありました。 このセクションでは、これを回避するために別のアプローチを使用しましょう。

6.1. 指標の記録

まず、指標をキャプチャして記録するために追加された余分なコードを削除します。

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

次に、すべての定型文の代わりに、 @PerformanceMonitorを使用してisPrimeNumber()メソッドに注釈を付けましょう。

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

さまざまな設定を見てみましょう。

  • warnThresholdMillis –警告メッセージなしでメソッドを実行できる最大時間
  • errorThresholdMillis –エラーメッセージなしでメソッドを実行できる最大時間
  • recorderSource MeasurementRecorderSourceのインスタンス

6.2. コードの実行

最初にMavenビルドを実行してから、Javaエージェントを渡してコードを実行しましょう。

java -javaagent:target/dependency-jars/aspectjweaver-1.8.13.jar -jar target/spf4j-aspects-app.jar

結果が表示されます。

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

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

SPF4Jフレームワークは、すべてのメソッド呼び出しにかかった時間をログに記録していることがわかります。 また、 errorThresholdMillis の値である100ミリ秒を超えると、エラーとしてログに記録されます。 メソッドに渡された引数もログに記録されます。

6.3. 結果の表示

前のセクションを参照できるように、SPF4JUIを使用して以前に表示したのと同じ方法で結果を表示できます。

7. 結論

この記事では、メトリックのキャプチャと視覚化の基本的な概念について説明しました。

次に、簡単な例を使用して、SPF4Jフレームワークのパフォーマンス監視機能を理解しました。 また、組み込みのUIツールを使用してデータを視覚化しました。

いつものように、この記事の例はGitHubから入手できます。