Skip to content

Commit fd50d26

Browse files
committed
YARN-11687. Update CGroupsResourceCalculator to track usages using cgroupv2
- fix by teszt
1 parent 6a6e30d commit fd50d26

File tree

10 files changed

+194
-127
lines changed

10 files changed

+194
-127
lines changed

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/ResourceCalculatorProcessTree.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ public float getCpuUsagePercent() {
167167
*/
168168
public static ResourceCalculatorProcessTree getResourceCalculatorProcessTree(
169169
String pid, Class<? extends ResourceCalculatorProcessTree> clazz, Configuration conf) {
170-
171170
if (clazz != null) {
172171
try {
173172
Constructor <? extends ResourceCalculatorProcessTree> c = clazz.getConstructor(String.class);

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/resources/AbstractCGroupsHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,11 @@ public String getCGroupMountPath() {
559559
return this.cGroupsMountConfig.getMountPath();
560560
}
561561

562+
@Override
563+
public String getCGroupV2MountPath() {
564+
return this.cGroupsMountConfig.getV2MountPath();
565+
}
566+
562567
@Override
563568
public String toString() {
564569
return CGroupsHandlerImpl.class.getName() + "{" +

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/resources/AbstractCGroupsResourceCalculator.java

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
import java.nio.charset.StandardCharsets;
2424
import java.nio.file.Path;
2525
import java.nio.file.Paths;
26+
import java.util.Arrays;
27+
import java.util.Collections;
2628
import java.util.List;
2729
import java.util.Map;
2830
import java.util.concurrent.ConcurrentHashMap;
29-
import java.util.stream.Collectors;
3031

3132
import org.slf4j.Logger;
3233
import org.slf4j.LoggerFactory;
@@ -58,7 +59,7 @@ public abstract class AbstractCGroupsResourceCalculator extends ResourceCalculat
5859
private final String rssMemoryKey;
5960
private final String virtualMemoryKey;
6061

61-
public AbstractCGroupsResourceCalculator(
62+
protected AbstractCGroupsResourceCalculator(
6263
String pid,
6364
List<String> totalJiffiesKeys,
6465
String rssMemoryKey,
@@ -79,22 +80,19 @@ public void initialize() throws YarnException {
7980

8081
@Override
8182
public long getCumulativeCpuTime() {
82-
//https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
8383
long totalJiffies = getTotalJiffies();
84-
return jiffyLengthMs < 0 || totalJiffies == UNAVAILABLE
84+
return jiffyLengthMs == UNAVAILABLE || totalJiffies == UNAVAILABLE
8585
? UNAVAILABLE
8686
: getTotalJiffies() * jiffyLengthMs;
8787
}
8888

8989
@Override
9090
public long getRssMemorySize(int olderThanAge) {
91-
//https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files
9291
return 1 < olderThanAge ? UNAVAILABLE : getStat(rssMemoryKey);
9392
}
9493

9594
@Override
9695
public long getVirtualMemorySize(int olderThanAge) {
97-
//https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files
9896
return 1 < olderThanAge ? UNAVAILABLE : getStat(virtualMemoryKey);
9997
}
10098

@@ -117,70 +115,65 @@ public float getCpuUsagePercent() {
117115

118116
@Override
119117
public void updateProcessTree() {
120-
121-
List<Path> filesToRead;
122-
try {
123-
filesToRead = getFilesToRead();
124-
} catch (IOException e) {
125-
LOG.error("Failed to get cgroup files to update for pid:" + pid, e);
126-
return;
127-
}
128-
129-
for (Path statFile : filesToRead) {
118+
for (Path statFile : getFilesToRead()) {
130119
try {
131-
String[] lines = fileToLines(statFile);
132-
if (lines != null && 1 == lines.length) {
133-
addSingleLineToStat(statFile, lines[0]);
134-
} else if (lines != null && 1 < lines.length) {
120+
List<String> lines = fileToLines(statFile);
121+
if (1 == lines.size()) {
122+
addSingleLineToStat(statFile, lines.get(0));
123+
} else if (1 < lines.size()) {
135124
addMultiLineToStat(statFile, lines);
136125
}
137126
} catch (IOException e) {
138-
LOG.warn("Failed to read cgroup files for pid: " + pid, e);
127+
LOG.debug(String.format("Failed to read cgroup file %s for pid %s", statFile, pid), e);
139128
}
140129
}
141-
130+
LOG.debug("After updateProcessTree the {} pid has stats {}", pid, stats);
142131
cpuTimeTracker.updateElapsedJiffies(BigInteger.valueOf(getTotalJiffies()), clock.getTime());
143132
}
144133

145134
private void addSingleLineToStat(Path file, String line) {
146135
stats.put(file.getFileName().toString(), line.trim());
147136
}
148137

149-
private void addMultiLineToStat(Path file, String[] lines) {
138+
private void addMultiLineToStat(Path file, List<String> lines) {
150139
for (String line : lines) {
151140
String[] parts = line.split(" ");
152-
stats.put(file.getFileName() + "#" + parts[0], parts[1]);
141+
if (1 < parts.length) {
142+
stats.put(file.getFileName() + "#" + parts[0], parts[1]);
143+
}
153144
}
154145
}
155146

156147
private long getTotalJiffies() {
157-
List<Long> jiffies = totalJiffiesKeys.stream()
158-
.map(this::getStat).collect(Collectors.toList());
159-
return jiffies.stream().allMatch(stats -> stats == UNAVAILABLE)
160-
? UNAVAILABLE
161-
: jiffies.stream().filter(stat -> UNAVAILABLE < stat).reduce(0L, Long::sum);
148+
Long reduce = totalJiffiesKeys.stream()
149+
.map(this::getStat)
150+
.filter(stats -> stats != UNAVAILABLE)
151+
.reduce(0L, Long::sum);
152+
return reduce == 0 ? UNAVAILABLE : reduce;
162153
}
163154

164-
protected abstract List<Path> getFilesToRead() throws IOException;
165-
166155
private long getStat(String key) {
167156
return Long.parseLong(stats.getOrDefault(key, String.valueOf(UNAVAILABLE)));
168157
}
169158

170-
protected String[] readLinesFromCGroupFileFromProcDir() throws IOException {
159+
protected abstract List<Path> getFilesToRead();
160+
161+
protected List<String> readLinesFromCGroupFileFromProcDir() throws IOException {
171162
// https://docs.kernel.org/admin-guide/cgroup-v2.html#processes
172-
return fileToString(Paths.get(procFs, pid, "cgroup"))
173-
.split(System.lineSeparator());
163+
Path cgroup = Paths.get(procFs, pid, "cgroup");
164+
List<String> result = Arrays.asList(fileToString(cgroup).split(System.lineSeparator()));
165+
LOG.debug("The {} pid has the following lines in the procfs cgroup file {}", pid, result);
166+
return result;
174167
}
175168

176169
protected String fileToString(Path path) throws IOException {
177170
return FileUtils.readFileToString(path.toFile(), StandardCharsets.UTF_8).trim();
178171
}
179172

180-
protected String[] fileToLines(Path path) throws IOException {
173+
protected List<String> fileToLines(Path path) throws IOException {
181174
return path.toFile().exists()
182-
? FileUtils.readFileToString(path.toFile(), StandardCharsets.UTF_8).trim().split(System.lineSeparator())
183-
: null;
175+
? Arrays.asList(FileUtils.readFileToString(path.toFile(), StandardCharsets.UTF_8).trim().split(System.lineSeparator()))
176+
: Collections.emptyList();
184177
}
185178

186179
@VisibleForTesting
@@ -203,10 +196,6 @@ void setProcFs(String procFs) {
203196
this.procFs = procFs;
204197
}
205198

206-
public CpuTimeTracker getCpuTimeTracker() {
207-
return cpuTimeTracker;
208-
}
209-
210199
public CGroupsHandler getcGroupsHandler() {
211200
return cGroupsHandler;
212201
}

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/resources/CGroupsHandler.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,10 @@ String getCGroupParam(CGroupController controller, String cGroupId,
239239
* @return parameter value as read from the parameter file
240240
*/
241241
String getCGroupMountPath();
242+
243+
/**
244+
* Returns CGroupV2 Mount Path.
245+
* @return parameter value as read from the parameter file
246+
*/
247+
String getCGroupV2MountPath();
242248
}

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/resources/CGroupsResourceCalculator.java

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,22 @@
2121
import java.io.File;
2222
import java.io.IOException;
2323
import java.nio.file.Path;
24+
import java.nio.file.Paths;
25+
import java.util.ArrayList;
2426
import java.util.Arrays;
2527
import java.util.List;
26-
import java.util.stream.Collectors;
27-
import java.util.stream.Stream;
2828

29-
import org.apache.hadoop.yarn.exceptions.YarnException;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
3031

3132
/**
32-
* A cgroups file-system based Resource calculator without the process tree
33-
* features.
33+
* A CGroupV1 file-system based Resource calculator without the process tree features.
34+
*
35+
* Warning!!!
36+
* ResourceCalculatorProcessTree can be used with mapreduce.job.process-tree.class property.
37+
* However, those instances runs in the mapreduce task, and can not access to the
38+
* ResourceHandlerModule, what is only initialised in the NodeManager process not in the container.
39+
* So this implementation will not work with the mapreduce.job.process-tree.class property.
3440
*
3541
* Limitation: CGroups does not have the ability to measure virtual memory usage.
3642
* This includes memory reserved but not used.
@@ -40,20 +46,50 @@
4046
* resource calculator or CombinedResourceCalculator.
4147
*/
4248
public class CGroupsResourceCalculator extends AbstractCGroupsResourceCalculator {
43-
static final String CPU_STAT = "cpuacct.stat";
44-
static final String MEM_STAT = "memory.usage_in_bytes";
45-
static final String MEMSW_STAT = "memory.memsw.usage_in_bytes";
49+
private static final Logger LOG = LoggerFactory.getLogger(CGroupsResourceCalculator.class);
4650

47-
private File cpuStat;
48-
private File memStat;
49-
private File memswStat;
51+
/**
52+
* <a href="https://docs.kernel.org/admin-guide/cgroup-v1/cpuacct.html">Documentation</a>
53+
*
54+
* ...
55+
* cpuacct.stat file lists a few statistics which further divide the CPU time obtained by the cgroup into user and system times.
56+
* Currently the following statistics are supported:
57+
* - user: Time spent by tasks of the cgroup in user mode.
58+
* - system: Time spent by tasks of the cgroup in kernel mode.
59+
* user and system are in USER_HZ unit.
60+
* ...
61+
*
62+
* <a href="https://litux.nl/mirror/kerneldevelopment/0672327201/ch10lev1sec3.html">Documentation</a>
63+
*
64+
* ...
65+
* In kernels earlier than 2.6, changing the value of HZ resulted in user-space anomalies.
66+
* This happened because values were exported to user-space in units of ticks-per-second.
67+
* As these interfaces became permanent, applications grew to rely on a specific value of HZ.
68+
* Consequently, changing HZ would scale various exported values by some constantwithout user-space knowing!
69+
* Uptime would read 20 hours when it was in fact two!
70+
*
71+
* To prevent such problems, the kernel needs to scale all exported jiffies values.
72+
* It does this by defining USER_HZ, which is the HZ value that user-space expects. On x86,
73+
* because HZ was historically 100, USER_HZ is 100. The macro jiffies_to_clock_t()
74+
* is then used to scale a tick count in terms of HZ to a tick count in terms of USER_HZ.
75+
* The macro used depends on whether USER_HZ and HZ are integer multiples of themselves.
76+
* ...
77+
*
78+
*/
79+
private static final String CPU_STAT = "cpuacct.stat";
5080

5181
/**
52-
* Create resource calculator for all Yarn containers.
82+
* <a href="https://docs.kernel.org/admin-guide/cgroup-v1/memory.html#usage-in-bytes">Documentation</a>
83+
*
84+
* ...
85+
* For efficiency, as other kernel components, memory cgroup uses some optimization to avoid unnecessary cacheline false sharing.
86+
* usage_in_bytes is affected by the method and doesn’t show ‘exact’ value of memory (and swap) usage,
87+
* it’s a fuzz value for efficient access. (Of course, when necessary, it’s synchronized.)
88+
* ...
89+
*
5390
*/
54-
public CGroupsResourceCalculator() {
55-
this(null);
56-
}
91+
private static final String MEM_STAT = "memory.usage_in_bytes";
92+
private static final String MEMSW_STAT = "memory.memsw.usage_in_bytes";
5793

5894
/**
5995
* Create resource calculator for the container that has the specified pid.
@@ -69,33 +105,41 @@ public CGroupsResourceCalculator(String pid) {
69105
}
70106

71107
@Override
72-
public void initialize() throws YarnException {
73-
super.initialize();
108+
protected List<Path> getFilesToRead() {
109+
List<Path> result = new ArrayList<>();
110+
74111
try {
75-
setCGroupFilePaths();
112+
String cpuRelative = getCGroupRelativePath(CGroupsHandler.CGroupController.CPUACCT);
113+
if (cpuRelative != null) {
114+
File cpuDir = new File(cGroupsHandler.getControllerPath(CGroupsHandler.CGroupController.CPUACCT), cpuRelative);
115+
result.add(Paths.get(cpuDir.getAbsolutePath(), CPU_STAT));
116+
}
76117
} catch (IOException e) {
77-
throw new YarnException("Could not init cgroup files for pid: " + pid, e);
118+
LOG.debug("Exception while looking for CPUACCT controller for pid: " + pid, e);
78119
}
79-
}
80120

81-
@Override
82-
protected List<Path> getFilesToRead() throws IOException {
83-
return Stream.of(
84-
cpuStat,
85-
memStat,
86-
memswStat
87-
).map(File::toPath).collect(Collectors.toList());
121+
try {
122+
String memoryRelative = getCGroupRelativePath(CGroupsHandler.CGroupController.MEMORY);
123+
if (memoryRelative != null) {
124+
File memDir = new File(cGroupsHandler.getControllerPath(CGroupsHandler.CGroupController.MEMORY), memoryRelative);
125+
result.add(Paths.get(memDir.getAbsolutePath(), MEM_STAT));
126+
result.add(Paths.get(memDir.getAbsolutePath(), MEMSW_STAT));
127+
}
128+
} catch (IOException e) {
129+
LOG.debug("Exception while looking for MEMORY controller for pid: " + pid, e);
130+
}
131+
132+
return result;
88133
}
89134

90135
private String getCGroupRelativePath(CGroupsHandler.CGroupController controller)
91-
throws YarnException, IOException {
136+
throws IOException {
92137
return pid == null
93138
? cGroupsHandler.getRelativePathForCGroup("")
94139
: getCGroupRelativePathForPid(controller);
95140
}
96141

97-
private String getCGroupRelativePathForPid(CGroupsHandler.CGroupController controller)
98-
throws YarnException, IOException {
142+
private String getCGroupRelativePathForPid(CGroupsHandler.CGroupController controller) throws IOException {
99143
for (String line : readLinesFromCGroupFileFromProcDir()) {
100144
// example line: 6:cpuacct,cpu:/yarn/container_1
101145
String[] parts = line.split(":");
@@ -105,19 +149,7 @@ private String getCGroupRelativePathForPid(CGroupsHandler.CGroupController contr
105149
return cGroupsHandler.getRelativePathForCGroup(cgroup);
106150
}
107151
}
108-
throw new YarnException(String.format(
109-
"Controller %s not found for %s pid", controller, pid));
110-
}
111-
112-
void setCGroupFilePaths() throws YarnException, IOException {
113-
File cpuDir = new File(
114-
cGroupsHandler.getControllerPath(CGroupsHandler.CGroupController.CPUACCT),
115-
getCGroupRelativePath(CGroupsHandler.CGroupController.CPUACCT));
116-
File memDir = new File(
117-
cGroupsHandler.getControllerPath(CGroupsHandler.CGroupController.MEMORY),
118-
getCGroupRelativePath(CGroupsHandler.CGroupController.MEMORY));
119-
cpuStat = new File(cpuDir, CPU_STAT);
120-
memStat = new File(memDir, MEM_STAT);
121-
memswStat = new File(memDir, MEMSW_STAT);
152+
LOG.debug("No {} controller found for pid {}", controller, pid);
153+
return null;
122154
}
123155
}

0 commit comments

Comments
 (0)