summaryrefslogtreecommitdiffstats
path: root/tools/perf/util/thread.c
diff options
context:
space:
mode:
authorJoel Fernandes <joelagnelf@nvidia.com>2026-07-18 13:15:36 -0400
committerPaul E. McKenney <paulmck@kernel.org>2026-07-23 10:39:57 -0700
commit34ff636f322c6d5e4f0a52338fa63a7dff09a495 (patch)
tree7629759c1ec7148b95bc8ab3080df20a5ecd2273 /tools/perf/util/thread.c
downloadlinux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.tar.gz
linux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.zip
scftorture: Count single_rpc offline failures in statistics outputgrafted
scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Diffstat (limited to 'tools/perf/util/thread.c')
-rw-r--r--tools/perf/util/thread.c638
1 files changed, 638 insertions, 0 deletions
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
new file mode 100644
index 000000000..e483ffcb5
--- /dev/null
+++ b/tools/perf/util/thread.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <linux/kernel.h>
+#include <linux/zalloc.h>
+#include "dso.h"
+#include "session.h"
+#include "thread.h"
+#include "thread-stack.h"
+#include "debug.h"
+#include "namespaces.h"
+#include "comm.h"
+#include "map.h"
+#include "symbol.h"
+#include "unwind.h"
+#include "callchain.h"
+#include "dwarf-regs.h"
+
+#include <api/fs/fs.h>
+
+int thread__init_maps(struct thread *thread, struct machine *machine)
+{
+ pid_t pid = thread__pid(thread);
+
+ if (pid == thread__tid(thread) || pid == -1) {
+ thread__set_maps(thread, maps__new(machine));
+ } else {
+ struct thread *leader = machine__findnew_thread(machine, pid, pid);
+
+ if (leader) {
+ thread__set_maps(thread, maps__get(thread__maps(leader)));
+ thread__put(leader);
+ }
+ }
+
+ return thread__maps(thread) ? 0 : -1;
+}
+
+struct thread *thread__new(pid_t pid, pid_t tid)
+ NO_THREAD_SAFETY_ANALYSIS /* Allocation/creation is inherently single threaded. */
+{
+ RC_STRUCT(thread) *_thread = zalloc(sizeof(*_thread));
+ struct thread *thread;
+
+ if (ADD_RC_CHK(thread, _thread) != NULL) {
+ struct comm *comm;
+ char comm_str[32];
+
+ thread__set_pid(thread, pid);
+ thread__set_tid(thread, tid);
+ thread__set_ppid(thread, -1);
+ thread__set_cpu(thread, -1);
+ thread__set_guest_cpu(thread, -1);
+ thread__set_e_machine(thread, EM_NONE);
+ thread__set_e_is_big_endian(thread, false);
+ thread__set_lbr_stitch_enable(thread, false);
+ INIT_LIST_HEAD(thread__namespaces_list(thread));
+ INIT_LIST_HEAD(thread__comm_list(thread));
+ init_rwsem(thread__namespaces_lock(thread));
+ init_rwsem(thread__comm_lock(thread));
+
+ snprintf(comm_str, sizeof(comm_str), ":%d", tid);
+ comm = comm__new(comm_str, 0, false);
+ if (!comm)
+ goto err_thread;
+
+ list_add(&comm->list, thread__comm_list(thread));
+ refcount_set(thread__refcnt(thread), 1);
+ /* Thread holds first ref to nsdata. */
+ RC_CHK_ACCESS(thread)->nsinfo = nsinfo__new(pid);
+ srccode_state_init(thread__srccode_state(thread));
+ }
+
+ return thread;
+
+err_thread:
+ thread__delete(thread);
+ return NULL;
+}
+
+static void (*thread__priv_destructor)(void *priv);
+
+void thread__set_priv_destructor(void (*destructor)(void *priv))
+{
+ assert(thread__priv_destructor == NULL);
+
+ thread__priv_destructor = destructor;
+}
+
+void thread__delete(struct thread *thread)
+{
+ struct namespaces *namespaces, *tmp_namespaces;
+ struct comm *comm, *tmp_comm;
+
+ thread_stack__free(thread);
+
+ if (thread__maps(thread)) {
+ maps__put(thread__maps(thread));
+ thread__set_maps(thread, NULL);
+ }
+ down_write(thread__namespaces_lock(thread));
+ list_for_each_entry_safe(namespaces, tmp_namespaces,
+ thread__namespaces_list(thread), list) {
+ list_del_init(&namespaces->list);
+ namespaces__free(namespaces);
+ }
+ up_write(thread__namespaces_lock(thread));
+
+ down_write(thread__comm_lock(thread));
+ list_for_each_entry_safe(comm, tmp_comm, thread__comm_list(thread), list) {
+ list_del_init(&comm->list);
+ comm__free(comm);
+ }
+ up_write(thread__comm_lock(thread));
+
+ nsinfo__zput(RC_CHK_ACCESS(thread)->nsinfo);
+ srccode_state_free(thread__srccode_state(thread));
+
+ exit_rwsem(thread__namespaces_lock(thread));
+ exit_rwsem(thread__comm_lock(thread));
+ thread__free_stitch_list(thread);
+
+ if (thread__priv_destructor)
+ thread__priv_destructor(thread__priv(thread));
+
+ RC_CHK_FREE(thread);
+}
+
+struct thread *thread__get(struct thread *thread)
+{
+ struct thread *result;
+
+ if (RC_CHK_GET(result, thread))
+ refcount_inc(thread__refcnt(thread));
+
+ return result;
+}
+
+void thread__put(struct thread *thread)
+{
+ if (thread && refcount_dec_and_test(thread__refcnt(thread)))
+ thread__delete(thread);
+ else
+ RC_CHK_PUT(thread);
+}
+
+static struct namespaces *__thread__namespaces(struct thread *thread)
+{
+ if (list_empty(thread__namespaces_list(thread)))
+ return NULL;
+
+ return list_first_entry(thread__namespaces_list(thread), struct namespaces, list);
+}
+
+struct namespaces *thread__namespaces(struct thread *thread)
+{
+ struct namespaces *ns;
+
+ down_read(thread__namespaces_lock(thread));
+ ns = __thread__namespaces(thread);
+ up_read(thread__namespaces_lock(thread));
+
+ return ns;
+}
+
+static int __thread__set_namespaces(struct thread *thread, u64 timestamp,
+ struct perf_record_namespaces *event)
+{
+ struct namespaces *new, *curr = __thread__namespaces(thread);
+
+ new = namespaces__new(event);
+ if (!new)
+ return -ENOMEM;
+
+ list_add(&new->list, thread__namespaces_list(thread));
+
+ if (timestamp && curr) {
+ /*
+ * setns syscall must have changed few or all the namespaces
+ * of this thread. Update end time for the namespaces
+ * previously used.
+ */
+ curr = list_next_entry(new, list);
+ curr->end_time = timestamp;
+ }
+
+ return 0;
+}
+
+int thread__set_namespaces(struct thread *thread, u64 timestamp,
+ struct perf_record_namespaces *event)
+{
+ int ret;
+
+ down_write(thread__namespaces_lock(thread));
+ ret = __thread__set_namespaces(thread, timestamp, event);
+ up_write(thread__namespaces_lock(thread));
+ return ret;
+}
+
+static struct comm *__thread__comm(struct thread *thread)
+ SHARED_LOCKS_REQUIRED(thread__comm_lock(thread))
+{
+ if (list_empty(thread__comm_list(thread)))
+ return NULL;
+
+ return list_first_entry(thread__comm_list(thread), struct comm, list);
+}
+
+struct comm *thread__comm(struct thread *thread)
+{
+ struct comm *res = NULL;
+
+ down_read(thread__comm_lock(thread));
+ res = __thread__comm(thread);
+ up_read(thread__comm_lock(thread));
+ return res;
+}
+
+struct comm *thread__exec_comm(struct thread *thread)
+{
+ struct comm *comm, *last = NULL, *second_last = NULL;
+
+ down_read(thread__comm_lock(thread));
+ list_for_each_entry(comm, thread__comm_list(thread), list) {
+ if (comm->exec) {
+ up_read(thread__comm_lock(thread));
+ return comm;
+ }
+ second_last = last;
+ last = comm;
+ }
+ up_read(thread__comm_lock(thread));
+
+ /*
+ * 'last' with no start time might be the parent's comm of a synthesized
+ * thread (created by processing a synthesized fork event). For a main
+ * thread, that is very probably wrong. Prefer a later comm to avoid
+ * that case.
+ */
+ if (second_last && !last->start && thread__pid(thread) == thread__tid(thread))
+ return second_last;
+
+ return last;
+}
+
+static int ____thread__set_comm(struct thread *thread, const char *str,
+ u64 timestamp, bool exec)
+ EXCLUSIVE_LOCKS_REQUIRED(thread__comm_lock(thread))
+{
+ struct comm *new, *curr = __thread__comm(thread);
+
+ /* Override the default :tid entry */
+ if (!thread__comm_set(thread)) {
+ int err = comm__override(curr, str, timestamp, exec);
+ if (err)
+ return err;
+ } else {
+ new = comm__new(str, timestamp, exec);
+ if (!new)
+ return -ENOMEM;
+ list_add(&new->list, thread__comm_list(thread));
+
+ if (exec)
+ unwind__flush_access(thread__maps(thread));
+ }
+
+ thread__set_comm_set(thread, true);
+
+ return 0;
+}
+
+int __thread__set_comm(struct thread *thread, const char *str, u64 timestamp,
+ bool exec)
+{
+ int ret;
+
+ down_write(thread__comm_lock(thread));
+ ret = ____thread__set_comm(thread, str, timestamp, exec);
+ up_write(thread__comm_lock(thread));
+ return ret;
+}
+
+int thread__set_comm_from_proc(struct thread *thread)
+{
+ char path[64];
+ char *comm = NULL;
+ size_t sz;
+ int err = -1;
+
+ if (!(snprintf(path, sizeof(path), "%d/task/%d/comm",
+ thread__pid(thread), thread__tid(thread)) >= (int)sizeof(path)) &&
+ procfs__read_str(path, &comm, &sz) == 0) {
+ /* sz==0: read got nothing, e.g. race during exit teardown */
+ if (sz == 0) {
+ free(comm);
+ return -1;
+ }
+ comm[sz - 1] = '\0';
+ err = thread__set_comm(thread, comm, 0);
+ }
+
+ return err;
+}
+
+static const char *__thread__comm_str(struct thread *thread)
+ SHARED_LOCKS_REQUIRED(thread__comm_lock(thread))
+{
+ const struct comm *comm = __thread__comm(thread);
+
+ if (!comm)
+ return NULL;
+
+ return comm__str(comm);
+}
+
+const char *thread__comm_str(struct thread *thread)
+{
+ const char *str;
+
+ down_read(thread__comm_lock(thread));
+ str = __thread__comm_str(thread);
+ up_read(thread__comm_lock(thread));
+
+ return str;
+}
+
+static int __thread__comm_len(struct thread *thread, const char *comm)
+{
+ if (!comm)
+ return 0;
+ thread__set_comm_len(thread, strlen(comm));
+
+ return thread__var_comm_len(thread);
+}
+
+/* CHECKME: it should probably better return the max comm len from its comm list */
+int thread__comm_len(struct thread *thread)
+{
+ int comm_len = thread__var_comm_len(thread);
+
+ if (!comm_len) {
+ const char *comm;
+
+ down_read(thread__comm_lock(thread));
+ comm = __thread__comm_str(thread);
+ comm_len = __thread__comm_len(thread, comm);
+ up_read(thread__comm_lock(thread));
+ }
+
+ return comm_len;
+}
+
+size_t thread__fprintf(struct thread *thread, FILE *fp)
+{
+ return fprintf(fp, "Thread %d %s\n", thread__tid(thread), thread__comm_str(thread)) +
+ maps__fprintf(thread__maps(thread), fp);
+}
+
+int thread__insert_map(struct thread *thread, struct map *map)
+{
+ int ret;
+ uint16_t e_machine;
+
+ ret = maps__fixup_overlap_and_insert(thread__maps(thread), map);
+ if (ret)
+ return ret;
+
+ e_machine = thread__e_machine(thread, /*machine=*/NULL, /*e_flags=*/NULL);
+ return unwind__prepare_access(thread__maps(thread), e_machine);
+}
+
+static int thread__prepare_access(struct thread *thread)
+{
+ uint16_t e_machine = thread__e_machine(thread, /*machine=*/NULL, /*e_flags=*/NULL);
+
+ return unwind__prepare_access(thread__maps(thread), e_machine);
+}
+
+static int thread__clone_maps(struct thread *thread, struct thread *parent, bool do_maps_clone)
+{
+ /* This is new thread, we share map groups for process. */
+ if (thread__pid(thread) == thread__pid(parent))
+ return thread__prepare_access(thread);
+
+ if (maps__equal(thread__maps(thread), thread__maps(parent))) {
+ pr_debug("broken map groups on thread %d/%d parent %d/%d\n",
+ thread__pid(thread), thread__tid(thread),
+ thread__pid(parent), thread__tid(parent));
+ return 0;
+ }
+ /* But this one is new process, copy maps. */
+ return do_maps_clone ? maps__copy_from(thread__maps(thread), thread__maps(parent)) : 0;
+}
+
+int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp, bool do_maps_clone)
+{
+ if (thread__comm_set(parent)) {
+ const char *comm = thread__comm_str(parent);
+ int err;
+ if (!comm)
+ return -ENOMEM;
+ err = thread__set_comm(thread, comm, timestamp);
+ if (err)
+ return err;
+ }
+
+ thread__set_ppid(thread, thread__tid(parent));
+ return thread__clone_maps(thread, parent, do_maps_clone);
+}
+
+void thread__find_cpumode_addr_location(struct thread *thread, u64 addr,
+ bool symbols, struct addr_location *al)
+{
+ size_t i;
+ const u8 cpumodes[] = {
+ PERF_RECORD_MISC_USER,
+ PERF_RECORD_MISC_KERNEL,
+ PERF_RECORD_MISC_GUEST_USER,
+ PERF_RECORD_MISC_GUEST_KERNEL
+ };
+
+ for (i = 0; i < ARRAY_SIZE(cpumodes); i++) {
+ if (symbols)
+ thread__find_symbol(thread, cpumodes[i], addr, al);
+ else
+ thread__find_map(thread, cpumodes[i], addr, al);
+
+ if (al->map)
+ break;
+ }
+}
+
+static uint16_t read_proc_e_machine_for_pid(pid_t pid, uint32_t *e_flags, bool *is_big_endian)
+{
+ char path[6 /* "/proc/" */ + 11 /* max length of pid */ + 5 /* "/exe\0" */];
+ int fd;
+ uint16_t e_machine = EM_NONE;
+
+ snprintf(path, sizeof(path), "/proc/%d/exe", pid);
+ fd = open(path, O_RDONLY);
+ if (fd >= 0) {
+ e_machine = dso__read_e_machine_endian(/*optional_dso=*/NULL, fd, e_flags,
+ is_big_endian);
+ close(fd);
+ }
+ return e_machine;
+}
+
+struct thread__e_machine_callback_args {
+ struct machine *machine;
+ uint32_t e_flags;
+ uint16_t e_machine;
+ bool is_big_endian;
+};
+
+static int thread__e_machine_callback(struct map *map, void *_args)
+{
+ struct thread__e_machine_callback_args *args = _args;
+ struct dso *dso = map__dso(map);
+
+ if (!dso)
+ return 0; // No dso, continue search.
+
+ args->e_machine =
+ dso__e_machine_endian(dso, args->machine, &args->e_flags, &args->is_big_endian);
+ return args->e_machine != EM_NONE ? 1 /* stop search */ : 0 /* continue search */;
+}
+
+uint16_t thread__e_machine_endian(struct thread *thread, struct machine *machine, uint32_t *e_flags,
+ bool *is_big_endian)
+{
+ pid_t tid, pid;
+ uint16_t e_machine;
+ uint32_t local_e_flags = 0;
+ struct thread__e_machine_callback_args args;
+
+ if (!thread) {
+ if (is_big_endian) {
+ *is_big_endian = perf_arch_is_big_endian(
+ machine && machine->env ? perf_env__arch(machine->env) : NULL);
+ }
+ return perf_env__e_machine(machine ? machine->env : NULL, e_flags);
+ }
+
+ e_machine = RC_CHK_ACCESS(thread)->e_machine;
+ args.machine = machine;
+ args.e_flags = 0;
+ args.e_machine = EM_NONE;
+ args.is_big_endian = false;
+
+ if (e_machine != EM_NONE) {
+ if (e_flags)
+ *e_flags = thread__e_flags(thread);
+ if (is_big_endian)
+ *is_big_endian = thread__e_is_big_endian(thread);
+ return e_machine;
+ }
+
+ if (machine == NULL) {
+ struct maps *maps = thread__maps(thread);
+
+ machine = maps__machine(maps);
+ args.machine = machine;
+ }
+ tid = thread__tid(thread);
+ pid = thread__pid(thread);
+ if (pid != tid) {
+ struct thread *parent = machine__findnew_thread(machine, pid, pid);
+
+ if (parent) {
+ e_machine = thread__e_machine_endian(parent, machine, &local_e_flags,
+ &args.is_big_endian);
+ thread__put(parent);
+ goto out;
+ }
+ /* Something went wrong, fallback. */
+ }
+ /* Reading on the PID thread. First try to find from the maps. */
+ maps__for_each_map(thread__maps(thread), thread__e_machine_callback, &args);
+
+ if (args.e_machine != EM_NONE) {
+ e_machine = args.e_machine;
+ local_e_flags = args.e_flags;
+ } else {
+ /* Maps failed, perhaps we're live with map events disabled. */
+ bool is_live = machine->machines == NULL;
+
+ if (!is_live) {
+ /* Check if the session has a data file. */
+ struct perf_session *session = container_of(machine->machines,
+ struct perf_session,
+ machines);
+
+ is_live = !!session->data;
+ }
+ /* Read from /proc/pid/exe if live. */
+ if (is_live) {
+ e_machine = read_proc_e_machine_for_pid(pid, &local_e_flags,
+ &args.is_big_endian);
+ } else if (machine && machine->env) {
+ /* Offline analysis: fallback to environment metadata. */
+ e_machine = perf_env__e_machine(machine->env, &local_e_flags);
+ args.is_big_endian = perf_arch_is_big_endian(perf_env__arch(machine->env));
+ }
+ }
+out:
+ if (e_machine != EM_NONE) {
+ thread__set_e_flags(thread, local_e_flags);
+ thread__set_e_is_big_endian(thread, args.is_big_endian);
+ thread__set_e_machine(thread, e_machine);
+ if (is_big_endian)
+ *is_big_endian = args.is_big_endian;
+ } else {
+ e_machine = EM_HOST;
+ local_e_flags = EF_HOST;
+ if (is_big_endian)
+ *is_big_endian = (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__);
+ }
+ if (e_flags)
+ *e_flags = local_e_flags;
+ return e_machine;
+}
+
+struct thread *thread__main_thread(struct machine *machine, struct thread *thread)
+{
+ if (thread__pid(thread) == thread__tid(thread))
+ return thread__get(thread);
+
+ if (thread__pid(thread) == -1)
+ return NULL;
+
+ return machine__find_thread(machine, thread__pid(thread), thread__pid(thread));
+}
+
+int thread__memcpy(struct thread *thread, struct machine *machine,
+ void *buf, u64 ip, int len, bool *is64bit)
+{
+ u8 cpumode = PERF_RECORD_MISC_USER;
+ struct addr_location al;
+ struct dso *dso;
+ long offset;
+
+ if (machine__kernel_ip(machine, ip))
+ cpumode = PERF_RECORD_MISC_KERNEL;
+
+ addr_location__init(&al);
+ if (!thread__find_map(thread, cpumode, ip, &al)) {
+ addr_location__exit(&al);
+ return -1;
+ }
+
+ dso = map__dso(al.map);
+
+ if (!dso || dso__data(dso)->status == DSO_DATA_STATUS_ERROR || map__load(al.map) < 0) {
+ addr_location__exit(&al);
+ return -1;
+ }
+
+ offset = map__map_ip(al.map, ip);
+ if (is64bit)
+ *is64bit = dso__is_64_bit(dso);
+
+ addr_location__exit(&al);
+
+ return dso__data_read_offset(dso, machine, offset, buf, len);
+}
+
+void thread__free_stitch_list(struct thread *thread)
+{
+ struct lbr_stitch *lbr_stitch = thread__lbr_stitch(thread);
+ struct stitch_list *pos, *tmp;
+
+ if (!lbr_stitch)
+ return;
+
+ list_for_each_entry_safe(pos, tmp, &lbr_stitch->lists, node) {
+ map_symbol__exit(&pos->cursor.ms);
+ list_del_init(&pos->node);
+ free(pos);
+ }
+
+ list_for_each_entry_safe(pos, tmp, &lbr_stitch->free_lists, node) {
+ list_del_init(&pos->node);
+ free(pos);
+ }
+
+ for (unsigned int i = 0 ; i < lbr_stitch->prev_lbr_cursor_size; i++)
+ map_symbol__exit(&lbr_stitch->prev_lbr_cursor[i].ms);
+
+ zfree(&lbr_stitch->prev_lbr_cursor);
+ free(thread__lbr_stitch(thread));
+ thread__set_lbr_stitch(thread, NULL);
+}