Spring BootアプリのメトリクスをMicrometerで収集する #micrometer

Micrometer (micrometer-core)には、Javaでよく使われるライブラリ、ミドルウェア、JVM自身のメトリクスを収集するためのBinderと呼ばれるClassと、その設定を行うAutoConfigurationが含まれているので簡単にメトリクス収集を始める事ができます。
(ちなみに、MicrometerはJMXには依存してないので、JVMオプションでJMXを有効する必要はありません)

それらをいつくか紹介してみようと思います。


なお、この記事で使用しているバージョンは以下です。
(2018/05/23現在の最新)

  • Spring Boot: 2.0.2 or 1.5.13
    • Spring Boot
    • spring-boot-starter-actuator
  • Micrometer: 1.0.4
    • micrometer-core
    • micrometer-spring-legacy ※Spring Boot 1.5の場合は必須
    • micrometer-registry-prometheus ※僕はモニタリングにPrometheusを使っています



Micrometerの標準Binderの紹介

まずは、Micrometerにどんな標準Binderが含まれているのかを紹介します。

Binderのソースを見てもらうとわかるように、Javaでよく使われるものがいくつかサポートされています。

https://github.com/micrometer-metrics/micrometer/tree/v1.0.4/micrometer-core/src/main/java/io/micrometer/core/instrument/binder

例えば、

  • ライブラリ(Logback, OkHttp3, GuavaCache, Caffeineなど)
  • ミドルウェア(Tomcat, Jetty)
  • JVM自身のメトリクス(GC, Thread, Heap Memory, Non-heap Memoryなど)

などなど。


そして、各Binderの設定を行うAutoConfigurationは、
Spring Boot 2.0の場合は、spring-boot-actuator-autoconfigureの中にあります。
例えば、これら。

Spring Boot 1.5の場合は、micrometer-spring-legacyの中です。
例えば、これら。


標準Binderが提供されていないライブラリやミドルウェアのメトリクスを収集したい場合は、標準BinderとAutoConfigurationのソースを参考にして自分で作ると良いでしょう。



実際にメトリクスを収集してみる

では次に、いつくかのメトリクスを実際に収集してみましょう。
※収集可能になるメトリクスを以下に例示していきますが、それらはPrometheusによってexportされるものです

ほとんどのメトリクスは spring-boot-starter-actuatormicrometer-core を追加するだけで収集可能になります。
(Spring Boot 1.5の場合は micrometer-spring-legacy も)
また、他にも自分のモニタリングツールに合わせて micrometer-registry-prometheus なども追加してください。


JVMのメトリクス

GCのメトリクス

  • Minor GCの発生回数
  • Minor GCの停止時間
  • Major GCの発生回数
  • Major GCの停止時間

などが収集されます。

# 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="Allocation Failure",} 14.0
jvm_gc_pause_seconds_sum{action="end of minor GC",cause="Allocation Failure",} 0.106
jvm_gc_pause_seconds_max{action="end of minor GC",cause="Allocation Failure",} 0.007
jvm_gc_pause_seconds_count{action="end of major GC",cause="System.gc()",} 4.0
jvm_gc_pause_seconds_sum{action="end of major GC",cause="System.gc()",} 0.481
jvm_gc_pause_seconds_max{action="end of major GC",cause="System.gc()",} 0.19
# HELP jvm_gc_concurrent_phase_time_seconds Time spent in concurrent phase
# TYPE jvm_gc_concurrent_phase_time_seconds summary
jvm_gc_concurrent_phase_time_seconds_count{action="end of major GC",cause="No GC",} 1.0
jvm_gc_concurrent_phase_time_seconds_sum{action="end of major GC",cause="No GC",} 0.337
jvm_gc_concurrent_phase_time_seconds_max{action="end of major GC",cause="No GC",} 0.0


Heap memoryのメトリクス

  • Young Generation (Eden, Survivor)
  • Old Generation
  • Young Generation (Eden, Survivor)のMax
  • Old GenerationのMax

などが収集されます。

# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="Par Eden Space",} 4177576.0
jvm_memory_used_bytes{area="heap",id="Par Survivor Space",} 87808.0
jvm_memory_used_bytes{area="heap",id="CMS Old Gen",} 3.3789184E7
# 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="Par Eden Space",} 8912896.0
jvm_memory_committed_bytes{area="heap",id="Par Survivor Space",} 1114112.0
jvm_memory_committed_bytes{area="heap",id="CMS Old Gen",} 5.6410112E7
# 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="Par Eden Space",} 6.979584E7
jvm_memory_max_bytes{area="heap",id="Par Survivor Space",} 8716288.0
jvm_memory_max_bytes{area="heap",id="CMS Old Gen",} 4.39156736E8


