Skip to content

Commit

Permalink
[CONTINT-3542] Send in-<inode> using the cgroup controller when con…
Browse files Browse the repository at this point in the history
…tainer-id cannot be retrieved
  • Loading branch information
AliDatadog committed Jan 11, 2024
2 parents ed10c5c + 421ae2b commit 2f549e6
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 125 deletions.
96 changes: 69 additions & 27 deletions statsd/container_linux.go
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
"path"
"regexp"
"strings"
"syscall"
Expand All @@ -20,8 +21,11 @@ const (
// selfMountinfo is the path to the mountinfo path where we can find the container id in case cgroup namespace is preventing the use of /proc/self/cgroup
selfMountInfoPath = "/proc/self/mountinfo"

// mountsPath is the path to the file listing all the mount points
mountsPath = "/proc/mounts"
// defaultCgroupMountPath is the default path to the cgroup mount point.
defaultCgroupMountPath = "/sys/fs/cgroup"

// cgroupV1BaseController is the controller used to identify the container-id for cgroup v1
cgroupV1BaseController = "memory"

uuidSource = "[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}"
containerSource = "[0-9a-f]{64}"
Expand Down Expand Up @@ -51,6 +55,9 @@ var (
expContainerID = regexp.MustCompile(fmt.Sprintf(`(%s|%s|%s)(?:.scope)?$`, uuidSource, containerSource, taskSource))

cIDMountInfoRegexp = regexp.MustCompile(cIDRegexpStr)

// initContainerID initializes the container ID.
initContainerID = internalInitContainerID
)

// parseContainerID finds the first container ID reading from r and returns it.
Expand Down Expand Up @@ -109,55 +116,90 @@ func readMountinfo(path string) string {
return parseMountinfo(f)
}

// isCgroupV1 checks if Cgroup V1 is used
func isCgroupV1(mountsPath string) bool {
f, err := os.Open(mountsPath)
func isHostCgroupNamespace() bool {
fi, err := os.Stat("/proc/self/ns/cgroup")
if err != nil {
return false
}
defer f.Close()

scn := bufio.NewScanner(f)
inode := fi.Sys().(*syscall.Stat_t).Ino

return inode == hostCgroupNamespaceInode
}

// parseCgroupNodePath parses /proc/self/cgroup and returns a map of controller to its associated cgroup node path.
func parseCgroupNodePath(r io.Reader) map[string]string {
res := make(map[string]string)
scn := bufio.NewScanner(r)
for scn.Scan() {
line := scn.Text()

tokens := strings.Fields(line)
if len(tokens) >= 3 {
fsType := tokens[2]
if fsType == "cgroup" {
return true
}
tokens := strings.Split(line, ":")
if len(tokens) != 3 {
continue
}
if tokens[1] == cgroupV1BaseController || tokens[1] == "" {
res[tokens[1]] = tokens[2]
}
}

return false
return res
}

func isHostCgroupNamespace() bool {
fi, err := os.Stat("/proc/self/ns/cgroup")
// getCgroupInode returns the cgroup controller inode if it exists otherwise an empty string.
// The inode is prefixed by "in-" and is used by the agent to retrieve the container ID.
// For cgroup v1, we use the memory controller.
func getCgroupInode(cgroupMountPath, procSelfCgroupPath string) string {
// Parse /proc/self/cgroup to retrieve the paths to the memory controller (cgroupv1) and the cgroup node (cgroupv2)
f, err := os.Open(procSelfCgroupPath)
if err != nil {
return false
return ""
}
defer f.Close()
cgroupControllersPaths := parseCgroupNodePath(f)
// Retrieve the cgroup inode from /sys/fs/cgroup+controller+cgroupNodePath
for _, controller := range []string{cgroupV1BaseController, ""} {
cgroupNodePath, ok := cgroupControllersPaths[controller]
if !ok {
continue
}
inode := inodeForPath(path.Join(cgroupMountPath, controller, cgroupNodePath))
if inode != "" {
return inode
}
}
return ""
}

inode := fi.Sys().(*syscall.Stat_t).Ino

return inode == hostCgroupNamespaceInode
// inodeForPath returns the inode for the provided path or empty on failure.
func inodeForPath(path string) string {
fi, err := os.Stat(path)
if err != nil {
return ""
}
stats, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return ""
}
return fmt.Sprintf("in-%d", stats.Ino)
}

// initContainerID initializes the container ID.
// internalInitContainerID initializes the container ID.
// It can either be provided by the user or read from cgroups.
func initContainerID(userProvidedID string, cgroupFallback bool) {
func internalInitContainerID(userProvidedID string, cgroupFallback bool) {
initOnce.Do(func() {
if userProvidedID != "" {
containerID = userProvidedID
return
}

if cgroupFallback {
if isCgroupV1(mountsPath) || isHostCgroupNamespace() {
isHostCgroupNs := isHostCgroupNamespace()
if isHostCgroupNs {
containerID = readContainerID(cgroupPath)
} else {
containerID = readMountinfo(selfMountInfoPath)
return
}
containerID = readMountinfo(selfMountInfoPath)
if containerID != "" {
containerID = getCgroupInode(defaultCgroupMountPath, cgroupPath)
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion statsd/container_stub.go
Expand Up @@ -3,7 +3,7 @@

package statsd

func initContainerID(userProvidedID string, cgroupFallback bool) {
var initContainerID = func(userProvidedID string, cgroupFallback bool) {
initOnce.Do(func() {
if userProvidedID != "" {
containerID = userProvidedID
Expand Down

0 comments on commit 2f549e6

Please sign in to comment.