You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
392 lines
8.3 KiB
392 lines
8.3 KiB
package common |
|
|
|
// |
|
// gopsutil is a port of psutil(http://pythonhosted.org/psutil/). |
|
// This covers these architectures. |
|
// - linux (amd64, arm) |
|
// - freebsd (amd64) |
|
// - windows (amd64) |
|
import ( |
|
"bufio" |
|
"bytes" |
|
"context" |
|
"errors" |
|
"fmt" |
|
"io/ioutil" |
|
"net/url" |
|
"os" |
|
"os/exec" |
|
"path" |
|
"path/filepath" |
|
"reflect" |
|
"runtime" |
|
"strconv" |
|
"strings" |
|
"time" |
|
) |
|
|
|
var ( |
|
Timeout = 3 * time.Second |
|
ErrTimeout = errors.New("command timed out") |
|
) |
|
|
|
type Invoker interface { |
|
Command(string, ...string) ([]byte, error) |
|
CommandWithContext(context.Context, string, ...string) ([]byte, error) |
|
} |
|
|
|
type Invoke struct{} |
|
|
|
func (i Invoke) Command(name string, arg ...string) ([]byte, error) { |
|
ctx, cancel := context.WithTimeout(context.Background(), Timeout) |
|
defer cancel() |
|
return i.CommandWithContext(ctx, name, arg...) |
|
} |
|
|
|
func (i Invoke) CommandWithContext(ctx context.Context, name string, arg ...string) ([]byte, error) { |
|
cmd := exec.CommandContext(ctx, name, arg...) |
|
|
|
var buf bytes.Buffer |
|
cmd.Stdout = &buf |
|
cmd.Stderr = &buf |
|
|
|
if err := cmd.Start(); err != nil { |
|
return buf.Bytes(), err |
|
} |
|
|
|
if err := cmd.Wait(); err != nil { |
|
return buf.Bytes(), err |
|
} |
|
|
|
return buf.Bytes(), nil |
|
} |
|
|
|
type FakeInvoke struct { |
|
Suffix string // Suffix species expected file name suffix such as "fail" |
|
Error error // If Error specfied, return the error. |
|
} |
|
|
|
// Command in FakeInvoke returns from expected file if exists. |
|
func (i FakeInvoke) Command(name string, arg ...string) ([]byte, error) { |
|
if i.Error != nil { |
|
return []byte{}, i.Error |
|
} |
|
|
|
arch := runtime.GOOS |
|
|
|
commandName := filepath.Base(name) |
|
|
|
fname := strings.Join(append([]string{commandName}, arg...), "") |
|
fname = url.QueryEscape(fname) |
|
fpath := path.Join("testdata", arch, fname) |
|
if i.Suffix != "" { |
|
fpath += "_" + i.Suffix |
|
} |
|
if PathExists(fpath) { |
|
return ioutil.ReadFile(fpath) |
|
} |
|
return []byte{}, fmt.Errorf("could not find testdata: %s", fpath) |
|
} |
|
|
|
func (i FakeInvoke) CommandWithContext(ctx context.Context, name string, arg ...string) ([]byte, error) { |
|
return i.Command(name, arg...) |
|
} |
|
|
|
var ErrNotImplementedError = errors.New("not implemented yet") |
|
|
|
// ReadLines reads contents from a file and splits them by new lines. |
|
// 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 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]) |
|
} |
|
|
|
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]) |
|
} |
|
|
|
// ReadInts reads contents from single line file and returns them as []int32. |
|
func ReadInts(filename string) ([]int64, error) { |
|
f, err := os.Open(filename) |
|
if err != nil { |
|
return []int64{}, err |
|
} |
|
defer f.Close() |
|
|
|
var ret []int64 |
|
|
|
r := bufio.NewReader(f) |
|
|
|
// The int files that this is concerned with should only be one liners. |
|
line, err := r.ReadString('\n') |
|
if err != nil { |
|
return []int64{}, err |
|
} |
|
|
|
i, err := strconv.ParseInt(strings.Trim(line, "\n"), 10, 32) |
|
if err != nil { |
|
return []int64{}, err |
|
} |
|
ret = append(ret, i) |
|
|
|
return ret, nil |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// StringsHas checks the target string slice contains src or not |
|
func StringsHas(target []string, src string) bool { |
|
for _, t := range target { |
|
if strings.TrimSpace(t) == src { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// StringsContains checks the src in any string of the target string slice |
|
func StringsContains(target []string, src string) bool { |
|
for _, t := range target { |
|
if strings.Contains(t, src) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// IntContains checks the src in any int of the target int slice. |
|
func IntContains(target []int, src int) bool { |
|
for _, t := range target { |
|
if src == t { |
|
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 |
|
} |
|
|
|
//GetEnv retrieves the environment variable key. If it does not exist it returns the default. |
|
func GetEnv(key string, dfault string, combineWith ...string) string { |
|
value := os.Getenv(key) |
|
if value == "" { |
|
value = dfault |
|
} |
|
|
|
switch len(combineWith) { |
|
case 0: |
|
return value |
|
case 1: |
|
return filepath.Join(value, combineWith[0]) |
|
default: |
|
all := make([]string, len(combineWith)+1) |
|
all[0] = value |
|
copy(all[1:], combineWith) |
|
return filepath.Join(all...) |
|
} |
|
panic("invalid switch case") |
|
} |
|
|
|
func HostProc(combineWith ...string) string { |
|
return GetEnv("HOST_PROC", "/proc", combineWith...) |
|
} |
|
|
|
func HostSys(combineWith ...string) string { |
|
return GetEnv("HOST_SYS", "/sys", combineWith...) |
|
} |
|
|
|
func HostEtc(combineWith ...string) string { |
|
return GetEnv("HOST_ETC", "/etc", combineWith...) |
|
} |
|
|
|
func HostVar(combineWith ...string) string { |
|
return GetEnv("HOST_VAR", "/var", combineWith...) |
|
} |
|
|
|
func HostRun(combineWith ...string) string { |
|
return GetEnv("HOST_RUN", "/run", combineWith...) |
|
} |
|
|
|
// https://gist.github.com/kylelemons/1525278 |
|
func Pipeline(cmds ...*exec.Cmd) ([]byte, []byte, error) { |
|
// Require at least one command |
|
if len(cmds) < 1 { |
|
return nil, nil, nil |
|
} |
|
|
|
// Collect the output from the command(s) |
|
var output bytes.Buffer |
|
var stderr bytes.Buffer |
|
|
|
last := len(cmds) - 1 |
|
for i, cmd := range cmds[:last] { |
|
var err error |
|
// Connect each command's stdin to the previous command's stdout |
|
if cmds[i+1].Stdin, err = cmd.StdoutPipe(); err != nil { |
|
return nil, nil, err |
|
} |
|
// Connect each command's stderr to a buffer |
|
cmd.Stderr = &stderr |
|
} |
|
|
|
// Connect the output and error for the last command |
|
cmds[last].Stdout, cmds[last].Stderr = &output, &stderr |
|
|
|
// Start each command |
|
for _, cmd := range cmds { |
|
if err := cmd.Start(); err != nil { |
|
return output.Bytes(), stderr.Bytes(), err |
|
} |
|
} |
|
|
|
// Wait for each command to complete |
|
for _, cmd := range cmds { |
|
if err := cmd.Wait(); err != nil { |
|
return output.Bytes(), stderr.Bytes(), err |
|
} |
|
} |
|
|
|
// Return the pipeline output and the collected standard error |
|
return output.Bytes(), stderr.Bytes(), nil |
|
} |
|
|
|
// getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running |
|
// sysctl commands (see DoSysctrl). |
|
func getSysctrlEnv(env []string) []string { |
|
foundLC := false |
|
for i, line := range env { |
|
if strings.HasPrefix(line, "LC_ALL") { |
|
env[i] = "LC_ALL=C" |
|
foundLC = true |
|
} |
|
} |
|
if !foundLC { |
|
env = append(env, "LC_ALL=C") |
|
} |
|
return env |
|
}
|
|
|