Non-Heap memoryのメトリクス

  • Code Cache
  • Metaspace
  • Compressed Class Space
  • Code CacheのMax
  • MetaspaceのMax
  • Compressed Class SpaceのMax

などが収集されます。

# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="nonheap",id="Code Cache",} 2.3145152E7
jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 5.0919768E7
jvm_memory_used_bytes{area="nonheap",id="Compressed Class Space",} 6311672.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="nonheap",id="Code Cache",} 2.3855104E7
jvm_memory_committed_bytes{area="nonheap",id="Metaspace",} 5.3735424E7
jvm_memory_committed_bytes{area="nonheap",id="Compressed Class Space",} 6811648.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="nonheap",id="Code Cache",} 2.5165824E8
jvm_memory_max_bytes{area="nonheap",id="Metaspace",} -1.0
jvm_memory_max_bytes{area="nonheap",id="Compressed Class Space",} 1.073741824E9


Threadのメトリクス

  • Thread数
  • Daemon Thread数

などが収集されます。

# HELP jvm_threads_live The current number of live threads including both daemon and non-daemon threads
# TYPE jvm_threads_live gauge
jvm_threads_live 33.0
# HELP jvm_threads_daemon The current number of live daemon threads
# TYPE jvm_threads_daemon gauge
jvm_threads_daemon 6.0


ClassLoaderのメトリクス

  • loaded数
  • unloaded数
# HELP jvm_classes_loaded The number of classes that are currently loaded in the Java virtual machine
# TYPE jvm_classes_loaded gauge
jvm_classes_loaded 9296.0
# HELP jvm_classes_unloaded_total The total number of classes unloaded since the Java virtual machine has started execution
# TYPE jvm_classes_unloaded_total counter
jvm_classes_unloaded_total 59.0



Tomcatのメトリクス

  • ThreadPool busy
  • ThreadPool current
  • ThreadPool max

などが収集されます。

# HELP tomcat_threads_busy  
# TYPE tomcat_threads_busy gauge
tomcat_threads_busy{name="http-nio-8080",} 0.0
# HELP tomcat_threads_current  
# TYPE tomcat_threads_current gauge
tomcat_threads_current{name="http-nio-8080",} 10.0
# HELP tomcat_threads_config_max  
# TYPE tomcat_threads_config_max gauge
tomcat_threads_config_max{name="http-nio-8080",} 200.0



Jettyのメトリクス

Requestのメトリクス

  • 処理中のリクエスト

などが収集されます。

# HELP jetty_requests_active Number of requests currently active
# TYPE jetty_requests_active gauge
jetty_requests_active 0.0


Responseのメトリクス

  • HTTP status毎のレスポンス数
# HELP jetty_responses_total Number of requests with response status
# TYPE jetty_responses_total counter
jetty_responses_total{status="1xx",} 0.0
jetty_responses_total{status="2xx",} 104.0
jetty_responses_total{status="3xx",} 0.0
jetty_responses_total{status="4xx",} 0.0
jetty_responses_total{status="5xx",} 0.0


Threadのメトリクス

JettyのThreadPoolのメトリクスを収集する標準Binderが無かったので(Micrometer 1.0.4現在)、僕の方で作成してPRしています。
まだマージされてませんが、ご参考までに。

https://github.com/micrometer-metrics/micrometer/pull/593

これにより、以下のメトリクスが収集できるようになります。

  • ThreadPool busy
  • ThreadPool current
  • ThreadPool min
  • ThreadPool max
# HELP jetty_threads_busy The current number of busy threads
# TYPE jetty_threads_busy gauge
jetty_threads_busy 2.0
# HELP jetty_threads_current The current number of current threads
# TYPE jetty_threads_current gauge
jetty_threads_current 4.0
# HELP jetty_threads_config_min The number of min threads
# TYPE jetty_threads_config_min gauge
jetty_threads_config_min 4.0
# HELP jetty_threads_config_max The number of max threads
# TYPE jetty_threads_config_max gauge
jetty_threads_config_max 100.0



