diff --git a/plugins/system/mock_PS.go b/plugins/system/mock_PS.go index 957839811..fef3a2e4c 100644 --- a/plugins/system/mock_PS.go +++ b/plugins/system/mock_PS.go @@ -1,8 +1,9 @@ package system -import "github.com/stretchr/testify/mock" - -import "github.com/shirou/gopsutil/load" +import ( + "github.com/influxdb/tivan/plugins/system/ps/load" + "github.com/stretchr/testify/mock" +) type MockPS struct { mock.Mock diff --git a/plugins/system/ps/.gitignore b/plugins/system/ps/.gitignore new file mode 100644 index 000000000..9204bd207 --- /dev/null +++ b/plugins/system/ps/.gitignore @@ -0,0 +1,3 @@ +*~ +#* +_obj diff --git a/plugins/system/ps/LICENSE b/plugins/system/ps/LICENSE new file mode 100644 index 000000000..f835f8475 --- /dev/null +++ b/plugins/system/ps/LICENSE @@ -0,0 +1,27 @@ +gopsutil is distributed under BSD license reproduced below. + +Copyright (c) 2014, WAKAYAMA Shirou +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the psutil authors nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/system/ps/README.rst b/plugins/system/ps/README.rst new file mode 100644 index 000000000..89532614d --- /dev/null +++ b/plugins/system/ps/README.rst @@ -0,0 +1,267 @@ +gopsutil: psutil for golang +============================== + +.. image:: https://drone.io/github.com/shirou/gopsutil/status.png + :target: https://drone.io/github.com/shirou/gopsutil + +.. image:: https://coveralls.io/repos/shirou/gopsutil/badge.png?branch=master + :target: https://coveralls.io/r/shirou/gopsutil?branch=master + + +This is a port of psutil (http://pythonhosted.org/psutil/). The challenge is porting all +psutil functions on some architectures... + +.. highlights:: Package Structure Changed! + + Package (a.k.a. directory) structure has been changed!! see `issue 24 `_ + +.. highlights:: golang 1.4 will become REQUIRED! + + Since syscall package becomes frozen, we should use golang/x/sys of golang 1.4 as soon as possible. + + +Available Architectures +------------------------------------ + +- FreeBSD i386/amd64 +- Linux i386/amd64/arm(raspberry pi) +- Windows/amd64 +- Darwin/amd64 + +All works are implemented without cgo by porting c struct to golang struct. + + +Usage +--------- + +.. code:: go + + import ( + "fmt" + + "github.com/shirou/gopsutil/mem" + ) + + func main() { + v, _ := mem.VirtualMemory() + + // almost every return value is a struct + fmt.Printf("Total: %v, Free:%v, UsedPercent:%f%%\n", v.Total, v.Free, v.UsedPercent) + + // convert to JSON. String() is also implemented + fmt.Println(v) + } + +The output is below. + +:: + + Total: 3179569152, Free:284233728, UsedPercent:84.508194% + {"total":3179569152,"available":492572672,"used":2895335424,"usedPercent":84.50819439828305, (snip)} + + +Documentation +------------------------ + +see http://godoc.org/github.com/shirou/gopsutil + + +More Info +-------------------- + +Several methods have been added which are not present in psutil, but will provide useful information. + +- host/HostInfo() (linux) + + - Hostname + - Uptime + - Procs + - OS (ex: "linux") + - Platform (ex: "ubuntu", "arch") + - PlatformFamily (ex: "debian") + - PlatformVersion (ex: "Ubuntu 13.10") + - VirtualizationSystem (ex: "LXC") + - VirtualizationRole (ex: "guest"/"host") + +- cpu/CPUInfo() (linux, freebsd) + + - CPU (ex: 0, 1, ...) + - VendorID (ex: "GenuineIntel") + - Family + - Model + - Stepping + - PhysicalID + - CoreID + - Cores (ex: 2) + - ModelName (ex: "Intel(R) Core(TM) i7-2640M CPU @ 2.80GHz") + - Mhz + - CacheSize + - Flags (ex: "fpu vme de pse tsc msr pae mce cx8 ...") + +- load/LoadAvg() (linux, freebsd) + + - Load1 + - Load5 + - Load15 + +- docker/GetDockerIDList() (linux only) + + - container id list ([]string) + +- docker/CgroupCPU() (linux only) + + - user + - system + +- docker/CgroupMem() (linux only) + + - various status + +Some codes are ported from Ohai. many thanks. + + +Current Status +------------------ + +- x: work +- b: almost work but something broken + +================= ====== ======= ====== ======= +name Linux FreeBSD MacOSX Windows +cpu_times x x x +cpu_count x x x x +cpu_percent x x x +cpu_times_percent x x x +virtual_memory x x x x +swap_memory x x x +disk_partitions x x x x +disk_io_counters x x +disk_usage x x x x +net_io_counters x x b x +boot_time x x x x +users x x x x +pids x x x x +pid_exists x x x x +net_connections +================= ====== ======= ====== ======= + +Process class +^^^^^^^^^^^^^^^ + +================ ===== ======= ====== ======= +name Linux FreeBSD MacOSX Windows +pid x x x x +ppid x x x x +name x x x x +cmdline x x x +create_time x +status x x x +cwd x +exe x x x +uids x x x +gids x x x +terminal x x x +io_counters x +nice x x x +num_fds x +num_ctx_switches x +num_threads x x x x +cpu_times x +memory_info x x x +memory_info_ex x +memory_maps x +open_files x +send_signal x x x +suspend x x x +resume x x x +terminate x x x +kill x x x +username x +ionice +rlimit +num_handlres +threads +cpu_percent +cpu_affinity +memory_percent +children +connections +is_running +================ ===== ======= ====== ======= + +Original Metrics +^^^^^^^^^^^^^^^^^^^ +================== ===== ======= ====== ======= +item Linux FreeBSD MacOSX Windows +**HostInfo** +hostname x x x x + uptime x x x + proces x x + os x x x x + platform x x x + platformfamiliy x x x + virtualization x +**CPU** + VendorID x x x x + Family x x x x + Model x x x x + Stepping x x x x + PhysicalID x + CoreID x + Cores x x + ModelName x x x x +**LoadAvg** + Load1 x x x + Load5 x x x + Load15 x x x +**GetDockerID** + container id x no no no +**CgroupsCPU** + user x no no no + system x no no no +**CgroupsMem** + various x no no no +================== ===== ======= ====== ======= + +- future work + + - process_iter + - wait_procs + - Process class + + - parent (use ppid instead) + - as_dict + - wait + + +License +------------ + +New BSD License (same as psutil) + + +Related Works +----------------------- + +I have been influenced by the following great works: + +- psutil: http://pythonhosted.org/psutil/ +- dstat: https://github.com/dagwieers/dstat +- gosiger: https://github.com/cloudfoundry/gosigar/ +- goprocinfo: https://github.com/c9s/goprocinfo +- go-ps: https://github.com/mitchellh/go-ps +- ohai: https://github.com/opscode/ohai/ +- bosun: https://github.com/bosun-monitor/bosun/tree/master/cmd/scollector/collectors +- mackerel: https://github.com/mackerelio/mackerel-agent/tree/master/metrics + +How to Contribute +--------------------------- + +1. Fork it +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Add some feature') +4. Push to the branch (git push origin my-new-feature) +5. Create new Pull Request + +My English is terrible, so documentation or correcting comments are also +welcome. diff --git a/plugins/system/ps/common/common.go b/plugins/system/ps/common/common.go new file mode 100644 index 000000000..1b77e36c2 --- /dev/null +++ b/plugins/system/ps/common/common.go @@ -0,0 +1,151 @@ +// +// gopsutil is a port of psutil(http://pythonhosted.org/psutil/). +// This covers these architectures. +// - linux (amd64, arm) +// - freebsd (amd64) +// - windows (amd64) +package common + +import ( + "bufio" + "errors" + "os" + "reflect" + "strconv" + "strings" +) + +var NotImplementedError = errors.New("not implemented yet") + +// ReadLines reads contents from file and splits them by new line. +// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1). +func ReadLines(filename string) ([]string, error) { + return ReadLinesOffsetN(filename, 0, -1) +} + +// ReadLines reads contents from file and splits them by new line. +// The offset tells at which line number to start. +// The count determines the number of lines to read (starting from offset): +// n >= 0: at most n lines +// n < 0: whole file +func ReadLinesOffsetN(filename string, offset uint, n int) ([]string, error) { + f, err := os.Open(filename) + if err != nil { + return []string{""}, err + } + defer f.Close() + + var ret []string + + r := bufio.NewReader(f) + for i := 0; i < n+int(offset) || n < 0; i++ { + line, err := r.ReadString('\n') + if err != nil { + break + } + if i < int(offset) { + continue + } + ret = append(ret, strings.Trim(line, "\n")) + } + + return ret, nil +} + +func IntToString(orig []int8) string { + ret := make([]byte, len(orig)) + size := -1 + for i, o := range orig { + if o == 0 { + size = i + break + } + ret[i] = byte(o) + } + if size == -1 { + size = len(orig) + } + + return string(ret[0:size]) +} + +func ByteToString(orig []byte) string { + n := -1 + l := -1 + for i, b := range orig { + // skip left side null + if l == -1 && b == 0 { + continue + } + if l == -1 { + l = i + } + + if b == 0 { + break + } + n = i + 1 + } + if n == -1 { + return string(orig) + } + return string(orig[l:n]) +} + +// Parse to int32 without error +func mustParseInt32(val string) int32 { + vv, _ := strconv.ParseInt(val, 10, 32) + return int32(vv) +} + +// Parse to uint64 without error +func mustParseUint64(val string) uint64 { + vv, _ := strconv.ParseInt(val, 10, 64) + return uint64(vv) +} + +// Parse to Float64 without error +func mustParseFloat64(val string) float64 { + vv, _ := strconv.ParseFloat(val, 64) + return vv +} + +// Check the target string slice containes src or not +func StringContains(target []string, src string) bool { + for _, t := range target { + if strings.TrimSpace(t) == src { + return true + } + } + return false +} + +// get struct attributes. +// This method is used only for debugging platform dependent code. +func attributes(m interface{}) map[string]reflect.Type { + typ := reflect.TypeOf(m) + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + + attrs := make(map[string]reflect.Type) + if typ.Kind() != reflect.Struct { + return nil + } + + for i := 0; i < typ.NumField(); i++ { + p := typ.Field(i) + if !p.Anonymous { + attrs[p.Name] = p.Type + } + } + + return attrs +} + +func PathExists(filename string) bool { + if _, err := os.Stat(filename); err == nil { + return true + } + return false +} diff --git a/plugins/system/ps/common/common_darwin.go b/plugins/system/ps/common/common_darwin.go new file mode 100644 index 000000000..7d6f3c692 --- /dev/null +++ b/plugins/system/ps/common/common_darwin.go @@ -0,0 +1,60 @@ +// +build darwin + +package common + +import ( + "os/exec" + "strings" + "syscall" + "unsafe" +) + +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 +} + +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 +} diff --git a/plugins/system/ps/common/common_freebsd.go b/plugins/system/ps/common/common_freebsd.go new file mode 100644 index 000000000..3c1124655 --- /dev/null +++ b/plugins/system/ps/common/common_freebsd.go @@ -0,0 +1,61 @@ +// +build freebsd + +package common + +import ( + "syscall" + "os/exec" + "strings" + "unsafe" +) + +func DoSysctrl(mib string) ([]string, error) { + out, err := exec.Command("/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 +} + +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 +} + diff --git a/plugins/system/ps/common/common_test.go b/plugins/system/ps/common/common_test.go new file mode 100644 index 000000000..231fb4728 --- /dev/null +++ b/plugins/system/ps/common/common_test.go @@ -0,0 +1,90 @@ +package common + +import ( + "fmt" + "strings" + "testing" +) + +func TestReadlines(t *testing.T) { + ret, err := ReadLines("common_test.go") + if err != nil { + t.Error(err) + } + if !strings.Contains(ret[0], "package common") { + t.Error("could not read correctly") + } +} + +func TestReadLinesOffsetN(t *testing.T) { + ret, err := ReadLinesOffsetN("common_test.go", 2, 1) + if err != nil { + t.Error(err) + } + fmt.Println(ret[0]) + if !strings.Contains(ret[0], `import (`) { + t.Error("could not read correctly") + } +} + +func TestIntToString(t *testing.T) { + src := []int8{65, 66, 67} + dst := IntToString(src) + if dst != "ABC" { + t.Error("could not convert") + } +} +func TestByteToString(t *testing.T) { + src := []byte{65, 66, 67} + dst := ByteToString(src) + if dst != "ABC" { + t.Error("could not convert") + } + + src = []byte{0, 65, 66, 67} + dst = ByteToString(src) + if dst != "ABC" { + t.Error("could not convert") + } +} + +func TestmustParseInt32(t *testing.T) { + ret := mustParseInt32("11111") + if ret != int32(11111) { + t.Error("could not parse") + } +} +func TestmustParseUint64(t *testing.T) { + ret := mustParseUint64("11111") + if ret != uint64(11111) { + t.Error("could not parse") + } +} +func TestmustParseFloat64(t *testing.T) { + ret := mustParseFloat64("11111.11") + if ret != float64(11111.11) { + t.Error("could not parse") + } + ret = mustParseFloat64("11111") + if ret != float64(11111) { + t.Error("could not parse") + } +} +func TestStringContains(t *testing.T) { + target, err := ReadLines("common_test.go") + if err != nil { + t.Error(err) + } + if !StringContains(target, "func TestStringContains(t *testing.T) {") { + t.Error("cloud not test correctly") + } +} + +func TestPathExists(t *testing.T) { + if !PathExists("common_test.go") { + t.Error("exists but return not exists") + } + if PathExists("should_not_exists.go") { + t.Error("not exists but return exists") + } +} diff --git a/plugins/system/ps/common/common_windows.go b/plugins/system/ps/common/common_windows.go new file mode 100644 index 000000000..4cc0ba9ea --- /dev/null +++ b/plugins/system/ps/common/common_windows.go @@ -0,0 +1,144 @@ +// +build windows + +package common + +import ( + "fmt" + "os/exec" + "strings" + "syscall" + "unsafe" +) + +// for double values +type PDH_FMT_COUNTERVALUE_DOUBLE struct { + CStatus uint32 + DoubleValue float64 +} + +// for 64 bit integer values +type PDH_FMT_COUNTERVALUE_LARGE struct { + CStatus uint32 + LargeValue int64 +} + +// for long values +type PDH_FMT_COUNTERVALUE_LONG struct { + CStatus uint32 + LongValue int32 + padding [4]byte +} + +// windows system const +const ( + ERROR_SUCCESS = 0 + ERROR_FILE_NOT_FOUND = 2 + DRIVE_REMOVABLE = 2 + DRIVE_FIXED = 3 + HKEY_LOCAL_MACHINE = 0x80000002 + RRF_RT_REG_SZ = 0x00000002 + RRF_RT_REG_DWORD = 0x00000010 + PDH_FMT_LONG = 0x00000100 + PDH_FMT_DOUBLE = 0x00000200 + PDH_FMT_LARGE = 0x00000400 + PDH_INVALID_DATA = 0xc0000bc6 + PDH_INVALID_HANDLE = 0xC0000bbc + PDH_NO_DATA = 0x800007d5 +) + +var ( + Modkernel32 = syscall.NewLazyDLL("kernel32.dll") + ModNt = syscall.NewLazyDLL("ntdll.dll") + ModPdh = syscall.NewLazyDLL("pdh.dll") + + ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes") + ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation") + PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") + PdhAddCounter = ModPdh.NewProc("PdhAddCounterW") + PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") + PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") + PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") +) + +type FILETIME struct { + DwLowDateTime uint32 + DwHighDateTime uint32 +} + +// borrowed from net/interface_windows.go +func BytePtrToString(p *uint8) string { + a := (*[10000]uint8)(unsafe.Pointer(p)) + i := 0 + for a[i] != 0 { + i++ + } + return string(a[:i]) +} + +// exec wmic and return lines splited by newline +func GetWmic(target string, query ...string) ([][]string, error) { + cmd := []string{target} + cmd = append(cmd, query...) + cmd = append(cmd, "/format:csv") + out, err := exec.Command("wmic", cmd...).Output() + if err != nil { + return [][]string{}, err + } + lines := strings.Split(string(out), "\r\r\n") + if len(lines) <= 2 { + return [][]string{}, fmt.Errorf("wmic result malformed: [%q]", lines) + } + var ret [][]string + for _, l := range lines[2:] { // skip first two lines + var lr []string + for _, r := range strings.Split(l, ",") { + if r == "" { + continue + } + lr = append(lr, strings.TrimSpace(r)) + } + if len(lr) != 0 { + ret = append(ret, lr) + } + } + + return ret, nil + +} + +// CounterInfo +// copied from https://github.com/mackerelio/mackerel-agent/ +type CounterInfo struct { + PostName string + CounterName string + Counter syscall.Handle +} + +// CreateQuery XXX +// copied from https://github.com/mackerelio/mackerel-agent/ +func CreateQuery() (syscall.Handle, error) { + var query syscall.Handle + r, _, err := PdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query))) + if r != 0 { + return 0, err + } + return query, nil +} + +// CreateCounter XXX +func CreateCounter(query syscall.Handle, pname, cname string) (*CounterInfo, error) { + var counter syscall.Handle + r, _, err := PdhAddCounter.Call( + uintptr(query), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(cname))), + 0, + uintptr(unsafe.Pointer(&counter))) + if r != 0 { + return nil, err + } + return &CounterInfo{ + PostName: pname, + CounterName: cname, + Counter: counter, + }, nil +} diff --git a/plugins/system/ps/coverall.sh b/plugins/system/ps/coverall.sh new file mode 100644 index 000000000..35aa298ba --- /dev/null +++ b/plugins/system/ps/coverall.sh @@ -0,0 +1,26 @@ +#/bin/sh + +# see http://www.songmu.jp/riji/entry/2015-01-15-goveralls-multi-package.html + +set -e +# cleanup +cleanup() { + if [ $tmpprof != "" ] && [ -f $tmpprof ]; then + rm -f $tmpprof + fi + exit +} +trap cleanup INT QUIT TERM EXIT + +# メインの処理 +prof=${1:-".profile.cov"} +echo "mode: count" > $prof +gopath1=$(echo $GOPATH | cut -d: -f1) +for pkg in $(go list ./...); do + tmpprof=$gopath1/src/$pkg/profile.tmp + go test -covermode=count -coverprofile=$tmpprof $pkg + if [ -f $tmpprof ]; then + cat $tmpprof | tail -n +2 >> $prof + rm $tmpprof + fi +done diff --git a/plugins/system/ps/cpu/cpu.go b/plugins/system/ps/cpu/cpu.go new file mode 100644 index 000000000..b8e8edb44 --- /dev/null +++ b/plugins/system/ps/cpu/cpu.go @@ -0,0 +1,74 @@ +package cpu + +import ( + "encoding/json" + "runtime" + "strconv" + "strings" +) + +type CPUTimesStat struct { + CPU string `json:"cpu"` + User float64 `json:"user"` + System float64 `json:"system"` + Idle float64 `json:"idle"` + Nice float64 `json:"nice"` + Iowait float64 `json:"iowait"` + Irq float64 `json:"irq"` + Softirq float64 `json:"softirq"` + Steal float64 `json:"steal"` + Guest float64 `json:"guest"` + GuestNice float64 `json:"guest_nice"` + Stolen float64 `json:"stolen"` +} + +type CPUInfoStat struct { + CPU int32 `json:"cpu"` + VendorID string `json:"vendor_id"` + Family string `json:"family"` + Model string `json:"model"` + Stepping int32 `json:"stepping"` + PhysicalID string `json:"physical_id"` + CoreID string `json:"core_id"` + Cores int32 `json:"cores"` + ModelName string `json:"model_name"` + Mhz float64 `json:"mhz"` + CacheSize int32 `json:"cache_size"` + Flags []string `json:"flags"` +} + +var lastCPUTimes []CPUTimesStat +var lastPerCPUTimes []CPUTimesStat + +func CPUCounts(logical bool) (int, error) { + return runtime.NumCPU(), nil +} + +func (c CPUTimesStat) String() string { + v := []string{ + `"cpu":"` + c.CPU + `"`, + `"user":` + strconv.FormatFloat(c.User, 'f', 1, 64), + `"system":` + strconv.FormatFloat(c.System, 'f', 1, 64), + `"idle":` + strconv.FormatFloat(c.Idle, 'f', 1, 64), + `"nice":` + strconv.FormatFloat(c.Nice, 'f', 1, 64), + `"iowait":` + strconv.FormatFloat(c.Iowait, 'f', 1, 64), + `"irq":` + strconv.FormatFloat(c.Irq, 'f', 1, 64), + `"softirq":` + strconv.FormatFloat(c.Softirq, 'f', 1, 64), + `"steal":` + strconv.FormatFloat(c.Steal, 'f', 1, 64), + `"guest":` + strconv.FormatFloat(c.Guest, 'f', 1, 64), + `"guest_nice":` + strconv.FormatFloat(c.GuestNice, 'f', 1, 64), + `"stolen":` + strconv.FormatFloat(c.Stolen, 'f', 1, 64), + } + + return `{` + strings.Join(v, ",") + `}` +} + +func (c CPUInfoStat) String() string { + s, _ := json.Marshal(c) + return string(s) +} + +func init() { + lastCPUTimes, _ = CPUTimes(false) + lastPerCPUTimes, _ = CPUTimes(true) +} diff --git a/plugins/system/ps/cpu/cpu_darwin.go b/plugins/system/ps/cpu/cpu_darwin.go new file mode 100644 index 000000000..72b08d1a8 --- /dev/null +++ b/plugins/system/ps/cpu/cpu_darwin.go @@ -0,0 +1,149 @@ +// +build darwin + +package cpu + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +// sys/resource.h +const ( + CPUser = 0 + CPNice = 1 + CPSys = 2 + CPIntr = 3 + CPIdle = 4 + CPUStates = 5 +) + +// time.h +const ( + ClocksPerSec = 128 +) + +func CPUTimes(percpu bool) ([]CPUTimesStat, error) { + var ret []CPUTimesStat + + var sysctlCall string + var ncpu int + if percpu { + sysctlCall = "kern.cp_times" + ncpu, _ = CPUCounts(true) + } else { + sysctlCall = "kern.cp_time" + ncpu = 1 + } + + cpuTimes, err := common.DoSysctrl(sysctlCall) + if err != nil { + return ret, err + } + + for i := 0; i < ncpu; i++ { + offset := CPUStates * i + user, err := strconv.ParseFloat(cpuTimes[CPUser+offset], 64) + if err != nil { + return ret, err + } + nice, err := strconv.ParseFloat(cpuTimes[CPNice+offset], 64) + if err != nil { + return ret, err + } + sys, err := strconv.ParseFloat(cpuTimes[CPSys+offset], 64) + if err != nil { + return ret, err + } + idle, err := strconv.ParseFloat(cpuTimes[CPIdle+offset], 64) + if err != nil { + return ret, err + } + intr, err := strconv.ParseFloat(cpuTimes[CPIntr+offset], 64) + if err != nil { + return ret, err + } + + c := CPUTimesStat{ + User: float64(user / ClocksPerSec), + Nice: float64(nice / ClocksPerSec), + System: float64(sys / ClocksPerSec), + Idle: float64(idle / ClocksPerSec), + Irq: float64(intr / ClocksPerSec), + } + if !percpu { + c.CPU = "cpu-total" + } else { + c.CPU = fmt.Sprintf("cpu%d", i) + } + + ret = append(ret, c) + } + + return ret, nil +} + +// Returns only one CPUInfoStat on FreeBSD +func CPUInfo() ([]CPUInfoStat, error) { + var ret []CPUInfoStat + + out, err := exec.Command("/usr/sbin/sysctl", "machdep.cpu").Output() + if err != nil { + return ret, err + } + + c := CPUInfoStat{} + for _, line := range strings.Split(string(out), "\n") { + values := strings.Fields(line) + if len(values) < 1 { + continue + } + + t, err := strconv.ParseInt(values[1], 10, 64) + // err is not checked here because some value is string. + if strings.HasPrefix(line, "machdep.cpu.brand_string") { + c.ModelName = strings.Join(values[1:], " ") + } else if strings.HasPrefix(line, "machdep.cpu.family") { + c.Family = values[1] + } else if strings.HasPrefix(line, "machdep.cpu.model") { + c.Model = values[1] + } else if strings.HasPrefix(line, "machdep.cpu.stepping") { + if err != nil { + return ret, err + } + c.Stepping = int32(t) + } else if strings.HasPrefix(line, "machdep.cpu.features") { + for _, v := range values[1:] { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if strings.HasPrefix(line, "machdep.cpu.leaf7_features") { + for _, v := range values[1:] { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if strings.HasPrefix(line, "machdep.cpu.extfeatures") { + for _, v := range values[1:] { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if strings.HasPrefix(line, "machdep.cpu.core_count") { + if err != nil { + return ret, err + } + c.Cores = int32(t) + } else if strings.HasPrefix(line, "machdep.cpu.cache.size") { + if err != nil { + return ret, err + } + c.CacheSize = int32(t) + } else if strings.HasPrefix(line, "machdep.cpu.vendor") { + c.VendorID = values[1] + } + + // TODO: + // c.Mhz = mustParseFloat64(values[1]) + } + + return append(ret, c), nil +} diff --git a/plugins/system/ps/cpu/cpu_freebsd.go b/plugins/system/ps/cpu/cpu_freebsd.go new file mode 100644 index 000000000..dc28dd6b6 --- /dev/null +++ b/plugins/system/ps/cpu/cpu_freebsd.go @@ -0,0 +1,134 @@ +// +build freebsd + +package cpu + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +// sys/resource.h +const ( + CPUser = 0 + CPNice = 1 + CPSys = 2 + CPIntr = 3 + CPIdle = 4 + CPUStates = 5 +) + +// time.h +const ( + ClocksPerSec = 128 +) + +func CPUTimes(percpu bool) ([]CPUTimesStat, error) { + var ret []CPUTimesStat + + var sysctlCall string + var ncpu int + if percpu { + sysctlCall = "kern.cp_times" + ncpu, _ = CPUCounts(true) + } else { + sysctlCall = "kern.cp_time" + ncpu = 1 + } + + cpuTimes, err := common.DoSysctrl(sysctlCall) + if err != nil { + return ret, err + } + + for i := 0; i < ncpu; i++ { + offset := CPUStates * i + user, err := strconv.ParseFloat(cpuTimes[CPUser+offset], 64) + if err != nil { + return ret, err + } + nice, err := strconv.ParseFloat(cpuTimes[CPNice+offset], 64) + if err != nil { + return ret, err + } + sys, err := strconv.ParseFloat(cpuTimes[CPSys+offset], 64) + if err != nil { + return ret, err + } + idle, err := strconv.ParseFloat(cpuTimes[CPIdle+offset], 64) + if err != nil { + return ret, err + } + intr, err := strconv.ParseFloat(cpuTimes[CPIntr+offset], 64) + if err != nil { + return ret, err + } + + c := CPUTimesStat{ + User: float64(user / ClocksPerSec), + Nice: float64(nice / ClocksPerSec), + System: float64(sys / ClocksPerSec), + Idle: float64(idle / ClocksPerSec), + Irq: float64(intr / ClocksPerSec), + } + if !percpu { + c.CPU = "cpu-total" + } else { + c.CPU = fmt.Sprintf("cpu%d", i) + } + + ret = append(ret, c) + } + + return ret, nil +} + +// Returns only one CPUInfoStat on FreeBSD +func CPUInfo() ([]CPUInfoStat, error) { + filename := "/var/run/dmesg.boot" + lines, _ := common.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] + t, err := strconv.ParseFloat(matches[2], 64) + if err != nil { + return ret, nil + } + c.Mhz = t + } 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] + t, err := strconv.ParseInt(matches[5], 10, 32) + if err != nil { + return ret, nil + } + c.Stepping = int32(t) + } 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? + t, err := strconv.ParseInt(matches[1], 10, 32) + if err != nil { + return ret, nil + } + c.Cores = int32(t) + } + + } + + return append(ret, c), nil +} diff --git a/plugins/system/ps/cpu/cpu_linux.go b/plugins/system/ps/cpu/cpu_linux.go new file mode 100644 index 000000000..2faca2dc1 --- /dev/null +++ b/plugins/system/ps/cpu/cpu_linux.go @@ -0,0 +1,206 @@ +// +build linux + +package cpu + +import ( + "errors" + "os/exec" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +func CPUTimes(percpu bool) ([]CPUTimesStat, error) { + filename := "/proc/stat" + var lines = []string{} + if percpu { + var startIdx uint = 1 + for { + linen, _ := common.ReadLinesOffsetN(filename, startIdx, 1) + line := linen[0] + if !strings.HasPrefix(line, "cpu") { + break + } + lines = append(lines, line) + startIdx += 1 + } + } else { + lines, _ = common.ReadLinesOffsetN(filename, 0, 1) + } + + ret := make([]CPUTimesStat, 0, len(lines)) + + for _, line := range lines { + ct, err := parseStatLine(line) + if err != nil { + continue + } + ret = append(ret, *ct) + + } + return ret, nil +} + +func CPUInfo() ([]CPUInfoStat, error) { + filename := "/proc/cpuinfo" + lines, _ := common.ReadLines(filename) + + var ret []CPUInfoStat + + var c CPUInfoStat + for _, line := range lines { + fields := strings.Split(line, ":") + if len(fields) < 2 { + if c.VendorID != "" { + ret = append(ret, c) + } + continue + } + key := strings.TrimSpace(fields[0]) + value := strings.TrimSpace(fields[1]) + + switch key { + case "processor": + c = CPUInfoStat{} + t, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return ret, err + } + c.CPU = int32(t) + case "vendor_id": + c.VendorID = value + case "cpu family": + c.Family = value + case "model": + c.Model = value + case "model name": + c.ModelName = value + case "stepping": + t, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return ret, err + } + c.Stepping = int32(t) + case "cpu MHz": + t, err := strconv.ParseFloat(value, 64) + if err != nil { + return ret, err + } + c.Mhz = t + case "cache size": + t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64) + if err != nil { + return ret, err + } + c.CacheSize = int32(t) + case "physical id": + c.PhysicalID = value + case "core id": + c.CoreID = value + case "cpu cores": + t, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return ret, err + } + c.Cores = int32(t) + case "flags": + c.Flags = strings.Split(value, ",") + } + } + return ret, nil +} + +var CLK_TCK = 100 + +func init() { + out, err := exec.Command("getconf", "CLK_TCK").CombinedOutput() + if err == nil { + i, err := strconv.Atoi(strings.TrimSpace(string(out))) + if err == nil { + CLK_TCK = i + } + } +} + +func parseStatLine(line string) (*CPUTimesStat, error) { + fields := strings.Fields(line) + + if strings.HasPrefix(fields[0], "cpu") == false { + // return CPUTimesStat{}, e + return nil, errors.New("not contain cpu") + } + + cpu := fields[0] + if cpu == "cpu" { + cpu = "cpu-total" + } + user, err := strconv.ParseFloat(fields[1], 64) + if err != nil { + return nil, err + } + nice, err := strconv.ParseFloat(fields[2], 64) + if err != nil { + return nil, err + } + system, err := strconv.ParseFloat(fields[3], 64) + if err != nil { + return nil, err + } + idle, err := strconv.ParseFloat(fields[4], 64) + if err != nil { + return nil, err + } + iowait, err := strconv.ParseFloat(fields[5], 64) + if err != nil { + return nil, err + } + irq, err := strconv.ParseFloat(fields[6], 64) + if err != nil { + return nil, err + } + softirq, err := strconv.ParseFloat(fields[7], 64) + if err != nil { + return nil, err + } + stolen, err := strconv.ParseFloat(fields[8], 64) + if err != nil { + return nil, err + } + + cpu_tick := float64(CLK_TCK) + ct := &CPUTimesStat{ + CPU: cpu, + User: float64(user) / cpu_tick, + Nice: float64(nice) / cpu_tick, + System: float64(system) / cpu_tick, + Idle: float64(idle) / cpu_tick, + Iowait: float64(iowait) / cpu_tick, + Irq: float64(irq) / cpu_tick, + Softirq: float64(softirq) / cpu_tick, + Stolen: float64(stolen) / cpu_tick, + } + if len(fields) > 9 { // Linux >= 2.6.11 + steal, err := strconv.ParseFloat(fields[9], 64) + if err != nil { + return nil, err + } + ct.Steal = float64(steal) + } + if len(fields) > 10 { // Linux >= 2.6.24 + guest, err := strconv.ParseFloat(fields[10], 64) + if err != nil { + return nil, err + } + ct.Guest = float64(guest) + } + if len(fields) > 11 { // Linux >= 3.2.0 + guestNice, err := strconv.ParseFloat(fields[11], 64) + if err != nil { + return nil, err + } + ct.GuestNice = float64(guestNice) + } + + return ct, nil +} diff --git a/plugins/system/ps/cpu/cpu_test.go b/plugins/system/ps/cpu/cpu_test.go new file mode 100644 index 000000000..7de17a767 --- /dev/null +++ b/plugins/system/ps/cpu/cpu_test.go @@ -0,0 +1,98 @@ +package cpu + +import ( + "fmt" + "runtime" + "testing" + "time" +) + +func TestCpu_times(t *testing.T) { + v, err := CPUTimes(false) + if err != nil { + t.Errorf("error %v", err) + } + if len(v) == 0 { + t.Error("could not get CPUs ", err) + } + empty := CPUTimesStat{} + for _, vv := range v { + if vv == empty { + t.Errorf("could not get CPU User: %v", vv) + } + } +} + +func TestCpu_counts(t *testing.T) { + v, err := CPUCounts(true) + if err != nil { + t.Errorf("error %v", err) + } + if v == 0 { + t.Errorf("could not get CPU counts: %v", v) + } +} + +func TestCPUTimeStat_String(t *testing.T) { + v := CPUTimesStat{ + CPU: "cpu0", + User: 100.1, + System: 200.1, + Idle: 300.1, + } + e := `{"cpu":"cpu0","user":100.1,"system":200.1,"idle":300.1,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guest_nice":0.0,"stolen":0.0}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("CPUTimesStat string is invalid: %v", v) + } +} + +func TestCpuInfo(t *testing.T) { + v, err := CPUInfo() + if err != nil { + t.Errorf("error %v", err) + } + if len(v) == 0 { + t.Errorf("could not get CPU Info") + } + for _, vv := range v { + if vv.ModelName == "" { + t.Errorf("could not get CPU Info: %v", vv) + } + } +} + +func testCPUPercent(t *testing.T, percpu bool) { + numcpu := runtime.NumCPU() + testCount := 3 + + if runtime.GOOS != "windows" { + testCount = 100 + v, err := CPUPercent(time.Millisecond, percpu) + if err != nil { + t.Errorf("error %v", err) + } + if (percpu && len(v) != numcpu) || (!percpu && len(v) != 1) { + t.Fatalf("wrong number of entries from CPUPercent: %v", v) + } + } + for i := 0; i < testCount; i++ { + duration := time.Duration(10) * time.Microsecond + v, err := CPUPercent(duration, percpu) + if err != nil { + t.Errorf("error %v", err) + } + for _, percent := range v { + if percent < 0.0 || percent > 100.0*float64(numcpu) { + t.Fatalf("CPUPercent value is invalid: %f", percent) + } + } + } +} + +func TestCPUPercent(t *testing.T) { + testCPUPercent(t, false) +} + +func TestCPUPercentPerCpu(t *testing.T) { + testCPUPercent(t, true) +} diff --git a/plugins/system/ps/cpu/cpu_unix.go b/plugins/system/ps/cpu/cpu_unix.go new file mode 100644 index 000000000..fd488a284 --- /dev/null +++ b/plugins/system/ps/cpu/cpu_unix.go @@ -0,0 +1,61 @@ +// +build linux freebsd darwin + +package cpu + +import "time" + +func init() { + lastCPUTimes, _ = CPUTimes(false) + lastPerCPUTimes, _ = CPUTimes(true) +} + +func CPUPercent(interval time.Duration, percpu bool) ([]float64, error) { + getAllBusy := func(t CPUTimesStat) (float64, float64) { + busy := t.User + t.System + t.Nice + t.Iowait + t.Irq + + t.Softirq + t.Steal + t.Guest + t.GuestNice + t.Stolen + return busy + t.Idle, busy + } + + calculate := func(t1, t2 CPUTimesStat) float64 { + t1All, t1Busy := getAllBusy(t1) + t2All, t2Busy := getAllBusy(t2) + + if t2Busy <= t1Busy { + return 0 + } + if t2All <= t1All { + return 1 + } + return (t2Busy - t1Busy) / (t2All - t1All) * 100 + } + + cpuTimes, err := CPUTimes(percpu) + if err != nil { + return nil, err + } + + if interval > 0 { + if !percpu { + lastCPUTimes = cpuTimes + } else { + lastPerCPUTimes = cpuTimes + } + time.Sleep(interval) + cpuTimes, err = CPUTimes(percpu) + if err != nil { + return nil, err + } + } + + ret := make([]float64, len(cpuTimes)) + if !percpu { + ret[0] = calculate(lastCPUTimes[0], cpuTimes[0]) + lastCPUTimes = cpuTimes + } else { + for i, t := range cpuTimes { + ret[i] = calculate(lastPerCPUTimes[i], t) + } + lastPerCPUTimes = cpuTimes + } + return ret, nil +} diff --git a/plugins/system/ps/cpu/cpu_windows.go b/plugins/system/ps/cpu/cpu_windows.go new file mode 100644 index 000000000..c5f8c1d54 --- /dev/null +++ b/plugins/system/ps/cpu/cpu_windows.go @@ -0,0 +1,101 @@ +// +build windows + +package cpu + +import ( + "strconv" + "syscall" + "time" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +// TODO: Get percpu +func CPUTimes(percpu bool) ([]CPUTimesStat, error) { + var ret []CPUTimesStat + + var lpIdleTime common.FILETIME + var lpKernelTime common.FILETIME + var lpUserTime common.FILETIME + r, _, _ := common.ProcGetSystemTimes.Call( + uintptr(unsafe.Pointer(&lpIdleTime)), + uintptr(unsafe.Pointer(&lpKernelTime)), + uintptr(unsafe.Pointer(&lpUserTime))) + if r == 0 { + return ret, syscall.GetLastError() + } + + LOT := float64(0.0000001) + HIT := (LOT * 4294967296.0) + idle := ((HIT * float64(lpIdleTime.DwHighDateTime)) + (LOT * float64(lpIdleTime.DwLowDateTime))) + user := ((HIT * float64(lpUserTime.DwHighDateTime)) + (LOT * float64(lpUserTime.DwLowDateTime))) + kernel := ((HIT * float64(lpKernelTime.DwHighDateTime)) + (LOT * float64(lpKernelTime.DwLowDateTime))) + system := (kernel - idle) + + ret = append(ret, CPUTimesStat{ + Idle: float64(idle), + User: float64(user), + System: float64(system), + }) + return ret, nil +} + +func CPUInfo() ([]CPUInfoStat, error) { + var ret []CPUInfoStat + lines, err := common.GetWmic("cpu", "get", "Family,L2CacheSize,Manufacturer,Name,NumberOfLogicalProcessors,ProcessorId,Stepping") + if err != nil { + return ret, err + } + for i, t := range lines { + cache, err := strconv.Atoi(t[2]) + if err != nil { + cache = 0 + } + cores, err := strconv.Atoi(t[5]) + if err != nil { + cores = 0 + } + stepping := 0 + if len(t) > 7 { + stepping, err = strconv.Atoi(t[6]) + if err != nil { + stepping = 0 + } + } + cpu := CPUInfoStat{ + CPU: int32(i), + Family: t[1], + CacheSize: int32(cache), + VendorID: t[3], + ModelName: t[4], + Cores: int32(cores), + PhysicalID: t[6], + Stepping: int32(stepping), + Flags: []string{}, + } + ret = append(ret, cpu) + } + return ret, nil +} + +func CPUPercent(interval time.Duration, percpu bool) ([]float64, error) { + ret := []float64{} + + lines, err := common.GetWmic("cpu", "get", "loadpercentage") + if err != nil { + return ret, err + } + for _, l := range lines { + if len(l) < 2 { + continue + } + p, err := strconv.Atoi(l[1]) + if err != nil { + p = 0 + } + // but windows can only get one percent. + ret = append(ret, float64(p)/100.0) + } + return ret, nil +} diff --git a/plugins/system/ps/disk/binary.go b/plugins/system/ps/disk/binary.go new file mode 100644 index 000000000..418e591f4 --- /dev/null +++ b/plugins/system/ps/disk/binary.go @@ -0,0 +1,634 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package binary implements simple translation between numbers and byte +// sequences and encoding and decoding of varints. +// +// Numbers are translated by reading and writing fixed-size values. +// A fixed-size value is either a fixed-size arithmetic +// type (int8, uint8, int16, float32, complex64, ...) +// or an array or struct containing only fixed-size values. +// +// The varint functions encode and decode single integer values using +// a variable-length encoding; smaller values require fewer bytes. +// For a specification, see +// http://code.google.com/apis/protocolbuffers/docs/encoding.html. +// +// This package favors simplicity over efficiency. Clients that require +// high-performance serialization, especially for large data structures, +// should look at more advanced solutions such as the encoding/gob +// package or protocol buffers. +package disk + +import ( + "errors" + "io" + "math" + "reflect" +) + +// A ByteOrder specifies how to convert byte sequences into +// 16-, 32-, or 64-bit unsigned integers. +type ByteOrder interface { + Uint16([]byte) uint16 + Uint32([]byte) uint32 + Uint64([]byte) uint64 + PutUint16([]byte, uint16) + PutUint32([]byte, uint32) + PutUint64([]byte, uint64) + String() string +} + +// LittleEndian is the little-endian implementation of ByteOrder. +var LittleEndian littleEndian + +// BigEndian is the big-endian implementation of ByteOrder. +var BigEndian bigEndian + +type littleEndian struct{} + +func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } + +func (littleEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +func (littleEndian) Uint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func (littleEndian) PutUint32(b []byte, v uint32) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} + +func (littleEndian) Uint64(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func (littleEndian) PutUint64(b []byte, v uint64) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +func (littleEndian) String() string { return "LittleEndian" } + +func (littleEndian) GoString() string { return "binary.LittleEndian" } + +type bigEndian struct{} + +func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 } + +func (bigEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v >> 8) + b[1] = byte(v) +} + +func (bigEndian) Uint32(b []byte) uint32 { + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func (bigEndian) PutUint32(b []byte, v uint32) { + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} + +func (bigEndian) Uint64(b []byte) uint64 { + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func (bigEndian) PutUint64(b []byte, v uint64) { + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +func (bigEndian) String() string { return "BigEndian" } + +func (bigEndian) GoString() string { return "binary.BigEndian" } + +// Read reads structured binary data from r into data. +// Data must be a pointer to a fixed-size value or a slice +// of fixed-size values. +// Bytes read from r are decoded using the specified byte order +// and written to successive fields of the data. +// When reading into structs, the field data for fields with +// blank (_) field names is skipped; i.e., blank field names +// may be used for padding. +// When reading into a struct, all non-blank fields must be exported. +func Read(r io.Reader, order ByteOrder, data interface{}) error { + // Fast path for basic types and slices. + if n := intDataSize(data); n != 0 { + var b [8]byte + var bs []byte + if n > len(b) { + bs = make([]byte, n) + } else { + bs = b[:n] + } + if _, err := io.ReadFull(r, bs); err != nil { + return err + } + switch data := data.(type) { + case *int8: + *data = int8(b[0]) + case *uint8: + *data = b[0] + case *int16: + *data = int16(order.Uint16(bs)) + case *uint16: + *data = order.Uint16(bs) + case *int32: + *data = int32(order.Uint32(bs)) + case *uint32: + *data = order.Uint32(bs) + case *int64: + *data = int64(order.Uint64(bs)) + case *uint64: + *data = order.Uint64(bs) + case []int8: + for i, x := range bs { // Easier to loop over the input for 8-bit values. + data[i] = int8(x) + } + case []uint8: + copy(data, bs) + case []int16: + for i := range data { + data[i] = int16(order.Uint16(bs[2*i:])) + } + case []uint16: + for i := range data { + data[i] = order.Uint16(bs[2*i:]) + } + case []int32: + for i := range data { + data[i] = int32(order.Uint32(bs[4*i:])) + } + case []uint32: + for i := range data { + data[i] = order.Uint32(bs[4*i:]) + } + case []int64: + for i := range data { + data[i] = int64(order.Uint64(bs[8*i:])) + } + case []uint64: + for i := range data { + data[i] = order.Uint64(bs[8*i:]) + } + } + return nil + } + + // Fallback to reflect-based decoding. + v := reflect.ValueOf(data) + size := -1 + switch v.Kind() { + case reflect.Ptr: + v = v.Elem() + size = dataSize(v) + case reflect.Slice: + size = dataSize(v) + } + if size < 0 { + return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String()) + } + d := &decoder{order: order, buf: make([]byte, size)} + if _, err := io.ReadFull(r, d.buf); err != nil { + return err + } + d.value(v) + return nil +} + +// Write writes the binary representation of data into w. +// Data must be a fixed-size value or a slice of fixed-size +// values, or a pointer to such data. +// Bytes written to w are encoded using the specified byte order +// and read from successive fields of the data. +// When writing structs, zero values are written for fields +// with blank (_) field names. +func Write(w io.Writer, order ByteOrder, data interface{}) error { + // Fast path for basic types and slices. + if n := intDataSize(data); n != 0 { + var b [8]byte + var bs []byte + if n > len(b) { + bs = make([]byte, n) + } else { + bs = b[:n] + } + switch v := data.(type) { + case *int8: + bs = b[:1] + b[0] = byte(*v) + case int8: + bs = b[:1] + b[0] = byte(v) + case []int8: + for i, x := range v { + bs[i] = byte(x) + } + case *uint8: + bs = b[:1] + b[0] = *v + case uint8: + bs = b[:1] + b[0] = byte(v) + case []uint8: + bs = v + case *int16: + bs = b[:2] + order.PutUint16(bs, uint16(*v)) + case int16: + bs = b[:2] + order.PutUint16(bs, uint16(v)) + case []int16: + for i, x := range v { + order.PutUint16(bs[2*i:], uint16(x)) + } + case *uint16: + bs = b[:2] + order.PutUint16(bs, *v) + case uint16: + bs = b[:2] + order.PutUint16(bs, v) + case []uint16: + for i, x := range v { + order.PutUint16(bs[2*i:], x) + } + case *int32: + bs = b[:4] + order.PutUint32(bs, uint32(*v)) + case int32: + bs = b[:4] + order.PutUint32(bs, uint32(v)) + case []int32: + for i, x := range v { + order.PutUint32(bs[4*i:], uint32(x)) + } + case *uint32: + bs = b[:4] + order.PutUint32(bs, *v) + case uint32: + bs = b[:4] + order.PutUint32(bs, v) + case []uint32: + for i, x := range v { + order.PutUint32(bs[4*i:], x) + } + case *int64: + bs = b[:8] + order.PutUint64(bs, uint64(*v)) + case int64: + bs = b[:8] + order.PutUint64(bs, uint64(v)) + case []int64: + for i, x := range v { + order.PutUint64(bs[8*i:], uint64(x)) + } + case *uint64: + bs = b[:8] + order.PutUint64(bs, *v) + case uint64: + bs = b[:8] + order.PutUint64(bs, v) + case []uint64: + for i, x := range v { + order.PutUint64(bs[8*i:], x) + } + } + _, err := w.Write(bs) + return err + } + + // Fallback to reflect-based encoding. + v := reflect.Indirect(reflect.ValueOf(data)) + size := dataSize(v) + if size < 0 { + return errors.New("binary.Write: invalid type " + reflect.TypeOf(data).String()) + } + buf := make([]byte, size) + e := &encoder{order: order, buf: buf} + e.value(v) + _, err := w.Write(buf) + return err +} + +// Size returns how many bytes Write would generate to encode the value v, which +// must be a fixed-size value or a slice of fixed-size values, or a pointer to such data. +// If v is neither of these, Size returns -1. +func Size(v interface{}) int { + return dataSize(reflect.Indirect(reflect.ValueOf(v))) +} + +// dataSize returns the number of bytes the actual data represented by v occupies in memory. +// For compound structures, it sums the sizes of the elements. Thus, for instance, for a slice +// it returns the length of the slice times the element size and does not count the memory +// occupied by the header. If the type of v is not acceptable, dataSize returns -1. +func dataSize(v reflect.Value) int { + if v.Kind() == reflect.Slice { + if s := sizeof(v.Type().Elem()); s >= 0 { + return s * v.Len() + } + return -1 + } + return sizeof(v.Type()) +} + +// sizeof returns the size >= 0 of variables for the given type or -1 if the type is not acceptable. +func sizeof(t reflect.Type) int { + switch t.Kind() { + case reflect.Array: + if s := sizeof(t.Elem()); s >= 0 { + return s * t.Len() + } + + case reflect.Struct: + sum := 0 + for i, n := 0, t.NumField(); i < n; i++ { + s := sizeof(t.Field(i).Type) + if s < 0 { + return -1 + } + sum += s + } + return sum + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Ptr: + return int(t.Size()) + } + + return -1 +} + +type coder struct { + order ByteOrder + buf []byte +} + +type decoder coder +type encoder coder + +func (d *decoder) uint8() uint8 { + x := d.buf[0] + d.buf = d.buf[1:] + return x +} + +func (e *encoder) uint8(x uint8) { + e.buf[0] = x + e.buf = e.buf[1:] +} + +func (d *decoder) uint16() uint16 { + x := d.order.Uint16(d.buf[0:2]) + d.buf = d.buf[2:] + return x +} + +func (e *encoder) uint16(x uint16) { + e.order.PutUint16(e.buf[0:2], x) + e.buf = e.buf[2:] +} + +func (d *decoder) uint32() uint32 { + x := d.order.Uint32(d.buf[0:4]) + d.buf = d.buf[4:] + return x +} + +func (e *encoder) uint32(x uint32) { + e.order.PutUint32(e.buf[0:4], x) + e.buf = e.buf[4:] +} + +func (d *decoder) uint64() uint64 { + x := d.order.Uint64(d.buf[0:8]) + d.buf = d.buf[8:] + return x +} + +func (e *encoder) uint64(x uint64) { + e.order.PutUint64(e.buf[0:8], x) + e.buf = e.buf[8:] +} + +func (d *decoder) int8() int8 { return int8(d.uint8()) } + +func (e *encoder) int8(x int8) { e.uint8(uint8(x)) } + +func (d *decoder) int16() int16 { return int16(d.uint16()) } + +func (e *encoder) int16(x int16) { e.uint16(uint16(x)) } + +func (d *decoder) int32() int32 { return int32(d.uint32()) } + +func (e *encoder) int32(x int32) { e.uint32(uint32(x)) } + +func (d *decoder) int64() int64 { return int64(d.uint64()) } + +func (e *encoder) int64(x int64) { e.uint64(uint64(x)) } + +func (d *decoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // Note: Calling v.CanSet() below is an optimization. + // It would be sufficient to check the field name, + // but creating the StructField info for each field is + // costly (run "go test -bench=ReadStruct" and compare + // results when making changes to this code). + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + d.value(v) + } else { + d.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Int8: + v.SetInt(int64(d.int8())) + case reflect.Int16: + v.SetInt(int64(d.int16())) + case reflect.Int32: + v.SetInt(int64(d.int32())) + case reflect.Int64: + v.SetInt(d.int64()) + + case reflect.Uint8: + v.SetUint(uint64(d.uint8())) + case reflect.Uint16: + v.SetUint(uint64(d.uint16())) + case reflect.Uint32: + v.SetUint(uint64(d.uint32())) + case reflect.Uint64: + v.SetUint(d.uint64()) + + case reflect.Float32: + v.SetFloat(float64(math.Float32frombits(d.uint32()))) + case reflect.Float64: + v.SetFloat(math.Float64frombits(d.uint64())) + + case reflect.Complex64: + v.SetComplex(complex( + float64(math.Float32frombits(d.uint32())), + float64(math.Float32frombits(d.uint32())), + )) + case reflect.Complex128: + v.SetComplex(complex( + math.Float64frombits(d.uint64()), + math.Float64frombits(d.uint64()), + )) + } +} + +func (e *encoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // see comment for corresponding code in decoder.value() + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + e.value(v) + } else { + e.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch v.Type().Kind() { + case reflect.Int8: + e.int8(int8(v.Int())) + case reflect.Int16: + e.int16(int16(v.Int())) + case reflect.Int32: + e.int32(int32(v.Int())) + case reflect.Int64: + e.int64(v.Int()) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch v.Type().Kind() { + case reflect.Uint8: + e.uint8(uint8(v.Uint())) + case reflect.Uint16: + e.uint16(uint16(v.Uint())) + case reflect.Uint32: + e.uint32(uint32(v.Uint())) + case reflect.Uint64: + e.uint64(v.Uint()) + } + + case reflect.Float32, reflect.Float64: + switch v.Type().Kind() { + case reflect.Float32: + e.uint32(math.Float32bits(float32(v.Float()))) + case reflect.Float64: + e.uint64(math.Float64bits(v.Float())) + } + + case reflect.Complex64, reflect.Complex128: + switch v.Type().Kind() { + case reflect.Complex64: + x := v.Complex() + e.uint32(math.Float32bits(float32(real(x)))) + e.uint32(math.Float32bits(float32(imag(x)))) + case reflect.Complex128: + x := v.Complex() + e.uint64(math.Float64bits(real(x))) + e.uint64(math.Float64bits(imag(x))) + } + } +} + +func (d *decoder) skip(v reflect.Value) { + d.buf = d.buf[dataSize(v):] +} + +func (e *encoder) skip(v reflect.Value) { + n := dataSize(v) + for i := range e.buf[0:n] { + e.buf[i] = 0 + } + e.buf = e.buf[n:] +} + +// intDataSize returns the size of the data required to represent the data when encoded. +// It returns zero if the type cannot be implemented by the fast path in Read or Write. +func intDataSize(data interface{}) int { + switch data := data.(type) { + case int8, *int8, *uint8: + return 1 + case []int8: + return len(data) + case []uint8: + return len(data) + case int16, *int16, *uint16: + return 2 + case []int16: + return 2 * len(data) + case []uint16: + return 2 * len(data) + case int32, *int32, *uint32: + return 4 + case []int32: + return 4 * len(data) + case []uint32: + return 4 * len(data) + case int64, *int64, *uint64: + return 8 + case []int64: + return 8 * len(data) + case []uint64: + return 8 * len(data) + } + return 0 +} diff --git a/plugins/system/ps/disk/disk.go b/plugins/system/ps/disk/disk.go new file mode 100644 index 000000000..fc2f79694 --- /dev/null +++ b/plugins/system/ps/disk/disk.go @@ -0,0 +1,51 @@ +package disk + +import ( + "encoding/json" +) + +type DiskUsageStat struct { + Path string `json:"path"` + Total uint64 `json:"total"` + Free uint64 `json:"free"` + Used uint64 `json:"used"` + UsedPercent float64 `json:"used_percent"` + InodesTotal uint64 `json:"inodes_total"` + InodesUsed uint64 `json:"inodes_used"` + InodesFree uint64 `json:"inodes_free"` + InodesUsedPercent float64 `json:"inodes_used_percent"` +} + +type DiskPartitionStat struct { + Device string `json:"device"` + Mountpoint string `json:"mountpoint"` + Fstype string `json:"fstype"` + Opts string `json:"opts"` +} + +type DiskIOCountersStat struct { + ReadCount uint64 `json:"read_count"` + WriteCount uint64 `json:"write_count"` + ReadBytes uint64 `json:"read_bytes"` + WriteBytes uint64 `json:"write_bytes"` + ReadTime uint64 `json:"read_time"` + WriteTime uint64 `json:"write_time"` + Name string `json:"name"` + IoTime uint64 `json:"io_time"` + SerialNumber string `json:"serial_number"` +} + +func (d DiskUsageStat) String() string { + s, _ := json.Marshal(d) + return string(s) +} + +func (d DiskPartitionStat) String() string { + s, _ := json.Marshal(d) + return string(s) +} + +func (d DiskIOCountersStat) String() string { + s, _ := json.Marshal(d) + return string(s) +} diff --git a/plugins/system/ps/disk/disk_darwin.go b/plugins/system/ps/disk/disk_darwin.go new file mode 100644 index 000000000..3aa85c89a --- /dev/null +++ b/plugins/system/ps/disk/disk_darwin.go @@ -0,0 +1,100 @@ +// +build darwin + +package disk + +import ( + "syscall" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +func DiskPartitions(all bool) ([]DiskPartitionStat, error) { + var ret []DiskPartitionStat + + count, err := Getfsstat(nil, MntWait) + if err != nil { + return ret, err + } + fs := make([]Statfs_t, count) + _, err = Getfsstat(fs, MntWait) + for _, stat := range fs { + opts := "rw" + if stat.Flags&MntReadOnly != 0 { + opts = "ro" + } + if stat.Flags&MntSynchronous != 0 { + opts += ",sync" + } + if stat.Flags&MntNoExec != 0 { + opts += ",noexec" + } + if stat.Flags&MntNoSuid != 0 { + opts += ",nosuid" + } + if stat.Flags&MntUnion != 0 { + opts += ",union" + } + if stat.Flags&MntAsync != 0 { + opts += ",async" + } + if stat.Flags&MntSuidDir != 0 { + opts += ",suiddir" + } + if stat.Flags&MntSoftDep != 0 { + opts += ",softdep" + } + if stat.Flags&MntNoSymFollow != 0 { + opts += ",nosymfollow" + } + if stat.Flags&MntGEOMJournal != 0 { + opts += ",gjounalc" + } + if stat.Flags&MntMultilabel != 0 { + opts += ",multilabel" + } + if stat.Flags&MntACLs != 0 { + opts += ",acls" + } + if stat.Flags&MntNoATime != 0 { + opts += ",noattime" + } + if stat.Flags&MntClusterRead != 0 { + opts += ",nocluster" + } + if stat.Flags&MntClusterWrite != 0 { + opts += ",noclusterw" + } + if stat.Flags&MntNFS4ACLs != 0 { + opts += ",nfs4acls" + } + d := DiskPartitionStat{ + Device: common.IntToString(stat.Mntfromname[:]), + Mountpoint: common.IntToString(stat.Mntonname[:]), + Fstype: common.IntToString(stat.Fstypename[:]), + Opts: opts, + } + ret = append(ret, d) + } + + return ret, nil +} + +func DiskIOCounters() (map[string]DiskIOCountersStat, error) { + return nil, common.NotImplementedError +} + +func Getfsstat(buf []Statfs_t, flags int) (n int, err error) { + var _p0 unsafe.Pointer + var bufsize uintptr + if len(buf) > 0 { + _p0 = unsafe.Pointer(&buf[0]) + bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf)) + } + r0, _, e1 := syscall.Syscall(SYS_GETFSSTAT64, uintptr(_p0), bufsize, uintptr(flags)) + n = int(r0) + if e1 != 0 { + err = e1 + } + return +} diff --git a/plugins/system/ps/disk/disk_darwin_amd64.go b/plugins/system/ps/disk/disk_darwin_amd64.go new file mode 100644 index 000000000..f58e21312 --- /dev/null +++ b/plugins/system/ps/disk/disk_darwin_amd64.go @@ -0,0 +1,58 @@ +// +build darwin +// +build amd64 + +package disk + +const ( + MntWait = 1 + MfsNameLen = 15 /* length of fs type name, not inc. nul */ + MNameLen = 90 /* length of buffer for returned name */ + + MFSTYPENAMELEN = 16 /* length of fs type name including null */ + MAXPATHLEN = 1024 + MNAMELEN = MAXPATHLEN + + SYS_GETFSSTAT64 = 347 +) + +type Fsid struct{ val [2]int32 } /* file system id type */ +type uid_t int32 + +// sys/mount.h +const ( + MntReadOnly = 0x00000001 /* read only filesystem */ + MntSynchronous = 0x00000002 /* filesystem written synchronously */ + MntNoExec = 0x00000004 /* can't exec from filesystem */ + MntNoSuid = 0x00000008 /* don't honor setuid bits on fs */ + MntUnion = 0x00000020 /* union with underlying filesystem */ + MntAsync = 0x00000040 /* filesystem written asynchronously */ + MntSuidDir = 0x00100000 /* special handling of SUID on dirs */ + MntSoftDep = 0x00200000 /* soft updates being done */ + MntNoSymFollow = 0x00400000 /* do not follow symlinks */ + MntGEOMJournal = 0x02000000 /* GEOM journal support enabled */ + MntMultilabel = 0x04000000 /* MAC support for individual objects */ + MntACLs = 0x08000000 /* ACL support enabled */ + MntNoATime = 0x10000000 /* disable update of file access time */ + MntClusterRead = 0x40000000 /* disable cluster read */ + MntClusterWrite = 0x80000000 /* disable cluster write */ + MntNFS4ACLs = 0x00000010 +) + +type Statfs_t struct { + Bsize uint32 + Iosize int32 + Blocks uint64 + Bfree uint64 + Bavail uint64 + Files uint64 + Ffree uint64 + Fsid Fsid + Owner uint32 + Type uint32 + Flags uint32 + Fssubtype uint32 + Fstypename [16]int8 + Mntonname [1024]int8 + Mntfromname [1024]int8 + Reserved [8]uint32 +} diff --git a/plugins/system/ps/disk/disk_freebsd.go b/plugins/system/ps/disk/disk_freebsd.go new file mode 100644 index 000000000..56aa14fa9 --- /dev/null +++ b/plugins/system/ps/disk/disk_freebsd.go @@ -0,0 +1,173 @@ +// +build freebsd + +package disk + +import ( + "bytes" + "encoding/binary" + "strconv" + "syscall" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +const ( + CTLKern = 1 + KernDevstat = 773 + KernDevstatAll = 772 +) + +func DiskPartitions(all bool) ([]DiskPartitionStat, error) { + var ret []DiskPartitionStat + + // get length + count, err := syscall.Getfsstat(nil, MNT_WAIT) + if err != nil { + return ret, err + } + + fs := make([]Statfs, count) + _, err = Getfsstat(fs, MNT_WAIT) + + for _, stat := range fs { + opts := "rw" + if stat.Flags&MNT_RDONLY != 0 { + opts = "ro" + } + if stat.Flags&MNT_SYNCHRONOUS != 0 { + opts += ",sync" + } + if stat.Flags&MNT_NOEXEC != 0 { + opts += ",noexec" + } + if stat.Flags&MNT_NOSUID != 0 { + opts += ",nosuid" + } + if stat.Flags&MNT_UNION != 0 { + opts += ",union" + } + if stat.Flags&MNT_ASYNC != 0 { + opts += ",async" + } + if stat.Flags&MNT_SUIDDIR != 0 { + opts += ",suiddir" + } + if stat.Flags&MNT_SOFTDEP != 0 { + opts += ",softdep" + } + if stat.Flags&MNT_NOSYMFOLLOW != 0 { + opts += ",nosymfollow" + } + if stat.Flags&MNT_GJOURNAL != 0 { + opts += ",gjounalc" + } + if stat.Flags&MNT_MULTILABEL != 0 { + opts += ",multilabel" + } + if stat.Flags&MNT_ACLS != 0 { + opts += ",acls" + } + if stat.Flags&MNT_NOATIME != 0 { + opts += ",noattime" + } + if stat.Flags&MNT_NOCLUSTERR != 0 { + opts += ",nocluster" + } + if stat.Flags&MNT_NOCLUSTERW != 0 { + opts += ",noclusterw" + } + if stat.Flags&MNT_NFS4ACLS != 0 { + opts += ",nfs4acls" + } + + d := DiskPartitionStat{ + Device: common.IntToString(stat.Mntfromname[:]), + Mountpoint: common.IntToString(stat.Mntonname[:]), + Fstype: common.IntToString(stat.Fstypename[:]), + Opts: opts, + } + ret = append(ret, d) + } + + return ret, nil +} + +func DiskIOCounters() (map[string]DiskIOCountersStat, error) { + // statinfo->devinfo->devstat + // /usr/include/devinfo.h + + // sysctl.sysctl ('kern.devstat.all', 0) + ret := make(map[string]DiskIOCountersStat) + mib := []int32{CTLKern, KernDevstat, KernDevstatAll} + + buf, length, err := common.CallSyscall(mib) + if err != nil { + return nil, err + } + + ds := Devstat{} + devstatLen := int(unsafe.Sizeof(ds)) + count := int(length / uint64(devstatLen)) + + buf = buf[8:] // devstat.all has version in the head. + // parse buf to Devstat + for i := 0; i < count; i++ { + b := buf[i*devstatLen : i*devstatLen+devstatLen] + d, err := parseDevstat(b) + if err != nil { + continue + } + un := strconv.Itoa(int(d.Unit_number)) + name := common.IntToString(d.Device_name[:]) + un + + ds := DiskIOCountersStat{ + ReadCount: d.Operations[DEVSTAT_READ], + WriteCount: d.Operations[DEVSTAT_WRITE], + ReadBytes: d.Bytes[DEVSTAT_READ], + WriteBytes: d.Bytes[DEVSTAT_WRITE], + ReadTime: d.Duration[DEVSTAT_READ].Compute(), + WriteTime: d.Duration[DEVSTAT_WRITE].Compute(), + Name: name, + } + ret[name] = ds + } + + return ret, nil +} + +func (b Bintime) Compute() uint64 { + BINTIME_SCALE := 5.42101086242752217003726400434970855712890625e-20 + return uint64(b.Sec) + b.Frac*uint64(BINTIME_SCALE) +} + +// BT2LD(time) ((long double)(time).sec + (time).frac * BINTIME_SCALE) + +// Getfsstat is borrowed from pkg/syscall/syscall_freebsd.go +// change Statfs_t to Statfs in order to get more information +func Getfsstat(buf []Statfs, flags int) (n int, err error) { + var _p0 unsafe.Pointer + var bufsize uintptr + if len(buf) > 0 { + _p0 = unsafe.Pointer(&buf[0]) + bufsize = unsafe.Sizeof(Statfs{}) * uintptr(len(buf)) + } + r0, _, e1 := syscall.Syscall(syscall.SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags)) + n = int(r0) + if e1 != 0 { + err = e1 + } + return +} + +func parseDevstat(buf []byte) (Devstat, error) { + var ds Devstat + br := bytes.NewReader(buf) + // err := binary.Read(br, binary.LittleEndian, &ds) + err := Read(br, binary.LittleEndian, &ds) + if err != nil { + return ds, err + } + + return ds, nil +} diff --git a/plugins/system/ps/disk/disk_freebsd_amd64.go b/plugins/system/ps/disk/disk_freebsd_amd64.go new file mode 100644 index 000000000..bbae1595c --- /dev/null +++ b/plugins/system/ps/disk/disk_freebsd_amd64.go @@ -0,0 +1,111 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package disk + +const ( + sizeofPtr = 0x8 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x8 + sizeofLongLong = 0x8 + sizeofLongDouble = 0x8 + + DEVSTAT_NO_DATA = 0x00 + DEVSTAT_READ = 0x01 + DEVSTAT_WRITE = 0x02 + DEVSTAT_FREE = 0x03 + + MNT_RDONLY = 0x00000001 + MNT_SYNCHRONOUS = 0x00000002 + MNT_NOEXEC = 0x00000004 + MNT_NOSUID = 0x00000008 + MNT_UNION = 0x00000020 + MNT_ASYNC = 0x00000040 + MNT_SUIDDIR = 0x00100000 + MNT_SOFTDEP = 0x00200000 + MNT_NOSYMFOLLOW = 0x00400000 + MNT_GJOURNAL = 0x02000000 + MNT_MULTILABEL = 0x04000000 + MNT_ACLS = 0x08000000 + MNT_NOATIME = 0x10000000 + MNT_NOCLUSTERR = 0x40000000 + MNT_NOCLUSTERW = 0x80000000 + MNT_NFS4ACLS = 0x00000010 + + MNT_WAIT = 1 + MNT_NOWAIT = 2 + MNT_LAZY = 3 + MNT_SUSPEND = 4 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int64 + _C_long_long int64 + _C_long_double int64 +) + +type Statfs struct { + Version uint32 + Type uint32 + Flags uint64 + Bsize uint64 + Iosize uint64 + Blocks uint64 + Bfree uint64 + Bavail int64 + Files uint64 + Ffree int64 + Syncwrites uint64 + Asyncwrites uint64 + Syncreads uint64 + Asyncreads uint64 + Spare [10]uint64 + Namemax uint32 + Owner uint32 + Fsid Fsid + Charspare [80]int8 + Fstypename [16]int8 + Mntfromname [88]int8 + Mntonname [88]int8 +} +type Fsid struct { + Val [2]int32 +} + +type Devstat struct { + Sequence0 uint32 + Allocated int32 + Start_count uint32 + End_count uint32 + Busy_from Bintime + Dev_links _Ctype_struct___0 + Device_number uint32 + Device_name [16]int8 + Unit_number int32 + Bytes [4]uint64 + Operations [4]uint64 + Duration [4]Bintime + Busy_time Bintime + Creation_time Bintime + Block_size uint32 + Pad_cgo_0 [4]byte + Tag_types [3]uint64 + Flags uint32 + Device_type uint32 + Priority uint32 + Pad_cgo_1 [4]byte + Id *byte + Sequence1 uint32 + Pad_cgo_2 [4]byte +} +type Bintime struct { + Sec int64 + Frac uint64 +} + +type _Ctype_struct___0 struct { + Empty uint64 +} diff --git a/plugins/system/ps/disk/disk_linux.go b/plugins/system/ps/disk/disk_linux.go new file mode 100644 index 000000000..2ec2fcb31 --- /dev/null +++ b/plugins/system/ps/disk/disk_linux.go @@ -0,0 +1,122 @@ +// +build linux + +package disk + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +const ( + SectorSize = 512 +) + +// Get disk partitions. +// should use setmntent(3) but this implement use /etc/mtab file +func DiskPartitions(all bool) ([]DiskPartitionStat, error) { + + filename := "/etc/mtab" + lines, err := common.ReadLines(filename) + if err != nil { + return nil, err + } + + ret := make([]DiskPartitionStat, 0, len(lines)) + + for _, line := range lines { + fields := strings.Fields(line) + d := DiskPartitionStat{ + Device: fields[0], + Mountpoint: fields[1], + Fstype: fields[2], + Opts: fields[3], + } + ret = append(ret, d) + } + + return ret, nil +} + +func DiskIOCounters() (map[string]DiskIOCountersStat, error) { + filename := "/proc/diskstats" + lines, err := common.ReadLines(filename) + if err != nil { + return nil, err + } + ret := make(map[string]DiskIOCountersStat, 0) + empty := DiskIOCountersStat{} + + for _, line := range lines { + fields := strings.Fields(line) + name := fields[2] + reads, err := strconv.ParseUint((fields[3]), 10, 64) + if err != nil { + return ret, err + } + rbytes, err := strconv.ParseUint((fields[5]), 10, 64) + if err != nil { + return ret, err + } + rtime, err := strconv.ParseUint((fields[6]), 10, 64) + if err != nil { + return ret, err + } + writes, err := strconv.ParseUint((fields[7]), 10, 64) + if err != nil { + return ret, err + } + wbytes, err := strconv.ParseUint((fields[9]), 10, 64) + if err != nil { + return ret, err + } + wtime, err := strconv.ParseUint((fields[10]), 10, 64) + if err != nil { + return ret, err + } + iotime, err := strconv.ParseUint((fields[12]), 10, 64) + if err != nil { + return ret, err + } + d := DiskIOCountersStat{ + ReadBytes: rbytes * SectorSize, + WriteBytes: wbytes * SectorSize, + ReadCount: reads, + WriteCount: writes, + ReadTime: rtime, + WriteTime: wtime, + IoTime: iotime, + } + if d == empty { + continue + } + d.Name = name + + d.SerialNumber = GetDiskSerialNumber(name) + ret[name] = d + } + return ret, nil +} + +func GetDiskSerialNumber(name string) string { + n := fmt.Sprintf("--name=%s", name) + out, err := exec.Command("/sbin/udevadm", "info", "--query=property", n).Output() + + // does not return error, just an empty string + if err != nil { + return "" + } + lines := strings.Split(string(out), "\n") + for _, line := range lines { + values := strings.Split(line, "=") + if len(values) < 2 || values[0] != "ID_SERIAL" { + // only get ID_SERIAL, not ID_SERIAL_SHORT + continue + } + return values[1] + } + return "" +} diff --git a/plugins/system/ps/disk/disk_test.go b/plugins/system/ps/disk/disk_test.go new file mode 100644 index 000000000..d83160e33 --- /dev/null +++ b/plugins/system/ps/disk/disk_test.go @@ -0,0 +1,97 @@ +package disk + +import ( + "fmt" + "runtime" + "testing" +) + +func TestDisk_usage(t *testing.T) { + path := "/" + if runtime.GOOS == "windows" { + path = "C:" + } + v, err := DiskUsage(path) + if err != nil { + t.Errorf("error %v", err) + } + if v.Path != path { + t.Errorf("error %v", err) + } +} + +func TestDisk_partitions(t *testing.T) { + ret, err := DiskPartitions(false) + if err != nil || len(ret) == 0 { + t.Errorf("error %v", err) + } + empty := DiskPartitionStat{} + for _, disk := range ret { + if disk == empty { + t.Errorf("Could not get device info %v", disk) + } + } +} + +func TestDisk_io_counters(t *testing.T) { + ret, err := DiskIOCounters() + if err != nil { + t.Errorf("error %v", err) + } + if len(ret) == 0 { + t.Errorf("ret is empty", ret) + } + empty := DiskIOCountersStat{} + for part, io := range ret { + fmt.Println(io) + if io == empty { + t.Errorf("io_counter error %v, %v", part, io) + } + } +} + +func TestDiskUsageStat_String(t *testing.T) { + v := DiskUsageStat{ + Path: "/", + Total: 1000, + Free: 2000, + Used: 3000, + UsedPercent: 50.1, + InodesTotal: 4000, + InodesUsed: 5000, + InodesFree: 6000, + InodesUsedPercent: 49.1, + } + e := `{"path":"/","total":1000,"free":2000,"used":3000,"used_percent":50.1,"inodes_total":4000,"inodes_used":5000,"inodes_free":6000,"inodes_used_percent":49.1}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("DiskUsageStat string is invalid: %v", v) + } +} + +func TestDiskPartitionStat_String(t *testing.T) { + v := DiskPartitionStat{ + Device: "sd01", + Mountpoint: "/", + Fstype: "ext4", + Opts: "ro", + } + e := `{"device":"sd01","mountpoint":"/","fstype":"ext4","opts":"ro"}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("DiskUsageStat string is invalid: %v", v) + } +} + +func TestDiskIOCountersStat_String(t *testing.T) { + v := DiskIOCountersStat{ + Name: "sd01", + ReadCount: 100, + WriteCount: 200, + ReadBytes: 300, + WriteBytes: 400, + SerialNumber: "SERIAL", + } + e := `{"read_count":100,"write_count":200,"read_bytes":300,"write_bytes":400,"read_time":0,"write_time":0,"name":"sd01","io_time":0,"serial_number":"SERIAL"}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("DiskUsageStat string is invalid: %v", v) + } +} diff --git a/plugins/system/ps/disk/disk_unix.go b/plugins/system/ps/disk/disk_unix.go new file mode 100644 index 000000000..bc53d404b --- /dev/null +++ b/plugins/system/ps/disk/disk_unix.go @@ -0,0 +1,30 @@ +// +build freebsd linux darwin + +package disk + +import "syscall" + +func DiskUsage(path string) (*DiskUsageStat, error) { + stat := syscall.Statfs_t{} + err := syscall.Statfs(path, &stat) + if err != nil { + return nil, err + } + + bsize := stat.Bsize + + ret := &DiskUsageStat{ + Path: path, + Total: (uint64(stat.Blocks) * uint64(bsize)), + Free: (uint64(stat.Bfree) * uint64(bsize)), + InodesTotal: (uint64(stat.Files)), + InodesFree: (uint64(stat.Ffree)), + } + + ret.InodesUsed = (ret.InodesTotal - ret.InodesFree) + ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0 + ret.Used = (ret.Total - ret.Free) + ret.UsedPercent = (float64(ret.Used) / float64(ret.Total)) * 100.0 + + return ret, nil +} diff --git a/plugins/system/ps/disk/disk_windows.go b/plugins/system/ps/disk/disk_windows.go new file mode 100644 index 000000000..b8de918e2 --- /dev/null +++ b/plugins/system/ps/disk/disk_windows.go @@ -0,0 +1,196 @@ +// +build windows + +package disk + +import ( + "bytes" + "fmt" + "syscall" + "time" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +var ( + procGetDiskFreeSpaceExW = common.Modkernel32.NewProc("GetDiskFreeSpaceExW") + procGetLogicalDriveStringsW = common.Modkernel32.NewProc("GetLogicalDriveStringsW") + procGetDriveType = common.Modkernel32.NewProc("GetDriveTypeW") + provGetVolumeInformation = common.Modkernel32.NewProc("GetVolumeInformationW") +) + +var ( + FileFileCompression = int64(16) // 0x00000010 + FileReadOnlyVolume = int64(524288) // 0x00080000 +) + +const WaitMSec = 500 + +func DiskUsage(path string) (DiskUsageStat, error) { + ret := DiskUsageStat{} + + ret.Path = path + lpFreeBytesAvailable := int64(0) + lpTotalNumberOfBytes := int64(0) + lpTotalNumberOfFreeBytes := int64(0) + diskret, _, err := procGetDiskFreeSpaceExW.Call( + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), + uintptr(unsafe.Pointer(&lpFreeBytesAvailable)), + uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)), + uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes))) + if diskret == 0 { + return ret, err + } + ret.Total = uint64(lpTotalNumberOfBytes) + // ret.Free = uint64(lpFreeBytesAvailable) // python psutil does not use this + ret.Free = uint64(lpTotalNumberOfFreeBytes) + ret.Used = ret.Total - ret.Free + ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0 + + //TODO: implement inodes stat + ret.InodesTotal = 0 + ret.InodesUsed = 0 + ret.InodesFree = 0 + ret.InodesUsedPercent = 0.0 + return ret, nil +} + +func DiskPartitions(all bool) ([]DiskPartitionStat, error) { + var ret []DiskPartitionStat + lpBuffer := make([]byte, 254) + diskret, _, err := procGetLogicalDriveStringsW.Call( + uintptr(len(lpBuffer)), + uintptr(unsafe.Pointer(&lpBuffer[0]))) + if diskret == 0 { + return ret, err + } + for _, v := range lpBuffer { + if v >= 65 && v <= 90 { + path := string(v) + ":" + if path == "A:" || path == "B:" { // skip floppy drives + continue + } + typepath, _ := syscall.UTF16PtrFromString(path) + typeret, _, _ := procGetDriveType.Call(uintptr(unsafe.Pointer(typepath))) + if typeret == 0 { + return ret, syscall.GetLastError() + } + // 2: DRIVE_REMOVABLE 3: DRIVE_FIXED 5: DRIVE_CDROM + + if typeret == 2 || typeret == 3 || typeret == 5 { + lpVolumeNameBuffer := make([]byte, 256) + lpVolumeSerialNumber := int64(0) + lpMaximumComponentLength := int64(0) + lpFileSystemFlags := int64(0) + lpFileSystemNameBuffer := make([]byte, 256) + volpath, _ := syscall.UTF16PtrFromString(string(v) + ":/") + driveret, _, err := provGetVolumeInformation.Call( + uintptr(unsafe.Pointer(volpath)), + uintptr(unsafe.Pointer(&lpVolumeNameBuffer[0])), + uintptr(len(lpVolumeNameBuffer)), + uintptr(unsafe.Pointer(&lpVolumeSerialNumber)), + uintptr(unsafe.Pointer(&lpMaximumComponentLength)), + uintptr(unsafe.Pointer(&lpFileSystemFlags)), + uintptr(unsafe.Pointer(&lpFileSystemNameBuffer[0])), + uintptr(len(lpFileSystemNameBuffer))) + if driveret == 0 { + return ret, err + } + opts := "rw" + if lpFileSystemFlags&FileReadOnlyVolume != 0 { + opts = "ro" + } + if lpFileSystemFlags&FileFileCompression != 0 { + opts += ".compress" + } + + d := DiskPartitionStat{ + Mountpoint: path, + Device: path, + Fstype: string(bytes.Replace(lpFileSystemNameBuffer, []byte("\x00"), []byte(""), -1)), + Opts: opts, + } + ret = append(ret, d) + } + } + } + return ret, nil +} + +func DiskIOCounters() (map[string]DiskIOCountersStat, error) { + ret := make(map[string]DiskIOCountersStat, 0) + query, err := common.CreateQuery() + if err != nil { + return ret, err + } + + drivebuf := make([]byte, 256) + r, _, err := procGetLogicalDriveStringsW.Call( + uintptr(len(drivebuf)), + uintptr(unsafe.Pointer(&drivebuf[0]))) + + if r == 0 { + return ret, err + } + + drivemap := make(map[string][]*common.CounterInfo, 0) + for _, v := range drivebuf { + if v >= 65 && v <= 90 { + drive := string(v) + r, _, err = procGetDriveType.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive + `:\`)))) + if r != common.DRIVE_FIXED { + continue + } + drivemap[drive] = make([]*common.CounterInfo, 0, 2) + var counter *common.CounterInfo + + counter, err = common.CreateCounter(query, + "read", + fmt.Sprintf(`\PhysicalDisk(0 %s:)\Disk Reads/sec`, drive)) + if err != nil { + return nil, err + } + drivemap[drive] = append(drivemap[drive], counter) + counter, err = common.CreateCounter(query, + "write", + fmt.Sprintf(`\PhysicalDisk(0 %s:)\Disk Writes/sec`, drive)) + if err != nil { + return nil, err + } + drivemap[drive] = append(drivemap[drive], counter) + } + } + r, _, err = common.PdhCollectQueryData.Call(uintptr(query)) + if r != 0 && err != nil { + return nil, err + } + time.Sleep(time.Duration(WaitMSec) * time.Millisecond) + r, _, err = common.PdhCollectQueryData.Call(uintptr(query)) + if r != 0 && err != nil { + return nil, err + } + + for drive, counters := range drivemap { + stat := DiskIOCountersStat{} + for _, v := range counters { + var fmtValue common.PDH_FMT_COUNTERVALUE_LARGE + r, _, err := common.PdhGetFormattedCounterValue.Call(uintptr(v.Counter), common.PDH_FMT_LARGE, uintptr(0), uintptr(unsafe.Pointer(&fmtValue))) + if r != 0 && r != common.PDH_INVALID_DATA { + return nil, err + } + + switch v.PostName { + case "read": + stat.ReadCount = uint64(fmtValue.LargeValue) + case "write": + stat.WriteCount = uint64(fmtValue.LargeValue) + default: + return ret, fmt.Errorf("unknown postname: %s", v.PostName) + } + stat.Name = drive + } + ret[drive] = stat + } + + return ret, nil +} diff --git a/plugins/system/ps/disk/types_freebsd.go b/plugins/system/ps/disk/types_freebsd.go new file mode 100644 index 000000000..44869042f --- /dev/null +++ b/plugins/system/ps/disk/types_freebsd.go @@ -0,0 +1,85 @@ +// +build ignore +// Hand writing: _Ctype_struct___0 + +/* +Input to cgo -godefs. + +*/ + +package disk + +/* +#include +#include +#include + +enum { + sizeofPtr = sizeof(void*), +}; + +// because statinfo has long double snap_time, redefine with changing long long +struct statinfo2 { + long cp_time[CPUSTATES]; + long tk_nin; + long tk_nout; + struct devinfo *dinfo; + long long snap_time; +}; +*/ +import "C" + +// Machine characteristics; for internal use. + +const ( + sizeofPtr = C.sizeofPtr + sizeofShort = C.sizeof_short + sizeofInt = C.sizeof_int + sizeofLong = C.sizeof_long + sizeofLongLong = C.sizeof_longlong + sizeofLongDouble = C.sizeof_longlong + + DEVSTAT_NO_DATA = 0x00 + DEVSTAT_READ = 0x01 + DEVSTAT_WRITE = 0x02 + DEVSTAT_FREE = 0x03 + + // from sys/mount.h + MNT_RDONLY = 0x00000001 /* read only filesystem */ + MNT_SYNCHRONOUS = 0x00000002 /* filesystem written synchronously */ + MNT_NOEXEC = 0x00000004 /* can't exec from filesystem */ + MNT_NOSUID = 0x00000008 /* don't honor setuid bits on fs */ + MNT_UNION = 0x00000020 /* union with underlying filesystem */ + MNT_ASYNC = 0x00000040 /* filesystem written asynchronously */ + MNT_SUIDDIR = 0x00100000 /* special handling of SUID on dirs */ + MNT_SOFTDEP = 0x00200000 /* soft updates being done */ + MNT_NOSYMFOLLOW = 0x00400000 /* do not follow symlinks */ + MNT_GJOURNAL = 0x02000000 /* GEOM journal support enabled */ + MNT_MULTILABEL = 0x04000000 /* MAC support for individual objects */ + MNT_ACLS = 0x08000000 /* ACL support enabled */ + MNT_NOATIME = 0x10000000 /* disable update of file access time */ + MNT_NOCLUSTERR = 0x40000000 /* disable cluster read */ + MNT_NOCLUSTERW = 0x80000000 /* disable cluster write */ + MNT_NFS4ACLS = 0x00000010 + + MNT_WAIT = 1 /* synchronously wait for I/O to complete */ + MNT_NOWAIT = 2 /* start all I/O, but do not wait for it */ + MNT_LAZY = 3 /* push data not written by filesystem syncer */ + MNT_SUSPEND = 4 /* Suspend file system after sync */ + +) + +// Basic types + +type ( + _C_short C.short + _C_int C.int + _C_long C.long + _C_long_long C.longlong + _C_long_double C.longlong +) + +type Statfs C.struct_statfs +type Fsid C.struct_fsid + +type Devstat C.struct_devstat +type Bintime C.struct_bintime diff --git a/plugins/system/ps/doc.go b/plugins/system/ps/doc.go new file mode 100644 index 000000000..6a65fe268 --- /dev/null +++ b/plugins/system/ps/doc.go @@ -0,0 +1 @@ +package gopsutil diff --git a/plugins/system/ps/docker/docker_linux.go b/plugins/system/ps/docker/docker_linux.go new file mode 100644 index 000000000..c8e19fc12 --- /dev/null +++ b/plugins/system/ps/docker/docker_linux.go @@ -0,0 +1,193 @@ +// +build linux + +package docker + +import ( + "encoding/json" + "os/exec" + "path" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" + cpu "github.com/shirou/gopsutil/cpu" +) + +type CgroupMemStat struct { + ContainerID string `json:"container_id"` + Cache uint64 `json:"cache"` + RSS uint64 `json:"rss"` + RSSHuge uint64 `json:"rss_huge"` + MappedFile uint64 `json:"mapped_file"` + Pgpgin uint64 `json:"pgpgin"` + Pgpgout uint64 `json:"pgpgout"` + Pgfault uint64 `json:"pgfault"` + Pgmajfault uint64 `json:"pgmajfault"` + InactiveAnon uint64 `json:"inactive_anon"` + ActiveAnon uint64 `json:"active_anon"` + InctiveFile uint64 `json:"inactive_file"` + ActiveFile uint64 `json:"active_file"` + Unevictable uint64 `json:"unevictable"` + HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit"` + TotalCache uint64 `json:"total_cache"` + TotalRSS uint64 `json:"total_rss"` + TotalRSSHuge uint64 `json:"total_rss_huge"` + TotalMappedFile uint64 `json:"total_mapped_file"` + TotalPgpgIn uint64 `json:"total_pgpgin"` + TotalPgpgOut uint64 `json:"total_pgpgout"` + TotalPgFault uint64 `json:"total_pgfault"` + TotalPgMajFault uint64 `json:"total_pgmajfault"` + TotalInactiveAnon uint64 `json:"total_inactive_anon"` + TotalActiveAnon uint64 `json:"total_active_anon"` + TotalInactiveFile uint64 `json:"total_inactive_file"` + TotalActiveFile uint64 `json:"total_active_file"` + TotalUnevictable uint64 `json:"total_unevictable"` +} + +// GetDockerIDList returnes a list of DockerID. +// This requires certain permission. +func GetDockerIDList() ([]string, error) { + out, err := exec.Command("docker", "ps", "-q", "--no-trunc").Output() + if err != nil { + return []string{}, err + } + lines := strings.Split(string(out), "\n") + ret := make([]string, 0, len(lines)) + + for _, l := range lines { + ret = append(ret, l) + } + + return ret, nil +} + +// CgroupCPU returnes specified cgroup id CPU status. +// containerid is same as docker id if you use docker. +// If you use container via systemd.slice, you could use +// containerid = docker-.scope and base=/sys/fs/cgroup/cpuacct/system.slice/ +func CgroupCPU(containerid string, base string) (*cpu.CPUTimesStat, error) { + if len(base) == 0 { + base = "/sys/fs/cgroup/cpuacct/docker" + } + path := path.Join(base, containerid, "cpuacct.stat") + + lines, err := common.ReadLines(path) + if err != nil { + return nil, err + } + // empty containerid means all cgroup + if len(containerid) == 0 { + containerid = "all" + } + ret := &cpu.CPUTimesStat{CPU: containerid} + for _, line := range lines { + fields := strings.Split(line, " ") + if fields[0] == "user" { + user, err := strconv.ParseFloat(fields[1], 64) + if err == nil { + ret.User = float64(user) + } + } + if fields[0] == "system" { + system, err := strconv.ParseFloat(fields[1], 64) + if err == nil { + ret.System = float64(system) + } + } + } + + return ret, nil +} + +func CgroupCPUDocker(containerid string) (*cpu.CPUTimesStat, error) { + return CgroupCPU(containerid, "/sys/fs/cgroup/cpuacct/docker") +} + +func CgroupMem(containerid string, base string) (*CgroupMemStat, error) { + if len(base) == 0 { + base = "/sys/fs/cgroup/memory/docker" + } + path := path.Join(base, containerid, "memory.stat") + // empty containerid means all cgroup + if len(containerid) == 0 { + containerid = "all" + } + lines, err := common.ReadLines(path) + if err != nil { + return nil, err + } + ret := &CgroupMemStat{ContainerID: containerid} + for _, line := range lines { + fields := strings.Split(line, " ") + v, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + continue + } + switch fields[0] { + case "cache": + ret.Cache = v + case "rss": + ret.RSS = v + case "rss_huge": + ret.RSSHuge = v + case "mapped_file": + ret.MappedFile = v + case "pgpgin": + ret.Pgpgin = v + case "pgpgout": + ret.Pgpgout = v + case "pgfault": + ret.Pgfault = v + case "pgmajfault": + ret.Pgmajfault = v + case "inactive_anon": + ret.InactiveAnon = v + case "active_anon": + ret.ActiveAnon = v + case "inactive_file": + ret.InctiveFile = v + case "active_file": + ret.ActiveFile = v + case "unevictable": + ret.Unevictable = v + case "hierarchical_memory_limit": + ret.HierarchicalMemoryLimit = v + case "total_cache": + ret.TotalCache = v + case "total_rss": + ret.TotalRSS = v + case "total_rss_huge": + ret.TotalRSSHuge = v + case "total_mapped_file": + ret.TotalMappedFile = v + case "total_pgpgin": + ret.TotalPgpgIn = v + case "total_pgpgout": + ret.TotalPgpgOut = v + case "total_pgfault": + ret.TotalPgFault = v + case "total_pgmajfault": + ret.TotalPgMajFault = v + case "total_inactive_anon": + ret.TotalInactiveAnon = v + case "total_active_anon": + ret.TotalActiveAnon = v + case "total_inactive_file": + ret.TotalInactiveFile = v + case "total_active_file": + ret.TotalActiveFile = v + case "total_unevictable": + ret.TotalUnevictable = v + } + } + return ret, nil +} + +func CgroupMemDocker(containerid string) (*CgroupMemStat, error) { + return CgroupMem(containerid, "/sys/fs/cgroup/memory/docker") +} + +func (m CgroupMemStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} diff --git a/plugins/system/ps/docker/docker_linux_test.go b/plugins/system/ps/docker/docker_linux_test.go new file mode 100644 index 000000000..48cedfba7 --- /dev/null +++ b/plugins/system/ps/docker/docker_linux_test.go @@ -0,0 +1,60 @@ +// +build linux + +package docker + +import ( + "testing" +) + +func TestGetDockerIDList(t *testing.T) { + // If there is not docker environment, this test always fail. + // not tested here + /* + _, err := GetDockerIDList() + if err != nil { + t.Errorf("error %v", err) + } + */ +} + +func TestCgroupCPU(t *testing.T) { + v, _ := GetDockerIDList() + for _, id := range v { + v, err := CgroupCPUDocker(id) + if err != nil { + t.Errorf("error %v", err) + } + if v.CPU == "" { + t.Errorf("could not get CgroupCPU %v", v) + } + + } +} + +func TestCgroupCPUInvalidId(t *testing.T) { + _, err := CgroupCPUDocker("bad id") + if err == nil { + t.Error("Expected path does not exist error") + } +} + +func TestCgroupMem(t *testing.T) { + v, _ := GetDockerIDList() + for _, id := range v { + v, err := CgroupMemDocker(id) + if err != nil { + t.Errorf("error %v", err) + } + empty := &CgroupMemStat{} + if v == empty { + t.Errorf("Could not CgroupMemStat %v", v) + } + } +} + +func TestCgroupMemInvalidId(t *testing.T) { + _, err := CgroupMemDocker("bad id") + if err == nil { + t.Error("Expected path does not exist error") + } +} diff --git a/plugins/system/ps/host/host.go b/plugins/system/ps/host/host.go new file mode 100644 index 000000000..523b63461 --- /dev/null +++ b/plugins/system/ps/host/host.go @@ -0,0 +1,37 @@ +package host + +import ( + "encoding/json" +) + +// A HostInfoStat describes the host status. +// This is not in the psutil but it useful. +type HostInfoStat struct { + Hostname string `json:"hostname"` + Uptime uint64 `json:"uptime"` + Procs uint64 `json:"procs"` // number of processes + OS string `json:"os"` // ex: freebsd, linux + Platform string `json:"platform"` // ex: ubuntu, linuxmint + PlatformFamily string `json:"platform_family"` // ex: debian, rhel + PlatformVersion string `json:"platform_version"` + VirtualizationSystem string `json:"virtualization_system"` + VirtualizationRole string `json:"virtualization_role"` // guest or host + +} + +type UserStat struct { + User string `json:"user"` + Terminal string `json:"terminal"` + Host string `json:"host"` + Started int `json:"started"` +} + +func (h HostInfoStat) String() string { + s, _ := json.Marshal(h) + return string(s) +} + +func (u UserStat) String() string { + s, _ := json.Marshal(u) + return string(s) +} diff --git a/plugins/system/ps/host/host_darwin.go b/plugins/system/ps/host/host_darwin.go new file mode 100644 index 000000000..1e6e3c6f5 --- /dev/null +++ b/plugins/system/ps/host/host_darwin.go @@ -0,0 +1,139 @@ +// +build darwin + +package host + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +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 := common.DoSysctrl("kern.boottime") + if err == nil { + // ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014 + v := strings.Replace(values[2], ",", "", 1) + t, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return ret, err + } + ret.Uptime = t + } + + return ret, nil +} + +func BootTime() (uint64, error) { + values, err := common.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 uint64(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 := Utmpx{} + entrySize := int(unsafe.Sizeof(u)) + count := len(buf) / entrySize + + for i := 0; i < count; i++ { + b := buf[i*entrySize : i*entrySize+entrySize] + + var u Utmpx + br := bytes.NewReader(b) + err := binary.Read(br, binary.LittleEndian, &u) + if err != nil { + continue + } + if u.Type != 7 { // skip if not USERPROCESS + continue + } + user := UserStat{ + User: common.IntToString(u.User[:]), + Terminal: common.IntToString(u.Line[:]), + Host: common.IntToString(u.Host[:]), + Started: int(u.Tv.Sec), + } + 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/plugins/system/ps/host/host_darwin_amd64.go b/plugins/system/ps/host/host_darwin_amd64.go new file mode 100644 index 000000000..3ea52d527 --- /dev/null +++ b/plugins/system/ps/host/host_darwin_amd64.go @@ -0,0 +1,19 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_darwin.go + +package host + +type Utmpx struct { + User [256]int8 + Id [4]int8 + Line [32]int8 + Pid int32 + Type int16 + Pad_cgo_0 [6]byte + Tv Timeval + Host [256]int8 + Pad [16]uint32 +} +type Timeval struct { + Sec int32 +} diff --git a/plugins/system/ps/host/host_freebsd.go b/plugins/system/ps/host/host_freebsd.go new file mode 100644 index 000000000..5b955b42b --- /dev/null +++ b/plugins/system/ps/host/host_freebsd.go @@ -0,0 +1,143 @@ +// +build freebsd + +package host + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +const ( + UTNameSize = 16 /* see MAXLOGNAME in */ + UTLineSize = 8 + UTHostSize = 16 +) + +func HostInfo() (*HostInfoStat, error) { + ret := &HostInfoStat{ + OS: runtime.GOOS, + PlatformFamily: "freebsd", + } + + 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 := common.DoSysctrl("kern.boottime") + if err == nil { + // ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014 + v := strings.Replace(values[2], ",", "", 1) + t, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return ret, err + } + ret.Uptime = t + } + + return ret, nil +} + +func BootTime() (int64, error) { + values, err := common.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/utmp" + 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 := Utmp{} + entrySize := int(unsafe.Sizeof(u)) + count := len(buf) / entrySize + + for i := 0; i < count; i++ { + b := buf[i*entrySize : i*entrySize+entrySize] + + var u Utmp + br := bytes.NewReader(b) + err := binary.Read(br, binary.LittleEndian, &u) + if err != nil || u.Time == 0 { + continue + } + user := UserStat{ + User: common.IntToString(u.Name[:]), + Terminal: common.IntToString(u.Line[:]), + Host: common.IntToString(u.Host[:]), + Started: int(u.Time), + } + + 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/plugins/system/ps/host/host_freebsd_amd64.go b/plugins/system/ps/host/host_freebsd_amd64.go new file mode 100644 index 000000000..7706cbdea --- /dev/null +++ b/plugins/system/ps/host/host_freebsd_amd64.go @@ -0,0 +1,28 @@ +// +build freebsd +// +build amd64 +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs host/types_freebsd.go + +package host + +const ( + sizeofPtr = 0x8 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x8 + sizeofLongLong = 0x8 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int64 + _C_long_long int64 +) + +type Utmp struct { + Line [8]int8 + Name [16]int8 + Host [16]int8 + Time int32 +} diff --git a/plugins/system/ps/host/host_linux.go b/plugins/system/ps/host/host_linux.go new file mode 100644 index 000000000..1a6926980 --- /dev/null +++ b/plugins/system/ps/host/host_linux.go @@ -0,0 +1,362 @@ +// +build linux + +package host + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + "syscall" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +type LSB struct { + ID string + Release string + Codename string + Description string +} + +func HostInfo() (*HostInfoStat, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + + ret := &HostInfoStat{ + Hostname: hostname, + OS: runtime.GOOS, + } + + 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 + } + uptime, err := BootTime() + if err == nil { + ret.Uptime = uptime + } + + return ret, nil +} + +func BootTime() (uint64, error) { + sysinfo := &syscall.Sysinfo_t{} + if err := syscall.Sysinfo(sysinfo); err != nil { + return 0, err + } + return uint64(sysinfo.Uptime), nil +} + +func Users() ([]UserStat, error) { + utmpfile := "/var/run/utmp" + + file, err := os.Open(utmpfile) + if err != nil { + return nil, err + } + + buf, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + + u := utmp{} + entrySize := int(unsafe.Sizeof(u)) + count := len(buf) / entrySize + + ret := make([]UserStat, 0, count) + + for i := 0; i < count; i++ { + b := buf[i*entrySize : i*entrySize+entrySize] + + var u utmp + br := bytes.NewReader(b) + err := binary.Read(br, binary.LittleEndian, &u) + if err != nil { + continue + } + user := UserStat{ + User: common.IntToString(u.User[:]), + Terminal: common.IntToString(u.Line[:]), + Host: common.IntToString(u.Host[:]), + Started: int(u.Tv.TvSec), + } + ret = append(ret, user) + } + + return ret, nil + +} + +func getLSB() (*LSB, error) { + ret := &LSB{} + if common.PathExists("/etc/lsb-release") { + contents, err := common.ReadLines("/etc/lsb-release") + if err != nil { + return ret, err // return empty + } + for _, line := range contents { + field := strings.Split(line, "=") + if len(field) < 2 { + continue + } + switch field[0] { + case "DISTRIB_ID": + ret.ID = field[1] + case "DISTRIB_RELEASE": + ret.Release = field[1] + case "DISTRIB_CODENAME": + ret.Codename = field[1] + case "DISTRIB_DESCRIPTION": + ret.Description = field[1] + } + } + } else if common.PathExists("/usr/bin/lsb_release") { + out, err := exec.Command("/usr/bin/lsb_release").Output() + if err != nil { + return ret, err + } + for _, line := range strings.Split(string(out), "\n") { + field := strings.Split(line, ":") + if len(field) < 2 { + continue + } + switch field[0] { + case "Distributor ID": + ret.ID = field[1] + case "Release": + ret.Release = field[1] + case "Codename": + ret.Codename = field[1] + case "Description": + ret.Description = field[1] + } + } + + } + + return ret, nil +} + +func GetPlatformInformation() (platform string, family string, version string, err error) { + + lsb, err := getLSB() + if err != nil { + lsb = &LSB{} + } + + if common.PathExists("/etc/oracle-release") { + platform = "oracle" + contents, err := common.ReadLines("/etc/oracle-release") + if err == nil { + version = getRedhatishVersion(contents) + } + } else if common.PathExists("/etc/enterprise-release") { + platform = "oracle" + contents, err := common.ReadLines("/etc/enterprise-release") + if err == nil { + version = getRedhatishVersion(contents) + } + } else if common.PathExists("/etc/debian_version") { + if lsb.ID == "Ubuntu" { + platform = "ubuntu" + version = lsb.Release + } else if lsb.ID == "LinuxMint" { + platform = "linuxmint" + version = lsb.Release + } else { + if common.PathExists("/usr/bin/raspi-config") { + platform = "raspbian" + } else { + platform = "debian" + } + contents, err := common.ReadLines("/etc/debian_version") + if err == nil { + version = contents[0] + } + } + } else if common.PathExists("/etc/redhat-release") { + contents, err := common.ReadLines("/etc/redhat-release") + if err == nil { + version = getRedhatishVersion(contents) + platform = getRedhatishPlatform(contents) + } + } else if common.PathExists("/etc/system-release") { + contents, err := common.ReadLines("/etc/system-release") + if err == nil { + version = getRedhatishVersion(contents) + platform = getRedhatishPlatform(contents) + } + } else if common.PathExists("/etc/gentoo-release") { + platform = "gentoo" + contents, err := common.ReadLines("/etc/gentoo-release") + if err == nil { + version = getRedhatishVersion(contents) + } + // TODO: suse detection + // TODO: slackware detecion + } else if common.PathExists("/etc/arch-release") { + platform = "arch" + // TODO: exherbo detection + } else if lsb.ID == "RedHat" { + platform = "redhat" + version = lsb.Release + } else if lsb.ID == "Amazon" { + platform = "amazon" + version = lsb.Release + } else if lsb.ID == "ScientificSL" { + platform = "scientific" + version = lsb.Release + } else if lsb.ID == "XenServer" { + platform = "xenserver" + version = lsb.Release + } else if lsb.ID != "" { + platform = strings.ToLower(lsb.ID) + version = lsb.Release + } + + switch platform { + case "debian", "ubuntu", "linuxmint", "raspbian": + family = "debian" + case "fedora": + family = "fedora" + case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm": + family = "rhel" + case "suse": + family = "suse" + case "gentoo": + family = "gentoo" + case "slackware": + family = "slackware" + case "arch": + family = "arch" + case "exherbo": + family = "exherbo" + } + + return platform, family, version, nil + +} + +func getRedhatishVersion(contents []string) string { + c := strings.ToLower(strings.Join(contents, "")) + + if strings.Contains(c, "rawhide") { + return "rawhide" + } + if matches := regexp.MustCompile(`release (\d[\d.]*)`).FindStringSubmatch(c); matches != nil { + return matches[1] + } + return "" +} + +func getRedhatishPlatform(contents []string) string { + c := strings.ToLower(strings.Join(contents, "")) + + if strings.Contains(c, "red hat") { + return "redhat" + } + f := strings.Split(c, " ") + + return f[0] +} + +func GetVirtualization() (string, string, error) { + var system string + var role string + + if common.PathExists("/proc/xen") { + system = "xen" + role = "guest" // assume guest + + if common.PathExists("/proc/xen/capabilities") { + contents, err := common.ReadLines("/proc/xen/capabilities") + if err == nil { + if common.StringContains(contents, "control_d") { + role = "host" + } + } + } + } + if common.PathExists("/proc/modules") { + contents, err := common.ReadLines("/proc/modules") + if err == nil { + if common.StringContains(contents, "kvm") { + system = "kvm" + role = "host" + } else if common.StringContains(contents, "vboxdrv") { + system = "vbox" + role = "host" + } else if common.StringContains(contents, "vboxguest") { + system = "vbox" + role = "guest" + } + } + } + + if common.PathExists("/proc/cpuinfo") { + contents, err := common.ReadLines("/proc/cpuinfo") + if err == nil { + if common.StringContains(contents, "QEMU Virtual CPU") || + common.StringContains(contents, "Common KVM processor") || + common.StringContains(contents, "Common 32-bit KVM processor") { + system = "kvm" + role = "guest" + } + } + } + + if common.PathExists("/proc/bc/0") { + system = "openvz" + role = "host" + } else if common.PathExists("/proc/vz") { + system = "openvz" + role = "guest" + } + + // not use dmidecode because it requires root + + if common.PathExists("/proc/self/status") { + contents, err := common.ReadLines("/proc/self/status") + if err == nil { + + if common.StringContains(contents, "s_context:") || + common.StringContains(contents, "VxID:") { + system = "linux-vserver" + } + // TODO: guest or host + } + } + + if common.PathExists("/proc/self/cgroup") { + contents, err := common.ReadLines("/proc/self/cgroup") + if err == nil { + + if common.StringContains(contents, "lxc") || + common.StringContains(contents, "docker") { + system = "lxc" + role = "guest" + } else if common.PathExists("/usr/bin/lxc-version") { // TODO: which + system = "lxc" + role = "host" + } + } + } + + return system, role, nil +} diff --git a/plugins/system/ps/host/host_linux_386.go b/plugins/system/ps/host/host_linux_386.go new file mode 100644 index 000000000..d8f31c2f6 --- /dev/null +++ b/plugins/system/ps/host/host_linux_386.go @@ -0,0 +1,44 @@ +// ATTENTION - FILE MANUAL FIXED AFTER CGO. +// Fixed line: Tv _Ctype_struct_timeval -> Tv UtTv +// Created by cgo -godefs, MANUAL FIXED +// cgo -godefs types_linux.go + +package host + +const ( + sizeofPtr = 0x4 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x4 + sizeofLongLong = 0x8 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int32 + _C_long_long int64 +) + +type utmp struct { + Type int16 + Pad_cgo_0 [2]byte + Pid int32 + Line [32]int8 + Id [4]int8 + User [32]int8 + Host [256]int8 + Exit exit_status + Session int32 + Tv UtTv + Addr_v6 [4]int32 + X__unused [20]int8 +} +type exit_status struct { + Termination int16 + Exit int16 +} +type UtTv struct { + TvSec int32 + TvUsec int32 +} diff --git a/plugins/system/ps/host/host_linux_amd64.go b/plugins/system/ps/host/host_linux_amd64.go new file mode 100644 index 000000000..b04fc17e3 --- /dev/null +++ b/plugins/system/ps/host/host_linux_amd64.go @@ -0,0 +1,42 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_linux.go + +package host + +const ( + sizeofPtr = 0x8 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x8 + sizeofLongLong = 0x8 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int64 + _C_long_long int64 +) + +type utmp struct { + Type int16 + Pad_cgo_0 [2]byte + Pid int32 + Line [32]int8 + Id [4]int8 + User [32]int8 + Host [256]int8 + Exit exit_status + Session int32 + Tv UtTv + Addr_v6 [4]int32 + X__glibc_reserved [20]int8 +} +type exit_status struct { + Termination int16 + Exit int16 +} +type UtTv struct { + TvSec int32 + TvUsec int32 +} diff --git a/plugins/system/ps/host/host_linux_arm.go b/plugins/system/ps/host/host_linux_arm.go new file mode 100644 index 000000000..d4455ea39 --- /dev/null +++ b/plugins/system/ps/host/host_linux_arm.go @@ -0,0 +1,27 @@ +// +build linux +// +build arm + +package host + +type exitStatus struct { + Etermination int16 // Process termination status. + Eexit int16 // Process exit status. +} +type timeval struct { + TvSec uint32 // Seconds. + TvUsec uint32 // Microseconds. +} + +type utmp struct { + Type int16 // Type of login. + Pid int32 // Process ID of login process. + Line [32]byte // Devicename. + ID [4]byte // Inittab ID. + User [32]byte // Username. + Host [256]byte // Hostname for remote login. + Exit exitStatus // Exit status of a process marked + Session int32 // Session ID, used for windowing. + Tv timeval // Time entry was made. + AddrV6 [16]byte // Internet address of remote host. + Unused [20]byte // Reserved for future use. // original is 20 +} diff --git a/plugins/system/ps/host/host_linux_test.go b/plugins/system/ps/host/host_linux_test.go new file mode 100644 index 000000000..7808eee3d --- /dev/null +++ b/plugins/system/ps/host/host_linux_test.go @@ -0,0 +1,61 @@ +// +build linux + +package host + +import ( + "testing" +) + +func TestGetRedhatishVersion(t *testing.T) { + var ret string + c := []string{"Rawhide"} + ret = getRedhatishVersion(c) + if ret != "rawhide" { + t.Errorf("Could not get version rawhide: %v", ret) + } + + c = []string{"Fedora release 15 (Lovelock)"} + ret = getRedhatishVersion(c) + if ret != "15" { + t.Errorf("Could not get version fedora: %v", ret) + } + + c = []string{"Enterprise Linux Server release 5.5 (Carthage)"} + ret = getRedhatishVersion(c) + if ret != "5.5" { + t.Errorf("Could not get version redhat enterprise: %v", ret) + } + + c = []string{""} + ret = getRedhatishVersion(c) + if ret != "" { + t.Errorf("Could not get version with no value: %v", ret) + } +} + +func TestGetRedhatishPlatform(t *testing.T) { + var ret string + c := []string{"red hat"} + ret = getRedhatishPlatform(c) + if ret != "redhat" { + t.Errorf("Could not get platform redhat: %v", ret) + } + + c = []string{"Fedora release 15 (Lovelock)"} + ret = getRedhatishPlatform(c) + if ret != "fedora" { + t.Errorf("Could not get platform fedora: %v", ret) + } + + c = []string{"Enterprise Linux Server release 5.5 (Carthage)"} + ret = getRedhatishPlatform(c) + if ret != "enterprise" { + t.Errorf("Could not get platform redhat enterprise: %v", ret) + } + + c = []string{""} + ret = getRedhatishPlatform(c) + if ret != "" { + t.Errorf("Could not get platform with no value: %v", ret) + } +} diff --git a/plugins/system/ps/host/host_test.go b/plugins/system/ps/host/host_test.go new file mode 100644 index 000000000..4c6fbc96d --- /dev/null +++ b/plugins/system/ps/host/host_test.go @@ -0,0 +1,67 @@ +package host + +import ( + "fmt" + "testing" +) + +func TestHostInfo(t *testing.T) { + v, err := HostInfo() + if err != nil { + t.Errorf("error %v", err) + } + empty := &HostInfoStat{} + if v == empty { + t.Errorf("Could not get hostinfo %v", v) + } +} + +func TestBoot_time(t *testing.T) { + v, err := BootTime() + if err != nil { + t.Errorf("error %v", err) + } + if v == 0 { + t.Errorf("Could not boot time %v", v) + } +} + +func TestUsers(t *testing.T) { + v, err := Users() + if err != nil { + t.Errorf("error %v", err) + } + empty := UserStat{} + for _, u := range v { + if u == empty { + t.Errorf("Could not Users %v", v) + } + } +} + +func TestHostInfoStat_String(t *testing.T) { + v := HostInfoStat{ + Hostname: "test", + Uptime: 3000, + Procs: 100, + OS: "linux", + Platform: "ubuntu", + } + e := `{"hostname":"test","uptime":3000,"procs":100,"os":"linux","platform":"ubuntu","platform_family":"","platform_version":"","virtualization_system":"","virtualization_role":""}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("HostInfoStat string is invalid: %v", v) + } +} + +func TestUserStat_String(t *testing.T) { + v := UserStat{ + User: "user", + Terminal: "term", + Host: "host", + Started: 100, + } + e := `{"user":"user","terminal":"term","host":"host","started":100}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("UserStat string is invalid: %v", v) + } +} diff --git a/plugins/system/ps/host/host_windows.go b/plugins/system/ps/host/host_windows.go new file mode 100644 index 000000000..cfbdd69e5 --- /dev/null +++ b/plugins/system/ps/host/host_windows.go @@ -0,0 +1,64 @@ +// +build windows + +package host + +import ( + "fmt" + "os" + "strings" + "time" + + common "github.com/shirou/gopsutil/common" + process "github.com/shirou/gopsutil/process" +) + +var ( + procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime") +) + +func HostInfo() (*HostInfoStat, error) { + ret := &HostInfoStat{} + hostname, err := os.Hostname() + if err != nil { + return ret, err + } + + ret.Hostname = hostname + uptime, err := BootTime() + if err == nil { + ret.Uptime = uptime + } + + procs, err := process.Pids() + if err != nil { + return ret, err + } + + ret.Procs = uint64(len(procs)) + + return ret, nil +} + +func BootTime() (uint64, error) { + lines, err := common.GetWmic("os", "get", "LastBootUpTime") + if err != nil { + return 0, err + } + if len(lines) == 0 || len(lines[0]) != 2 { + return 0, fmt.Errorf("could not get LastBootUpTime") + } + format := "20060102150405" + t, err := time.Parse(format, strings.Split(lines[0][1], ".")[0]) + if err != nil { + return 0, err + } + now := time.Now() + return uint64(now.Sub(t).Seconds()), nil +} + +func Users() ([]UserStat, error) { + + var ret []UserStat + + return ret, nil +} diff --git a/plugins/system/ps/host/types_darwin.go b/plugins/system/ps/host/types_darwin.go new file mode 100644 index 000000000..b85822788 --- /dev/null +++ b/plugins/system/ps/host/types_darwin.go @@ -0,0 +1,17 @@ +// +build ignore +// plus hand editing about timeval + +/* +Input to cgo -godefs. +*/ + +package host + +/* +#include +#include +*/ +import "C" + +type Utmpx C.struct_utmpx +type Timeval C.struct_timeval diff --git a/plugins/system/ps/host/types_freebsd.go b/plugins/system/ps/host/types_freebsd.go new file mode 100644 index 000000000..dd768f4b8 --- /dev/null +++ b/plugins/system/ps/host/types_freebsd.go @@ -0,0 +1,40 @@ +// +build ignore + +/* +Input to cgo -godefs. +*/ + +package host + +/* +#define KERNEL +#include +#include + +enum { + sizeofPtr = sizeof(void*), +}; + +*/ +import "C" + +// Machine characteristics; for internal use. + +const ( + sizeofPtr = C.sizeofPtr + sizeofShort = C.sizeof_short + sizeofInt = C.sizeof_int + sizeofLong = C.sizeof_long + sizeofLongLong = C.sizeof_longlong +) + +// Basic types + +type ( + _C_short C.short + _C_int C.int + _C_long C.long + _C_long_long C.longlong +) + +type Utmp C.struct_utmp diff --git a/plugins/system/ps/host/types_linux.go b/plugins/system/ps/host/types_linux.go new file mode 100644 index 000000000..928545515 --- /dev/null +++ b/plugins/system/ps/host/types_linux.go @@ -0,0 +1,45 @@ +// +build ignore + +/* +Input to cgo -godefs. +*/ + +package host + +/* +#define KERNEL +#include +#include + +enum { + sizeofPtr = sizeof(void*), +}; + +*/ +import "C" + +// Machine characteristics; for internal use. + +const ( + sizeofPtr = C.sizeofPtr + sizeofShort = C.sizeof_short + sizeofInt = C.sizeof_int + sizeofLong = C.sizeof_long + sizeofLongLong = C.sizeof_longlong +) + +// Basic types + +type ( + _C_short C.short + _C_int C.int + _C_long C.long + _C_long_long C.longlong +) + +type utmp C.struct_utmp +type exit_status C.struct_exit_status +type UtTv struct { + TvSec int32 + TvUsec int32 +} diff --git a/plugins/system/ps/load/load.go b/plugins/system/ps/load/load.go new file mode 100644 index 000000000..58746e72e --- /dev/null +++ b/plugins/system/ps/load/load.go @@ -0,0 +1,16 @@ +package load + +import ( + "encoding/json" +) + +type LoadAvgStat struct { + Load1 float64 `json:"load1"` + Load5 float64 `json:"load5"` + Load15 float64 `json:"load15"` +} + +func (l LoadAvgStat) String() string { + s, _ := json.Marshal(l) + return string(s) +} diff --git a/plugins/system/ps/load/load_darwin.go b/plugins/system/ps/load/load_darwin.go new file mode 100644 index 000000000..d90a42902 --- /dev/null +++ b/plugins/system/ps/load/load_darwin.go @@ -0,0 +1,37 @@ +// +build darwin + +package load + +import ( + "strconv" + + common "github.com/shirou/gopsutil/common" +) + +func LoadAvg() (*LoadAvgStat, error) { + values, err := common.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/plugins/system/ps/load/load_freebsd.go b/plugins/system/ps/load/load_freebsd.go new file mode 100644 index 000000000..d82afbecc --- /dev/null +++ b/plugins/system/ps/load/load_freebsd.go @@ -0,0 +1,37 @@ +// +build freebsd + +package load + +import ( + "strconv" + + common "github.com/shirou/gopsutil/common" +) + +func LoadAvg() (*LoadAvgStat, error) { + values, err := common.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/plugins/system/ps/load/load_linux.go b/plugins/system/ps/load/load_linux.go new file mode 100644 index 000000000..6c9926b3b --- /dev/null +++ b/plugins/system/ps/load/load_linux.go @@ -0,0 +1,40 @@ +// +build linux + +package load + +import ( + "io/ioutil" + "strconv" + "strings" +) + +func LoadAvg() (*LoadAvgStat, error) { + filename := "/proc/loadavg" + line, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + values := strings.Fields(string(line)) + + 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: load1, + Load5: load5, + Load15: load15, + } + + return ret, nil +} diff --git a/plugins/system/ps/load/load_test.go b/plugins/system/ps/load/load_test.go new file mode 100644 index 000000000..39cee399f --- /dev/null +++ b/plugins/system/ps/load/load_test.go @@ -0,0 +1,30 @@ +package load + +import ( + "fmt" + "testing" +) + +func TestLoad(t *testing.T) { + v, err := LoadAvg() + if err != nil { + t.Errorf("error %v", err) + } + + empty := &LoadAvgStat{} + if v == empty { + t.Errorf("error load: %v", v) + } +} + +func TestLoadAvgStat_String(t *testing.T) { + v := LoadAvgStat{ + Load1: 10.1, + Load5: 20.1, + Load15: 30.1, + } + e := `{"load1":10.1,"load5":20.1,"load15":30.1}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("LoadAvgStat string is invalid: %v", v) + } +} diff --git a/plugins/system/ps/load/load_windows.go b/plugins/system/ps/load/load_windows.go new file mode 100644 index 000000000..70ad565d8 --- /dev/null +++ b/plugins/system/ps/load/load_windows.go @@ -0,0 +1,13 @@ +// +build windows + +package load + +import ( + common "github.com/shirou/gopsutil/common" +) + +func LoadAvg() (*LoadAvgStat, error) { + ret := LoadAvgStat{} + + return &ret, common.NotImplementedError +} diff --git a/plugins/system/ps/mem/mem.go b/plugins/system/ps/mem/mem.go new file mode 100644 index 000000000..67f8741e7 --- /dev/null +++ b/plugins/system/ps/mem/mem.go @@ -0,0 +1,38 @@ +package mem + +import ( + "encoding/json" +) + +type VirtualMemoryStat struct { + Total uint64 `json:"total"` + Available uint64 `json:"available"` + Used uint64 `json:"used"` + UsedPercent float64 `json:"used_percent"` + Free uint64 `json:"free"` + Active uint64 `json:"active"` + Inactive uint64 `json:"inactive"` + Buffers uint64 `json:"buffers"` + Cached uint64 `json:"cached"` + Wired uint64 `json:"wired"` + Shared uint64 `json:"shared"` +} + +type SwapMemoryStat struct { + Total uint64 `json:"total"` + Used uint64 `json:"used"` + Free uint64 `json:"free"` + UsedPercent float64 `json:"used_percent"` + Sin uint64 `json:"sin"` + Sout uint64 `json:"sout"` +} + +func (m VirtualMemoryStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} + +func (m SwapMemoryStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} diff --git a/plugins/system/ps/mem/mem_darwin.go b/plugins/system/ps/mem/mem_darwin.go new file mode 100644 index 000000000..6f901d254 --- /dev/null +++ b/plugins/system/ps/mem/mem_darwin.go @@ -0,0 +1,109 @@ +// +build darwin + +package mem + +import ( + "os/exec" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +func getPageSize() (uint64, error) { + out, err := exec.Command("pagesize").Output() + if err != nil { + return 0, err + } + o := strings.TrimSpace(string(out)) + p, err := strconv.ParseUint(o, 10, 64) + if err != nil { + return 0, err + } + + return p, nil +} + +// VirtualMemory returns VirtualmemoryStat. +func VirtualMemory() (*VirtualMemoryStat, error) { + p, err := getPageSize() + if err != nil { + return nil, err + } + + total, err := common.DoSysctrl("hw.memsize") + if err != nil { + return nil, err + } + free, err := common.DoSysctrl("vm.page_free_count") + if err != nil { + return nil, err + } + parsed := make([]uint64, 0, 7) + vv := []string{ + total[0], + free[0], + } + for _, target := range vv { + t, err := strconv.ParseUint(target, 10, 64) + if err != nil { + return nil, err + } + parsed = append(parsed, t) + } + + ret := &VirtualMemoryStat{ + Total: parsed[0] * p, + Free: parsed[1] * 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) { + var ret *SwapMemoryStat + + swapUsage, err := common.DoSysctrl("vm.swapusage") + if err != nil { + return ret, err + } + + total := strings.Replace(swapUsage[2], "M", "", 1) + used := strings.Replace(swapUsage[5], "M", "", 1) + free := strings.Replace(swapUsage[8], "M", "", 1) + + total_v, err := strconv.ParseFloat(total, 64) + if err != nil { + return nil, err + } + used_v, err := strconv.ParseFloat(used, 64) + if err != nil { + return nil, err + } + free_v, err := strconv.ParseFloat(free, 64) + if err != nil { + return nil, err + } + + u := float64(0) + if total_v != 0 { + u = ((total_v - free_v) / total_v) * 100.0 + } + + // vm.swapusage shows "M", multiply 1000 + ret = &SwapMemoryStat{ + Total: uint64(total_v * 1000), + Used: uint64(used_v * 1000), + Free: uint64(free_v * 1000), + UsedPercent: u, + } + + return ret, nil +} diff --git a/plugins/system/ps/mem/mem_freebsd.go b/plugins/system/ps/mem/mem_freebsd.go new file mode 100644 index 000000000..a380519f9 --- /dev/null +++ b/plugins/system/ps/mem/mem_freebsd.go @@ -0,0 +1,131 @@ +// +build freebsd + +package mem + +import ( + "os/exec" + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +func VirtualMemory() (*VirtualMemoryStat, error) { + pageSize, err := common.DoSysctrl("vm.stats.vm.v_page_size") + if err != nil { + return nil, err + } + p, err := strconv.ParseUint(pageSize[0], 10, 64) + if err != nil { + return nil, err + } + + pageCount, err := common.DoSysctrl("vm.stats.vm.v_page_count") + if err != nil { + return nil, err + } + free, err := common.DoSysctrl("vm.stats.vm.v_free_count") + if err != nil { + return nil, err + } + active, err := common.DoSysctrl("vm.stats.vm.v_active_count") + if err != nil { + return nil, err + } + inactive, err := common.DoSysctrl("vm.stats.vm.v_inactive_count") + if err != nil { + return nil, err + } + cache, err := common.DoSysctrl("vm.stats.vm.v_cache_count") + if err != nil { + return nil, err + } + buffer, err := common.DoSysctrl("vfs.bufspace") + if err != nil { + return nil, err + } + wired, err := common.DoSysctrl("vm.stats.vm.v_wire_count") + if err != nil { + return nil, err + } + + parsed := make([]uint64, 0, 7) + vv := []string{ + pageCount[0], + free[0], + active[0], + inactive[0], + cache[0], + buffer[0], + wired[0], + } + for _, target := range vv { + t, err := strconv.ParseUint(target, 10, 64) + if err != nil { + return nil, err + } + parsed = append(parsed, t) + } + + ret := &VirtualMemoryStat{ + Total: parsed[0] * p, + Free: parsed[1] * p, + Active: parsed[2] * p, + Inactive: parsed[3] * p, + Cached: parsed[4] * p, + Buffers: parsed[5], + Wired: parsed[6] * 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 +} + +// Return swapinfo +// FreeBSD can have multiple swap devices. but use only first device +func SwapMemory() (*SwapMemoryStat, error) { + out, err := exec.Command("swapinfo").Output() + if err != nil { + return nil, err + } + var ret *SwapMemoryStat + for _, line := range strings.Split(string(out), "\n") { + values := strings.Fields(line) + // skip title line + if len(values) == 0 || values[0] == "Device" { + continue + } + + u := strings.Replace(values[4], "%", "", 1) + total_v, err := strconv.ParseUint(values[1], 10, 64) + if err != nil { + return nil, err + } + used_v, err := strconv.ParseUint(values[2], 10, 64) + if err != nil { + return nil, err + } + free_v, err := strconv.ParseUint(values[3], 10, 64) + if err != nil { + return nil, err + } + up_v, err := strconv.ParseFloat(u, 64) + if err != nil { + return nil, err + } + + ret = &SwapMemoryStat{ + Total: total_v, + Used: used_v, + Free: free_v, + UsedPercent: up_v, + } + } + + return ret, nil +} diff --git a/plugins/system/ps/mem/mem_linux.go b/plugins/system/ps/mem/mem_linux.go new file mode 100644 index 000000000..00559c5a0 --- /dev/null +++ b/plugins/system/ps/mem/mem_linux.go @@ -0,0 +1,92 @@ +// +build linux + +package mem + +import ( + "strconv" + "strings" + "syscall" + + common "github.com/shirou/gopsutil/common" +) + +func VirtualMemory() (*VirtualMemoryStat, error) { + filename := "/proc/meminfo" + lines, _ := common.ReadLines(filename) + + ret := &VirtualMemoryStat{} + for _, line := range lines { + fields := strings.Split(line, ":") + if len(fields) != 2 { + continue + } + key := strings.TrimSpace(fields[0]) + value := strings.TrimSpace(fields[1]) + value = strings.Replace(value, " kB", "", -1) + + t, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return ret, err + } + switch key { + case "MemTotal": + ret.Total = t * 1000 + case "MemFree": + ret.Free = t * 1000 + case "Buffers": + ret.Buffers = t * 1000 + case "Cached": + ret.Cached = t * 1000 + case "Active": + ret.Active = t * 1000 + case "Inactive": + ret.Inactive = t * 1000 + } + } + 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 +} + +func SwapMemory() (*SwapMemoryStat, error) { + sysinfo := &syscall.Sysinfo_t{} + + if err := syscall.Sysinfo(sysinfo); err != nil { + return nil, err + } + ret := &SwapMemoryStat{ + Total: uint64(sysinfo.Totalswap), + Free: uint64(sysinfo.Freeswap), + } + ret.Used = ret.Total - ret.Free + //check Infinity + if ret.Total != 0 { + ret.UsedPercent = float64(ret.Total-ret.Free) / float64(ret.Total) * 100.0 + } else { + ret.UsedPercent = 0 + } + lines, _ := common.ReadLines("/proc/vmstat") + for _, l := range lines { + fields := strings.Fields(l) + if len(fields) < 2 { + continue + } + switch fields[0] { + case "pswpin": + value, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + continue + } + ret.Sin = value * 4 * 1024 + case "pswpout": + value, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + continue + } + ret.Sout = value * 4 * 1024 + } + } + return ret, nil +} diff --git a/plugins/system/ps/mem/mem_test.go b/plugins/system/ps/mem/mem_test.go new file mode 100644 index 000000000..28693574a --- /dev/null +++ b/plugins/system/ps/mem/mem_test.go @@ -0,0 +1,55 @@ +package mem + +import ( + "fmt" + "testing" +) + +func TestVirtual_memory(t *testing.T) { + v, err := VirtualMemory() + if err != nil { + t.Errorf("error %v", err) + } + empty := &VirtualMemoryStat{} + if v == empty { + t.Errorf("error %v", v) + } +} + +func TestSwap_memory(t *testing.T) { + v, err := SwapMemory() + if err != nil { + t.Errorf("error %v", err) + } + empty := &SwapMemoryStat{} + if v == empty { + t.Errorf("error %v", v) + } +} + +func TestVirtualMemoryStat_String(t *testing.T) { + v := VirtualMemoryStat{ + Total: 10, + Available: 20, + Used: 30, + UsedPercent: 30.1, + Free: 40, + } + e := `{"total":10,"available":20,"used":30,"used_percent":30.1,"free":40,"active":0,"inactive":0,"buffers":0,"cached":0,"wired":0,"shared":0}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("VirtualMemoryStat string is invalid: %v", v) + } +} + +func TestSwapMemoryStat_String(t *testing.T) { + v := SwapMemoryStat{ + Total: 10, + Used: 30, + Free: 40, + UsedPercent: 30.1, + } + e := `{"total":10,"used":30,"free":40,"used_percent":30.1,"sin":0,"sout":0}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("SwapMemoryStat string is invalid: %v", v) + } +} diff --git a/plugins/system/ps/mem/mem_windows.go b/plugins/system/ps/mem/mem_windows.go new file mode 100644 index 000000000..985ab8fdf --- /dev/null +++ b/plugins/system/ps/mem/mem_windows.go @@ -0,0 +1,50 @@ +// +build windows + +package mem + +import ( + "syscall" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +var ( + procGlobalMemoryStatusEx = common.Modkernel32.NewProc("GlobalMemoryStatusEx") +) + +type MEMORYSTATUSEX struct { + cbSize uint32 + dwMemoryLoad uint32 + ullTotalPhys uint64 // in bytes + ullAvailPhys uint64 + ullTotalPageFile uint64 + ullAvailPageFile uint64 + ullTotalVirtual uint64 + ullAvailVirtual uint64 + ullAvailExtendedVirtual uint64 +} + +func VirtualMemory() (*VirtualMemoryStat, error) { + var memInfo MEMORYSTATUSEX + memInfo.cbSize = uint32(unsafe.Sizeof(memInfo)) + mem, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo))) + if mem == 0 { + return nil, syscall.GetLastError() + } + + ret := &VirtualMemoryStat{ + Total: memInfo.ullTotalPhys, + Available: memInfo.ullAvailPhys, + UsedPercent: float64(memInfo.dwMemoryLoad), + } + + ret.Used = ret.Total - ret.Available + return ret, nil +} + +func SwapMemory() (*SwapMemoryStat, error) { + ret := &SwapMemoryStat{} + + return ret, nil +} diff --git a/plugins/system/ps/mktypes.sh b/plugins/system/ps/mktypes.sh new file mode 100644 index 000000000..7bf2e2412 --- /dev/null +++ b/plugins/system/ps/mktypes.sh @@ -0,0 +1,37 @@ + +DIRS="cpu disk docker host load mem net process" + +GOOS=`uname | tr '[:upper:]' '[:lower:]'` +ARCH=`uname -m` + +case $ARCH in + amd64) + GOARCH="amd64" + ;; + x86_64) + GOARCH="amd64" + ;; + i386) + GOARCH="386" + ;; + i686) + GOARCH="386" + ;; + arm) + GOARCH="arm" + ;; + *) + echo "unknown arch: $ARCH" + exit 1 +esac + +for DIR in $DIRS +do + if [ -e ${DIR}/types_${GOOS}.go ]; then + echo "// +build $GOOS" > ${DIR}/${DIR}_${GOOS}_${GOARCH}.go + echo "// +build $GOARCH" >> ${DIR}/${DIR}_${GOOS}_${GOARCH}.go + go tool cgo -godefs ${DIR}/types_${GOOS}.go >> ${DIR}/${DIR}_${GOOS}_${GOARCH}.go + fi +done + + diff --git a/plugins/system/ps/net/net.go b/plugins/system/ps/net/net.go new file mode 100644 index 000000000..825517760 --- /dev/null +++ b/plugins/system/ps/net/net.go @@ -0,0 +1,137 @@ +package net + +import ( + "encoding/json" + "net" +) + +type NetIOCountersStat struct { + Name string `json:"name"` // interface name + BytesSent uint64 `json:"bytes_sent"` // number of bytes sent + BytesRecv uint64 `json:"bytes_recv"` // number of bytes received + PacketsSent uint64 `json:"packets_sent"` // number of packets sent + PacketsRecv uint64 `json:"packets_recv"` // number of packets received + Errin uint64 `json:"errin"` // total number of errors while receiving + Errout uint64 `json:"errout"` // total number of errors while sending + Dropin uint64 `json:"dropin"` // total number of incoming packets which were dropped + Dropout uint64 `json:"dropout"` // total number of outgoing packets which were dropped (always 0 on OSX and BSD) +} + +// Addr is implemented compatibility to psutil +type Addr struct { + IP string `json:"ip"` + Port uint32 `json:"port"` +} + +type NetConnectionStat struct { + Fd uint32 `json:"fd"` + Family uint32 `json:"family"` + Type uint32 `json:"type"` + Laddr Addr `json:"localaddr"` + Raddr Addr `json:"remoteaddr"` + Status string `json:"status"` + Pid int32 `json:"pid"` +} + +// NetInterfaceAddr is designed for represent interface addresses +type NetInterfaceAddr struct { + Addr string `json:"addr"` +} + +type NetInterfaceStat struct { + MTU int `json:"mtu"` // maximum transmission unit + Name string `json:"name"` // e.g., "en0", "lo0", "eth0.100" + HardwareAddr string `json:"hardwareaddr"` // IEEE MAC-48, EUI-48 and EUI-64 form + Flags []string `json:"flags"` // e.g., FlagUp, FlagLoopback, FlagMulticast + Addrs []NetInterfaceAddr `json:"addrs"` +} + +func (n NetIOCountersStat) String() string { + s, _ := json.Marshal(n) + return string(s) +} + +func (n NetConnectionStat) String() string { + s, _ := json.Marshal(n) + return string(s) +} + +func (a Addr) String() string { + s, _ := json.Marshal(a) + return string(s) +} + +func (n NetInterfaceStat) String() string { + s, _ := json.Marshal(n) + return string(s) +} + +func (n NetInterfaceAddr) String() string { + s, _ := json.Marshal(n) + return string(s) +} + +func NetInterfaces() ([]NetInterfaceStat, error) { + is, err := net.Interfaces() + if err != nil { + return nil, err + } + ret := make([]NetInterfaceStat, 0, len(is)) + for _, ifi := range is { + + var flags []string + if ifi.Flags&net.FlagUp != 0 { + flags = append(flags, "up") + } + if ifi.Flags&net.FlagBroadcast != 0 { + flags = append(flags, "broadcast") + } + if ifi.Flags&net.FlagLoopback != 0 { + flags = append(flags, "loopback") + } + if ifi.Flags&net.FlagPointToPoint != 0 { + flags = append(flags, "pointtopoint") + } + if ifi.Flags&net.FlagMulticast != 0 { + flags = append(flags, "multicast") + } + + r := NetInterfaceStat{ + Name: ifi.Name, + MTU: ifi.MTU, + HardwareAddr: ifi.HardwareAddr.String(), + Flags: flags, + } + addrs, err := ifi.Addrs() + if err == nil { + r.Addrs = make([]NetInterfaceAddr, 0, len(addrs)) + for _, addr := range addrs { + r.Addrs = append(r.Addrs, NetInterfaceAddr{ + Addr: addr.String(), + }) + } + + } + ret = append(ret, r) + } + + return ret, nil +} + +func getNetIOCountersAll(n []NetIOCountersStat) ([]NetIOCountersStat, error) { + r := NetIOCountersStat{ + Name: "all", + } + for _, nic := range n { + r.BytesRecv += nic.BytesRecv + r.PacketsRecv += nic.PacketsRecv + r.Errin += nic.Errin + r.Dropin += nic.Dropin + r.BytesSent += nic.BytesSent + r.PacketsSent += nic.PacketsSent + r.Errout += nic.Errout + r.Dropout += nic.Dropout + } + + return []NetIOCountersStat{r}, nil +} diff --git a/plugins/system/ps/net/net_darwin.go b/plugins/system/ps/net/net_darwin.go new file mode 100644 index 000000000..2c4be7661 --- /dev/null +++ b/plugins/system/ps/net/net_darwin.go @@ -0,0 +1,74 @@ +// +build darwin + +package net + +import ( + "os/exec" + "strconv" + "strings" + + "github.com/shirou/gopsutil/common" +) + +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) + exists := make([]string, 0, len(ret)) + + for _, line := range lines { + values := strings.Fields(line) + if len(values) < 1 || values[0] == "Name" { + // skip first line + continue + } + if common.StringContains(exists, values[0]) { + // skip if already get + continue + } + exists = append(exists, values[0]) + + base := 1 + // sometimes Address is ommitted + if len(values) < 11 { + base = 0 + } + + parsed := make([]uint64, 0, 3) + vv := []string{ + values[base+3], // PacketsRecv + values[base+4], // Errin + values[base+5], // Dropin + } + for _, target := range vv { + if target == "-" { + parsed = append(parsed, 0) + continue + } + + t, err := strconv.ParseUint(target, 10, 64) + if err != nil { + return nil, err + } + parsed = append(parsed, t) + } + + n := NetIOCountersStat{ + Name: values[0], + PacketsRecv: parsed[0], + Errin: parsed[1], + Dropin: parsed[2], + } + ret = append(ret, n) + } + + if pernic == false { + return getNetIOCountersAll(ret) + } + + return ret, nil +} diff --git a/plugins/system/ps/net/net_freebsd.go b/plugins/system/ps/net/net_freebsd.go new file mode 100644 index 000000000..b9e14286b --- /dev/null +++ b/plugins/system/ps/net/net_freebsd.go @@ -0,0 +1,83 @@ +// +build freebsd + +package net + +import ( + "os/exec" + "strconv" + "strings" + + "github.com/shirou/gopsutil/common" +) + +func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { + out, err := exec.Command("/usr/bin/netstat", "-ibdn").Output() + if err != nil { + return nil, err + } + + lines := strings.Split(string(out), "\n") + ret := make([]NetIOCountersStat, 0, len(lines)-1) + exists := make([]string, 0, len(ret)) + + for _, line := range lines { + values := strings.Fields(line) + if len(values) < 1 || values[0] == "Name" { + continue + } + if common.StringContains(exists, values[0]) { + // skip if already get + continue + } + exists = append(exists, values[0]) + + base := 1 + // sometimes Address is ommitted + if len(values) < 13 { + base = 0 + } + + parsed := make([]uint64, 0, 8) + vv := []string{ + values[base+3], // PacketsRecv + values[base+4], // Errin + values[base+5], // Dropin + values[base+6], // BytesRecvn + values[base+7], // PacketSent + values[base+8], // Errout + values[base+9], // BytesSent + values[base+11], // Dropout + } + for _, target := range vv { + if target == "-" { + parsed = append(parsed, 0) + continue + } + + t, err := strconv.ParseUint(target, 10, 64) + if err != nil { + return nil, err + } + parsed = append(parsed, t) + } + + n := NetIOCountersStat{ + Name: values[0], + PacketsRecv: parsed[0], + Errin: parsed[1], + Dropin: parsed[2], + BytesRecv: parsed[3], + PacketsSent: parsed[4], + Errout: parsed[5], + BytesSent: parsed[6], + Dropout: parsed[7], + } + ret = append(ret, n) + } + + if pernic == false { + return getNetIOCountersAll(ret) + } + + return ret, nil +} diff --git a/plugins/system/ps/net/net_linux.go b/plugins/system/ps/net/net_linux.go new file mode 100644 index 000000000..9184439a4 --- /dev/null +++ b/plugins/system/ps/net/net_linux.go @@ -0,0 +1,91 @@ +// +build linux + +package net + +import ( + "strconv" + "strings" + + common "github.com/shirou/gopsutil/common" +) + +// NetIOCounters returnes network I/O statistics for every network +// interface installed on the system. If pernic argument is false, +// return only sum of all information (which name is 'all'). If true, +// every network interface installed on the system is returned +// separately. +func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { + filename := "/proc/net/dev" + lines, err := common.ReadLines(filename) + if err != nil { + return nil, err + } + + statlen := len(lines) - 1 + + ret := make([]NetIOCountersStat, 0, statlen) + + for _, line := range lines[2:] { + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + interfaceName := strings.TrimSpace(parts[0]) + if interfaceName == "" { + continue + } + + fields := strings.Fields(strings.TrimSpace(parts[1])) + bytesRecv, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return ret, err + } + packetsRecv, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return ret, err + } + errIn, err := strconv.ParseUint(fields[2], 10, 64) + if err != nil { + return ret, err + } + dropIn, err := strconv.ParseUint(fields[3], 10, 64) + if err != nil { + return ret, err + } + bytesSent, err := strconv.ParseUint(fields[8], 10, 64) + if err != nil { + return ret, err + } + packetsSent, err := strconv.ParseUint(fields[9], 10, 64) + if err != nil { + return ret, err + } + errOut, err := strconv.ParseUint(fields[10], 10, 64) + if err != nil { + return ret, err + } + dropOut, err := strconv.ParseUint(fields[13], 10, 64) + if err != nil { + return ret, err + } + + nic := NetIOCountersStat{ + Name: interfaceName, + BytesRecv: bytesRecv, + PacketsRecv: packetsRecv, + Errin: errIn, + Dropin: dropIn, + BytesSent: bytesSent, + PacketsSent: packetsSent, + Errout: errOut, + Dropout: dropOut, + } + ret = append(ret, nic) + } + + if pernic == false { + return getNetIOCountersAll(ret) + } + + return ret, nil +} diff --git a/plugins/system/ps/net/net_test.go b/plugins/system/ps/net/net_test.go new file mode 100644 index 000000000..a6648fbec --- /dev/null +++ b/plugins/system/ps/net/net_test.go @@ -0,0 +1,122 @@ +package net + +import ( + "fmt" + "testing" +) + +func TestAddrString(t *testing.T) { + v := Addr{IP: "192.168.0.1", Port: 8000} + + s := fmt.Sprintf("%v", v) + if s != "{\"ip\":\"192.168.0.1\",\"port\":8000}" { + t.Errorf("Addr string is invalid: %v", v) + } +} + +func TestNetIOCountersStatString(t *testing.T) { + v := NetIOCountersStat{ + Name: "test", + BytesSent: 100, + } + e := `{"name":"test","bytes_sent":100,"bytes_recv":0,"packets_sent":0,"packets_recv":0,"errin":0,"errout":0,"dropin":0,"dropout":0}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("NetIOCountersStat string is invalid: %v", v) + } +} + +func TestNetConnectionStatString(t *testing.T) { + v := NetConnectionStat{ + Fd: 10, + Family: 10, + Type: 10, + } + e := `{"fd":10,"family":10,"type":10,"localaddr":{"ip":"","port":0},"remoteaddr":{"ip":"","port":0},"status":"","pid":0}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("NetConnectionStat string is invalid: %v", v) + } + +} + +func TestNetIOCountersAll(t *testing.T) { + v, err := NetIOCounters(false) + per, err := NetIOCounters(true) + if err != nil { + t.Errorf("Could not get NetIOCounters: %v", err) + } + if len(v) != 1 { + t.Errorf("Could not get NetIOCounters: %v", v) + } + if v[0].Name != "all" { + t.Errorf("Invalid NetIOCounters: %v", v) + } + var pr uint64 + for _, p := range per { + pr += p.PacketsRecv + } + if v[0].PacketsRecv != pr { + t.Errorf("invalid sum value: %v, %v", v[0].PacketsRecv, pr) + } +} + +func TestNetIOCountersPerNic(t *testing.T) { + v, err := NetIOCounters(true) + if err != nil { + t.Errorf("Could not get NetIOCounters: %v", err) + } + if len(v) == 0 { + t.Errorf("Could not get NetIOCounters: %v", v) + } + for _, vv := range v { + if vv.Name == "" { + t.Errorf("Invalid NetIOCounters: %v", vv) + } + } +} + +func Test_getNetIOCountersAll(t *testing.T) { + n := []NetIOCountersStat{ + NetIOCountersStat{ + Name: "a", + BytesRecv: 10, + PacketsRecv: 10, + }, + NetIOCountersStat{ + Name: "b", + BytesRecv: 10, + PacketsRecv: 10, + Errin: 10, + }, + } + ret, err := getNetIOCountersAll(n) + if err != nil { + t.Error(err) + } + if len(ret) != 1 { + t.Errorf("invalid return count") + } + if ret[0].Name != "all" { + t.Errorf("invalid return name") + } + if ret[0].BytesRecv != 20 { + t.Errorf("invalid count bytesrecv") + } + if ret[0].Errin != 10 { + t.Errorf("invalid count errin") + } +} + +func TestNetInterfaces(t *testing.T) { + v, err := NetInterfaces() + if err != nil { + t.Errorf("Could not get NetInterfaceStat: %v", err) + } + if len(v) == 0 { + t.Errorf("Could not get NetInterfaceStat: %v", err) + } + for _, vv := range v { + if vv.Name == "" { + t.Errorf("Invalid NetInterface: %v", vv) + } + } +} diff --git a/plugins/system/ps/net/net_windows.go b/plugins/system/ps/net/net_windows.go new file mode 100644 index 000000000..8937c4e89 --- /dev/null +++ b/plugins/system/ps/net/net_windows.go @@ -0,0 +1,98 @@ +// +build windows + +package net + +import ( + "net" + "os" + "syscall" + "unsafe" + + common "github.com/shirou/gopsutil/common" +) + +var ( + modiphlpapi = syscall.NewLazyDLL("iphlpapi.dll") + procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") + procGetExtendedUdpTable = modiphlpapi.NewProc("GetExtendedUdpTable") +) + +const ( + TCPTableBasicListener = iota + TCPTableBasicConnections + TCPTableBasicAll + TCPTableOwnerPIDListener + TCPTableOwnerPIDConnections + TCPTableOwnerPIDAll + TCPTableOwnerModuleListener + TCPTableOwnerModuleConnections + TCPTableOwnerModuleAll +) + +func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { + ifs, err := net.Interfaces() + if err != nil { + return nil, err + } + + ai, err := getAdapterList() + if err != nil { + return nil, err + } + var ret []NetIOCountersStat + + for _, ifi := range ifs { + name := ifi.Name + for ; ai != nil; ai = ai.Next { + name = common.BytePtrToString(&ai.Description[0]) + c := NetIOCountersStat{ + Name: name, + } + + row := syscall.MibIfRow{Index: ai.Index} + e := syscall.GetIfEntry(&row) + if e != nil { + return nil, os.NewSyscallError("GetIfEntry", e) + } + c.BytesSent = uint64(row.OutOctets) + c.BytesRecv = uint64(row.InOctets) + c.PacketsSent = uint64(row.OutUcastPkts) + c.PacketsRecv = uint64(row.InUcastPkts) + c.Errin = uint64(row.InErrors) + c.Errout = uint64(row.OutErrors) + c.Dropin = uint64(row.InDiscards) + c.Dropout = uint64(row.OutDiscards) + + ret = append(ret, c) + } + } + + if pernic == false { + return getNetIOCountersAll(ret) + } + return ret, nil +} + +// Return a list of network connections opened by a process +func NetConnections(kind string) ([]NetConnectionStat, error) { + var ret []NetConnectionStat + + return ret, common.NotImplementedError +} + +// borrowed from src/pkg/net/interface_windows.go +func getAdapterList() (*syscall.IpAdapterInfo, error) { + b := make([]byte, 1000) + l := uint32(len(b)) + a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) + err := syscall.GetAdaptersInfo(a, &l) + if err == syscall.ERROR_BUFFER_OVERFLOW { + b = make([]byte, l) + a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) + err = syscall.GetAdaptersInfo(a, &l) + } + if err != nil { + return nil, os.NewSyscallError("GetAdaptersInfo", err) + } + return a, nil +} diff --git a/plugins/system/ps/process/binary.go b/plugins/system/ps/process/binary.go new file mode 100644 index 000000000..8891f0f14 --- /dev/null +++ b/plugins/system/ps/process/binary.go @@ -0,0 +1,634 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package binary implements simple translation between numbers and byte +// sequences and encoding and decoding of varints. +// +// Numbers are translated by reading and writing fixed-size values. +// A fixed-size value is either a fixed-size arithmetic +// type (int8, uint8, int16, float32, complex64, ...) +// or an array or struct containing only fixed-size values. +// +// The varint functions encode and decode single integer values using +// a variable-length encoding; smaller values require fewer bytes. +// For a specification, see +// http://code.google.com/apis/protocolbuffers/docs/encoding.html. +// +// This package favors simplicity over efficiency. Clients that require +// high-performance serialization, especially for large data structures, +// should look at more advanced solutions such as the encoding/gob +// package or protocol buffers. +package process + +import ( + "errors" + "io" + "math" + "reflect" +) + +// A ByteOrder specifies how to convert byte sequences into +// 16-, 32-, or 64-bit unsigned integers. +type ByteOrder interface { + Uint16([]byte) uint16 + Uint32([]byte) uint32 + Uint64([]byte) uint64 + PutUint16([]byte, uint16) + PutUint32([]byte, uint32) + PutUint64([]byte, uint64) + String() string +} + +// LittleEndian is the little-endian implementation of ByteOrder. +var LittleEndian littleEndian + +// BigEndian is the big-endian implementation of ByteOrder. +var BigEndian bigEndian + +type littleEndian struct{} + +func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } + +func (littleEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +func (littleEndian) Uint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func (littleEndian) PutUint32(b []byte, v uint32) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} + +func (littleEndian) Uint64(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func (littleEndian) PutUint64(b []byte, v uint64) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +func (littleEndian) String() string { return "LittleEndian" } + +func (littleEndian) GoString() string { return "binary.LittleEndian" } + +type bigEndian struct{} + +func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 } + +func (bigEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v >> 8) + b[1] = byte(v) +} + +func (bigEndian) Uint32(b []byte) uint32 { + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func (bigEndian) PutUint32(b []byte, v uint32) { + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} + +func (bigEndian) Uint64(b []byte) uint64 { + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func (bigEndian) PutUint64(b []byte, v uint64) { + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +func (bigEndian) String() string { return "BigEndian" } + +func (bigEndian) GoString() string { return "binary.BigEndian" } + +// Read reads structured binary data from r into data. +// Data must be a pointer to a fixed-size value or a slice +// of fixed-size values. +// Bytes read from r are decoded using the specified byte order +// and written to successive fields of the data. +// When reading into structs, the field data for fields with +// blank (_) field names is skipped; i.e., blank field names +// may be used for padding. +// When reading into a struct, all non-blank fields must be exported. +func Read(r io.Reader, order ByteOrder, data interface{}) error { + // Fast path for basic types and slices. + if n := intDataSize(data); n != 0 { + var b [8]byte + var bs []byte + if n > len(b) { + bs = make([]byte, n) + } else { + bs = b[:n] + } + if _, err := io.ReadFull(r, bs); err != nil { + return err + } + switch data := data.(type) { + case *int8: + *data = int8(b[0]) + case *uint8: + *data = b[0] + case *int16: + *data = int16(order.Uint16(bs)) + case *uint16: + *data = order.Uint16(bs) + case *int32: + *data = int32(order.Uint32(bs)) + case *uint32: + *data = order.Uint32(bs) + case *int64: + *data = int64(order.Uint64(bs)) + case *uint64: + *data = order.Uint64(bs) + case []int8: + for i, x := range bs { // Easier to loop over the input for 8-bit values. + data[i] = int8(x) + } + case []uint8: + copy(data, bs) + case []int16: + for i := range data { + data[i] = int16(order.Uint16(bs[2*i:])) + } + case []uint16: + for i := range data { + data[i] = order.Uint16(bs[2*i:]) + } + case []int32: + for i := range data { + data[i] = int32(order.Uint32(bs[4*i:])) + } + case []uint32: + for i := range data { + data[i] = order.Uint32(bs[4*i:]) + } + case []int64: + for i := range data { + data[i] = int64(order.Uint64(bs[8*i:])) + } + case []uint64: + for i := range data { + data[i] = order.Uint64(bs[8*i:]) + } + } + return nil + } + + // Fallback to reflect-based decoding. + v := reflect.ValueOf(data) + size := -1 + switch v.Kind() { + case reflect.Ptr: + v = v.Elem() + size = dataSize(v) + case reflect.Slice: + size = dataSize(v) + } + if size < 0 { + return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String()) + } + d := &decoder{order: order, buf: make([]byte, size)} + if _, err := io.ReadFull(r, d.buf); err != nil { + return err + } + d.value(v) + return nil +} + +// Write writes the binary representation of data into w. +// Data must be a fixed-size value or a slice of fixed-size +// values, or a pointer to such data. +// Bytes written to w are encoded using the specified byte order +// and read from successive fields of the data. +// When writing structs, zero values are written for fields +// with blank (_) field names. +func Write(w io.Writer, order ByteOrder, data interface{}) error { + // Fast path for basic types and slices. + if n := intDataSize(data); n != 0 { + var b [8]byte + var bs []byte + if n > len(b) { + bs = make([]byte, n) + } else { + bs = b[:n] + } + switch v := data.(type) { + case *int8: + bs = b[:1] + b[0] = byte(*v) + case int8: + bs = b[:1] + b[0] = byte(v) + case []int8: + for i, x := range v { + bs[i] = byte(x) + } + case *uint8: + bs = b[:1] + b[0] = *v + case uint8: + bs = b[:1] + b[0] = byte(v) + case []uint8: + bs = v + case *int16: + bs = b[:2] + order.PutUint16(bs, uint16(*v)) + case int16: + bs = b[:2] + order.PutUint16(bs, uint16(v)) + case []int16: + for i, x := range v { + order.PutUint16(bs[2*i:], uint16(x)) + } + case *uint16: + bs = b[:2] + order.PutUint16(bs, *v) + case uint16: + bs = b[:2] + order.PutUint16(bs, v) + case []uint16: + for i, x := range v { + order.PutUint16(bs[2*i:], x) + } + case *int32: + bs = b[:4] + order.PutUint32(bs, uint32(*v)) + case int32: + bs = b[:4] + order.PutUint32(bs, uint32(v)) + case []int32: + for i, x := range v { + order.PutUint32(bs[4*i:], uint32(x)) + } + case *uint32: + bs = b[:4] + order.PutUint32(bs, *v) + case uint32: + bs = b[:4] + order.PutUint32(bs, v) + case []uint32: + for i, x := range v { + order.PutUint32(bs[4*i:], x) + } + case *int64: + bs = b[:8] + order.PutUint64(bs, uint64(*v)) + case int64: + bs = b[:8] + order.PutUint64(bs, uint64(v)) + case []int64: + for i, x := range v { + order.PutUint64(bs[8*i:], uint64(x)) + } + case *uint64: + bs = b[:8] + order.PutUint64(bs, *v) + case uint64: + bs = b[:8] + order.PutUint64(bs, v) + case []uint64: + for i, x := range v { + order.PutUint64(bs[8*i:], x) + } + } + _, err := w.Write(bs) + return err + } + + // Fallback to reflect-based encoding. + v := reflect.Indirect(reflect.ValueOf(data)) + size := dataSize(v) + if size < 0 { + return errors.New("binary.Write: invalid type " + reflect.TypeOf(data).String()) + } + buf := make([]byte, size) + e := &encoder{order: order, buf: buf} + e.value(v) + _, err := w.Write(buf) + return err +} + +// Size returns how many bytes Write would generate to encode the value v, which +// must be a fixed-size value or a slice of fixed-size values, or a pointer to such data. +// If v is neither of these, Size returns -1. +func Size(v interface{}) int { + return dataSize(reflect.Indirect(reflect.ValueOf(v))) +} + +// dataSize returns the number of bytes the actual data represented by v occupies in memory. +// For compound structures, it sums the sizes of the elements. Thus, for instance, for a slice +// it returns the length of the slice times the element size and does not count the memory +// occupied by the header. If the type of v is not acceptable, dataSize returns -1. +func dataSize(v reflect.Value) int { + if v.Kind() == reflect.Slice { + if s := sizeof(v.Type().Elem()); s >= 0 { + return s * v.Len() + } + return -1 + } + return sizeof(v.Type()) +} + +// sizeof returns the size >= 0 of variables for the given type or -1 if the type is not acceptable. +func sizeof(t reflect.Type) int { + switch t.Kind() { + case reflect.Array: + if s := sizeof(t.Elem()); s >= 0 { + return s * t.Len() + } + + case reflect.Struct: + sum := 0 + for i, n := 0, t.NumField(); i < n; i++ { + s := sizeof(t.Field(i).Type) + if s < 0 { + return -1 + } + sum += s + } + return sum + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Ptr: + return int(t.Size()) + } + + return -1 +} + +type coder struct { + order ByteOrder + buf []byte +} + +type decoder coder +type encoder coder + +func (d *decoder) uint8() uint8 { + x := d.buf[0] + d.buf = d.buf[1:] + return x +} + +func (e *encoder) uint8(x uint8) { + e.buf[0] = x + e.buf = e.buf[1:] +} + +func (d *decoder) uint16() uint16 { + x := d.order.Uint16(d.buf[0:2]) + d.buf = d.buf[2:] + return x +} + +func (e *encoder) uint16(x uint16) { + e.order.PutUint16(e.buf[0:2], x) + e.buf = e.buf[2:] +} + +func (d *decoder) uint32() uint32 { + x := d.order.Uint32(d.buf[0:4]) + d.buf = d.buf[4:] + return x +} + +func (e *encoder) uint32(x uint32) { + e.order.PutUint32(e.buf[0:4], x) + e.buf = e.buf[4:] +} + +func (d *decoder) uint64() uint64 { + x := d.order.Uint64(d.buf[0:8]) + d.buf = d.buf[8:] + return x +} + +func (e *encoder) uint64(x uint64) { + e.order.PutUint64(e.buf[0:8], x) + e.buf = e.buf[8:] +} + +func (d *decoder) int8() int8 { return int8(d.uint8()) } + +func (e *encoder) int8(x int8) { e.uint8(uint8(x)) } + +func (d *decoder) int16() int16 { return int16(d.uint16()) } + +func (e *encoder) int16(x int16) { e.uint16(uint16(x)) } + +func (d *decoder) int32() int32 { return int32(d.uint32()) } + +func (e *encoder) int32(x int32) { e.uint32(uint32(x)) } + +func (d *decoder) int64() int64 { return int64(d.uint64()) } + +func (e *encoder) int64(x int64) { e.uint64(uint64(x)) } + +func (d *decoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // Note: Calling v.CanSet() below is an optimization. + // It would be sufficient to check the field name, + // but creating the StructField info for each field is + // costly (run "go test -bench=ReadStruct" and compare + // results when making changes to this code). + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + d.value(v) + } else { + d.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Int8: + v.SetInt(int64(d.int8())) + case reflect.Int16: + v.SetInt(int64(d.int16())) + case reflect.Int32: + v.SetInt(int64(d.int32())) + case reflect.Int64: + v.SetInt(d.int64()) + + case reflect.Uint8: + v.SetUint(uint64(d.uint8())) + case reflect.Uint16: + v.SetUint(uint64(d.uint16())) + case reflect.Uint32: + v.SetUint(uint64(d.uint32())) + case reflect.Uint64: + v.SetUint(d.uint64()) + + case reflect.Float32: + v.SetFloat(float64(math.Float32frombits(d.uint32()))) + case reflect.Float64: + v.SetFloat(math.Float64frombits(d.uint64())) + + case reflect.Complex64: + v.SetComplex(complex( + float64(math.Float32frombits(d.uint32())), + float64(math.Float32frombits(d.uint32())), + )) + case reflect.Complex128: + v.SetComplex(complex( + math.Float64frombits(d.uint64()), + math.Float64frombits(d.uint64()), + )) + } +} + +func (e *encoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // see comment for corresponding code in decoder.value() + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + e.value(v) + } else { + e.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch v.Type().Kind() { + case reflect.Int8: + e.int8(int8(v.Int())) + case reflect.Int16: + e.int16(int16(v.Int())) + case reflect.Int32: + e.int32(int32(v.Int())) + case reflect.Int64: + e.int64(v.Int()) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch v.Type().Kind() { + case reflect.Uint8: + e.uint8(uint8(v.Uint())) + case reflect.Uint16: + e.uint16(uint16(v.Uint())) + case reflect.Uint32: + e.uint32(uint32(v.Uint())) + case reflect.Uint64: + e.uint64(v.Uint()) + } + + case reflect.Float32, reflect.Float64: + switch v.Type().Kind() { + case reflect.Float32: + e.uint32(math.Float32bits(float32(v.Float()))) + case reflect.Float64: + e.uint64(math.Float64bits(v.Float())) + } + + case reflect.Complex64, reflect.Complex128: + switch v.Type().Kind() { + case reflect.Complex64: + x := v.Complex() + e.uint32(math.Float32bits(float32(real(x)))) + e.uint32(math.Float32bits(float32(imag(x)))) + case reflect.Complex128: + x := v.Complex() + e.uint64(math.Float64bits(real(x))) + e.uint64(math.Float64bits(imag(x))) + } + } +} + +func (d *decoder) skip(v reflect.Value) { + d.buf = d.buf[dataSize(v):] +} + +func (e *encoder) skip(v reflect.Value) { + n := dataSize(v) + for i := range e.buf[0:n] { + e.buf[i] = 0 + } + e.buf = e.buf[n:] +} + +// intDataSize returns the size of the data required to represent the data when encoded. +// It returns zero if the type cannot be implemented by the fast path in Read or Write. +func intDataSize(data interface{}) int { + switch data := data.(type) { + case int8, *int8, *uint8: + return 1 + case []int8: + return len(data) + case []uint8: + return len(data) + case int16, *int16, *uint16: + return 2 + case []int16: + return 2 * len(data) + case []uint16: + return 2 * len(data) + case int32, *int32, *uint32: + return 4 + case []int32: + return 4 * len(data) + case []uint32: + return 4 * len(data) + case int64, *int64, *uint64: + return 8 + case []int64: + return 8 * len(data) + case []uint64: + return 8 * len(data) + } + return 0 +} diff --git a/plugins/system/ps/process/process.go b/plugins/system/ps/process/process.go new file mode 100644 index 000000000..7b4b877df --- /dev/null +++ b/plugins/system/ps/process/process.go @@ -0,0 +1,142 @@ +package process + +import ( + "encoding/json" + "runtime" + "time" + + cpu "github.com/shirou/gopsutil/cpu" +) + +type Process struct { + Pid int32 `json:"pid"` + name string + status string + numCtxSwitches *NumCtxSwitchesStat + uids []int32 + gids []int32 + numThreads int32 + memInfo *MemoryInfoStat + + lastCPUTimes *cpu.CPUTimesStat + lastCPUTime time.Time +} + +type OpenFilesStat struct { + Path string `json:"path"` + Fd uint64 `json:"fd"` +} + +type MemoryInfoStat struct { + RSS uint64 `json:"rss"` // bytes + VMS uint64 `json:"vms"` // bytes + Swap uint64 `json:"swap"` // bytes +} + +type RlimitStat struct { + Resource int32 `json:"resource"` + Soft int32 `json:"soft"` + Hard int32 `json:"hard"` +} + +type IOCountersStat struct { + ReadCount uint64 `json:"read_count"` + WriteCount uint64 `json:"write_count"` + ReadBytes uint64 `json:"read_bytes"` + WriteBytes uint64 `json:"write_bytes"` +} + +type NumCtxSwitchesStat struct { + Voluntary int64 `json:"voluntary"` + Involuntary int64 `json:"involuntary"` +} + +func (p Process) String() string { + s, _ := json.Marshal(p) + return string(s) +} + +func (o OpenFilesStat) String() string { + s, _ := json.Marshal(o) + return string(s) +} + +func (m MemoryInfoStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} + +func (r RlimitStat) String() string { + s, _ := json.Marshal(r) + return string(s) +} + +func (i IOCountersStat) String() string { + s, _ := json.Marshal(i) + return string(s) +} + +func (p NumCtxSwitchesStat) String() string { + s, _ := json.Marshal(p) + return string(s) +} + +func PidExists(pid int32) (bool, error) { + pids, err := Pids() + if err != nil { + return false, err + } + + for _, i := range pids { + if i == pid { + return true, err + } + } + + return false, err +} + +// If interval is 0, return difference from last call(non-blocking). +// If interval > 0, wait interval sec and return diffrence between start and end. +func (p *Process) CPUPercent(interval time.Duration) (float64, error) { + numcpu := runtime.NumCPU() + calculate := func(t1, t2 *cpu.CPUTimesStat, delta float64) float64 { + if delta == 0 { + return 0 + } + delta_proc := (t2.User - t1.User) + (t2.System - t1.System) + overall_percent := ((delta_proc / delta) * 100) * float64(numcpu) + return overall_percent + } + + cpuTimes, err := p.CPUTimes() + if err != nil { + return 0, err + } + + if interval > 0 { + p.lastCPUTimes = cpuTimes + p.lastCPUTime = time.Now() + time.Sleep(interval) + cpuTimes, err = p.CPUTimes() + if err != nil { + return 0, err + } + } else { + if p.lastCPUTimes == nil { + // invoked first time + p.lastCPUTimes, err = p.CPUTimes() + if err != nil { + return 0, err + } + p.lastCPUTime = time.Now() + return 0, nil + } + } + + delta := (time.Now().Sub(p.lastCPUTime).Seconds()) * float64(numcpu) + ret := calculate(p.lastCPUTimes, cpuTimes, float64(delta)) + p.lastCPUTimes = cpuTimes + p.lastCPUTime = time.Now() + return ret, nil +} diff --git a/plugins/system/ps/process/process_darwin.go b/plugins/system/ps/process/process_darwin.go new file mode 100644 index 000000000..cd661c17a --- /dev/null +++ b/plugins/system/ps/process/process_darwin.go @@ -0,0 +1,359 @@ +// +build darwin + +package process + +import ( + "bytes" + "os/exec" + "strconv" + "strings" + "syscall" + "unsafe" + + common "github.com/shirou/gopsutil/common" + cpu "github.com/shirou/gopsutil/cpu" + net "github.com/shirou/gopsutil/net" +) + +// copied from sys/sysctl.h +const ( + CTLKern = 1 // "high kernel": proc, limits + KernProc = 14 // struct: process entries + KernProcPID = 1 // by process id + KernProcProc = 8 // only return procs + KernProcAll = 0 // everything + KernProcPathname = 12 // path to executable +) + +type _Ctype_struct___0 struct { + Pad uint64 +} + +// MemoryInfoExStat is different between OSes +type MemoryInfoExStat struct { +} + +type MemoryMapsStat struct { +} + +func Pids() ([]int32, error) { + var ret []int32 + + pids, err := callPs("pid", 0) + if err != nil { + return ret, err + } + + for _, pid := range pids { + v, err := strconv.Atoi(pid[0]) + if err != nil { + return ret, err + } + ret = append(ret, int32(v)) + } + + return ret, nil +} + +func (p *Process) Ppid() (int32, error) { + r, err := callPs("ppid", p.Pid) + v, err := strconv.Atoi(r[0][0]) + if err != nil { + return 0, err + } + + return int32(v), err +} +func (p *Process) Name() (string, error) { + k, err := p.getKProc() + if err != nil { + return "", err + } + + return common.IntToString(k.Proc.P_comm[:]), nil +} +func (p *Process) Exe() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) Cmdline() (string, error) { + r, err := callPs("command", p.Pid) + if err != nil { + return "", err + } + return strings.Join(r[0], " "), err +} +func (p *Process) CreateTime() (int64, error) { + return 0, common.NotImplementedError +} +func (p *Process) Cwd() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) Parent() (*Process, error) { + return p, common.NotImplementedError +} +func (p *Process) Status() (string, error) { + r, err := callPs("state", p.Pid) + if err != nil { + return "", err + } + + return r[0][0], err +} +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.Eproc.Pcred.P_ruid), int32(k.Eproc.Ucred.Uid), int32(k.Eproc.Pcred.P_svuid)) + + 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.Eproc.Pcred.P_rgid), int32(k.Eproc.Ucred.Ngroups), int32(k.Eproc.Pcred.P_svgid)) + + return gids, nil +} +func (p *Process) Terminal() (string, error) { + return "", common.NotImplementedError + /* + k, err := p.getKProc() + if err != nil { + return "", err + } + + ttyNr := uint64(k.Eproc.Tdev) + termmap, err := getTerminalMap() + if err != nil { + return "", err + } + + return termmap[ttyNr], nil + */ +} +func (p *Process) Nice() (int32, error) { + k, err := p.getKProc() + if err != nil { + return 0, err + } + return int32(k.Proc.P_nice), nil +} +func (p *Process) IOnice() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) Rlimit() ([]RlimitStat, error) { + var rlimit []RlimitStat + return rlimit, common.NotImplementedError +} +func (p *Process) IOCounters() (*IOCountersStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) NumFDs() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) NumThreads() (int32, error) { + return 0, common.NotImplementedError + + /* + 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, common.NotImplementedError +} +func (p *Process) CPUTimes() (*cpu.CPUTimesStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) CPUAffinity() ([]int32, error) { + return nil, common.NotImplementedError +} +func (p *Process) MemoryInfo() (*MemoryInfoStat, error) { + r, err := callPs("rss,vsize,pagein", p.Pid) + if err != nil { + return nil, err + } + rss, err := strconv.Atoi(r[0][0]) + if err != nil { + return nil, err + } + vms, err := strconv.Atoi(r[0][1]) + if err != nil { + return nil, err + } + pagein, err := strconv.Atoi(r[0][2]) + if err != nil { + return nil, err + } + + ret := &MemoryInfoStat{ + RSS: uint64(rss), + VMS: uint64(vms), + Swap: uint64(pagein), + } + + return ret, nil +} +func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) MemoryPercent() (float32, error) { + return 0, common.NotImplementedError +} + +func (p *Process) Children() ([]*Process, error) { + return nil, common.NotImplementedError +} + +func (p *Process) OpenFiles() ([]OpenFilesStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) Connections() ([]net.NetConnectionStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) IsRunning() (bool, error) { + return true, common.NotImplementedError +} +func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { + var ret []MemoryMapsStat + return &ret, common.NotImplementedError +} + +func copyParams(k *KinfoProc, p *Process) error { + + return nil +} + +func processes() ([]Process, error) { + results := make([]Process, 0, 50) + + mib := []int32{CTLKern, KernProc, KernProcAll, 0} + buf, length, err := common.CallSyscall(mib) + if err != nil { + return results, err + } + + // get kinfo_proc size + k := KinfoProc{} + procinfoLen := int(unsafe.Sizeof(k)) + count := int(length / uint64(procinfoLen)) + /* + fmt.Println(length, procinfoLen, count) + b := buf[0*procinfoLen : 0*procinfoLen+procinfoLen] + fmt.Println(b) + kk, err := parseKinfoProc(b) + fmt.Printf("%#v", kk) + */ + + // 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.Proc.P_pid)) + 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 := Read(br, LittleEndian, &k) + if err != nil { + return k, err + } + + return k, nil +} + +func (p *Process) getKProc() (*KinfoProc, error) { + mib := []int32{CTLKern, KernProc, KernProcPID, p.Pid} + procK := KinfoProc{} + length := uint64(unsafe.Sizeof(procK)) + buf := make([]byte, length) + _, _, syserr := syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(len(mib)), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if syserr != 0 { + return nil, syserr + } + 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 +} + +// call ps command. +// Return value deletes Header line(you must not input wrong arg). +// And splited by Space. Caller have responsibility to manage. +// If passed arg pid is 0, get information from all process. +func callPs(arg string, pid int32) ([][]string, error) { + var cmd []string + if pid == 0 { // will get from all processes. + cmd = []string{"-x", "-o", arg} + } else { + cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))} + } + out, err := exec.Command("/bin/ps", cmd...).Output() + if err != nil { + return [][]string{}, err + } + lines := strings.Split(string(out), "\n") + + var ret [][]string + for _, l := range lines[1:] { + var lr []string + for _, r := range strings.Split(l, " ") { + if r == "" { + continue + } + lr = append(lr, strings.TrimSpace(r)) + } + if len(lr) != 0 { + ret = append(ret, lr) + } + } + + return ret, nil +} diff --git a/plugins/system/ps/process/process_darwin_amd64.go b/plugins/system/ps/process/process_darwin_amd64.go new file mode 100644 index 000000000..7ac7bdd6b --- /dev/null +++ b/plugins/system/ps/process/process_darwin_amd64.go @@ -0,0 +1,234 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_darwin.go + +package process + +const ( + sizeofPtr = 0x8 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x8 + sizeofLongLong = 0x8 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int64 + _C_long_long int64 +) + +type Timespec struct { + Sec int64 + Nsec int64 +} + +type Timeval struct { + Sec int64 + Usec int32 + Pad_cgo_0 [4]byte +} + +type Rusage struct { + Utime Timeval + Stime Timeval + Maxrss int64 + Ixrss int64 + Idrss int64 + Isrss int64 + Minflt int64 + Majflt int64 + Nswap int64 + Inblock int64 + Oublock int64 + Msgsnd int64 + Msgrcv int64 + Nsignals int64 + Nvcsw int64 + Nivcsw int64 +} + +type Rlimit struct { + Cur uint64 + Max uint64 +} + +type UGid_t uint32 + +type KinfoProc struct { + Proc ExternProc + Eproc Eproc +} + +type Eproc struct { + Paddr *uint64 + Sess *Session + Pcred Upcred + Ucred Uucred + Pad_cgo_0 [4]byte + Vm Vmspace + Ppid int32 + Pgid int32 + Jobc int16 + Pad_cgo_1 [2]byte + Tdev int32 + Tpgid int32 + Pad_cgo_2 [4]byte + Tsess *Session + Wmesg [8]int8 + Xsize int32 + Xrssize int16 + Xccount int16 + Xswrss int16 + Pad_cgo_3 [2]byte + Flag int32 + Login [12]int8 + Spare [4]int32 + Pad_cgo_4 [4]byte +} + +type Proc struct{} + +type Session struct{} + +type ucred struct { + Link _Ctype_struct___0 + Ref uint64 + Posix Posix_cred + Label *Label + Audit Au_session +} + +type Uucred struct { + Ref int32 + Uid uint32 + Ngroups int16 + Pad_cgo_0 [2]byte + Groups [16]uint32 +} + +type Upcred struct { + Pc_lock [72]int8 + Pc_ucred *ucred + P_ruid uint32 + P_svuid uint32 + P_rgid uint32 + P_svgid uint32 + P_refcnt int32 + Pad_cgo_0 [4]byte +} + +type Vmspace struct { + Dummy int32 + Pad_cgo_0 [4]byte + Dummy2 *int8 + Dummy3 [5]int32 + Pad_cgo_1 [4]byte + Dummy4 [3]*int8 +} + +type Sigacts struct{} + +type ExternProc struct { + P_un [16]byte + P_vmspace uint64 + P_sigacts uint64 + Pad_cgo_0 [3]byte + P_flag int32 + P_stat int8 + P_pid int32 + P_oppid int32 + P_dupfd int32 + Pad_cgo_1 [4]byte + User_stack uint64 + Exit_thread uint64 + P_debugger int32 + Sigwait int32 + P_estcpu uint32 + P_cpticks int32 + P_pctcpu uint32 + Pad_cgo_2 [4]byte + P_wchan uint64 + P_wmesg uint64 + P_swtime uint32 + P_slptime uint32 + P_realtimer Itimerval + P_rtime Timeval + P_uticks uint64 + P_sticks uint64 + P_iticks uint64 + P_traceflag int32 + Pad_cgo_3 [4]byte + P_tracep uint64 + P_siglist int32 + Pad_cgo_4 [4]byte + P_textvp uint64 + P_holdcnt int32 + P_sigmask uint32 + P_sigignore uint32 + P_sigcatch uint32 + P_priority uint8 + P_usrpri uint8 + P_nice int8 + P_comm [17]int8 + Pad_cgo_5 [4]byte + P_pgrp uint64 + P_addr uint64 + P_xstat uint16 + P_acflag uint16 + Pad_cgo_6 [4]byte + P_ru uint64 +} + +type Itimerval struct { + Interval Timeval + Value Timeval +} + +type Vnode struct{} + +type Pgrp struct{} + +type UserStruct struct{} + +type Au_session struct { + Aia_p *AuditinfoAddr + Mask AuMask +} + +type Posix_cred struct { + Uid uint32 + Ruid uint32 + Svuid uint32 + Ngroups int16 + Pad_cgo_0 [2]byte + Groups [16]uint32 + Rgid uint32 + Svgid uint32 + Gmuid uint32 + Flags int32 +} + +type Label struct{} + +type AuditinfoAddr struct { + Auid uint32 + Mask AuMask + Termid AuTidAddr + Asid int32 + Flags uint64 +} +type AuMask struct { + Success uint32 + Failure uint32 +} +type AuTidAddr struct { + Port int32 + Type uint32 + Addr [4]uint32 +} + +type UcredQueue struct { + Next *ucred + Prev **ucred +} diff --git a/plugins/system/ps/process/process_freebsd.go b/plugins/system/ps/process/process_freebsd.go new file mode 100644 index 000000000..5fce20838 --- /dev/null +++ b/plugins/system/ps/process/process_freebsd.go @@ -0,0 +1,263 @@ +// +build freebsd + +package process + +import ( + "bytes" + "encoding/binary" + "unsafe" + + common "github.com/shirou/gopsutil/common" + cpu "github.com/shirou/gopsutil/cpu" + net "github.com/shirou/gopsutil/net" +) + +// 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) { + 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 "", common.NotImplementedError +} +func (p *Process) Cmdline() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) CreateTime() (int64, error) { + return 0, common.NotImplementedError +} +func (p *Process) Cwd() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) Parent() (*Process, error) { + return p, common.NotImplementedError +} +func (p *Process) Status() (string, error) { + k, err := p.getKProc() + if err != nil { + return "", err + } + + return string(k.KiStat[:]), nil +} +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, common.NotImplementedError +} +func (p *Process) IOnice() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) Rlimit() ([]RlimitStat, error) { + var rlimit []RlimitStat + return rlimit, common.NotImplementedError +} +func (p *Process) IOCounters() (*IOCountersStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { + return nil, common.NotImplementedError +} +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 +} +func (p *Process) Threads() (map[string]string, error) { + ret := make(map[string]string, 0) + return ret, common.NotImplementedError +} +func (p *Process) CPUTimes() (*cpu.CPUTimesStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) CPUAffinity() ([]int32, error) { + return nil, common.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, common.NotImplementedError +} +func (p *Process) MemoryPercent() (float32, error) { + return 0, common.NotImplementedError +} + +func (p *Process) Children() ([]*Process, error) { + return nil, common.NotImplementedError +} + +func (p *Process) OpenFiles() ([]OpenFilesStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) Connections() ([]net.NetConnectionStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) IsRunning() (bool, error) { + return true, common.NotImplementedError +} +func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { + var ret []MemoryMapsStat + return &ret, common.NotImplementedError +} + +func copyParams(k *KinfoProc, p *Process) error { + + return nil +} + +func processes() ([]Process, error) { + results := make([]Process, 0, 50) + + mib := []int32{CTLKern, KernProc, KernProcProc, 0} + buf, length, err := common.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 (p *Process) getKProc() (*KinfoProc, error) { + mib := []int32{CTLKern, KernProc, KernProcPID, p.Pid} + + buf, length, err := common.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/plugins/system/ps/process/process_freebsd_386.go b/plugins/system/ps/process/process_freebsd_386.go new file mode 100644 index 000000000..4d9ebfdb3 --- /dev/null +++ b/plugins/system/ps/process/process_freebsd_386.go @@ -0,0 +1,96 @@ +// +build freebsd +// +build 386 + +package process + +// copied from sys/sysctl.h +const ( + CTLKern = 1 // "high kernel": proc, limits + KernProc = 14 // struct: process entries + KernProcPID = 1 // by process id + KernProcProc = 8 // only return procs + KernProcPathname = 12 // path to executable +) + +// copied from sys/user.h +type KinfoProc struct { + KiStructsize int32 + KiLayout int32 + KiArgs int32 + KiPaddr int32 + KiAddr int32 + KiTracep int32 + KiTextvp int32 + KiFd int32 + KiVmspace int32 + KiWchan int32 + 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 int32 + KiRssize int32 + KiSwrss int32 + KiTsize int32 + KiDsize int32 + KiSsize int32 + KiXstat [2]byte + KiAcflag [2]byte + KiPctcpu int32 + KiEstcpu int32 + KiSlptime int32 + KiSwtime int32 + KiCow int32 + KiRuntime int64 + KiStart [8]byte + KiChildtime [8]byte + KiFlag int32 + KiKflag int32 + 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 [72]byte + KiRusageCh [72]byte + KiPcb int32 + KiKstack int32 + KiUdata int32 + KiTdaddr int32 + KiSpareptrs [24]byte + KiSpareint64s [48]byte + KiSflag int32 + KiTdflags int32 +} diff --git a/plugins/system/ps/process/process_freebsd_amd64.go b/plugins/system/ps/process/process_freebsd_amd64.go new file mode 100644 index 000000000..0dafda2d2 --- /dev/null +++ b/plugins/system/ps/process/process_freebsd_amd64.go @@ -0,0 +1,96 @@ +// +build freebsd +// +build amd64 + +package process + +// copied from sys/sysctl.h +const ( + CTLKern = 1 // "high kernel": proc, limits + KernProc = 14 // struct: process entries + KernProcPID = 1 // by process id + KernProcProc = 8 // only return procs + KernProcPathname = 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/plugins/system/ps/process/process_linux.go b/plugins/system/ps/process/process_linux.go new file mode 100644 index 000000000..b7bd4e527 --- /dev/null +++ b/plugins/system/ps/process/process_linux.go @@ -0,0 +1,597 @@ +// +build linux + +package process + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "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" +) + +const ( + PrioProcess = 0 // linux/resource.h +) + +// MemoryInfoExStat is different between OSes +type MemoryInfoExStat struct { + RSS uint64 `json:"rss"` // bytes + VMS uint64 `json:"vms"` // bytes + Shared uint64 `json:"shared"` // bytes + Text uint64 `json:"text"` // bytes + Lib uint64 `json:"lib"` // bytes + Data uint64 `json:"data"` // bytes + Dirty uint64 `json:"dirty"` // bytes +} + +func (m MemoryInfoExStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} + +type MemoryMapsStat struct { + Path string `json:"path"` + Rss uint64 `json:"rss"` + Size uint64 `json:"size"` + Pss uint64 `json:"pss"` + SharedClean uint64 `json:"shared_clean"` + SharedDirty uint64 `json:"shared_dirty"` + PrivateClean uint64 `json:"private_clean"` + PrivateDirty uint64 `json:"private_dirty"` + Referenced uint64 `json:"referenced"` + Anonymous uint64 `json:"anonymous"` + Swap uint64 `json:"swap"` +} + +func (m MemoryMapsStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} + +// Create new Process instance +// This only stores Pid +func NewProcess(pid int32) (*Process, error) { + p := &Process{ + Pid: int32(pid), + } + err := p.fillFromStatus() + return p, err +} + +func (p *Process) Ppid() (int32, error) { + _, ppid, _, _, _, err := p.fillFromStat() + if err != nil { + return -1, err + } + return ppid, nil +} +func (p *Process) Name() (string, error) { + return p.name, nil +} +func (p *Process) Exe() (string, error) { + return p.fillFromExe() +} +func (p *Process) Cmdline() (string, error) { + return p.fillFromCmdline() +} +func (p *Process) CreateTime() (int64, error) { + _, _, _, createTime, _, err := p.fillFromStat() + if err != nil { + return 0, err + } + return createTime, nil +} + +func (p *Process) Cwd() (string, error) { + return p.fillFromCwd() +} +func (p *Process) Parent() (*Process, error) { + return nil, common.NotImplementedError +} +func (p *Process) Status() (string, error) { + return p.status, nil +} +func (p *Process) Uids() ([]int32, error) { + return p.uids, nil +} +func (p *Process) Gids() ([]int32, error) { + return p.gids, nil +} +func (p *Process) Terminal() (string, error) { + terminal, _, _, _, _, err := p.fillFromStat() + if err != nil { + return "", err + } + return terminal, nil +} +func (p *Process) Nice() (int32, error) { + _, _, _, _, nice, err := p.fillFromStat() + if err != nil { + return 0, err + } + return nice, nil +} +func (p *Process) IOnice() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) Rlimit() ([]RlimitStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) IOCounters() (*IOCountersStat, error) { + return p.fillFromIO() +} +func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { + return p.numCtxSwitches, nil +} +func (p *Process) NumFDs() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) NumThreads() (int32, error) { + return p.numThreads, nil +} +func (p *Process) Threads() (map[string]string, error) { + ret := make(map[string]string, 0) + return ret, nil +} +func (p *Process) CPUTimes() (*cpu.CPUTimesStat, error) { + _, _, cpuTimes, _, _, err := p.fillFromStat() + if err != nil { + return nil, err + } + return cpuTimes, nil +} +func (p *Process) CPUAffinity() ([]int32, error) { + return nil, common.NotImplementedError +} +func (p *Process) MemoryInfo() (*MemoryInfoStat, error) { + return p.memInfo, nil +} +func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { + _, memInfoEx, err := p.fillFromStatm() + if err != nil { + return nil, err + } + return memInfoEx, nil +} +func (p *Process) MemoryPercent() (float32, error) { + return 0, common.NotImplementedError +} + +func (p *Process) Children() ([]*Process, error) { + return nil, common.NotImplementedError +} + +func (p *Process) OpenFiles() ([]OpenFilesStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) Connections() ([]net.NetConnectionStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) IsRunning() (bool, error) { + return true, common.NotImplementedError +} + +// MemoryMaps get memory maps from /proc/(pid)/smaps +func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { + pid := p.Pid + var ret []MemoryMapsStat + smapsPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "smaps") + contents, err := ioutil.ReadFile(smapsPath) + if err != nil { + return nil, err + } + lines := strings.Split(string(contents), "\n") + + // function of parsing a block + getBlock := func(first_line []string, block []string) (MemoryMapsStat, error) { + m := MemoryMapsStat{} + m.Path = first_line[len(first_line)-1] + + for _, line := range block { + if strings.Contains(line, "VmFlags") { + continue + } + field := strings.Split(line, ":") + if len(field) < 2 { + continue + } + v := strings.Trim(field[1], " kB") // remove last "kB" + t, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return m, err + } + + switch field[0] { + case "Size": + m.Size = t + case "Rss": + m.Rss = t + case "Pss": + m.Pss = t + case "Shared_Clean": + m.SharedClean = t + case "Shared_Dirty": + m.SharedDirty = t + case "Private_Clean": + m.PrivateClean = t + case "Private_Dirty": + m.PrivateDirty = t + case "Referenced": + m.Referenced = t + case "Anonymous": + m.Anonymous = t + case "Swap": + m.Swap = t + } + } + return m, nil + } + + blocks := make([]string, 16) + for _, line := range lines { + field := strings.Split(line, " ") + if strings.HasSuffix(field[0], ":") == false { + // new block section + if len(blocks) > 0 { + g, err := getBlock(field, blocks) + if err != nil { + return &ret, err + } + ret = append(ret, g) + } + // starts new block + blocks = make([]string, 16) + } else { + blocks = append(blocks, line) + } + } + + return &ret, nil +} + +/** +** Internal functions +**/ + +// Get num_fds from /proc/(pid)/fd +func (p *Process) fillFromfd() (int32, []*OpenFilesStat, error) { + pid := p.Pid + statPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "fd") + d, err := os.Open(statPath) + if err != nil { + return 0, nil, err + } + defer d.Close() + fnames, err := d.Readdirnames(-1) + numFDs := int32(len(fnames)) + + openfiles := make([]*OpenFilesStat, numFDs) + for _, fd := range fnames { + fpath := filepath.Join(statPath, fd) + filepath, err := os.Readlink(fpath) + if err != nil { + continue + } + t, err := strconv.ParseUint(fd, 10, 64) + if err != nil { + return numFDs, openfiles, err + } + o := &OpenFilesStat{ + Path: filepath, + Fd: t, + } + openfiles = append(openfiles, o) + } + + return numFDs, openfiles, nil +} + +// Get cwd from /proc/(pid)/cwd +func (p *Process) fillFromCwd() (string, error) { + pid := p.Pid + cwdPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "cwd") + cwd, err := os.Readlink(cwdPath) + if err != nil { + return "", err + } + return string(cwd), nil +} + +// Get exe from /proc/(pid)/exe +func (p *Process) fillFromExe() (string, error) { + pid := p.Pid + exePath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "exe") + exe, err := os.Readlink(exePath) + if err != nil { + return "", err + } + return string(exe), nil +} + +// Get cmdline from /proc/(pid)/cmdline +func (p *Process) fillFromCmdline() (string, error) { + pid := p.Pid + cmdPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "cmdline") + cmdline, err := ioutil.ReadFile(cmdPath) + if err != nil { + return "", err + } + ret := strings.FieldsFunc(string(cmdline), func(r rune) bool { + if r == '\u0000' { + return true + } + return false + }) + + return strings.Join(ret, " "), nil +} + +// Get IO status from /proc/(pid)/io +func (p *Process) fillFromIO() (*IOCountersStat, error) { + pid := p.Pid + ioPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "io") + ioline, err := ioutil.ReadFile(ioPath) + if err != nil { + return nil, err + } + lines := strings.Split(string(ioline), "\n") + ret := &IOCountersStat{} + + for _, line := range lines { + field := strings.Fields(line) + if len(field) < 2 { + continue + } + t, err := strconv.ParseUint(field[1], 10, 64) + if err != nil { + return nil, err + } + param := field[0] + if strings.HasSuffix(param, ":") { + param = param[:len(param)-1] + } + switch param { + case "syscr": + ret.ReadCount = t + case "syscw": + ret.WriteCount = t + case "read_bytes": + ret.ReadBytes = t + case "write_bytes": + ret.WriteBytes = t + } + } + + return ret, nil +} + +// Get memory info from /proc/(pid)/statm +func (p *Process) fillFromStatm() (*MemoryInfoStat, *MemoryInfoExStat, error) { + pid := p.Pid + memPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "statm") + contents, err := ioutil.ReadFile(memPath) + if err != nil { + return nil, nil, err + } + fields := strings.Split(string(contents), " ") + + vms, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return nil, nil, err + } + rss, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return nil, nil, err + } + memInfo := &MemoryInfoStat{ + RSS: rss * PageSize, + VMS: vms * PageSize, + } + + shared, err := strconv.ParseUint(fields[2], 10, 64) + if err != nil { + return nil, nil, err + } + text, err := strconv.ParseUint(fields[3], 10, 64) + if err != nil { + return nil, nil, err + } + lib, err := strconv.ParseUint(fields[4], 10, 64) + if err != nil { + return nil, nil, err + } + dirty, err := strconv.ParseUint(fields[5], 10, 64) + if err != nil { + return nil, nil, err + } + + memInfoEx := &MemoryInfoExStat{ + RSS: rss * PageSize, + VMS: vms * PageSize, + Shared: shared * PageSize, + Text: text * PageSize, + Lib: lib * PageSize, + Dirty: dirty * PageSize, + } + + return memInfo, memInfoEx, nil +} + +// Get various status from /proc/(pid)/status +func (p *Process) fillFromStatus() error { + pid := p.Pid + statPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "status") + contents, err := ioutil.ReadFile(statPath) + if err != nil { + return err + } + lines := strings.Split(string(contents), "\n") + p.numCtxSwitches = &NumCtxSwitchesStat{} + p.memInfo = &MemoryInfoStat{} + for _, line := range lines { + tabParts := strings.SplitN(line, "\t", 2) + if len(tabParts) < 2 { + continue + } + value := tabParts[1] + switch strings.TrimRight(tabParts[0], ":") { + case "Name": + p.name = strings.Trim(value, " \t") + case "State": + // get between "(" and ")" + s := strings.Index(value, "(") + 1 + e := strings.Index(value, ")") + p.status = value[s:e] + case "Uid": + p.uids = make([]int32, 0, 4) + for _, i := range strings.Split(value, "\t") { + v, err := strconv.ParseInt(i, 10, 32) + if err != nil { + return err + } + p.uids = append(p.uids, int32(v)) + } + case "Gid": + p.gids = make([]int32, 0, 4) + for _, i := range strings.Split(value, "\t") { + v, err := strconv.ParseInt(i, 10, 32) + if err != nil { + return err + } + p.gids = append(p.gids, int32(v)) + } + case "Threads": + v, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return err + } + p.numThreads = int32(v) + case "voluntary_ctxt_switches": + v, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + p.numCtxSwitches.Voluntary = v + case "nonvoluntary_ctxt_switches": + v, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + p.numCtxSwitches.Involuntary = v + case "VmRSS": + value := strings.Trim(value, " kB") // remove last "kB" + v, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + p.memInfo.RSS = v * 1024 + case "VmSize": + value := strings.Trim(value, " kB") // remove last "kB" + v, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + p.memInfo.VMS = v * 1024 + case "VmSwap": + value := strings.Trim(value, " kB") // remove last "kB" + v, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + p.memInfo.Swap = v * 1024 + } + + } + return nil +} + +func (p *Process) fillFromStat() (string, int32, *cpu.CPUTimesStat, int64, int32, error) { + pid := p.Pid + statPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "stat") + contents, err := ioutil.ReadFile(statPath) + if err != nil { + return "", 0, nil, 0, 0, err + } + fields := strings.Fields(string(contents)) + + termmap, err := getTerminalMap() + terminal := "" + if err == nil { + t, err := strconv.ParseUint(fields[6], 10, 64) + if err != nil { + return "", 0, nil, 0, 0, err + } + terminal = termmap[t] + } + + ppid, err := strconv.ParseInt(fields[3], 10, 32) + if err != nil { + return "", 0, nil, 0, 0, err + } + utime, err := strconv.ParseFloat(fields[13], 64) + if err != nil { + return "", 0, nil, 0, 0, err + } + + stime, err := strconv.ParseFloat(fields[14], 64) + if err != nil { + return "", 0, nil, 0, 0, err + } + + cpuTimes := &cpu.CPUTimesStat{ + CPU: "cpu", + User: float64(utime / ClockTicks), + System: float64(stime / ClockTicks), + } + + bootTime, _ := host.BootTime() + t, err := strconv.ParseUint(fields[21], 10, 64) + if err != nil { + return "", 0, nil, 0, 0, err + } + + ctime := ((t / uint64(ClockTicks)) + uint64(bootTime)) * 1000 + createTime := int64(ctime) + + // p.Nice = mustParseInt32(fields[18]) + // use syscall instead of parse Stat file + snice, _ := syscall.Getpriority(PrioProcess, int(pid)) + nice := int32(snice) // FIXME: is this true? + + return terminal, int32(ppid), cpuTimes, createTime, nice, nil +} + +func Pids() ([]int32, error) { + var ret []int32 + + d, err := os.Open("/proc") + if err != nil { + return nil, err + } + defer d.Close() + + fnames, err := d.Readdirnames(-1) + if err != nil { + return nil, err + } + for _, fname := range fnames { + pid, err := strconv.ParseInt(fname, 10, 32) + if err != nil { + // if not numeric name, just skip + continue + } + ret = append(ret, int32(pid)) + } + + return ret, nil +} diff --git a/plugins/system/ps/process/process_linux_386.go b/plugins/system/ps/process/process_linux_386.go new file mode 100644 index 000000000..541b854c7 --- /dev/null +++ b/plugins/system/ps/process/process_linux_386.go @@ -0,0 +1,9 @@ +// +build linux +// +build 386 + +package process + +const ( + ClockTicks = 100 // C.sysconf(C._SC_CLK_TCK) + PageSize = 4096 // C.sysconf(C._SC_PAGE_SIZE) +) diff --git a/plugins/system/ps/process/process_linux_amd64.go b/plugins/system/ps/process/process_linux_amd64.go new file mode 100644 index 000000000..b4a4ce8b7 --- /dev/null +++ b/plugins/system/ps/process/process_linux_amd64.go @@ -0,0 +1,9 @@ +// +build linux +// +build amd64 + +package process + +const ( + ClockTicks = 100 // C.sysconf(C._SC_CLK_TCK) + PageSize = 4096 // C.sysconf(C._SC_PAGE_SIZE) +) diff --git a/plugins/system/ps/process/process_linux_arm.go b/plugins/system/ps/process/process_linux_arm.go new file mode 100644 index 000000000..c6123a489 --- /dev/null +++ b/plugins/system/ps/process/process_linux_arm.go @@ -0,0 +1,9 @@ +// +build linux +// +build arm + +package process + +const ( + ClockTicks = 100 // C.sysconf(C._SC_CLK_TCK) + PageSize = 4096 // C.sysconf(C._SC_PAGE_SIZE) +) diff --git a/plugins/system/ps/process/process_posix.go b/plugins/system/ps/process/process_posix.go new file mode 100644 index 000000000..38f06e9e7 --- /dev/null +++ b/plugins/system/ps/process/process_posix.go @@ -0,0 +1,102 @@ +// +build linux freebsd darwin + +package process + +import ( + "os" + "os/exec" + "os/user" + "strconv" + "strings" + "syscall" +) + +// POSIX +func getTerminalMap() (map[uint64]string, error) { + ret := make(map[uint64]string) + var termfiles []string + + d, err := os.Open("/dev") + if err != nil { + return nil, err + } + defer d.Close() + + devnames, err := d.Readdirnames(-1) + for _, devname := range devnames { + if strings.HasPrefix(devname, "/dev/tty") { + termfiles = append(termfiles, "/dev/tty/"+devname) + } + } + + ptsd, err := os.Open("/dev/pts") + if err != nil { + return nil, err + } + defer ptsd.Close() + + ptsnames, err := ptsd.Readdirnames(-1) + for _, ptsname := range ptsnames { + termfiles = append(termfiles, "/dev/pts/"+ptsname) + } + + for _, name := range termfiles { + stat := syscall.Stat_t{} + if err = syscall.Stat(name, &stat); err != nil { + return nil, err + } + rdev := uint64(stat.Rdev) + ret[rdev] = strings.Replace(name, "/dev", "", -1) + } + return ret, nil +} + +func (p *Process) SendSignal(sig syscall.Signal) error { + sigAsStr := "INT" + switch sig { + case syscall.SIGSTOP: + sigAsStr = "STOP" + case syscall.SIGCONT: + sigAsStr = "CONT" + case syscall.SIGTERM: + sigAsStr = "TERM" + case syscall.SIGKILL: + sigAsStr = "KILL" + } + + cmd := exec.Command("kill", "-s", sigAsStr, strconv.Itoa(int(p.Pid))) + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return err + } + + return nil +} + +func (p *Process) Suspend() error { + return p.SendSignal(syscall.SIGSTOP) +} +func (p *Process) Resume() error { + return p.SendSignal(syscall.SIGCONT) +} +func (p *Process) Terminate() error { + return p.SendSignal(syscall.SIGTERM) +} +func (p *Process) Kill() error { + return p.SendSignal(syscall.SIGKILL) +} +func (p *Process) Username() (string, error) { + uids, err := p.Uids() + if err != nil { + return "", err + } + if len(uids) > 0 { + u, err := user.LookupId(strconv.Itoa(int(uids[0]))) + if err != nil { + return "", err + } + return u.Username, nil + } + return "", nil +} diff --git a/plugins/system/ps/process/process_posix_test.go b/plugins/system/ps/process/process_posix_test.go new file mode 100644 index 000000000..eb9129220 --- /dev/null +++ b/plugins/system/ps/process/process_posix_test.go @@ -0,0 +1,19 @@ +// +build linux freebsd + +package process + +import ( + "os" + "syscall" + "testing" +) + +func Test_SendSignal(t *testing.T) { + checkPid := os.Getpid() + + p, _ := NewProcess(int32(checkPid)) + err := p.SendSignal(syscall.SIGCONT) + if err != nil { + t.Errorf("send signal %v", err) + } +} diff --git a/plugins/system/ps/process/process_test.go b/plugins/system/ps/process/process_test.go new file mode 100644 index 000000000..e3b902de2 --- /dev/null +++ b/plugins/system/ps/process/process_test.go @@ -0,0 +1,241 @@ +package process + +import ( + "os" + "runtime" + "strings" + "testing" + "time" +) + +func testGetProcess() Process { + checkPid := os.Getpid() // process.test + ret, _ := NewProcess(int32(checkPid)) + return *ret +} + +func Test_Pids(t *testing.T) { + ret, err := Pids() + if err != nil { + t.Errorf("error %v", err) + } + if len(ret) == 0 { + t.Errorf("could not get pids %v", ret) + } +} + +func Test_Pid_exists(t *testing.T) { + checkPid := os.Getpid() + + ret, err := PidExists(int32(checkPid)) + if err != nil { + t.Errorf("error %v", err) + } + + if ret == false { + t.Errorf("could not get process exists: %v", ret) + } +} + +func Test_NewProcess(t *testing.T) { + checkPid := os.Getpid() + + ret, err := NewProcess(int32(checkPid)) + if err != nil { + t.Errorf("error %v", err) + } + empty := &Process{} + if runtime.GOOS != "windows" { // Windows pid is 0 + if empty == ret { + t.Errorf("error %v", ret) + } + } + +} + +func Test_Process_memory_maps(t *testing.T) { + checkPid := os.Getpid() + + ret, err := NewProcess(int32(checkPid)) + + mmaps, err := ret.MemoryMaps(false) + if err != nil { + t.Errorf("memory map get error %v", err) + } + empty := MemoryMapsStat{} + for _, m := range *mmaps { + if m == empty { + t.Errorf("memory map get error %v", m) + } + } +} +func Test_Process_MemoryInfo(t *testing.T) { + p := testGetProcess() + + v, err := p.MemoryInfo() + if err != nil { + t.Errorf("geting ppid error %v", err) + } + empty := MemoryInfoStat{} + if v == nil || *v == empty { + t.Errorf("could not get memory info %v", v) + } +} + +func Test_Process_CmdLine(t *testing.T) { + p := testGetProcess() + + v, err := p.Cmdline() + if err != nil { + t.Errorf("geting ppid error %v", err) + } + if !strings.Contains(v, "process.test") { + t.Errorf("invalid cmd line %v", v) + } +} + +func Test_Process_Ppid(t *testing.T) { + p := testGetProcess() + + v, err := p.Ppid() + if err != nil { + t.Errorf("geting ppid error %v", err) + } + if v == 0 { + t.Errorf("return value is 0 %v", v) + } +} + +func Test_Process_Status(t *testing.T) { + p := testGetProcess() + + v, err := p.Status() + if err != nil { + t.Errorf("geting ppid error %v", err) + } + if !strings.HasPrefix(v, "S") && v != "running" && v != "sleeping" { + t.Errorf("could not get state %v", v) + } +} + +func Test_Process_Terminal(t *testing.T) { + p := testGetProcess() + + _, err := p.Terminal() + if err != nil { + t.Errorf("geting terminal error %v", err) + } + + /* + if v == "" { + t.Errorf("could not get terminal %v", v) + } + */ +} + +func Test_Process_IOCounters(t *testing.T) { + p := testGetProcess() + + v, err := p.IOCounters() + if err != nil { + t.Errorf("geting iocounter error %v", err) + return + } + empty := &IOCountersStat{} + if v == empty { + t.Errorf("error %v", v) + } +} + +func Test_Process_NumCtx(t *testing.T) { + p := testGetProcess() + + _, err := p.NumCtxSwitches() + if err != nil { + t.Errorf("geting numctx error %v", err) + return + } +} + +func Test_Process_Nice(t *testing.T) { + p := testGetProcess() + + n, err := p.Nice() + if err != nil { + t.Errorf("geting nice error %v", err) + } + if n != 0 && n != 20 && n != 8 { + t.Errorf("invalid nice: %d", n) + } +} +func Test_Process_NumThread(t *testing.T) { + p := testGetProcess() + + n, err := p.NumThreads() + if err != nil { + t.Errorf("geting NumThread error %v", err) + } + if n < 0 { + t.Errorf("invalid NumThread: %d", n) + } +} + +func Test_Process_Name(t *testing.T) { + p := testGetProcess() + + n, err := p.Name() + if err != nil { + t.Errorf("geting name error %v", err) + } + if !strings.Contains(n, "process.test") { + t.Errorf("invalid Exe %s", n) + } +} +func Test_Process_Exe(t *testing.T) { + p := testGetProcess() + + n, err := p.Exe() + if err != nil { + t.Errorf("geting Exe error %v", err) + } + if !strings.Contains(n, "process.test") { + t.Errorf("invalid Exe %s", n) + } +} + +func Test_Process_CpuPercent(t *testing.T) { + p := testGetProcess() + percent, err := p.CPUPercent(0) + if err != nil { + t.Errorf("error %v", err) + } + duration := time.Duration(1000) * time.Microsecond + time.Sleep(duration) + percent, err = p.CPUPercent(0) + if err != nil { + t.Errorf("error %v", err) + } + + numcpu := runtime.NumCPU() + // if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO + if percent < 0.0 { + t.Fatalf("CPUPercent value is invalid: %f, %d", percent, numcpu) + } +} + +func Test_Process_CpuPercentLoop(t *testing.T) { + p := testGetProcess() + numcpu := runtime.NumCPU() + + for i := 0; i < 2; i++ { + duration := time.Duration(100) * time.Microsecond + percent, err := p.CPUPercent(duration) + if err != nil { + t.Errorf("error %v", err) + } + // if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO + if percent < 0.0 { + t.Fatalf("CPUPercent value is invalid: %f, %d", percent, numcpu) + } + } +} diff --git a/plugins/system/ps/process/process_windows.go b/plugins/system/ps/process/process_windows.go new file mode 100644 index 000000000..f31fe7795 --- /dev/null +++ b/plugins/system/ps/process/process_windows.go @@ -0,0 +1,305 @@ +// +build windows + +package process + +import ( + "errors" + "fmt" + "strconv" + "syscall" + "unsafe" + + "github.com/shirou/w32" + + common "github.com/shirou/gopsutil/common" + cpu "github.com/shirou/gopsutil/cpu" + net "github.com/shirou/gopsutil/net" +) + +const ( + NoMoreFiles = 0x12 + MaxPathLength = 260 +) + +type SystemProcessInformation struct { + NextEntryOffset uint64 + NumberOfThreads uint64 + Reserved1 [48]byte + Reserved2 [3]byte + UniqueProcessID uintptr + Reserved3 uintptr + HandleCount uint64 + Reserved4 [4]byte + Reserved5 [11]byte + PeakPagefileUsage uint64 + PrivatePageCount uint64 + Reserved6 [6]uint64 +} + +// Memory_info_ex 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 _, proc := range procs { + ret = append(ret, proc.Pid) + } + + return ret, nil +} + +func (p *Process) Ppid() (int32, error) { + ret, _, _, err := p.getFromSnapProcess(p.Pid) + if err != nil { + return 0, err + } + return ret, nil +} +func (p *Process) Name() (string, error) { + query := fmt.Sprintf("ProcessId = %d", p.Pid) + lines, err := common.GetWmic("process", "where", query, "get", "Name") + if err != nil { + return "", err + } + if len(lines) == 0 { + return "", fmt.Errorf("could not get Name") + } + return lines[0][1], nil +} +func (p *Process) Exe() (string, error) { + query := fmt.Sprintf("ProcessId = %d", p.Pid) + lines, err := common.GetWmic("process", "where", query, "get", "ExecutablePath") + if err != nil { + return "", err + } + if len(lines) == 0 { + return "", fmt.Errorf("could not get ExecutablePath") + } + return lines[0][1], nil +} +func (p *Process) Cmdline() (string, error) { + query := fmt.Sprintf("ProcessId = %d", p.Pid) + lines, err := common.GetWmic("process", "where", query, "get", "CommandLine") + if err != nil { + return "", err + } + if len(lines) == 0 { + return "", fmt.Errorf("could not get command line") + } + + return lines[0][1], nil +} +func (p *Process) Cwd() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) Parent() (*Process, error) { + return p, common.NotImplementedError +} +func (p *Process) Status() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) Username() (string, error) { + return "", common.NotImplementedError +} +func (p *Process) Uids() ([]int32, error) { + var uids []int32 + + return uids, common.NotImplementedError +} +func (p *Process) Gids() ([]int32, error) { + var gids []int32 + return gids, common.NotImplementedError +} +func (p *Process) Terminal() (string, error) { + return "", common.NotImplementedError +} + +// Nice returnes priority in Windows +func (p *Process) Nice() (int32, error) { + query := fmt.Sprintf("ProcessId = %d", p.Pid) + lines, err := common.GetWmic("process", "where", query, "get", "Priority") + if err != nil { + return 0, err + } + if len(lines) == 0 { + return 0, fmt.Errorf("could not get command line") + } + priority, err := strconv.Atoi(lines[0][1]) + if err != nil { + return 0, err + } + + return int32(priority), nil +} +func (p *Process) IOnice() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) Rlimit() ([]RlimitStat, error) { + var rlimit []RlimitStat + + return rlimit, common.NotImplementedError +} +func (p *Process) IOCounters() (*IOCountersStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) NumFDs() (int32, error) { + return 0, common.NotImplementedError +} +func (p *Process) NumThreads() (int32, error) { + query := fmt.Sprintf("ProcessId = %d", p.Pid) + lines, err := common.GetWmic("process", "where", query, "get", "ThreadCount") + if err != nil { + return 0, err + } + if len(lines) == 0 { + return 0, fmt.Errorf("could not get command line") + } + count, err := strconv.Atoi(lines[0][1]) + if err != nil { + return 0, err + } + + return int32(count), nil +} +func (p *Process) Threads() (map[string]string, error) { + ret := make(map[string]string, 0) + return ret, common.NotImplementedError +} +func (p *Process) CPUTimes() (*cpu.CPUTimesStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) CPUAffinity() ([]int32, error) { + return nil, common.NotImplementedError +} +func (p *Process) MemoryInfo() (*MemoryInfoStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { + return nil, common.NotImplementedError +} +func (p *Process) MemoryPercent() (float32, error) { + return 0, common.NotImplementedError +} + +func (p *Process) Children() ([]*Process, error) { + return nil, common.NotImplementedError +} + +func (p *Process) OpenFiles() ([]OpenFilesStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) Connections() ([]net.NetConnectionStat, error) { + return nil, common.NotImplementedError +} + +func (p *Process) IsRunning() (bool, error) { + return true, common.NotImplementedError +} + +func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { + ret := make([]MemoryMapsStat, 0) + return &ret, common.NotImplementedError +} + +func NewProcess(pid int32) (*Process, error) { + p := &Process{Pid: pid} + + return p, nil +} + +func (p *Process) SendSignal(sig syscall.Signal) error { + return common.NotImplementedError +} + +func (p *Process) Suspend() error { + return common.NotImplementedError +} +func (p *Process) Resume() error { + return common.NotImplementedError +} +func (p *Process) Terminate() error { + return common.NotImplementedError +} +func (p *Process) Kill() error { + return common.NotImplementedError +} + +func (p *Process) getFromSnapProcess(pid int32) (int32, int32, string, error) { + snap := w32.CreateToolhelp32Snapshot(w32.TH32CS_SNAPPROCESS, uint32(pid)) + if snap == 0 { + return 0, 0, "", syscall.GetLastError() + } + defer w32.CloseHandle(snap) + var pe32 w32.PROCESSENTRY32 + pe32.DwSize = uint32(unsafe.Sizeof(pe32)) + if w32.Process32First(snap, &pe32) == false { + return 0, 0, "", syscall.GetLastError() + } + + if pe32.Th32ProcessID == uint32(pid) { + szexe := syscall.UTF16ToString(pe32.SzExeFile[:]) + return int32(pe32.Th32ParentProcessID), int32(pe32.CntThreads), szexe, nil + } + + for w32.Process32Next(snap, &pe32) { + if pe32.Th32ProcessID == uint32(pid) { + szexe := syscall.UTF16ToString(pe32.SzExeFile[:]) + return int32(pe32.Th32ParentProcessID), int32(pe32.CntThreads), szexe, nil + } + } + return 0, 0, "", errors.New("Couldn't find pid:" + string(pid)) +} + +// Get processes +func processes() ([]*Process, error) { + lines, err := common.GetWmic("process", "get", "processid") + if err != nil { + return nil, err + } + var results []*Process + for _, l := range lines { + pid, err := strconv.Atoi(l[1]) + if err != nil { + continue + } + p, err := NewProcess(int32(pid)) + if err != nil { + continue + } + results = append(results, p) + } + + return results, nil +} + +func getProcInfo(pid int32) (*SystemProcessInformation, error) { + initialBufferSize := uint64(0x4000) + bufferSize := initialBufferSize + buffer := make([]byte, bufferSize) + + var sysProcInfo SystemProcessInformation + ret, _, _ := common.ProcNtQuerySystemInformation.Call( + uintptr(unsafe.Pointer(&sysProcInfo)), + uintptr(unsafe.Pointer(&buffer[0])), + uintptr(unsafe.Pointer(&bufferSize)), + uintptr(unsafe.Pointer(&bufferSize))) + if ret != 0 { + return nil, syscall.GetLastError() + } + + return &sysProcInfo, nil +} diff --git a/plugins/system/ps/process/types_darwin.go b/plugins/system/ps/process/types_darwin.go new file mode 100644 index 000000000..21216cd09 --- /dev/null +++ b/plugins/system/ps/process/types_darwin.go @@ -0,0 +1,160 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Hand Writing +// - all pointer in ExternProc to uint64 + +// +build ignore + +/* +Input to cgo -godefs. +*/ + +// +godefs map struct_in_addr [4]byte /* in_addr */ +// +godefs map struct_in6_addr [16]byte /* in6_addr */ +// +godefs map struct_ [16]byte /* in6_addr */ + +package process + +/* +#define __DARWIN_UNIX03 0 +#define KERNEL +#define _DARWIN_USE_64_BIT_INODE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + sizeofPtr = sizeof(void*), +}; + +union sockaddr_all { + struct sockaddr s1; // this one gets used for fields + struct sockaddr_in s2; // these pad it out + struct sockaddr_in6 s3; + struct sockaddr_un s4; + struct sockaddr_dl s5; +}; + +struct sockaddr_any { + struct sockaddr addr; + char pad[sizeof(union sockaddr_all) - sizeof(struct sockaddr)]; +}; + +struct ucred_queue { + struct ucred *tqe_next; + struct ucred **tqe_prev; + TRACEBUF +}; + +*/ +import "C" + +// Machine characteristics; for internal use. + +const ( + sizeofPtr = C.sizeofPtr + sizeofShort = C.sizeof_short + sizeofInt = C.sizeof_int + sizeofLong = C.sizeof_long + sizeofLongLong = C.sizeof_longlong +) + +// Basic types + +type ( + _C_short C.short + _C_int C.int + _C_long C.long + _C_long_long C.longlong +) + +// Time + +type Timespec C.struct_timespec + +type Timeval C.struct_timeval + +// Processes + +type Rusage C.struct_rusage + +type Rlimit C.struct_rlimit + +type UGid_t C.gid_t + +type KinfoProc C.struct_kinfo_proc + +type Eproc C.struct_eproc + +type Proc C.struct_proc + +type Session C.struct_session + +type ucred C.struct_ucred + +type Uucred C.struct__ucred + +type Upcred C.struct__pcred + +type Vmspace C.struct_vmspace + +type Sigacts C.struct_sigacts + +type ExternProc C.struct_extern_proc + +type Itimerval C.struct_itimerval + +type Vnode C.struct_vnode + +type Pgrp C.struct_pgrp + +type UserStruct C.struct_user + +type Au_session C.struct_au_session + +type Posix_cred C.struct_posix_cred + +type Label C.struct_label + +type AuditinfoAddr C.struct_auditinfo_addr +type AuMask C.struct_au_mask +type AuTidAddr C.struct_au_tid_addr + +// TAILQ(ucred) +type UcredQueue C.struct_ucred_queue diff --git a/plugins/system/ps/windows_memo.rst b/plugins/system/ps/windows_memo.rst new file mode 100644 index 000000000..38abed819 --- /dev/null +++ b/plugins/system/ps/windows_memo.rst @@ -0,0 +1,36 @@ +Windows memo +===================== + +Size +---------- + +DWORD + 32-bit unsigned integer +DWORDLONG + 64-bit unsigned integer +DWORD_PTR + unsigned long type for pointer precision +DWORD32 + 32-bit unsigned integer +DWORD64 + 64-bit unsigned integer +HALF_PTR + _WIN64 = int, else short +INT + 32-bit signed integer +INT_PTR + _WIN64 = __int64 else int +LONG + 32-bit signed integer +LONGLONG + 64-bit signed integer +LONG_PTR + _WIN64 = __int64 else long +SHORT + 16-bit integer +SIZE_T + maximum number of bytes to which a pointer can point. typedef ULONG_PTR SIZE_T; +SSIZE_T + signed version of SIZE_T. typedef LONG_PTR SSIZE_T; +WORD + 16-bit unsigned integer \ No newline at end of file diff --git a/plugins/system/system.go b/plugins/system/system.go index 6edcfe43c..0a933ca7f 100644 --- a/plugins/system/system.go +++ b/plugins/system/system.go @@ -2,7 +2,7 @@ package system import ( "github.com/influxdb/tivan/plugins" - "github.com/shirou/gopsutil/load" + "github.com/influxdb/tivan/plugins/system/ps/load" "github.com/vektra/cypress" ) diff --git a/plugins/system/system_test.go b/plugins/system/system_test.go index 1c136a567..2ee8802d7 100644 --- a/plugins/system/system_test.go +++ b/plugins/system/system_test.go @@ -3,96 +3,92 @@ package system import ( "testing" - "github.com/shirou/gopsutil/load" + "github.com/influxdb/tivan/plugins/system/ps/load" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/vektra/neko" ) -func TestSystemStats(t *testing.T) { - n := neko.Start(t) - +func TestSystemStats_GenerateLoad(t *testing.T) { var mps MockPS - n.CheckMock(&mps.Mock) + defer mps.AssertExpectations(t) - n.It("generates metrics from the system information", func() { - ss := &SystemStats{ps: &mps} + ss := &SystemStats{ps: &mps} - lv := &load.LoadAvgStat{ - Load1: 0.3, - Load5: 1.5, - Load15: 0.8, - } + lv := &load.LoadAvgStat{ + Load1: 0.3, + Load5: 1.5, + Load15: 0.8, + } - mps.On("LoadAvg").Return(lv, nil) + mps.On("LoadAvg").Return(lv, nil) - msgs, err := ss.Read() - require.NoError(t, err) + msgs, err := ss.Read() + require.NoError(t, err) - name, ok := msgs[0].GetString("name") - require.True(t, ok) + name, ok := msgs[0].GetString("name") + require.True(t, ok) - assert.Equal(t, "load1", name) + assert.Equal(t, "load1", name) - val, ok := msgs[0].GetFloat("value") - require.True(t, ok) + val, ok := msgs[0].GetFloat("value") + require.True(t, ok) - assert.Equal(t, 0.3, val) + assert.Equal(t, 0.3, val) - name, ok = msgs[1].GetString("name") - require.True(t, ok) + name, ok = msgs[1].GetString("name") + require.True(t, ok) - assert.Equal(t, "load5", name) + assert.Equal(t, "load5", name) - val, ok = msgs[1].GetFloat("value") - require.True(t, ok) + val, ok = msgs[1].GetFloat("value") + require.True(t, ok) - assert.Equal(t, 1.5, val) + assert.Equal(t, 1.5, val) - name, ok = msgs[2].GetString("name") - require.True(t, ok) + name, ok = msgs[2].GetString("name") + require.True(t, ok) - assert.Equal(t, "load15", name) + assert.Equal(t, "load15", name) - val, ok = msgs[2].GetFloat("value") - require.True(t, ok) + val, ok = msgs[2].GetFloat("value") + require.True(t, ok) - assert.Equal(t, 0.8, val) - }) - - n.It("adds any tags registered", func() { - ss := &SystemStats{ - ps: &mps, - tags: map[string]string{ - "host": "my.test", - "dc": "us-west-1", - }, - } - - lv := &load.LoadAvgStat{ - Load1: 0.3, - Load5: 1.5, - Load15: 0.8, - } - - mps.On("LoadAvg").Return(lv, nil) - - msgs, err := ss.Read() - require.NoError(t, err) - - for _, m := range msgs { - val, ok := m.GetTag("host") - require.True(t, ok) - - assert.Equal(t, val, "my.test") - - val, ok = m.GetTag("dc") - require.True(t, ok) - - assert.Equal(t, val, "us-west-1") - } - }) - - n.Meow() + assert.Equal(t, 0.8, val) +} + +func TestSystemStats_AddTags(t *testing.T) { + var mps MockPS + defer mps.AssertExpectations(t) + + ss := &SystemStats{ + ps: &mps, + tags: map[string]string{ + "host": "my.test", + "dc": "us-west-1", + }, + } + + lv := &load.LoadAvgStat{ + Load1: 0.3, + Load5: 1.5, + Load15: 0.8, + } + + mps.On("LoadAvg").Return(lv, nil) + + msgs, err := ss.Read() + require.NoError(t, err) + + for _, m := range msgs { + val, ok := m.GetTag("host") + require.True(t, ok) + + assert.Equal(t, val, "my.test") + + val, ok = m.GetTag("dc") + require.True(t, ok) + + assert.Equal(t, val, "us-west-1") + } }