Skip to content

Commit 91818e3

Browse files
charles-typfacebook-github-bot
authored andcommitted
Report native CMN mesh (uncore) metrics for Phoenix (#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-chiplet) 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 XP watchpoints, cloned at install time) produced unusable numbers on Phoenix — write bandwidth reported as 0 and mesh bandwidth of ~680 GB/s — and added network/`/dev/mem`/kernel-module fragility to the install path. **Why** Root cause of the cmn-tools garbage: Meta fleet telemetry (dynolog) holds ~1.3k CMN perf_event fds 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 Cypress HN-S MC-request event genuinely has no read/write split (confirmed against the event set and the Phoenix SoC Architecture Specification, Table 15-4), so a DRAM byte-level read/write split is not observable from HN-S counters at all. **Fix** Drop the cmn-tools apparatus entirely (install_platform_tools.sh, cmn_mesh_discovery.sh, cmn_topdown_runner.sh, the install.py / topdown.py hooks, and the log-parsing path in the report generator). Instead report native CMN PMU metrics mapped to the "HNF Effectiveness" group in the Phoenix SoC Architecture Specification, Table 15-4: - **CMN Memory Bandwidth (MBps)** — read+write total, now summing local **and** remote MC requests across both chiplets (each 64B request counted once at its home HN-S, so no double-counting). - **CMN Local / Remote Memory Bandwidth (MBps)** — NUMA 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 (Table 15-4 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 dynolog counter contention described above. Differential Revision: D102244354
1 parent 71fd704 commit 91818e3

2 files changed

Lines changed: 188 additions & 12 deletions

File tree

perfutils/collect_neoversev3_perf_counters.sh

Lines changed: 26 additions & 4 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) ---------
131+
### CMN-Cypress Uncore PMU Events (mesh / SLC / memory) ---------
132132
### 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.
133+
### These map to the "HNF Effectiveness" group in the Phoenix SoC
134+
### Architecture Specification, Table 15-4 (CMN Performance Events):
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 Meta fleet telemetry (dynolog) holds 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. Read/write DRAM
146+
### *byte* split is not observable from HN-S events on Cypress (it requires XP
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: 162 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -788,28 +788,176 @@ def dispatch_stall_mcq(grouped_df):
788788

789789

790790
# ===========================================================================
791-
# SVE predication effectiveness (V3-specific)
791+
# CMN mesh (uncore) metrics
792+
#
793+
# CMN-Cypress exposes one arm_cmn_N PMU per chiplet. The HN-S (Home Node with
794+
# SLC) events below map to the "HNF Effectiveness" group in the Phoenix SoC
795+
# Architecture Specification, Table 15-4 (CMN Performance Events):
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 both chiplets yields total DRAM traffic with
804+
# no double-counting. The event does NOT distinguish reads from writes at the
805+
# HN-S (confirmed against the Cypress event set), which is why the memory
806+
# bandwidth metric is reported as a read+write 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 chiplets. Each
815+
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
817+
requesting chiplet's own DMS; remote counts requests routed to the peer
818+
chiplet's DMS (non-zero under NUMA-1 interleaving or cross-socket access).
801819
"""
802820
mc_reqs = _sum_cmn_event(grouped_df, "hns_mc_reqs_local_all")
821+
try:
822+
mc_reqs = mc_reqs + _sum_cmn_event(grouped_df, "hns_mc_reqs_remote_all")
823+
except KeyError:
824+
# Older collect script without remote MC reqs — local-only fallback.
825+
pass
826+
dur = get_duration_series(grouped_df.get_group("instructions"))
827+
mc_reqs.index = dur.index
828+
bw_series = (mc_reqs * 64).div(dur)
829+
return {
830+
"name": "CMN Memory Bandwidth (MBps)",
831+
"series": bw_series,
832+
"prefix": 10**-6,
833+
}
834+
835+
836+
@skip_if_missing
837+
def cmn_mem_local_bw_MBps(grouped_df):
838+
"""Local DRAM bandwidth — MC requests served by the requester's own chiplet DMS."""
839+
mc_reqs = _sum_cmn_event(grouped_df, "hns_mc_reqs_local_all")
803840
dur = get_duration_series(grouped_df.get_group("instructions"))
804841
mc_reqs.index = dur.index
805842
bw_series = (mc_reqs * 64).div(dur)
806843
return {
807-
"name": "CMN Memory Read Bandwidth (MBps)",
844+
"name": "CMN Local Memory Bandwidth (MBps)",
808845
"series": bw_series,
809846
"prefix": 10**-6,
810847
}
811848

812849

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

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

0 commit comments

Comments
 (0)