kakakakakku blog

Weekly Tech Blog : Keep on Learning 👍

パフォーマンスメトリクスをクライアント側に返す HTTP ヘッダー "Server-Timing"

HTTP ヘッダーを使ってアプリケーションのパフォーマンスメトリクスをクライアント側に返す仕様「Server Timing」を調べながら試してみた.詳細な仕様は以下の W3C ドキュメントに載っている.ドキュメントの Introduction に載っているけど,今まではクライアント側で "レスポンスタイム" は確認できても「どの段階でなぜ時間がかかったのか」という詳細までは確認できなかったという背景がある.そして Google Chrome 65 以上など,Server Timing をサポートしてるブラウザなら DevTools で可視化できる❗️

www.w3.org

Server Timing 仕様

仕様としては HTTP ヘッダー Server-Timing にパフォーマンスメトリクスを設定してレスポンスを返す.フォーマットは以下の通り(W3C ドキュメントを引用).

Server-Timing             = #server-timing-metric
server-timing-metric      = metric-name *( OWS ";" OWS server-timing-param )
metric-name               = token
server-timing-param       = server-timing-param-name OWS "=" OWS server-timing-param-value
server-timing-param-name  = token
server-timing-param-value = token / quoted-string

設定するレスポンスは具体的なサンプルを見るのが良いと思う.MDN に載っているサンプルを以下にまとめる.

  • Server-Timing: missedCache(キャッシュミスだった)
    • メトリクス名のみを返せる
  • Server-Timing: cpu;dur=2.4(CPU 処理時間は 2.4 ms だった)
    • dur を使って時間付きでメトリクスを返せる
  • Server-Timing: cache;desc="Cache Read";dur=23.2(キャッシュ読み取り時間は 23.2 ms だった)
    • desc を使って説明付きでメトリクスを返せる
  • Server-Timing: db;dur=53, app;dur=47.2(データベース処理は 53 ms / アプリケーション処理は 47.2 ms だった)
    • メトリクスを複数返せる

developer.mozilla.org

サンプル実装

Go の Gin を使ってサンプル実装をする.Server-Timing は MDN に載っているサンプルを参考に組み合わせて,シンプルに HTTP ヘッダーを返すように実装する.なお,調べたところ HashiCorp の Mitchell が公開している middleware "HTTP Server-Timing for Go" もあるようだった(特にメンテナンスはされてなさそうだけど).

github.com

サンプル 1

以下のメトリクスを返す.

  • missedCache
  • cpu;dur=2.4
package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  r.GET("/", func(c *gin.Context) {
    c.Header("Server-Timing", "missedCache, cpu;dur=2.4")
    c.JSON(http.StatusOK, gin.H{
      "message": "hello",
    })
  })
  r.Run()
}

期待通りに HTTP ヘッダーを取得できた.

$ curl -is http://localhost:8080/ | grep Server-Timing
Server-Timing: missedCache, cpu;dur=2.4

サンプル 2

以下のメトリクスを返す.

  • cache;desc="Cache Read";dur=1.2
  • db;dur=3.4
  • app;dur=2.3
package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  r.GET("/", func(c *gin.Context) {
    c.Header("Server-Timing", "cache;desc=\"Cache Read\";dur=1.2, db;dur=3.4, app;dur=2.3")
    c.JSON(http.StatusOK, gin.H{
      "message": "hello",
    })
  })
  r.Run()
}

期待通りに HTTP ヘッダーを取得できた.

$ curl -is http://localhost:8080/ | grep Server-Timing
Server-Timing: cache;desc="Cache Read";dur=1.2, db;dur=3.4, app;dur=2.3

Google Chrome の DevTools でも確認できた❗️

活用事例

Server Timing の活用事例を探してみたら CDN (Content Delivery Network) でよくサポートされていた.例えば Amazon CloudFront や Akamai など.確かにキャッシュ関連は詳細に情報を取得できると良さそう.

aws.amazon.com

techdocs.akamai.com

サービスだと Ameba News で HTTP ヘッダー Server-Timing を返してるというブログ記事があって,実際に確認できた.Fastly というメトリクス名になっていた.

$ curl --head -s https://news.ameba.jp/entry/xxx/ | grep server-timing
server-timing: HIT-CLUSTER, fastly;desc="Edge time";dur=1

$ curl --head -s https://news.ameba.jp/entry/xxx/ | grep server-timing
server-timing: HIT, fastly;desc="Edge time";dur=0

news.ameba.jp

他にはどんな活用事例があるだろう〜