diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/util/CgroupsLCEResourcesHandler.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/util/CgroupsLCEResourcesHandler.java index d5bd225..a457152 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/util/CgroupsLCEResourcesHandler.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/util/CgroupsLCEResourcesHandler.java @@ -59,7 +59,11 @@ private final String MTAB_FILE = "/proc/mounts"; private final String CGROUPS_FSTYPE = "cgroup"; private final String CONTROLLER_CPU = "cpu"; + private final String CPU_PERIOD_US = "cfs_period_us"; + private final String CPU_QUOTA_US = "cfs_quota_us"; private final int CPU_DEFAULT_WEIGHT = 1024; // set by kernel + private final int MAX_QUOTA_US = 1000 * 1000; + private final int MIN_PERIOD_US = 1000; private final Map controllerPaths; // Controller -> path private long deleteCgroupTimeout; @@ -117,8 +121,27 @@ public void init(LinuxContainerExecutor lce) throws IOException { } initializeControllerPaths(); - } + // cap overall usage to the number of cores allocated to YARN + int periodUS = MIN_PERIOD_US; + int quotaUS = MAX_QUOTA_US; + int vcores = + conf.getInt(YarnConfiguration.NM_VCORES, + YarnConfiguration.DEFAULT_NM_VCORES); + + periodUS = (quotaUS) / vcores; + + // cfs_period_us can't be less than 1000 microseconds + // if the value of periodUS is less than 1000, we can't really use cgroups + // to limit cpu + if (periodUS < MIN_PERIOD_US) { + periodUS = 100 * MIN_PERIOD_US; + quotaUS = -1; + } + + updateCgroup(CONTROLLER_CPU, "", CPU_PERIOD_US, String.valueOf(periodUS)); + updateCgroup(CONTROLLER_CPU, "", CPU_QUOTA_US, String.valueOf(quotaUS)); + } boolean isCpuWeightEnabled() { return this.cpuWeightEnabled; @@ -274,7 +297,7 @@ public String getResourcesOption(ContainerId containerId) { BufferedReader in = null; try { - in = new BufferedReader(new FileReader(new File(MTAB_FILE))); + in = new BufferedReader(new FileReader(new File(getMtabFileName()))); for (String str = in.readLine(); str != null; str = in.readLine()) { @@ -292,13 +315,13 @@ public String getResourcesOption(ContainerId containerId) { } } } catch (IOException e) { - throw new IOException("Error while reading " + MTAB_FILE, e); + throw new IOException("Error while reading " + getMtabFileName(), e); } finally { // Close the streams try { in.close(); } catch (IOException e2) { - LOG.warn("Error closing the stream: " + MTAB_FILE, e2); + LOG.warn("Error closing the stream: " + getMtabFileName(), e2); } } @@ -334,7 +357,12 @@ private void initializeControllerPaths() throws IOException { } } else { throw new IOException("Not able to enforce cpu weights; cannot find " - + "cgroup for cpu controller in " + MTAB_FILE); + + "cgroup for cpu controller in " + getMtabFileName()); } } + + @VisibleForTesting + String getMtabFileName() { + return MTAB_FILE; + } } diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/util/TestCgroupsLCEResourcesHandler.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/util/TestCgroupsLCEResourcesHandler.java index 611045e..3f7d97c 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/util/TestCgroupsLCEResourcesHandler.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/util/TestCgroupsLCEResourcesHandler.java @@ -17,13 +17,16 @@ */ package org.apache.hadoop.yarn.server.nodemanager.util; +import org.apache.commons.io.FileUtils; +import org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor; import org.junit.Assert; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.util.Clock; import org.junit.Test; -import java.io.File; -import java.io.FileOutputStream; +import java.io.*; +import java.util.List; +import java.util.Scanner; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -70,4 +73,100 @@ public void run() { Assert.assertFalse(handler.deleteCgroup(file.getPath())); } + static class MockLinuxContainerExecutor extends LinuxContainerExecutor { + @Override + public void mountCgroups(List x, String y) { + } + } + + static class CustomCgroupsLCEResourceHandler extends + CgroupsLCEResourcesHandler { + + String mtabFile; + + void setMtabFile(String file) { + mtabFile = file; + } + + @Override + String getMtabFileName() { + return mtabFile; + } + } + + @Test + public void testInit() throws IOException { + int cores = 3; + int expectedQuotaUS = 1000 * 1000; + LinuxContainerExecutor mockLCE = new MockLinuxContainerExecutor(); + CustomCgroupsLCEResourceHandler handler = + new CustomCgroupsLCEResourceHandler(); + YarnConfiguration conf = new YarnConfiguration(); + conf.setInt(YarnConfiguration.NM_VCORES, cores); + handler.setConf(conf); + handler.initConfig(); + + // create mock cgroup + File cgroupDir = new File("target", UUID.randomUUID().toString()); + if (!cgroupDir.mkdir()) { + String message = "Could not create dir " + cgroupDir.getAbsolutePath(); + throw new IOException(message); + } + File cgroupMountDir = new File(cgroupDir.getAbsolutePath(), "/hadoop-yarn"); + if (!cgroupMountDir.mkdir()) { + String message = + "Could not create dir " + cgroupMountDir.getAbsolutePath(); + throw new IOException(message); + } + + // create mock mtab + String mtabContent = + "none " + cgroupDir.getAbsolutePath() + " cgroup rw,relatime,cpu 0 0"; + File mockMtab = new File("target", UUID.randomUUID().toString()); + if (!mockMtab.exists()) { + if (!mockMtab.createNewFile()) { + String message = "Could not create file " + mockMtab.getAbsolutePath(); + throw new IOException(message); + } + } + FileWriter mtabWriter = new FileWriter(mockMtab.getAbsoluteFile()); + mtabWriter.write(mtabContent); + mtabWriter.close(); + mockMtab.deleteOnExit(); + + // setup our handler and call init() + handler.setMtabFile(mockMtab.getAbsolutePath()); + handler.init(mockLCE); + + // check values + int expectedPeriodUS = (1000 * 1000) / cores; + int period = readIntFromFile(cgroupMountDir + "/" + "cpu.cfs_period_us"); + int quota = readIntFromFile(cgroupMountDir + "/" + "cpu.cfs_quota_us"); + Assert.assertEquals(expectedPeriodUS, period); + Assert.assertEquals(expectedQuotaUS, quota); + + conf.setInt(YarnConfiguration.NM_VCORES, 20); + handler.setConf(conf); + handler.init(mockLCE); + expectedPeriodUS = expectedQuotaUS / 20; + period = readIntFromFile(cgroupMountDir + "/" + "cpu.cfs_period_us"); + quota = readIntFromFile(cgroupMountDir + "/" + "cpu.cfs_quota_us"); + Assert.assertEquals(expectedPeriodUS, period); + Assert.assertEquals(expectedQuotaUS, quota); + + conf.setInt(YarnConfiguration.NM_VCORES, 2000); + handler.setConf(conf); + handler.init(mockLCE); + quota = readIntFromFile(cgroupMountDir + "/" + "cpu.cfs_quota_us"); + Assert.assertEquals(-1, quota); + FileUtils.deleteQuietly(cgroupDir); + } + + private int readIntFromFile(String filename) throws IOException { + Scanner scanner = new Scanner(new File(filename)); + if (scanner.hasNextInt()) { + return scanner.nextInt(); + } + return -1; + } }