Spring Boot Actuator の prometheus エンドポイントが出力する CPU 負荷関連項目

## 概要

- Spring Boot Actuator の prometheus エンドポイントが出力する CPU 負荷関連項目 (CPU使用率など) についてまとめる
- prometheus エンドポイントの出力している値が何を示しているか、Micrometer のソースコードの該当箇所を探す

## Spring Boot Actuator について

Spring Boot Actuator は Spring Boot アプリケーションのメトリクス情報などを HTTP などで取得できる機能。

[Spring Boot Actuator: 本番対応機能 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/production-ready-features.html)

> Spring Boot には、アプリケーションを本番環境にプッシュするときにアプリケーションを監視および管理するのに役立つ多くの追加機能が含まれています。HTTP エンドポイントまたは JMX を使用して、アプリケーションを管理および監視することを選択できます。監査、ヘルス、およびメトリックの収集もアプリケーションに自動的に適用できます。

[Spring Boot Actuator: 本番対応機能 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/production-ready-features.html)

> アクチュエーターエンドポイントを使用すると、アプリケーションを監視および操作できます。Spring Boot には多数のエンドポイントが組み込まれており、独自のエンドポイントを追加できます。例：health エンドポイントは、基本的なアプリケーション正常性情報を提供します。

## prometheus エンドポイントについて

Spring Boot アプリケーションの URL パス /actuator/prometheus にアクセスすることで Prometheus 用の出力結果を取得できる。

[Spring Boot Actuator: 本番対応機能 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/production-ready-features.html)

> Prometheus サーバーによってスクレイピング可能な形式でメトリックを公開します。micrometer-registry-prometheus への依存が必要です。

## prometheus エンドポイントが出力する CPU 関連の項目

### system_cpu_count

プロセッサ数を表す。

[Runtime \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Runtime.html)

> public int availableProcessors()
> 
> Java仮想マシンが使用できるプロセッサの数を返します。
> 
> この値は、仮想マシンの呼出し中に変更される可能性があります。 このため、利用可能なプロセッサ数に影響を受けるアプリケーションは、このプロパティをときどきポーリングして、リソース使用法を調整する必要があります。
> 
> 戻り値:
> 仮想マシンから利用可能な最大プロセッサ数。この値が1より小さくなることはない

/actuator/prometheus の出力例。

```
# HELP system_cpu_count The number of processors available to the Java virtual machine
# TYPE system_cpu_count gauge
system_cpu_count 4.0
```

### system_cpu_usage

システム全体の CPU 使用率を表す (JVM 以外のプロセスも含んでいる)。

[OperatingSystemMXBean \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/jdk.management/com/sun/management/OperatingSystemMXBean.html)

> double getSystemCpuLoad()
> 
> システム全体の「最新のCPU使用率」を返します。 これは、[0.0,1.0]間隔のdouble値です。 0.0という値は最近の監視期間中にすべてのCPUがアイドル状態だったことを意味し、1.0という値は最近の監視期間中にすべてのCPUが常時アクティブに動作していたことを意味します。 システム内で進行中のアクティビティに応じて、0.0から1.0までのすべての値があり得ます。 システムの最新のCPU使用率を取得できない場合、このメソッドは負の値を返します。
> 
> 戻り値:
> システム全体の「最新のCPU使用率」。取得できない場合は負の値。

/actuator/prometheus の出力例。

```
# HELP system_cpu_usage The "recent cpu usage" for the whole system
# TYPE system_cpu_usage gauge
system_cpu_usage 0.8520833333333333
```

### process_cpu_usage

JVM の CPU 使用率。

[OperatingSystemMXBean \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/jdk.management/com/sun/management/OperatingSystemMXBean.html)

