Merge pull request #77 from shirou/add_process_parent_darwin_linux

Add process/Parent, net/NetConnections on darwin and linux
pull/79/head
shirou 10 years ago
commit 918bd3f404

@ -4,6 +4,7 @@ package common
import (
"os/exec"
"strconv"
"strings"
"syscall"
"unsafe"
@ -58,3 +59,31 @@ func CallSyscall(mib []int32) ([]byte, uint64, error) {
return buf, length, nil
}
func CallLsof(invoke Invoker, pid int32, args ...string) ([]string, error) {
var cmd []string
if pid == 0 { // will get from all processes.
cmd = []string{"-a", "-n", "-P"}
} else {
cmd = []string{"-a", "-n", "-P", "-p", strconv.Itoa(int(pid))}
}
cmd = append(cmd, args...)
lsof, err := exec.LookPath("lsof")
if err != nil {
return []string{}, err
}
out, err := invoke.Command(lsof, cmd...)
if err != nil {
return []string{}, err
}
lines := strings.Split(string(out), "\n")
var ret []string
for _, l := range lines[1:] {
if len(l) == 0 {
continue
}
ret = append(ret, l)
}
return ret, nil
}

@ -0,0 +1,37 @@
// +build linux
package common
import (
"os/exec"
"strconv"
"strings"
)
func CallLsof(invoke Invoker, pid int32, args ...string) ([]string, error) {
var cmd []string
if pid == 0 { // will get from all processes.
cmd = []string{"-a", "-n", "-P"}
} else {
cmd = []string{"-a", "-n", "-P", "-p", strconv.Itoa(int(pid))}
}
cmd = append(cmd, args...)
lsof, err := exec.LookPath("lsof")
if err != nil {
return []string{}, err
}
out, err := invoke.Command(lsof, cmd...)
if err != nil {
return []string{}, err
}
lines := strings.Split(string(out), "\n")
var ret []string
for _, l := range lines[1:] {
if len(l) == 0 {
continue
}
ret = append(ret, l)
}
return ret, nil
}

@ -2,9 +2,21 @@ package net
import (
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
"syscall"
"github.com/shirou/gopsutil/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
type NetIOCountersStat struct {
Name string `json:"name"` // interface name
BytesSent uint64 `json:"bytes_sent"` // number of bytes sent
@ -46,6 +58,13 @@ type NetInterfaceStat struct {
Addrs []NetInterfaceAddr `json:"addrs"`
}
var constMap = map[string]int{
"TCP": syscall.SOCK_STREAM,
"UDP": syscall.SOCK_DGRAM,
"IPv4": syscall.AF_INET,
"IPv6": syscall.AF_INET6,
}
func (n NetIOCountersStat) String() string {
s, _ := json.Marshal(n)
return string(s)
@ -135,3 +154,74 @@ func getNetIOCountersAll(n []NetIOCountersStat) ([]NetIOCountersStat, error) {
return []NetIOCountersStat{r}, nil
}
func parseNetLine(line string) (NetConnectionStat, error) {
f := strings.Fields(line)
if len(f) < 9 {
return NetConnectionStat{}, fmt.Errorf("wrong line,%s", line)
}
pid, err := strconv.Atoi(f[1])
if err != nil {
return NetConnectionStat{}, err
}
fd, err := strconv.Atoi(strings.Trim(f[3], "u"))
if err != nil {
return NetConnectionStat{}, fmt.Errorf("unknown fd, %s", f[3])
}
netFamily, ok := constMap[f[4]]
if !ok {
return NetConnectionStat{}, fmt.Errorf("unknown family, %s", f[4])
}
netType, ok := constMap[f[7]]
if !ok {
return NetConnectionStat{}, fmt.Errorf("unknown type, %s", f[7])
}
laddr, raddr, err := parseNetAddr(f[8])
if err != nil {
return NetConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8])
}
n := NetConnectionStat{
Fd: uint32(fd),
Family: uint32(netFamily),
Type: uint32(netType),
Laddr: laddr,
Raddr: raddr,
Pid: int32(pid),
}
if len(f) == 10 {
n.Status = strings.Trim(f[9], "()")
}
return n, nil
}
func parseNetAddr(line string) (laddr Addr, raddr Addr, err error) {
parse := func(l string) (Addr, error) {
host, port, err := net.SplitHostPort(l)
if err != nil {
return Addr{}, fmt.Errorf("wrong addr, %s", l)
}
lport, err := strconv.Atoi(port)
if err != nil {
return Addr{}, err
}
return Addr{IP: host, Port: uint32(lport)}, nil
}
addrs := strings.Split(line, "->")
if len(addrs) == 0 {
return laddr, raddr, fmt.Errorf("wrong netaddr, %s", line)
}
laddr, err = parse(addrs[0])
if len(addrs) == 2 { // remote addr exists
raddr, err = parse(addrs[1])
if err != nil {
return laddr, raddr, err
}
}
return laddr, raddr, err
}

@ -90,3 +90,58 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) {
return ret, nil
}
// Return a list of network connections opened by a process
func NetConnections(kind string) ([]NetConnectionStat, error) {
var ret []NetConnectionStat
args := []string{"-i"}
switch strings.ToLower(kind) {
default:
fallthrough
case "":
fallthrough
case "all":
fallthrough
case "inet":
args = append(args, "tcp")
case "inet4":
args = append(args, "4")
case "inet6":
args = append(args, "6")
case "tcp":
args = append(args, "tcp")
case "tcp4":
args = append(args, "4tcp")
case "tcp6":
args = append(args, "6tcp")
case "udp":
args = append(args, "udp")
case "udp4":
args = append(args, "6udp")
case "udp6":
args = append(args, "6udp")
case "unix":
return ret, common.NotImplementedError
}
// we can not use -F filter to get all of required information at once.
r, err := common.CallLsof(invoke, 0, args...)
if err != nil {
return nil, err
}
for _, rr := range r {
if strings.HasPrefix(rr, "COMMAND") {
continue
}
n, err := parseNetLine(rr)
if err != nil {
// fmt.Println(err) // TODO: should debug print?
continue
}
ret = append(ret, n)
}
return ret, nil
}

@ -81,3 +81,9 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) {
return ret, nil
}
func NetConnections(kind string) ([]NetConnectionStat, error) {
var ret []NetConnectionStat
return ret, common.NotImplementedError
}

