11package com .timgroup .statsd ;
22
3+ import java .io .BufferedReader ;
34import java .io .IOException ;
5+ import java .io .StringReader ;
46import java .nio .file .Files ;
57import java .nio .file .Path ;
68import java .nio .file .Paths ;
9+ import java .util .Arrays ;
10+ import java .util .HashMap ;
11+ import java .util .List ;
12+ import java .util .Map ;
713import java .util .regex .Matcher ;
814import java .util .regex .Pattern ;
915
1420 */
1521class 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