`github.com/prometheus/client_golang` を使って 99.9 percentileでのHTTPハンドラのメトリクスを取ってみる

github.com/prometheus/client_golang でメトリクスを取る

この記事はGoアドベントカレンダー向けの記事です。

この記事では github.com/prometheus/client_golang を使って 基本的なメトリクスを取ると同時に、HTTPハンドラのメトリクスを追加してみます。 また、HTTPハンドラのメトリクスで、99.9 percentileのhttp_request_duration_microsecondsのメトリクスを取ってみます。

基本的なメトリクスの取得とエンドポイントの公開

github.com/prometheus/client_golang を使えばメトリクスを取ることが出来ます。 prometheus向けのメトリクスのエンドポイントも生やせます。

下のような形で簡単にメトリクスのエンドポイントを生やす事ができます。

package main

import (
    "log"
    "net/http"

    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    http.Handle("/metrics", promhttp.Handler())
    log.Fatal(http.ListenAndServe(":8080", nil))
}

デフォルトでは、以下の2つのメトリクスのコレクターが登録されています。

  • Process Collector
  • Go Collector
    • GCやゴルーチン、OSのスレッド数などのメトリクスが取れます。

以下の記事が詳しいです。

HTTPハンドラのメトリクスを取る

HTTPハンドラのメトリクスを取ってみました。 以下のようなメトリクスが取れます。

デフォルトでは、50 percentile, 90 percentile, 99 percentileのメトリクスが取れます。

http_request_duration_microseconds{handler="hello_world",quantile="0.5"} 82897.181
http_request_duration_microseconds{handler="hello_world",quantile="0.9"} 96527.057
http_request_duration_microseconds{handler="hello_world",quantile="0.99"} 98644.142
http_request_duration_microseconds_sum{handler="hello_world"} 1.0489467330000002e+06
http_request_duration_microseconds_count{handler="hello_world"}

ソースは下のようになりました。

package main

import (
    "time"
    "log"
    "net/http"
    "fmt"
    "math/rand"
 
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    http.Handle("/metrics", promhttp.Handler())
    http.Handle("/", prometheus.InstrumentHandlerFunc("hello_world", hello))
    log.Fatal(http.ListenAndServe(":8080", nil))
}
 
func hello(w http.ResponseWriter, _ *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintln(w, "Hello World")
}

簡単ですね。

99.9 percentileのメトリクスを取ってみる

99.9 percentileのメトリクスを取りたい時はどうすれば良いでしょうか。 ライブラリの中のコードを参考にすると、下のような形で取ることが出来ます。

   http.Handle("/", prometheus.InstrumentHandlerFuncWithOpts(
        prometheus.SummaryOpts{
            Subsystem:   "http",
            ConstLabels: prometheus.Labels{"handler": "hello_world"},
            Objectives:  map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001, 0.999: 0.0001},
        },
        hello))

メトリクスは以下のような形で取れます。 増えましたね。

http_request_duration_microseconds{handler="hello_world",quantile="0.5"} 50336.671
http_request_duration_microseconds{handler="hello_world",quantile="0.9"} 91294.148
http_request_duration_microseconds{handler="hello_world",quantile="0.99"} 100699.414
+ http_request_duration_microseconds{handler="hello_world",quantile="0.999"} 104040.94

まとめ

github.com/prometheus/client_golang を使うことで簡単なメトリクスがデフォルト設定されていることを確認しました。 また、InstrumentHandlerFuncやInstrumentHandlerFuncWithOptsなどを使うことで HTTPエンドポイントのメトリクスが自動で取得されることが分かります。

エンドポイント毎のレスポンスタイムの劣化などは非常に便利な情報です。 HTTPサーバを書いているなら、メトリクスを取って監視してみてはいかがでしょうか。

この記事では紹介しませんが 他にも自分でコードを書くことでメトリクスを取ることが可能です。

個人的に気になっていたメトリクスどうやって取るんだろうということで 調べて記事にしました。 終わり。

Spring 5.0/Spring Boot 2.0にしたらMockMvcを使ったテストが406 Not Acceptableを返してくる話

Content-Type周りの挙動で死んでしまった回

使っているSpringは5.1.0、Spring Bootは2.0.6です。

元はこんなコード。

   @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(controller)
            .setMessageConverters(httpMessageConverter());
    }

    @Test
    public void testGetMailSuppression() throws Exception {
        // setup
        String someId = ...;
        // 省略
        
        // exercise
        mvc.perform(get("/endpoint/{id}", someId)
            // verify
            .andExpect(status().isOk()); // なぜか406が帰ってくる
    }

ContentNegotiationManager周りに変更が入っているので、以下のコードを追加しました。

   @Before
    public void setUp() {
+       ContentNegotiationManagerFactoryBean factoryBean =
+           new ContentNegotiationManagerFactoryBean();
+       factoryBean.setFavorPathExtension(false); // この辺の設定はWebMvcの設定で使ってるやつ
+       factoryBean.afterPropertiesSet();
        this.mockMvc = MockMvcBuilders.standaloneSetup(controller)
+           .setContentNegotiationManager(factoryBean.build())
            .setMessageConverters(httpMessageConverter());
    }

    @Test
    public void testGetMailSuppression() throws Exception {
        // setup
        String someId = ...;
        // 省略
        
        // exercise
        mvc.perform(get("/endpoint/{id}", someId)
            // verify
            .andExpect(status().isOk()); // なぜか406が帰ってくる
    }

まとめ

よく分からず、適当なコードを書いたら直ってしまった回・・・ 挙動の違いはまた今度見てみます。

この辺が影響してそう。

Spring Boot 1.5.x を使っているプロジェクトでSpring Bootを2系にしたら @Scheduledが動かないことがある

Spring Boot 1.5.xでは動いていた @Scheduledをつけた定期処理が Spring Boot 2.xでは動かないことがあります。

EnableSchedulingを自分のアプリケーションのConfigurationクラスにつけてください。 どこにもつけてないんだと思います。

ちょっとハマったよ。

余談

じゃあ、元々なんで動いてたの?

以前はここについてた。ちなみに2.x系で対応するクラスを見つけられなかった。 spring-boot/MetricExportAutoConfiguration.java at v1.5.17.RELEASE · spring-projects/spring-boot · GitHub