All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay
@ 2026-10-26  6:50 Ian Rogers
  2026-10-26  6:50 ` [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Ian Rogers @ 2026-10-26  6:50 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Alireza Haghdoost,
	Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Jiri Olsa, Adrian Hunter,
	James Clark, Alessio Podda, Michael Liang, Shimin Guo,
	Tanushree Shah, linux-perf-users, linux-kernel

Three standalone fixes found while profiling `perf top` and DSO symbol/DWARF
resolution:

1. **perf libdw: Optimize fallback logic for missing DWARF**:
   `libdw__addr2line()` previously returned `-1` when `dwfl_module_getdwarf()`
   or `dwfl_module_getsrc()` returned `NULL`, while `addr2line()` in
   `srcline.c` only stopped its fallback loop on `ret > 0`. This caused
   `perf` to fall back through `llvm`, `libbfd`, and the external
   `addr2line` subprocess on every stripped DSO or unmapped address, and
   kept the `Dwfl` ELF mmap open on stripped binaries. Standardize backend
   return values so `0` denotes conclusively missing DWARF/line info
   (stopping the fallback loop and freeing the `Dwfl` session when no
   DWARF exists), `< 0` denotes backend/initialization error, and `> 0`
   denotes resolved source line info.
   An earlier version of this patch was posted at:
   https://lore.kernel.org/linux-perf-users/20260824062841.1529489-3-irogers@google.com/
   This version addresses the review feedback from that thread.

2. **perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n**:
   `pr_debugN()` unconditionally evaluated its arguments and called
   `eprintf()` -> `veprintf()` before checking `verbose >= n`, accounting
   for ~15% of total CPU time in `perf top` during ELF symbol loading even
   at default `verbose == 0`. Guard `eprintf()` with `unlikely(verbose >= (n))`.

3. **perf hist: Do not delete cumulative caller entries before stat_acc decays**:
   When `symbol_conf.cumulate_callchain` is enabled (`perf top -g`),
   callchain caller entries with `he->stat.period == 0` were deleted on the
   first `hists__decay_entries()` pass even while `he->stat_acc->period > 0`.
   Require `he->stat_acc->period == 0` before deleting entries in
   cumulative mode.

Assisted-by: Antigravity:gemini-3.1-pro

Ian Rogers (3):
  perf libdw: Optimize fallback logic for missing DWARF
  perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n
  perf hist: Do not delete cumulative caller entries before stat_acc
    decays

 tools/perf/tests/pmu-events.c      |  5 ++-
 tools/perf/util/addr2line.c        | 28 ++++++++++---
 tools/perf/util/debug.h            |  5 +--
 tools/perf/util/dso.h              |  2 +-
 tools/perf/util/hist.c             |  6 ++-
 tools/perf/util/libbfd.c           |  6 +--
 tools/perf/util/libdw.c            | 25 ++++++++---
 tools/perf/util/libdw.h            |  5 ++-
 tools/perf/util/llvm-c-helpers.cpp | 66 +++++++++++++++++++-----------
 tools/perf/util/llvm.c             | 12 ++++--
 tools/perf/util/srcline.c          | 21 +++++++---
 11 files changed, 125 insertions(+), 56 deletions(-)

-- 
2.56.0.307.g5fe7bd680c-goog


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF
  2026-10-26  6:50 [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Ian Rogers
@ 2026-10-26  6:50 ` Ian Rogers
  2026-10-26  9:31   ` sashiko-bot
  2026-10-26  6:50 ` [PATCH 2/3] perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n Ian Rogers
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Ian Rogers @ 2026-10-26  6:50 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Alireza Haghdoost,
	Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Jiri Olsa, Adrian Hunter,
	James Clark, Alessio Podda, Michael Liang, Shimin Guo,
	Tanushree Shah, linux-perf-users, linux-kernel

When libdw encounters a DWARF definition it cannot accurately un-inline
or an address block natively missing structural line maps,
dwfl_module_getsrc() returns NULL. Previously, libdw__addr2line()
returned -1 in both dwfl_module_getdwarf() and dwfl_module_getsrc()
failure paths while addr2line() in srcline.c only stopped its fallback
loop on ret > 0. This caused perf's addr2line_style loop to fall back to
llvm, libbfd, and the cmd__addr2line() subprocess wrapper on every
unmapped address or stripped DSO.