HikariCPのメトリクス

  • ConnectionPool Active
  • ConnectionPool Pending
  • ConnectionPool Idle
  • ConnectionPool Total
  • ConnectionPool Min
  • ConnectionPool Max

などが収集されます。

# HELP hikaricp_connections_active Active connections
# TYPE hikaricp_connections_active gauge
hikaricp_connections_active{pool="HikariPool-1",} 0.0
# HELP hikaricp_connections_pending Pending threads
# TYPE hikaricp_connections_pending gauge
hikaricp_connections_pending{pool="HikariPool-1",} 0.0
# HELP hikaricp_connections_idle Idle connections
# TYPE hikaricp_connections_idle gauge
hikaricp_connections_idle{pool="HikariPool-1",} 10.0
# HELP hikaricp_connections Total connections
# TYPE hikaricp_connections gauge
hikaricp_connections{pool="HikariPool-1",} 10.0
# HELP hikaricp_connections_min Min connections
# TYPE hikaricp_connections_min gauge
hikaricp_connections_min{pool="HikariPool-1",} 10.0
# HELP hikaricp_connections_max Max connections
# TYPE hikaricp_connections_max gauge
hikaricp_connections_max{pool="HikariPool-1",} 10.0

余談ですが、HikariCPのメトリクス収集のロジックはHikariCP自身がMicrometerを使って実装しています。
ソースは以下。


Spring Boot 1.5の場合は話が少しややこしくて、micrometer-spring-legacyも依存ライブラリに追加してもHikariCPのメトリクスは収集されません。
(Spring Boot 2.0を使っている方はここは読み飛ばしてください)

理由は、HikariCPがMicrometerを使ったメトリクス収集ロジックを実装したのが2.7.0からで、Spring Boot 1.5は2.5.xを使うようになっているからです。

そして、MicrometerでHikariCPのメトリクスが正しく収集出来るようになったのは2.7.9からです。

FYI:
https://github.com/spring-projects/spring-boot/issues/12759

なので、pom.xmlbuild.gradleでHikariCPのバージョン(hikaricp.version)を上書きして使うか(Spring Bootが指定したバージョンではないから問題が出るかも。自己責任で)、
以下のHikariCP設定をapplication.ymlに追加して 以下のようにHikariCPのJMX設定を行って、 *2018/05/24 修正
(設定内容が間違っていたので修正。application.ymlの設定例は紛らわしいので削除しました)
メトリクスをJMXで収集出来るようにすると良いでしょう。

僕が以前書いた記事も参照してください。

@Configuration
public class HikariCPConfig {

    @Bean(destroyMethod = "close")
    public DataSource dataSource(MBeanExporter mBeanExporter, DataSourceProperties properties) {
        DataSource ds = DataSourceBuilder.create(properties.getClassLoader())
                                         .type(HikariDataSource.class)
                                         .driverClassName(properties.determineDriverClassName())
                                         .url(properties.determineUrl())
                                         .username(properties.determineUsername())
                                         .password(properties.determinePassword())
                                         .build();

        // Configuration to monitor HikariCP metrics via JMX
        HikariDataSource dataSource = (HikariDataSource) ds;
        dataSource.setRegisterMbeans(true);
        dataSource.setPoolName("dataSource");
        mBeanExporter.addExcludedBean("dataSource");

        return dataSource;
    }
}

Prometheusを使っているのであれば、JMXでメトリクスを収集するのにjmx_exporterが使えます。

jmx_exporterのyamlファイルの例

lowercaseOutputName: true
whitelistObjectNames:
  - com.zaxxer.hikari:*
