Skip to content

Commit 73bf9fb

Browse files
committed
retrieve the inode of the cgroup node controller and send it as container id
1 parent 9b5e1e0 commit 73bf9fb

2 files changed

Lines changed: 303 additions & 97 deletions

File tree

Lines changed: 138 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package com.timgroup.statsd;
22

3+
import java.io.BufferedReader;
34
import java.io.IOException;
5+
import java.io.StringReader;
46
import java.nio.file.Files;
57
import java.nio.file.Path;
68
import java.nio.file.Paths;
9+
import java.util.Arrays;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
713
import java.util.regex.Matcher;
814
import java.util.regex.Pattern;
915

@@ -14,50 +20,89 @@
1420
*/
1521
class CgroupReader {
1622
private static final Path CGROUP_PATH = Paths.get("/proc/self/cgroup");
23+
/**
24+
* DEFAULT_CGROUP_MOUNT_PATH is the default cgroup mount path.
25+
**/
26+
private static final Path DEFAULT_CGROUP_MOUNT_PATH = Paths.get("/sys/fs/cgroup");
27+
/**
28+
* CGROUP_NS_PATH is the path to the cgroup namespace file.
29+
**/
30+
private static final Path CGROUP_NS_PATH = Paths.get("/proc/self/ns/cgroup");
1731
private static final String CONTAINER_SOURCE = "[0-9a-f]{64}";
1832
private static final String TASK_SOURCE = "[0-9a-f]{32}-\\d+";
1933
private static final Pattern LINE_RE = Pattern.compile("^\\d+:[^:]*:(.+)$", Pattern.MULTILINE | Pattern.UNIX_LINES);
2034
private static final Pattern CONTAINER_RE = Pattern.compile(
2135
"(" + CONTAINER_SOURCE + "|" + TASK_SOURCE + ")(?:.scope)?$");
2236

37+
/**
38+
* CGROUPV1_BASE_CONTROLLER is the controller used to identify the container-id
39+
* in cgroup v1 (memory).
40+
**/
41+
private static final String CGROUPV1_BASE_CONTROLLER = "memory";
42+
/**
43+
* CGROUPV2_BASE_CONTROLLER is the controller used to identify the container-id
44+
* in cgroup v2.
45+
**/
46+
private static final String CGROUPV2_BASE_CONTROLLER = "";
47+
/**
48+
* HOST_CGROUP_NAMESPACE_INODE is the inode of the host cgroup namespace.
49+
**/
50+
private static final long HOST_CGROUP_NAMESPACE_INODE = 0xEFFFFFFBL;
51+
2352
private boolean readOnce = false;
53+
/**
54+
* containerID holds either the container ID or the cgroup controller inode.
55+
**/
2456
public String containerID;
2557

2658
/**
27-
* Parses /proc/self/cgroup and returns the container ID if available.
59+
* Returns the container ID if available or the cgroup controller inode.
2860
*
29-
* @throws IOException
30-
* if /proc/self/cgroup is readable and still an I/O error
31-
* occurs reading from the stream
61+
* @throws IOException if /proc/self/cgroup is readable and still an I/O error
62+
* occurs reading from the stream.
3263
*/
3364
public String getContainerID() throws IOException {
3465
if (readOnce) {
3566
return containerID;
3667
}
3768

38-
containerID = read(CGROUP_PATH);
69+
final String cgroupContent = read(CGROUP_PATH);
70+
if (cgroupContent == null || cgroupContent.isEmpty()) {
71+
return null;
72+
}
73+
containerID = parse(cgroupContent);
74+
/*
75+
* If the container ID is not available it means that the application is either
76+
* not running in a container or running is private cgroup namespace, we
77+
* fallback to the cgroup controller inode. The agent (7.51+) will use it to get
78+
* the container ID.
79+
*
80+
*/
81+
if ((containerID == null || containerID.equals("")) && !isHostCgroupNamespace(CGROUP_NS_PATH)) {
82+
containerID = getCgroupInode(DEFAULT_CGROUP_MOUNT_PATH, cgroupContent);
83+
}
3984
return containerID;
4085
}
4186

87+
/**
88+
* Parses `path` (=/proc/self/cgroup) and returns the container ID if available.
89+
*
90+
* @throws IOException if /proc/self/cgroup is readable and still an I/O error
91+
* occurs reading from the stream.
92+
*/
4293
private String read(Path path) throws IOException {
4394
readOnce = true;
4495
if (!Files.isReadable(path)) {
4596
return null;
4697
}
4798

48-
final String content = new String(Files.readAllBytes(path));
49-
if (content.isEmpty()) {
50-
return null;
51-
}
52-
53-
return parse(content);
99+
return new String(Files.readAllBytes(path));
54100
}
55101

56102
/**
57103
* Parses a Cgroup file content and returns the corresponding container ID.
58104
*
59-
* @param cgroupsContent
60-
* Cgroup file content
105+
* @param cgroupsContent Cgroup file content
61106
*/
62107
public static String parse(final String cgroupsContent) {
63108
final Matcher lines = LINE_RE.matcher(cgroupsContent);
@@ -71,4 +116,84 @@ public static String parse(final String cgroupsContent) {
71116

72117
return null;
73118
}
119+
120+
/**
121+
* Returns true if the host cgroup namespace is used.
122+
* It looks at the inode of `/proc/self/ns/cgroup` and compares it to
123+
* HOST_CGROUP_NAMESPACE_INODE.
124+
*
125+
* @param path Path to the cgroup namespace file.
126+
*/
127+
private static boolean isHostCgroupNamespace(final Path path) {
128+
long hostCgroupInode = inodeForPath(path);
129+
return hostCgroupInode == HOST_CGROUP_NAMESPACE_INODE;
130+
}
131+
132+
/**
133+
* Returns the inode for the given path.
134+
*
135+
* @param path Path to the cgroup namespace file.
136+
*/
137+
private static long inodeForPath(final Path path) {
138+
try {
139+
long inode = (long) Files.getAttribute(path, "unix:ino");
140+
return inode;
141+
} catch (Exception e) {
142+
return 0;
143+
}
144+
}
145+
146+
/**
147+
* Returns the cgroup controller inode for the given cgroup mount path and
148+
* procSelfCgroupPath.
149+
*
150+
* @param cgroupMountPath Path to the cgroup mount point.
151+
* @param cgroupContent String content of the cgroup file.
152+
*/
153+
public static String getCgroupInode(final Path cgroupMountPath, final String cgroupContent) throws IOException {
154+
Map<String, String> cgroupControllersPaths = parseCgroupNodePath(cgroupContent);
155+
if (cgroupControllersPaths == null) {
156+
return null;
157+
}
158+
159+
// Retrieve the cgroup inode from /sys/fs/cgroup+controller+cgroupNodePath
160+
List<String> controllers = Arrays.asList(CGROUPV1_BASE_CONTROLLER, CGROUPV2_BASE_CONTROLLER);
161+
for (String controller : controllers) {
162+
String cgroupNodePath = cgroupControllersPaths.get(controller);
163+
if (cgroupNodePath == null) {
164+
continue;
165+
}
166+
Path path = Paths.get(cgroupMountPath.toString(), controller, cgroupNodePath);
167+
long inode = inodeForPath(path);
168+
if (inode > 2) {
169+
return "in-" + inode;
170+
}
171+
}
172+
173+
return null;
174+
}
175+
176+
/**
177+
* Returns a map of cgroup controllers and their corresponding cgroup path.
178+
*
179+
* @param cgroupContent Cgroup file content.
180+
*/
181+
public static Map<String, String> parseCgroupNodePath(final String cgroupContent) throws IOException {
182+
Map<String, String> res = new HashMap<>();
183+
BufferedReader br = new BufferedReader(new StringReader(cgroupContent));
184+
185+
String line;
186+
while ((line = br.readLine()) != null) {
187+
String[] tokens = line.split(":");
188+
if (tokens.length != 3) {
189+
continue;
190+
}
191+
if (CGROUPV1_BASE_CONTROLLER.equals(tokens[1]) || tokens[1].isEmpty()) {
192+
res.put(tokens[1], tokens[2]);
193+
}
194+
}
195+
196+
br.close();
197+
return res;
198+
}
74199
}

0 commit comments

Comments
 (0)