Skip to content

Commit ea0421c

Browse files
charles-typfacebook-github-bot
authored andcommitted
Report native CMN mesh (uncore) performance metrics (#605)
Summary: **Problem** The Neoverse V3 perf report exposed a single CMN memory metric ("CMN Memory Read Bandwidth") derived from `hns_mc_reqs_local_all`. That name was misleading (the event counts read+write) and, more importantly, the metric was incomplete: it ignored remote (cross-mesh) memory traffic and surfaced none of the other CMN mesh telemetry the hardware exposes. An earlier attempt to add DRAM read/write bandwidth via the external Arm cmn-tools repo (port-filtered crosspoint watchpoints, cloned at install time) produced unusable numbers on this platform -- write bandwidth reported as 0 and implausibly high mesh bandwidth -- and added network / `/dev/mem` / kernel-module fragility to the install path. **Why** Root cause of the cmn-tools garbage: on a typical deployment, system PMU-monitoring daemons hold many CMN perf_event handles continuously, starving the CMN DTC's small hardware counter budget. cmn-tools' watchpoint programming collides with the arm-cmn driver under that contention. Named perf events do not -- perf multiplexes and scales them, so steady-state values stay accurate. Separately, the HN-S MC-request event has no read/write split (confirmed against the Arm CMN event set), so a DRAM byte-level read/write split is not observable from HN-S counters at all. **Fix** Drop the cmn-tools apparatus entirely (the platform-tools install hook, mesh discovery and topdown-runner scripts, and the log-parsing path in the report generator). Instead report native CMN PMU metrics that correspond to the HN-S "effectiveness" event group in the Arm CMN Technical Reference Manual: - **CMN Memory Bandwidth (MBps)** -- read+write total, now summing local **and** remote MC requests across all mesh instances (each 64B request counted once at its home HN-S, so no double-counting). - **CMN Local / Remote Memory Bandwidth (MBps)** -- memory locality split. - **CMN Memory Read Mix %** -- read share via HN-S QoS PoCQ occupancy (the only read/write proxy the HN-S exposes). - **CMN MC Retry %** -- memory-controller backpressure (mc_retry). - **CMN Snoop Filter Hit Rate %** -- coherence-directory effectiveness (sf_hit_ratio). - **CMN Mesh Frequency (GHz)** -- from dtc_cycles, for DVFS-throttling visibility. The collect script keeps the CMN event group intentionally lean because of the counter contention described above. Differential Revision: D102244354
1 parent b4d5f7f commit ea0421c

2 files changed

Lines changed: 195 additions & 18 deletions

File tree

perfutils/collect_neoversev3_perf_counters.sh

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,38 @@ L2_CHI_BUSY='r198,r199,r19A,r19B,r19C'
128128
# sve_pred_full_spec (0x8076), sve_pred_partial_spec (0x8077)
129129
SVE_PRED='r8074,r8075,r8076,r8077'
130130

131-
### CMN-Cypress Uncore PMU Events (SLC / System Level Cache) ---------
132-
### Auto-discover arm_cmn_N devices (one per chiplet on NV3).
133-
### HN-S (Home Node with SLC) events provide true SLC miss rate,
134-
### which is not observable from the core PMU on Neoverse V3.
131+
### CMN Uncore PMU Events (mesh / SLC / memory) ---------
132+
### Auto-discover arm_cmn_N devices (one per mesh instance).
133+
### These correspond to the HN-S "effectiveness" event group described in the
134+
### Arm CMN Technical Reference Manual:
135+
### mc_requests -> hns_mc_reqs_{local,remote}_all (64B DRAM requests)
136+
### mc_retry -> hns_mc_retries_local
137+
### pocq_occupancy -> hns_qos_pocq_occupancy_{read,write}
138+
### slc/sf_hit -> hns_slc_sf_cache_access_all, hns_cache_miss_all,
139+
### hns_cache_fill_all, hns_sf_hit_all
140+
### plus dtc_cycles for the mesh clock frequency.
141+
###
142+
### NOTE ON MULTIPLEXING: the CMN DTC has a small number of hardware counters,
143+
### and system PMU-monitoring daemons may hold several of them continuously, so
144+
### this event set is kept intentionally lean. perf multiplexes and scales the
145+
### counts; keeping the group small minimizes scaling error. A read/write DRAM
146+
### *byte* split is not observable from HN-S events (it requires crosspoint
147+
### watchpoint programming, which collides with the arm-cmn driver), so memory
148+
### bandwidth is reported as a read+write total with a PoCQ-occupancy-based
149+
### read/write mix proxy.
135150
CMN_SLC_EVENTS=""
136151
for cmn_dev in $(find /sys/bus/event_source/devices/ -maxdepth 1 -name 'arm_cmn_*' -printf '%f\n' 2>/dev/null | sort); do
137152
CMN_SLC_EVENTS+="${cmn_dev}/hns_slc_sf_cache_access_all/,"
138153
CMN_SLC_EVENTS+="${cmn_dev}/hns_cache_miss_all/,"
139154
CMN_SLC_EVENTS+="${cmn_dev}/hns_cache_fill_all/,"
155+
CMN_SLC_EVENTS+="${cmn_dev}/hns_sf_hit_all/,"
140156
CMN_SLC_EVENTS+="${cmn_dev}/hns_mc_reqs_local_all/,"
157+
CMN_SLC_EVENTS+="${cmn_dev}/hns_mc_reqs_remote_all/,"
158+
CMN_SLC_EVENTS+="${cmn_dev}/hns_mc_retries_local/,"
141159
CMN_SLC_EVENTS+="${cmn_dev}/hns_pocq_reqs_recvd_all/,"
160+
CMN_SLC_EVENTS+="${cmn_dev}/hns_qos_pocq_occupancy_read/,"
161+
CMN_SLC_EVENTS+="${cmn_dev}/hns_qos_pocq_occupancy_write/,"
162+
CMN_SLC_EVENTS+="${cmn_dev}/dtc_cycles/,"
142163
done
143164
CMN_SLC_EVENTS="${CMN_SLC_EVENTS%,}"
144165

@@ -152,7 +173,7 @@ CPU_GROUP_MUX="${INSTRUCTIONS_RATE},${L1_DCACHE_MISSES},${L1_ICACHE_MISSES},${L2
152173

153174
PERF_PID=
154175
wrapup() {
155-
kill -INT "$PERF_PID"
176+
kill -INT "$PERF_PID" 2>/dev/null
156177
}
157178

158179
trap wrapup SIGINT SIGTERM
@@ -179,6 +200,7 @@ collect_counters() {
179200
if [[ -n "$CMN_SLC_EVENTS" ]]; then
180201
events+=" -e ${CMN_SLC_EVENTS}"
181202
fi
203+
182204
if [[ -n "$outfile" ]]; then
183205
perf_stat "$events" "$interval_ms" > "$outfile"
184206
else

perfutils/generate_arm_neoversev3_perf_report.py

Lines changed: 168 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#
44
# This source code is licensed under the MIT license found in the
55
# LICENSE file in the root directory of this source tree.
6-
# (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
76

87
"""
98
ARM Neoverse V3 performance report generator.
@@ -165,11 +164,11 @@ def _align(grouped_df, ev_a, ev_b):
165164

166165

167166
def _sum_cmn_event(grouped_df, event_suffix):
168-
"""Sum a CMN HN-S event across all chiplets (arm_cmn_0, arm_cmn_1, ...).
167+
"""Sum a CMN HN-S event across all mesh instances (arm_cmn_0, arm_cmn_1, ...).
169168
170-
CMN-Cypress exposes one arm_cmn_N PMU per chiplet. This helper aggregates
171-
a given event across all discovered chiplets so metrics reflect the full
172-
system-level cache.
169+
Arm CMN exposes one arm_cmn_N PMU per mesh instance (one per die on
170+
multi-die parts). This helper aggregates a given event across all
171+
discovered mesh instances so metrics reflect the full system-level cache.
173172
"""
174173
total = None
175174
for name, group in grouped_df:
@@ -788,28 +787,178 @@ def dispatch_stall_mcq(grouped_df):
788787

789788

790789
# ===========================================================================
791-
# SVE predication effectiveness (V3-specific)
790+
# CMN mesh (uncore) metrics
791+
#
792+
# Arm CMN exposes one arm_cmn_N PMU per mesh instance (one per die on
793+
# multi-die parts). The HN-S (Home Node with SLC) events below correspond to
794+
# the HN-S "effectiveness" event group described in the Arm CMN Technical
795+
# Reference Manual:
796+
# mc_requests -> hns_mc_reqs_{local,remote}_all
797+
# mc_retry -> hns_mc_retries_{local,remote}
798+
# pocq_occupancy-> hns_qos_pocq_occupancy_{read,write,all}
799+
# sf_hit_ratio -> hns_sf_hit_all / hns_slc_sf_cache_access_all
800+
#
801+
# Each hns_mc_reqs_* event counts a 64B cache-line request to the memory
802+
# controller and is counted exactly once at the request's home HN-S, so
803+
# summing local + remote across all mesh instances yields total DRAM traffic
804+
# with no double-counting. The event does NOT distinguish reads from writes at
805+
# the HN-S, which is why the memory bandwidth metric is reported as a read+write
806+
# total.
792807
# ===========================================================================
793808

794809

795810
@skip_if_missing
796-
def cmn_mem_read_bw_MBps(grouped_df):
797-
"""Memory read bandwidth from CMN MC request counters.
811+
def cmn_mem_bw_MBps(grouped_df):
812+
"""Total DRAM bandwidth (read+write) from CMN MC request counters.
798813
799-
Each hns_mc_reqs_local_all is a cache-line (64B) request to the memory
800-
controller, analogous to Grace's SCF cmem_rd_data.
814+
Sums local + remote memory-controller requests across all mesh instances.
815+
Each hns_mc_reqs_*_all is a 64B cache-line request to a memory controller,
816+
counted once at its home HN-S. Local counts requests served by the local
817+
memory subsystem; remote counts requests routed to a memory controller on
818+
another mesh instance (non-zero under interleaved memory or cross-node
819+
access).
801820
"""
802821
mc_reqs = _sum_cmn_event(grouped_df, "hns_mc_reqs_local_all")
822+
try:
823+
mc_reqs = mc_reqs + _sum_cmn_event(grouped_df, "hns_mc_reqs_remote_all")
824+
except KeyError:
825+
# Older collect script without remote MC reqs — local-only fallback.
826+
pass
827+
dur = get_duration_series(grouped_df.get_group("instructions"))
828+
mc_reqs.index = dur.index
829+
bw_series = (mc_reqs * 64).div(dur)
830+
return {
831+
"name": "CMN Memory Bandwidth (MBps)",
832+
"series": bw_series,
833+
"prefix": 10**-6,
834+
}
835+
836+
837+
@skip_if_missing
838+
def cmn_mem_local_bw_MBps(grouped_df):
839+
"""Local DRAM bandwidth — MC requests served by the local memory subsystem."""
840+
mc_reqs = _sum_cmn_event(grouped_df, "hns_mc_reqs_local_all")
803841
dur = get_duration_series(grouped_df.get_group("instructions"))
804842
mc_reqs.index = dur.index
805843
bw_series = (mc_reqs * 64).div(dur)
806844
return {
807-
"name": "CMN Memory Read Bandwidth (MBps)",
845+
"name": "CMN Local Memory Bandwidth (MBps)",
808846
"series": bw_series,
809847
"prefix": 10**-6,
810848
}
811849

812850

851+
@skip_if_missing
852+
def cmn_mem_remote_bw_MBps(grouped_df):
853+
"""Remote DRAM bandwidth — MC requests routed to another mesh instance.
854+
855+
With memory interleaved across mesh instances this reflects the cross-mesh
856+
share of traffic; with node-local memory it captures cross-node access.
857+
"""
858+
mc_reqs = _sum_cmn_event(grouped_df, "hns_mc_reqs_remote_all")
859+
dur = get_duration_series(grouped_df.get_group("instructions"))
860+
mc_reqs.index = dur.index
861+
bw_series = (mc_reqs * 64).div(dur)
862+
return {
863+
"name": "CMN Remote Memory Bandwidth (MBps)",
864+
"series": bw_series,
865+
"prefix": 10**-6,
866+
}
867+
868+
869+
@skip_if_missing
870+
def cmn_mem_read_pct(grouped_df):
871+
"""Approximate read share of memory traffic from HN-S PoCQ occupancy.
872+
873+
The HN-S has no read/write split on MC requests, but the QoS PoCQ
874+
occupancy counters (read vs write) track how long read- vs write-class
875+
requests sit in the point-of-coherency queue, giving a usable read/write
876+
mix proxy. Reported as the read fraction of (read + write) occupancy.
877+
"""
878+
read_occ = _sum_cmn_event(grouped_df, "hns_qos_pocq_occupancy_read")
879+
write_occ = _sum_cmn_event(grouped_df, "hns_qos_pocq_occupancy_write")
880+
write_occ.index = read_occ.index
881+
total = read_occ + write_occ
882+
return {
883+
"name": "CMN Memory Read Mix %",
884+
"series": read_occ.div(total),
885+
"prefix": 100,
886+
}
887+
888+
889+
@skip_if_missing
890+
def cmn_mc_retry_pct(grouped_df):
891+
"""Memory-controller retry rate — retried MC requests / total MC requests.
892+
893+
Maps to the HN-S "mc_retry" metric. A high retry rate indicates
894+
the memory controller is backpressuring the mesh (DRAM-bound).
895+
"""
896+
retries = _sum_cmn_event(grouped_df, "hns_mc_retries_local")
897+
try:
898+
retries = retries + _sum_cmn_event(grouped_df, "hns_mc_retries_remote")
899+
except KeyError:
900+
pass
901+
reqs = _sum_cmn_event(grouped_df, "hns_mc_reqs_local_all")
902+
try:
903+
reqs = reqs + _sum_cmn_event(grouped_df, "hns_mc_reqs_remote_all")
904+
except KeyError:
905+
pass
906+
retries.index = reqs.index
907+
return {
908+
"name": "CMN MC Retry %",
909+
"series": retries.div(reqs),
910+
"prefix": 100,
911+
}
912+
913+
914+
@skip_if_missing
915+
def cmn_sf_hit_rate(grouped_df):
916+
"""Snoop-filter hit rate — hns_sf_hit_all / hns_slc_sf_cache_access_all.
917+
918+
Maps to the HN-S "sf_hit_ratio". Complements the existing SLC
919+
(L3) hit-rate metric with coherence-directory effectiveness.
920+
"""
921+
hit_s = _sum_cmn_event(grouped_df, "hns_sf_hit_all")
922+
access_s = _sum_cmn_event(grouped_df, "hns_slc_sf_cache_access_all")
923+
hit_s.index = access_s.index
924+
return {
925+
"name": "CMN Snoop Filter Hit Rate %",
926+
"series": hit_s.div(access_s),
927+
"prefix": 100,
928+
}
929+
930+
931+
@skip_if_missing
932+
def cmn_mesh_freq_ghz(grouped_df):
933+
"""Mesh clock frequency (GHz) derived from the DTC cycle counter.
934+
935+
dtc_cycles increments at the mesh clock; dividing by the wall-clock
936+
sample duration recovers the effective mesh frequency. Useful for
937+
detecting mesh DVFS throttling under load.
938+
"""
939+
cyc = _sum_cmn_event(grouped_df, "dtc_cycles")
940+
# dtc_cycles is summed across mesh instances; use the per-instance average.
941+
n_cmn = 0
942+
for name, _ in grouped_df:
943+
if isinstance(name, str) and name.endswith("/dtc_cycles/"):
944+
n_cmn += 1
945+
dur = get_duration_series(grouped_df.get_group("instructions"))
946+
cyc.index = dur.index
947+
if n_cmn > 1:
948+
cyc = cyc / n_cmn
949+
freq = cyc.div(dur) # cycles per second
950+
return {
951+
"name": "CMN Mesh Frequency (GHz)",
952+
"series": freq,
953+
"prefix": 10**-9,
954+
}
955+
956+
957+
# ===========================================================================
958+
# SVE predication effectiveness (V3-specific)
959+
# ===========================================================================
960+
961+
813962
@skip_if_missing
814963
def sve_pred_empty_pct(grouped_df):
815964
"""SVE predicated ops with no active lanes (wasted work)."""
@@ -946,8 +1095,14 @@ def main(
9461095
sve_pred_empty_pct(grouped_df),
9471096
sve_pred_full_pct(grouped_df),
9481097
sve_pred_partial_pct(grouped_df),
949-
# --- CMN uncore (SLC / memory bandwidth) ---
950-
cmn_mem_read_bw_MBps(grouped_df),
1098+
# --- CMN mesh (uncore) metrics ---
1099+
cmn_mem_bw_MBps(grouped_df),
1100+
cmn_mem_local_bw_MBps(grouped_df),
1101+
cmn_mem_remote_bw_MBps(grouped_df),
1102+
cmn_mem_read_pct(grouped_df),
1103+
cmn_mc_retry_pct(grouped_df),
1104+
cmn_sf_hit_rate(grouped_df),
1105+
cmn_mesh_freq_ghz(grouped_df),
9511106
]
9521107

9531108
filtered_metrics = list(itertools.filterfalse(lambda x: x is None, metrics))

0 commit comments

Comments
 (0)