From df04fc7eac597b98b76f9fe24708b4e31b2fca3b Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Thu, 14 Nov 2024 16:47:07 +0000 Subject: [PATCH] allow getting task dump backtraces in a programmatic format Fixes #6309. --- tokio/src/runtime/dump.rs | 146 +++++++++++++++++++++++++++- tokio/src/runtime/task/trace/mod.rs | 4 + 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/tokio/src/runtime/dump.rs b/tokio/src/runtime/dump.rs index aea2381127b..7bcec99d765 100644 --- a/tokio/src/runtime/dump.rs +++ b/tokio/src/runtime/dump.rs @@ -3,7 +3,7 @@ //! See [Handle::dump][crate::runtime::Handle::dump]. use crate::task::Id; -use std::fmt; +use std::{fmt, path::Path}; /// A snapshot of a runtime's state. /// @@ -30,14 +30,158 @@ pub struct Task { trace: Trace, } +/// A backtrace symbol. This is similar to [backtrace::BacktraceSymbol], +/// but is a separate struct to avoid public dependency issues. +/// +/// This struct is guaranteed to be pure data and operations involving +/// it will not call platform functions that take an unpredictable amount +/// of time to finish. +#[derive(Clone, Debug)] +pub struct BacktraceSymbol { + name: Option>, + name_demangled: Option, + addr: Option<*mut std::ffi::c_void>, + filename: Option, + lineno: Option, + colno: Option, +} + +impl BacktraceSymbol { + pub(crate) fn from_backtrace_symbol(sym: &backtrace::BacktraceSymbol) -> Self { + let name = sym.name(); + Self { + name: name.as_ref().map(|name| name.as_bytes().into()), + name_demangled: name.map(|name| format!("{}", name)), + addr: sym.addr(), + filename: sym.filename().map(From::from), + lineno: sym.lineno(), + colno: sym.colno(), + } + } + + /// Return the raw name of the symbol. + pub fn name_raw(&self) -> Option<&[u8]> { + self.name.as_deref() + } + + /// Return the demangled name of the symbol. + pub fn name_demangled(&self) -> Option<&str> { + self.name_demangled.as_deref() + } + + /// Returns the starting address of this symbol. + pub fn addr(&self) -> Option<*mut std::ffi::c_void> { + self.addr + } + + /// Returns the file name where this function was defined. If debuginfo + /// is missing, this is likely to return None. + pub fn filename(&self) -> Option<&Path> { + self.filename.as_deref() + } + + /// Returns the line number for where this symbol is currently executing If debuginfo + /// is missing, this is likely to return None. + pub fn lineno(&self) -> Option { + self.lineno + } + + /// Returns the column number for where this symbol is currently executing If debuginfo + /// is missing, this is likely to return None. + pub fn colno(&self) -> Option { + self.colno + } +} + +/// A backtrace frame. This is similar to [backtrace::BacktraceFrame], +/// but is a separate struct to avoid public dependency issues. +/// +/// This struct is guaranteed to be pure data and operations involving +/// it will not call platform functions that take an unpredictable amount +/// of time to finish. +#[derive(Clone, Debug)] +pub struct BacktraceFrame { + ip: *mut std::ffi::c_void, + symbol_address: *mut std::ffi::c_void, + symbols: Vec, +} + +impl BacktraceFrame { + pub(crate) fn from_resolved_backtrace_frame(frame: &backtrace::BacktraceFrame) -> Self { + Self { + ip: frame.ip(), + symbol_address: frame.symbol_address(), + symbols: frame + .symbols() + .iter() + .map(BacktraceSymbol::from_backtrace_symbol) + .collect(), + } + } + + /// Return the instruction pointer of this frame. + /// + /// See the ABI docs for your platform for the exact meaning. + pub fn ip(&self) -> *mut std::ffi::c_void { + self.ip + } + + /// Returns the starting symbol address of the frame of this function. + pub fn symbol_address(&self) -> *mut std::ffi::c_void { + self.symbol_address + } + + /// Return an iterator over the symbols of this backtrace frame. + /// + /// Due to inlining, it is possible for there to be multiple [BacktraceSymbol] items relating + /// to a single frame. The first symbol listed is the "innermost function", + /// whereas the last symbol is the outermost (last caller). + pub fn symbols(&self) -> impl Iterator { + self.symbols.iter() + } +} + /// An execution trace of a task's last poll. /// +///
+/// Resolving a backtrace, either via the [`Display`][std::fmt::Display] impl or via +/// [`resolve_backtraces`][Trace::resolve_backtraces], parses debuginfo, which is +/// possibly a CPU-expensive operation that can take a platform-specific but +/// long time to run - often over 100 milliseconds, especially if the current +/// process's binary is big. In some cases, the platform might internally cache some of the +/// debuginfo, so successive calls to `resolve_backtraces` might be faster than +/// the first call, but all guarantees are platform-dependent. +/// +/// To avoid blocking the runtime, it is recommended +/// that you resolve backtraces inside of a [spawn_blocking()][crate::task::spawn_blocking] +/// and to have some concurrency-limiting mechanism to avoid unexpected performance impact. +///
+/// /// See [Handle::dump][crate::runtime::Handle::dump]. #[derive(Debug)] pub struct Trace { inner: super::task::trace::Trace, } +impl Trace { + /// Resolve and return a list of backtraces that are involved in polls in this task. + pub fn resolve_backtraces(&self) -> Vec> { + self.inner + .backtraces() + .iter() + .map(|backtrace| { + let mut backtrace = backtrace::Backtrace::from(backtrace.clone()); + backtrace.resolve(); + backtrace + .frames() + .iter() + .map(BacktraceFrame::from_resolved_backtrace_frame) + .collect() + }) + .collect() + } +} + impl Dump { pub(crate) fn new(tasks: Vec) -> Self { Self { diff --git a/tokio/src/runtime/task/trace/mod.rs b/tokio/src/runtime/task/trace/mod.rs index bb411f42d72..851e96a73a1 100644 --- a/tokio/src/runtime/task/trace/mod.rs +++ b/tokio/src/runtime/task/trace/mod.rs @@ -138,6 +138,10 @@ impl Trace { pub(crate) fn root(future: F) -> Root { Root { future } } + + pub(crate) fn backtraces(&self) -> &[Backtrace] { + &self.backtraces + } } /// If this is a sub-invocation of [`Trace::capture`], capture a backtrace.