@ -89,3 +89,57 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) {
return ret, nil
}
func NetConnections(kind string) ([]NetConnectionStat, error) {
var ret []NetConnectionStat
args := []string{"-i"}
switch strings.ToLower(kind) {
default:
fallthrough
case "":
fallthrough
case "all":
fallthrough
case "inet":
args = append(args, "tcp")
case "inet4":
args = append(args, "4")
case "inet6":
args = append(args, "6")
case "tcp":
args = append(args, "tcp")
case "tcp4":
args = append(args, "4tcp")
case "tcp6":
args = append(args, "6tcp")
case "udp":
args = append(args, "udp")
case "udp4":
args = append(args, "6udp")
case "udp6":
args = append(args, "6udp")
case "unix":
return ret, common.NotImplementedError
}
// we can not use -F filter to get all of required information at once.
r, err := common.CallLsof(invoke, 0, args...)
if err != nil {
return nil, err
}
for _, rr := range r {
if strings.HasPrefix(rr, "COMMAND") {
continue
}
n, err := parseNetLine(rr)
if err != nil {
// fmt.Println(err) // TODO: should debug print?
continue
}
ret = append(ret, n)
}
return ret, nil
}

@ -2,6 +2,7 @@ package net
import (
"fmt"
"os"
"testing"
)
@ -120,3 +121,23 @@ func TestNetInterfaces(t *testing.T) {
}
}
}
func TestNetConnections(t *testing.T) {
if ci := os.Getenv("CI"); ci != "" { // skip if test on drone.io
return
}
v, err := NetConnections("inet")
if err != nil {
t.Errorf("could not get NetConnections: %v", err)
}
if len(v) == 0 {
t.Errorf("could not get NetConnections: %v", v)
}
for _, vv := range v {
if vv.Family == 0 {
t.Errorf("invalid NetConnections: %v", vv)
}
}
}

