LoginSignup
0
0

More than 1 year has passed since last update.

Grafana Tempo の Parquet ファイルの内容を JSON で取り出す

Last updated at Posted at 2023-03-12

分散トレーシングのバックエンド Grafana Tempo のストレージはデフォルトで Apache Parquet 形式となっています。

今のところ Tempo には、Parquet ファイルの内容をそのまま JSON 化するような機能は見当たらなかったので Go で実装してみました。

はじめに

Tempo を実行してトレースデータを登録しておきます。

Tempo 実行

Tempo は JaegerZipkin 等に対応していますが、今回は OpenTelemetry の http プロトコルを使ってトレースを登録するような設定にしてみました。

tempo.yaml
server:
  http_listen_port: 3200

distributor:
  receivers:
    otlp:
      protocols:
        http:

storage:
  trace:
    backend: local
    local:
      path: ./tmp/blocks
    wal:
      path: ./tmp/wal

この設定ファイルを使って Tempo を実行します。

Tempo 実行例
$ tempo -config.file tempo.yaml

トレース登録

次にトレースデータを登録します。

OpenTelemetry 用の receiver はポート番号 4318 となっているので、その /v1/traces へ OpenTelemetry 形式のトレースデータを POST します。

トレース登録例
$ curl http://localhost:4318/v1/traces -H "Content-Type: application/json" -d @data/trace1.json
{}

$ curl http://localhost:4318/v1/traces -H "Content-Type: application/json" -d @data/trace2.json
{}

トレースの内容はこのようにしました。(内容はかなり適当です)

data/trace1.json
{
    "resourceSpans": [
        {
            "resource": {
                "attributes": [
                    {
                        "key": "service.name",
                        "value": {
                            "stringValue": "sample-app1"
                        }
                    }
                ]
            },
            "scopeSpans": [
                {
                    "scope": {
                        "name": "sample-scope"
                    },
                    "spans": [
                        {
                            "traceId": "00000000000000000000000000000001",
                            "spanId": "0000000000000001",
                            "name": "proc1",
                            "startTimeUnixNano": "1678536011308000000",
                            "endTimeUnixNano": "1678536021308000000",
                            "attributes": [
                                {
                                    "key": "api",
                                    "value": {
                                        "stringValue": "GET /proc1"
                                    }
                                },
                                {
                                    "key": "protocol",
                                    "value": {
                                        "stringValue": "http"
                                    }
                                },
                                {
                                    "key": "status_code",
                                    "value": {
                                        "stringValue": "200"
                                    }
                                }
                            ]
                        },
                        ...省略
                    ]
                }
            ]
        }
    ]
}

このままだと、バックエンド(local)で設定した tmp/blocks へ Parquet ファイルは出力されていないので 1、Tempo の flush 機能(ポート番号は 3200)を呼び出します。

flush の実行例
$ curl http://localhost:3200/flush

これで、tmp/blocks/single-tenant/06527bdb-・・・/data.parquet ファイルが生成されました。

Parquet ファイルの JSON 化

data.parquet の内容を JSON で出力する処理を Go で実装します。

Tempo のソースコード tempodb/encoding/vparquet/schema.go を参考に、Parquet ファイルからデータを取得する項目を定義します。

schema.go
package main

...省略

type Resource struct {
	Attrs []Attribute

	ServiceName      string  `parquet:",snappy,dict"`
	Cluster          *string `parquet:",snappy,optional,dict"`
	Namespace        *string `parquet:",snappy,optional,dict"`
	Pod              *string `parquet:",snappy,optional,dict"`
	Container        *string `parquet:",snappy,optional,dict"`
	K8sClusterName   *string `parquet:",snappy,optional,dict"`
	K8sNamespaceName *string `parquet:",snappy,optional,dict"`
	K8sPodName       *string `parquet:",snappy,optional,dict"`
	K8sContainerName *string `parquet:",snappy,optional,dict"`
}

type ResourceSpans struct {
	Resource   Resource    `parquet:""`
	ScopeSpans []ScopeSpan `parquet:"ils"`
}

type Trace struct {
	TraceID           []byte          `parquet:""`
	ResourceSpans     []ResourceSpans `parquet:"rs"`
	TraceIDText       string          `parquet:",snappy"`
	StartTimeUnixNano uint64          `parquet:",delta"`
	EndTimeUnixNano   uint64          `parquet:",delta"`
	DurationNanos     uint64          `parquet:",delta"`
	RootServiceName   string          `parquet:",dict"`
	RootSpanName      string          `parquet:",dict"`
}

