From patchwork Thu Jul 20 07:08:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yosry Ahmed X-Patchwork-Id: 13319872 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id D2BABEB64DC for ; Thu, 20 Jul 2023 07:08:47 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id 944512800C3; Thu, 20 Jul 2023 03:08:45 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id 8097528004C; Thu, 20 Jul 2023 03:08:45 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 65C252800C3; Thu, 20 Jul 2023 03:08:45 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0016.hostedemail.com [216.40.44.16]) by kanga.kvack.org (Postfix) with ESMTP id 4257828004C for ; Thu, 20 Jul 2023 03:08:45 -0400 (EDT) Received: from smtpin05.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay03.hostedemail.com (Postfix) with ESMTP id 171DAA0180 for ; Thu, 20 Jul 2023 07:08:45 +0000 (UTC) X-FDA: 81031112610.05.536A0D7 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) by imf07.hostedemail.com (Postfix) with ESMTP id 3B2A64001D for ; Thu, 20 Jul 2023 07:08:43 +0000 (UTC) Authentication-Results: imf07.hostedemail.com; dkim=pass header.d=google.com header.s=20221208 header.b=byOeZzDw; dmarc=pass (policy=reject) header.from=google.com; spf=pass (imf07.hostedemail.com: domain of 3et24ZAoKCEU5vzy5hotlknvvnsl.jvtspu14-ttr2hjr.vyn@flex--yosryahmed.bounces.google.com designates 209.85.219.202 as permitted sender) smtp.mailfrom=3et24ZAoKCEU5vzy5hotlknvvnsl.jvtspu14-ttr2hjr.vyn@flex--yosryahmed.bounces.google.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1689836923; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:content-transfer-encoding: in-reply-to:in-reply-to:references:references:dkim-signature; bh=VyMIM9+tcYiRGMTsBGjvi0vU3Mwws8djANktEDDglwA=; b=emVdC7yGj2CHgY7JWn2UXgGkOehOXTvsJqCSwQZfq/GnaUrTsMDu1anDTA++yZfJQHjpVg JE64LjSIUF7ibqhpvZe/aqLeyRCCvUEO6CB31xTCCS4dMVnVXZI/qh0IEk0UerY7tpnARy QlpFo3mQ7yHAz7YhndksXsY82yDlAr8= ARC-Authentication-Results: i=1; imf07.hostedemail.com; dkim=pass header.d=google.com header.s=20221208 header.b=byOeZzDw; dmarc=pass (policy=reject) header.from=google.com; spf=pass (imf07.hostedemail.com: domain of 3et24ZAoKCEU5vzy5hotlknvvnsl.jvtspu14-ttr2hjr.vyn@flex--yosryahmed.bounces.google.com designates 209.85.219.202 as permitted sender) smtp.mailfrom=3et24ZAoKCEU5vzy5hotlknvvnsl.jvtspu14-ttr2hjr.vyn@flex--yosryahmed.bounces.google.com ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1689836923; a=rsa-sha256; cv=none; b=mGHW+A/CLPmHgNvdFv9xc0QFXx5DMICBYDzY/orkyhd0Ai+zaSrdDoiz+TgDZGAws2oM0w mUCBOhda4E8NEhnbMwayuIsw/c0KjsmImZEB3vwuqu2QuaWPLzMkzpit+2zgVwg5wLIeOi LfoXti6L9t4/p3GN/SYTSzb6NeBr3R0= Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-cfdebfe9c14so327062276.3 for ; Thu, 20 Jul 2023 00:08:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1689836922; x=1692428922; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=VyMIM9+tcYiRGMTsBGjvi0vU3Mwws8djANktEDDglwA=; b=byOeZzDwSe+TlCjYRKBoXqqWywqR6C1yG/A40dOXCXNDbvhvWImB25mBWajRKzlpsd 7pw0yGkSnP78uE/qIJ91xiFtjZ8WsotdIkdUAUo9pCwMIjy6NapBzMWDaD5jSQ5WRKqG Se0fctUKe/3aEu5ubRL9pul76zHCCtINkNBEyKURGH2aFpknJ55HycotBhqzgpzYPQKE UJtv9JLEiNXgfyyCUh834Zn94be/WG5kodBP7zmNrBOk5cu/mZ3dShiUkQYtjSSYO1Rs 4gRgPSThLVz2L65vzOc7OySwGSHuufaREX+F7eqZX0F2Ko4WmwxK9yLR5upAijtlIKWO +cGA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1689836922; x=1692428922; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=VyMIM9+tcYiRGMTsBGjvi0vU3Mwws8djANktEDDglwA=; b=AIoiGScqYWZdYaIeoBAEaXROjiPaxsRcj+UIs941p8BDew7bUBgEyG+U9sbJxU7kCF aERz/e5wzQqhCYSvnkgzTARYZZzbthWgmzqqK2iXxQPY1ym8ZfywqKCk1Frd4Yu/LIBn z8Y73ZqR98FmxAuoMK6iNqEHyXSHJexufycetTq4bJmV2FlifvIcC1Hm9S2CILlzVBMg JrtYYOL395Od3ujvUoJo+MHYemrBrAztF72oSmroyN581lmtwHJktJxTeHhfgTOm9275 mLN9LcPFgeISTnKCbuBsoQessb/u6f2tDlMjqGZLr/AUF4ErrfqZ7s9+ic8gDzpgAsDk d/Sw== X-Gm-Message-State: ABy/qLZRJ4y1GJ9SunY1FwFy6jlw1wkvYKoE16ag8eBGnNtkgiXLBKaY TrWKq9W/RzBrFOOuK5W42opFcP0M/x0h/UtZ X-Google-Smtp-Source: APBJJlGnnUAk0gRTSVnJDIUOzDDJab7OFbTWyp59V1sIEHIaW1b9MjwRCYZFrCmirYEKnPFJ8IXeXLXbdqJdCfyP X-Received: from yosry.c.googlers.com ([fda3:e722:ac3:cc00:7f:e700:c0a8:2327]) (user=yosryahmed job=sendgmr) by 2002:a05:6902:1709:b0:cab:e42c:876b with SMTP id by9-20020a056902170900b00cabe42c876bmr40935ybb.3.1689836922401; Thu, 20 Jul 2023 00:08:42 -0700 (PDT) Date: Thu, 20 Jul 2023 07:08:25 +0000 In-Reply-To: <20230720070825.992023-1-yosryahmed@google.com> Mime-Version: 1.0 References: <20230720070825.992023-1-yosryahmed@google.com> X-Mailer: git-send-email 2.41.0.255.g8b1d071c50-goog Message-ID: <20230720070825.992023-9-yosryahmed@google.com> Subject: [RFC PATCH 8/8] selftests: cgroup: test_memcontrol: add a selftest for memcg recharging From: Yosry Ahmed To: Andrew Morton , Johannes Weiner , Michal Hocko , Roman Gushchin , Shakeel Butt Cc: Muchun Song , "Matthew Wilcox (Oracle)" , Tejun Heo , Zefan Li , Yu Zhao , Luis Chamberlain , Kees Cook , Iurii Zaikin , "T.J. Mercier" , Greg Thelen , linux-kernel@vger.kernel.org, linux-mm@kvack.org, cgroups@vger.kernel.org, Yosry Ahmed X-Rspamd-Queue-Id: 3B2A64001D X-Rspam-User: X-Rspamd-Server: rspam02 X-Stat-Signature: yh3xxa8cpcc7xtg8w45mijrgbznfwt8c X-HE-Tag: 1689836923-692455 X-HE-Meta: U2FsdGVkX18dlYhRR/BZjYd/VDzgafbaiiFGXk28ARbAblq4M6Z+/g1QjODWDsNo8z0/a2QX94SPIMll/XUij9ssqobzz3+IdwWE+cL6xkp3AkN8kVlAT+qvYD6I/eGnIl+DSl0MdVXBEqwMxO47HAVw2IMuoS4UmTRH3cpNLmDQjkk8gSKNn0zBVTlgsI/5mmYG2XsLPJY+ToZ7QH1Jj7CtCTjCOlv9bD826hlM9xTUiZAos7AAC7i7Pl5nCzFEAUHOq9Y1Zj6wA/swv1p4WqlFcxsRWQNtqBpc7Kyyp+oobvF5F1XjuyfKYeegxjzJ/wWwKfO2jiOj/fEgRlt8o9ps5dAxJamMuaklrWOvpbpHml76utBGvu5zjBeHCWHd4UjzUYw0F5GRsr6kKL1OHYL/CQFWYZKYajx9RCkBe04MoAYKOvCG1Mopa9ow4D9G6Oz1r063E0JVReofDkRrsUbG3dvWUf/voAkxftgloi12nWjvY0JHqVuJ393k/iJxdnanYhmpBeq6kP/0aPbORsmyvhf3rOd/MwyR24YA/FBlhCYWd3S+615/97Vs7VkbbMKqgpQFxskFD3AEBpjYuTiJ5OTfo7MzHtcmXYI/0jA2vZjSe/OxdGvavgRiVz7GKHlkztdTrzkOc6/cK+xxhzBtrLgbPkAgXEfE2T4Y2zthOCKO59lSGfPm89bvL3+gHeElod/CGhV0yQjAE/Cqa/QEAVLMcwSPY1IXTgfq4N1wJYEdwX9V3us/Mk2JFKZlZ0Snwp0fH8+q5S8warSnLtY4B4dxz3/Bpu5i8PmdglcA2KAG7Rm7KQ18UyUtxKf2bcuvy+RoMGjDkc7Mxf6Etkv7KQJRlltml9n5I65apBddeUC+/z4eIbcJJCK+0Lchx1p1opACDFgDt7Hdv8IAl9/qrBHLvF4BizLVwuasFhMftWewPIiumOoGXc3Y05AIr3DZUyPz2oKFlrWGpVx kf5azP38 7iyIAg7+w7rsnt/K14KF0z0p40pdNUEgsrCwxsEXV9kfi/ZMGOd7dZBdrGniIPwHMyt4lwDDPtnYpWV/kujWuON54GpA3zUu8vBi+Oh+8bRu5g6CFn1hSnkJjMOAGDES0Qbj3JdpTqhxNFmAlF6zoJdbL0Yjjak00gvp2bkCCyWimczIgwB242EINUzi7qr8rwv7TJGjEvmvd2AlyiI8REq48BxE7wHYB+LtorGPfWePcEGJqmfuIN3D+bJeVRF9Wr5h8cj2ni0G9YBNR29OCqJYV5b9rMwFujIbGCRxaKj6J7loOcAi5gEMyhdpn8c28tjA+fOP6SCuO2zoCCFFeHx32HeEo5rumYM5BHsS3fUk2ZFjFyZ0Ijde6SVHAlWUCMqX+q4m41dcYmf6o8K/GdrMo4xqQSNTXe8ymqMNaDxC+b3VnWXefglybfj2bi/km7YY4Ql4RB45Sx8WBM9qB4MaN6hNvbgrGQPkCJJSOQgOn0uvjJrlFZ+9zaPSeAa5qEI8aGG01AGWLC5JhjKcBgtC4vrfYLVI6Kt06Z1rXWsJvEfyriquKKF+opSVjJW035WyFlEkD2+i8OwndfeCyieLAuxTVYnFfIZHYFzRGsgOFw4aulpSRs0ZVmhDYP4SmbR/pGh6Fa+RPe0e2QksiRQ87+hgHp7DD9R7/9bxDpmCpTItpyGr7fb5WrA== X-Bogosity: Ham, tests=bogofilter, spamicity=0.007979, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: When a memcg is removed, any mapped pages charged to it are recharged to the memcg of the process(es) mapping them. Any remaining pages are recharged using deferred recharge on the next time they are accessed or ditied. Add a selftest that exercises these paths for shmem and normal files: - A page is recharged on offlining if it is already mapped into the address space of a process in a different memcg. - A page is recharged after offlining when written to by a process in a different memcg (if the write results in dirtying the page). - A page is recharged after offlining when read by a process in a different memcg. - A page is recharged after offlining when mapped by a process in a different memcg. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/cgroup/cgroup_util.c | 14 + tools/testing/selftests/cgroup/cgroup_util.h | 1 + .../selftests/cgroup/test_memcontrol.c | 310 ++++++++++++++++++ 3 files changed, 325 insertions(+) diff --git a/tools/testing/selftests/cgroup/cgroup_util.c b/tools/testing/selftests/cgroup/cgroup_util.c index e8bbbdb77e0d..e853b2a4db77 100644 --- a/tools/testing/selftests/cgroup/cgroup_util.c +++ b/tools/testing/selftests/cgroup/cgroup_util.c @@ -519,6 +519,20 @@ int is_swap_enabled(void) return cnt > 1; } + +int is_memcg_recharging_enabled(void) +{ + char buf[10]; + bool enabled; + + if (read_text("/proc/sys/vm/recharge_offline_memcgs", + buf, sizeof(buf)) <= 0) + return -1; + + enabled = strtol(buf, NULL, 10); + return enabled; +} + int set_oom_adj_score(int pid, int score) { char path[PATH_MAX]; diff --git a/tools/testing/selftests/cgroup/cgroup_util.h b/tools/testing/selftests/cgroup/cgroup_util.h index c92df4e5d395..10c0fa36bfd7 100644 --- a/tools/testing/selftests/cgroup/cgroup_util.h +++ b/tools/testing/selftests/cgroup/cgroup_util.h @@ -49,6 +49,7 @@ extern int get_temp_fd(void); extern int alloc_pagecache(int fd, size_t size); extern int alloc_anon(const char *cgroup, void *arg); extern int is_swap_enabled(void); +extern int is_memcg_recharging_enabled(void); extern int set_oom_adj_score(int pid, int score); extern int cg_wait_for_proc_count(const char *cgroup, int count); extern int cg_killall(const char *cgroup); diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index c7c9572003a8..4e1ea93e0a54 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "../kselftest.h" #include "cgroup_util.h" @@ -1287,6 +1289,313 @@ static int test_memcg_oom_group_score_events(const char *root) return ret; } +/* Map 50M from the beginning of a file */ +static int map_fd_50M_noexit(const char *cgroup, void *arg) +{ + size_t size = MB(50); + int ppid = getppid(); + int fd = (long)arg; + char *memory; + + memory = mmap(NULL, size, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, 0); + if (memory == MAP_FAILED) { + fprintf(stderr, "error: mmap, errno %d\n", errno); + return -1; + } + + while (getppid() == ppid) + sleep(1); + + munmap(memory, size); + return 0; +} + +/* + * Write 50M to the beginning of a file. + * The file is sync'ed first to make sure any dirty pages are laundered before + * we dirty them again. + */ +static int write_fd_50M(const char *cgroup, void *arg) +{ + size_t size = MB(50); + int fd = (long)arg; + char buf[PAGE_SIZE]; + int i; + + fsync(fd); + lseek(fd, 0, SEEK_SET); + for (i = 0; i < size; i += sizeof(buf)) + write(fd, buf, sizeof(buf)); + + return 0; +} + +/* See write_fd_50M() */ +static int write_fd_50M_noexit(const char *cgroup, void *arg) +{ + int ppid = getppid(); + + write_fd_50M(cgroup, arg); + + while (getppid() == ppid) + sleep(1); + + return 0; +} + +/* Read 50M from the beginning of a file */ +static int read_fd_50M_noexit(const char *cgroup, void *arg) +{ + size_t size = MB(50); + int ppid = getppid(); + int fd = (long)arg; + char buf[PAGE_SIZE]; + int i; + + lseek(fd, 0, SEEK_SET); + for (i = 0; i < size; i += sizeof(buf)) + read(fd, buf, sizeof(buf)); + + while (getppid() == ppid) + sleep(1); + + return 0; +} + +#define TEST_RECHARGE_DIR "/test-recharge" + +static int __test_memcg_recharge(const char *root, char *stat_name) +{ + char *parent = NULL, *child1 = NULL, *child2 = NULL; + long stat, prev, pstat, current; + int ret = KSFT_FAIL; + char file_path[256]; + int i, pid; + struct { + int fd; + int (*before_fn)(const char *cgroup, void *arg); + int (*after_fn)(const char *cgroup, void *arg); + } test_files[] = { + /* test recharge for already mapped file */ + { + .before_fn = map_fd_50M_noexit, + }, + /* test recharge on new mapping after offline */ + { + .after_fn = map_fd_50M_noexit, + }, + /* test recharge on write after offline */ + { + .after_fn = write_fd_50M_noexit, + }, + /* test recharge on read after offline */ + { + .after_fn = read_fd_50M_noexit, + } + }; + + parent = cg_name(root, "parent"); + if (!parent) + goto cleanup; + + if (cg_create(parent)) + goto cleanup; + + if (cg_write(parent, "cgroup.subtree_control", "+memory")) + goto cleanup; + + child1 = cg_name(parent, "child1"); + if (!child1) + goto cleanup; + + if (cg_create(child1)) + goto cleanup; + + child2 = cg_name(parent, "child2"); + if (!child2) + goto cleanup; + + if (cg_create(child2)) + goto cleanup; + + for (i = 0; i < ARRAY_SIZE(test_files); i++) { + long target = MB(50) * (i+1); /* 50MB per file */ + int fd; + + snprintf(file_path, sizeof(file_path), "%s/file%d", + TEST_RECHARGE_DIR, i); + + fd = open(file_path, O_CREAT | O_RDWR); + if (fd < 0) + goto cleanup; + + test_files[i].fd = fd; + if (cg_run(child1, write_fd_50M, (void *)(long) fd)) + goto cleanup; + + stat = 0; + do { + sleep(1); + prev = stat; + stat = cg_read_key_long(child1, "memory.stat", + stat_name); + } while (stat < target && stat > prev); + + if (stat < target) { + fprintf(stderr, "error: child1 %s %ld < %ld", + stat_name, stat, target); + goto cleanup; + } + + current = cg_read_long(child1, "memory.current"); + if (current < target) { + fprintf(stderr, "error: child1 current %ld < %ld", + current, target); + goto cleanup; + } + + if (test_files[i].before_fn) { + pid = cg_run_nowait(child2, test_files[i].before_fn, + (void *)(long)fd); + if (pid < 0) + goto cleanup; + /* make sure before_fn() finishes executing before offlining */ + sleep(1); + } + } + + current = cg_read_long(child2, "memory.current"); + if (current > MB(1)) { + fprintf(stderr, "error: child2 current %ld > 1M\n", current); + goto cleanup; + } + + stat = cg_read_key_long(child2, "memory.stat", stat_name); + if (stat > 0) { + fprintf(stderr, "error: child2 %s %ld > 0\n", + stat_name, stat); + goto cleanup; + } + + if (cg_destroy(child1) < 0) + goto cleanup; + + for (i = 0; i < ARRAY_SIZE(test_files); i++) { + long target = MB(50) * (i+1); + int fd = test_files[i].fd; + + if (test_files[i].after_fn) { + pid = cg_run_nowait(child2, test_files[i].after_fn, + (void *)(long)fd); + if (pid < 0) + goto cleanup; + } + + stat = 0; + do { + sleep(1); + prev = stat; + stat = cg_read_key_long(child2, "memory.stat", + stat_name); + } while (stat < target && stat > prev); + + if (stat < target) { + fprintf(stderr, "error: child2 %s %ld < %ld\n", + stat_name, stat, target); + goto cleanup; + } + + current = cg_read_long(child2, "memory.current"); + if (current < target) { + fprintf(stderr, "error: child2 current %ld < %ld\n", + current, target); + goto cleanup; + } + } + + pstat = cg_read_key_long(parent, "memory.stat", stat_name); + if (stat < pstat) { + fprintf(stderr, "error: recharged %s (%ld) < total (%ld)\n", + stat_name, stat, pstat); + goto cleanup; + } + + ret = KSFT_PASS; +cleanup: + if (child2) { + cg_destroy(child2); + free(child2); + } + if (child1) { + cg_destroy(child1); + free(child1); + } + if (parent) { + cg_destroy(parent); + free(parent); + } + for (i = 0; i < ARRAY_SIZE(test_files); i++) { + close(test_files[i].fd); + snprintf(file_path, sizeof(file_path), "%s/file%d", + TEST_RECHARGE_DIR, i); + remove(file_path); + } + return ret; +} + +static int test_memcg_recharge(const char *root) +{ + int i, ret = KSFT_PASS; + struct { + char *mount_type, *stat_name; + } test_setups[] = { + /* test both shmem & normal files */ + { + .mount_type = "tmpfs", + .stat_name = "shmem", + }, + { + .stat_name = "file", + } + }; + + if (!is_memcg_recharging_enabled()) + return KSFT_SKIP; + + if (unshare(CLONE_NEWNS) < 0) + return KSFT_FAIL; + + if (mount(NULL, "/", "", MS_REC | MS_PRIVATE, NULL) < 0) + return KSFT_FAIL; + + for (i = 0; i < ARRAY_SIZE(test_setups); i++) { + int setup_ret = KSFT_FAIL; + char *mount_type = test_setups[i].mount_type; + char *stat_name = test_setups[i].stat_name; + + if (mkdir(TEST_RECHARGE_DIR, 0777) < 0) + goto next; + + if (mount_type && + mount(NULL, TEST_RECHARGE_DIR, mount_type, 0, NULL) < 0) + goto next; + + setup_ret = __test_memcg_recharge(root, stat_name); + +next: + if (mount_type) + umount(TEST_RECHARGE_DIR); + remove(TEST_RECHARGE_DIR); + + if (setup_ret == KSFT_FAIL) { + ret = KSFT_FAIL; + break; + } + } + umount("/"); + return ret; +} + #define T(x) { x, #x } struct memcg_test { int (*fn)(const char *root); @@ -1306,6 +1615,7 @@ struct memcg_test { T(test_memcg_oom_group_leaf_events), T(test_memcg_oom_group_parent_events), T(test_memcg_oom_group_score_events), + T(test_memcg_recharge), }; #undef T