GCPを使って、できるだけ楽してGo言語のgoroutineのリークを監視する

## ゴルーチン リーク
Go言語はめちゃめちゃ簡単にゴルーチン（スレッドのようなもの）を作れるのが魅力です。

ゴルーチンはメインフローとは別に切り離されて実行されるので、デッドロックなどでゴルーチンの処理が途中で止まっていても気づかず、「終了したと思っていたが、実は実行中で残っていました」ということがありました。

それ以来、ゴルーチンの起動数を定期的にログに出力して、ゴルーチンのリークが起こっていないかチェックしているのですが、その作業が面倒くさくなってきました。

## もっと楽したい
GCPのCloud Monitoringは主に、GCPの各種サービスの状態モニタリングを行う機能なのですが、アプリの状態モニタリングも行えるので、それを使ってゴルーチンの起動数を監視することにしました。

## 方針
Cloud Monitoringに、Go言語のpprofの値を送るようにして、Cloud Monitoring上でゴルーチンの起動数をグラフ表示します。

プログラムからCloud Monitoringに値を送るには、OpenCensusというオープンソースのライブラリを使うのですが、色々お膳立てが必要で、ちょっとした値を送りたい場合でも、結構コードを書く必要があります。

しかし、OpenCensusのGo言語のライブラリには、pprofの値をよしなに取得して送る機能があり、それを使えば数行のコードでできるので、今回はそれをそのまま使いました。

## 手順
### 権限設定
Cloud Monitoringにデータを送るには、「モニタリング管理者」権限が必要なので、アプリに付与しておきます。

### サンプルプログラム
```go
package main

import (
	"context"
	"log"
	"os"
	"time"

	"contrib.go.opencensus.io/exporter/stackdriver"
	"go.opencensus.io/metric/metricexport"
	"go.opencensus.io/plugin/runmetrics"
)

func main() {
	//////////////////////////////////////////////////
	// Cloud Monitoringにpprofを送る
	//////////////////////////////////////////////////
	os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "./service-account-key.json")

	// exporter(Cloud Monitoring)
	exporter, err := stackdriver.NewExporter(stackdriver.Options{
		// Cloud Montoringからの検索用文字列
		MetricPrefix: "test-app-go-pprof",
		// データ送信頻度（60秒以上）
		ReportingInterval: 120 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer exporter.Flush()
	exporter.StartMetricsExporter()
	defer exporter.StopMetricsExporter()

	// metrics(pprof)
	err = runmetrics.Enable(runmetrics.RunMetricOptions{
		EnableCPU:    true,
		EnableMemory: true,
	})
	if err != nil {
		log.Fatal(err)
	}

	// metrics -> exporter
	metricexport.NewReader().ReadAndExport(exporter)

	//////////////////////////////////////////////////
	// ゴルーチン リーク テスト
	//////////////////////////////////////////////////
	ctx := context.Background()
	for {
		go func() {
			var a [1000 * 1000 * 10]byte // メモリも消費
			_ = a
			<-ctx.Done() // 停止
		}()
		time.Sleep(60 * time.Second)
	}
}
```
#### 補足説明
- OpenCensusはデータ部分（metrics）と送信部分（exporter）に分かれています。
- exporterをCloud Monitoring（旧名stackdriver）にして、runmetricsでpprofからmetrictを生成するようにします。
- exporter（Cloud Monitoring）がmetrics（pprof）を読み込んで出力するよう紐付けします。
- Cloud Monitoringには色んな種類のデータが送られているので、識別しやすいように「MetricPrefix」を付けます。

## Cloud Monitoring
プログラムを実行すると、Cloud Monitoringに

``custom.googleapis.com/opencensus/test-app-go-pprof/process/cpu_goroutines``

という名前で、ゴルーチン数が記録されるので、``[Cloud Monitoring]-[Metrics Explorer]``で見ることができます。

![Art076.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100574/4fd4b9c5-01e9-04d3-fea3-1dc51dc5b449.png)

## 応用
他のpprofの値も送られるので、ダッシュボードでゴルーチン数とメモリの使用量をならべて表示するといったことも、プログラムの修正無しにできます。

![Art078.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100574/67886da7-44e6-2ee5-eb36-5b33cb7444bd.png)


ゴルーチンの数がしきい値を超えたら、Slackに通知などもできます。

![out_Art075.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100574/404ce9c5-5adb-5a8a-1244-f3fa2ea7ec4d.png)


## 感想など
これでゴルーチンのリーク監視作業から解放されました。

## 元記事・関連記事
- [GCPを使って、できるだけ楽してGo言語のgoroutineのリークを監視する](https://www.kwbtblog.com/entry/2020/07/22/235648)
- [Google Kubernetes Engine（GKE）で、コンテナでエラーが発生したらSlackやメールに通知する方法](https://www.kwbtblog.com/entry/2020/07/20/035720)