Update the addr2line_style pipeline return semantics consistently:
1. Backends return 0 to denote conclusively missing information
   (e.g. valid ELF but no DWARF inside, or parsed DWARF with no mapped
   lines), stopping the fallback loop.
2. Backends (and uncompiled stubs) return < 0 to denote initialization
   or backend errors, instructing the loop to iterate to the next
   configured fallback.
3. Backends return > 0 on successful source line resolution.

Fixes: bf96e296d52d ("perf libdw: Fix Dwfl discovery with split files")
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/addr2line.c        | 28 ++++++++++---
 tools/perf/util/dso.h              |  2 +-
 tools/perf/util/libbfd.c           |  6 +--
 tools/perf/util/libdw.c            | 25 ++++++++---
 tools/perf/util/libdw.h            |  5 ++-
 tools/perf/util/llvm-c-helpers.cpp | 66 +++++++++++++++++++-----------
 tools/perf/util/llvm.c             | 12 ++++--
 tools/perf/util/srcline.c          | 21 +++++++---
 8 files changed, 115 insertions(+), 50 deletions(-)

diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c
index 6f64f6b42442..960dd48701bb 100644
--- a/tools/perf/util/addr2line.c
+++ b/tools/perf/util/addr2line.c
@@ -298,6 +298,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	struct io io = { .eof = false };
 	enum cmd_a2l_style cmd_a2l_style;
 	const char *current_dso_name;
+	bool a2l_synced = false;
 
 	mutex_lock(dso__lock(dso));
 	current_dso_name = dso__symsrc_filename(dso) ?: dso_name;
@@ -316,11 +317,14 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	if (a2l == NULL) {
 		if (!symbol_conf.addr2line_disable_warn)
 			pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name);
+		ret = -1;
 		goto out_unlock;
 	}
 	cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name);
-	if (cmd_a2l_style == BROKEN)
+	if (cmd_a2l_style == BROKEN) {
+		ret = -1;
 		goto out_unlock;
+	}
 
 	/*
 	 * Take ownership of the a2l subprocess so we can safely perform
@@ -344,6 +348,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	if (written != len) {
 		if (!symbol_conf.addr2line_disable_warn)
 			pr_warning("%s %s: could not send request\n", __func__, dso_name);
+		ret = -1;
 		goto out;
 	}
 	io__init(&io, a2l->out, buf, sizeof(buf));
@@ -353,6 +358,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	case -1:
 		if (!symbol_conf.addr2line_disable_warn)
 			pr_warning("%s %s: could not read first record\n", __func__, dso_name);
+		ret = -1;
 		goto out;
 	case 0:
 		/*
@@ -373,6 +379,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 			break;
 		case 0:
 			/* The sentinel as expected. */
+			a2l_synced = true;
 			break;
 		default:
 			if (!symbol_conf.addr2line_disable_warn)
@@ -388,17 +395,23 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 
 	if (file) {
 		*file = strdup(record_filename);
-		ret = 1;
+		if (!*file) {
+			ret = -ENOMEM;
+			goto out;
+		}
 	}
 	if (line_nr)
 		*line_nr = record_line_nr;
+	ret = 1;
 
 	if (unwind_inlines) {
 		if (node && inline_list__append_record(dso, node, sym,
 						       record_function,
 						       record_filename,
 						       record_line_nr)) {
-			ret = 0;
+			if (file && *file)
+				zfree(file);
+			ret = -ENOMEM;
 			goto out;
 		}
 	}
@@ -421,12 +434,17 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 						       record_function,
 						       record_filename,
 						       record_line_nr)) {
-				ret = 0;
+				if (file && *file)
+					zfree(file);
+				inline_node__clear_frames(node);
+				ret = -ENOMEM;
 				goto out;
 			}
 			ret = 1; /* found at least one inline frame */
 		}
 	}
+	if (record_status == 0)
+		a2l_synced = true;
 
 out:
 	mutex_lock(dso__lock(dso));
@@ -434,7 +452,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	free(record_filename);
 
 	current_dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso);
-	if (!io.eof && dso__a2l(dso) == NULL && current_dso_name &&
+	if (a2l_synced && !io.eof && dso__a2l(dso) == NULL && current_dso_name &&
 	    !strcmp(current_dso_name, dso_name))
 		dso__set_a2l(dso, a2l);
 	else
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
index 3329f417810c..d5f7382d4e13 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -760,7 +760,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso)
 	return RC_CHK_ACCESS(dso)->symsrc_filename;
 }
 