> double getProcessCpuLoad()
> 
> Java仮想マシン・プロセスの「最近のCPU使用率」を返します。 これは、[0.0,1.0]間隔のdouble値です。 0.0という値は最近の監視期間中にどのCPUもJVMプロセスのスレッドを実行していなかったことを意味し、1.0という値は最近の監視期間中にすべてのCPUが常時アクティブにJVMのスレッドを実行していたことを意味します。 JVMのスレッドには、JVMの内部スレッドだけでなく、アプリケーションのスレッドも含まれます。 JVMプロセスおよびシステム全体で進行中のアクティビティに応じて、0.0から1.0までのすべての値があり得ます。 Java仮想マシンの最新のCPU使用率を取得できない場合、このメソッドは負の値を返します。
> 
> 戻り値:
> Java仮想マシン・プロセスの「最新のCPU使用率」。取得できない場合は負の値。

/actuator/prometheus の出力例。

```
# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
# TYPE process_cpu_usage gauge
process_cpu_usage 0.0191621405839776
```

### system_load_average_1m

最後の1分のシステム負荷平均。

[OperatingSystemMXBean \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.management/java/lang/management/OperatingSystemMXBean.html)

> double getSystemLoadAverage()
> 
> 最後の1分のシステム負荷平均を返します。 システム負荷平均とは、利用可能なプロセッサのキューに入れられた実行可能なエンティティの数と、ある期間で平均した、利用可能なプロセッサで実行されている実行可能なエンティティの数の合計です。 負荷平均の計算方法はオペレーティング・システムによって異なりますが、通常は、減衰時間依存平均が使用されます。
> 
> 負荷平均を利用できない場合は、負の値が返されます。
> 
> このメソッドは、システム負荷のヒントを提供するために設計されていて、頻繁に照会できます。 このメソッドを実装するのに高額な費用を要するプラットフォームでは、負荷平均を利用できないことがあります。
> 
> 戻り値:
> システム負荷平均。利用できない場合は負の値

/actuator/prometheus の出力例。

```
# HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time
# TYPE system_load_average_1m gauge
system_load_average_1m 11.9169921875
```

## Micrometer のソースコードを追う

prometheus エンドポイントの出力している値が何を示しているか、Micrometer のソースコードの該当箇所を探す。

- system.cpu.count → Runtime::availableProcessors
- system.load.average.1m → OperatingSystemMXBean::getSystemLoadAverage
- system.cpu.usage → OperatingSystemMXBean::getSystemCpuLoad
- process.cpu.usage → OperatingSystemMXBean::processCpuUsage

io.micrometer.core.instrument.binder.system.ProcessorMetrics クラスで値を取得するための設定がされている。

