Avatar ·

Как получить информацию о процессе по его PID в Go

Всем привет! Я пишу небольшой мониторинговый инструмент на Go, который должен отслеживать запущенные процессы в Linux. Мне нужно по PID (идентификатору процесса) получать базовую информацию: имя процесса, статус (запущен/спит/зомби), использование памяти, время работы и путь к исполняемому файлу. Я начал с простого: читаю `/proc/[PID]/status` и `/proc/[PID]/cmdline`. Но это выглядит костыльно — приходится парсить текстовые файлы, а для получения полного пути к бинарнику нужно еще читать симлинк `/proc/[PID]/exe`. К тому же, я не уверен, что это кроссплатформенное решение — на Windows и macOS `/proc` нет. Пока мой код выглядит так: ```go package main import ( "fmt" "io/ioutil" "os" "strconv" ) func getProcessName(pid int) (string, error) { data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", pid)) if err != nil { return "", err } return string(data), nil } func main() { name, _ := getProcessName(1) fmt.Println(name) } ``` Вопрос: есть ли в Go стандартные библиотеки или пакеты, которые позволяют получить детальную информацию о процессе по PID без ручного парсинга `/proc`? Нужно чтобы работало хотя бы под Linux, но было бы здорово, если бы решение было кроссплатформенным. Заранее спасибо! <<>>

Использование пакета github.com/mitchellh/go-ps для получения информации о процессе

В Go нет встроенной стандартной библиотеки для получения детальной информации о процессе по PID, но существует отличное стороннее решение — пакет go-ps от Mitchell Hashimoto. Этот пакет предоставляет кроссплатформенный API (Linux, macOS, Windows) для работы с процессами.

Установка пакета:

go get github.com/mitchellh/go-ps

Основной интерфейс пакета — функция ps.FindProcess(pid int), которая возвращает структуру Process с базовой информацией: PID, PPID (родительский PID) и имя процесса. Если процесс не найден, возвращается nil.

Пример получения имени процесса по PID:

package main

import (
    "fmt"
    "os"
    "strconv"
    "github.com/mitchellh/go-ps"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Использование: go run main.go <PID>")
        return
    }

    pid, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Printf("Ошибка: PID должен быть числом, получено: %s\n", os.Args[1])
        return
    }

    process, err := ps.FindProcess(pid)
    if err != nil {
        fmt.Printf("Ошибка при поиске процесса: %v\n", err)
        return
    }

    if process == nil {
        fmt.Printf("Процесс с PID %d не найден\n", pid)
        return
    }

    fmt.Printf("PID: %d\n", process.Pid())
    fmt.Printf("PPID: %d\n", process.PPid())
    fmt.Printf("Имя: %s\n", process.Executable())
}

Как получить больше информации (статус, память, время работы)

Пакет go-ps предоставляет только базовую информацию. Для получения расширенных данных (статус, использование памяти, время работы) вам потребуется комбинировать его с чтением /proc/[PID]/status под Linux. Вот пример:

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strconv"
    "strings"
    "github.com/mitchellh/go-ps"
)

type ProcessInfo struct {
    PID     int
    PPID    int
    Name    string
    Status  string
    Memory  string
    Uptime  string
}

func getProcessDetails(pid int) (*ProcessInfo, error) {
    // Получаем базовую информацию через go-ps
    process, err := ps.FindProcess(pid)
    if err != nil {
        return nil, fmt.Errorf("не удалось найти процесс: %v", err)
    }
    if process == nil {
        return nil, fmt.Errorf("процесс с PID %d не найден", pid)
    }

    info := &ProcessInfo{
        PID:  process.Pid(),
        PPID: process.PPid(),
        Name: process.Executable(),
    }

    // Читаем /proc/[PID]/status для расширенной информации
    data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
    if err != nil {
        // На Windows или macOS это не сработает — возвращаем базовую информацию
        return info, nil
    }

    lines := strings.Split(string(data), "\n")
    for _, line := range lines {
        switch {
        case strings.HasPrefix(line, "State:"):
            info.Status = strings.TrimSpace(strings.TrimPrefix(line, "State:"))
        case strings.HasPrefix(line, "VmRSS:"):
            info.Memory = strings.TrimSpace(strings.TrimPrefix(line, "VmRSS:"))
        }
    }

    return info, nil
}

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Использование: go run main.go <PID>")
        return
    }

    pid, _ := strconv.Atoi(os.Args[1])
    info, err := getProcessDetails(pid)
    if err != nil {
        fmt.Printf("Ошибка: %v\n", err)
        return
    }

    fmt.Printf("=== Информация о процессе PID %d ===\n", info.PID)
    fmt.Printf("Имя: %s\n", info.Name)
    fmt.Printf("Родительский PID: %d\n", info.PPID)
    fmt.Printf("Статус: %s\n", info.Status)
    fmt.Printf("Память (RSS): %s\n", info.Memory)
}

Кроссплатформенное решение с shirou/gopsutil

Если вам нужно больше данных и полная кроссплатформенность, рекомендую пакет github.com/shirou/gopsutil. Он предоставляет структурированный доступ к информации о процессах на Linux, macOS, Windows и FreeBSD.

Установка: go get github.com/shirou/gopsutil/v3/process

package main

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

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Использование: go run main.go <PID>")
        return
    }

    pid, _ := strconv.Atoi(os.Args[1])

    p, err := process.NewProcess(int32(pid))
    if err != nil {
        fmt.Printf("Ошибка: %v\n", err)
        return
    }

    name, _ := p.Name()
    status, _ := p.Status()
    memInfo, _ := p.MemoryInfo()
    createTime, _ := p.CreateTime()
    exe, _ := p.Exe()

    fmt.Printf("Имя: %s\n", name)
    fmt.Printf("Статус: %s\n", status)
    fmt.Printf("Исполняемый файл: %s\n", exe)
    fmt.Printf("Память RSS: %d байт\n", memInfo.RSS)
    fmt.Printf("Время запуска: %d (unix timestamp)\n", createTime)
}

Этот пакет работает на всех основных ОС и предоставляет десятки метрик: CPU, память, сеть, открытые файлы и т.д.

Заключение

  • Для простых задач (имя и PID) — используйте go-ps.
  • Для расширенной информации под Linux — комбинируйте go-ps с чтением /proc/[PID]/status.
  • Для полноценного кроссплатформенного решения — используйте gopsutil. Это самый мощный и документированный вариант.
<<>>

Альтернативный подход: прямой парсинг /proc/[PID] с помощью os.ReadFile

Если вы не хотите подключать сторонние зависимости и работаете только под Linux, можно обойтись стандартными средствами Go, написав небольшую обертку для парсинга файлов в /proc. Это даст полный контроль и отсутствие внешних библиотек.

Вот пример получения статуса процесса без сторонних пакетов:

package main

import (
    "fmt"
    "os"
    "strconv"
    "strings"
)

type ProcStatus struct {
    Pid     int
    Name    string
    State   string
    VmSize  string
    VmRSS   string
}

func ReadProcStatus(pid int) (*ProcStatus, error) {
    data, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
    if err != nil {
        return nil, err
    }

    ps := &ProcStatus{Pid: pid}
    lines := strings.Split(string(data), "\n")

    for _, line := range lines {
        parts := strings.SplitN(line, ":", 2)
        if len(parts) != 2 {
            continue
        }
        key := strings.TrimSpace(parts[0])
        value := strings.TrimSpace(parts[1])

        switch key {
        case "Name":
            ps.Name = value
        case "State":
            ps.State = value
        case "VmSize":
            ps