From nobody Mon May 11 00:50:37 2026 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 46B76C433EF for ; Tue, 19 Apr 2022 21:33:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1358010AbiDSVfr (ORCPT ); Tue, 19 Apr 2022 17:35:47 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60660 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1346877AbiDSVfn (ORCPT ); Tue, 19 Apr 2022 17:35:43 -0400 Received: from mail-qt1-f182.google.com (mail-qt1-f182.google.com [209.85.160.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1222D27FEA; Tue, 19 Apr 2022 14:33:00 -0700 (PDT) Received: by mail-qt1-f182.google.com with SMTP id d14so5328934qtw.5; Tue, 19 Apr 2022 14:33:00 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=J8G/ibBIrn0G3wVr557AIgTZhD6q0eUwk63lcPEE0+k=; b=lKoLNV1zNT5kktAzElOnCutYNXYOsWVcnzmK0HOjKxKiE83QiErA0aWih+IsEgXIAM A0ksdo4XQ+EQ7VfBe0dOWm0/BwUoS42qDZ9HjTCllamS6dzDChRnQmPYu/AzirI6Eapd YMnEjvhGSK1VKnmGfBEGwlw/sHNQh1aM6KfFPoEmFJGgaQLJcwaS4B3MGZm+9EMH0qBk BMbngJgYQLKG7tZv0OEp1cEtd12euLkETzgk4Ik/kXJSdxQjuIYjzFRL4ParOSGgGM7R fQnaxQiKYc9ELkeQYy8RrNSaE7r5I/49/ZKBq74UDm7uAOYYM5YTV+HHkV+1VBi8k5hG 4jUA== X-Gm-Message-State: AOAM532LVdsfs891go7KV9rlH+HiCsJ4UtPpBR86YT8BypLpdXR57gSI OxH63NsSaIFAxEu8KFQKVF4= X-Google-Smtp-Source: ABdhPJwgWeJHL84yhLP90qLNwzu1Cx7Zvu+0rEnKk4A5dDJ/lDGBYQK3s4PNcKjjgeAHG7CpmnWhvw== X-Received: by 2002:a05:622a:8:b0:2e1:cb36:9244 with SMTP id x8-20020a05622a000800b002e1cb369244mr11781499qtw.93.1650403979018; Tue, 19 Apr 2022 14:32:59 -0700 (PDT) Received: from localhost (fwdproxy-ash-007.fbsv.net. [2a03:2880:20ff:7::face:b00c]) by smtp.gmail.com with ESMTPSA id u13-20020a05622a010d00b002f0c6664db1sm628991qtw.49.2022.04.19.14.32.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 Apr 2022 14:32:58 -0700 (PDT) From: David Vernet To: tj@kernel.org, lizefan.x@bytedance.com, hannes@cmpxchg.org, cgroups@vger.kernel.org, linux-kernel@vger.kernel.org Cc: kernel-team@fb.com Subject: [PATCH 1/4] cgroup: Add new test_cpu.c test suite in cgroup selftests Date: Tue, 19 Apr 2022 14:32:42 -0700 Message-Id: <20220419213244.2104972-2-void@manifault.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220419213244.2104972-1-void@manifault.com> References: <20220419213244.2104972-1-void@manifault.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The cgroup selftests suite currently contains tests that validate various aspects of cgroup, such as validating the expected behavior for memory controllers, the expected behavior of cgroup.procs, etc. There are no tests that validate the expected behavior of the cgroup cpu controller. This patch therefore adds a new test_cpu.c file that will contain cpu controller testcases. The file currently only contains a single testcase that validates creating nested cgroups with cgroup.subtree_control including cpu. Future patches will add more sophisticated testcases that validate functional aspects of the cpu controller. Signed-off-by: David Vernet --- tools/testing/selftests/cgroup/.gitignore | 1 + tools/testing/selftests/cgroup/Makefile | 2 + tools/testing/selftests/cgroup/test_cpu.c | 110 ++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 tools/testing/selftests/cgroup/test_cpu.c diff --git a/tools/testing/selftests/cgroup/.gitignore b/tools/testing/self= tests/cgroup/.gitignore index be9643ef6285..306ee1b01e72 100644 --- a/tools/testing/selftests/cgroup/.gitignore +++ b/tools/testing/selftests/cgroup/.gitignore @@ -4,3 +4,4 @@ test_core test_freezer test_kmem test_kill +test_cpu diff --git a/tools/testing/selftests/cgroup/Makefile b/tools/testing/selfte= sts/cgroup/Makefile index 745fe25fa0b9..478217cc1371 100644 --- a/tools/testing/selftests/cgroup/Makefile +++ b/tools/testing/selftests/cgroup/Makefile @@ -10,6 +10,7 @@ TEST_GEN_PROGS +=3D test_kmem TEST_GEN_PROGS +=3D test_core TEST_GEN_PROGS +=3D test_freezer TEST_GEN_PROGS +=3D test_kill +TEST_GEN_PROGS +=3D test_cpu =20 LOCAL_HDRS +=3D $(selfdir)/clone3/clone3_selftests.h $(selfdir)/pidfd/pidf= d.h =20 @@ -20,3 +21,4 @@ $(OUTPUT)/test_kmem: cgroup_util.c $(OUTPUT)/test_core: cgroup_util.c $(OUTPUT)/test_freezer: cgroup_util.c $(OUTPUT)/test_kill: cgroup_util.c +$(OUTPUT)/test_cpu: cgroup_util.c diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/self= tests/cgroup/test_cpu.c new file mode 100644 index 000000000000..4faa279bbab3 --- /dev/null +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include + +#include "../kselftest.h" +#include "cgroup_util.h" + +/* + * This test creates two nested cgroups with and without enabling + * the cpu controller. + */ +static int test_cgcpu_subtree_control(const char *root) +{ + char *parent =3D NULL, *child =3D NULL, *parent2 =3D NULL, *child2 =3D NU= LL; + int ret =3D KSFT_FAIL; + + // Create two nested cgroups with the cpu controller enabled. + parent =3D cg_name(root, "cgcpu_test_0"); + if (!parent) + goto cleanup; + + if (cg_create(parent)) + goto cleanup; + + if (cg_write(parent, "cgroup.subtree_control", "+cpu")) + goto cleanup; + + child =3D cg_name(parent, "cgcpu_test_child"); + if (!child) + goto cleanup; + + if (cg_create(child)) + goto cleanup; + + if (cg_read_strstr(child, "cgroup.controllers", "cpu")) + goto cleanup; + + // Create two nested cgroups without enabling the cpu controller. + parent2 =3D cg_name(root, "cgcpu_test_1"); + if (!parent2) + goto cleanup; + + if (cg_create(parent2)) + goto cleanup; + + child2 =3D cg_name(parent2, "cgcpu_test_child"); + if (!child2) + goto cleanup; + + if (cg_create(child2)) + goto cleanup; + + if (!cg_read_strstr(child2, "cgroup.controllers", "cpu")) + goto cleanup; + + ret =3D KSFT_PASS; + +cleanup: + cg_destroy(child); + free(child); + cg_destroy(child2); + free(child2); + cg_destroy(parent); + free(parent); + cg_destroy(parent2); + free(parent2); + + return ret; +} + +#define T(x) { x, #x } +struct cgcpu_test { + int (*fn)(const char *root); + const char *name; +} tests[] =3D { + T(test_cgcpu_subtree_control), +}; +#undef T + +int main(int argc, char *argv[]) +{ + char root[PATH_MAX]; + int i, ret =3D EXIT_SUCCESS; + + if (cg_find_unified_root(root, sizeof(root))) + ksft_exit_skip("cgroup v2 isn't mounted\n"); + + if (cg_read_strstr(root, "cgroup.subtree_control", "cpu")) + if (cg_write(root, "cgroup.subtree_control", "+cpu")) + ksft_exit_skip("Failed to set cpu controller\n"); + + for (i =3D 0; i < ARRAY_SIZE(tests); i++) { + switch (tests[i].fn(root)) { + case KSFT_PASS: + ksft_test_result_pass("%s\n", tests[i].name); + break; + case KSFT_SKIP: + ksft_test_result_skip("%s\n", tests[i].name); + break; + default: + ret =3D EXIT_FAILURE; + ksft_test_result_fail("%s\n", tests[i].name); + break; + } + } + + return ret; +} --=20 2.30.2 From nobody Mon May 11 00:50:37 2026 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 358A8C433FE for ; Tue, 19 Apr 2022 21:33:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1358018AbiDSVfs (ORCPT ); Tue, 19 Apr 2022 17:35:48 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60694 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1358008AbiDSVfq (ORCPT ); Tue, 19 Apr 2022 17:35:46 -0400 Received: from mail-qt1-f173.google.com (mail-qt1-f173.google.com [209.85.160.173]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EC02331907; Tue, 19 Apr 2022 14:33:02 -0700 (PDT) Received: by mail-qt1-f173.google.com with SMTP id f22so4061853qtp.13; Tue, 19 Apr 2022 14:33:02 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=IislFSHuxEloX/PvVR3ULKpFKGrJ80ALsCIDW418z0Y=; b=PEp3yM4G6qqNiN6yl2vXMF/2e4WNggCZ6Nd0v8ro5YczNKIfQZIaeFUQA4EqpLljI7 IJplZIb7dkuuqycIZub241NaceNFRDn2dO1y50i5S7PQe9pgbfAd+G79t2iQsmKJfymz rAWmlWJbWNAJEGk9XcRyLro28XsOYysJvJwfehXAyUIvc3tqRTdmuEUZk/kVwDYwi8XJ yTc2LqBzIeKzGTEvcksGaAXUQBL/O5frhf4Q1letVP5zn+X6WiJfxlpXP2CS1RAYW3VQ rsmmIgQ7nlLLdQ0IkJLVukVfAIi87EiqUy5GntxxsG7hLoEvZZMfVdj0TQpBQh5k9e0y 5cxg== X-Gm-Message-State: AOAM530UQhNXhEnArPrSFfVFSXSaUZJe9xOI0OFucEnjPUwCCsOt7DLZ 5G3Ych8byZY8FawfG0s8/oY= X-Google-Smtp-Source: ABdhPJw+60SN/9RLN7t8BG6nXdGk1CtsGNWMY04yUNLlqVuNY2ENraBOB56yltq6kbjN3PH9bHsrsw== X-Received: by 2002:ac8:7dc6:0:b0:2f2:1b0:524d with SMTP id c6-20020ac87dc6000000b002f201b0524dmr6861140qte.402.1650403981974; Tue, 19 Apr 2022 14:33:01 -0700 (PDT) Received: from localhost (fwdproxy-ash-023.fbsv.net. [2a03:2880:20ff:17::face:b00c]) by smtp.gmail.com with ESMTPSA id t125-20020a372d83000000b0069c1df12422sm572472qkh.84.2022.04.19.14.33.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 Apr 2022 14:33:01 -0700 (PDT) From: David Vernet To: tj@kernel.org, lizefan.x@bytedance.com, hannes@cmpxchg.org, cgroups@vger.kernel.org, linux-kernel@vger.kernel.org Cc: kernel-team@fb.com Subject: [PATCH 2/4] cgroup: Add test_cgcpu_stats() testcase to cgroup cpu selftests Date: Tue, 19 Apr 2022 14:32:43 -0700 Message-Id: <20220419213244.2104972-3-void@manifault.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220419213244.2104972-1-void@manifault.com> References: <20220419213244.2104972-1-void@manifault.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" test_cpu.c includes testcases that validate the cgroup cpu controller. This patch adds a new testcase called test_cgcpu_stats() that verifies the expected behavior of the cpu.stat interface. In doing so, we define a new hog_cpus_timed() function which takes a cpu_hog_func_param struct that configures how many CPUs it uses, and how long it runs. Future patches will also spawn threads that hog CPUs, so this function will eventually serve those use-cases as well. Signed-off-by: David Vernet --- tools/testing/selftests/cgroup/cgroup_util.h | 3 + tools/testing/selftests/cgroup/test_cpu.c | 105 +++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/tools/testing/selftests/cgroup/cgroup_util.h b/tools/testing/s= elftests/cgroup/cgroup_util.h index 4f66d10626d2..1df13dc8b8aa 100644 --- a/tools/testing/selftests/cgroup/cgroup_util.h +++ b/tools/testing/selftests/cgroup/cgroup_util.h @@ -8,6 +8,9 @@ =20 #define MB(x) (x << 20) =20 +#define USEC_PER_SEC 1000000L +#define NSEC_PER_SEC 1000000000L + /* * Checks if two given values differ by less than err% of their sum. */ diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/self= tests/cgroup/test_cpu.c index 4faa279bbab3..57f6308b1ef4 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -2,11 +2,19 @@ =20 #define _GNU_SOURCE #include +#include +#include #include +#include =20 #include "../kselftest.h" #include "cgroup_util.h" =20 +struct cpu_hog_func_param { + int nprocs; + long runtime_nsec; +}; + /* * This test creates two nested cgroups with and without enabling * the cpu controller. @@ -70,12 +78,109 @@ static int test_cgcpu_subtree_control(const char *root) return ret; } =20 +static void *hog_cpu_thread_func(void *arg) +{ + while (1) + ; + + return NULL; +} + +static int hog_cpus_timed(const char *cgroup, void *arg) +{ + const struct cpu_hog_func_param *param =3D + (struct cpu_hog_func_param *)arg; + long nsecs_remaining =3D param->runtime_nsec; + int i, ret; + + for (i =3D 0; i < param->nprocs; i++) { + pthread_t tid; + + ret =3D pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL); + if (ret !=3D 0) + return ret; + } + + while (nsecs_remaining > 0) { + long nsecs_so_far; + struct timespec ts =3D { + .tv_sec =3D nsecs_remaining / NSEC_PER_SEC, + .tv_nsec =3D nsecs_remaining % NSEC_PER_SEC, + }; + + ret =3D nanosleep(&ts, NULL); + if (ret && errno !=3D EINTR) + return ret; + + ret =3D clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); + if (ret !=3D 0) + return ret; + + nsecs_so_far =3D ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; + nsecs_remaining =3D nsecs_so_far > param->runtime_nsec + ? 0 + : param->runtime_nsec - nsecs_so_far; + } + + return 0; +} + +/* + * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that + * cpu.stats shows the expected output. + */ +static int test_cgcpu_stats(const char *root) +{ + int ret =3D KSFT_FAIL; + long usage_usec, user_usec, system_usec; + long usage_seconds =3D 2; + long expected_usage_usec =3D usage_seconds * USEC_PER_SEC; + char *cgcpu; + + cgcpu =3D cg_name(root, "cgcpu_test"); + if (!cgcpu) + goto cleanup; + + if (cg_create(cgcpu)) + goto cleanup; + + usage_usec =3D cg_read_key_long(cgcpu, "cpu.stat", "usage_usec"); + user_usec =3D cg_read_key_long(cgcpu, "cpu.stat", "user_usec"); + system_usec =3D cg_read_key_long(cgcpu, "cpu.stat", "system_usec"); + if (usage_usec !=3D 0 || user_usec !=3D 0 || system_usec !=3D 0) + goto cleanup; + + struct cpu_hog_func_param param =3D { + .nprocs =3D 1, + .runtime_nsec =3D usage_seconds * NSEC_PER_SEC, + }; + if (cg_run(cgcpu, hog_cpus_timed, (void *)¶m)) + goto cleanup; + + usage_usec =3D cg_read_key_long(cgcpu, "cpu.stat", "usage_usec"); + user_usec =3D cg_read_key_long(cgcpu, "cpu.stat", "user_usec"); + if (user_usec <=3D 0) + goto cleanup; + + if (!values_close(usage_usec, expected_usage_usec, 1)) + goto cleanup; + + ret =3D KSFT_PASS; + +cleanup: + cg_destroy(cgcpu); + free(cgcpu); + + return ret; +} + #define T(x) { x, #x } struct cgcpu_test { int (*fn)(const char *root); const char *name; } tests[] =3D { T(test_cgcpu_subtree_control), + T(test_cgcpu_stats), }; #undef T =20 --=20 2.30.2 From nobody Mon May 11 00:50:37 2026 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4A3E5C433EF for ; Tue, 19 Apr 2022 21:33:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1358039AbiDSVgA (ORCPT ); Tue, 19 Apr 2022 17:36:00 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60722 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1358017AbiDSVfs (ORCPT ); Tue, 19 Apr 2022 17:35:48 -0400 Received: from mail-qk1-f176.google.com (mail-qk1-f176.google.com [209.85.222.176]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BBC831FA5B; Tue, 19 Apr 2022 14:33:04 -0700 (PDT) Received: by mail-qk1-f176.google.com with SMTP id e128so12448705qkd.7; Tue, 19 Apr 2022 14:33:04 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=/2A26v8BYg4XGR0A4AftHu79JJJI4dmWW5Xn/YADuRg=; b=fd5U0+LRU6i9zidW5nAcV0bXjO5g9Qokp+YrrwzIWpsCLGPLBn3UOuuiwz8iYm7dll W0QTzc/2HolE7BL55V1UGjWQyXyjdhuAR2u4LASq2dNY6LZsHnE5qmIs10AqxfN4enSI e2XOxqRdzU3Z2m0pOUTU6ew7gboCpKwQMENocVhx0OGrzdeTGj+OGVEIkN8u0TD4h5ab SkD8aqcivrJFMKpL9W3Av4cuZ14I7kCXVXtnUN6RtJT5jeJQ6Y8PMcUzRGaPqXoACYl9 HdT98WgcW1tSdCX+beRzL4mf0wxyEpeATrsGLheYOLe7xSgKIhi9/28hA20QPFKphSIk 5EJQ== X-Gm-Message-State: AOAM533xYdeVzu9goBp82YTqx590+dXhB5oXUlsByQ4+y5y6ajn95sxU Mob83Q75Z/XMyevk1nCcEGJdPyYi7jE= X-Google-Smtp-Source: ABdhPJyrLxnX3OXIU3UQh5W6o2MTspMjiZzHAFalEiCdFAFMfrc6KuMTVpF5wfh62qvSHgvecbcUlQ== X-Received: by 2002:a05:620a:198f:b0:69e:953c:1843 with SMTP id bm15-20020a05620a198f00b0069e953c1843mr7654783qkb.209.1650403983770; Tue, 19 Apr 2022 14:33:03 -0700 (PDT) Received: from localhost (fwdproxy-ash-000.fbsv.net. [2a03:2880:20ff::face:b00c]) by smtp.gmail.com with ESMTPSA id q27-20020a05620a039b00b0069c8307d9c4sm578265qkm.18.2022.04.19.14.33.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 Apr 2022 14:33:03 -0700 (PDT) From: David Vernet To: tj@kernel.org, lizefan.x@bytedance.com, hannes@cmpxchg.org, cgroups@vger.kernel.org, linux-kernel@vger.kernel.org Cc: kernel-team@fb.com Subject: [PATCH 3/4] cgroup: Add test_cgcpu_weight_overprovisioned() testcase Date: Tue, 19 Apr 2022 14:32:44 -0700 Message-Id: <20220419213244.2104972-4-void@manifault.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220419213244.2104972-1-void@manifault.com> References: <20220419213244.2104972-1-void@manifault.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" test_cpu.c includes testcases that validate the cgroup cpu controller. This patch adds a new testcase called test_cgcpu_weight_overprovisioned() that verifies the expected behavior of creating multiple processes with different cpu.weight, on a system that is overprovisioned. So as to avoid code duplication, this patch also updates cpu_hog_func_param to take a new hog_clock_type enum which informs how time is counted in hog_cpus_timed() (either process time or wall clock time). Signed-off-by: David Vernet --- tools/testing/selftests/cgroup/cgroup_util.c | 12 ++ tools/testing/selftests/cgroup/cgroup_util.h | 1 + tools/testing/selftests/cgroup/test_cpu.c | 138 ++++++++++++++++++- 3 files changed, 144 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/cgroup/cgroup_util.c b/tools/testing/s= elftests/cgroup/cgroup_util.c index 0cf7e90c0052..b690fdc8b4cd 100644 --- a/tools/testing/selftests/cgroup/cgroup_util.c +++ b/tools/testing/selftests/cgroup/cgroup_util.c @@ -190,6 +190,18 @@ int cg_write(const char *cgroup, const char *control, = char *buf) return -1; } =20 +int cg_write_numeric(const char *cgroup, const char *control, long value) +{ + char buf[64]; + int ret; + + ret =3D sprintf(buf, "%lu", value); + if (ret < 0) + return ret; + + return cg_write(cgroup, control, buf); +} + int cg_find_unified_root(char *root, size_t len) { char buf[10 * PAGE_SIZE]; diff --git a/tools/testing/selftests/cgroup/cgroup_util.h b/tools/testing/s= elftests/cgroup/cgroup_util.h index 1df13dc8b8aa..0f79156697cf 100644 --- a/tools/testing/selftests/cgroup/cgroup_util.h +++ b/tools/testing/selftests/cgroup/cgroup_util.h @@ -35,6 +35,7 @@ extern long cg_read_long(const char *cgroup, const char *= control); long cg_read_key_long(const char *cgroup, const char *control, const char = *key); extern long cg_read_lc(const char *cgroup, const char *control); extern int cg_write(const char *cgroup, const char *control, char *buf); +int cg_write_numeric(const char *cgroup, const char *control, long value); extern int cg_run(const char *cgroup, int (*fn)(const char *cgroup, void *arg), void *arg); diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/self= tests/cgroup/test_cpu.c index 57f6308b1ef4..2afac9f9e1e2 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -2,6 +2,8 @@ =20 #define _GNU_SOURCE #include +#include +#include #include #include #include @@ -10,9 +12,17 @@ #include "../kselftest.h" #include "cgroup_util.h" =20 +enum hog_clock_type { + // Count elapsed time using the CLOCK_PROCESS_CPUTIME_ID clock. + CPU_HOG_CLOCK_PROCESS, + // Count elapsed time using system wallclock time. + CPU_HOG_CLOCK_WALL, +}; + struct cpu_hog_func_param { int nprocs; long runtime_nsec; + enum hog_clock_type clock_type; }; =20 /* @@ -90,8 +100,14 @@ static int hog_cpus_timed(const char *cgroup, void *arg) { const struct cpu_hog_func_param *param =3D (struct cpu_hog_func_param *)arg; + long start_time; long nsecs_remaining =3D param->runtime_nsec; int i, ret; + struct timespec ts; + + ret =3D clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret !=3D 0) + return ret; =20 for (i =3D 0; i < param->nprocs; i++) { pthread_t tid; @@ -101,22 +117,29 @@ static int hog_cpus_timed(const char *cgroup, void *a= rg) return ret; } =20 + start_time =3D ts.tv_nsec + ts.tv_sec * NSEC_PER_SEC; while (nsecs_remaining > 0) { - long nsecs_so_far; - struct timespec ts =3D { - .tv_sec =3D nsecs_remaining / NSEC_PER_SEC, - .tv_nsec =3D nsecs_remaining % NSEC_PER_SEC, - }; + long nsecs_so_far, baseline; + clockid_t clock_id; =20 + ts.tv_sec =3D nsecs_remaining / NSEC_PER_SEC; + ts.tv_nsec =3D nsecs_remaining % NSEC_PER_SEC; ret =3D nanosleep(&ts, NULL); if (ret && errno !=3D EINTR) return ret; =20 - ret =3D clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); + if (param->clock_type =3D=3D CPU_HOG_CLOCK_PROCESS) { + clock_id =3D CLOCK_PROCESS_CPUTIME_ID; + baseline =3D 0; + } else { + clock_id =3D CLOCK_MONOTONIC; + baseline =3D start_time; + } + ret =3D clock_gettime(clock_id, &ts); if (ret !=3D 0) return ret; =20 - nsecs_so_far =3D ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; + nsecs_so_far =3D ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec - baseline; nsecs_remaining =3D nsecs_so_far > param->runtime_nsec ? 0 : param->runtime_nsec - nsecs_so_far; @@ -153,6 +176,7 @@ static int test_cgcpu_stats(const char *root) struct cpu_hog_func_param param =3D { .nprocs =3D 1, .runtime_nsec =3D usage_seconds * NSEC_PER_SEC, + .clock_type =3D CPU_HOG_CLOCK_PROCESS, }; if (cg_run(cgcpu, hog_cpus_timed, (void *)¶m)) goto cleanup; @@ -174,6 +198,105 @@ static int test_cgcpu_stats(const char *root) return ret; } =20 +/* + * First, this test creates the following hierarchy: + * A + * A/B cpu.weight =3D 50 + * A/C cpu.weight =3D 100 + * A/D cpu.weight =3D 150 + * + * A separate process is then created for each child cgroup which spawns as + * many threads as there are cores, and hogs each CPU as much as possible + * for some time interval. + * + * Once all of the children have exited, we verify that each child cgroup + * was given proportional runtime as informed by their cpu.weight. + */ +static int test_cgcpu_weight_overprovisioned(const char *root) +{ + struct child { + char *cgroup; + pid_t pid; + long usage; + }; + int ret =3D KSFT_FAIL, i; + char *parent =3D NULL; + struct child children[3] =3D {NULL}; + long usage_seconds =3D 10; + + parent =3D cg_name(root, "cgcpu_test_0"); + if (!parent) + goto cleanup; + + if (cg_create(parent)) + goto cleanup; + + if (cg_write(parent, "cgroup.subtree_control", "+cpu")) + goto cleanup; + + for (i =3D 0; i < ARRAY_SIZE(children); i++) { + children[i].cgroup =3D cg_name_indexed(parent, "cgcpu_child", i); + if (!children[i].cgroup) + goto cleanup; + + if (cg_create(children[i].cgroup)) + goto cleanup; + + if (cg_write_numeric(children[i].cgroup, "cpu.weight", + 50 * (i + 1))) + goto cleanup; + } + + for (i =3D 0; i < ARRAY_SIZE(children); i++) { + struct cpu_hog_func_param param =3D { + .nprocs =3D get_nprocs(), + .runtime_nsec =3D usage_seconds * NSEC_PER_SEC, + .clock_type =3D CPU_HOG_CLOCK_WALL, + }; + pid_t pid =3D cg_run_nowait(children[i].cgroup, hog_cpus_timed, + (void *)¶m); + if (pid <=3D 0) + goto cleanup; + children[i].pid =3D pid; + } + + for (i =3D 0; i < ARRAY_SIZE(children); i++) { + int retcode; + + waitpid(children[i].pid, &retcode, 0); + if (!WIFEXITED(retcode)) + goto cleanup; + if (WEXITSTATUS(retcode)) + goto cleanup; + } + + for (i =3D 0; i < ARRAY_SIZE(children); i++) + children[i].usage =3D cg_read_key_long(children[i].cgroup, + "cpu.stat", "usage_usec"); + + for (i =3D 0; i < ARRAY_SIZE(children) - 1; i++) { + long delta; + + if (children[i + 1].usage <=3D children[i].usage) + goto cleanup; + + delta =3D children[i + 1].usage - children[i].usage; + if (!values_close(delta, children[0].usage, 35)) + goto cleanup; + } + + ret =3D KSFT_PASS; +cleanup: + for (i =3D 0; i < ARRAY_SIZE(children); i++) { + cg_destroy(children[i].cgroup); + free(children[i].cgroup); + } + cg_destroy(parent); + free(parent); + + return ret; +} + #define T(x) { x, #x } struct cgcpu_test { int (*fn)(const char *root); @@ -181,6 +304,7 @@ struct cgcpu_test { } tests[] =3D { T(test_cgcpu_subtree_control), T(test_cgcpu_stats), + T(test_cgcpu_weight_overprovisioned), }; #undef T =20 --=20 2.30.2 From nobody Mon May 11 00:50:37 2026 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1D803C433EF for ; Tue, 19 Apr 2022 21:33:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1358026AbiDSVgD (ORCPT ); Tue, 19 Apr 2022 17:36:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:60836 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1358024AbiDSVfz (ORCPT ); Tue, 19 Apr 2022 17:35:55 -0400 Received: from mail-qt1-f173.google.com (mail-qt1-f173.google.com [209.85.160.173]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 55B7037023; Tue, 19 Apr 2022 14:33:06 -0700 (PDT) Received: by mail-qt1-f173.google.com with SMTP id f22so4061948qtp.13; Tue, 19 Apr 2022 14:33:06 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=d/Ucq9GPy/+6d7dpHjxvay/RmHJPJU/gWJXLA2RvJP4=; b=f3R6/pepxyNxbeQJmifygfFFshnz/ZL8HqiMWcfrI4eJOAMjEbJHMQqLf/6zLAnqsg b2gtrTgvmZ/JBgv36nIy6A9qqR+jvSqT1RZ0bRK3MRtlqPdkHF65Bn7LT3PfrZsWo1UW 22JtaKAHVtonbwht2IbWZrRw+BojAOrr9/e98ECNOddWwhNQVM+DLa3MKCVz5/riP7cj kwV3KVCeDav06zy+dT/IAdyOxWdKALTfi1n4KvLZRybVXRFT39QE91AlQlUQRkADKh6s qtLit6Zbd7u0LTx+vAfeq2bxoUoRGyqbmmIpSPucPCHkgcdp2sItNWKp0oPkkcyA7EM+ IIHA== X-Gm-Message-State: AOAM532uGEL0ZGcmQosA2kojZqOkFTo7cUVvVjfqghLTlNIyunpVM4Ty QURgEHdU+Sf7ZJWJQjBEhYw= X-Google-Smtp-Source: ABdhPJyviHhLP1ZxOY8SzwMXUDlMghJ5K2SboMxk2Hjkom+7xsrW91sCo9uwMIzz4TeiwF7LkZBZoQ== X-Received: by 2002:ac8:1191:0:b0:2ed:bb6:ab07 with SMTP id d17-20020ac81191000000b002ed0bb6ab07mr12064899qtj.418.1650403985357; Tue, 19 Apr 2022 14:33:05 -0700 (PDT) Received: from localhost (fwdproxy-ash-022.fbsv.net. [2a03:2880:20ff:16::face:b00c]) by smtp.gmail.com with ESMTPSA id b22-20020a05620a0f9600b0069e8ab5034dsm558131qkn.1.2022.04.19.14.33.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 Apr 2022 14:33:05 -0700 (PDT) From: David Vernet To: tj@kernel.org, lizefan.x@bytedance.com, hannes@cmpxchg.org, cgroups@vger.kernel.org, linux-kernel@vger.kernel.org Cc: kernel-team@fb.com Subject: [PATCH 4/4] cgroup: Add test_cgcpu_weight_underprovisioned() testcase Date: Tue, 19 Apr 2022 14:32:45 -0700 Message-Id: <20220419213244.2104972-5-void@manifault.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220419213244.2104972-1-void@manifault.com> References: <20220419213244.2104972-1-void@manifault.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" test_cpu.c includes testcases that validate the cgroup cpu controller. This patch adds a new testcase called test_cgcpu_weight_underprovisioned() that verifies that processes with different cpu.weight that are all running on an underprovisioned system, still get roughly the same amount of cpu time. Because test_cgcpu_weight_underprovisioned() is very similar to test_cgcpu_weight_overprovisioned(), this patch also pulls the common logic into a separate helper function that is invoked from both testcases, and which uses function pointers to invoke the unique portions of the testcases. Signed-off-by: David Vernet --- tools/testing/selftests/cgroup/test_cpu.c | 149 +++++++++++++++++----- 1 file changed, 114 insertions(+), 35 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/self= tests/cgroup/test_cpu.c index 2afac9f9e1e2..7adeadba88c4 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -19,6 +19,12 @@ enum hog_clock_type { CPU_HOG_CLOCK_WALL, }; =20 +struct cpu_hogger { + char *cgroup; + pid_t pid; + long usage; +}; + struct cpu_hog_func_param { int nprocs; long runtime_nsec; @@ -198,31 +204,15 @@ static int test_cgcpu_stats(const char *root) return ret; } =20 -/* - * First, this test creates the following hierarchy: - * A - * A/B cpu.weight =3D 50 - * A/C cpu.weight =3D 100 - * A/D cpu.weight =3D 150 - * - * A separate process is then created for each child cgroup which spawns as - * many threads as there are cores, and hogs each CPU as much as possible - * for some time interval. - * - * Once all of the children have exited, we verify that each child cgroup - * was given proportional runtime as informed by their cpu.weight. - */ -static int test_cgcpu_weight_overprovisioned(const char *root) +static int +run_cgcpu_weight_test( + const char *root, + pid_t (*spawn_child)(const struct cpu_hogger *child), + int (*validate)(const struct cpu_hogger *children, int num_children)) { - struct child { - char *cgroup; - pid_t pid; - long usage; - }; int ret =3D KSFT_FAIL, i; char *parent =3D NULL; - struct child children[3] =3D {NULL}; - long usage_seconds =3D 10; + struct cpu_hogger children[3] =3D {NULL}; =20 parent =3D cg_name(root, "cgcpu_test_0"); if (!parent) @@ -248,13 +238,7 @@ static int test_cgcpu_weight_overprovisioned(const cha= r *root) } =20 for (i =3D 0; i < ARRAY_SIZE(children); i++) { - struct cpu_hog_func_param param =3D { - .nprocs =3D get_nprocs(), - .runtime_nsec =3D usage_seconds * NSEC_PER_SEC, - .clock_type =3D CPU_HOG_CLOCK_WALL, - }; - pid_t pid =3D cg_run_nowait(children[i].cgroup, hog_cpus_timed, - (void *)¶m); + pid_t pid =3D spawn_child(&children[i]); if (pid <=3D 0) goto cleanup; children[i].pid =3D pid; @@ -274,7 +258,43 @@ static int test_cgcpu_weight_overprovisioned(const cha= r *root) children[i].usage =3D cg_read_key_long(children[i].cgroup, "cpu.stat", "usage_usec"); =20 - for (i =3D 0; i < ARRAY_SIZE(children) - 1; i++) { + if (validate(children, ARRAY_SIZE(children))) + goto cleanup; + + ret =3D KSFT_PASS; +cleanup: + for (i =3D 0; i < ARRAY_SIZE(children); i++) { + cg_destroy(children[i].cgroup); + free(children[i].cgroup); + } + cg_destroy(parent); + free(parent); + + return ret; +} + +static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus) +{ + long usage_seconds =3D 10; + struct cpu_hog_func_param param =3D { + .nprocs =3D ncpus, + .runtime_nsec =3D usage_seconds * NSEC_PER_SEC, + .clock_type =3D CPU_HOG_CLOCK_WALL, + }; + return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)¶m); +} + +static pid_t weight_hog_all_cpus(const struct cpu_hogger *child) +{ + return weight_hog_ncpus(child, get_nprocs()); +} + +static int +overprovision_validate(const struct cpu_hogger *children, int num_children) +{ + int ret =3D KSFT_FAIL, i; + + for (i =3D 0; i < num_children - 1; i++) { long delta; =20 if (children[i + 1].usage <=3D children[i].usage) @@ -287,16 +307,74 @@ static int test_cgcpu_weight_overprovisioned(const ch= ar *root) =20 ret =3D KSFT_PASS; cleanup: - for (i =3D 0; i < ARRAY_SIZE(children); i++) { - cg_destroy(children[i].cgroup); - free(children[i].cgroup); + return ret; +} + +/* + * First, this test creates the following hierarchy: + * A + * A/B cpu.weight =3D 50 + * A/C cpu.weight =3D 100 + * A/D cpu.weight =3D 150 + * + * A separate process is then created for each child cgroup which spawns as + * many threads as there are cores, and hogs each CPU as much as possible + * for some time interval. + * + * Once all of the children have exited, we verify that each child cgroup + * was given proportional runtime as informed by their cpu.weight. + */ +static int test_cgcpu_weight_overprovisioned(const char *root) +{ + return run_cgcpu_weight_test(root, weight_hog_all_cpus, + overprovision_validate); +} + +static pid_t weight_hog_one_cpu(const struct cpu_hogger *child) +{ + return weight_hog_ncpus(child, 1); +} + +static int +underprovision_validate(const struct cpu_hogger *children, int num_childre= n) +{ + int ret =3D KSFT_FAIL, i; + + for (i =3D 0; i < num_children - 1; i++) { + if (!values_close(children[i + 1].usage, children[0].usage, 15)) + goto cleanup; } - cg_destroy(parent); - free(parent); =20 + ret =3D KSFT_PASS; +cleanup: return ret; } =20 +/* + * First, this test creates the following hierarchy: + * A + * A/B cpu.weight =3D 50 + * A/C cpu.weight =3D 100 + * A/D cpu.weight =3D 150 + * + * A separate process is then created for each child cgroup which spawns a + * single thread that hogs a CPU. The testcase is only run on systems that + * have at least one core per-thread in the child processes. + * + * Once all of the children have exited, we verify that each child cgroup + * had roughly the same runtime despite having different cpu.weight. + */ +static int test_cgcpu_weight_underprovisioned(const char *root) +{ + // Only run the test if there are enough cores to avoid overprovisioning + // the system. + if (get_nprocs() < 4) + return KSFT_SKIP; + + return run_cgcpu_weight_test(root, weight_hog_one_cpu, + underprovision_validate); +} + #define T(x) { x, #x } struct cgcpu_test { int (*fn)(const char *root); @@ -305,6 +383,7 @@ struct cgcpu_test { T(test_cgcpu_subtree_control), T(test_cgcpu_stats), T(test_cgcpu_weight_overprovisioned), + T(test_cgcpu_weight_underprovisioned), }; #undef T =20 --=20 2.30.2