Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow getting task dump backtraces in a programmatic format #6975

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
450ms
50ms
8MB
ABI
adaptor
adaptors
Adaptors
Expand All @@ -44,7 +45,9 @@ awaitable
backend
backpressure
backtrace
BacktraceFrame
backtraces
BacktraceSymbol
backtracing
binded
bitfield
Expand Down Expand Up @@ -75,7 +78,9 @@ datagrams
deallocate
deallocated
Deallocates
debuginfo
decrementing
demangled
dequeued
deregister
deregistered
Expand Down Expand Up @@ -133,6 +138,7 @@ implementers
implementor
implementors
incrementing
inlining
interoperate
invariants
Invariants
Expand Down
155 changes: 154 additions & 1 deletion tokio/src/runtime/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -30,14 +30,167 @@ 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<Vec<u8>>,
name_demangled: Option<String>,
addr: Option<*mut std::ffi::c_void>,
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)),
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()
}
Comment on lines +62 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need both? I think users can construct demangled one by using name_raw.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just had it since backtrace had that, and for some reason I thought the API was somewhat private. Apparently rustc_demangle is a perfectly fine public API.

Copy link
Member

@mox692 mox692 Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, Never mind ... I missed you were using the Display impl of SymbolName to get a demangle name in 991434a. (I left comment above just because it looked like similar two fields were being added for the same already demangled data.)

To be clear, I think it's fine (or even better) to provide a name API that returns the demangled name, as the backtrace crate already does. (If users request a mangle API, which is probably unlikely, we can add it later.)

Copy link
Author

@arielb1 arielb1 Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So would you rather have the PR this way or with the name_raw and name_demangled functions?

I definitely don't want to have a version of the code that exposes only the demangled name, since AFAICT if it's a C symbol rather than a Rust symbol the demangling is lossy.


/// 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<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 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<BacktraceSymbol>,
}

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<Item = &BacktraceSymbol> {
self.symbols.iter()
}
}

/// An execution trace of a task's last poll.
///
/// <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 (one [`Vec<BacktraceFrame>`]) 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<Vec<BacktraceFrame>> {
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()
mox692 marked this conversation as resolved.
Show resolved Hide resolved
})
.collect()
}
}

impl Dump {
pub(crate) fn new(tasks: Vec<Task>) -> Self {
Self {
Expand Down
4 changes: 4 additions & 0 deletions tokio/src/runtime/task/trace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading