feat: add GetProcessInfo for system package

This commit is contained in:
dudaodong
2024-09-13 16:10:09 +08:00
parent e58c9b797b
commit 2bbcb85286
5 changed files with 261 additions and 1 deletions

View File

@@ -31,6 +31,11 @@ import (
- [CompareOsEnv](#CompareOsEnv)
- [ExecCommand](#ExecCommand)
- [GetOsBits](#GetOsBits)
- [StartProcess](#StartProcess)
- [StopProcess](#StopProcess)
- [KillProcess](#KillProcess)
- [GetProcessInfo](#GetProcessInfo)
<div STYLE="page-break-after: always;"></div>
@@ -248,7 +253,7 @@ func main() {
```go
type (
Option func(*exec.Cmd)
Option func(*exec.Cmd)
)
func ExecCommand(command string, opts ...Option) (stdout, stderr string, err error)
```
@@ -403,4 +408,37 @@ func main() {
// Output:
// <nil>
}
```
### <span id="GetProcessInfo">GetProcessInfo</span>
<p>根据进程id获取进程信息。</p>
<b>函数签名:</b>
```go
func GetProcessInfo(pid int) (*ProcessInfo, error)
```
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/system"
)
func main() {
pid, err := system.StartProcess("ls", "-a")
if err != nil {
return
}
processInfo, err := system.GetProcessInfo(pid)
if err != nil {
return
}
fmt.Println(processInfo)
}
```

View File

@@ -34,6 +34,8 @@ import (
- [StartProcess](#StartProcess)
- [StopProcess](#StopProcess)
- [KillProcess](#KillProcess)
- [GetProcessInfo](#GetProcessInfo)
<div STYLE="page-break-after: always;"></div>
@@ -407,4 +409,37 @@ func main() {
// Output:
// <nil>
}
```
### <span id="GetProcessInfo">GetProcessInfo</span>
<p>Retrieves detailed process information by pid.</p>
<b>Signature:</b>
```go
func GetProcessInfo(pid int) (*ProcessInfo, error)
```
<b>Example:<span style="float:right;display:inline-block">[Run](todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/system"
)
func main() {
pid, err := system.StartProcess("ls", "-a")
if err != nil {
return
}
processInfo, err := system.GetProcessInfo(pid)
if err != nil {
return
}
fmt.Println(processInfo)
}
```

View File

@@ -6,9 +6,12 @@ package system
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"unicode/utf8"
"github.com/duke-git/lancet/v2/validator"
@@ -166,3 +169,156 @@ func KillProcess(pid int) error {
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: todo
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
}

View File

@@ -117,3 +117,17 @@ func ExampleKillProcess() {
// Output:
// <nil>
}
func ExampleGetProcessInfo() {
pid, err := StartProcess("ls", "-a")
if err != nil {
return
}
processInfo, err := GetProcessInfo(pid)
if err != nil {
return
}
fmt.Println(processInfo)
}

View File

@@ -4,6 +4,7 @@ import (
"os/exec"
"strings"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
@@ -125,3 +126,19 @@ func TestStopProcess(t *testing.T) {
err = StopProcess(pid)
assert.IsNil(err)
}
func TestGetProcessInfo(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestGetProcessInfo")
pid, err := StartProcess("ls", "-a")
assert.IsNil(err)
time.Sleep(1 * time.Second)
processInfo, err := GetProcessInfo(pid)
assert.IsNil(err)
t.Log(processInfo)
}