rules:
  - pattern: '^com.zaxxer.hikari<type=Pool \((\w+)\)><>ActiveConnections:'
    name: hikaricp_pool_active_connections
    labels:
      source: $1
    help: HikariCP Pool active connections $1
    type: GAUGE
  - pattern: '^com.zaxxer.hikari<type=Pool \((\w+)\)><>IdleConnections:'
    name: hikaricp_pool_idle_connections
    labels:
      source: $1
    help: HikariCP Pool idle connections $1
    type: GAUGE
  - pattern: '^com.zaxxer.hikari<type=Pool \((\w+)\)><>TotalConnections:'
    name: hikaricp_pool_total_connections
    labels:
      source: $1
    help: HikariCP Pool total connections $1
    type: GAUGE
  - pattern: '^com.zaxxer.hikari<type=Pool \((\w+)\)><>ThreadsAwaitingConnection:'
    name: hikaricp_pool_threads_awaiting_connection
    labels:
      source: $1
    help: HikariCP Pool threads awaiting connection(pending threads) $1
    type: GAUGE
  - pattern: '^com.zaxxer.hikari<type=PoolConfig \((\w+)\)><>MaximumPoolSize:'
    name: hikaricp_pool_maximum_pool_size
    labels:
      source: $1
    help: HikariCP PoolConfig maximum pool size $1
    type: GAUGE
  - pattern: '^com.zaxxer.hikari<type=PoolConfig \((\w+)\)><>MinimumIdle:'
    name: hikaricp_pool_minimum_idle
    labels:
      source: $1
    help: HikariCP PoolConfig minimum idle $1
    type: GAUGE

jmx_exporter設定については公式ドキュメントを参照してください。


HTTPサーバとしてのレイテンシ

  • アプリのURI毎のリクエスト処理回数

などが収集されます。

# HELP http_server_requests_seconds  
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{exception="None",method="GET",status="200",uri="/",} 4.0
http_server_requests_seconds_sum{exception="None",method="GET",status="200",uri="/",} 0.764760048
http_server_requests_seconds_count{exception="None",method="GET",status="200",uri="/**/favicon.ico",} 2.0
http_server_requests_seconds_sum{exception="None",method="GET",status="200",uri="/**/favicon.ico",} 0.022893644
# HELP http_server_requests_seconds_max  
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{exception="None",method="GET",status="200",uri="/",} 0.620949991
http_server_requests_seconds_max{exception="None",method="GET",status="200",uri="/**/favicon.ico",} 0.021386207


さらに、以下のような設定をapplication.ymlに追加するとアプリのURI毎のレイテンシが取れます。
http.server.requestsの設定値はお好みでどうぞ。

application.yml

詳しくはmakingさんのブログを読んでください。
Spring Boot 1.5の場合は、僕が以前書いた記事も参照してください。

management:
  metrics:
    distribution:
      percentiles:
        http.server.requests: 0.5, 0.75, 0.95, 0.98, 0.99, 0.999, 1.0

アプリのURI毎のレイテンシのメトリクス

http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="0.5",} 0.096468992
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="0.75",} 0.868220928
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="0.95",} 0.868220928
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="0.98",} 0.868220928
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="0.99",} 0.868220928
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="0.999",} 0.868220928
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/",quantile="1.0",} 0.868220928
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="0.5",} 0.019922944
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="0.75",} 0.019922944
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="0.95",} 0.019922944
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="0.98",} 0.019922944
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="0.99",} 0.019922944
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="0.999",} 0.019922944
http_server_requests_seconds{exception="None",method="GET",status="200",uri="/**/favicon.ico",quantile="1.0",} 0.019922944


レイテンシに関する補足

上記のレイテンシはMicrometerのTimerを使って計測されていて、Percentile設定は使う人がapplication.yml(もしくはapplication.properties)で自由に行えるようになっています。

公式ドキュメントは以下。

ソースで言うと、アプリケーションプロパティの設定値をTimerに設定しているのはPropertiesMeterFilterというFilterです。

PropertiesMeterFilterは全てのTimerに適用されます。
よって、自分がTimerを作る場合もPercentile設定はハードコーディングせずに、application.ymlで設定した方が良いと思います。

Timer.builder("my.timer")
// .publishPercentiles(...)  ※ハードコーディングしない
   .description("My Timer")
   .register(registry);
management:
  metrics:
    distribution:
      percentiles:
        my.timer: 0.5, 0.75, 0.95, 0.98, 0.99, 0.999, 1.0  # my.timer は Timerの名前



ExecutorServiceのメトリクス

別の記事として追記しました



まとめ

  • Micrometerには、Javaでよく使われるライブラリ、ミドルウェア、JVM自身のメトリクスを収集するためのBinderが含まれているので簡単にメトリクス収集を始める事ができる
  • application.ymlに management.metrics.distribution の設定を行うと、アプリのURI毎のレイテンシが収集できる

以上です。

Micrometerがもっと色んなライブラリに対応したり、ミドルウェアに組み込まれたりして、モニタリングがもっと簡単に出来るようになったらいいなー。



あわせて読みたい

 
comments powered by Disqus