diff --git a/Makefile b/Makefile index 1af928d..f24a427 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,13 @@ VERSION=0.1.2 -GITCOMMIT?=$(shell git describe --dirty --always) -LDFLAGS=-ldflags "-w -s -X main.version=${VERSION} -X main.commit=${GITCOMMIT}" +LDFLAGS=-ldflags "-w -s -X main.version=${VERSION}" all: mackerel-plugin-linux-usage -.PHONY: mackerel-plugin-linux-usage +.PHONY: mackerel-plugin-linux-usage linux check lint -mackerel-plugin-linux-usage: main.go +mackerel-plugin-linux-usage: *.go go build $(LDFLAGS) -o mackerel-plugin-linux-usage -linux: main.go +linux: *.go GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o mackerel-plugin-linux-usage check: diff --git a/README.md b/README.md index edc0d2d..5a5420c 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,141 @@ # mackerel-plugin-linux-usage -mackerel metric plugin for linux usage. CPU usage (max 100%), Load average per cores, Number of processes and TCP +LinuxのCPU使用率、CPUあたりのロードアベレージ、プロセス数、TCP接続に関するメトリクスを取得するMackerel用プラグインです。Linuxの `/proc` を参照し、mackerel-agentが読み取れる形式で標準出力に出力します。 -## Usage +## 動作環境 +- Linux(`/proc` がマウントされ、実行ユーザーが必要な情報を読み取れること) +- リリースバイナリの対応アーキテクチャ: amd64、arm64 +- ソースからビルドする場合: Go 1.26.0以上 +- 状態ファイルの保存先に対する読み書き権限 + +## インストール + +### mkrを利用する場合 + +```sh +mkr plugin install monitoring-forge/mackerel-plugin-linux-usage ``` -Usage: - mackerel-plugin-linux-usage [OPTIONS] -Application Options: - -v, --version Show version +### リリースバイナリを利用する場合 + +[GitHub Releases](https://github.com/monitoring-forge/mackerel-plugin-linux-usage/releases)から、環境に合ったZIPファイルをダウンロードして展開してください。実行権限を付与し、任意のディレクトリに配置します。 + +## 使い方 -Help Options: - -h, --help Show this help message +### 手動で実行する + +追加の引数は不要です。 + +```sh +/path/to/mackerel-plugin-linux-usage ``` +出力は「メトリクス名・値・UNIXタイムスタンプ」のタブ区切りです。以下は出力の一部です。 + +```text +linux-usage.cpu.user 2.5 1750000000 +linux-usage.cpu.idle 95 1750000000 +linux-usage.loadavg.loadavg1 0.25 1750000000 +linux-usage.process.all 120 1750000000 +linux-usage.process.running 2 1750000000 +linux-usage.tcp-opens.active 12 1750000000 ``` -$ ./mackerel-plugin-linux-usage -linux-usage.cpu.guest_nice 0 1672116689 -linux-usage.cpu.guest 0 1672116689 -linux-usage.cpu.steal 0.004797 1672116689 -linux-usage.cpu.softirq 0.155898 1672116689 -linux-usage.cpu.irq 0.088742 1672116689 -linux-usage.cpu.iowait 0.028781 1672116689 -linux-usage.cpu.idle 98.455413 1672116689 -linux-usage.cpu.system 0.765098 1672116689 -linux-usage.cpu.nice 0 1672116689 -linux-usage.cpu.user 0.501271 1672116689 -linux-usage.loadavg.loadavg1 0 1672116689 -linux-usage.loadavg.loadavg5 0 1672116689 -linux-usage.loadavg.loadavg15 0 1672116689 -linux-usage.process.all 86 1672116689 -linux-usage.process.running 2 1672116689 -linux-usage.tcp-opens.active 0 1672116689 -linux-usage.tcp-opens.passive 8.325359 1672116689 -linux-usage.tcp-listen.overflows 0 1672116689 -linux-usage.tcp-listen.drops 0 1672116689 + +| オプション | 説明 | +|---|---| +| `-h`, `--help` | ヘルプを表示します。 | +| `-v`, `--version` | バージョン、OS、アーキテクチャ、Goバージョン、コミット情報を表示します。 | + +CPU使用率とTCP関連のメトリクスは前回実行との差分を使うため、状態ファイルがない初回実行では出力されません。通常は約1分間隔で実行してください。 + +### mackerel-agentに設定する + +`mackerel-agent.conf` に次の設定を追加し、mackerel-agentを再起動してください。`command` は実際のインストール先に合わせて変更します。 + +```toml +[plugin.metrics.linux-usage] +command = "/opt/mackerel-agent/plugins/bin/mackerel-plugin-linux-usage" ``` -## Install +グラフ定義のみを確認する場合は、次のように実行します。 + +```sh +MACKEREL_AGENT_PLUGIN_META=1 /path/to/mackerel-plugin-linux-usage +``` + +## メトリクスの意味 + +### CPU使用率: `linux-usage.cpu.*` + +`/proc/stat` のCPU時間の前回値との差分から算出します。単位は `%` で、すべての論理CPUを合計した時間を100%として正規化します。CPU数によって上限が増えることはありません。`idle` を含む各項目の合計は約100%になります。 + +| メトリクス名 | 意味 | +|---|---| +| `linux-usage.cpu.user` | 通常のユーザープロセスの実行時間。`guest` 分を除きます。 | +| `linux-usage.cpu.nice` | nice値で優先度を調整したユーザープロセスの実行時間。`guest_nice` 分を除きます。 | +| `linux-usage.cpu.system` | カーネルでの実行時間。 | +| `linux-usage.cpu.idle` | CPUがアイドル状態だった時間。 | +| `linux-usage.cpu.iowait` | I/O完了待ちとして計上された時間。ディスク使用率そのものではありません。 | +| `linux-usage.cpu.irq` | ハードウェア割り込み処理の時間。 | +| `linux-usage.cpu.softirq` | ソフトウェア割り込み処理の時間。 | +| `linux-usage.cpu.steal` | 仮想CPUがハイパーバイザーにより実行を待たされた時間。 | +| `linux-usage.cpu.guest` | ゲストの仮想CPUの実行時間。 | +| `linux-usage.cpu.guest_nice` | nice値で優先度を調整したゲストの仮想CPUの実行時間。 | + +### ロードアベレージ: `linux-usage.loadavg.*` + +`/proc/loadavg` のロードアベレージを、`/proc/stat` から取得した論理CPU数で割った値です。単位は無次元で、パーセントではありません。実行可能なタスクに加えて、割り込み不能な待機状態(I/O待ちなど)のタスクも負荷に含まれます。 + +| メトリクス名 | 意味 | +|---|---| +| `linux-usage.loadavg.loadavg1` | 過去1分のロードアベレージ ÷ 論理CPU数。 | +| `linux-usage.loadavg.loadavg5` | 過去5分のロードアベレージ ÷ 論理CPU数。 | +| `linux-usage.loadavg.loadavg15` | 過去15分のロードアベレージ ÷ 論理CPU数。 | + +例: 4論理CPUでロードアベレージが2の場合、出力は `0.5` です。`1.0` は論理CPU数と同じ数のタスクが負荷に含まれる状態を表しますが、CPU使用率100%を意味するものではありません。 + +### プロセス数: `linux-usage.process.*` + +`/proc` にあるPIDごとの `stat` を読み取り、取得できたプロセスを数えます。単位はプロセス数です。 + +| メトリクス名 | 意味 | +|---|---| +| `linux-usage.process.all` | 状態を読み取れたプロセスの総数。 | +| `linux-usage.process.running` | 状態が `R`(実行中または実行待ち)のプロセス数。 | + +スレッド単位の集計ではありません。読み取り中に終了したプロセスや、権限不足などで状態を取得できなかったプロセスは除外されます。 + +### TCP接続: `linux-usage.tcp-opens.*` + +`/proc/self/net/snmp` の累積カウンターを使います。出力値は **1分あたりの増加数(件/分)** です。現在の接続数ではありません。 + +| メトリクス名 | 元のカウンター | 意味 | +|---|---|---| +| `linux-usage.tcp-opens.active` | `Tcp.ActiveOpens` | 能動的な接続開始(CLOSEDからSYN-SENTへの遷移)。接続成功数とは異なります。 | +| `linux-usage.tcp-opens.passive` | `Tcp.PassiveOpens` | 受動的な接続開始(LISTENからSYN-RCVDへの遷移)。 | + +### TCP待ち受け: `linux-usage.tcp-listen.*` + +`/proc/self/net/netstat` の累積カウンターを使います。出力値は **1分あたりの増加数(件/分)** です。 + +| メトリクス名 | 元のカウンター | 意味 | +|---|---|---| +| `linux-usage.tcp-listen.overflows` | `TcpExt.ListenOverflows` | 待ち受けソケットのacceptキューが満杯になったことによるオーバーフロー。 | +| `linux-usage.tcp-listen.drops` | `TcpExt.ListenDrops` | 待ち受けソケットで発生したパケットのドロップ。キューあふれ以外の原因も含みます。 | + +TCP関連の値は `go-mackerel-plugin` が `(現在値 − 前回値) × 60 ÷ 経過秒数` で計算するため、小数になる場合があります。対応するカウンターが取得できない環境では、その項目は出力されません。 + +## 状態ファイルと注意事項 + +- 保存先は環境変数 `MACKEREL_PLUGIN_WORKDIR` です。未設定の場合はOSの一時ディレクトリ(通常 `/tmp`)を使います。指定するディレクトリは事前に作成し、実行ユーザーに読み書き権限を付与してください。 +- CPU状態は `mackerel-plugin-linux-usage-<実効UID>` に保存します。JSON形式とファイル名は従来と互換性があり、`saferio` で読み取りとアトミックな更新を行います。TCP差分用の状態は `go-mackerel-plugin` が別ファイルに保存します。 +- 同じ実効UID・保存先で複数の実行を行うとCPU状態を共有します。手動確認で定期実行の差分計算に影響を与えたくない場合は、別の `MACKEREL_PLUGIN_WORKDIR` を使ってください。 +- 前回のCPU状態が600秒より古い場合、その回はメトリクス取得がエラーになります。CPU状態は更新されるため、次回実行から再計算できます。TCP差分にも600秒の上限があり、超過した回はログにエラーが記録され、TCP関連の値は0になります。 +- CPUカウンターに変化がない場合は、CPU使用率を出力しません。再起動などでカウンターが減少した場合、CPUの負の差分は0として扱い、TCPの負の差分はログにエラーが記録され、0として出力されます。 +- コンテナ内では、見えている `/proc` とネットワーク名前空間の情報を取得します。CPU使用率・ロードアベレージはcgroupのCPU制限に合わせた正規化ではありません。 + + +## ライセンス -Please download release page or `mkr plugin install monitoring-forge/mackerel-plugin-linux-usage`. \ No newline at end of file +[MIT License](LICENSE) diff --git a/go.mod b/go.mod index 9c002bf..0d5f881 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,16 @@ module github.com/monitoring-forge/mackerel-plugin-linux-usage go 1.26.0 require ( - github.com/jessevdk/go-flags v1.6.1 github.com/mackerelio/go-mackerel-plugin v0.1.6 github.com/mackerelio/golib v1.2.2 + github.com/monitoring-forge/flagrun v0.0.10 + github.com/monitoring-forge/saferio v0.0.3 github.com/prometheus/procfs v0.22.0 ) require ( + github.com/jessevdk/go-flags v1.6.1 // indirect + github.com/mackerelio/checkers v0.2.1 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect ) diff --git a/go.sum b/go.sum index aeb7ca3..81eb0e4 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,22 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +github.com/mackerelio/checkers v0.2.1 h1:vRXzAT+CEyaLjSrHVxKUBjyWq1kfo8QcI8ZoSKZATQs= +github.com/mackerelio/checkers v0.2.1/go.mod h1:CW3k/5bvHhxDrfKgWvMvNH0R51zco141ZVxlI7o/KAc= github.com/mackerelio/go-mackerel-plugin v0.1.6 h1:NKAutTQLxBH8/T0aeawIMfPSg4ZRf+TivkOwgUK2Yj0= github.com/mackerelio/go-mackerel-plugin v0.1.6/go.mod h1:2OkQFK5HpUiVJ+p8Xa8wwxzaPusUFTNsj5OQjL9Mw7k= github.com/mackerelio/golib v1.2.2 h1:fQfW1o2KvPyIsAH/7+M2J0gpf2jLu+NyYpDCN79zxW4= github.com/mackerelio/golib v1.2.2/go.mod h1:itcSOfrsT5XvkzflFPebT43RrliaKDz2T6YGYt3jclw= +github.com/monitoring-forge/flagrun v0.0.10 h1:KybhBetKlDxuIBj/rilKQWE60fynVzReKl+KLBcWCuc= +github.com/monitoring-forge/flagrun v0.0.10/go.mod h1:hxpSc4cgzDMi6lj40T97GNO/PglgM5FW79fRGdzexBY= +github.com/monitoring-forge/saferio v0.0.3 h1:1i3va3QQ9QPunwsgekDzC8RV91KT1Xcb++Z0Dnl9zFY= +github.com/monitoring-forge/saferio v0.0.3/go.mod h1:1o6nyDJ4isee9VH3eUx5uw4C7LEgPFsq7M3tO+VMyvM= github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics= github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= diff --git a/main.go b/main.go index 706f5eb..2ca80dd 100644 --- a/main.go +++ b/main.go @@ -1,64 +1,28 @@ package main import ( - "fmt" "os" - "path/filepath" - "runtime" - "github.com/jessevdk/go-flags" mp "github.com/mackerelio/go-mackerel-plugin" "github.com/mackerelio/golib/pluginutil" + "github.com/monitoring-forge/flagrun" ) var version string -var commit string - -const ( - OK = iota - WARNING - CRITICAL - UNKNOWN -) type Opt struct { Version bool `short:"v" long:"version" description:"Show version"` + workDir string } func main() { - os.Exit(_main()) -} - -func _main() int { - opt := &Opt{} - psr := flags.NewParser(opt, flags.HelpFlag|flags.PassDoubleDash) - _, err := psr.Parse() - if opt.Version { - if commit == "" { - commit = "dev" - } - fmt.Printf( - "%s-%s\n%s/%s, %s, %s\n", - filepath.Base(os.Args[0]), - version, - runtime.GOOS, - runtime.GOARCH, - runtime.Version(), - commit) - return OK - } else if flags.WroteHelp(err) { - fmt.Fprintf(os.Stdout, "%v\n", err) - return OK - } else if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - return UNKNOWN + opt := &Opt{ + workDir: pluginutil.PluginWorkDir(), } + os.Exit(flagrun.Ship(opt, flagrun.Version(version))) +} - tmpDir := pluginutil.PluginWorkDir() - u := LinuxUsagePlugin{ - workDir: tmpDir, - } - plugin := mp.NewMackerelPlugin(u) +func (opt *Opt) Run(_ []string) { + plugin := mp.NewMackerelPlugin(opt) plugin.Run() - return OK } diff --git a/open_unix.go b/open_unix.go deleted file mode 100644 index d4ff433..0000000 --- a/open_unix.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !windows -// +build !windows - -package main - -import ( - "os" - "syscall" -) - -// openRD opens a file for reading with O_NOFOLLOW flag to prevent symlink attacks. -// This function is specific to Unix-like systems where syscall.O_NOFOLLOW is available. -func openRD(filename string) (*os.File, error) { - return os.OpenFile(filename, os.O_RDONLY|syscall.O_NOFOLLOW, 0) -} diff --git a/open_windows.go b/open_windows.go deleted file mode 100644 index 1f6d94d..0000000 --- a/open_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build windows -// +build windows - -package main - -import "os" - -// openRD opens a file for reading with appropriate flags for Windows. -// Since Windows doesn't support O_NOFOLLOW, we simply open the file with O_RDONLY. -func openRD(filename string) (*os.File, error) { - return os.OpenFile(filename, os.O_RDONLY, 0) -} diff --git a/statsfile.go b/statsfile.go index 197c1fe..22dbc44 100644 --- a/statsfile.go +++ b/statsfile.go @@ -1,11 +1,9 @@ package main import ( - "encoding/json" - "os" - "path/filepath" "time" + "github.com/monitoring-forge/saferio" "github.com/prometheus/procfs" ) @@ -14,48 +12,13 @@ type stats struct { Time int64 `json:"time"` } -func fileExists(dir, filename string) bool { - _, err := os.Stat(filepath.Join(dir, filename)) - return err == nil -} - func writeStats(dir, filename string, st procfs.CPUStat) error { - newFile, err := os.CreateTemp(dir, "linux-usage-") - if err != nil { - return err - } - n := time.Now().Unix() - je := json.NewEncoder(newFile) - err = je.Encode(stats{st, n}) - if err != nil { - newFile.Close() - _ = os.Remove(newFile.Name()) - return err - } - - err = newFile.Close() - if err != nil { - _ = os.Remove(newFile.Name()) - return err - } - - if err := os.Rename(newFile.Name(), filepath.Join(dir, filename)); err != nil { - _ = os.Remove(newFile.Name()) - return err - } - return nil + return saferio.WriteJSON(dir, filename, stats{CPUStat: st, Time: time.Now().Unix()}) } func readStats(dir, filename string) (int64, procfs.CPUStat, error) { - file, err := openRD(filepath.Join(dir, filename)) - if err != nil { - return 0, procfs.CPUStat{}, err - } - defer file.Close() st := stats{} - decoder := json.NewDecoder(file) - err = decoder.Decode(&st) - if err != nil { + if err := saferio.ReadJSON(dir, filename, &st); err != nil { return 0, procfs.CPUStat{}, err } return st.Time, st.CPUStat, nil diff --git a/statsfile_test.go b/statsfile_test.go index 66e6864..5b88c18 100644 --- a/statsfile_test.go +++ b/statsfile_test.go @@ -9,27 +9,10 @@ import ( "testing" "time" + "github.com/monitoring-forge/saferio" "github.com/prometheus/procfs" ) -func TestFileExists(t *testing.T) { - tmpDir := t.TempDir() - - // Test non-existent file - if fileExists(tmpDir, "nonexistent.txt") { - t.Error("fileExists should return false for non-existent file") - } - - // Test existing file - existFile := filepath.Join(tmpDir, "exists.txt") - if err := os.WriteFile(existFile, []byte("test"), 0644); err != nil { - t.Fatalf("failed to create test file: %v", err) - } - if !fileExists(tmpDir, "exists.txt") { - t.Error("fileExists should return true for existing file") - } -} - func TestWriteStats(t *testing.T) { tmpDir := t.TempDir() filename := "test-stats.json" @@ -53,7 +36,7 @@ func TestWriteStats(t *testing.T) { } // Verify file exists - if !fileExists(tmpDir, filename) { + if !saferio.FileExists(tmpDir, filename) { t.Error("writeStats should create the target file") } @@ -201,3 +184,47 @@ func TestCorruptedStatsFile(t *testing.T) { t.Error("readStats should return an error for corrupted file content") } } + +func TestReadStatsLegacyFormat(t *testing.T) { + dir := t.TempDir() + // State written before the saferio migration must remain readable. + data := []byte(`{"cpustat":{"User":100,"Idle":900},"time":1234567890}`) + if err := os.WriteFile(filepath.Join(dir, "legacy.json"), data, 0600); err != nil { + t.Fatal(err) + } + timestamp, cpu, err := readStats(dir, "legacy.json") + if err != nil { + t.Fatal(err) + } + if timestamp != 1234567890 || cpu.User != 100 || cpu.Idle != 900 { + t.Fatalf("unexpected legacy state: time=%d cpu=%+v", timestamp, cpu) + } +} + +func TestStatsSymlink(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.json") + original := []byte(`{"cpustat":{"User":100},"time":1234567890}`) + if err := os.WriteFile(target, original, 0600); err != nil { + t.Fatal(err) + } + if err := os.Symlink(target, filepath.Join(dir, "state.json")); err != nil { + t.Fatal(err) + } + if _, _, err := readStats(dir, "state.json"); err == nil { + t.Fatal("readStats must reject symlinks") + } + if err := writeStats(dir, "state.json", procfs.CPUStat{User: 200}); err != nil { + t.Fatal(err) + } + data, err := os.ReadFile(target) + if err != nil { + t.Fatal(err) + } + if string(data) != string(original) { + t.Fatal("writeStats modified the symlink target") + } + if _, cpu, err := readStats(dir, "state.json"); err != nil || cpu.User != 200 { + t.Fatalf("replacement state: cpu=%+v err=%v", cpu, err) + } +} diff --git a/usageplugin.go b/usageplugin.go index cf6b70f..6da3772 100644 --- a/usageplugin.go +++ b/usageplugin.go @@ -7,21 +7,18 @@ import ( "time" mp "github.com/mackerelio/go-mackerel-plugin" + "github.com/monitoring-forge/saferio" "github.com/prometheus/procfs" ) var tooOldDuration = 600.0 // seconds -type LinuxUsagePlugin struct { - workDir string -} - -func (u LinuxUsagePlugin) tempfilePath() string { +func tempfilePath() string { uid := os.Geteuid() return fmt.Sprintf("mackerel-plugin-linux-usage-%d", uid) } -func (u LinuxUsagePlugin) GraphDefinition() map[string]mp.Graphs { +func (o *Opt) GraphDefinition() map[string]mp.Graphs { return map[string]mp.Graphs{ "linux-usage.cpu": { Label: "Linux CPU usage max 100%", @@ -75,7 +72,7 @@ func (u LinuxUsagePlugin) GraphDefinition() map[string]mp.Graphs { } } -func (u LinuxUsagePlugin) gaugeMetrics(pf procfs.FS) (map[string]float64, error) { +func (o *Opt) gaugeMetrics(pf procfs.FS) (map[string]float64, error) { res := map[string]float64{} st, err := pf.Stat() if err != nil { @@ -117,7 +114,7 @@ func (u LinuxUsagePlugin) gaugeMetrics(pf procfs.FS) (map[string]float64, error) return res, nil } -func (u LinuxUsagePlugin) gaugeNetMetrics(pf procfs.FS) (map[string]float64, error) { +func (o *Opt) gaugeNetMetrics(pf procfs.FS) (map[string]float64, error) { res := map[string]float64{} selffs, err := pf.Self() if err != nil { @@ -151,7 +148,7 @@ func minZero(a float64) float64 { return max(a, 0) } -func (u LinuxUsagePlugin) cpuMetrics(pf procfs.FS) (map[string]float64, error) { +func (o *Opt) cpuMetrics(pf procfs.FS) (map[string]float64, error) { res := map[string]float64{} cur, err := pf.Stat() @@ -159,21 +156,21 @@ func (u LinuxUsagePlugin) cpuMetrics(pf procfs.FS) (map[string]float64, error) { return res, err } - path := u.tempfilePath() + path := tempfilePath() curCPU := cur.CPUTotal defer func() { - if writeErr := writeStats(u.workDir, path, curCPU); writeErr != nil { + if writeErr := writeStats(o.workDir, path, curCPU); writeErr != nil { fmt.Fprintf(os.Stderr, "Error: failed to save stats to %s: %v\n", path, writeErr) } }() - if !fileExists(u.workDir, path) { + if !saferio.FileExists(o.workDir, path) { fmt.Fprintf(os.Stderr, "Notice: first time execution command\n") return res, nil } - prevTime, prevCPU, err := readStats(u.workDir, path) + prevTime, prevCPU, err := readStats(o.workDir, path) if err != nil { return res, err } @@ -244,22 +241,22 @@ func (u LinuxUsagePlugin) cpuMetrics(pf procfs.FS) (map[string]float64, error) { return res, nil } -func (u LinuxUsagePlugin) FetchMetrics() (map[string]float64, error) { +func (o *Opt) FetchMetrics() (map[string]float64, error) { pf, err := procfs.NewDefaultFS() if err != nil { return nil, err } - res, err := u.gaugeMetrics(pf) + res, err := o.gaugeMetrics(pf) if err != nil { return nil, err } - net, err := u.gaugeNetMetrics(pf) + net, err := o.gaugeNetMetrics(pf) if err != nil { return nil, err } - cpu, err := u.cpuMetrics(pf) + cpu, err := o.cpuMetrics(pf) if err != nil { return nil, err } diff --git a/usageplugin_test.go b/usageplugin_test.go index e2fad01..cec6746 100644 --- a/usageplugin_test.go +++ b/usageplugin_test.go @@ -119,7 +119,7 @@ func TestGaugeMetrics(t *testing.T) { buildProcfsTree(t, procDir) // Set the workDir to the temporary directory - plugin := LinuxUsagePlugin{ + o := &Opt{ workDir: tmpDir, } @@ -130,7 +130,7 @@ func TestGaugeMetrics(t *testing.T) { } // Call gaugeMetrics and check for errors - res, err := plugin.gaugeMetrics(pf) + res, err := o.gaugeMetrics(pf) if err != nil { t.Fatalf("gaugeMetrics failed: %v", err) } @@ -150,7 +150,7 @@ func TestGaugeNetMetrics(t *testing.T) { tmpDir := t.TempDir() procDir := filepath.Join(tmpDir, "proc") buildProcfsTree(t, procDir) - plugin := LinuxUsagePlugin{ + o := &Opt{ workDir: tmpDir, } // Create a procfs.FS instance pointing to the temporary procfs directory @@ -158,7 +158,7 @@ func TestGaugeNetMetrics(t *testing.T) { if err != nil { t.Fatalf("Failed to create procfs FS: %v", err) } - res, err := plugin.gaugeNetMetrics(pf) + res, err := o.gaugeNetMetrics(pf) if err != nil { t.Fatalf("gaugeNetMetrics failed: %v", err) } @@ -202,10 +202,10 @@ softirq 6428505879 0 1343933345 732 1669274374 90025047 0 430534 0 6764 33248350 t.Fatalf("Failed to write /proc/stat: %v", err) } - plugin := LinuxUsagePlugin{ + o := &Opt{ workDir: tmpDir, } - res, err := plugin.cpuMetrics(pf) + res, err := o.cpuMetrics(pf) if err != nil { t.Fatalf("cpuMetrics failed: %v", err) } @@ -228,7 +228,7 @@ softirq 6428709196 0 1343939038 732 1669293799 90025278 0 431563 0 6764 33250120 t.Fatalf("Failed to write /proc/stat for second execution: %v", err) } - res2, err := plugin.cpuMetrics(pf) + res2, err := o.cpuMetrics(pf) if err != nil { t.Fatalf("cpuMetrics failed on second execution: %v", err) }