このページの本文へ

FIXER Tech Blog - Development

FIXER cloud.config Tech Blog

Prometheusで辞書形式のメトリクスを持つExporterを作りたい!

2023年08月02日 10時00分更新

文● 三上柊悟/FIXER

  • この記事をはてなブックマークに追加
  • 本文印刷

 本記事はFIXERが提供する「cloud.config Tech Blog」に掲載された「Prometheusで辞書形式でメトリクスを持つようなExporterを作りたい!」を再編集したものです。

 PrometheusのExporterは自作することができ、先人が既に簡単な書き方の記事を挙げています。 しかし、タイトルにある通り辞書型のような形式でメトリクスを保持するような方法が記載されているドキュメントは見当たらなかったので、ブログにしてみようと思いました。

動作環境
・macOS
 ・Ventura 13.2.1
・Go
 ・1.20.3
・Rancher Desktop

そもそもPrometheusとは? Exporterとは?

 Prometheusはざっくり言うと、「メトリック監視のために時系列ごとの値を保存することができるOSS」です。 値の収集はymlで定義されたExporterと呼ばれるものからPrometheus側からスクレイピングをしており、そこで収集したデータを独自の圧縮形式でファイルに保存しています。 Prometheusで収集したデータはPrometheusのウェブUIまたはGrafanaなどの可視化ツールでpromqlと呼ばれるクエリを使うことで値の取得やグラフの描画ができます。

 詳しくは公式ドキュメントを参考にしてみてください。

ソースコード

 今回はプロセス監視のため、 psコマンドで取得したcpu使用率をアプリケーションのファイルパスをキーとするような辞書型のメトリクスで出力できるようにExporterを実装しました。

package main
import (
"flag"
"net/http"
"os/exec"
"strings"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// メトリクスの名前空間用の変数
var namespace = "process"
// メトリクス保持をする変数
var cpuUsagePercentGauge = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "cpu_usage_percent",
Help: "cpu usage percent by process",
},
[]string{"filename"},
)
// メトリクス収集用の関数
func Collect() {
// psコマンドの実行結果を取得
commandResult, _ := exec.Command("/bin/zsh", "-c", "ps aux | awk '{ printf(\"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\\n\",$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);}'").Output()
convertedCommandResult := string(commandResult)
convertedCommandResultArray := strings.Split(convertedCommandResult, "\n")
// 取得した実行結果からアプリケーションのファイルパスをキーとしてcpu使用率を代入
for i := 0; i < len(convertedCommandResultArray) - 1; i++ {
row := strings.Split(convertedCommandResultArray[i + 1], ",")
if len(row) > 10 {
cpuUsagePercent, _ := strconv.ParseFloat(row[2], 64)
fileName := row[10]
cpuUsagePercentGauge.With(prometheus.Labels{"filename": fileName}).Set(cpuUsagePercent)
}
}
}
func main() {
flag.Parse()
// 並列処理で無限ループ
go func() {
for {
// メトリクス収集
Collect()
// 一度メトリクス収集してから30秒待つ
time.Sleep(30 * time.Second)
}
}()
// localhost:8191で待機
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8191", nil)
Collect()
}

環境構築

Exporter

 作成したソースコードをビルドして実行ファイルを生成した後、コマンドラインで実行しておけばExporter側の準備は完了です。

Prometheus

 予め適当なディレクトリにprometheus.ymlを配置しておきます。

global:
 scrape_interval: 15s
 evaluation_interval: 15s
scrape_configs:
 - job_name: 'process_exporter'
 static_configs:
 - targets: ['host.docker.internal:8191']

 その後また別のディレクトリでdocker-compose.ymlを配置します。

version: '3'
services:
 prometheus:
 image: prom/prometheus
 container_name: prometheus
 volumes:
 - /path/to/prometheus.yml:/etc/prometheus/prometheus.yml
 ports:
 - 9090:9090

 最後にdocker-compose.ymlを配置したディレクトリで下記コマンドを実行すればPrometheus側の準備は完了です。

実行結果

 http://localhost:9090 をブラウザーで開いた後、クエリの入力欄にprocess_cpu_usage_percentと入力してExecuteボタンを押せば確認できます。

 また、クエリを編集すれば出力するプロセスの絞り込みができたり、関数を使って合計値をグラフで出力することも可能です。

感想

 今回はローカルマシンでプロセスを監視しましたが、VM上で実行すればVMのプロセスを監視できるはずです。加えて、外部からPrometheusにアクセスできるようにすればわざわざVMに接続してタスクマネージャーなどを開かなくてもウェブ上から閲覧できます。そういう仕組みづくりができればVMの負荷が高騰した時もどのプロセスが原因なのか後から調査する場合でも対応ができるようになると思います。

 こういった仕組みづくりはやっぱり好きだなぁとコードを書いていてしみじみ思いました。

三上柊悟/FIXER
開発したり運用したりしてる人。好物はメロンパン。

カテゴリートップへ

この連載の記事