Repository: tokio-rs/tokio (language: rust)

Issue:
Support an API to extract backtrace information from taskdump


**Is your feature request related to a problem? Please describe.**
Currently, the only supported output for taskdump is logging, as shown in the examples [here](https://github.com/tokio-rs/tokio/blob/4846959e8a534cf2aff63f8613e5f69a7d2ce5f8/examples/dump.rs#L50-L54). However, I would like to add a public API that can extract this backtrace information. By adding such an API, it will become possible to handle taskdump data programmatically, making it useful for various purposes.

**Describe the solution you'd like**
I would like to add a public API something like this:

```rust
struct Symbol {
  // one call stack info
}

pub struct TraceTree {
  symbol: Symbol,
  children: Vec<TraceTree>
}

impl Task {
  pub fn trace_tree(&self) -> TraceTree { ... }
}

async fn run {
  let handle = tokio::runtime::Handle::current();
  let dump = handle.dump().await;
  for task in dump.tasks().iter() {
    // get a trace tree per one tokio task.
    let root: TraceTree = task.trace_tree();
		
    // some traverse operations...
  }
}
```

We already have an internal struct named [Tree](https://github.com/tokio-rs/tokio/blob/4846959e8a534cf2aff63f8613e5f69a7d2ce5f8/tokio/src/runtime/task/trace/tree.rs#L11), so I believe it's not too hard to provide such an API.

**Additional context**
related issue: https://github.com/tokio-rs/tokio/issues/5638



Discussion / hints:
cc @jswrenn 
It's not hard to provide an API here, but it might be hard to provide the *right* API. The current `Tree` type is an implementation detail used to aid printing task dumps. It's not currently designed to function well as a public API for traversing the tracing tree. Rust doesn't have a standard interface for tree traversal, so we'll need to decide for ourselves what interface to expose. We'll also need to ensure that this API doesn't lock us into sub-optimal representation decisions.
Thanks for clarifying the context!
Yeah, deciding on the right api seems difficult, but I believe we should eventually reach.
Is there a strong reason we don't just export an API that is of the form:
```rust
pub struct TokioBacktraceFrame {
    name: Option<Vec<u8>>,
    name_demangled: Option<String>,
    addr: Option<*mut c_void>,
    filename: Option<PathBuf>,
    lineno: Option<u32>,
    colno: Option<u32>,
}

impl TokioBacktraceFrame {
    pub fn name_raw(&self) -> Option<&[u8]> { /* .. */ }
    pub fn name_demangled(&self) -> Option<&str> { /* ... */ }
    pub fn addr(&self) -> Option<*mut c_void> { /* ... */ }
    pub fn filename(&self) -> Option<&Path> { /* ... */ }
    pub fn lineno(&self) -> Option<u32> { /* ... */ }
    pub fn colno(&self) -> Option<u32> { /* ... */ }
}

impl Trace {
    pub fn dump(&self) -> Vec<Vec<TokioBacktraceFrame>>;
}
```

I believe that this will work with every form of taking backtraces (especially given that all fields are options). Possibly it will require some inefficiency, but we can always expose a more efficient API later.
Tokio does not and will not depend on serde.
K, so without the derive(Serialize) but with exposing accessors. People will have to do another map if they want to turn it to a JSON which will have some negative performance implications, but I think this is acceptable.
We can probably expose some accessors.
So are you positive for a function returning a `Vec<Vec<TokioBacktraceFrame>>` for a `TokioBacktraceFrame` that is supposed to be a plain old data type with accessors?
Yes, we can add it.

Fix patch:
diff --git a/spellcheck.dic b/spellcheck.dic
index f368d2d7214..ad73ea17bdc 100644
--- a/spellcheck.dic
+++ b/spellcheck.dic
@@ -1,4 +1,4 @@
-292
+298
 &
 +
 <
@@ -30,6 +30,8 @@
 450ms
 50ms
 8MB
+ABI
+accessors
 adaptor
 adaptors
 Adaptors
@@ -75,8 +77,11 @@ datagrams
 deallocate
 deallocated
 Deallocates
+debuginfo
 decrementing
+demangled
 dequeued
+dereferenced
 deregister
 deregistered
 deregistering
@@ -133,6 +138,7 @@ implementers
 implementor
 implementors
 incrementing
+inlining
 interoperate
 invariants
 Invariants
@@ -291,4 +297,3 @@ Wakers
 wakeup
 wakeups
 workstealing
-
diff --git a/tokio/src/runtime/dump.rs b/tokio/src/runtime/dump.rs
index aea2381127b..d262f3987cc 100644
--- a/tokio/src/runtime/dump.rs
+++ b/tokio/src/runtime/dump.rs
@@ -1,13 +1,13 @@
 //! Snapshots of runtime state.
 //!
-//! See [Handle::dump][crate::runtime::Handle::dump].
+//! 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.
 ///
-/// See [Handle::dump][crate::runtime::Handle::dump].
+/// See [`Handle::dump`][crate::runtime::Handle::dump].
 #[derive(Debug)]
 pub struct Dump {
     tasks: Tasks,
@@ -15,7 +15,7 @@ pub struct Dump {
 
 /// Snapshots of tasks.
 ///
-/// See [Handle::dump][crate::runtime::Handle::dump].
+/// See [`Handle::dump`][crate::runtime::Handle::dump].
 #[derive(Debug)]
 pub struct Tasks {
     tasks: Vec<Task>,
@@ -23,21 +23,199 @@ pub struct Tasks {
 
 /// A snapshot of a task.
 ///
-/// See [Handle::dump][crate::runtime::Handle::dump].
+/// See [`Handle::dump`][crate::runtime::Handle::dump].
 #[derive(Debug)]
 pub struct Task {
     id: Id,
     trace: Trace,
 }
 
+/// Represents an address that should not be dereferenced.
+///
+/// This type exists to get the auto traits correct, the public API
+/// uses raw pointers to make life easier for users.
+#[derive(Copy, Clone, Debug)]
+struct Address(*mut std::ffi::c_void);
+
+// Safe since Address should not be dereferenced
+unsafe impl Send for Address {}
+unsafe impl Sync for Address {}
+
+/// A backtrace symbol.
+///
+/// This struct provides accessors for backtrace symbols, similar to [`backtrace::BacktraceSymbol`].
+#[derive(Clone, Debug)]
+pub struct BacktraceSymbol {
+    name: Option<Box<[u8]>>,
+    name_demangled: Option<Box<str>>,
+    addr: Option<Address>,
+    filename: Option<std::path::PathBuf>,
+    lineno: Option<u32>,
+    colno: Option<u32>,
+}
+
+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).into()),
+            addr: sym.addr().map(Address),
+            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.map(|addr| addr.0)
+    }
+
+    /// 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<u32> {
+        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<u32> {
+        self.colno
+    }
+}
+
+/// A backtrace frame.
+///
+/// This struct represents one stack frame in a captured backtrace, similar to [`backtrace::BacktraceFrame`].
+#[derive(Clone, Debug)]
+pub struct BacktraceFrame {
+    ip: Address,
+    symbol_address: Address,
+    symbols: Box<[BacktraceSymbol]>,
+}
+
+impl BacktraceFrame {
+    pub(crate) fn from_resolved_backtrace_frame(frame: &backtrace::BacktraceFrame) -> Self {
+        Self {
+            ip: Address(frame.ip()),
+            symbol_address: 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.0
+    }
+
+    /// Returns the starting symbol address of the frame of this function.
+    pub fn symbol_address(&self) -> *mut std::ffi::c_void {
+        self.symbol_address.0
+    }
+
+    /// 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<Item = &BacktraceSymbol> {
+        self.symbols.iter()
+    }
+}
+
+/// A captured backtrace.
+///
+/// This struct provides access to each backtrace frame, similar to [`backtrace::Backtrace`].
+#[derive(Clone, Debug)]
+pub struct Backtrace {
+    frames: Box<[BacktraceFrame]>,
+}
+
+impl Backtrace {
+    /// Return the frames in this backtrace, innermost (in a task dump,
+    /// likely to be a leaf future's poll function) first.
+    pub fn frames(&self) -> impl Iterator<Item = &BacktraceFrame> {
+        self.frames.iter()
+    }
+}
+
 /// An execution trace of a task's last poll.
 ///
-/// See [Handle::dump][crate::runtime::Handle::dump].
+/// <div class="warning">
+///
+/// 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.
+/// </div>
+///
+/// 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 trace.
+    ///
+    /// The exact backtraces included here are unstable and might change in the future,
+    /// but you can expect one [`Backtrace`] for every call to
+    /// [`poll`] to a bottom-level Tokio future - so if something like [`join!`] is
+    /// used, there will be a backtrace for each future in the join.
+    ///
+    /// [`poll`]: std::future::Future::poll
+    /// [`join!`]: macro@join
+    pub fn resolve_backtraces(&self) -> Vec<Backtrace> {
+        self.inner
+            .backtraces()
+            .iter()
+            .map(|backtrace| {
+                let mut backtrace = backtrace::Backtrace::from(backtrace.clone());
+                backtrace.resolve();
+                Backtrace {
+                    frames: backtrace
+                        .frames()
+                        .iter()
+                        .map(BacktraceFrame::from_resolved_backtrace_frame)
+                        .collect(),
+                }
+            })
+            .collect()
+    }
+}
+
 impl Dump {
     pub(crate) fn new(tasks: Vec<Task>) -> 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<F>(future: F) -> Root<F> {
         Root { future }
     }
+
+    pub(crate) fn backtraces(&self) -> &[Backtrace] {
+        &self.backtraces
+    }
 }
 
 /// If this is a sub-invocation of [`Trace::capture`], capture a backtrace.


Test patch: