gopsutilでシステム情報を取得する

# はじめに

[gopsutil](https://github.com/shirou/gopsutil)は、 PCのさまざまなリソース(CPU、メモリ、ストレージ、プロセス、etc...)を取得するためのライブラリです。

OSによってある程度違いはあるものの、CPUやメモリなどの基本的な情報であればどのOSでも共通の処理で取得することができます。

そんなgopsutilを使用して、主要な情報を取得するための方法について書いてみたいと思います。

ちなみに実行環境としては以下のとおりです。

- Macbook Air (M1)
- RAM 16GB
- macOS Sequioa 15.0.1
- go v1.23.5

# インストール

現在(2025/2/4)、`v4.25.1`が最新となりますのでv4系をインストールします。

```bash
go mod init psutil
go get go get github.com/shirou/gopsutil/v4
```

# 使い方

## CPU

```go:cpu.go
package main

import (
	"github.com/shirou/gopsutil/v4/cpu"
	"fmt"
	"log"
)

func main() {
	// 物理コア数
	physicalCores, err := cpu.Counts(false)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Physical cores: ", physicalCores)

	// 論理コア数
	logicalCores, err := cpu.Counts(true)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Logical cores: ", logicalCores)

	// CPU使用率(コアごと)
	percentages, err := cpu.Percent(0, true)
	if err != nil {
		log.Fatal(err)
	}
	for i, percentage := range percentages {
		fmt.Printf("Core %d: %.3f%%\n", i, percentage)
	}

	// CPU使用率(全体)
	percentages, err = cpu.Percent(0, false)
	if err != nil {
		log.Fatal(err)
	}
	for _, percentage := range percentages {
		fmt.Printf("CPU: %.3f%%\n", percentage)
	}

	// CPU情報
	info, err := cpu.Info()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Info : %v\n", info)

	// CPU時間(コアごと)
	timeStat, err := cpu.Times(true)
	if err != nil {
		log.Fatal(err)
	}
	for _, stat := range timeStat {
		fmt.Printf("Stat : %v\n", stat)
	}

	// CPU時間(全体)
	timeStat, err = cpu.Times(false)
	if err != nil {
		log.Fatal(err)
	}
	for _, stat := range timeStat {
		fmt.Printf("Stat : %v\n", stat)
	}
}

```

```bash:output
Physical cores:  8
Logical cores:  8
Core 0: 34.038%
Core 1: 32.291%
Core 2: 29.049%
Core 3: 27.104%
Core 4: 8.905%
Core 5: 6.503%
Core 6: 4.603%
Core 7: 3.474%
CPU: 18.208%
Info : [{"cpu":0,"vendorId":"","family":"0","model":"0","stepping":0,"physicalId":"","coreId":"","cores":8,"modelName":"Apple M1","mhz":3204,"cacheSize":0,"flags":null,"microcode":""}]
Stat : {"cpu":"cpu0","user":274114.5,"system":150971.8,"idle":823754.2,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu1","user":261994.3,"system":141485.7,"idle":846034.8,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu2","user":234671.2,"system":128580.5,"idle":887236.7,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu3","user":220393.6,"system":118787.9,"idle":912236.6,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu4","user":83856.2,"system":28089.3,"idle":1145093.0,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu5","user":62548.0,"system":19218.3,"idle":1175573.6,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu6","user":45141.3,"system":12750.9,"idle":1199839.4,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu7","user":34312.5,"system":9388.3,"idle":1214280.5,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
Stat : {"cpu":"cpu-total","user":1217031.6,"system":609272.6,"idle":8204048.9,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0}
```

### `func Counts(logical bool) (int, error)`

CPUのコア数を返す関数です。

引数を`true`に設定することで論理コア数、`false`に設定することで物理コア数を取得することができます。

### `func Percent(interval time.Duration, percpu bool) ([]float64, error)`

CPU使用率を返す関数です。

`interval`で0を指定した場合、最後の呼び出しと比較された値が返される的なことが書いてありますが、1回だけ呼び出す分には特に関係なさそうという理解です。

`percpu`については、`true`に設定することでコアごと、`false`に設定することで全体の値を取得できます。

### `func Info() ([]InfoStat, error)`

CPUの名称やクロック数といった情報を取得できる関数です。

M1だと取得できていない情報もありそうですが、他のCPUだと違ったりするのでしょうか？

### `func Times(percpu bool) ([]TimeStat, error)`

CPU時間を取得できる関数です。

`percpu`を`true`にすることでコアごと、`false`にすることで全体の値を取得することができます。

## メモリ

```go:memory.go
package main

import (
	"github.com/shirou/gopsutil/v4/mem"
	"fmt"
	"log"
)

func main() {
	// スワップメモリ
	swapMemory, err := mem.SwapMemory()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Swap: %v\n", swapMemory)

	// 仮想メモリ
	virtualMemory, err := mem.VirtualMemory()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Virtual: %v\n", virtualMemory)
}
```

```bash:output
Swap: {"total":7516192768,"used":6048514048,"free":1467678720,"usedPercent":80.47310965401786,(省略)...}
Virtual: {"total":17179869184,"available":3002744832,"used":14177124352,"usedPercent":82.52172470092773,"free":74203136,(省略)...}
```

### `func SwapMemory() (*SwapMemoryStat, error)`

スワップメモリに関する情報を取得できる関数です。

`Total`、`Used`、`Free`、`UsedPercent`あたりを主に使うことになりそうです。

### `func VirtualMemory() (*VirtualMemoryStat, error)`

仮想メモリに関する情報を取得できる関数です。

SwapMemoryと同様、主に使用するのは`Total`、`Available`、`Used`、`UsedPercent`、`Free`あたりになりそうです。

それ以外にもさまざまな項目があるのですが、取得できるOSが限られているため使い所は限られるのかなと思います。

## ストレージ

```go:storage.go
package main

import (
	"github.com/shirou/gopsutil/v4/disk"
	"fmt"
	"log"
)

func main() {
	// IOカウンター
	iocounters, err := disk.IOCounters()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("IOCounters: %+v\n", iocounters)

	// パーティション情報
	partitions, err := disk.Partitions(false)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Partitions: %+v\n", partitions)

	// ディスク使用量
	for _, partition := range partitions {
		usageStat, err := disk.Usage(partition.Mountpoint)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("Disk: %v\n", usageStat)
	}
}
```

```bash:output
IOCounters: map[disk0:{"readCount":202849236,"mergedReadCount":0,"writeCount":83471681,"mergedWriteCount":0,"readBytes":9225728118784,"writeBytes":1490395799552,"readTime":76255585,"writeTime":7014452,"iopsInProgress":0,"ioTime":83270037,"weightedIO":0,"name":"disk0","serialNumber":"","label":""}, (省略)...]
Partitions: [{"device":"/dev/disk3s3s1","mountpoint":"/","fstype":"apfs","opts":["ro","journaled","multilabel"]} (省略)...]
Disk: {"path":"/","fstype":"apfs","total":245107195904,"free":44452356096,"used":200654839808,"usedPercent":81.8641162565417,"inodesTotal":434512441,"inodesUsed":407401,"inodesFree":434105040,"inodesUsedPercent":0.09376049142859871}, (省略)...
```

#### `func IOCounters(names ...string) (map[string]IOCountersStat, error)`

IOCounterを取得できる関数です。

IOCounterについての説明は特に見当たらないので予想ですが、中身を見ている限りストレージの総読み書き量みたいな感じなんでしょうか。

#### `func Partitions(all bool) ([]PartitionStat, error)`

パーティション一覧を取得できる関数です。

デバイスごとにマウントポイントやファイルシステムが格納されています。この関数から取得できる`Mountpoint`は後の`Usage()`で使用します。

#### `func Usage(path string) (*UsageStat, error)`

ストレージの使用量を取得できる関数です。

合計容量や使用済み容量、空き容量などストレージ周りで知りたい情報はだいたい取得できそうです。

## ネットワーク

```go:network.go
package main

import (
	"fmt"
	"github.com/shirou/gopsutil/v4/net"
	"log"
)

func main() {
	// コネクション
	connections, err := net.Connections("all")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Connections: %v\n", connections)

	// インターフェース
	interfaces, err := net.Interfaces()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Interfaces: %v\n", interfaces)
}
```

```bash:output
Connections: [{"fd":8,"family":2,"type":1,"localaddr":{"ip":"*","port":52392},"remoteaddr":{"ip":"","port":0},"status":"LISTEN","uids":null,"pid":438} (省略)...
Interfaces: [{"index":1,"mtu":16384,"name":"lo0","hardwareAddr":"","flags":["up","loopback","multicast"],"addrs":[{"addr":"127.0.0.1/8"} (省略)...
```

### `func Connections(kind string) ([]ConnectionStat, error)`

アドレスやポートなどの接続情報を取得できる関数です。

他にPIDなども取得できるみたいですが、ポートなどを使用しているプロセスのPIDになるんでしょうかね。

### `func Intefaces() ([]IntafaceStat, error)`

ネットワークインターフェース情報を取得できる関数です。

MACアドレスやConnectionsと同様IPアドレスなども取得できそうです。

## プロセス

```go:process.go
package main

import (
	"fmt"
	"github.com/shirou/gopsutil/v4/process"
	"log"
)

func main() {
	// プロセス一覧
	processes, err := process.Processes()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Processes: %v\n", processes)

	// PID == 1(launchd)
	if len(processes) == 0 {
		fmt.Println("No processes found")
	}
	p := processes[0]

	// 実行ユーザー名
	username, err := p.Username()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Username: %v\n", username)

	// プロセス名
	name, err := p.Name()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Name: %s\n", name)

	// プロセスがフォアグラウンドか
	foreground, err := p.Foreground()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Foreground: %t\n", foreground)

	// プロセスがバックグランドか
	background, err := p.Background()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Background: %t\n", background)

	// 子プロセス一覧
	children, err := p.Children()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Children: %v\n", children)

	// 接続情報(ネットワークのものと同じ)
	connections, err := p.Connections()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Connections: %v\n", connections)

	// 実行パス
	exe, err := p.Exe()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Exe: %v\n", exe)

	// グループID一覧
	gids, err := p.Gids()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Gids: %v\n", gids)

	// CPU時間の割合(%)
	cpuPercent, err := p.CPUPercent()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("CPUPercent: %v\n", cpuPercent)

	// メモリ情報
	memoryInfo, err := p.MemoryInfo()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("MemoryInfo: %v\n", memoryInfo)

	// メモリ使用率(%)
	memoryPercent, err := p.MemoryPercent()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("MemoryPercent: %v\n", memoryPercent)

	// スレッド数
	numThreads, err := p.NumThreads()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("NumThreads: %v\n", numThreads)

	// ステータス(gopsutil独自)
	status, err := p.Status()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status: %v\n", status)
}
```

```bash:output
Processes: [{"pid":1} {"pid":81} {"pid":82} (省略)...]
Username: root
Name: launchd
Foreground: false
Background: true
Children: [{"pid":81} {"pid":82} (省略)...]
Connections: []
Exe: /sbin/launchd
Gids: [0 0 0]
CPUPercent: 0
MemoryInfo: {"rss":0,"vms":0,"hwm":0,"data":0,"stack":0,"locked":0,"swap":0}
MemoryPercent: 0
NumThreads: 0
Status: [sleep]
```

### `func Processes() ([]*Process, error)`

プロセス一覧を取得するための関数です。

取得したプロセスに対して以下の関数を実行して必要な情報を取得するかたちになります。

今回はプロセス一覧の中からPIDが1のプロセス(launchd)について見ていきます。

### `func (p *Process) Usename() (string, error)`

プロセスを実行しているユーザー名を取得する関数です。

rootユーザーによって実行されていることが確認できます。

### `func (p *Process) Name() (string, error)`

プロセス名を取得できる関数です。

launchdという名称が取得できていることがわかります。

### `func (p *Process) Foreground() (bool, error)`

プロセスがフォアグラウンドで実行されているかを取得できる関数です。

今回はバックグラウンドで実行されているため、`false`が返されています。

### `func (p *Process) Background() (bool, error)`

プロセスがフォアグラウンドで実行されているかを取得できる関数です。

当然と言えば当然ですが、`Foreground()`とは逆の結果になります。

### `func (p *Process) Children() ([]*Process, error)`

子プロセス一覧を取得できる関数です。

今回はlaunchdの子プロセスになるため、`Processes()`で取得していたプロセス一覧のうち、launchdを除いたものが取得されています。

### `func (p *Process) Connections() ([]net.ConnectionStat, error)`

ネットワークの項目で実行したものと同じく、接続情報を取得するための関数になります。

### `func (p *Process) Exe() (string, error)`

実行パスを取得できる関数です。

`/sbin/launchd`が返されており、実際にターミナルで`which launchd`と実行した場合と同じ値になります。

### `func (p *Process) Gids() ([]uint32, error)`

グループID一覧を取得できる関数です。

### `func (p *Process) CPUPercent() (float64, error)`

CPU時間(%)を取得できる関数です。

特定のプロセスに対してどのぐらいCPUを使用しているかを取得する場合に使えそうです。

### `func (p *Process) MemoryInfo() (*MemoryInfoStat, error)`

プロセスが使用しているメモリ関連の情報を取得するための関数です。

こちらでは実メモリ使用量(RSS)などが取得できます。

### `func (p *Process) MemoryPercent() (float32, error)`

プロセスのメモリ使用量(%)を取得できる関数です。

こちらは割合での取得になります。

### `func (p *Process) NumThreads() (int32, error)`

スレッド数を取得できる関数です。

### `func (p *Process) Status() ([]string, error)`

gopsutil固有のステータスを取得できる関数です。

返り値は`running`、`sleep`、`idle`、`zombie`、`wait`、`lock`のいずれかになります。

# おわりに

基本的なデータであればOS問わず共通のメソッドで取得できるため、マルチプラットフォームで動作させることを目指す場合には便利そうなライブラリだと感じました。