Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
158 changes: 128 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -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`.
[MIT License](LICENSE)
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
52 changes: 8 additions & 44 deletions main.go
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 0 additions & 15 deletions open_unix.go

This file was deleted.

12 changes: 0 additions & 12 deletions open_windows.go

This file was deleted.

43 changes: 3 additions & 40 deletions statsfile.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package main

import (
"encoding/json"
"os"
"path/filepath"
"time"

"github.com/monitoring-forge/saferio"
"github.com/prometheus/procfs"
)

Expand All @@ -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
Expand Down
Loading
Loading