mirror of
https://github.com/duke-git/lancet.git
synced 2025-09-26 19:41:20 +08:00
325 lines
7.5 KiB
Go
325 lines
7.5 KiB
Go
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
|
// Use of this source code is governed by MIT license
|
|
|
|
// Package system contain some functions about os, runtime, shell command.
|
|
package system
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/duke-git/lancet/v2/validator"
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
|
)
|
|
|
|
type (
|
|
Option func(*exec.Cmd)
|
|
)
|
|
|
|
// IsWindows check if current os is windows.
|
|
// Play: https://go.dev/play/p/XzJULbzmf9m
|
|
func IsWindows() bool {
|
|
return runtime.GOOS == "windows"
|
|
}
|
|
|
|
// IsLinux check if current os is linux.
|
|
// Play: https://go.dev/play/p/zIflQgZNuxD
|
|
func IsLinux() bool {
|
|
return runtime.GOOS == "linux"
|
|
}
|
|
|
|
// IsMac check if current os is macos.
|
|
// Play: https://go.dev/play/p/Mg4Hjtyq7Zc
|
|
func IsMac() bool {
|
|
return runtime.GOOS == "darwin"
|
|
}
|
|
|
|
// GetOsEnv gets the value of the environment variable named by the key.
|
|
// Play: https://go.dev/play/p/D88OYVCyjO-
|
|
func GetOsEnv(key string) string {
|
|
return os.Getenv(key)
|
|
}
|
|
|
|
// SetOsEnv sets the value of the environment variable named by the key.
|
|
// Play: https://go.dev/play/p/D88OYVCyjO-
|
|
func SetOsEnv(key, value string) error {
|
|
return os.Setenv(key, value)
|
|
}
|
|
|
|
// RemoveOsEnv remove a single environment variable.
|
|
// Play: https://go.dev/play/p/fqyq4b3xUFQ
|
|
func RemoveOsEnv(key string) error {
|
|
return os.Unsetenv(key)
|
|
}
|
|
|
|
// CompareOsEnv gets env named by the key and compare it with comparedEnv.
|
|
// Play: https://go.dev/play/p/BciHrKYOHbp
|
|
func CompareOsEnv(key, comparedEnv string) bool {
|
|
env := GetOsEnv(key)
|
|
if env == "" {
|
|
return false
|
|
}
|
|
return env == comparedEnv
|
|
}
|
|
|
|
// ExecCommand execute command, return the stdout and stderr string of command, and error if error occur
|
|
// param `command` is a complete command string, like, ls -a (linux), dir(windows), ping 127.0.0.1
|
|
// in linux, use /bin/bash -c to execute command
|
|
// in windows, use powershell.exe to execute command
|
|
// Play: https://go.dev/play/p/n-2fLyZef-4
|
|
func ExecCommand(command string, opts ...Option) (stdout, stderr string, err error) {
|
|
var out bytes.Buffer
|
|
var errOut bytes.Buffer
|
|
|
|
cmd := exec.Command("/bin/bash", "-c", command)
|
|
if IsWindows() {
|
|
cmd = exec.Command("powershell.exe", command)
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
if opt != nil {
|
|
opt(cmd)
|
|
}
|
|
}
|
|
cmd.Stdout = &out
|
|
cmd.Stderr = &errOut
|
|
|
|
err = cmd.Run()
|
|
|
|
if err != nil {
|
|
if utf8.Valid(errOut.Bytes()) {
|
|
stderr = byteToString(errOut.Bytes(), "UTF8")
|
|
} else if validator.IsGBK(errOut.Bytes()) {
|
|
stderr = byteToString(errOut.Bytes(), "GBK")
|
|
}
|
|
return
|
|
}
|
|
|
|
data := out.Bytes()
|
|
if utf8.Valid(data) {
|
|
stdout = byteToString(data, "UTF8")
|
|
} else if validator.IsGBK(data) {
|
|
stdout = byteToString(data, "GBK")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func byteToString(data []byte, charset string) string {
|
|
var result string
|
|
|
|
switch charset {
|
|
case "GBK":
|
|
decodeBytes, _ := simplifiedchinese.GBK.NewDecoder().Bytes(data)
|
|
result = string(decodeBytes)
|
|
case "GB18030":
|
|
decodeBytes, _ := simplifiedchinese.GB18030.NewDecoder().Bytes(data)
|
|
result = string(decodeBytes)
|
|
case "UTF8":
|
|
fallthrough
|
|
default:
|
|
result = string(data)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetOsBits return current os bits (32 or 64).
|
|
// Play: https://go.dev/play/p/ml-_XH3gJbW
|
|
func GetOsBits() int {
|
|
return 32 << (^uint(0) >> 63)
|
|
}
|
|
|
|
// StartProcess start a new process with the specified name and arguments.
|
|
// Play: https://go.dev/play/p/5GVol6ryS_X
|
|
func StartProcess(command string, args ...string) (int, error) {
|
|
cmd := exec.Command(command, args...)
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return cmd.Process.Pid, nil
|
|
}
|
|
|
|
// StopProcess stop a process by pid.
|
|
// Play: https://go.dev/play/p/jJZhRYGGcmD
|
|
func StopProcess(pid int) error {
|
|
process, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return process.Signal(os.Kill)
|
|
}
|
|
|
|
// KillProcess kill a process by pid.
|
|
// Play: https://go.dev/play/p/XKmvV-ExBWa
|
|
func KillProcess(pid int) error {
|
|
process, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return process.Kill()
|
|
}
|
|
|
|
// ProcessInfo contains detailed information about a process.
|
|
type ProcessInfo struct {
|
|
PID int
|
|
CPU string
|
|
Memory string
|
|
State string
|
|
User string
|
|
Cmd string
|
|
Threads []string
|
|
IOStats string
|
|
StartTime string
|
|
ParentPID int
|
|
NetworkConnections string
|
|
}
|
|
|
|
// GetProcessInfo retrieves detailed process information by pid.
|
|
// Play: https://go.dev/play/p/NQDVywEYYx7
|
|
func GetProcessInfo(pid int) (*ProcessInfo, error) {
|
|
var cmd *exec.Cmd
|
|
|
|
if runtime.GOOS == "windows" {
|
|
cmd = exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/FO", "CSV", "/V")
|
|
} else {
|
|
cmd = exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "pid,%cpu,%mem,state,user,comm")
|
|
}
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
processInfo, err := parseProcessInfo(output, pid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if runtime.GOOS != "windows" {
|
|
processInfo.Threads, _ = getThreadsInfo(pid)
|
|
processInfo.IOStats, _ = getIOStats(pid)
|
|
processInfo.StartTime, _ = getProcessStartTime(pid)
|
|
processInfo.ParentPID, _ = getParentProcess(pid)
|
|
processInfo.NetworkConnections, _ = getNetworkConnections(pid)
|
|
}
|
|
|
|
return processInfo, nil
|
|
}
|
|
|
|
// parseProcessInfo parses the output of `ps` or `tasklist` to fill the ProcessInfo structure.
|
|
func parseProcessInfo(output []byte, pid int) (*ProcessInfo, error) {
|
|
lines := strings.Split(string(output), "\n")
|
|
|
|
if len(lines) < 2 {
|
|
return nil, fmt.Errorf("no process found with PID %d", pid)
|
|
}
|
|
|
|
var processInfo ProcessInfo
|
|
if runtime.GOOS == "windows" {
|
|
fields := strings.Split(lines[1], "\",\"")
|
|
if len(fields) < 9 {
|
|
return nil, fmt.Errorf("unexpected tasklist output format")
|
|
}
|
|
|
|
processInfo = ProcessInfo{
|
|
PID: pid,
|
|
CPU: "N/A",
|
|
Memory: fields[4], // Memory usage in K
|
|
State: fields[5],
|
|
User: "N/A",
|
|
Cmd: fields[8],
|
|
}
|
|
} else {
|
|
fields := strings.Fields(lines[1])
|
|
if len(fields) < 6 {
|
|
return nil, fmt.Errorf("unexpected ps output format")
|
|
}
|
|
|
|
processInfo = ProcessInfo{
|
|
PID: pid,
|
|
CPU: fields[1],
|
|
Memory: fields[2],
|
|
State: fields[3],
|
|
User: fields[4],
|
|
Cmd: fields[5],
|
|
}
|
|
}
|
|
|
|
return &processInfo, nil
|
|
}
|
|
|
|
func getThreadsInfo(pid int) ([]string, error) {
|
|
cmd := exec.Command("ps", "-T", "-p", strconv.Itoa(pid))
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lines := strings.Split(string(output), "\n")
|
|
|
|
var threads []string
|
|
for _, line := range lines[1:] {
|
|
if strings.TrimSpace(line) != "" {
|
|
threads = append(threads, line)
|
|
}
|
|
}
|
|
|
|
return threads, nil
|
|
}
|
|
|
|
func getIOStats(pid int) (string, error) {
|
|
filePath := fmt.Sprintf("/proc/%d/io", pid)
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(data), nil
|
|
}
|
|
|
|
func getProcessStartTime(pid int) (string, error) {
|
|
cmd := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "lstart=")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
func getParentProcess(pid int) (int, error) {
|
|
cmd := exec.Command("ps", "-o", "ppid=", "-p", strconv.Itoa(pid))
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
ppid, err := strconv.Atoi(strings.TrimSpace(string(output)))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return ppid, nil
|
|
}
|
|
|
|
func getNetworkConnections(pid int) (string, error) {
|
|
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid), "-i")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|