-void dso__set_symsrc_filename(struct dso *dso, char *val);
+void dso__set_symsrc_filename(struct dso *dso, char *val) EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso));
 
 static inline void dso__free_symsrc_filename(struct dso *dso)
 {
diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c
index a4dcb406c79e..992e2c2a7198 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -288,7 +288,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 
 		if (node && inline_list__append_dso_a2l(dso, node, sym)) {
 			inline_node__clear_frames(node);
-			ret = 0;
+			ret = -ENOMEM;
 			goto out;
 		}
 
@@ -302,7 +302,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 			if (node != NULL) {
 				if (inline_list__append_dso_a2l(dso, node, sym)) {
 					inline_node__clear_frames(node);
-					ret = 0;
+					ret = -ENOMEM;
 					goto out;
 				}
 			}
@@ -312,7 +312,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 	if (file) {
 		*file = a2l->filename ? strdup(a2l->filename) : NULL;
 		if (!*file) {
-			/* Leave ret as 0 so that another addr2line is tried. */
+			ret = a2l->filename ? -ENOMEM : 0;
 			goto out;
 		}
 	}
diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index c4504cceb313..e660623338d5 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -7,6 +7,7 @@
 #include "callchain.h"
 #include <fcntl.h>
 #include <unistd.h>
+#include <string.h>
 #include <elfutils/libdwfl.h>
 
 static const Dwfl_Callbacks offline_callbacks = {
@@ -175,10 +176,15 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 	int lineno = 0;
 	int ret = 0;
 
+	if (!dso__has_srcline(dso))
+		return 0;
+
 	mutex_lock(dso__lock(dso));
 	dwfl = dso__libdw_dwfl(dso);
-	if (!dwfl)
+	if (!dwfl) {
+		ret = -1;
 		goto out;
+	}
 
 	mod = dwfl_addrmodule(dwfl, addr);
 	if (!mod) {
@@ -192,14 +198,23 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 	 * libdw.
 	 */
 	if (!dwfl_module_getdwarf(mod, &bias)) {
-		ret = -1;
+		/*
+		 * Free the Dwfl session so the mmap'd ELF binary is unmapped
+		 * immediately, and mark the DSO as lacking srcline/DWARF info
+		 * so neither libdw nor slower fallback backends (addr2line
+		 * subprocess, libbfd) are repeatedly invoked on stripped DSOs.
+		 */
+		dso__free_libdw(dso);
+		dso__set_has_srcline(dso, false);
+		ret = 0;
 		goto out;
 	}
 
 	/* Find source line information for the address. */
 	dwline = dwfl_module_getsrc(mod, addr + bias);
 	if (!dwline) {
-		ret = -1;
+		/* conclusively missing information -> abort fallbacks */
+		ret = 0;
 		goto out;
 	}
 
@@ -228,7 +243,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 				free(*file);
 				*file = NULL;
 			}
-			ret = 0;
+			ret = -ENOMEM;
 			goto out;
 		}
 
@@ -245,7 +260,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 				*file = NULL;
 			}
 			inline_node__clear_frames(node);
-			ret = 0;
+			ret = args.err;
 			goto out;
 		}
 	}
diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h
index b12094737415..c8f319ce35df 100644
--- a/tools/perf/util/libdw.h
+++ b/tools/perf/util/libdw.h
@@ -23,7 +23,8 @@ struct symbol;
  * finds the source line information for the given address, and optionally
  * resolves inline function call chains.
  *
- * Returns 1 on success (found), 0 on failure (not found).
+ * Returns 1 on success (found), 0 when DWARF/line info is conclusively
+ * missing, and < 0 on initialization or uncompiled error.
  */
 int libdw__addr2line(u64 addr, char **file,
 		     unsigned int *line_nr, struct dso *dso,
@@ -47,7 +48,7 @@ static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_
 				   struct inline_node *node __maybe_unused,
 				   struct symbol *sym __maybe_unused)
 {
-	return 0;
+	return -1;
 }
 
 static inline void dso__free_libdw(struct dso *dso __maybe_unused)
diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp
index 004081bd12c9..d990166100f0 100644
--- a/tools/perf/util/llvm-c-helpers.cpp
+++ b/tools/perf/util/llvm-c-helpers.cpp
@@ -50,20 +50,20 @@ static LLVMSymbolizer *get_symbolizer()
 	return instance;
 }
 
-/* Returns 0 on error, 1 on success. */
+/* Returns < 0 on error, 0 when line info is missing, 1 on success. */
 static int extract_file_and_line(const DILineInfo &line_info, char **file,
 				 unsigned int *line)
 {
-	if (file) {
-		if (line_info.FileName == "<invalid>") {
-			/* Match the convention of libbfd. */
+	if (line_info.FileName == "<invalid>") {
+		if (file)
 			*file = nullptr;
-		} else {
-			/* The caller expects to get something it can free(). */
-			*file = strdup(line_info.FileName.c_str());
-			if (*file == nullptr)
-				return 0;
-		}
+		return 0;
+	}
+	if (file) {
+		/* The caller expects to get something it can free(). */
+		*file = strdup(line_info.FileName.c_str());
+		if (*file == nullptr)
+			return -ENOMEM;
 	}
 	if (line)
 		*line = line_info.Line;
@@ -86,30 +86,42 @@ int llvm_addr2line(const char *dso_name, u64 addr,
 		Expected<DIInliningInfo> res_or_err =
 			symbolizer->symbolizeInlinedCode(dso_name,
 							 sectioned_addr);
-		if (!res_or_err)
-			return 0;
+		if (!res_or_err) {
+			consumeError(res_or_err.takeError());
+			return -1;
+		}
 		unsigned num_frames = res_or_err->getNumberOfFrames();
 		if (num_frames == 0)
 			return 0;
 
-		if (extract_file_and_line(res_or_err->getFrame(0),
-					  file, line) == 0)
-			return 0;
+		int ret = extract_file_and_line(res_or_err->getFrame(0),
+						file, line);
+		if (ret <= 0)
+			return ret;
 
 		*inline_frames = (llvm_a2l_frame *)calloc(
 			num_frames, sizeof(**inline_frames));
-		if (*inline_frames == nullptr)
-			return 0;
+		if (*inline_frames == nullptr) {
+			if (file)
+				zfree(file);
+			return -ENOMEM;
+		}
 
 		for (unsigned i = 0; i < num_frames; ++i) {
 			const DILineInfo &src = res_or_err->getFrame(i);
 
 			llvm_a2l_frame &dst = (*inline_frames)[i];
-			if (src.FileName == "<invalid>")
-				/* Match the convention of libbfd. */
-				dst.filename = nullptr;
-			else
-				dst.filename = strdup(src.FileName.c_str());
+			if (src.FileName == "<invalid>") {
+				for (unsigned j = 0; j < i; ++j) {
+					zfree(&(*inline_frames)[j].filename);
+					zfree(&(*inline_frames)[j].funcname);
+				}
+				zfree(inline_frames);
+				if (file)
+					zfree(file);
+				return 0;
+			}
+			dst.filename = strdup(src.FileName.c_str());
 			dst.funcname = strdup(src.FunctionName.c_str());
 			dst.line = src.Line;
 
@@ -120,7 +132,9 @@ int llvm_addr2line(const char *dso_name, u64 addr,
 					zfree(&(*inline_frames)[j].funcname);
 				}
 				zfree(inline_frames);
-				return 0;
+				if (file)
+					zfree(file);
+				return -ENOMEM;
 			}
 		}
 
@@ -131,8 +145,10 @@ int llvm_addr2line(const char *dso_name, u64 addr,
 
 		Expected<DILineInfo> res_or_err =
 			symbolizer->symbolizeCode(dso_name, sectioned_addr);
-		if (!res_or_err)
-			return 0;
+		if (!res_or_err) {
+			consumeError(res_or_err.takeError());
+			return -1;
+		}
 		return extract_file_and_line(*res_or_err, file, line);
 	}
 }
diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c
index 722e71aab100..0ecb1a53359e 100644
--- a/tools/perf/util/llvm.c
+++ b/tools/perf/util/llvm.c
@@ -44,8 +44,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused
 	int num_frames = llvm_addr2line(dso_name, addr, file, line,
 					node && unwind_inlines, &inline_frames);
 
-	if (num_frames == 0 || !inline_frames) {
-		/* Error, or we didn't want inlines. */
+	if (num_frames <= 0 || !inline_frames) {
+		/*
+		 * Error (< 0 -> iterate fallbacks), missing line info (0), or
+		 * we resolved lines without wanting inlines (1).
+		 */
 		return num_frames;
 	}
 
@@ -60,8 +63,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused
 						      inline_frames[i].line);
 		}
 		if (inline_list__append(inline_sym, srcline, node) != 0) {
+			if (file)
+				zfree(file);
+			inline_node__clear_frames(node);
 			free_llvm_inline_frames(inline_frames, num_frames);
-			return 0;
+			return -1;
 		}
 	}
 	free_llvm_inline_frames(inline_frames, num_frames);
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index a8d16106f619..23951c35fb66 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -181,9 +181,9 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int *
 			break;
 		case A2L_STYLE_UNKNOWN:
 		default:
