Commit 57f6aebc authored by Shirou WAKAYAMA's avatar Shirou WAKAYAMA

add Timeout to invoke command and use common.Invoke refs: #201

parent bae75faa
......@@ -5,6 +5,8 @@ import (
"runtime"
"strconv"
"strings"
"github.com/shirou/gopsutil/internal/common"
)
type TimesStat struct {
......@@ -37,6 +39,12 @@ type InfoStat struct {
Flags []string `json:"flags"`
}
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
var lastCPUTimes []TimesStat
var lastPerCPUTimes []TimesStat
......
......@@ -36,7 +36,7 @@ func Info() ([]InfoStat, error) {
if err != nil {
return ret, err
}
out, err := exec.Command(sysctl, "machdep.cpu").Output()
out, err := invoke.Command(sysctl, "machdep.cpu")
if err != nil {
return ret, err
}
......@@ -90,7 +90,7 @@ func Info() ([]InfoStat, error) {
// Use the rated frequency of the CPU. This is a static value and does not
// account for low power or Turbo Boost modes.
out, err = exec.Command(sysctl, "hw.cpufrequency").Output()
out, err = invoke.Command(sysctl, "hw.cpufrequency")
if err != nil {
return ret, err
}
......
......@@ -29,7 +29,7 @@ func init() {
if err != nil {
return
}
out, err := exec.Command(getconf, "CLK_TCK").Output()
out, err := invoke.Command(getconf, "CLK_TCK")
// ignore errors
if err == nil {
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
......
......@@ -19,7 +19,7 @@ func init() {
if err != nil {
return
}
out, err := exec.Command(getconf, "CLK_TCK").Output()
out, err := invoke.Command(getconf, "CLK_TCK")
// ignore errors
if err == nil {
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
......
......@@ -2,8 +2,16 @@ package disk
import (
"encoding/json"
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
type UsageStat struct {
Path string `json:"path"`
Fstype string `json:"fstype"`
......
......@@ -339,7 +339,7 @@ func GetDiskSerialNumber(name string) string {
return ""
}
out, err := exec.Command(udevadm, "info", "--query=property", n).Output()
out, err := invoke.Command(udevadm, "info", "--query=property", n)
// does not return error, just an empty string
if err != nil {
......
package docker
import "errors"
import (
"errors"
"github.com/shirou/gopsutil/internal/common"
)
var ErrDockerNotAvailable = errors.New("docker not available")
var ErrCgroupNotAvailable = errors.New("cgroup not available")
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
type CgroupMemStat struct {
ContainerID string `json:"containerID"`
Cache uint64 `json:"cache"`
......
......@@ -23,7 +23,7 @@ func GetDockerStat() ([]CgroupDockerStat, error) {
return nil, ErrDockerNotAvailable
}
out, err := exec.Command(path, "ps", "-a", "--no-trunc", "--format", "{{.ID}}|{{.Image}}|{{.Names}}|{{.Status}}").Output()
out, err := invoke.Command(path, "ps", "-a", "--no-trunc", "--format", "{{.ID}}|{{.Image}}|{{.Names}}|{{.Status}}")
if err != nil {
return []CgroupDockerStat{}, err
}
......@@ -65,7 +65,7 @@ func GetDockerIDList() ([]string, error) {
return nil, ErrDockerNotAvailable
}
out, err := exec.Command(path, "ps", "-q", "--no-trunc").Output()
out, err := invoke.Command(path, "ps", "-q", "--no-trunc")
if err != nil {
return []string{}, err
}
......
......@@ -2,17 +2,25 @@ package host
import (
"encoding/json"
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
// A HostInfoStat describes the host status.
// This is not in the psutil but it useful.
type InfoStat struct {
Hostname string `json:"hostname"`
Uptime uint64 `json:"uptime"`
BootTime uint64 `json:"bootTime"`
Procs uint64 `json:"procs"` // number of processes
OS string `json:"os"` // ex: freebsd, linux
Platform string `json:"platform"` // ex: ubuntu, linuxmint
Procs uint64 `json:"procs"` // number of processes
OS string `json:"os"` // ex: freebsd, linux
Platform string `json:"platform"` // ex: ubuntu, linuxmint
PlatformFamily string `json:"platformFamily"` // ex: debian, rhel
PlatformVersion string `json:"platformVersion"`
VirtualizationSystem string `json:"virtualizationSystem"`
......
......@@ -131,12 +131,12 @@ func PlatformInformation() (string, string, string, error) {
if err != nil {
return "", "", "", err
}
out, err := exec.Command(uname, "-s").Output()
out, err := invoke.Command(uname, "-s")
if err == nil {
platform = strings.ToLower(strings.TrimSpace(string(out)))
}
out, err = exec.Command(uname, "-r").Output()
out, err = invoke.Command(uname, "-r")
if err == nil {
version = strings.ToLower(strings.TrimSpace(string(out)))
}
......
......@@ -136,12 +136,12 @@ func PlatformInformation() (string, string, string, error) {
return "", "", "", err
}
out, err := exec.Command(uname, "-s").Output()
out, err := invoke.Command(uname, "-s")
if err == nil {
platform = strings.ToLower(strings.TrimSpace(string(out)))
}
out, err = exec.Command(uname, "-r").Output()
out, err = invoke.Command(uname, "-r")
if err == nil {
version = strings.ToLower(strings.TrimSpace(string(out)))
}
......
......@@ -164,7 +164,7 @@ func getLSB() (*LSB, error) {
if err != nil {
return ret, err
}
out, err := exec.Command(lsb_release).Output()
out, err := invoke.Command(lsb_release)
if err != nil {
return ret, err
}
......
......@@ -8,8 +8,10 @@ package common
// - windows (amd64)
import (
"bufio"
"bytes"
"errors"
"io/ioutil"
"log"
"net/url"
"os"
"os/exec"
......@@ -19,6 +21,12 @@ import (
"runtime"
"strconv"
"strings"
"time"
)
var (
Timeout = 3 * time.Second
TimeoutErr = errors.New("Command timed out.")
)
type Invoker interface {
......@@ -28,7 +36,8 @@ type Invoker interface {
type Invoke struct{}
func (i Invoke) Command(name string, arg ...string) ([]byte, error) {
return exec.Command(name, arg...).Output()
cmd := exec.Command(name, arg...)
return CombinedOutputTimeout(cmd, Timeout)
}
type FakeInvoke struct {
......@@ -118,20 +127,20 @@ func IntToString(orig []int8) string {
}
func UintToString(orig []uint8) 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])
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 {
......@@ -294,3 +303,41 @@ func HostSys(combineWith ...string) string {
func HostEtc(combineWith ...string) string {
return GetEnv("HOST_ETC", "/etc", combineWith...)
}
// CombinedOutputTimeout runs the given command with the given timeout and
// returns the combined output of stdout and stderr.
// If the command times out, it attempts to kill the process.
// copied from https://github.com/influxdata/telegraf
func CombinedOutputTimeout(c *exec.Cmd, timeout time.Duration) ([]byte, error) {
var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
if err := c.Start(); err != nil {
return nil, err
}
err := WaitTimeout(c, timeout)
return b.Bytes(), err
}
// WaitTimeout waits for the given command to finish with a timeout.
// It assumes the command has already been started.
// If the command times out, it attempts to kill the process.
// copied from https://github.com/influxdata/telegraf
func WaitTimeout(c *exec.Cmd, timeout time.Duration) error {
timer := time.NewTimer(timeout)
done := make(chan error)
go func() { done <- c.Wait() }()
select {
case err := <-done:
timer.Stop()
return err
case <-timer.C:
if err := c.Process.Kill(); err != nil {
log.Printf("FATAL error killing process: %s", err)
return err
}
// wait for the command to return after killing it
<-done
return TimeoutErr
}
}
......@@ -2,8 +2,16 @@ package mem
import (
"encoding/json"
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
// Memory usage statistics. Total, Available and Used contain numbers of bytes
// for human consumption.
//
......
......@@ -16,7 +16,7 @@ func getVMStat(vms *VirtualMemoryStat) error {
if err != nil {
return err
}
out, err := exec.Command(vm_stat).Output()
out, err := invoke.Command(vm_stat)
if err != nil {
return err
}
......
......@@ -3,7 +3,6 @@
package mem
import (
"os/exec"
"strconv"
"strings"
"testing"
......@@ -15,7 +14,7 @@ func TestVirtualMemoryDarwin(t *testing.T) {
v, err := VirtualMemory()
assert.Nil(t, err)
outBytes, err := exec.Command("/usr/sbin/sysctl", "hw.memsize").Output()
outBytes, err := invoke.Command("/usr/sbin/sysctl", "hw.memsize")
assert.Nil(t, err)
outString := string(outBytes)
outString = strings.TrimSpace(outString)
......
......@@ -93,7 +93,7 @@ func SwapMemory() (*SwapMemoryStat, error) {
return nil, err
}
out, err := exec.Command(swapinfo).Output()
out, err := invoke.Command(swapinfo)
if err != nil {
return nil, err
}
......
......@@ -21,7 +21,7 @@ func IOCounters(pernic bool) ([]IOCountersStat, error) {
if err != nil {
return nil, err
}
out, err := exec.Command(netstat, "-ibdnW").Output()
out, err := invoke.Command(netstat, "-ibdnW")
if err != nil {
return nil, err
}
......
......@@ -16,7 +16,7 @@ func IOCounters(pernic bool) ([]IOCountersStat, error) {
if err != nil {
return nil, err
}
out, err := exec.Command(netstat, "-ibdnW").Output()
out, err := invoke.Command(netstat, "-ibdnW")
if err != nil {
return nil, err
}
......
......@@ -9,6 +9,8 @@ import (
"strconv"
"strings"
"syscall"
"github.com/shirou/gopsutil/internal/common"
)
// POSIX
......@@ -72,7 +74,7 @@ func (p *Process) SendSignal(sig syscall.Signal) error {
}
cmd := exec.Command(kill, "-s", sigAsStr, strconv.Itoa(int(p.Pid)))
cmd.Stderr = os.Stderr
err = cmd.Run()
err = common.WaitTimeout(cmd, common.Timeout)
if err != nil {
return err
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment