2024年5月30日木曜日

Go 言語でプロセスの存在確認を行う(gopsutil 編)

shirou/gopsutil: psutil for golang を使用して、プロセスの存在確認を行う。

プロジェクト作成

go mod init で作成。

go mod init github.com/mikoto2000/TIL/golang/process/isExists/gopsutil

ライブラリ追加

go get で追加。

go get github.com/shirou/gopsutil/v3/process

実装

gopsutil には func PidExists(pid int32) (bool, error) が存在するので、それを利用する。

main.go

package main

import (
    "github.com/shirou/gopsutil/v3/process"
    "log"
    "os"
    "strconv"
)

/**
 * コマンドライン引数のひとつ目に PID をうけとり、
 * その PID のプロセスが実行中かを表示する。
 */
func main() {
    pidString := os.Args[1]
    log.Printf("PID String: %s", pidString)

    pid, err := strconv.Atoi(pidString)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("PID Decimal: %d", pid)

    // gopsutil を使って PID のプロセスの実行確認
    isRunning, err := process.PidExists(int32(pid))
    if err != nil {
        log.Fatal(err)
    }

    // 表示
    if isRunning {
        log.Printf("PID %d は実行中です。\n", pid)
    } else {
        log.Printf("PID %d は実行していません。\n", pid)
    }
}

動作確認

Windows

試しに gvim を起動して、それが実行中かを確認するとの、適当な PID を入れて実行されていないのを確認する。

> Get-Process -Name gvim

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     16    20.49      39.34       4.72   30084   1 gvim

> .\gopsutil.exe 30084
2024/05/30 20:11:09 PID String: 30084
2024/05/30 20:11:09 PID Decimal: 30084
2024/05/30 20:11:09 PID 30084 は実行中です。
> .\gopsutil.exe 99999999
2024/05/30 20:11:18 PID String: 99999999
2024/05/30 20:11:18 PID Decimal: 99999999
2024/05/30 20:11:18 PID 99999999 は実行していません。

OK そう。

Linux

試しに自分の bash が実行中かを確認するとの、適当な PID を入れて実行されていないのを確認する。

$ ps
    PID TTY          TIME CMD
    234 pts/1    00:00:00 bash
  31817 pts/1    00:00:00 ps
$ ./gopsutil 234
2024/05/30 11:12:30 PID String: 234
2024/05/30 11:12:30 PID Decimal: 234
2024/05/30 11:12:30 PID 234 は実行中です。
$ ./gopsutil 9999999
2024/05/30 11:12:34 PID String: 9999999
2024/05/30 11:12:34 PID Decimal: 9999999
2024/05/30 11:12:34 PID 9999999 は実行していません。

こちらも OK そう。

以上。

前回(syscall を使う)よりも大分楽ができましたね!

参考資料

Go 言語でプロセスの存在確認を行う(syscall 編)

Windows と Linux それぞれで、どうやってプロセスの存在確認を行うかというのをやっていく。

今回使用するのは syscall パッケージ。

プロジェクト作成

go mod init で作成。

go mod init github.com/mikoto2000/TIL/golang/process/isExists/syscall

実装

サードパーティーのライブラリは使っていないので、そのまま実装に進む。

Go 言語はビルドタグを使うことで、環境変数応じてビルドするかしないかを、ファイルごとに定義することができる。(See: Goのビルドタグの書き方が// +buildから//go:buildに変わった理由)

この仕組みを利用して、 Windows 版の処理と Linux 版の処理を書き分けていく。

説明を記載する体力がなくなったので、各 API の詳細は参考資料のリンク先を参照してくださいで済ませます。

Windows 版の処理(process_windows.go)

//go:build windows

package main

import (
    "os"
    "syscall"
)

func IsRunningProcess(process *os.Process) (bool, error) {
    const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000

    handle, err := syscall.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(process.Pid))
    if err != nil {
        // そもそもチェック処理で失敗
        return false, err
    }

    defer syscall.CloseHandle(handle)

    var exitCode uint32
    err = syscall.GetExitCodeProcess(handle, &exitCode)
    if err != nil {
        // そもそもチェック処理で失敗
        return false, err
    }

    if exitCode == 259 { // STILL_ACTIVE
        // プロセスが実行中
        return true, nil
    } else {
        // プロセスが実行中でない
        return false, nil
    }
}

Linux 版の処理(process_nowindows.go)

//go:build !windows

package main

import (
    "os"
    "syscall"
)

