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

tools/perf: Correctly calculate sample period for inherited SAMPLE_READ values

Sample period calculation in deliver_sample_value is updated to
calculate the per-thread period delta for events that are inherit +
PERF_SAMPLE_READ. When the sampling event has this configuration, the
read_format.id is used with the tid from the sample to lookup the
storage of the previously accumulated counter total before calculating
the delta. All existing valid configurations where read_format.value
represents some global value continue to use just the read_format.id to
locate the storage of the previously accumulated total.

perf_sample_id is modified to support tracking per-thread
values, along with the existing global per-id values. In the
per-thread case, values are stored in a hash by tid within the
perf_sample_id, and are dynamically allocated as the number is not known
ahead of time.

Signed-off-by: Ben Gainey <ben.gainey@arm.com>
Cc: james.clark@arm.com
Link: https://lore.kernel.org/r/20241001121505.1009685-2-ben.gainey@arm.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>

authored by

Ben Gainey and committed by
Namhyung Kim
80c281fc ad321b19

+129 -12
+48
tools/lib/perf/evsel.c
··· 5 5 #include <perf/evsel.h> 6 6 #include <perf/cpumap.h> 7 7 #include <perf/threadmap.h> 8 + #include <linux/hash.h> 8 9 #include <linux/list.h> 9 10 #include <internal/evsel.h> 10 11 #include <linux/zalloc.h> ··· 24 23 int idx) 25 24 { 26 25 INIT_LIST_HEAD(&evsel->node); 26 + INIT_LIST_HEAD(&evsel->per_stream_periods); 27 27 evsel->attr = *attr; 28 28 evsel->idx = idx; 29 29 evsel->leader = evsel; ··· 533 531 534 532 void perf_evsel__free_id(struct perf_evsel *evsel) 535 533 { 534 + struct perf_sample_id_period *pos, *n; 535 + 536 536 xyarray__delete(evsel->sample_id); 537 537 evsel->sample_id = NULL; 538 538 zfree(&evsel->id); 539 539 evsel->ids = 0; 540 + 541 + perf_evsel_for_each_per_thread_period_safe(evsel, n, pos) { 542 + list_del_init(&pos->node); 543 + free(pos); 544 + } 545 + } 546 + 547 + bool perf_evsel__attr_has_per_thread_sample_period(struct perf_evsel *evsel) 548 + { 549 + return (evsel->attr.sample_type & PERF_SAMPLE_READ) && 550 + (evsel->attr.sample_type & PERF_SAMPLE_TID) && 551 + evsel->attr.inherit; 552 + } 553 + 554 + u64 *perf_sample_id__get_period_storage(struct perf_sample_id *sid, u32 tid, bool per_thread) 555 + { 556 + struct hlist_head *head; 557 + struct perf_sample_id_period *res; 558 + int hash; 559 + 560 + if (!per_thread) 561 + return &sid->period; 562 + 563 + hash = hash_32(tid, PERF_SAMPLE_ID__HLIST_BITS); 564 + head = &sid->periods[hash]; 565 + 566 + hlist_for_each_entry(res, head, hnode) 567 + if (res->tid == tid) 568 + return &res->period; 569 + 570 + if (sid->evsel == NULL) 571 + return NULL; 572 + 573 + res = zalloc(sizeof(struct perf_sample_id_period)); 574 + if (res == NULL) 575 + return NULL; 576 + 577 + INIT_LIST_HEAD(&res->node); 578 + res->tid = tid; 579 + 580 + list_add_tail(&res->node, &sid->evsel->per_stream_periods); 581 + hlist_add_head(&res->hnode, &sid->periods[hash]); 582 + 583 + return &res->period; 540 584 } 541 585 542 586 void perf_counts_values__scale(struct perf_counts_values *count,
+61 -2
tools/lib/perf/include/internal/evsel.h
··· 11 11 struct perf_thread_map; 12 12 struct xyarray; 13 13 14 + /** 15 + * The per-thread accumulated period storage node. 16 + */ 17 + struct perf_sample_id_period { 18 + struct list_head node; 19 + struct hlist_node hnode; 20 + /* Holds total ID period value for PERF_SAMPLE_READ processing. */ 21 + u64 period; 22 + /* The TID that the values belongs to */ 23 + u32 tid; 24 + }; 25 + 26 + /** 27 + * perf_evsel_for_each_per_thread_period_safe - safely iterate thru all the 28 + * per_stream_periods 29 + * @evlist:perf_evsel instance to iterate 30 + * @item: struct perf_sample_id_period iterator 31 + * @tmp: struct perf_sample_id_period temp iterator 32 + */ 33 + #define perf_evsel_for_each_per_thread_period_safe(evsel, tmp, item) \ 34 + list_for_each_entry_safe(item, tmp, &(evsel)->per_stream_periods, node) 35 + 36 + 37 + #define PERF_SAMPLE_ID__HLIST_BITS 4 38 + #define PERF_SAMPLE_ID__HLIST_SIZE (1 << PERF_SAMPLE_ID__HLIST_BITS) 39 + 14 40 /* 15 41 * Per fd, to map back from PERF_SAMPLE_ID to evsel, only used when there are 16 42 * more than one entry in the evlist. ··· 60 34 pid_t machine_pid; 61 35 struct perf_cpu vcpu; 62 36 63 - /* Holds total ID period value for PERF_SAMPLE_READ processing. */ 64 - u64 period; 37 + /* 38 + * Per-thread, and global event counts are mutually exclusive: 39 + * Whilst it is possible to combine events into a group with differing 40 + * values of PERF_SAMPLE_READ, it is not valid to have inconsistent 41 + * values for `inherit`. Therefore it is not possible to have a 42 + * situation where a per-thread event is sampled as a global event; 43 + * all !inherit groups are global, and all groups where the sampling 44 + * event is inherit + PERF_SAMPLE_READ will be per-thread. Any event 45 + * that is part of such a group that is inherit but not PERF_SAMPLE_READ 46 + * will be read as per-thread. If such an event can also trigger a 47 + * sample (such as with sample_period > 0) then it will not cause 48 + * `read_format` to be included in its PERF_RECORD_SAMPLE, and 49 + * therefore will not expose the per-thread group members as global. 50 + */ 51 + union { 52 + /* 53 + * Holds total ID period value for PERF_SAMPLE_READ processing 54 + * (when period is not per-thread). 55 + */ 56 + u64 period; 57 + /* 58 + * Holds total ID period value for PERF_SAMPLE_READ processing 59 + * (when period is per-thread). 60 + */ 61 + struct hlist_head periods[PERF_SAMPLE_ID__HLIST_SIZE]; 62 + }; 65 63 }; 66 64 67 65 struct perf_evsel { ··· 107 57 u64 *id; 108 58 u32 ids; 109 59 struct perf_evsel *leader; 60 + 61 + /* For events where the read_format value is per-thread rather than 62 + * global, stores the per-thread cumulative period */ 63 + struct list_head per_stream_periods; 110 64 111 65 /* parse modifier helper */ 112 66 int nr_members; ··· 141 87 142 88 int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads); 143 89 void perf_evsel__free_id(struct perf_evsel *evsel); 90 + 91 + bool perf_evsel__attr_has_per_thread_sample_period(struct perf_evsel *evsel); 92 + 93 + u64 *perf_sample_id__get_period_storage(struct perf_sample_id *sid, u32 tid, 94 + bool per_thread); 144 95 145 96 #endif /* __LIBPERF_INTERNAL_EVSEL_H */
+20 -10
tools/perf/util/session.c
··· 1171 1171 union perf_event *event, 1172 1172 struct perf_sample *sample, 1173 1173 struct sample_read_value *v, 1174 - struct machine *machine) 1174 + struct machine *machine, 1175 + bool per_thread) 1175 1176 { 1176 1177 struct perf_sample_id *sid = evlist__id2sid(evlist, v->id); 1177 1178 struct evsel *evsel; 1179 + u64 *storage = NULL; 1178 1180 1179 1181 if (sid) { 1180 - sample->id = v->id; 1181 - sample->period = v->value - sid->period; 1182 - sid->period = v->value; 1182 + storage = perf_sample_id__get_period_storage(sid, sample->tid, per_thread); 1183 1183 } 1184 1184 1185 - if (!sid || sid->evsel == NULL) { 1185 + if (storage) { 1186 + sample->id = v->id; 1187 + sample->period = v->value - *storage; 1188 + *storage = v->value; 1189 + } 1190 + 1191 + if (!storage || sid->evsel == NULL) { 1186 1192 ++evlist->stats.nr_unknown_id; 1187 1193 return 0; 1188 1194 } ··· 1209 1203 union perf_event *event, 1210 1204 struct perf_sample *sample, 1211 1205 struct machine *machine, 1212 - u64 read_format) 1206 + u64 read_format, 1207 + bool per_thread) 1213 1208 { 1214 1209 int ret = -EINVAL; 1215 1210 struct sample_read_value *v = sample->read.group.values; 1216 1211 1217 1212 if (tool->dont_split_sample_group) 1218 - return deliver_sample_value(evlist, tool, event, sample, v, machine); 1213 + return deliver_sample_value(evlist, tool, event, sample, v, machine, 1214 + per_thread); 1219 1215 1220 1216 sample_read_group__for_each(v, sample->read.group.nr, read_format) { 1221 1217 ret = deliver_sample_value(evlist, tool, event, sample, v, 1222 - machine); 1218 + machine, per_thread); 1223 1219 if (ret) 1224 1220 break; 1225 1221 } ··· 1236 1228 /* We know evsel != NULL. */ 1237 1229 u64 sample_type = evsel->core.attr.sample_type; 1238 1230 u64 read_format = evsel->core.attr.read_format; 1231 + bool per_thread = perf_evsel__attr_has_per_thread_sample_period(&evsel->core); 1239 1232 1240 1233 /* Standard sample delivery. */ 1241 1234 if (!(sample_type & PERF_SAMPLE_READ)) ··· 1245 1236 /* For PERF_SAMPLE_READ we have either single or group mode. */ 1246 1237 if (read_format & PERF_FORMAT_GROUP) 1247 1238 return deliver_sample_group(evlist, tool, event, sample, 1248 - machine, read_format); 1239 + machine, read_format, per_thread); 1249 1240 else 1250 1241 return deliver_sample_value(evlist, tool, event, sample, 1251 - &sample->read.one, machine); 1242 + &sample->read.one, machine, 1243 + per_thread); 1252 1244 } 1253 1245 1254 1246 static int machines__deliver_event(struct machines *machines,