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

Commit 166ae693 authored by Peter Collingbourne's avatar Peter Collingbourne Committed by Gerrit Code Review
Browse files

Merge "Introduce additional service options for controlling memory cgroups."

parents 2141f9a6 d7157c22
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;