func()

in metric/system/cgroup/util.go [433:564]


func (r *Reader) ProcessCgroupPaths(pid int) (PathList, error) {
	cgroupPath := filepath.Join("proc", strconv.Itoa(pid), "cgroup")
	cgroup, err := os.Open(r.rootfsMountpoint.ResolveHostFS(cgroupPath))
	if err != nil {
		return PathList{}, err //return a blank error so other events can use any file not found errors
	}
	defer cgroup.Close()

	version, err := r.CgroupsVersion(pid)
	if err != nil {
		return PathList{}, fmt.Errorf("error finding cgroup version for pid %d: %w", pid, err)
	}

	cPaths := PathList{V1: map[string]ControllerPath{}, V2: map[string]ControllerPath{}}
	sc := bufio.NewScanner(cgroup)
	for sc.Scan() {
		// http://man7.org/linux/man-pages/man7/cgroups.7.html
		// Format: hierarchy-ID:subsystem-list:cgroup-path
		// Example:
		// 2:cpu:/docker/b29faf21b7eff959f64b4192c34d5d67a707fe8561e9eaa608cb27693fba4242
		line := sc.Text()

		fields := strings.Split(line, ":")
		if len(fields) != 3 {
			continue
		}

		path := fields[2]
		if r.cgroupsHierarchyOverride != "" {
			path = r.cgroupsHierarchyOverride
		}

		//on newer docker versions (1.41+?), docker will do  namespacing with cgroups
		// such that we'll get a cgroup path like `0::/../../user.slice/user-1000.slice/session-520.scope`
		// `man 7 cgroups` says the following about the path field in the `cgroup` file (emphasis mine):
		//
		// This field contains the pathname of the control group
		// in the hierarchy to which the process belongs.  This
		// pathname is **relative to the mount point of the hierarchy**.
		//
		// However, when we try to append something like `/../..` to another path, we obviously blow things up.
		// we need to use the absolute path of the container cgroup
		if cgroupNSStateFetch() && r.rootfsMountpoint.IsSet() {
			if r.cgroupMountpoints.ContainerizedRootMount == "" {
				logp.L().Debugf("cgroup for process %d contains a relative cgroup path (%s), but we were not able to find a root cgroup. Cgroup monitoring for this PID may be incomplete",
					pid, path)
			} else {
				logp.L().Debugf("using root mount %s and path %s", r.cgroupMountpoints.ContainerizedRootMount, path)
				path = filepath.Join(r.cgroupMountpoints.ContainerizedRootMount, path)
			}
		}

		// cgroup V2
		// cgroup v2 controllers will always start with this string
		if strings.HasPrefix(line, "0::/") {
			// if you're running inside a container
			// that's operating with a hybrid cgroups config,
			// the containerized process won't see the V2 mount
			// inside /proc/self/mountinfo if docker is using cgroups V1
			// For this very annoying edge case, revert to the hostfs flag
			// If it's not set, warn the user that they've hit this.

			// we skip reading paths in case there are cgroups V1 controllers, we are at the cgroup V2 root and the cgroup V2 mount is not available
			// instead of returning an error because we don't want to break V1 metric collection for misconfigured hybrid systems that have only
			// a cgroup V2 root but don't have any other controllers. This case happens when cgroup V2 FS is mounted at a special location but not used
			if version == CgroupsV1 && line == "0::/" && r.cgroupMountpoints.V2Loc == "" {
				continue
			}

			controllerPath := filepath.Join(r.cgroupMountpoints.V2Loc, path)
			if r.cgroupMountpoints.V2Loc == "" && !r.rootfsMountpoint.IsSet() {
				logp.L().Debugf(`PID %d contains a cgroups V2 path (%s) but no V2 mountpoint was found.
This may be because metricbeat is running inside a container on a hybrid system.
To monitor cgroups V2 processess in this way, mount the unified (V2) hierarchy inside
the container as /sys/fs/cgroup/unified and start the system module with the hostfs setting.`, pid, line)
				continue
			} else if r.cgroupMountpoints.V2Loc == "" && r.rootfsMountpoint.IsSet() {
				controllerPath = r.rootfsMountpoint.ResolveHostFS(filepath.Join("/sys/fs/cgroup/unified", path))
			}

			// Check if there is an entry for controllerPath already cached.
			r.v2ControllerPathCache.Lock()
			cacheEntry, ok := r.v2ControllerPathCache.cache[controllerPath]
			if ok {
				// If the cached entry for controllerPath is not older than 5 minutes,
				// return the cached entry.
				if time.Since(cacheEntry.added) < 5*time.Minute {
					cPaths.V2 = cacheEntry.pathList.V2
					r.v2ControllerPathCache.Unlock()
					continue
				}

				// Consider the existing entry for controllerPath invalid, as it is
				// older than 5 minutes.
				delete(r.v2ControllerPathCache.cache, controllerPath)
			}
			r.v2ControllerPathCache.Unlock()

			cgpaths, err := os.ReadDir(controllerPath)
			if err != nil {
				return cPaths, fmt.Errorf("error fetching cgroupV2 controllers for cgroup location '%s' and path line '%s': %w", r.cgroupMountpoints.V2Loc, line, err)
			}
			// In order to produce the same kind of data for cgroups V1 and V2 controllers,
			// We iterate over the group, and look for controllers, since the V2 unified system doesn't list them under the PID
			for _, singlePath := range cgpaths {
				if strings.Contains(singlePath.Name(), "stat") {
					controllerName := strings.TrimSuffix(singlePath.Name(), ".stat")
					cPaths.V2[controllerName] = ControllerPath{ControllerPath: path, FullPath: controllerPath, IsV2: true}
				}
			}
			r.v2ControllerPathCache.Lock()
			r.v2ControllerPathCache.cache[controllerPath] = pathListWithTime{
				added:    time.Now(),
				pathList: cPaths,
			}
			r.v2ControllerPathCache.Unlock()
			// cgroup v1
		} else {
			subsystems := strings.Split(fields[1], ",")
			for _, subsystem := range subsystems {
				fullPath := filepath.Join(r.cgroupMountpoints.V1Mounts[subsystem], path)
				cPaths.V1[subsystem] = ControllerPath{ControllerPath: path, FullPath: fullPath, IsV2: false}
			}
		}
	}

	if sc.Err() != nil {
		return cPaths, fmt.Errorf("error scanning cgroup file: %w", err)
	}

	return cPaths, nil
}