Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf stat: Support metrics with hybrid events

One metric such as 'Kernel_Utilization' may be from different PMUs and
consists of different events.

For core,
Kernel_Utilization = cpu_clk_unhalted.thread:k / cpu_clk_unhalted.thread

For atom,
Kernel_Utilization = cpu_clk_unhalted.core:k / cpu_clk_unhalted.core

The metric group string for core is:
'{cpu_clk_unhalted.thread/metric-id=cpu_clk_unhalted.thread:k/k,cpu_clk_unhalted.thread/metric-id=cpu_clk_unhalted.thread/}:W'
It's internally expanded to:
'{cpu_clk_unhalted.thread_p/metric-id=cpu_clk_unhalted.thread_p:k/k,cpu_clk_unhalted.thread/metric-id=cpu_clk_unhalted.thread/}:W#cpu_core'

The metric group string for atom is:
'{cpu_clk_unhalted.core/metric-id=cpu_clk_unhalted.core:k/k,cpu_clk_unhalted.core/metric-id=cpu_clk_unhalted.core/}:W'
It's internally expanded to:
'{cpu_clk_unhalted.core/metric-id=cpu_clk_unhalted.core:k/k,cpu_clk_unhalted.core/metric-id=cpu_clk_unhalted.core/}:W#cpu_atom'

That means the group "{cpu_clk_unhalted.thread:k,cpu_clk_unhalted.thread}:W"
is from cpu_core PMU and the group "{cpu_clk_unhalted.core:k,cpu_clk_unhalted.core}"
is from cpu_atom PMU. And then next, check if the events in the group are
valid on that PMU. If one event is not valid on that PMU, the associated
group would be removed internally.

In this example, cpu_clk_unhalted.thread is valid on cpu_core and
cpu_clk_unhalted.core is valid on cpu_atom. So the checks for these two
groups are passed.

Before:

# ./perf stat -M Kernel_Utilization -a sleep 1
WARNING: events in group from different hybrid PMUs!
WARNING: grouped events cpus do not match, disabling group:
anon group { CPU_CLK_UNHALTED.THREAD_P:k, CPU_CLK_UNHALTED.THREAD_P:k, CPU_CLK_UNHALTED.THREAD, CPU_CLK_UNHALTED.THREAD }

Performance counter stats for 'system wide':

17,639,501 cpu_atom/CPU_CLK_UNHALTED.CORE/ # 1.00 Kernel_Utilization
17,578,757 cpu_atom/CPU_CLK_UNHALTED.CORE:k/
1,005,350,226 ns duration_time
43,012,352 cpu_core/CPU_CLK_UNHALTED.THREAD_P:k/ # 0.99 Kernel_Utilization
17,608,010 cpu_atom/CPU_CLK_UNHALTED.THREAD_P:k/
43,608,755 cpu_core/CPU_CLK_UNHALTED.THREAD/
17,630,838 cpu_atom/CPU_CLK_UNHALTED.THREAD/
1,005,350,226 ns duration_time

1.005350226 seconds time elapsed

After:

# ./perf stat -M Kernel_Utilization -a sleep 1

Performance counter stats for 'system wide':

17,981,895 CPU_CLK_UNHALTED.CORE [cpu_atom] # 1.00 Kernel_Utilization
17,925,405 CPU_CLK_UNHALTED.CORE:k [cpu_atom]
1,004,811,366 ns duration_time
41,246,425 CPU_CLK_UNHALTED.THREAD_P:k [cpu_core] # 0.99 Kernel_Utilization
41,819,129 CPU_CLK_UNHALTED.THREAD [cpu_core]
1,004,811,366 ns duration_time

1.004811366 seconds time elapsed

Reviewed-by: Kan Liang <kan.liang@linux.intel.com>
Signed-off-by: Xing Zhengjun <zhengjun.xing@linux.intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20220422065635.767648-1-zhengjun.xing@linux.intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Zhengjun Xing and committed by
Arnaldo Carvalho de Melo
60344f1a 17408e59

