StackDriver Monitoring APIを使ってGCPのメトリックの値を取得する 

# 概要
GCPの監視を行うため、`StackDriver` から値を取得し
しきい値を超えていないかチェックする [Nagios](https://ja.wikipedia.org/wiki/Nagios) 用のプラグインを`Golnag` で作成しました。
https://github.com/yuukichi-nankou/check-stackdriver-go

その際、苦戦したので `Golang`で `StackDriver Monitoring API`から値を取得する方法をまとめます。

> つい先日、StackDriverが管理コンソールに統合されました。
> https://cloud.google.com/blog/products/management-tools/cloud-monitoring-dashboards-using-an-api
> それに伴い、管理画面では単に Monitoring という名称になっていますが、
> 本記事では StackDriver Monitoring と記載しています。



# StackDriverからSDKを使って値を取得する
`StackDriver` は、GCPコンソール上で収集したメトリックやログの閲覧
しきい値を指定したアラート通知が可能な監視サービスです。
※ AWSの`CloudWatch`のようなイメージです。

今回は`StackDriver Monitoring`のデータを取得しました。
`StackDriver Monitoring`には GCP上のインスタンス数や、CPU使用率などの
リソース状態が自動で収集されています。

## メトリックの一覧
取得できるメトリックの一覧が公式ドキュメントに公開されています。
https://cloud.google.com/monitoring/api/metrics_gcp?hl=ja

今回は[Cloud Storage](https://cloud.google.com/monitoring/api/metrics_gcp?hl=ja#gcp-storage)バケット内の、ファイル数を取得します。

## StackDriver SDK
他のGCPのサービスと同様に、`StackDriver`もAPIとそれをプログラムから呼び出すSDKが公開されています。
[Golang用のSDK](https://pkg.go.dev/google.golang.org/api/monitoring/v3?tab=doc)も公開されているので、これを利用します。

## データの取得処理

### メトリックを指定し、一覧を取得する

以下は、SDKを利用し、`Cloud Storage`のメトリックを取得したサンプルです。
フィルタの指定で、メトリックを指定します。
`metric.type = storage.googleapis.com/storage/object_count `

<details><summary>メトリックデータを取得するサンプル</summary><div>

``` golang:sample.go

package main

import (
	"fmt"
	"os"
	monitoring "cloud.google.com/go/monitoring/apiv3"
	googlepb "github.com/golang/protobuf/ptypes/timestamp"
	"golang.org/x/net/context"
	"google.golang.org/api/iterator"
	monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
)

func main() {
	// StackDriverAPIに接続する際の認証鍵ファイルを指定する
	key_path := "./path/to/auth-key.json"
	os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", key_path)

	// GCPのプロジェクト名を指定する
	priject_id := "your-project-id"
	// フィルタを指定し、取得するデータを絞る
	filter_query := "metric.type = storage.googleapis.com/storage/object_count AND resource.type = gcs_bucket "
	
	// 取得期間をunixtimeで指定する
	var end_time int64 = 1583766000
	var start_time int64 = 1583809200

	// リクエストデータを作成する
	req := &monitoringpb.ListTimeSeriesRequest{
		Name:   "projects/" + priject_id,
		Filter: filter_query,
		Interval: &monitoringpb.TimeInterval{
			EndTime: &googlepb.Timestamp{
				Seconds: end_time,
			},
			StartTime: &googlepb.Timestamp{
				Seconds: start_time,
			},
		},
	}

	ctx := context.Background()
	client, _ := monitoring.NewMetricClient(ctx)
	it := client.ListTimeSeries(ctx, req)
	for {
		resp, err := it.Next()
		if err == iterator.Done {
			break
		}

		fmt.Println(resp)
	}
}
```

</div></details>

また以下が、実際にサンプルコードを実行した結果です。

<details><summary>サンプルの実行結果</summary><div>

``` 
$ go run sample/sample.go 
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"MULTI_REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"asia.artifacts.hogehoge-bucket.appspot.com" > labels:<key:"location" value:"asia" > labels:<key:"project_id" value:"hogehoge-bucket" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:76 > >
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"hogehoge-bucket" > labels:<key:"location" value:"asia-northeast2" > labels:<key:"project_id" value:"hogehoge-bucket" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:43 > >
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"hogehoge-bucket-frontend" > labels:<key:"location" value:"asia-northeast2" > labels:<key:"project_id" value:"hogehoge-bucket" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:575 > >
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"hogehoge-bucket-frontend-staging" > labels:<key:"location" value:"asia-northeast2" > labels:<key:"project_id" value:"hogehoge-bucket" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:568 > >
```
</div></details>

### フィルタを使い取得するメトリックを制限する

ListTimeSeries の結果はイテレータ形式で取得でき、サンプルのように複数件返ることがあります。
返る件数はメトリックによって変わります。
サンプルコードでは各バケット単位で取得していますが、
GCEのCPU使用率を取得するとインスタンス単位で値が返ります。

取得する件数を絞りたい場合は、フィルタを追加します。
例えば、サンプルコードで特定のバケットのみの値を取得したい場合
フィルタを以下のように追加すると

``` golang:sample.go
	filter_query := "metric.type = \"storage.googleapis.com/storage/object_count\" AND resource.type = \"gcs_bucket\" "
	// フィルタを追加する
	filter_query += "AND resource.labels.bucket_name = \"xtrans-friends-frontend-staging\" "
```

取得する値を絞ることが可能です。

```
$ go run sample/sample.go 
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"xtrans-friends-frontend-staging" > labels:<key:"location" value:"asia-northeast2" > labels:<key:"project_id" value:"xtrans-friends" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:568 > > points:<interval:<end_time:<seconds:1583841300 > start_time:<seconds:1583841300 > > value:<int64_value:568 > >
```

### 取得したメトリックから値を取り出す

メトリックの値はサンプルコードの `resp.Points` に配列として格納されています。
この値も取得期間に応じ複数取得することがあります。

また、値の取り出しは、メトリックのデータ型に応じて呼び出す関数が異なります。
メトリックのデータ型は `resp.ValueType.String()` で確認が可能です。

サンプルコードを一部修正すると、値を確認できます。

``` golang:sample.go
		fmt.Println(resp)
		// 追加
		fmt.Println(resp.ValueType.String())
		fmt.Println(resp.Points)
```

`storage/object_count` メトリックは `INT64` 型で値が格納されていることが分かります。

```
$ go run sample/sample.go 
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"xtrans-friends-frontend-staging" > labels:<key:"location" value:"asia-northeast2" > labels:<key:"project_id" value:"xtrans-friends" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:568 > > points:<interval:<end_time:<seconds:1583841300 > start_time:<seconds:1583841300 > > value:<int64_value:568 > >
INT64
[interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:568 >  interval:<end_time:<seconds:1583841300 > start_time:<seconds:1583841300 > > value:<int64_value:568 > ]
```

`INT64`型の値を取り出す際は `GetInt64Value()` 関数を利用しますが、`DOUBLE`型の値を取り出す際は `GetDoubleValue()` 関数を利用します。
詳しくは[SDKのドキュメント](https://pkg.go.dev/google.golang.org/api/monitoring/v3?tab=doc#Point)を参照してください。

サンプルコードを変更し値を取得します。

``` golang:sample.go
		fmt.Println(resp)
		// 追加
		fmt.Println(resp.ValueType.String())
		fmt.Println(resp.Points)
		// 先頭の値を取得する
		fmt.Println(resp.Points[0].GetValue().GetInt64Value())
```

メトリックの値を取得できました。

```
$ go run sample/sample.go 
metric:<type:"storage.googleapis.com/storage/object_count" labels:<key:"storage_class" value:"REGIONAL" > > resource:<type:"gcs_bucket" labels:<key:"bucket_name" value:"xtrans-friends-frontend-staging" > labels:<key:"location" value:"asia-northeast2" > labels:<key:"project_id" value:"xtrans-friends" > > metric_kind:GAUGE value_type:INT64 points:<interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:568 > > points:<interval:<end_time:<seconds:1583841300 > start_time:<seconds:1583841300 > > value:<int64_value:568 > >
INT64
[interval:<end_time:<seconds:1583841600 > start_time:<seconds:1583841600 > > value:<int64_value:568 >  interval:<end_time:<seconds:1583841300 > start_time:<seconds:1583841300 > > value:<int64_value:568 > ]
568
```

# まとめ
Golangで StackDriver Monitoring APIから値を取得することができました。
フィルタの指定方法や、値の取得など、ドキュメントに詳しく載っておらず苦労したので
StackDriver の値を Golangで取得したい ~~奇特な~~ 方の助けになれば幸いです。