Make NOHZ cpulist checks more uniform Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
diff --git a/check_configs_lib.py b/check_configs.py similarity index 95% rename from check_configs_lib.py rename to check_configs.py index 32b8f25..124f289 100644 --- a/check_configs_lib.py +++ b/check_configs.py
@@ -3,7 +3,8 @@ import os, re, sys, gzip from collections import defaultdict from dynticks_testing_lib import (parse_cpulist, get_cmdline_param, read_file, - read_int, read_cpulist, safe_parse_cpulist) + read_int, read_cpulist, safe_parse_cpulist, + get_nohz_full_cpus) from log_output import Log # max number of items (IRQ vectors, workqueues, ...) listed in a single message @@ -138,10 +139,6 @@ return _kernel_config -def check_nohz_full_mask(cpulist): - Log.header("NOHZ_FULL / ISOLATION MASK") - - REQUIRED_CONFIGS = [ ("CONFIG_NO_HZ_FULL", "full dynticks support"), ("CONFIG_HIGH_RES_TIMERS", "high resolution timers (needed by nohz_full)"), @@ -868,13 +865,27 @@ else: Log.print(Log.Status.OK, "ISOLCPUS_MATCH", "nohz_full is part of isolcpus. Ok!") +# nohz_full= and isolcpus=nohz,<list> are equivalent ways to request full +# dynticks: either is authoritative, nohz_full= takes precedence if both are set +def get_requested_nohz_full(): + requested = get_cmdline_param("nohz_full") + if requested is not None: + return expand_cpu_spec(requested) + + isolcpus = get_cmdline_param("isolcpus") + if isolcpus is None: + return None + + flags, cpus = split_isolcpus(isolcpus) + return cpus if "nohz" in flags else None + def check_isolcpus_cpuset(cpulist): Log.header("ISOLCPUS / CPUSET AND NOHZ_FULL") # the applied mask can differ from the requested one: CPU0 is always kept # as housekeeping and the mask is clamped to CONFIG_NR_CPUS - effective = read_cpulist("/sys/devices/system/cpu/nohz_full") - requested = expand_cpu_spec(get_cmdline_param("nohz_full")) + effective = get_nohz_full_cpus() + requested = get_requested_nohz_full() if not effective: Log.print(Log.Status.FAIL, "NOHZ_FULL_MASK", @@ -883,7 +894,8 @@ Log.print(Log.Status.INFO, "NOHZ_FULL_MASK", f"Effective nohz_full: {fmt_cpus(effective)}") if requested is None: - Log.print(Log.Status.WARN, "NOHZ_FULL_CMDLINE", "No nohz_full= found in /proc/cmdline") + Log.print(Log.Status.WARN, "NOHZ_FULL_CMDLINE", + "No nohz_full= or isolcpus=nohz found in /proc/cmdline") elif effective and sorted(requested) != sorted(effective): dropped = sorted(set(requested) - set(effective)) Log.print(Log.Status.FAIL, "NOHZ_FULL_MISMATCH", @@ -927,16 +939,15 @@ Log.print(Log.Status.WARN, "CGROUP_VERSION", "No cgroup hierarchy found under /sys/fs/cgroup") covered = set() - # the single partitions have been reported above: the nohz_full CPUs can be - # spread over several of them, so the verdict is on their union - if not covered: - Log.print(Log.Status.FAIL, "CPUSET_MISSING", - f"nohz_full specified {fmt_cpus(cpulist)} but no isolated partitions (isolcpus | cpusets) found!") - elif set(cpulist) - covered: - err_nohz_isolated(cpulist, sorted(covered)) - else: - Log.print(Log.Status.OK, "CPUSET_MATCH", - f"every nohz_full CPU ({fmt_cpus(cpulist)}) is in an isolated partition. Ok!") + if isolcpus is None: + if not covered: + Log.print(Log.Status.FAIL, "CPUSET_MISSING", + f"nohz_full specified {fmt_cpus(cpulist)} but no isolated partitions (isolcpus | cpusets) found!") + elif set(cpulist) - covered: + err_nohz_isolated(cpulist, sorted(covered)) + else: + Log.print(Log.Status.OK, "CPUSET_MATCH", + f"every nohz_full CPU ({fmt_cpus(cpulist)}) is in an isolated partition. Ok!") def check_configs(cpulist): if not cpulist: @@ -948,8 +959,9 @@ # one and SMT is expected to be *disabled* on a tuned nohz_full machine check_isolcpus_cpuset(cpulist) - Log.header("CPU TOPOLOGY / SMT") smt = is_smt_enabled() + if smt: + check_siblings_thread(cpulist) check_cpu_governor(cpulist) check_irq_affinity(cpulist) @@ -960,5 +972,4 @@ check_noise_knobs(cpulist) check_net_rps_xps(cpulist) - if smt: - check_siblings_thread(cpulist) + print(" ");
diff --git a/dynticks-testing.py b/dynticks-testing.py index d9a10e7..884ec93 100755 --- a/dynticks-testing.py +++ b/dynticks-testing.py
@@ -1,11 +1,10 @@ #!/usr/bin/python3 -from dynticks_testing_lib import (parse_cpulist, read_file, find_isolated_partition, - cgroup_attach, to_str_range) +from dynticks_testing_lib import (read_file, find_isolated_partition, + cgroup_attach, to_str_range, get_nohz_full_cpus) import os, sys, subprocess, time TRACING_PATH = "/sys/kernel/tracing" -NOHZ_FULL_PATH = "/sys/devices/system/cpu/nohz_full" # how long the user loops are left spinning while the trace is collected TRACE_DURATION_S = 10 @@ -43,19 +42,13 @@ except OSError: pass -# Parse nohz_full= -cpulist = read_file(NOHZ_FULL_PATH) -if cpulist is None: - print("Can't find %s" % NOHZ_FULL_PATH) - sys.exit(-1) - -if cpulist in ("(null)", ""): +# nohz_full CPUs, however they were requested (nohz_full= or isolcpus=nohz) +nohz_full = get_nohz_full_cpus() +if not nohz_full: print("nohz_full= not set ?") sys.exit(-1) -print("nohz_full=%s" % cpulist) - -nohz_full = parse_cpulist(cpulist) +print("nohz_full=%s" % to_str_range(nohz_full)) # Return the set of CPUs the calling thread of the current process is restricted to allowed_cpus = os.sched_getaffinity(0)
diff --git a/dynticks_testing_lib.py b/dynticks_testing_lib.py index 52d0b86..53c1131 100755 --- a/dynticks_testing_lib.py +++ b/dynticks_testing_lib.py
@@ -4,6 +4,7 @@ from itertools import groupby CGROUP_ROOT = "/sys/fs/cgroup" +NOHZ_FULL_PATH = "/sys/devices/system/cpu/nohz_full" def parse_cpulist(s): cpulist = [] @@ -65,6 +66,12 @@ return safe_parse_cpulist(value) or [] +# The single source of truth for "which CPUs are running full dynticks": the +# kernel populates this file the same way whether it was requested via +# nohz_full= or via the equivalent isolcpus=nohz,<list> flag. +def get_nohz_full_cpus(): + return read_cpulist(NOHZ_FULL_PATH) + # Return the cgroup v2 isolated partition owning 'cpu', or None def find_isolated_partition(cpu): for root, dirs, files in os.walk(CGROUP_ROOT):
diff --git a/noise_parse.py b/noise_parse.py index 9e2ce25..9a6dfe4 100755 --- a/noise_parse.py +++ b/noise_parse.py
@@ -3,8 +3,8 @@ import re, sys, statistics , os from optparse import OptionParser from collections import defaultdict -from dynticks_testing_lib import parse_cpulist, get_cmdline_param -from check_configs_lib import check_configs +from dynticks_testing_lib import parse_cpulist, get_nohz_full_cpus +from check_configs import check_configs class Noise: def __init__(self, ts): @@ -774,12 +774,11 @@ (options, trace_file) = parse_arguments() -# if --cpulist is not set, read nohz_full +# if --cpulist is not set, read the nohz_full CPUs from sysfs: this covers +# both nohz_full= and the equivalent isolcpus=nohz,<list> boot parameter if cpulist is None: - print("INFO: --cpulist param. not set: reading nohz_full from /proc/cmdline") - nohz_list = get_cmdline_param("nohz_full") - if nohz_list: - cpulist = parse_cpulist(nohz_list) + print("INFO: --cpulist param. not set: reading nohz_full from sysfs") + cpulist = get_nohz_full_cpus() # Check system configurations...