segmentio/parquet-goReadFile で上記の Trace を型パラメータに指定すると Parquet ファイルの内容を取り出せます。

あとは json.Marshal で JSON 化して出力するだけです。

main.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"

	"github.com/segmentio/parquet-go"
)

func main() {
	file := os.Args[1]

	rows, err := parquet.ReadFile[Trace](file)

	if err != nil {
		log.Fatal(err)
	}

	r, _ := json.Marshal(rows)

	fmt.Print(string(r))
}
ビルド例
$ go build

ビルド結果(tempo_parquet_json)を data.parquet ファイルに対して実行した結果はこのようになりました。

実行例
$ ./tempo_parquet_json tmp/blocks/single-tenant/06527bdb-・・・/data.parquet | jq
[
  {
    "TraceID": "AAAAAAAAAAAAAAAAAAAAAQ==",
    "ResourceSpans": [
      {
        "Resource": {
          "Attrs": [],
          "ServiceName": "sample-app1",
          "Cluster": null,
          "Namespace": null,
          "Pod": null,
          "Container": null,
          "K8sClusterName": null,
          "K8sNamespaceName": null,
          "K8sPodName": null,
          "K8sContainerName": null
        },
        "ScopeSpans": [
          {
            "Scope": {
              "Name": "sample-scope",
              "Version": ""
            },
            "Spans": [
              {
                "ID": "AAAAAAAAAAE=",
                "Name": "proc1",
                "Kind": 0,
                "ParentSpanID": "",
                "TraceState": "",
                "StartUnixNanos": 1678536011308000000,
                "EndUnixNanos": 1678536021308000000,
                "StatusCode": 0,
                "StatusMessage": "",
                "Attrs": [
                  {
                    "Key": "api",
                    "Value": "GET /proc1",
                    "ValueInt": null,
                    "ValueDouble": null,
                    "ValueBool": null,
                    "ValueKVList": "",
                    "ValueArray": ""
                  },
                  {
                    "Key": "protocol",
                    "Value": "http",
                    "ValueInt": null,
                    "ValueDouble": null,
                    "ValueBool": null,
                    "ValueKVList": "",
                    "ValueArray": ""
                  },
                  {
                    "Key": "status_code",
                    "Value": "200",
                    "ValueInt": null,
                    "ValueDouble": null,
                    "ValueBool": null,
                    "ValueKVList": "",
                    "ValueArray": ""
                  }
                ],
                "DroppedAttributesCount": 0,
                "Events": [],
                "DroppedEventsCount": 0,
                "Links": "",
                "DroppedLinksCount": 0,
                "HttpMethod": null,
                "HttpUrl": null,
                "HttpStatusCode": null
              },
              {
                "ID": "AAAAAAAAAAI=",
                "Name": "proc2",
                "Kind": 0,
                "ParentSpanID": "",
                "TraceState": "",
                "StartUnixNanos": 1678536031308000000,
                "EndUnixNanos": 1678536141308000000,
                "StatusCode": 0,
                "StatusMessage": "",
                "Attrs": [
                  {
                    "Key": "api",
                    "Value": "GET /proc2",
                    "ValueInt": null,
                    "ValueDouble": null,
                    "ValueBool": null,
                    "ValueKVList": "",
                    "ValueArray": ""
                  },
                  {
                    "Key": "protocol",
                    "Value": "http",
                    "ValueInt": null,
                    "ValueDouble": null,
                    "ValueBool": null,
                    "ValueKVList": "",
                    "ValueArray": ""
                  },
                  {
                    "Key": "status_code",
                    "Value": "200",
                    "ValueInt": null,
                    "ValueDouble": null,
                    "ValueBool": null,
                    "ValueKVList": "",
                    "ValueArray": ""
                  }
                ],
                "DroppedAttributesCount": 0,
                "Events": [],
                "DroppedEventsCount": 0,
                "Links": "",
                "DroppedLinksCount": 0,
                "HttpMethod": null,
                "HttpUrl": null,
                "HttpStatusCode": null
              }
            ]
          }
        ]
      }
    ],
    "TraceIDText": "1",
    "StartTimeUnixNano": 1678536011308000000,
    "EndTimeUnixNano": 1678536141308000000,
    "DurationNanos": 130000000000,
    "RootServiceName": "sample-app1",
    "RootSpanName": "proc2"
  },
  ...省略
]
  1. flush する前の wal 側に出力されている tmp/wal/.../0000000001 ファイルが Parquet ファイルだったりします

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0