func IsRunningProcess(process *os.Process) (bool, error) {
    err := process.Signal(syscall.Signal(0))
    if err != nil {
        if err == os.ErrProcessDone {
        // プロセスが存在していない
            return false, nil
        } else {
        // そもそもチェック処理で失敗
            return false, err
        }
    } else {
        // プロセスが既に存在している
        return true, nil
    }
}

メイン関数(main.go)

package main

import (
    "log"
    "os"
    "strconv"
)

/**
 * コマンドライン引数のひとつ目に PID をうけとり、
 * その PID のプロセスが実行中かを表示する。
 */
func main() {
    pidString := os.Args[1]
    log.Printf("PID String: %s", pidString)

    pid, err := strconv.Atoi(pidString)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("PID Decimal: %d", pid)

    // プロセスを取得
    process, err := os.FindProcess(pid)
    if err != nil {
        // そもそも PID が割り当てられたことが無い(Windows)
        // Linux では必ず成功するので後段のチェックが必須
        log.Printf("PID %d は実行していません。\n", pid)
        os.Exit(0)
    }
    log.Printf("PID from Process type: %d", process.Pid)

    // プロセスを渡してプロセスが実行中かを確認
    // (ビルドフラグによって windows とそれ以外で処理を分けている)
    isRunning, err := IsRunningProcess(process)
    if err != nil {
        log.Fatal(err)
    }

    // 表示
    if isRunning {
        log.Printf("PID %d は実行中です。\n", pid)
    } else {
        log.Printf("PID %d は実行していません。\n", pid)
    }
}

動作確認

Windows

試しに gvim を起動して、それが実行中かを確認するとの、適当な PID を入れて実行されていないのを確認する。

> $(Get-Process -Name gvim).Id
30084
> .\syscall.exe 30084
2024/05/30 17:49:29 PID String: 30084
2024/05/30 17:49:29 PID Decimal: 30084
2024/05/30 17:49:29 PID from Process type: 30084
2024/05/30 17:49:29 PID 30084 は実行中です。
> .\syscall.exe 99999999
2024/05/30 17:49:34 PID String: 99999999
2024/05/30 17:49:34 PID Decimal: 99999999
2024/05/30 17:49:34 PID 99999999 は実行していません。

OK そう。

Linux

試しに自分の bash が実行中かを確認するとの、適当な PID を入れて実行されていないのを確認する。

$ ps
    PID TTY          TIME CMD
 208144 pts/12   00:00:00 bash
 221983 pts/12   00:00:00 ps
$ ./syscall 208144
2024/05/30 17:53:14 PID String: 208144
2024/05/30 17:53:14 PID Decimal: 208144
2024/05/30 17:53:14 PID from Process type: 208144
2024/05/30 17:53:14 PID 208144 は実行中です。
$ ./syscall 999999
2024/05/30 17:53:20 PID String: 999999
2024/05/30 17:53:20 PID Decimal: 999999
2024/05/30 17:53:20 PID from Process type: 999999
2024/05/30 17:53:20 PID 999999 は実行していません。

こちらも OK そう。

以上。

補足

ツイッターにて以下コメントをもらったので、この後そちらも試す予定。

参考資料

2024年5月24日金曜日

go-github で GitHub の最新リリースタグを取得する

よくあるやつ。

Google が Go 用の GitHub クライアントを公開しているのでそれを利用する

プロジェクト作成

go mod init で作成。

go mod init github.com/mikoto2000/TIL/golang/github/go-github/firststep

ライブラリ追加

go get で追加。

go get github.com/google/go-github/v62

実装

コマンドライン引数にユーザー名とリポジトリを受け取り、そのリポジトリの latest タグを返却する。

■ main.go

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/google/go-github/v62/github"
)

/**
 * コマンドライン引数のひとつ目にユーザー名、ふたつ目にリポジトリ名を渡す。
 *
 * latest タグの取得に成功した場合、タグ名を標準出力へ出力する。
 * latest タグの取得に失敗した場合、エラーを出力し、終了コード 1 で終了する。
 */
func main() {
    ctx := context.Background()
    client := github.NewClient(nil)

    owner := os.Args[1]
    repo := os.Args[2]

    release, _, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
    if err != nil {
        log.Fatalf("Error getting latest release: %v", err)
    }

    fmt.Printf("%s\n", release.GetTagName())
}

動作確認

ぱっと見取得できているように見える。

$ ./firststep mikoto2000 devcontainer.vim
v0.6.1
$ ./firststep mikoto2000 devcontainer.vima
2024/05/24 03:13:10 Error getting latest release: GET https://api.github.com/repos/mikoto2000/devcontainer.vima/releases/latest: 404 Not Found []

以上。

参考資料