Prometheus の JSON を jq で処理する

Prometheus の JSON を jq で処理する

気付き

この作業で得られた気付きを先に書いておく

  • 更新代入 |= についての誤解
    • jq コマンドのパイプ文字 |unixシェルのパイプ文字とは若干イメージが異なる
    • |= は独立のオペレーターで、2つの演算子 |= を組み合わせたものではない
      • 下記の例でも
        • .data.result[].values[] | .[0] |= strflocaltime("%Y-%m-%dT%H:%M:%S%z")
          • これだとvalues[]の2つの要素が出てくる。.[0]のフィルターではなく、|=の結果が出力となる
        • .data.result[].values[] | .[0] | . = strflocaltime("%Y-%m-%dT%H:%M:%S%z")
          • これだとvalues[]の最初の要素しか出てこない。.[0] のフィルターがかかる。
  • フィルターをまとめるにはカッコ () を使う
  • JSONで少し離れた場所の情報は変数に持つ

やりたいこと

  1. unix timeの変換処理
  2. Prometheusでは整数になりそうな数値も整数にならないことがあるので(*)、それを整数に丸める
  3. instance を同時に出力
  4. CSVにする

(*): https://prometheus.io/docs/prometheus/latest/querying/functions/#increase

The increase is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if a counter increases only by integer increments.

入力

HTTP API /api/v1/query_range の結果がこんなだったとして

{
   "status" : "success",
   "data" : {
      "resultType" : "matrix",
      "result" : [
         {
            "metric" : {
               "__name__" : "foo",
               "job" : "node",
               "instance" : "bar:9100"
            },
            "values" : [
               [ 1689329940, "1.000023" ],
               [ 1689329955, "4.000056" ],
               [ 1689329970, "7.000089" ]
            ]
         },
         {
            "metric" : {
               "__name__" : "foo",
               "job" : "node",
               "instance" : "baz:9100"
            },
            "values" : [
               [ 1689329940, "1.000023" ],
               [ 1689329955, "4.000056" ],
               [ 1689329970, "7.000089" ]
            ]
         }
      ]
   }
}

unix time変換

unix timeを変換。iso8601拡張形式にしようとしたが、微妙に間違っている(タイムゾーンにコロン : がない)。

.data.result[].values[] | .[0] | strflocaltime("%Y-%m-%dT%H:%M:%S%z")

結果

2023-07-14T10:19:00+0000
2023-07-14T10:19:15+0000
2023-07-14T10:19:30+0000
2023-07-14T10:19:00+0000
2023-07-14T10:19:15+0000
2023-07-14T10:19:30+0000

無理やりコロンを挿入。jq の sub() の正規表現で、後方参照は名前付きキャプチャー((?<name>pattern))というものを使って、 "\(.name)" のように参照するそうだ。

.data.result[].values[] | .[0] | strflocaltime("%Y-%m-%dT%H:%M:%S%z") | gsub("(?<a>[+-][0-9][0-9])(?<b>[0-9][0-9])"; "\(.a):\(.b)")

結果

2023-07-14T10:19:00+00:00
2023-07-14T10:19:15+00:00
2023-07-14T10:19:30+00:00
2023-07-14T10:19:00+00:00
2023-07-14T10:19:15+00:00
2023-07-14T10:19:30+00:00

少数を整数に丸め

ダブルクォーテーションが付いているので、数値型にしてから整数にする

.data.result[].values[] | .[1] | tonumber | round

結果

1
4
7
1
4
7

instance を出力する

これは変数に覚えておく

.data.result[] | .metric.instance as $instance | $instance

結果

bar:9100
baz:9100

処理をまとめる

  • jqの場合、これが意外に面倒になる。今回は更新代入 |= を繰り返せば良かったので、比較的、単純
  • strflocaltimeとgsubをカッコでまとめてから代入する
  • tonumberとroundもカッコでくくる
  • 最後に配列にしてCSVにする

(雰囲気で、改行してインデントしてみたけど、何か整形ルールはあるのかな)

.data.result[] |
  .metric.instance as $instance |
  .values[] |
    .[0] |= (strflocaltime("%Y-%m-%dT%H:%M:%S%z") |
             gsub("(?<a>[+-][0-9][0-9])(?<b>[0-9][0-9])"; "\(.a):\(.b)")) |
    .[1] |= (tonumber | round) |
    [.[0], $instance, .[1]] | 
      @csv

結果

"2023-07-14T10:19:00+00:00","bar:9100",1
"2023-07-14T10:19:15+00:00","bar:9100",4
"2023-07-14T10:19:30+00:00","bar:9100",7
"2023-07-14T10:19:00+00:00","baz:9100",1
"2023-07-14T10:19:15+00:00","baz:9100",4
"2023-07-14T10:19:30+00:00","baz:9100",7

お試しURL

これはいつまで有効なんだろう?