diff --git a/common_darwin.go b/common_darwin.go new file mode 100644 index 0000000..3707636 --- /dev/null +++ b/common_darwin.go @@ -0,0 +1,20 @@ +// +build darwin + +package gopsutil + +import ( + "os/exec" + "strings" +) + +func doSysctrl(mib string) ([]string, error) { + out, err := exec.Command("/usr/sbin/sysctl", "-n", mib).Output() + if err != nil { + return []string{}, err + } + v := strings.Replace(string(out), "{ ", "", 1) + v = strings.Replace(string(v), " }", "", 1) + values := strings.Fields(string(v)) + + return values, nil +} diff --git a/cpu_darwin.go b/cpu_darwin.go new file mode 100644 index 0000000..e532c8b --- /dev/null +++ b/cpu_darwin.go @@ -0,0 +1,87 @@ +// +build darwin + +package gopsutil + +import ( + "regexp" + "strconv" + "strings" +) + +// sys/resource.h +const ( + CP_USER = 0 + CP_NICE = 1 + CP_SYS = 2 + CP_INTR = 3 + CP_IDLE = 4 + CPUSTATES = 5 +) + +// time.h +const ( + CLOCKS_PER_SEC = 128 +) + +// TODO: get per cpus +func CPUTimes(percpu bool) ([]CPUTimesStat, error) { + var ret []CPUTimesStat + + cpuTime, err := doSysctrl("kern.cp_time") + if err != nil { + return ret, err + } + + user, _ := strconv.ParseFloat(cpuTime[CP_USER], 32) + nice, _ := strconv.ParseFloat(cpuTime[CP_NICE], 32) + sys, _ := strconv.ParseFloat(cpuTime[CP_SYS], 32) + idle, _ := strconv.ParseFloat(cpuTime[CP_IDLE], 32) + intr, _ := strconv.ParseFloat(cpuTime[CP_INTR], 32) + + c := CPUTimesStat{ + User: float32(user / CLOCKS_PER_SEC), + Nice: float32(nice / CLOCKS_PER_SEC), + System: float32(sys / CLOCKS_PER_SEC), + Idle: float32(idle / CLOCKS_PER_SEC), + Irq: float32(intr / CLOCKS_PER_SEC), // FIXME: correct? + } + + ret = append(ret, c) + + return ret, nil +} + +// Returns only one CPUInfoStat on FreeBSD +func CPUInfo() ([]CPUInfoStat, error) { + filename := "/var/run/dmesg.boot" + lines, _ := readLines(filename) + + var ret []CPUInfoStat + + c := CPUInfoStat{} + for _, line := range lines { + if matches := regexp.MustCompile(`CPU:\s+(.+) \(([\d.]+).+\)`).FindStringSubmatch(line); matches != nil { + c.ModelName = matches[1] + c.Mhz = mustParseFloat64(matches[2]) + } else if matches := regexp.MustCompile(`Origin = "(.+)" Id = (.+) Family = (.+) Model = (.+) Stepping = (.+)`).FindStringSubmatch(line); matches != nil { + c.VendorID = matches[1] + c.Family = matches[3] + c.Model = matches[4] + c.Stepping = mustParseInt32(matches[5]) + } else if matches := regexp.MustCompile(`Features=.+<(.+)>`).FindStringSubmatch(line); matches != nil { + for _, v := range strings.Split(matches[1], ",") { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if matches := regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`).FindStringSubmatch(line); matches != nil { + for _, v := range strings.Split(matches[1], ",") { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if matches := regexp.MustCompile(`Logical CPUs per core: (\d+)`).FindStringSubmatch(line); matches != nil { + // FIXME: no this line? + c.Cores = mustParseInt32(matches[1]) + } + + } + + return append(ret, c), nil +} diff --git a/disk_darwin.go b/disk_darwin.go new file mode 100644 index 0000000..95bda72 --- /dev/null +++ b/disk_darwin.go @@ -0,0 +1,12 @@ +// +build darwin + +package gopsutil + +func DiskPartitions(all bool) ([]DiskPartitionStat, error) { + + return nil, NotImplementedError +} + +func DiskIOCounters() (map[string]DiskIOCountersStat, error) { + return nil, NotImplementedError +} diff --git a/disk_unix.go b/disk_unix.go index 93f2225..c58c023 100644 --- a/disk_unix.go +++ b/disk_unix.go @@ -1,4 +1,4 @@ -// +build freebsd linux +// +build freebsd linux darwin package gopsutil diff --git a/host_darwin.go b/host_darwin.go new file mode 100644 index 0000000..1a3b639 --- /dev/null +++ b/host_darwin.go @@ -0,0 +1,148 @@ +// +build darwin + +package gopsutil + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "unsafe" +) + +const ( + UTX_USERSIZE = 256 /* include/NetBSD/utmpx.h */ + UTX_IDSIZE = 4 + UTX_LINESIZE = 32 + UTX_HOSTSIZE = 256 +) + +type utmpx32 struct { + UtUser [UTX_USERSIZE]byte /* login name */ + UtId [UTX_IDSIZE]byte /* id */ + UtLine [UTX_LINESIZE]byte /* tty name */ + //TODO UtPid pid_t /* process id creating the entry */ + UtType [4]byte /* type of this entry */ + //TODO UtTv timeval32 /* time entry was created */ + UtHost [UTX_HOSTSIZE]byte /* host name */ + UtPad [16]byte /* reserved for future use */ +} + +func HostInfo() (*HostInfoStat, error) { + ret := &HostInfoStat{ + OS: runtime.GOOS, + PlatformFamily: "darwin", + } + + hostname, err := os.Hostname() + if err != nil { + return ret, err + } + ret.Hostname = hostname + + platform, family, version, err := GetPlatformInformation() + if err == nil { + ret.Platform = platform + ret.PlatformFamily = family + ret.PlatformVersion = version + } + system, role, err := GetVirtualization() + if err == nil { + ret.VirtualizationSystem = system + ret.VirtualizationRole = role + } + + values, err := doSysctrl("kern.boottime") + if err == nil { + // ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014 + v := strings.Replace(values[2], ",", "", 1) + ret.Uptime = mustParseUint64(v) + } + + return ret, nil +} + +func BootTime() (int64, error) { + values, err := doSysctrl("kern.boottime") + if err != nil { + return 0, err + } + // ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014 + v := strings.Replace(values[2], ",", "", 1) + + boottime, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, err + } + + return boottime, nil +} + +func Users() ([]UserStat, error) { + utmpfile := "/var/run/utmpx" + var ret []UserStat + + file, err := os.Open(utmpfile) + if err != nil { + return ret, err + } + + buf, err := ioutil.ReadAll(file) + if err != nil { + return ret, err + } + + u := utmpx32{} + entrySize := int(unsafe.Sizeof(u)) + count := len(buf) / entrySize + + for i := 0; i < count; i++ { + b := buf[i*entrySize : i*entrySize+entrySize] + + var u utmpx32 + br := bytes.NewReader(b) + err := binary.Read(br, binary.LittleEndian, &u) + if err != nil { + continue + } + user := UserStat{ + User: byteToString(u.UtUser[:]), + // Terminal: byteToString(u.UtLine[:]), + Host: byteToString(u.UtHost[:]), + // Started: int(u.UtTime), + } + ret = append(ret, user) + } + + return ret, nil + +} + +func GetPlatformInformation() (string, string, string, error) { + platform := "" + family := "" + version := "" + + out, err := exec.Command("uname", "-s").Output() + if err == nil { + platform = strings.ToLower(strings.TrimSpace(string(out))) + } + + out, err = exec.Command("uname", "-r").Output() + if err == nil { + version = strings.ToLower(strings.TrimSpace(string(out))) + } + + return platform, family, version, nil +} + +func GetVirtualization() (string, string, error) { + system := "" + role := "" + + return system, role, nil +} diff --git a/load_darwin.go b/load_darwin.go new file mode 100644 index 0000000..0d801d5 --- /dev/null +++ b/load_darwin.go @@ -0,0 +1,35 @@ +// +build darwin + +package gopsutil + +import ( + "strconv" +) + +func LoadAvg() (*LoadAvgStat, error) { + values, err := doSysctrl("vm.loadavg") + if err != nil { + return nil, err + } + + load1, err := strconv.ParseFloat(values[0], 64) + if err != nil { + return nil, err + } + load5, err := strconv.ParseFloat(values[1], 64) + if err != nil { + return nil, err + } + load15, err := strconv.ParseFloat(values[2], 64) + if err != nil { + return nil, err + } + + ret := &LoadAvgStat{ + Load1: float64(load1), + Load5: float64(load5), + Load15: float64(load15), + } + + return ret, nil +} diff --git a/mem_darwin.go b/mem_darwin.go new file mode 100644 index 0000000..eb5f7ec --- /dev/null +++ b/mem_darwin.go @@ -0,0 +1,74 @@ +// +build darwin + +package gopsutil + +import ( + "os/exec" + "strings" +) + +func getPageSize() (uint64, error) { + out, err := exec.Command("pagesize").Output() + if err != nil { + return 0, err + } + p := mustParseUint64(string(out)) + return p, nil +} + +// VirtualMemory returns VirtualmemoryStat. +func VirtualMemory() (*VirtualMemoryStat, error) { + p, _ := getPageSize() + + total, _ := doSysctrl("hw.memsize") + free, _ := doSysctrl("vm.page_free_count") + /* + active, _ := doSysctrl("vm.stats.vm.v_active_count") + inactive, _ := doSysctrl("vm.pageout_inactive_used") + cache, _ := doSysctrl("vm.stats.vm.v_cache_count") + buffer, _ := doSysctrl("vfs.bufspace") + wired, _ := doSysctrl("vm.stats.vm.v_wire_count") + */ + + ret := &VirtualMemoryStat{ + Total: mustParseUint64(total[0]) * p, + Free: mustParseUint64(free[0]) * p, + /* + Active: mustParseUint64(active[0]) * p, + Inactive: mustParseUint64(inactive[0]) * p, + Cached: mustParseUint64(cache[0]) * p, + Buffers: mustParseUint64(buffer[0]), + Wired: mustParseUint64(wired[0]) * p, + */ + } + + // TODO: platform independent (worked freebsd?) + ret.Available = ret.Free + ret.Buffers + ret.Cached + + ret.Used = ret.Total - ret.Free + ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0 + + return ret, nil +} + +// SwapMemory returns swapinfo. +func SwapMemory() (*SwapMemoryStat, error) { + swapUsage, _ := doSysctrl("vm.swapusage") + + var ret *SwapMemoryStat + + total := strings.Replace(swapUsage[3], "M", "", 1) + used := strings.Replace(swapUsage[6], "M", "", 1) + free := strings.Replace(swapUsage[9], "M", "", 1) + + u := "0" + + ret = &SwapMemoryStat{ + Total: mustParseUint64(total), + Used: mustParseUint64(used), + Free: mustParseUint64(free), + UsedPercent: mustParseFloat64(u), + } + + return ret, nil +} diff --git a/net_darwin.go b/net_darwin.go new file mode 100644 index 0000000..66657b8 --- /dev/null +++ b/net_darwin.go @@ -0,0 +1,47 @@ +// +build darwin + +package gopsutil + +import ( + "os/exec" + "strings" +) + +func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { + out, err := exec.Command("/usr/sbin/netstat", "-ibdn").Output() + if err != nil { + return nil, err + } + + lines := strings.Split(string(out), "\n") + ret := make([]NetIOCountersStat, 0, len(lines)-1) + + for _, line := range lines { + values := strings.Fields(line) + if len(values) < 1 || values[0] == "Name" { + continue + } + base := 1 + // sometimes Address is ommitted + if len(values) < 13 { + base = 0 + } + + n := NetIOCountersStat{ + Name: values[0], + PacketsRecv: mustParseUint64(values[base+3]), + Errin: mustParseUint64(values[base+4]), + Dropin: mustParseUint64(values[base+5]), + /* + BytesRecv: mustParseUint64(values[base+6]), + PacketsSent: mustParseUint64(values[base+7]), + Errout: mustParseUint64(values[base+8]), + BytesSent: mustParseUint64(values[base+9]), + Dropout: mustParseUint64(values[base+11]), + */ + } + ret = append(ret, n) + } + + return ret, nil +} diff --git a/process_darwin.go b/process_darwin.go new file mode 100644 index 0000000..b463e19 --- /dev/null +++ b/process_darwin.go @@ -0,0 +1,306 @@ +// +build darwin + +package gopsutil + +import ( + "bytes" + "encoding/binary" + "syscall" + "unsafe" +) + +// MemoryInfoExStat is different between OSes +type MemoryInfoExStat struct { +} + +type MemoryMapsStat struct { +} + +func Pids() ([]int32, error) { + var ret []int32 + procs, err := processes() + if err != nil { + return ret, nil + } + + for _, p := range procs { + ret = append(ret, p.Pid) + } + + return ret, nil +} + +func (p *Process) Ppid() (int32, error) { + return 0, NotImplementedError + + k, err := p.getKProc() + if err != nil { + return 0, err + } + + return k.KiPpid, nil +} +func (p *Process) Name() (string, error) { + k, err := p.getKProc() + if err != nil { + return "", err + } + + return string(k.KiComm[:]), nil +} +func (p *Process) Exe() (string, error) { + return "", NotImplementedError +} +func (p *Process) Cmdline() (string, error) { + return "", NotImplementedError +} +func (p *Process) CreateTime() (int64, error) { + return 0, NotImplementedError +} +func (p *Process) Cwd() (string, error) { + return "", NotImplementedError +} +func (p *Process) Parent() (*Process, error) { + return p, NotImplementedError +} +func (p *Process) Status() (string, error) { + k, err := p.getKProc() + if err != nil { + return "", err + } + + return string(k.KiStat[:]), nil +} +func (p *Process) Username() (string, error) { + return "", NotImplementedError +} +func (p *Process) Uids() ([]int32, error) { + k, err := p.getKProc() + if err != nil { + return nil, err + } + + uids := make([]int32, 0, 3) + + uids = append(uids, int32(k.KiRuid), int32(k.KiUID), int32(k.KiSvuid)) + + return uids, nil +} +func (p *Process) Gids() ([]int32, error) { + k, err := p.getKProc() + if err != nil { + return nil, err + } + + gids := make([]int32, 0, 3) + gids = append(gids, int32(k.KiRgid), int32(k.KiNgroups[0]), int32(k.KiSvuid)) + + return gids, nil +} +func (p *Process) Terminal() (string, error) { + k, err := p.getKProc() + if err != nil { + return "", err + } + + ttyNr := uint64(k.KiTdev) + + termmap, err := getTerminalMap() + if err != nil { + return "", err + } + + return termmap[ttyNr], nil +} +func (p *Process) Nice() (int32, error) { + return 0, NotImplementedError +} +func (p *Process) IOnice() (int32, error) { + return 0, NotImplementedError +} +func (p *Process) Rlimit() ([]RlimitStat, error) { + var rlimit []RlimitStat + return rlimit, NotImplementedError +} +func (p *Process) IOCounters() (*IOCountersStat, error) { + return nil, NotImplementedError +} +func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { + return nil, NotImplementedError +} +func (p *Process) NumFDs() (int32, error) { + return 0, NotImplementedError +} +func (p *Process) NumThreads() (int32, error) { + k, err := p.getKProc() + if err != nil { + return 0, err + } + + return k.KiNumthreads, nil +} +func (p *Process) Threads() (map[string]string, error) { + ret := make(map[string]string, 0) + return ret, NotImplementedError +} +func (p *Process) CPUTimes() (*CPUTimesStat, error) { + return nil, NotImplementedError +} +func (p *Process) CPUPercent() (int32, error) { + return 0, NotImplementedError +} +func (p *Process) CPUAffinity() ([]int32, error) { + return nil, NotImplementedError +} +func (p *Process) MemoryInfo() (*MemoryInfoStat, error) { + k, err := p.getKProc() + if err != nil { + return nil, err + } + + ret := &MemoryInfoStat{ + RSS: uint64(k.KiRssize), + VMS: uint64(k.KiSize), + } + + return ret, nil +} +func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { + return nil, NotImplementedError +} +func (p *Process) MemoryPercent() (float32, error) { + return 0, NotImplementedError +} + +func (p *Process) Children() ([]*Process, error) { + return nil, NotImplementedError +} + +func (p *Process) OpenFiles() ([]OpenFilesStat, error) { + return nil, NotImplementedError +} + +func (p *Process) Connections() ([]NetConnectionStat, error) { + return nil, NotImplementedError +} + +func (p *Process) IsRunning() (bool, error) { + return true, NotImplementedError +} +func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { + var ret []MemoryMapsStat + return &ret, NotImplementedError +} + +func copyParams(k *KinfoProc, p *Process) error { + + return nil +} + +func processes() ([]Process, error) { + results := make([]Process, 0, 50) + + mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0} + buf, length, err := callSyscall(mib) + if err != nil { + return results, err + } + + // get kinfo_proc size + k := KinfoProc{} + procinfoLen := int(unsafe.Sizeof(k)) + count := int(length / uint64(procinfoLen)) + + // parse buf to procs + for i := 0; i < count; i++ { + b := buf[i*procinfoLen : i*procinfoLen+procinfoLen] + k, err := parseKinfoProc(b) + if err != nil { + continue + } + p, err := NewProcess(int32(k.KiPid)) + if err != nil { + continue + } + copyParams(&k, p) + + results = append(results, *p) + } + + return results, nil +} + +func parseKinfoProc(buf []byte) (KinfoProc, error) { + var k KinfoProc + br := bytes.NewReader(buf) + err := binary.Read(br, binary.LittleEndian, &k) + if err != nil { + return k, err + } + + return k, nil +} + +func callSyscall(mib []int32) ([]byte, uint64, error) { + miblen := uint64(len(mib)) + + // get required buffer size + length := uint64(0) + _, _, err := syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(miblen), + 0, + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if err != 0 { + var b []byte + return b, length, err + } + if length == 0 { + var b []byte + return b, length, err + } + // get proc info itself + buf := make([]byte, length) + _, _, err = syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(miblen), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if err != 0 { + return buf, length, err + } + + return buf, length, nil +} + +func (p *Process) getKProc() (*KinfoProc, error) { + mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, p.Pid} + + buf, length, err := callSyscall(mib) + if err != nil { + return nil, err + } + procK := KinfoProc{} + if length != uint64(unsafe.Sizeof(procK)) { + return nil, err + } + + k, err := parseKinfoProc(buf) + if err != nil { + return nil, err + } + + return &k, nil +} + +func NewProcess(pid int32) (*Process, error) { + p := &Process{Pid: pid} + + return p, nil +} diff --git a/process_darwin_amd64.go b/process_darwin_amd64.go new file mode 100644 index 0000000..40fc60b --- /dev/null +++ b/process_darwin_amd64.go @@ -0,0 +1,96 @@ +// +build darwin +// +build amd64 + +package gopsutil + +// copied from sys/sysctl.h +const ( + CTL_KERN = 1 // "high kernel": proc, limits + KERN_PROC = 14 // struct: process entries + KERN_PROC_PID = 1 // by process id + KERN_PROC_PROC = 8 // only return procs + KERN_PROC_PATHNAME = 12 // path to executable +) + +// copied from sys/user.h +type KinfoProc struct { + KiStructsize int32 + KiLayout int32 + KiArgs int64 + KiPaddr int64 + KiAddr int64 + KiTracep int64 + KiTextvp int64 + KiFd int64 + KiVmspace int64 + KiWchan int64 + KiPid int32 + KiPpid int32 + KiPgid int32 + KiTpgid int32 + KiSid int32 + KiTsid int32 + KiJobc [2]byte + KiSpareShort1 [2]byte + KiTdev int32 + KiSiglist [16]byte + KiSigmask [16]byte + KiSigignore [16]byte + KiSigcatch [16]byte + KiUID int32 + KiRuid int32 + KiSvuid int32 + KiRgid int32 + KiSvgid int32 + KiNgroups [2]byte + KiSpareShort2 [2]byte + KiGroups [64]byte + KiSize int64 + KiRssize int64 + KiSwrss int64 + KiTsize int64 + KiDsize int64 + KiSsize int64 + KiXstat [2]byte + KiAcflag [2]byte + KiPctcpu int32 + KiEstcpu int32 + KiSlptime int32 + KiSwtime int32 + KiCow int32 + KiRuntime int64 + KiStart [16]byte + KiChildtime [16]byte + KiFlag int64 + KiKflag int64 + KiTraceflag int32 + KiStat [1]byte + KiNice [1]byte + KiLock [1]byte + KiRqindex [1]byte + KiOncpu [1]byte + KiLastcpu [1]byte + KiOcomm [17]byte + KiWmesg [9]byte + KiLogin [18]byte + KiLockname [9]byte + KiComm [20]byte + KiEmul [17]byte + KiSparestrings [68]byte + KiSpareints [36]byte + KiCrFlags int32 + KiJid int32 + KiNumthreads int32 + KiTid int32 + KiPri int32 + KiRusage [144]byte + KiRusageCh [144]byte + KiPcb int64 + KiKstack int64 + KiUdata int64 + KiTdaddr int64 + KiSpareptrs [48]byte + KiSpareint64s [96]byte + KiSflag int64 + KiTdflags int64 +} diff --git a/process_posix.go b/process_posix.go index ec52490..e52fed3 100644 --- a/process_posix.go +++ b/process_posix.go @@ -1,4 +1,4 @@ -// +build linux freebsd +// +build linux freebsd darwin package gopsutil