-			break;
+			return 0;
 		}
-		if (ret > 0)
+		if (ret >= 0)
 			return ret;
 	}
 
@@ -250,6 +250,10 @@ static struct inline_node *addr2inlines(const char *dso_name, u64 addr,
 
 	addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso,
 		  /*unwind_inlines=*/true, node, sym);
+	if (!dso__has_srcline(dso) && list_empty(&node->val)) {
+		inline_node__delete(node);
+		return NULL;
+	}
 
 	return node;
 }
@@ -276,8 +280,8 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 	if (dso_name == NULL)
 		goto out_err;
 
-	if (!addr2line(dso_name, addr, &file, &line, dso,
-		       unwind_inlines, /*node=*/NULL, sym)) {
+	if (addr2line(dso_name, addr, &file, &line, dso,
+		       unwind_inlines, /*node=*/NULL, sym) <= 0) {
 		free(dso_name);
 		goto out_err;
 	}
@@ -302,6 +306,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
 		dso__free_a2l_libbfd(dso);
+		dso__free_libdw(dso);
 	}
 	mutex_unlock(dso__lock(dso));
 out:
@@ -331,8 +336,8 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 	if (dso_name == NULL)
 		goto out_err;
 
-	if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true,
-			/*node=*/NULL, /*sym=*/NULL)) {
+	if (addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true,
+			/*node=*/NULL, /*sym=*/NULL) <= 0) {
 		free(dso_name);
 		goto out_err;
 	}
@@ -350,6 +355,7 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
 		dso__free_a2l_libbfd(dso);
+		dso__free_libdw(dso);
 	}
 	mutex_unlock(dso__lock(dso));
 
@@ -448,6 +454,9 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 	char *dso_name;
 	struct inline_node *node;
 
+	if (!dso__has_srcline(dso))
+		return NULL;
+
 	dso_name = srcline_dso_name(dso);
 	if (dso_name == NULL)
 		return NULL;
