Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d7157c22 authored by Peter Collingbourne's avatar Peter Collingbourne
Browse files

Introduce additional service options for controlling memory cgroups.

The memcg.limit_percent option can be used to limit the cgroup's
max RSS to the given value as a percentage of the device's physical
memory. The memcg.limit_property option specifies the name of a
property that can be used to control the cgroup's max RSS. These
new options correspond to the arguments to the limitProcessMemory
function in frameworks/av/media/libmedia/MediaUtils.cpp; this will
allow us to add these options to the rc files for the programs that
call this function and then remove the callers in a later change.

There is also a change in semantics: the memcg.* options now have
an effect on all devices which support memory cgroups, not just
those with ro.config.low_ram or ro.config.per_app_memcg set to true.
This change also brings the semantics in line with the documentation,
so it looks like the previous semantics were unintentional.

Change-Id: I9495826de6e477b952e23866743b5fa600adcacb
Bug: 118642754
parent e94eb514
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -235,9 +235,16 @@ runs the service.
  to "123,124,125". Since keycodes are handled very early in init,
  only PRODUCT_DEFAULT_PROPERTY_OVERRIDES properties can be used.

`memcg.limit_in_bytes <value>`
> Sets the child's memory.limit_in_bytes to the specified value (only if memcg is mounted),
  which must be equal or greater than 0.
`memcg.limit_in_bytes <value>` and `memcg.limit_percent <value>`
> Sets the child's memory.limit_in_bytes to the minimum of `limit_in_bytes`
  bytes and `limit_percent` which is interpreted as a percentage of the size
  of the device's physical memory (only if memcg is mounted).
  Values must be equal or greater than 0.

`memcg.limit_property <value>`
> Sets the child's memory.limit_in_bytes to the value of the specified property
  (only if memcg is mounted). This property will override the values specified
  via `memcg.limit_in_bytes` and `memcg.limit_percent`.

`memcg.soft_limit_in_bytes <value>`
> Sets the child's memory.soft_limit_in_bytes to the specified value (only if memcg is mounted),
+43 −7
Original line number Diff line number Diff line
@@ -235,9 +235,6 @@ Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
      ioprio_pri_(0),
      priority_(0),
      oom_score_adjust_(-1000),
      swappiness_(-1),
      soft_limit_in_bytes_(-1),
      limit_in_bytes_(-1),
      start_order_(0),
      args_(args) {}

@@ -630,6 +627,18 @@ Result<Success> Service::ParseMemcgLimitInBytes(std::vector<std::string>&& args)
    return Success();
}

Result<Success> Service::ParseMemcgLimitPercent(std::vector<std::string>&& args) {
    if (!ParseInt(args[1], &limit_percent_, 0)) {
        return Error() << "limit_percent value must be equal or greater than 0";
    }
    return Success();
}

Result<Success> Service::ParseMemcgLimitProperty(std::vector<std::string>&& args) {
    limit_property_ = std::move(args[1]);
    return Success();
}