+249 -22
+244 -19
tools/perf/util/metricgroup.c
··· 141 141 * output. 142 142 */ 143 143 const char *metric_unit; 144 + /** 145 + * The name of the CPU such as "cpu_core" or "cpu_atom" in hybrid systems 146 + * and "NULL" in non-hybrid systems. 147 + */ 148 + const char *pmu_name; 144 149 /** Optional null terminated array of referenced metrics. */ 145 150 struct metric_ref *metric_refs; 146 151 /** ··· 220 215 } 221 216 m->metric_expr = pe->metric_expr; 222 217 m->metric_unit = pe->unit; 218 + m->pmu_name = pe->pmu; 223 219 m->pctx->runtime = runtime; 224 220 m->has_constraint = metric_no_group || metricgroup__has_constraint(pe); 225 221 m->metric_refs = NULL; ··· 256 250 * @ids: the metric IDs to match. 257 251 * @metric_evlist: the list of perf events. 258 252 * @out_metric_events: holds the created metric events array. 253 + * @pmu_name: the name of the CPU. 259 254 */ 260 255 static int setup_metric_events(struct hashmap *ids, 261 256 struct evlist *metric_evlist, 262 - struct evsel ***out_metric_events) 257 + struct evsel ***out_metric_events, 258 + const char *pmu_name) 263 259 { 264 260 struct evsel **metric_events; 265 261 const char *metric_id; ··· 294 286 * about this event. 295 287 */ 296 288 if (hashmap__find(ids, metric_id, (void **)&val_ptr)) { 289 + if (evsel__is_hybrid(ev) && pmu_name && 290 + strcmp(pmu_name, ev->pmu_name)) { 291 + continue; 292 + } 297 293 metric_events[matched_events++] = ev; 298 294 299 295 if (matched_events >= ids_size) ··· 736 724 static int metricgroup__build_event_string(struct strbuf *events, 737 725 const struct expr_parse_ctx *ctx, 738 726 const char *modifier, 739 - bool has_constraint) 727 + bool has_constraint, 728 + const char *pmu_name) 740 729 { 741 730 struct hashmap_entry *cur; 742 731 size_t bkt; ··· 819 806 if (no_group) { 820 807 /* Strange case of a metric of just duration_time. */ 821 808 ret = strbuf_addf(events, "duration_time"); 822 - } else if (!has_constraint) 823 - ret = strbuf_addf(events, "}:W,duration_time"); 824 - else 809 + } else if (!has_constraint) { 810 + ret = strbuf_addf(events, "}:W"); 811 + if (pmu_name) 812 + ret = strbuf_addf(events, "#%s", pmu_name); 825 813 ret = strbuf_addf(events, ",duration_time"); 826 - } else if (!no_group && !has_constraint) 814 + } else 815 + ret = strbuf_addf(events, ",duration_time"); 816 + } else if (!no_group && !has_constraint) { 827 817 ret = strbuf_addf(events, "}:W"); 818 + if (pmu_name) 819 + ret = strbuf_addf(events, "#%s", pmu_name); 820 + } 828 821 829 822 return ret; 830 823 #undef RETURN_IF_NON_ZERO ··· 1169 1150 * @metric_list: The list that the metric or metric group are added to. 1170 1151 * @map: The map that is searched for metrics, most commonly the table for the 1171 1152 * architecture perf is running upon. 1153 + * @pmu_name: the name of the CPU. 1172 1154 */ 1173 - static int metricgroup__add_metric(const char *metric_name, const char *modifier, 1174 - bool metric_no_group, 1155 + static int metricgroup__add_metric(const char *metric_name, 1156 + const char *modifier, bool metric_no_group, 1175 1157 struct list_head *metric_list, 1176 - const struct pmu_events_map *map) 1158 + const struct pmu_events_map *map, 1159 + const char *pmu_name) 1177 1160 { 1178 1161 const struct pmu_event *pe; 1179 1162 LIST_HEAD(list); ··· 1188 1167 */ 1189 1168 map_for_each_metric(pe, i, map, metric_name) { 1190 1169 has_match = true; 1170 + if (pmu_name && pe->pmu && strcmp(pmu_name, pe->pmu)) 1171 + continue; 1191 1172 ret = add_metric(&list, pe, modifier, metric_no_group, 1192 1173 /*root_metric=*/NULL, 1193 1174 /*visited_metrics=*/NULL, map); ··· 1238 1215 * @metric_list: The list that metrics are added to. 1239 1216 * @map: The map that is searched for metrics, most commonly the table for the 1240 1217 * architecture perf is running upon. 1218 + * @pmu_name: the name of the CPU. 1241 1219 */ 1242 1220 static int metricgroup__add_metric_list(const char *list, bool metric_no_group, 1243 1221 struct list_head *metric_list, 1244 - const struct pmu_events_map *map) 1222 + const struct pmu_events_map *map, 1223 + const char *pmu_name) 1245 1224 { 1246 1225 char *list_itr, *list_copy, *metric_name, *modifier; 1247 1226 int ret, count = 0; ··· 1260 1235 1261 1236 ret = metricgroup__add_metric(metric_name, modifier, 1262 1237 metric_no_group, metric_list, 1263 - map); 1238 + map, pmu_name); 1264 1239 if (ret == -EINVAL) 1265 1240 pr_err("Cannot find metric or group `%s'\n", metric_name); 1266 1241 ··· 1335 1310 return ret; 1336 1311 } 1337 1312 1313 + static char *get_metric_pmus(char *orig_str, struct strbuf *metric_pmus) 1314 + { 1315 + char *llist, *nlist, *p1, *p2, *new_str = NULL; 1316 + int ret; 1317 + struct strbuf new_events; 1318 + 1319 + if (!strchr(orig_str, '#')) { 1320 + /* 1321 + * pmu name is added after '#'. If no '#' found, 1322 + * don't need to process pmu. 1323 + */ 1324 + return strdup(orig_str); 1325 + } 1326 + 1327 + nlist = strdup(orig_str); 1328 + if (!nlist) 1329 + return new_str; 1330 + 1331 + ret = strbuf_init(&new_events, 100); 1332 + if (ret) 1333 + goto err_out; 1334 + 1335 + ret = strbuf_grow(metric_pmus, 100); 1336 + if (ret) 1337 + goto err_out; 1338 + 1339 + llist = nlist; 1340 + while ((p1 = strsep(&llist, ",")) != NULL) { 1341 + p2 = strchr(p1, '#'); 1342 + if (p2) { 1343 + *p2 = 0; 1344 + ret = strbuf_addf(&new_events, "%s,", p1); 1345 + if (ret) 1346 + goto err_out; 1347 + 1348 + ret = strbuf_addf(metric_pmus, "%s,", p2 + 1); 1349 + if (ret) 1350 + goto err_out; 1351 + 1352 + } else { 1353 + ret = strbuf_addf(&new_events, "%s,", p1); 1354 + if (ret) 1355 + goto err_out; 1356 + } 1357 + } 1358 + 1359 + new_str = strdup(new_events.buf); 1360 + if (new_str) { 1361 + /* Remove last ',' */ 1362 + new_str[strlen(new_str) - 1] = 0; 1363 + } 1364 + err_out: 1365 + free(nlist); 1366 + strbuf_release(&new_events); 1367 + return new_str; 1368 + } 1369 + 1370 + static void set_pmu_unmatched_events(struct evlist *evlist, int group_idx, 1371 + char *pmu_name, 1372 + unsigned long *evlist_removed) 1373 + { 1374 + struct evsel *evsel, *pos; 1375 + int i = 0, j = 0; 1376 + 1377 + /* 1378 + * Move to the first evsel of a given group 1379 + */ 1380 + evlist__for_each_entry(evlist, evsel) { 1381 + if (evsel__is_group_leader(evsel) && 1382 + evsel->core.nr_members >= 1) { 1383 + if (i < group_idx) { 1384 + j += evsel->core.nr_members; 1385 + i++; 1386 + continue; 1387 + } 1388 + } 1389 + } 1390 + 1391 + i = 0; 1392 + evlist__for_each_entry(evlist, evsel) { 1393 + if (i < j) { 1394 + i++; 1395 + continue; 1396 + } 1397 + 1398 + /* 1399 + * Now we are at the first evsel in the group 1400 + */ 1401 + for_each_group_evsel(pos, evsel) { 1402 + if (evsel__is_hybrid(pos) && 1403 + strcmp(pos->pmu_name, pmu_name)) { 1404 + set_bit(pos->core.idx, evlist_removed); 1405 + } 1406 + } 1407 + break; 1408 + } 1409 + } 1410 + 1411 + static void remove_pmu_umatched_events(struct evlist *evlist, char *metric_pmus) 1412 + { 1413 + struct evsel *evsel, *tmp, *new_leader = NULL; 1414 + unsigned long *evlist_removed; 1415 + char *llist, *nlist, *p1; 1416 + bool need_new_leader = false; 1417 + int i = 0, new_nr_members = 0; 1418 + 1419 + nlist = strdup(metric_pmus); 1420 + if (!nlist) 1421 + return; 1422 + 1423 + evlist_removed = bitmap_zalloc(evlist->core.nr_entries); 1424 + if (!evlist_removed) { 1425 + free(nlist); 1426 + return; 1427 + } 1428 + 1429 + llist = nlist; 1430 + while ((p1 = strsep(&llist, ",")) != NULL) { 1431 + if (strlen(p1) > 0) { 1432 + /* 1433 + * p1 points to the string of pmu name, e.g. "cpu_atom". 1434 + * The metric group string has pmu suffixes, e.g. 1435 + * "{inst_retired.any,cpu_clk_unhalted.thread}:W#cpu_core, 1436 + * {cpu_clk_unhalted.core,inst_retired.any_p}:W#cpu_atom" 1437 + * By counting the pmu name, we can know the index of 1438 + * group. 1439 + */ 1440 + set_pmu_unmatched_events(evlist, i++, p1, 1441 + evlist_removed); 1442 + } 1443 + } 1444 + 1445 + evlist__for_each_entry_safe(evlist, tmp, evsel) { 1446 + if (test_bit(evsel->core.idx, evlist_removed)) { 1447 + if (!evsel__is_group_leader(evsel)) { 1448 + if (!need_new_leader) { 1449 + if (new_leader) 1450 + new_leader->core.leader->nr_members--; 1451 + else 1452 + evsel->core.leader->nr_members--; 1453 + } else 1454 + new_nr_members--; 1455 + } else { 1456 + /* 1457 + * If group leader is to remove, we need to 1458 + * prepare a new leader and adjust all group 1459 + * members. 1460 + */ 1461 + need_new_leader = true; 1462 + new_nr_members = 1463 + evsel->core.leader->nr_members - 1; 1464 + } 1465 + 1466 + evlist__remove(evlist, evsel); 1467 + evsel__delete(evsel); 1468 + } else { 1469 + if (!evsel__is_group_leader(evsel)) { 1470 + if (need_new_leader) { 1471 + need_new_leader = false; 1472 + new_leader = evsel; 1473 + new_leader->core.leader = 1474 + &new_leader->core; 1475 + new_leader->core.nr_members = 1476 + new_nr_members; 1477 + } else if (new_leader) 1478 + evsel->core.leader = &new_leader->core; 1479 + } else { 1480 + need_new_leader = false; 1481 + new_leader = NULL; 1482 + } 1483 + } 1484 + } 1485 + 1486 + bitmap_free(evlist_removed); 1487 + free(nlist); 1488 + } 1489 + 1338 1490 /** 1339 1491 * parse_ids - Build the event string for the ids and parse them creating an 1340 1492 * evlist. The encoded metric_ids are decoded. ··· 1521 1319 * @modifier: any modifiers added to the events. 1522 1320 * @has_constraint: false if events should be placed in a weak group. 1523 1321 * @out_evlist: the created list of events. 1322 + * @pmu_name: the name of the CPU. 1524 1323 */ 1525 1324 static int parse_ids(bool metric_no_merge, struct perf_pmu *fake_pmu, 1526 1325 struct expr_parse_ctx *ids, const char *modifier, 1527 - bool has_constraint, struct evlist **out_evlist) 1326 + bool has_constraint, struct evlist **out_evlist, 1327 + const char *pmu_name) 1528 1328 { 1529 1329 struct parse_events_error parse_error; 1530 1330 struct evlist *parsed_evlist; 1531 1331 struct strbuf events = STRBUF_INIT; 1332 + struct strbuf metric_pmus = STRBUF_INIT; 1333 + char *nlist = NULL; 1532 1334 int ret; 1533 1335 1534 1336 *out_evlist = NULL; ··· 1559 1353 ids__insert(ids->ids, tmp); 1560 1354 } 1561 1355 ret = metricgroup__build_event_string(&events, ids, modifier, 1562 - has_constraint); 1356 + has_constraint, pmu_name); 1563 1357 if (ret) 1564 1358 return ret; 1565 1359 ··· 1570 1364 } 1571 1365 pr_debug("Parsing metric events '%s'\n", events.buf); 1572 1366 parse_events_error__init(&parse_error); 1573 - ret = __parse_events(parsed_evlist, events.buf, &parse_error, fake_pmu); 1367 + nlist = get_metric_pmus(events.buf, &metric_pmus); 1368 + if (!nlist) { 1369 + ret = -ENOMEM; 1370 + goto err_out; 1371 + } 1372 + ret = __parse_events(parsed_evlist, nlist, &parse_error, fake_pmu); 1574 1373 if (ret) { 1575 1374 parse_events_error__print(&parse_error, events.buf); 1576 1375 goto err_out; 1577 1376 } 1377 + 1378 + if (metric_pmus.alloc) 1379 + remove_pmu_umatched_events(parsed_evlist, metric_pmus.buf); 1380 + 1578 1381 ret = decode_all_metric_ids(parsed_evlist, modifier); 1579 1382 if (ret) 1580 1383 goto err_out; ··· 1591 1376 *out_evlist = parsed_evlist; 1592 1377 parsed_evlist = NULL; 1593 1378 err_out: 1379 + if (nlist) 1380 + free(nlist); 1594 1381 parse_events_error__exit(&parse_error); 1595 1382 evlist__delete(parsed_evlist); 1596 1383 strbuf_release(&events); 1384 + strbuf_release(&metric_pmus); 1597 1385 return ret; 1598 1386 } 1599 1387 ··· 1615 1397 if (metric_events_list->nr_entries == 0) 1616 1398 metricgroup__rblist_init(metric_events_list); 1617 1399 ret = metricgroup__add_metric_list(str, metric_no_group, 1618 - &metric_list, map); 1400 + &metric_list, map, 1401 + perf_evlist->hybrid_pmu_name); 1619 1402 if (ret) 1620 1403 goto out; 1621 1404 ··· 1632 1413 ret = parse_ids(metric_no_merge, fake_pmu, combined, 1633 1414 /*modifier=*/NULL, 1634 1415 /*has_constraint=*/true, 1635 - &combined_evlist); 1416 + &combined_evlist, 1417 + perf_evlist->hybrid_pmu_name); 1636 1418 } 1637 1419 if (combined) 1638 1420 expr__ctx_free(combined); ··· 1670 1450 continue; 1671 1451 1672 1452 if (expr__subset_of_ids(n->pctx, m->pctx)) { 1453 + if (m->pmu_name && n->pmu_name 1454 + && strcmp(m->pmu_name, n->pmu_name)) 1455 + continue; 1673 1456 pr_debug("Events in '%s' fully contained within '%s'\n", 1674 1457 m->metric_name, n->metric_name); 1675 1458 metric_evlist = n->evlist; ··· 1682 1459 } 1683 1460 } 1684 1461 if (!metric_evlist) { 1685 - ret = parse_ids(metric_no_merge, fake_pmu, m->pctx, m->modifier, 1686 - m->has_constraint, &m->evlist); 1462 + ret = parse_ids(metric_no_merge, fake_pmu, m->pctx, 1463 + m->modifier, m->has_constraint, 1464 + &m->evlist, m->pmu_name); 1687 1465 if (ret) 1688 1466 goto out; 1689 1467 1690 1468 metric_evlist = m->evlist; 1691 1469 } 1692 - ret = setup_metric_events(m->pctx->ids, metric_evlist, &metric_events); 1470 + ret = setup_metric_events(m->pctx->ids, metric_evlist, 1471 + &metric_events, m->pmu_name); 1693 1472 if (ret) { 1694 1473 pr_debug("Cannot resolve IDs for %s: %s\n", 1695 1474 m->metric_name, m->metric_expr);
+5 -3
tools/perf/util/stat-display.c
··· 539 539 } 540 540 } 541 541 542 - static void uniquify_event_name(struct evsel *counter) 542 + static void uniquify_event_name(struct evsel *counter, 543 + struct perf_stat_config *stat_config) 543 544 { 544 545 char *new_name; 545 546 char *config; ··· 559 558 counter->name = new_name; 560 559 } 561 560 } else { 562 - if (perf_pmu__has_hybrid()) { 561 + if (perf_pmu__has_hybrid() && 562 + stat_config->metric_events.nr_entries == 0) { 563 563 ret = asprintf(&new_name, "%s/%s/", 564 564 counter->pmu_name, counter->name); 565 565 } else { ··· 621 619 return false; 622 620 cb(config, counter, data, true); 623 621 if (config->no_merge || hybrid_uniquify(counter)) 624 - uniquify_event_name(counter); 622 + uniquify_event_name(counter, config); 625 623 else if (counter->auto_merge_stats) 626 624 collect_all_aliases(config, counter, cb, data); 627 625 return true;