-- 
2.56.0.307.g5fe7bd680c-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/3] perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n
  2026-10-26  6:50 [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Ian Rogers
  2026-10-26  6:50 ` [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers
@ 2026-10-26  6:50 ` Ian Rogers
  2026-10-26  9:35   ` sashiko-bot
  2026-10-26  6:50 ` [PATCH 3/3] perf hist: Do not delete cumulative caller entries before stat_acc decays Ian Rogers
  2026-10-27 20:42 ` [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Arnaldo Carvalho de Melo
  3 siblings, 1 reply; 8+ messages in thread
From: Ian Rogers @ 2026-10-26  6:50 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Alireza Haghdoost,
	Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Jiri Olsa, Adrian Hunter,
	James Clark, Alessio Podda, Michael Liang, Shimin Guo,
	Tanushree Shah, linux-perf-users, linux-kernel

pr_debugN() unconditionally evaluated its arguments and called eprintf() ->
veprintf() before checking if verbose >= n. In hot paths such as
dso__load_sym_internal(), two pr_debug4() calls per symbol accounted for
~15% of total CPU time in 'perf top' even at the default verbose == 0.

Guard eprintf() in pr_debugN() with unlikely(verbose >= (n)) and define
pr_debug() in terms of pr_debugN(1, ...).

In test__parsing_callback() (tools/perf/tests/pmu-events.c), move the
test_generic_metric(mexp, 0) call out of the pr_debug() argument list so
the metric evaluation continues to run during 'perf test' when verbose == 0.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/pmu-events.c | 5 ++++-
 tools/perf/util/debug.h       | 5 ++---
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index f507ce39439a..e6e4f4cf1fa2 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -906,9 +906,12 @@ static int test__parsing_callback(const struct pmu_metric *pm,
 			struct metric_expr *mexp;
 
 			list_for_each_entry (mexp, &me->head, nd) {
+				double result;
+
 				if (strcmp(mexp->metric_name, pm->metric_name))
 					continue;
-				pr_debug("Result %f\n", test_generic_metric(mexp, 0));
+				result = test_generic_metric(mexp, 0);
+				pr_debug("Result %f\n", result);
 				err = 0;
 				(*failures)--;
 				goto out_err;
diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h
index 6b737e195ce1..7552c9171d90 100644
--- a/tools/perf/util/debug.h
+++ b/tools/perf/util/debug.h
@@ -33,10 +33,9 @@ extern int debug_type_profile;
 })
 #define pr_info(fmt, ...) \
 	eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__)
-#define pr_debug(fmt, ...) \
-	eprintf(1, verbose, pr_fmt(fmt), ##__VA_ARGS__)
 #define pr_debugN(n, fmt, ...) \
-	eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__)
+	(unlikely(verbose >= (n)) ? eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) : 0)
+#define pr_debug(fmt, ...) pr_debugN(1, pr_fmt(fmt), ##__VA_ARGS__)
 #define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__)
 #define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__)
 #define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__)
-- 
2.56.0.307.g5fe7bd680c-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/3] perf hist: Do not delete cumulative caller entries before stat_acc decays
  2026-10-26  6:50 [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Ian Rogers
  2026-10-26  6:50 ` [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers
  2026-10-26  6:50 ` [PATCH 2/3] perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n Ian Rogers
@ 2026-10-26  6:50 ` Ian Rogers
  2026-10-26  9:41   ` sashiko-bot
  2026-10-27 20:42 ` [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Arnaldo Carvalho de Melo
  3 siblings, 1 reply; 8+ messages in thread
From: Ian Rogers @ 2026-10-26  6:50 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Alireza Haghdoost,
	Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Jiri Olsa, Adrian Hunter,
	James Clark, Alessio Podda, Michael Liang, Shimin Guo,
	Tanushree Shah, linux-perf-users, linux-kernel

When symbol_conf.cumulate_callchain is enabled (the default in
'perf top -g'), callchain parent/caller entries have he->stat.period == 0
and accumulate their period in he->stat_acc->period.

hists__decay_entry() decays both he->stat.period and he->stat_acc->period,
but only checks if he->stat.period == 0 as the return condition for
deleting the entry in hists__decay_entries(). As a result, every callchain
caller entry with 0 self-period is unconditionally deleted from the
histogram on the first decay pass, destroying its callchain tree while it
still has non-zero cumulative period.

Only mark an entry for deletion in hists__decay_entry() when both
he->stat.period == 0 and (when cumulate_callchain is active)
he->stat_acc->period == 0.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/hist.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c
index 443694926f1a..e75a4fcce975 100644
--- a/tools/perf/util/hist.c
+++ b/tools/perf/util/hist.c
@@ -409,7 +409,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he)
 	u64 prev_period = he->stat.period;
 	u64 prev_latency = he->stat.latency;
 
-	if (prev_period == 0)
+	if (prev_period == 0 &&
+	    (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0))
 		return true;
 
 	he_stat__decay(&he->stat);
@@ -442,7 +443,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he)
 		}
 	}
 
-	return he->stat.period == 0 && he->stat.latency == 0;
+	return he->stat.period == 0 && he->stat.latency == 0 &&
+	       (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0);
 }
 
 static void hists__delete_entry(struct hists *hists, struct hist_entry *he)
