From nobody Mon Oct 6 17:06:29 2025 Received: from mail-pj1-f74.google.com (mail-pj1-f74.google.com [209.85.216.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 54CC122E3E9 for ; Sat, 19 Jul 2025 03:05:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752894358; cv=none; b=i296estii6LYhm05tCYy/W0QpxZ8lEDlaaTq2h4PwaWde2sG7mQDNl6Y0Gam4eeWyLrDr+A5yxwZLNYUH4c/af7QsGdEMiwKiDuRj26FNTDRRQD/dpFmD05ls/q47/Vrts0+XeTseSqUoOx2f8pbDkIdxUhclh1w8SeE8f862nM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752894358; c=relaxed/simple; bh=KH6S1EVXcp2k9GpaDuxbo9DEpDyEC5StJ9+FpyEM40M=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=Oa1wQMIdVh0ffbop/fEdpsojTV6yIrCSqp2ZL/6SxAnKS/Z4l6cHudKlbo23JZwZVMwoPAZ9YhgJAgLnOm8cDGCWP6v/cGlj0gN1mesjmCHSTV6zduJtx6/3z39KyEam+JlDqERj8DLGZDo60S/LFQ1KWuH9dZoL2FeRrf2y85s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=1KDpJz4d; arc=none smtp.client-ip=209.85.216.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="1KDpJz4d" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-31332dc2b59so2297100a91.0 for ; Fri, 18 Jul 2025 20:05:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1752894356; x=1753499156; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=EEQY36OrLpdtDDbFz4O1+R1L/HhHBHEcsXBUC9UAOjs=; b=1KDpJz4dk33s5+qeUT8pyPEAPGMJ4Ek+dTnKvK3BUgR9tEjSyguxViTRvCjabQsbCy AP1V/brQAcfTlcSeXcXhW1jOXUg/voacGjZ5sGss+/QRZicVib8dc3TUfG3IYI8JDItZ IkVwex4WbR86U97AfOanqkY4vP8BEiyEYnGd1vprYvl47MFhpf4crKONyNnHyIaSQ4ir 49N3pnad177ED0bvC25iPLNQj8R8bwOhDS1/Td9DvxDeeKTJyzOMzWzZRgJkk7rJBLVh Ci9DI91eq2p6UU1fLUcXnXky7l6WkdQRHFKMcXIDJqcZGhRwaGVOtCljPpyALHaxmf3q VHxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752894356; x=1753499156; h=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=EEQY36OrLpdtDDbFz4O1+R1L/HhHBHEcsXBUC9UAOjs=; b=t7YLjFNXiai+0AyxEqNMG0LvSGsnGapkNlBGioHnUNzQ2LJsXV2PK8UgxGduUzGPb8 zcUNwYU6rbsnqqbLys1bnfP76MooS2tCeROiTRZgRfq0Vvmxk4rADpeKB2yCdbaB+zQn oNNUQ9M67WQyT+fxHlRfA7h1GcBoSfPCXD24hxpSRmlzWvTRHyiogOkMTalTWDQGueRM R1M0mR2PZhi4P9rBebwFPTB6J3o9ZHkd3x7TIWVorsFZxzCMnJ/JYYwtKxa2LmRMlcUy VzrUWqB46R7aj1xeksRT8S/sLjFEB6s6KjPLtPi2riEYlGW57RkkRl5dEJULZUXrimle Prew== X-Forwarded-Encrypted: i=1; AJvYcCWXEMJZ5gjI+H0hEtUxD6450nlPaN6ohs9mYfC8PIEGa7w4PvLIsUVHz0rLovnhXaqjXSV3OchuLZILuv8=@vger.kernel.org X-Gm-Message-State: AOJu0YzAdhI08EaW8f7ge1RAiAMO0/Uf7ROvf5X4qD00I4K9d+XrrgBR OmGSJ4jLKncsMztceKVk5ynnCAF6ntpplHe1l6R9wo5ptgOuno4BLRohjwS314EZL1qp53Vywej J5UalFiUMJQ== X-Google-Smtp-Source: AGHT+IEjgLpESqb9x5G3AgQikzLLP8Ql1eepMkTRr1oFLDd9jCuc/7CRagtVBm1fNTsvWHcdziXRpggSRHAs X-Received: from pjbsi4.prod.google.com ([2002:a17:90b:5284:b0:311:4aa8:2179]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:3d8c:b0:316:3972:b9d0 with SMTP id 98e67ed59e1d1-31c9e5fcacfmr20143537a91.0.1752894355693; Fri, 18 Jul 2025 20:05:55 -0700 (PDT) Date: Fri, 18 Jul 2025 20:05:14 -0700 In-Reply-To: <20250719030517.1990983-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250719030517.1990983-1-irogers@google.com> X-Mailer: git-send-email 2.50.0.727.gbf7dc18ff4-goog Message-ID: <20250719030517.1990983-13-irogers@google.com> Subject: [PATCH v3 12/15] perf parse-events: Support user CPUs mixed with threads/processes From: Ian Rogers To: Thomas Falcon , Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , "Liang, Kan" , Ravi Bangoria , James Clark , Dapeng Mi , Weilin Wang , Andi Kleen , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Counting events system-wide with a specified CPU prior to this change worked: ``` $ perf stat -e 'msr/tsc/,msr/tsc,cpu=3Dcpu_core/,msr/tsc,cpu=3Dcpu_atom/' -= a sleep 1 Performance counter stats for 'system wide': 59,393,419,099 msr/tsc/ 33,927,965,927 msr/tsc,cpu=3Dcpu_core/ 25,465,608,044 msr/tsc,cpu=3Dcpu_atom/ ``` However, when counting with process the counts became system wide: ``` $ perf stat -e 'msr/tsc/,msr/tsc,cpu=3Dcpu_core/,msr/tsc,cpu=3Dcpu_atom/' p= erf test -F 10 10.1: Basic parsing test : Ok 10.2: Parsing without PMU name : Ok 10.3: Parsing with PMU name : Ok Performance counter stats for 'perf test -F 10': 59,233,549 msr/tsc/ 59,227,556 msr/tsc,cpu=3Dcpu_core/ 59,224,053 msr/tsc,cpu=3Dcpu_atom/ ``` Make the handling of CPU maps with event parsing clearer. When an event is parsed creating an evsel the cpus should be either the PMU's cpumask or user specified CPUs. Update perf_evlist__propagate_maps so that it doesn't clobber the user specified CPUs. Try to make the behavior clearer, firstly fix up missing cpumasks. Next, perform sanity checks and adjustments from the global evlist CPU requests and for the PMU including simplifying to the "any CPU"(-1) value. Finally remove the event if the cpumask is empty. So that events are opened with a CPU and a thread change stat's create_perf_stat_counter to give both. With the change things are fixed: ``` $ perf stat --no-scale -e 'msr/tsc/,msr/tsc,cpu=3Dcpu_core/,msr/tsc,cpu=3Dc= pu_atom/' perf test -F 10 10.1: Basic parsing test : Ok 10.2: Parsing without PMU name : Ok 10.3: Parsing with PMU name : Ok Performance counter stats for 'perf test -F 10': 63,704,975 msr/tsc/ 47,060,704 msr/tsc,cpu=3Dcpu_core/ (4.6= 2%) 16,640,591 msr/tsc,cpu=3Dcpu_atom/ (2.1= 8%) ``` However, note the "--no-scale" option is used. This is necessary as the running time for the event on the counter isn't the same as the enabled time because the thread doesn't necessarily run on the CPUs specified for the counter. All counter values are scaled with: scaled_value =3D value * time_enabled / time_running and so without --no-scale the scaled_value becomes very large. This problem already exists on hybrid systems for the same reason. Here are 2 runs of the same code with an instructions event that counts the same on both types of core, there is no real multiplexing happening on the event: ``` $ perf stat -e instructions perf test -F 10 ... Performance counter stats for 'perf test -F 10': 87,896,447 cpu_atom/instructions/ (14.37= %) 98,171,964 cpu_core/instructions/ (85.63= %) ... $ perf stat --no-scale -e instructions perf test -F 10 ... Performance counter stats for 'perf test -F 10': 13,069,890 cpu_atom/instructions/ (19.32= %) 83,460,274 cpu_core/instructions/ (80.68= %) ... ``` The scaling has inflated per-PMU instruction counts and the overall count by 2x. To fix this the kernel needs changing when a task+CPU event (or just task event on hybrid) is scheduled out. A fix could be that the state isn't inactive but off for such events, so that time_enabled counts don't accumulate on them. Reviewed-by: Thomas Falcon Signed-off-by: Ian Rogers --- tools/lib/perf/evlist.c | 119 ++++++++++++++++++++++----------- tools/perf/util/parse-events.c | 10 ++- tools/perf/util/stat.c | 6 +- 3 files changed, 87 insertions(+), 48 deletions(-) diff --git a/tools/lib/perf/evlist.c b/tools/lib/perf/evlist.c index 9d9dec21f510..3ed023f4b190 100644 --- a/tools/lib/perf/evlist.c +++ b/tools/lib/perf/evlist.c @@ -36,49 +36,88 @@ void perf_evlist__init(struct perf_evlist *evlist) static void __perf_evlist__propagate_maps(struct perf_evlist *evlist, struct perf_evsel *evsel) { - if (evsel->system_wide) { - /* System wide: set the cpu map of the evsel to all online CPUs. */ - perf_cpu_map__put(evsel->cpus); - evsel->cpus =3D perf_cpu_map__new_online_cpus(); - } else if (evlist->has_user_cpus && evsel->is_pmu_core) { - /* - * User requested CPUs on a core PMU, ensure the requested CPUs - * are valid by intersecting with those of the PMU. - */ + if (perf_cpu_map__is_empty(evsel->cpus)) { + if (perf_cpu_map__is_empty(evsel->pmu_cpus)) { + /* + * Assume the unset PMU cpus were for a system-wide + * event, like a software or tracepoint. + */ + evsel->pmu_cpus =3D perf_cpu_map__new_online_cpus(); + } + if (evlist->has_user_cpus && !evsel->system_wide) { + /* + * Use the user CPUs unless the evsel is set to be + * system wide, such as the dummy event. + */ + evsel->cpus =3D perf_cpu_map__get(evlist->user_requested_cpus); + } else { + /* + * System wide and other modes, assume the cpu map + * should be set to all PMU CPUs. + */ + evsel->cpus =3D perf_cpu_map__get(evsel->pmu_cpus); + } + } + /* + * Avoid "any CPU"(-1) for uncore and PMUs that require a CPU, even if + * requested. + */ + if (evsel->requires_cpu && perf_cpu_map__has_any_cpu(evsel->cpus)) { perf_cpu_map__put(evsel->cpus); - evsel->cpus =3D perf_cpu_map__intersect(evlist->user_requested_cpus, evs= el->pmu_cpus); + evsel->cpus =3D perf_cpu_map__get(evsel->pmu_cpus); + } =20 - /* - * Empty cpu lists would eventually get opened as "any" so remove - * genuinely empty ones before they're opened in the wrong place. - */ - if (perf_cpu_map__is_empty(evsel->cpus)) { - struct perf_evsel *next =3D perf_evlist__next(evlist, evsel); - - perf_evlist__remove(evlist, evsel); - /* Keep idx contiguous */ - if (next) - list_for_each_entry_from(next, &evlist->entries, node) - next->idx--; + /* + * Globally requested CPUs replace user requested unless the evsel is + * set to be system wide. + */ + if (evlist->has_user_cpus && !evsel->system_wide) { + assert(!perf_cpu_map__has_any_cpu(evlist->user_requested_cpus)); + if (!perf_cpu_map__equal(evsel->cpus, evlist->user_requested_cpus)) { + perf_cpu_map__put(evsel->cpus); + evsel->cpus =3D perf_cpu_map__get(evlist->user_requested_cpus); } - } else if (!evsel->pmu_cpus || evlist->has_user_cpus || - (!evsel->requires_cpu && perf_cpu_map__has_any_cpu(evlist->user_requeste= d_cpus))) { - /* - * The PMU didn't specify a default cpu map, this isn't a core - * event and the user requested CPUs or the evlist user - * requested CPUs have the "any CPU" (aka dummy) CPU value. In - * which case use the user requested CPUs rather than the PMU - * ones. - */ + } + + /* Ensure cpus only references valid PMU CPUs. */ + if (!perf_cpu_map__has_any_cpu(evsel->cpus) && + !perf_cpu_map__is_subset(evsel->pmu_cpus, evsel->cpus)) { + struct perf_cpu_map *tmp =3D perf_cpu_map__intersect(evsel->pmu_cpus, ev= sel->cpus); + perf_cpu_map__put(evsel->cpus); - evsel->cpus =3D perf_cpu_map__get(evlist->user_requested_cpus); - } else if (evsel->cpus !=3D evsel->pmu_cpus) { - /* - * No user requested cpu map but the PMU cpu map doesn't match - * the evsel's. Reset it back to the PMU cpu map. - */ + evsel->cpus =3D tmp; + } + + /* + * Was event requested on all the PMU's CPUs but the user requested is + * any CPU (-1)? If so switch to using any CPU (-1) to reduce the number + * of events. + */ + if (!evsel->system_wide && + !evsel->requires_cpu && + perf_cpu_map__equal(evsel->cpus, evsel->pmu_cpus) && + perf_cpu_map__has_any_cpu(evlist->user_requested_cpus)) { perf_cpu_map__put(evsel->cpus); - evsel->cpus =3D perf_cpu_map__get(evsel->pmu_cpus); + evsel->cpus =3D perf_cpu_map__get(evlist->user_requested_cpus); + } + + /* Sanity check assert before the evsel is potentially removed. */ + assert(!evsel->requires_cpu || !perf_cpu_map__has_any_cpu(evsel->cpus)); + + /* + * Empty cpu lists would eventually get opened as "any" so remove + * genuinely empty ones before they're opened in the wrong place. + */ + if (perf_cpu_map__is_empty(evsel->cpus)) { + struct perf_evsel *next =3D perf_evlist__next(evlist, evsel); + + perf_evlist__remove(evlist, evsel); + /* Keep idx contiguous */ + if (next) + list_for_each_entry_from(next, &evlist->entries, node) + next->idx--; + + return; } =20 if (evsel->system_wide) { @@ -98,6 +137,10 @@ static void perf_evlist__propagate_maps(struct perf_evl= ist *evlist) =20 evlist->needs_map_propagation =3D true; =20 + /* Clear the all_cpus set which will be merged into during propagation. */ + perf_cpu_map__put(evlist->all_cpus); + evlist->all_cpus =3D NULL; + list_for_each_entry_safe(evsel, n, &evlist->entries, node) __perf_evlist__propagate_maps(evlist, evsel); } diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index bd2d831d5123..fe2073c6b549 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -310,20 +310,18 @@ __add_event(struct list_head *list, int *idx, if (pmu) { is_pmu_core =3D pmu->is_core; pmu_cpus =3D perf_cpu_map__get(pmu->cpus); + if (perf_cpu_map__is_empty(pmu_cpus)) + pmu_cpus =3D cpu_map__online(); } else { is_pmu_core =3D (attr->type =3D=3D PERF_TYPE_HARDWARE || attr->type =3D=3D PERF_TYPE_HW_CACHE); pmu_cpus =3D is_pmu_core ? cpu_map__online() : NULL; } =20 - if (has_user_cpus) { + if (has_user_cpus) cpus =3D perf_cpu_map__get(user_cpus); - /* Existing behavior that pmu_cpus matches the given user ones. */ - perf_cpu_map__put(pmu_cpus); - pmu_cpus =3D perf_cpu_map__get(user_cpus); - } else { + else cpus =3D perf_cpu_map__get(pmu_cpus); - } =20 if (init_attr) event_attr_init(attr); diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c index b0205e99a4c9..50b1a92d16df 100644 --- a/tools/perf/util/stat.c +++ b/tools/perf/util/stat.c @@ -769,8 +769,6 @@ int create_perf_stat_counter(struct evsel *evsel, attr->enable_on_exec =3D 1; } =20 - if (target__has_cpu(target) && !target__has_per_thread(target)) - return evsel__open_per_cpu(evsel, evsel__cpus(evsel), cpu_map_idx); - - return evsel__open_per_thread(evsel, evsel->core.threads); + return evsel__open_per_cpu_and_thread(evsel, evsel__cpus(evsel), cpu_map_= idx, + evsel->core.threads); } --=20 2.50.0.727.gbf7dc18ff4-goog