Result<Success> Service::ParseMemcgSoftLimitInBytes(std::vector<std::string>&& args) {
    if (!ParseInt(args[1], &soft_limit_in_bytes_, 0)) {
        return Error() << "soft_limit_in_bytes value must be equal or greater than 0";
@@ -783,6 +792,10 @@ const Service::OptionParserMap::Map& Service::OptionParserMap::map() const {
        {"keycodes",    {1,     kMax, &Service::ParseKeycodes}},
        {"memcg.limit_in_bytes",
                        {1,     1,    &Service::ParseMemcgLimitInBytes}},
        {"memcg.limit_percent",
                        {1,     1,    &Service::ParseMemcgLimitPercent}},
        {"memcg.limit_property",
                        {1,     1,    &Service::ParseMemcgLimitProperty}},
        {"memcg.soft_limit_in_bytes",
                        {1,     1,    &Service::ParseMemcgSoftLimitInBytes}},
        {"memcg.swappiness",
@@ -1001,11 +1014,13 @@ Result<Success> Service::Start() {
    start_order_ = next_start_order_++;
    process_cgroup_empty_ = false;

    errno = -createProcessGroup(uid_, pid_);
    bool use_memcg = swappiness_ != -1 || soft_limit_in_bytes_ != -1 || limit_in_bytes_ != -1 ||
                      limit_percent_ != -1 || !limit_property_.empty();
    errno = -createProcessGroup(uid_, pid_, use_memcg);
    if (errno != 0) {
        PLOG(ERROR) << "createProcessGroup(" << uid_ << ", " << pid_ << ") failed for service '"
                    << name_ << "'";
    } else {
    } else if (use_memcg) {
        if (swappiness_ != -1) {
            if (!setProcessGroupSwappiness(uid_, pid_, swappiness_)) {
                PLOG(ERROR) << "setProcessGroupSwappiness failed";
@@ -1018,8 +1033,29 @@ Result<Success> Service::Start() {
            }
        }

        if (limit_in_bytes_ != -1) {
            if (!setProcessGroupLimit(uid_, pid_, limit_in_bytes_)) {
        size_t computed_limit_in_bytes = limit_in_bytes_;
        if (limit_percent_ != -1) {
            long page_size = sysconf(_SC_PAGESIZE);
            long num_pages = sysconf(_SC_PHYS_PAGES);
            if (page_size > 0 && num_pages > 0) {
                size_t max_mem = SIZE_MAX;
                if (size_t(num_pages) < SIZE_MAX / size_t(page_size)) {
                    max_mem = size_t(num_pages) * size_t(page_size);
                }
                computed_limit_in_bytes =
                        std::min(computed_limit_in_bytes, max_mem / 100 * limit_percent_);
            }
        }

        if (!limit_property_.empty()) {
            // This ends up overwriting computed_limit_in_bytes but only if the
            // property is defined.
            computed_limit_in_bytes = android::base::GetUintProperty(
                    limit_property_, computed_limit_in_bytes, SIZE_MAX);
        }

        if (computed_limit_in_bytes != size_t(-1)) {
            if (!setProcessGroupLimit(uid_, pid_, computed_limit_in_bytes)) {
                PLOG(ERROR) << "setProcessGroupLimit failed";
            }
        }
+8 −3
Original line number Diff line number Diff line
@@ -154,6 +154,8 @@ class Service {
    Result<Success> ParseOomScoreAdjust(std::vector<std::string>&& args);
    Result<Success> ParseOverride(std::vector<std::string>&& args);
    Result<Success> ParseMemcgLimitInBytes(std::vector<std::string>&& args);
    Result<Success> ParseMemcgLimitPercent(std::vector<std::string>&& args);
    Result<Success> ParseMemcgLimitProperty(std::vector<std::string>&& args);
    Result<Success> ParseMemcgSoftLimitInBytes(std::vector<std::string>&& args);
    Result<Success> ParseMemcgSwappiness(std::vector<std::string>&& args);
    Result<Success> ParseNamespace(std::vector<std::string>&& args);
@@ -213,9 +215,12 @@ class Service {

    int oom_score_adjust_;

    int swappiness_;
    int soft_limit_in_bytes_;
    int limit_in_bytes_;
    int swappiness_ = -1;
    int soft_limit_in_bytes_ = -1;

    int limit_in_bytes_ = -1;
    int limit_percent_ = -1;
    std::string limit_property_;

    bool process_cgroup_empty_ = false;

+3 −1
Original line number Diff line number Diff line
@@ -31,8 +31,10 @@ int killProcessGroup(uid_t uid, int initialPid, int signal);
// that it only returns 0 in the case that the cgroup exists and it contains no processes.
int killProcessGroupOnce(uid_t uid, int initialPid, int signal);

int createProcessGroup(uid_t uid, int initialPid);
int createProcessGroup(uid_t uid, int initialPid, bool memControl = false);

// Set various properties of a process group. For these functions to work, the process group must
// have been created by passing memControl=true to createProcessGroup.
bool setProcessGroupSwappiness(uid_t uid, int initialPid, int swappiness);
bool setProcessGroupSoftLimit(uid_t uid, int initialPid, int64_t softLimitInBytes);
bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes);
+58 −57
Original line number Diff line number Diff line
@@ -53,49 +53,31 @@ using android::base::WriteStringToFile;

using namespace std::chrono_literals;

#define MEM_CGROUP_PATH "/dev/memcg/apps"
#define MEM_CGROUP_TASKS "/dev/memcg/apps/tasks"
#define ACCT_CGROUP_PATH "/acct"
static const char kCpuacctCgroup[] = "/acct";
static const char kMemoryCgroup[] = "/dev/memcg/apps";

#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"

std::once_flag init_path_flag;

static const std::string& GetCgroupRootPath() {
    static std::string cgroup_root_path;
    std::call_once(init_path_flag, [&]() {
        // low-ram devices use per-app memcg by default, unlike high-end ones
        bool low_ram_device = GetBoolProperty("ro.config.low_ram", false);
        bool per_app_memcg =
            GetBoolProperty("ro.config.per_app_memcg", low_ram_device);
        if (per_app_memcg) {
            // Check if mem cgroup is mounted, only then check for
            // write-access to avoid SELinux denials
            cgroup_root_path =
                (access(MEM_CGROUP_TASKS, F_OK) || access(MEM_CGROUP_PATH, W_OK) ?
                ACCT_CGROUP_PATH : MEM_CGROUP_PATH);
        } else {
            cgroup_root_path = ACCT_CGROUP_PATH;
        }
    });
    return cgroup_root_path;
static bool isMemoryCgroupSupported() {
    static bool memcg_supported = !access("/dev/memcg/memory.limit_in_bytes", F_OK);
    return memcg_supported;
}

static std::string ConvertUidToPath(uid_t uid) {
    return StringPrintf("%s/uid_%d", GetCgroupRootPath().c_str(), uid);
static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
    return StringPrintf("%s/uid_%d", cgroup, uid);
}

static std::string ConvertUidPidToPath(uid_t uid, int pid) {
    return StringPrintf("%s/uid_%d/pid_%d", GetCgroupRootPath().c_str(), uid, pid);
static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
    return StringPrintf("%s/uid_%d/pid_%d", cgroup, uid, pid);
}

static int RemoveProcessGroup(uid_t uid, int pid) {
static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid) {
    int ret;

    auto uid_pid_path = ConvertUidPidToPath(uid, pid);
    auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
    ret = rmdir(uid_pid_path.c_str());

    auto uid_path = ConvertUidToPath(uid);
    auto uid_path = ConvertUidToPath(cgroup, uid);
    rmdir(uid_path.c_str());

    return ret;
@@ -124,8 +106,8 @@ static void RemoveUidProcessGroups(const std::string& uid_path) {
void removeAllProcessGroups()
{
    LOG(VERBOSE) << "removeAllProcessGroups()";
    const auto& cgroup_root_path = GetCgroupRootPath();
    std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path.c_str()), closedir);
    for (const char* cgroup_root_path : {kCpuacctCgroup, kMemoryCgroup}) {
        std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path), closedir);
        if (root == NULL) {
            PLOG(ERROR) << "Failed to open " << cgroup_root_path;
        } else {
@@ -139,19 +121,20 @@ void removeAllProcessGroups()
                    continue;
                }

            auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name);
                auto path = StringPrintf("%s/%s", cgroup_root_path, dir->d_name);
                RemoveUidProcessGroups(path);
                LOG(VERBOSE) << "Removing " << path;
                if (rmdir(path.c_str()) == -1) PLOG(WARNING) << "Failed to remove " << path;
            }
        }
    }
}

// Returns number of processes killed on success
// Returns 0 if there are no processes in the process cgroup left to kill
// Returns -1 on error
static int DoKillProcessGroupOnce(uid_t uid, int initialPid, int signal) {
    auto path = ConvertUidPidToPath(uid, initialPid) + PROCESSGROUP_CGROUP_PROCS_FILE;
static int DoKillProcessGroupOnce(const char* cgroup, uid_t uid, int initialPid, int signal) {
    auto path = ConvertUidPidToPath(cgroup, uid, initialPid) + PROCESSGROUP_CGROUP_PROCS_FILE;
    std::unique_ptr<FILE, decltype(&fclose)> fd(fopen(path.c_str(), "re"), fclose);
    if (!fd) {
        PLOG(WARNING) << "Failed to open process cgroup uid " << uid << " pid " << initialPid;
@@ -217,11 +200,16 @@ static int DoKillProcessGroupOnce(uid_t uid, int initialPid, int signal) {
}

static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {
    const char* cgroup =
            (!access(ConvertUidPidToPath(kCpuacctCgroup, uid, initialPid).c_str(), F_OK))
                    ? kCpuacctCgroup
                    : kMemoryCgroup;

    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();

    int retry = retries;
    int processes;
    while ((processes = DoKillProcessGroupOnce(uid, initialPid, signal)) > 0) {
    while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {
        LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;
        if (retry > 0) {
            std::this_thread::sleep_for(5ms);
@@ -251,7 +239,7 @@ static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries)
            LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
                      << " in " << static_cast<int>(ms) << "ms";
        }
        return RemoveProcessGroup(uid, initialPid);
        return RemoveProcessGroup(cgroup, uid, initialPid);
    } else {
        if (retries > 0) {
            LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
@@ -285,16 +273,29 @@ static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t
    return true;
}

int createProcessGroup(uid_t uid, int initialPid)
static bool isPerAppMemcgEnabled() {
    static bool per_app_memcg =
            GetBoolProperty("ro.config.per_app_memcg", GetBoolProperty("ro.config.low_ram", false));
    return per_app_memcg;
}

int createProcessGroup(uid_t uid, int initialPid, bool memControl)
{
    auto uid_path = ConvertUidToPath(uid);
    const char* cgroup;
    if (isMemoryCgroupSupported() && (memControl || isPerAppMemcgEnabled())) {
        cgroup = kMemoryCgroup;
    } else {
        cgroup = kCpuacctCgroup;
    }

    auto uid_path = ConvertUidToPath(cgroup, uid);

    if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
        PLOG(ERROR) << "Failed to make and chown " << uid_path;
        return -errno;
    }

    auto uid_pid_path = ConvertUidPidToPath(uid, initialPid);
    auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, initialPid);

    if (!MkdirAndChown(uid_pid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
        PLOG(ERROR) << "Failed to make and chown " << uid_pid_path;
@@ -313,12 +314,12 @@ int createProcessGroup(uid_t uid, int initialPid)
}

static bool SetProcessGroupValue(uid_t uid, int pid, const std::string& file_name, int64_t value) {
    if (GetCgroupRootPath() != MEM_CGROUP_PATH) {
    if (!isMemoryCgroupSupported()) {
        PLOG(ERROR) << "Memcg is not mounted.";
        return false;
    }

    auto path = ConvertUidPidToPath(uid, pid) + file_name;
    auto path = ConvertUidPidToPath(kMemoryCgroup, uid, pid) + file_name;

    if (!WriteStringToFile(std::to_string(value), path)) {
        PLOG(ERROR) << "Failed to write '" << value << "' to " << path;