/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.mapred; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * An interface of Linux control groups through a cgroup virtual file system. * A root privilege is not required to manage a control group as long as * a user has an appropriate permission for a target virtual file. */ public abstract class ControlGroup { private static Log LOG = LogFactory.getLog(ControlGroup.class); /** * An interface to a memory sub-system of Linux control groups. To use a * memory cgroup, create a new group and set the memory allocated to the * members of the group. By default, both use_hierarchy and * move_charge_at_immigrate are disable. */ public static class MemoryControlGroup extends ControlGroup { /* * memory.usage_in_bytes # show current res_counter usage for memory * memory.limit_in_bytes # set/show limit of memory usage * memory.failcnt # show the number of memory usage hits limits * memory.max_usage_in_bytes # show max memory usage recorded * memory.soft_limit_in_bytes # set/show soft limit of memory usage * memory.stat # show various statistics * memory.use_hierarchy # set/show hierarchical account enabled * memory.force_empty # trigger forced move charge to parent * memory.swappiness # set/show swappiness parameter of vmscan * memory.move_charge_at_immigrate # set/show controls of moving charges * memory.oom_control # set/show oom controls. * memory.numa_stat # show the number of memory usage per numa node * * Reference: https://www.kernel.org/doc/Documentation/cgroups/memory.txt */ public static final String MEM_USAGE_IN_BYTES = "memory.usage_in_bytes"; public static final String MEM_LIMIT_IN_BYTES = "memory.limit_in_bytes"; public static final String MEM_FAILCNT = "memory.failcnt"; public static final String MEM_MAX_USAGE_IN_BYTES = "memory.max_usage_in_bytes"; public static final String MEM_SOFT_LIMIT_IN_BYTES = "memory.soft_limit_in_bytes"; public static final String MEM_STAT = "memory.stat"; public static final String MEM_USE_HIERARCHY = "memory.use_hierarchy"; public static final String MEM_FORCE_EMPTY = "memory.force_empty"; public static final String MEM_SWAPPINESS = "memory.swappiness"; public static final String MEM_MOVE_CHARGE_AT_IMMIGRATE = "memory.move_charge_at_immigrate"; public static final String MEM_OOM_CONTROL = "memory.oom_control"; public static final String MEM_NUMA_STAT = "memory.numa_stat"; public static final String MEM_RSS_IN_BYTES = "rss"; public static final long MEM_LIMIT_IN_BYTES_DISABLE = -1; public static final long MEM_USE_HIERARCHY_ENABLE = 1; public static final long MEM_USE_HIERARCHY_DISABLE = 0; public static final long MEM_MOVE_CHARGE_AT_IMMIGRATE_ENABLE = 3; public static final long MEM_MOVE_CHARGE_AT_IMMIGRATE_DISABLE = 0; public static final long MEM_MAX_LIMIT_IN_BYTES = 9223372036854771712L; public static final String SUBSYS_MEMORY = "memory"; public static boolean isAvailable() { return isSubsystemAvailable(SUBSYS_MEMORY); } public MemoryControlGroup(String path) { super(path); } public MemoryControlGroup getSubGroup(String name) { return new MemoryControlGroup(getSubDirectory(name)); } public MemoryControlGroup createSubGroup(String name) { return new MemoryControlGroup(createSubDirectory(name)); } public long getMemoryUsage() { return getLongParameter(MEM_USAGE_IN_BYTES); } public long getMaxMemoryUsage() { return getLongParameter(MEM_MAX_USAGE_IN_BYTES); } public long getMemoryUsageLimit() { return getLongParameter(MEM_LIMIT_IN_BYTES); } public long getRSSMemoryUsage() { String [] kvPairs = this.getListParameter(MEM_STAT); if (kvPairs == null) { return 0; } for (String kvPair: kvPairs) { String [] kv = kvPair.split("\\s+"); long ret; if (kv.length >= 2 && kv[0].trim().compareToIgnoreCase(MEM_RSS_IN_BYTES) == 0) { try { ret = Long.parseLong(kv[1].trim()); } catch (NumberFormatException ne) { LOG.debug("Error reading parameter "+kv[1] +": \"java.lang.NumberFormatException: "+ne.getMessage()+"\""); ret = 0; } return ret; } } return 0; } public void setMemoryUsageLimit(long value) { if (value > MEM_MAX_LIMIT_IN_BYTES) value = MEM_LIMIT_IN_BYTES_DISABLE; setLongParameter(MEM_LIMIT_IN_BYTES, value); } public void disableMemoryUsageLimit() { setLongParameter(MEM_LIMIT_IN_BYTES, MEM_LIMIT_IN_BYTES_DISABLE); } public void enableMoveChargeAtImmigrate() { setLongParameter(MEM_MOVE_CHARGE_AT_IMMIGRATE, MEM_MOVE_CHARGE_AT_IMMIGRATE_ENABLE); } public void disableMoveChargeAtImmigrate() { setLongParameter(MEM_MOVE_CHARGE_AT_IMMIGRATE, MEM_MOVE_CHARGE_AT_IMMIGRATE_DISABLE); } public void enableUseHierarchy() { if (getLongParameter(MEM_USE_HIERARCHY) != 1) setLongParameter(MEM_USE_HIERARCHY, MEM_USE_HIERARCHY_ENABLE); } public void disableUseHierarchy() { setLongParameter(MEM_USE_HIERARCHY, MEM_USE_HIERARCHY_DISABLE); } public boolean canControl() { return super.canControl() && canWrite(MEM_LIMIT_IN_BYTES) && canMkdir(""); } } /** * An interface to a cpu sub-system of Linux control groups. Currently, this * implementation controls only CPU shares of each group. To use a cpu cgroup, * create a new group and set the cpu share allocated to the members of the * group. */ public static class CPUControlGroup extends ControlGroup { /* * cpu.cfs_period_us # the length of a period (in microseconds) * cpu.cfs_quota_us # the total available run-time within a period * (in microseconds) * cpu.shares # contains an integer value that specifies a relative * share of CPU time available to the tasks in a cgroup * cpu.stat # exports throttling statistics * * Reference: http://www.mjmwired.net/kernel/Documentation/scheduler/sched-bwc.txt * https://access.redhat.com/knowledge/docs/en-US/Red_Hat_Enterprise_Linux/6/html-single/Resource_Management_Guide/index.html#sec-cpu */ public static final String CPU_CFS_PERIOD_US = "cpu.cfs_period_us"; public static final String CPU_CFS_QUOTA_US = "cpu.cfs_quota_us"; public static final String CPU_SHARES = "cpu.shares"; public static final String CPU_STAT = "cpu.stat"; public static final long CPU_SHARES_DEFAULT = 1024; public static final String SUBSYS_CPU = "cpu"; public static boolean isAvailable() { return isSubsystemAvailable(SUBSYS_CPU); } public CPUControlGroup(String path) { super(path); } public CPUControlGroup getSubGroup(String name) { return new CPUControlGroup(getSubDirectory(name)); } public CPUControlGroup createSubGroup(String name) { return new CPUControlGroup(createSubDirectory(name)); } public long getCPUShares() { return getLongParameter(CPU_SHARES); } public void setCPUShares(long value) { setLongParameter(CPU_SHARES, value); } public boolean canControl() { return super.canControl() && canWrite(CPU_SHARES) && canMkdir(""); } } /* * tasks # attach a task(thread) and show list of threads * cgroup.procs # show list of processes * cgroup.event_control # an interface for event_fd() * * Reference: https://www.kernel.org/doc/Documentation/cgroups/memory.txt */ public static final String CG_TASKS = "tasks"; public static final String CG_PROCS = "cgroup.procs"; public static final String CG_EVENT_CONTROL = "cgroup.event_control"; public static final String PROC_CGROUP_PATH = "/proc/cgroups"; private String path; private static boolean isSubsystemAvailable(String tag) { tag = tag + "\t"; String str; BufferedReader buReader = null; try { buReader = new BufferedReader(new FileReader(new File(PROC_CGROUP_PATH))); str = buReader.readLine(); while (str != null) { if (str.startsWith(tag)) { buReader.close(); return true; } str = buReader.readLine(); } buReader.close(); } catch (FileNotFoundException fnfe) { return false; } catch (IOException e) { LOG.warn("Unable to get control group infomation", e); } return false; } public ControlGroup(String path) { this.path = path; } public void addToGroup(String pgrp) { setStringParameter(CG_PROCS, pgrp); } public void addToGroup(String[] pgrpList) { for(String pgrp: pgrpList) { addToGroup(pgrp); } } public String[] getThreadGroupList() { return getListParameter(CG_PROCS); } public boolean deleteGroup() { File target = new File(path); return target.delete(); } public boolean deleteSubGroup(String name) { File target = new File(path, name); return target.delete(); } protected String getSubDirectory(String name) { File target = new File(path, name); return target.getPath(); } protected String createSubDirectory(String name) { File target = new File(path, name); if(!target.mkdir()) { // It is okay only if it fails because of the directory is already existed. if(!target.exists()) { LOG.error("Fail to create sub-directory " + target); return ""; } } return target.getPath(); } protected long getLongParameter(String parameter) { long ret = 0; try { ret = Long.parseLong(getStringParameter(parameter).trim()); } catch (NumberFormatException ne) { LOG.debug("Error reading parameter "+parameter +": \"java.lang.NumberFormatException: "+ne.getMessage()+"\""); ret = 0; } return ret; } protected void setLongParameter(String parameter, long value) { setStringParameter(parameter, String.valueOf(value)); } protected String[] getListParameter(String parameter) { return StringUtils.split(getStringParameter(parameter), '\n'); } protected String getStringParameter(String parameter) { String ret = ""; try { ret = FileUtils.readFileToString(new File(this.path, parameter)); } catch (IOException e) { LOG.debug("Could not retrieve a parameter (" + parameter + ") @ "+path+": \""+e.getMessage()+"\""); ret = ""; } return ret; } protected void setStringParameter(String parameter, String data) { try { FileUtils.writeStringToFile(new File(this.path, parameter), data); } catch (IOException e) { LOG.warn("Could not set a parameter (" + parameter + "/" + data +") @ "+path+": \""+e.getMessage()+"\""); } } protected boolean canRead(String parameter){ return (new File(this.getSubDirectory(parameter))).canRead(); } protected boolean canWrite(String parameter){ return (new File(this.getSubDirectory(parameter))).canWrite(); } protected boolean canExecute(String parameter){ return (new File(this.getSubDirectory(parameter))).canExecute(); } protected boolean canMkdir(String parameter){ return canWrite(parameter) && canExecute(parameter); } public boolean canControl() { return canWrite(CG_PROCS); } }