-- 
2.56.0.307.g5fe7bd680c-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF
  2026-10-26  6:50 ` [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers
@ 2026-10-26  9:31   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-26  9:31 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> When libdw encounters a DWARF definition it cannot accurately un-inline
> or an address block natively missing structural line maps,
> dwfl_module_getsrc() returns NULL. Previously, libdw__addr2line()
> returned -1 in both dwfl_module_getdwarf() and dwfl_module_getsrc()
> failure paths while addr2line() in srcline.c only stopped its fallback
> loop on ret > 0. This caused perf's addr2line_style loop to fall back to
> llvm, libbfd, and the cmd__addr2line() subprocess wrapper on every
> unmapped address or stripped DSO.
> 
> Update the addr2line_style pipeline return semantics consistently:
> 1. Backends return 0 to denote conclusively missing information
>    (e.g. valid ELF but no DWARF inside, or parsed DWARF with no mapped
>    lines), stopping the fallback loop.
> 2. Backends (and uncompiled stubs) return < 0 to denote initialization
>    or backend errors, instructing the loop to iterate to the next
>    configured fallback.
> 3. Backends return > 0 on successful source line resolution.
> 
> Fixes: bf96e296d52d ("perf libdw: Fix Dwfl discovery with split files")
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792995775.git.irogers@google.com?part=1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/3] perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n
  2026-10-26  6:50 ` [PATCH 2/3] perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n Ian Rogers
@ 2026-10-26  9:35   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-26  9:35 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> pr_debugN() unconditionally evaluated its arguments and called eprintf() ->
> veprintf() before checking if verbose >= n. In hot paths such as
> dso__load_sym_internal(), two pr_debug4() calls per symbol accounted for
> ~15% of total CPU time in 'perf top' even at the default verbose == 0.
> 
> Guard eprintf() in pr_debugN() with unlikely(verbose >= (n)) and define
> pr_debug() in terms of pr_debugN(1, ...).
> 
> In test__parsing_callback() (tools/perf/tests/pmu-events.c), move the
> test_generic_metric(mexp, 0) call out of the pr_debug() argument list so
> the metric evaluation continues to run during 'perf test' when verbose == 0.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792995775.git.irogers@google.com?part=2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 3/3] perf hist: Do not delete cumulative caller entries before stat_acc decays
  2026-10-26  6:50 ` [PATCH 3/3] perf hist: Do not delete cumulative caller entries before stat_acc decays Ian Rogers
@ 2026-10-26  9:41   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-26  9:41 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> When symbol_conf.cumulate_callchain is enabled (the default in
> 'perf top -g'), callchain parent/caller entries have he->stat.period == 0
> and accumulate their period in he->stat_acc->period.
> 
> hists__decay_entry() decays both he->stat.period and he->stat_acc->period,
> but only checks if he->stat.period == 0 as the return condition for
> deleting the entry in hists__decay_entries(). As a result, every callchain
> caller entry with 0 self-period is unconditionally deleted from the
> histogram on the first decay pass, destroying its callchain tree while it
> still has non-zero cumulative period.
> 
> Only mark an entry for deletion in hists__decay_entry() when both
> he->stat.period == 0 and (when cumulate_callchain is active)
> he->stat_acc->period == 0.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792995775.git.irogers@google.com?part=3


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay
  2026-10-26  6:50 [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Ian Rogers
                   ` (2 preceding siblings ...)
  2026-10-26  6:50 ` [PATCH 3/3] perf hist: Do not delete cumulative caller entries before stat_acc decays Ian Rogers
@ 2026-10-27 20:42 ` Arnaldo Carvalho de Melo
  3 siblings, 0 replies; 8+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-27 20:42 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Namhyung Kim, Alireza Haghdoost, Peter Zijlstra, Ingo Molnar,
	Jiri Olsa, Adrian Hunter, James Clark, Alessio Podda,
	Michael Liang, Shimin Guo, Tanushree Shah, linux-perf-users,
	linux-kernel

On Sun, Oct 25, 2026 at 11:50:42PM -0700, Ian Rogers wrote:
> Three standalone fixes found while profiling `perf top` and DSO symbol/DWARF
> resolution:

Thanks, applied for v7.4.

- Arnaldo

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-10-27 20:42 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26  6:50 [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Ian Rogers
2026-10-26  6:50 ` [PATCH 1/3] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers
2026-10-26  9:31   ` sashiko-bot
2026-10-26  6:50 ` [PATCH 2/3] perf debug: Avoid calling eprintf() in pr_debugN() when verbose < n Ian Rogers
2026-10-26  9:35   ` sashiko-bot
2026-10-26  6:50 ` [PATCH 3/3] perf hist: Do not delete cumulative caller entries before stat_acc decays Ian Rogers
2026-10-26  9:41   ` sashiko-bot
2026-10-27 20:42 ` [PATCH 0/3] perf: Fix libdw addr2line fallback, pr_debugN overhead, and cumulative hist decay Arnaldo Carvalho de Melo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.