125 lines
3.2 KiB
Go
125 lines
3.2 KiB
Go
package sysinfo
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"regexp"
|
|
"runtime"
|
|
)
|
|
|
|
var (
|
|
// ErrDockerUnsupported is returned if Docker is not supported on the
|
|
// platform.
|
|
ErrDockerUnsupported = errors.New("Docker unsupported on this platform")
|
|
// ErrDockerNotFound is returned if a Docker ID is not found in
|
|
// /proc/self/cgroup
|
|
ErrDockerNotFound = errors.New("Docker ID not found")
|
|
)
|
|
|
|
// DockerID attempts to detect Docker.
|
|
func DockerID() (string, error) {
|
|
if "linux" != runtime.GOOS {
|
|
return "", ErrDockerUnsupported
|
|
}
|
|
|
|
f, err := os.Open("/proc/self/cgroup")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
return parseDockerID(f)
|
|
}
|
|
|
|
var (
|
|
dockerIDLength = 64
|
|
dockerIDRegexRaw = fmt.Sprintf("^[0-9a-f]{%d}$", dockerIDLength)
|
|
dockerIDRegex = regexp.MustCompile(dockerIDRegexRaw)
|
|
)
|
|
|
|
func parseDockerID(r io.Reader) (string, error) {
|
|
// Each line in the cgroup file consists of three colon delimited fields.
|
|
// 1. hierarchy ID - we don't care about this
|
|
// 2. subsystems - comma separated list of cgroup subsystem names
|
|
// 3. control group - control group to which the process belongs
|
|
//
|
|
// Example
|
|
// 5:cpuacct,cpu,cpuset:/daemons
|
|
|
|
for scanner := bufio.NewScanner(r); scanner.Scan(); {
|
|
line := scanner.Bytes()
|
|
cols := bytes.SplitN(line, []byte(":"), 3)
|
|
|
|
if len(cols) < 3 {
|
|
continue
|
|
}
|
|
|
|
// We're only interested in the cpu subsystem.
|
|
if !isCPUCol(cols[1]) {
|
|
continue
|
|
}
|
|
|
|
// We're only interested in Docker generated cgroups.
|
|
// Reference Implementation:
|
|
// case cpu_cgroup
|
|
// # docker native driver w/out systemd (fs)
|
|
// when %r{^/docker/([0-9a-f]+)$} then $1
|
|
// # docker native driver with systemd
|
|
// when %r{^/system\.slice/docker-([0-9a-f]+)\.scope$} then $1
|
|
// # docker lxc driver
|
|
// when %r{^/lxc/([0-9a-f]+)$} then $1
|
|
//
|
|
var id string
|
|
if bytes.HasPrefix(cols[2], []byte("/docker/")) {
|
|
id = string(cols[2][len("/docker/"):])
|
|
} else if bytes.HasPrefix(cols[2], []byte("/lxc/")) {
|
|
id = string(cols[2][len("/lxc/"):])
|
|
} else if bytes.HasPrefix(cols[2], []byte("/system.slice/docker-")) &&
|
|
bytes.HasSuffix(cols[2], []byte(".scope")) {
|
|
id = string(cols[2][len("/system.slice/docker-") : len(cols[2])-len(".scope")])
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
if err := validateDockerID(id); err != nil {
|
|
// We can stop searching at this point, the CPU
|
|
// subsystem should only occur once, and its cgroup is
|
|
// not docker or not a format we accept.
|
|
return "", err
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
return "", ErrDockerNotFound
|
|
}
|
|
|
|
func isCPUCol(col []byte) bool {
|
|
// Sometimes we have multiple subsystems in one line, as in this example
|
|
// from:
|
|
// https://source.datanerd.us/newrelic/cross_agent_tests/blob/master/docker_container_id/docker-1.1.2-native-driver-systemd.txt
|
|
//
|
|
// 3:cpuacct,cpu:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope
|
|
splitCSV := func(r rune) bool { return r == ',' }
|
|
subsysCPU := []byte("cpu")
|
|
|
|
for _, subsys := range bytes.FieldsFunc(col, splitCSV) {
|
|
if bytes.Equal(subsysCPU, subsys) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func validateDockerID(id string) error {
|
|
if !dockerIDRegex.MatchString(id) {
|
|
return fmt.Errorf("%s does not match %s",
|
|
id, dockerIDRegexRaw)
|
|
}
|
|
|
|
return nil
|
|
}
|