mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
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:
parent
a3a7a6c860
commit
8315564c33
@ -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
6
lib/stackdesc/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "stackdesc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
108
lib/stackdesc/src/lib.rs
Normal file
108
lib/stackdesc/src/lib.rs
Normal 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)*));
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user