@ -4,6 +4,7 @@ package process
import (
"bytes"
"fmt"
"strconv"
"strings"
"syscall"
@ -92,7 +93,22 @@ func (p *Process) Cwd() (string, error) {
return "", common.NotImplementedError
}
func (p *Process) Parent() (*Process, error) {
return p, common.NotImplementedError
rr, err := common.CallLsof(invoke, p.Pid, "-FR")
if err != nil {
return nil, err
}
for _, r := range rr {
if strings.HasPrefix(r, "p") { // skip if process
continue
}
l := string(r)
v, err := strconv.Atoi(strings.Replace(l, "R", "", 1))
if err != nil {
return nil, err
}
return NewProcess(int32(v))
}
return nil, fmt.Errorf("could not find parent line")
}
func (p *Process) Status() (string, error) {
r, err := callPs("state", p.Pid, false)
@ -166,15 +182,6 @@ func (p *Process) NumFDs() (int32, error) {
return 0, common.NotImplementedError
}
func (p *Process) NumThreads() (int32, error) {
/*
k, err := p.getKProc()
if err != nil {
return 0, err
}
return k.KiNumthreads, nil
*/
r, err := callPs("utime,stime", p.Pid, true)
if err != nil {
return 0, err

@ -4,6 +4,7 @@ package process
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@ -11,10 +12,10 @@ import (
"strings"
"syscall"
common "github.com/shirou/gopsutil/common"
cpu "github.com/shirou/gopsutil/cpu"
host "github.com/shirou/gopsutil/host"
net "github.com/shirou/gopsutil/net"
"github.com/shirou/gopsutil/common"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/net"
)
const (
@ -94,7 +95,18 @@ func (p *Process) Cwd() (string, error) {
return p.fillFromCwd()
}
func (p *Process) Parent() (*Process, error) {
return nil, common.NotImplementedError
r, err := callLsof("R", p.Pid)
if err != nil {
return nil, err
}
if len(r) != 1 { // TODO: pid 1
return nil, fmt.Errorf("wrong number of parents")
}
v, err := strconv.Atoi(r[0])
if err != nil {
return nil, err
}
return NewProcess(int32(v))
}
func (p *Process) Status() (string, error) {
err := p.fillFromStatus()
@ -619,3 +631,26 @@ func Pids() ([]int32, error) {
return ret, nil
}
func callLsof(arg string, pid int32) ([]string, error) {
var cmd []string
if pid == 0 { // will get from all processes.
cmd = []string{"-F" + arg}
} else {
cmd = []string{"-a", "-F" + arg, "-p", strconv.Itoa(int(pid))}
}
out, err := invoke.Command("/usr/bin/lsof", cmd...)
if err != nil {
return []string{}, err
}
lines := strings.Split(string(out), "\n")
var ret []string
for _, l := range lines[1:] {
if strings.HasPrefix(l, arg) {
ret = append(ret, l[1:]) // delete first char
}
}
return ret, nil
}

@ -1,7 +1,6 @@
package process
import (
"fmt"
"os"
"runtime"
"strings"
@ -38,7 +37,7 @@ func Test_Pids_Fail(t *testing.T) {
mu.Lock()
defer mu.Unlock()
invoke = common.FakeInvoke{Suffix: "fail", Error: fmt.Errorf("hoge")}
invoke = common.FakeInvoke{Suffix: "fail"}
ret, err := Pids()
invoke = common.Invoke{}
if err != nil {
@ -48,7 +47,6 @@ func Test_Pids_Fail(t *testing.T) {
t.Errorf("wrong getted pid nums: %v/%d", ret, len(ret))
}
}
func Test_Pid_exists(t *testing.T) {
checkPid := os.Getpid()
@ -276,3 +274,18 @@ func Test_Process_CreateTime(t *testing.T) {
t.Errorf("process created time is wrong.")
}
}
func Test_Parent(t *testing.T) {
p := testGetProcess()
c, err := p.Parent()
if err != nil {
t.Fatalf("error %v", err)
}
if c == nil {
t.Fatalf("could not get parent")
}
if c.Pid == 0 {
t.Fatalf("wrong parent pid")
}
}

Loading…
Cancel
Save