stackdesc: a thin library to annotate the current thread execution

Summary:
Add a thin library to store a side-by-side "stack" that can provide chainned
human-friendly context information. Right now, it only works with the current
native thread. It can be thought of an "annotation" of the native stack.

The concept is useful for both Python and Rust. So this diff adds a Rust
implementation, and a Python binding will be added as follow-up.

This diff only implements the minimal bits to make it useful. Some future
possibilities are:

- Have a way to collect information from all threads. This requires a global
  state and customized thread spawn / join wrappers.
- Have a way to "request" for the "stack description" from another thread.
  This might be useful for progress-bar use-case, where the progress bar
  logic runs in a different thread.

Note: I searched through `crates.io` for something similar. But I couldn't find
any. They are either coupled with error handling that will miss the "explain
why this prefetch happens" case, or are not lazy, which is undesirable as I'd
imagine some context to have complex logic like rendering a DAG graph.

Reviewed By: sfilipco

Differential Revision: D16023308

fbshipit-source-id: 320a23447dea85089ba8ab02436af3ec93466dd8
This commit is contained in:
Jun Wu 2019-08-01 19:48:55 -07:00 committed by Facebook Github Bot
parent a3a7a6c860
commit 8315564c33
3 changed files with 115 additions and 1 deletions

View File

@ -27,7 +27,7 @@ members = [
"procinfo", "procinfo",
"radixbuf", "radixbuf",
"revisionstore", "revisionstore",
"shlex", "stackdesc",
"treestate", "treestate",
"types", "types",
"vlqencoding", "vlqencoding",

6
lib/stackdesc/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "stackdesc"
version = "0.1.0"
edition = "2018"
[dependencies]

108
lib/stackdesc/src/lib.rs Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2019 Facebook, Inc.
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//! `stackdesc` provides utilities to describe what the current thread is
//! doing.
//!
//! Generating description has extra cost. So `stackdesc` API makes them lazy.
//! If nobody asks for the stack description, then the overhead is minimal.
//!
//! Typical usecases are:
//! - Adding more context on error.
//! - Understanding why an operation happens. For example, why a network
//! side-effect was triggered.
//!
//! Example:
//!
//! ```
//! # use stackdesc::{describe, render_stack};
//! # use std::cell::Cell;
//!
//! fn fetch_rev(rev: &str) {
//! describe!("fetch(rev={})", rev);
//! fetch_files(&["a", "b"]);
//! }
//!
//! fn fetch_files(items: &[&str]) {
//! describe!("fetch_files(len={})", items.len());
//!
//! // For testing
//! let rendered = render_stack();
//! assert_eq!(rendered, vec!["fetch(rev=master)", "fetch_files(len=2)"]);
//! }
//!
//! fetch_rev("master");
//! ```
use std::cell::RefCell;
use std::pin::Pin;
/// Contains logic to render description for a scope.
///
/// [`ScopeDescription`] expects first-create-first-drop. The easiest way to
/// achieve that is to use the [`describe!`] macro without using
/// [`ScopeDescription`] directly.
///
/// [`ScopeDescription`] usually matches a stack frame. That is, it is usually
/// put at the top of a function body and describes what that function does.
pub struct ScopeDescription<'a> {
/// A function that returns meaningful description of the current frame.
describe_func: Box<dyn Fn() -> String + 'a>,
}
thread_local! {
static STACK: RefCell<Vec<&'static ScopeDescription<'static>>> = RefCell::new(Vec::new());
}
impl<'a> ScopeDescription<'a> {
pub fn new(describe_func: impl Fn() -> String + 'a) -> Pin<Box<ScopeDescription<'a>>> {
let frame = Box::pin(ScopeDescription {
describe_func: Box::new(describe_func),
});
STACK.with(|stack| {
let mut stack = stack.borrow_mut();
let frame: &ScopeDescription<'a> = &frame;
// This is safe because ScopeDescription::drop removes its reference from the
// thread local state.
let frame: &'static ScopeDescription<'static> = unsafe { std::mem::transmute(frame) };
stack.push(frame)
});
frame
}
pub fn render(&self) -> String {
(self.describe_func)()
}
}
impl<'a> Drop for ScopeDescription<'a> {
fn drop(&mut self) {
STACK.with(|stack| {
let mut stack = stack.borrow_mut();
let frame = stack.pop().unwrap();
assert_eq!(
frame as *const ScopeDescription, self as *const ScopeDescription,
"incorrect use of ScopeDescription: not dropped in order"
);
});
}
}
/// Render descriptions for the current stack.
/// Return strings in this order: outer first, inner last.
pub fn render_stack() -> Vec<String> {
STACK.with(|stack| {
let stack = stack.borrow();
stack.iter().map(|f| f.render()).collect()
})
}
/// A shortcut to `let _desc = ScopeDescription::new(|| format!(...))`.
#[macro_export]
macro_rules! describe {
($($arg:tt)*) => {
let _frame = $crate::ScopeDescription::new(|| format!($($arg)*));
};
}