2014-06-10 13:38:01 +08:00
|
|
|
// +build linux
|
|
|
|
|
2014-12-30 21:09:05 +08:00
|
|
|
package docker
|
2014-06-10 13:38:01 +08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2015-11-21 22:00:37 +08:00
|
|
|
"fmt"
|
2015-10-19 11:40:01 +08:00
|
|
|
"os"
|
2014-06-10 13:38:01 +08:00
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2014-11-27 09:25:14 +08:00
|
|
|
|
2014-11-27 09:32:35 +08:00
|
|
|
cpu "github.com/shirou/gopsutil/cpu"
|
2015-10-19 11:40:01 +08:00
|
|
|
"github.com/shirou/gopsutil/internal/common"
|
2014-06-10 13:38:01 +08:00
|
|
|
)
|
|
|
|
|
2016-04-24 16:15:45 +08:00
|
|
|
// GetDockerStat returns a list of Docker basic stats.
|
|
|
|
// This requires certain permission.
|
|
|
|
func GetDockerStat() ([]CgroupDockerStat, error) {
|
|
|
|
path, err := exec.LookPath("docker")
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrDockerNotAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command(path, "ps", "-a", "--no-trunc", "--format", "{{.ID}}|{{.Image}}|{{.Names}}|{{.Status}}").Output()
|
|
|
|
if err != nil {
|
|
|
|
return []CgroupDockerStat{}, err
|
|
|
|
}
|
|
|
|
lines := strings.Split(string(out), "\n")
|
|
|
|
ret := make([]CgroupDockerStat, 0, len(lines))
|
|
|
|
|
|
|
|
for _, l := range lines {
|
|
|
|
if l == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cols := strings.Split(l, "|")
|
|
|
|
if len(cols) != 4 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
names := strings.Split(cols[2], ",")
|
|
|
|
stat := CgroupDockerStat{
|
|
|
|
ContainerID: cols[0],
|
|
|
|
Name: names[0],
|
|
|
|
Image: cols[1],
|
|
|
|
Status: cols[3],
|
|
|
|
Running: strings.Contains(cols[3], "Up"),
|
|
|
|
}
|
|
|
|
ret = append(ret, stat)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c CgroupDockerStat) String() string {
|
|
|
|
s, _ := json.Marshal(c)
|
|
|
|
return string(s)
|
|
|
|
}
|
|
|
|
|
2014-06-10 13:38:01 +08:00
|
|
|
// GetDockerIDList returnes a list of DockerID.
|
|
|
|
// This requires certain permission.
|
|
|
|
func GetDockerIDList() ([]string, error) {
|
2015-07-23 10:24:45 +08:00
|
|
|
path, err := exec.LookPath("docker")
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrDockerNotAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command(path, "ps", "-q", "--no-trunc").Output()
|
2014-06-10 13:38:01 +08:00
|
|
|
if err != nil {
|
|
|
|
return []string{}, err
|
|
|
|
}
|
|
|
|
lines := strings.Split(string(out), "\n")
|
|
|
|
ret := make([]string, 0, len(lines))
|
|
|
|
|
|
|
|
for _, l := range lines {
|
2015-07-23 10:24:45 +08:00
|
|
|
if l == "" {
|
|
|
|
continue
|
|
|
|
}
|
2014-06-10 13:38:01 +08:00
|
|
|
ret = append(ret, l)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CgroupCPU returnes specified cgroup id CPU status.
|
2016-04-01 20:34:39 +08:00
|
|
|
// containerID is same as docker id if you use docker.
|
2014-06-10 13:38:01 +08:00
|
|
|
// If you use container via systemd.slice, you could use
|
2016-04-01 20:34:39 +08:00
|
|
|
// containerID = docker-<container id>.scope and base=/sys/fs/cgroup/cpuacct/system.slice/
|
|
|
|
func CgroupCPU(containerID string, base string) (*cpu.TimesStat, error) {
|
|
|
|
statfile := getCgroupFilePath(containerID, base, "cpuacct", "cpuacct.stat")
|
2015-09-22 04:29:17 +08:00
|
|
|
lines, err := common.ReadLines(statfile)
|
2015-02-11 02:36:19 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
// empty containerID means all cgroup
|
|
|
|
if len(containerID) == 0 {
|
|
|
|
containerID = "all"
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
ret := &cpu.TimesStat{CPU: containerID}
|
2014-06-10 13:38:01 +08:00
|
|
|
for _, line := range lines {
|
|
|
|
fields := strings.Split(line, " ")
|
|
|
|
if fields[0] == "user" {
|
2015-02-13 22:14:36 +08:00
|
|
|
user, err := strconv.ParseFloat(fields[1], 64)
|
2014-06-10 13:38:01 +08:00
|
|
|
if err == nil {
|
2015-02-13 22:14:36 +08:00
|
|
|
ret.User = float64(user)
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if fields[0] == "system" {
|
2015-02-13 22:14:36 +08:00
|
|
|
system, err := strconv.ParseFloat(fields[1], 64)
|
2014-06-10 13:38:01 +08:00
|
|
|
if err == nil {
|
2015-02-13 22:14:36 +08:00
|
|
|
ret.System = float64(system)
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2016-03-22 22:09:12 +08:00
|
|
|
func CgroupCPUDocker(containerid string) (*cpu.TimesStat, error) {
|
2015-11-12 20:53:18 +08:00
|
|
|
return CgroupCPU(containerid, common.HostSys("fs/cgroup/cpuacct/docker"))
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
|
|
|
|
2016-04-01 20:34:39 +08:00
|
|
|
func CgroupMem(containerID string, base string) (*CgroupMemStat, error) {
|
|
|
|
statfile := getCgroupFilePath(containerID, base, "memory", "memory.stat")
|
2015-09-22 04:29:17 +08:00
|
|
|
|
2016-04-01 20:34:39 +08:00
|
|
|
// empty containerID means all cgroup
|
|
|
|
if len(containerID) == 0 {
|
|
|
|
containerID = "all"
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
2015-09-22 04:29:17 +08:00
|
|
|
lines, err := common.ReadLines(statfile)
|
Don't ignore err when getting CgroupMemDocker stats
Fixes panic: runtime error: index out of range
goroutine 10 [running]:
testing.func·006()
/usr/local/go/src/testing/testing.go:441 +0x181
github.com/shirou/gopsutil/docker.CgroupMem(0x586b30, 0x6, 0x5a87d0, 0x1c, 0x0, 0x0, 0x0)
/home/jwilder/go/src/github.com/shirou/gopsutil/docker/docker_linux.go:119 +0xf48
github.com/shirou/gopsutil/docker.CgroupMemDocker(0x586b30, 0x6, 0x0, 0x0, 0x0)
/home/jwilder/go/src/github.com/shirou/gopsutil/docker/docker_linux.go:184 +0x57
If the ID passed to the CGroupMemDocker does not exist, you can get
a panic at runtime. This can happen when a container exits before
calling the func.
2015-02-11 02:20:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
ret := &CgroupMemStat{ContainerID: containerID}
|
2014-06-10 13:38:01 +08:00
|
|
|
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
|
2016-03-23 09:52:46 +08:00
|
|
|
case "rssHuge":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.RSSHuge = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "mappedFile":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.MappedFile = v
|
2014-06-10 13:38:01 +08:00
|
|
|
case "pgpgin":
|
|
|
|
ret.Pgpgin = v
|
|
|
|
case "pgpgout":
|
|
|
|
ret.Pgpgout = v
|
|
|
|
case "pgfault":
|
|
|
|
ret.Pgfault = v
|
|
|
|
case "pgmajfault":
|
|
|
|
ret.Pgmajfault = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "inactiveAnon":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.InactiveAnon = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "activeAnon":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.ActiveAnon = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "inactiveFile":
|
2015-07-23 10:24:45 +08:00
|
|
|
ret.InactiveFile = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "activeFile":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.ActiveFile = v
|
2014-06-10 13:38:01 +08:00
|
|
|
case "unevictable":
|
|
|
|
ret.Unevictable = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "hierarchicalMemoryLimit":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.HierarchicalMemoryLimit = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalCache":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalCache = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalRss":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalRSS = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalRssHuge":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalRSSHuge = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalMappedFile":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalMappedFile = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalPgpgin":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalPgpgIn = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalPgpgout":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalPgpgOut = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalPgfault":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalPgFault = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalPgmajfault":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalPgMajFault = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalInactiveAnon":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalInactiveAnon = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalActiveAnon":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalActiveAnon = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalInactiveFile":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalInactiveFile = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalActiveFile":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalActiveFile = v
|
2016-03-23 09:52:46 +08:00
|
|
|
case "totalUnevictable":
|
2014-08-16 02:05:26 +08:00
|
|
|
ret.TotalUnevictable = v
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
|
|
|
}
|
2015-11-21 22:00:37 +08:00
|
|
|
|
2016-04-01 20:34:39 +08:00
|
|
|
r, err := getCgroupMemFile(containerID, base, "memory.usage_in_bytes")
|
2015-11-21 22:00:37 +08:00
|
|
|
if err == nil {
|
|
|
|
ret.MemUsageInBytes = r
|
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
r, err = getCgroupMemFile(containerID, base, "memory.max_usage_in_bytes")
|
2015-11-21 22:00:37 +08:00
|
|
|
if err == nil {
|
|
|
|
ret.MemMaxUsageInBytes = r
|
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
r, err = getCgroupMemFile(containerID, base, "memoryLimitInBbytes")
|
2015-11-21 22:00:37 +08:00
|
|
|
if err == nil {
|
|
|
|
ret.MemLimitInBytes = r
|
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
r, err = getCgroupMemFile(containerID, base, "memoryFailcnt")
|
2015-11-21 22:00:37 +08:00
|
|
|
if err == nil {
|
|
|
|
ret.MemFailCnt = r
|
|
|
|
}
|
|
|
|
|
2014-06-10 13:38:01 +08:00
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2016-04-01 20:34:39 +08:00
|
|
|
func CgroupMemDocker(containerID string) (*CgroupMemStat, error) {
|
|
|
|
return CgroupMem(containerID, common.HostSys("fs/cgroup/memory/docker"))
|
2014-06-10 13:38:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m CgroupMemStat) String() string {
|
|
|
|
s, _ := json.Marshal(m)
|
|
|
|
return string(s)
|
|
|
|
}
|
2015-11-21 22:00:37 +08:00
|
|
|
|
|
|
|
// getCgroupFilePath constructs file path to get targetted stats file.
|
2016-04-01 20:34:39 +08:00
|
|
|
func getCgroupFilePath(containerID, base, target, file string) string {
|
2015-11-21 22:00:37 +08:00
|
|
|
if len(base) == 0 {
|
|
|
|
base = common.HostSys(fmt.Sprintf("fs/cgroup/%s/docker", target))
|
|
|
|
}
|
2016-04-01 20:34:39 +08:00
|
|
|
statfile := path.Join(base, containerID, file)
|
2015-11-21 22:00:37 +08:00
|
|
|
|
|
|
|
if _, err := os.Stat(statfile); os.IsNotExist(err) {
|
|
|
|
statfile = path.Join(
|
2016-04-01 20:34:39 +08:00
|
|
|
common.HostSys(fmt.Sprintf("fs/cgroup/%s/system.slice", target)), "docker-"+containerID+".scope", file)
|
2015-11-21 22:00:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return statfile
|
|
|
|
}
|
|
|
|
|
|
|
|
// getCgroupMemFile reads a cgroup file and return the contents as uint64.
|
2016-04-01 20:34:39 +08:00
|
|
|
func getCgroupMemFile(containerID, base, file string) (uint64, error) {
|
|
|
|
statfile := getCgroupFilePath(containerID, base, "memory", file)
|
2015-11-21 22:00:37 +08:00
|
|
|
lines, err := common.ReadLines(statfile)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if len(lines) != 1 {
|
|
|
|
return 0, fmt.Errorf("wrong format file: %s", statfile)
|
|
|
|
}
|
|
|
|
return strconv.ParseUint(lines[0], 10, 64)
|
|
|
|
}
|