[micrometer/ProcessorMetrics\.java at v1\.3\.7 · micrometer\-metrics/micrometer · GitHub](https://github.com/micrometer-metrics/micrometer/blob/v1.3.7/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/ProcessorMetrics.java)

```java
/** List of public, exported interface class names from supported JVM implementations. */
private static final List<String> OPERATING_SYSTEM_BEAN_CLASS_NAMES = Arrays.asList(
    "com.sun.management.OperatingSystemMXBean", // HotSpot
    "com.ibm.lang.management.OperatingSystemMXBean" // J9
);
```

```java
public ProcessorMetrics(Iterable<Tag> tags) {
    this.tags = tags;
    this.operatingSystemBean = ManagementFactory.getOperatingSystemMXBean();
    this.operatingSystemBeanClass = getFirstClassFound(OPERATING_SYSTEM_BEAN_CLASS_NAMES);
    this.systemCpuUsage = detectMethod("getSystemCpuLoad");
    this.processCpuUsage = detectMethod("getProcessCpuLoad");
}

@Override
public void bindTo(MeterRegistry registry) {
    Runtime runtime = Runtime.getRuntime();
    Gauge.builder("system.cpu.count", runtime, Runtime::availableProcessors)
        .tags(tags)
        .description("The number of processors available to the Java virtual machine")
        .register(registry);

    if (operatingSystemBean.getSystemLoadAverage() >= 0) {
        Gauge.builder("system.load.average.1m", operatingSystemBean, OperatingSystemMXBean::getSystemLoadAverage)
            .tags(tags)
            .description("The sum of the number of runnable entities queued to available processors and the number " +
                "of runnable entities running on the available processors averaged over a period of time")
            .register(registry);
    }

    if (systemCpuUsage != null) {
        Gauge.builder("system.cpu.usage", operatingSystemBean, x -> invoke(systemCpuUsage))
            .tags(tags)
            .description("The \"recent cpu usage\" for the whole system")
            .register(registry);
    }

    if (processCpuUsage != null) {
        Gauge.builder("process.cpu.usage", operatingSystemBean, x -> invoke(processCpuUsage))
            .tags(tags)
            .description("The \"recent cpu usage\" for the Java Virtual Machine process")
            .register(registry);
    }
}
```

## Spring Boot Actuator を使用するサンプルコード

環境: Java 11 + Spring Boot 2.2.6 + Micrometer Registry Prometheus 1.3.7 + Gradle 6

### ファイル一覧

```
├── build.gradle
├── settings.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── MyApp.java
        └── resources
            └── application.properties
```

### build.gradle

Spring Boot Actuator を使用するため Spring Boot Actuator Starter を指定する。
Prometheus 用の出力をするため Micrometer Registry Prometheus を指定する。

```groovy
plugins {
  id 'java'
  // Spring Boot Plugin
  id 'org.springframework.boot' version '2.2.6.RELEASE'
  // Spring Dependency Management Plugin
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}

group = 'com.example'
version = '0.0.1'
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {
  // Spring Boot Web Starter
  implementation 'org.springframework.boot:spring-boot-starter-web'
  // Spring Boot Actuator Starter
  implementation 'org.springframework.boot:spring-boot-starter-actuator'
  // Micrometer Registry Prometheus
  implementation 'io.micrometer:micrometer-registry-prometheus:1.3.7'
}
```

### settings.gradle

```groovy
rootProject.name = 'myapp'
```

### src/main/java/com/example/MyApp.java 

```java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

// シンプルなサンプルアプリケーション
@SpringBootApplication
@RestController
public class MyApp {

  public static void main(String[] args) {
    SpringApplication.run(MyApp.class, args);
  }

  @GetMapping("/")
  public Map index() {
    return Map.of("message", "Hello, world.");
  }
}
```

### src/main/resources/application.properties

```properties
# Spring Boot Actuator のすべてのエンドポイントをいったん無効にする設定
management.endpoints.enabled-by-default=false

# Prometheus 用のエンドポイントを有効にする設定
management.endpoint.prometheus.enabled=true
management.endpoints.web.exposure.include=prometheus
```

## サンプル実行例

macOS Catalina + Java 11 (AdoptOpenJDK build 11.0.7+10) + Gradle 6.3 にて Spring Boot アプリケーションを起動。

```
$ gradle bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.6.RELEASE)
```

別のコンソールから以下のように永久ループでアクセスするシェルスクリプトで負荷をかける。

```
#!/bin/sh
while true
do
  curl http://localhost:8080/
done
```

また別のコンソールから curl で Spring Boot Actuator の HTTP エンドポイントにアクセスして値を取得する。

```
$ curl http://localhost:8080/actuator
{"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"prometheus":{"href":"http://localhost:8080/actuator/prometheus","templated":false}}}
```

```
$ curl http://localhost:8080/actuator/prometheus
# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
# TYPE process_cpu_usage gauge
process_cpu_usage 0.0191621405839776
# HELP tomcat_sessions_created_sessions_total  
# TYPE tomcat_sessions_created_sessions_total counter
tomcat_sessions_created_sessions_total 0.0
# HELP system_cpu_usage The "recent cpu usage" for the whole system
# TYPE system_cpu_usage gauge
system_cpu_usage 0.8520833333333333
# HELP system_cpu_count The number of processors available to the Java virtual machine
# TYPE system_cpu_count gauge
system_cpu_count 4.0
# HELP process_start_time_seconds Start time of the process since unix epoch.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1.587801165514E9
# HELP http_server_requests_seconds  
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/prometheus",} 6.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/prometheus",} 0.124753781
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 7040.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 4.218126
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator",} 1.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator",} 0.072301131
# HELP http_server_requests_seconds_max  
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/prometheus",} 0.011892492
http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 0.043555897
http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator",} 0.072301131
# HELP tomcat_sessions_alive_max_seconds  
# TYPE tomcat_sessions_alive_max_seconds gauge
tomcat_sessions_alive_max_seconds 0.0
# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool
# TYPE jvm_buffer_count_buffers gauge
jvm_buffer_count_buffers{id="mapped",} 0.0
jvm_buffer_count_buffers{id="direct",} 10.0
# HELP jvm_threads_live_threads The current number of live threads including both daemon and non-daemon threads
# TYPE jvm_threads_live_threads gauge
jvm_threads_live_threads 21.0
# HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use
# TYPE jvm_memory_committed_bytes gauge
jvm_memory_committed_bytes{area="heap",id="G1 Survivor Space",} 4194304.0
jvm_memory_committed_bytes{area="heap",id="G1 Old Gen",} 5.9768832E7
jvm_memory_committed_bytes{area="nonheap",id="Metaspace",} 4.0660992E7
jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 2555904.0
jvm_memory_committed_bytes{area="heap",id="G1 Eden Space",} 9.7517568E7
jvm_memory_committed_bytes{area="nonheap",id="Compressed Class Space",} 5451776.0
jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 9502720.0
# HELP process_files_open_files The open file descriptor count
# TYPE process_files_open_files gauge
process_files_open_files 65.0
# HELP tomcat_sessions_active_current_sessions  
# TYPE tomcat_sessions_active_current_sessions gauge
tomcat_sessions_active_current_sessions 0.0
# HELP jvm_gc_max_data_size_bytes Max size of old generation memory pool
# TYPE jvm_gc_max_data_size_bytes gauge
jvm_gc_max_data_size_bytes 2.147483648E9
# HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time
# TYPE system_load_average_1m gauge
system_load_average_1m 11.9169921875
# HELP jvm_gc_memory_allocated_bytes_total Incremented for an increase in the size of the young generation memory pool after one GC to before the next
# TYPE jvm_gc_memory_allocated_bytes_total counter
jvm_gc_memory_allocated_bytes_total 3.05135616E8
# HELP jvm_buffer_memory_used_bytes An estimate of the memory that the Java virtual machine is using for this buffer pool
# TYPE jvm_buffer_memory_used_bytes gauge
jvm_buffer_memory_used_bytes{id="mapped",} 0.0
jvm_buffer_memory_used_bytes{id="direct",} 81920.0
# HELP process_uptime_seconds The uptime of the Java virtual machine
# TYPE process_uptime_seconds gauge
process_uptime_seconds 250.055
# HELP jvm_gc_live_data_size_bytes Size of old generation memory pool after a full GC
# TYPE jvm_gc_live_data_size_bytes gauge
jvm_gc_live_data_size_bytes 1.0024192E7
# HELP jvm_gc_pause_seconds Time spent in GC pause
# TYPE jvm_gc_pause_seconds summary
jvm_gc_pause_seconds_count{action="end of minor GC",cause="Metadata GC Threshold",} 1.0
jvm_gc_pause_seconds_sum{action="end of minor GC",cause="Metadata GC Threshold",} 0.021
jvm_gc_pause_seconds_count{action="end of minor GC",cause="G1 Evacuation Pause",} 3.0
jvm_gc_pause_seconds_sum{action="end of minor GC",cause="G1 Evacuation Pause",} 0.049
# HELP jvm_gc_pause_seconds_max Time spent in GC pause
# TYPE jvm_gc_pause_seconds_max gauge
jvm_gc_pause_seconds_max{action="end of minor GC",cause="Metadata GC Threshold",} 0.0
jvm_gc_pause_seconds_max{action="end of minor GC",cause="G1 Evacuation Pause",} 0.022
# HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution
# TYPE jvm_classes_unloaded_classes_total counter
jvm_classes_unloaded_classes_total 0.0
# HELP tomcat_sessions_expired_sessions_total  
# TYPE tomcat_sessions_expired_sessions_total counter
tomcat_sessions_expired_sessions_total 0.0
# HELP jvm_threads_states_threads The current number of threads having NEW state
# TYPE jvm_threads_states_threads gauge
jvm_threads_states_threads{state="runnable",} 7.0
jvm_threads_states_threads{state="blocked",} 0.0
jvm_threads_states_threads{state="waiting",} 11.0
jvm_threads_states_threads{state="timed-waiting",} 3.0
jvm_threads_states_threads{state="new",} 0.0
jvm_threads_states_threads{state="terminated",} 0.0
# HELP process_files_max_files The maximum file descriptor count
# TYPE process_files_max_files gauge
process_files_max_files 10240.0
# HELP tomcat_sessions_rejected_sessions_total  
# TYPE tomcat_sessions_rejected_sessions_total counter
tomcat_sessions_rejected_sessions_total 0.0
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="G1 Survivor Space",} 4194304.0
jvm_memory_used_bytes{area="heap",id="G1 Old Gen",} 1.0024192E7
jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 3.907744E7
jvm_memory_used_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 1185920.0
jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 1.8874368E7
jvm_memory_used_bytes{area="nonheap",id="Compressed Class Space",} 4839600.0
jvm_memory_used_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 9437696.0
# HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine
# TYPE jvm_classes_loaded_classes gauge
jvm_classes_loaded_classes 6995.0
# HELP tomcat_sessions_active_max_sessions  
# TYPE tomcat_sessions_active_max_sessions gauge
tomcat_sessions_active_max_sessions 0.0
# HELP jvm_gc_memory_promoted_bytes_total Count of positive increases in the size of the old generation memory pool before GC to after GC
# TYPE jvm_gc_memory_promoted_bytes_total counter
jvm_gc_memory_promoted_bytes_total 7294864.0
# HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management
# TYPE jvm_memory_max_bytes gauge
jvm_memory_max_bytes{area="heap",id="G1 Survivor Space",} -1.0
jvm_memory_max_bytes{area="heap",id="G1 Old Gen",} 2.147483648E9
jvm_memory_max_bytes{area="nonheap",id="Metaspace",} -1.0
jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 6975488.0
jvm_memory_max_bytes{area="heap",id="G1 Eden Space",} -1.0
jvm_memory_max_bytes{area="nonheap",id="Compressed Class Space",} 1.073741824E9
jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 2.44682752E8
# HELP logback_events_total Number of error level events that made it to the logs
# TYPE logback_events_total counter
logback_events_total{level="warn",} 0.0
logback_events_total{level="debug",} 0.0
logback_events_total{level="error",} 0.0
logback_events_total{level="trace",} 0.0
logback_events_total{level="info",} 7.0
# HELP jvm_threads_peak_threads The peak live thread count since the Java virtual machine started or peak was reset
# TYPE jvm_threads_peak_threads gauge
jvm_threads_peak_threads 21.0
# HELP jvm_threads_daemon_threads The current number of live daemon threads
# TYPE jvm_threads_daemon_threads gauge
jvm_threads_daemon_threads 17.0
# HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool
# TYPE jvm_buffer_total_capacity_bytes gauge
jvm_buffer_total_capacity_bytes{id="mapped",} 0.0
jvm_buffer_total_capacity_bytes{id="direct",} 81920.0
```

## 参考資料

- [Spring Boot Actuator: Production\-ready Features](https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/production-ready-features.html)
- [Spring Boot Actuator: 本番対応機能 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/production-ready-features.html)
- [Micrometer Application Monitoring](https://micrometer.io/docs/registry/prometheus)