diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f6f2300..ec6e13b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,6 +33,11 @@ jobs:
- name: Install Protoc
uses: arduino/setup-protoc@v2
+ - name: Install libunwind-dev
+ uses: ConorMacBride/install-package@v1
+ with:
+ apt: libunwind-dev
+
- name: Environment
run: |
cargo --version
@@ -69,6 +74,10 @@ jobs:
- name: Install Protoc
uses: arduino/setup-protoc@v2
+ - name: Install libunwind-dev
+ run: |
+ sudo apt update && sudo apt install -y libunwind-dev
+
- name: Environment
run: |
cargo --version
diff --git a/.gitignore b/.gitignore
index 7d4d3e2..324ecc2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/target
+**/target
**/*.rs.bk
.vs
packages
diff --git a/.idea/proxide.iml b/.idea/proxide.iml
index 1651999..9828d78 100644
--- a/.idea/proxide.iml
+++ b/.idea/proxide.iml
@@ -4,8 +4,12 @@
+
+
+
+
diff --git a/Cargo.lock b/Cargo.lock
index 5a1c41f..fe059ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -89,6 +89,12 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "antidote"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
+
[[package]]
name = "anyhow"
version = "1.0.75"
@@ -229,6 +235,15 @@ version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -557,6 +572,18 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "escargot"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "768064bd3a0e2bedcba91dc87ace90beea91acc41b6a01a3ca8e9aa8827461bf"
+dependencies = [
+ "log",
+ "once_cell",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "fastrand"
version = "2.0.1"
@@ -575,6 +602,33 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.40",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
[[package]]
name = "futures"
version = "0.3.29"
@@ -1248,6 +1302,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
[[package]]
name = "portpicker"
version = "0.1.1"
@@ -1371,6 +1431,9 @@ dependencies = [
"protofish",
"rcgen",
"rmp-serde",
+ "rstack",
+ "rstack-launcher",
+ "rstack-self",
"rustls",
"serde",
"serde_json",
@@ -1526,6 +1589,42 @@ dependencies = [
"serde",
]
+[[package]]
+name = "rstack"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7df9d3ebd4f17b52e6134efe2fa20021c80688cbe823d481a729a993b730493"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "log",
+ "unwind",
+]
+
+[[package]]
+name = "rstack-launcher"
+version = "0.1.0"
+dependencies = [
+ "escargot",
+ "os-id",
+ "rstack-self",
+]
+
+[[package]]
+name = "rstack-self"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd5030da3aba0ec731502f74ec38e63798eea6bc8b8ba5972129afe3eababd2"
+dependencies = [
+ "antidote",
+ "backtrace",
+ "bincode",
+ "lazy_static",
+ "libc",
+ "rstack",
+ "serde",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -2108,6 +2207,27 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+[[package]]
+name = "unwind"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38290439f8459ba56c4bf15fc776463f495fefc4f0112f87a1a075540441b083"
+dependencies = [
+ "foreign-types",
+ "libc",
+ "unwind-sys",
+]
+
+[[package]]
+name = "unwind-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a81ba64bc45243d442e9bb2a362f303df152b5078c56ce4a0dc7d813c8df91"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
[[package]]
name = "utf8parse"
version = "0.2.1"
diff --git a/Cargo.toml b/Cargo.toml
index df08256..e0e749b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,11 +41,18 @@ wildmatch = "1"
glob = "0.3"
shell-words = "1"
+[target.'cfg(unix)'.dependencies]
+rstack = "0.3.3"
+
[dev-dependencies]
portpicker = "0.1.1"
grpc-tester = { version = "0.1.0", path = "test/rust_grpc"}
serial_test = "2.0.0"
lazy_static = "1.4.0"
+rstack-launcher = { version = "0.1.0", path = "test/rstack-launcher" }
+
+[target.'cfg(unix)'.dev-dependencies]
+rstack-self = "0.3.0"
[profile.release]
debug = true
\ No newline at end of file
diff --git a/src/connection/http2.rs b/src/connection/http2.rs
index c5a1c5f..223b2db 100644
--- a/src/connection/http2.rs
+++ b/src/connection/http2.rs
@@ -15,6 +15,7 @@ use std::sync::mpsc::Sender;
use std::task::{Context, Poll};
use std::time::SystemTime;
use tokio::io::{AsyncRead, AsyncWrite};
+use tokio::sync::{Semaphore, SemaphorePermit, TryAcquireError};
use tokio::task::{JoinHandle, JoinSet};
use uuid::Uuid;
@@ -91,6 +92,7 @@ where
// The client_connection will produce individual HTTP request that we'll accept.
// These requests will be handled in parallel by spawning them into their own
// tasks.
+ let processing_control = ProcessingControl::new();
while let Some(request) = client_connection.accept().await {
let (client_request, client_response) =
request.context(H2Error {}).context(ClientError {
@@ -104,6 +106,7 @@ where
client_request,
client_response,
server_stream,
+ processing_control.clone(),
&ui,
)?;
@@ -148,6 +151,12 @@ pub struct ProxyRequest
request_processor: ProcessingFuture,
}
+/// Manages the asynchronous auxiliary processing of requests.
+struct ProcessingControl
+{
+ callstack_capture_limiter: Semaphore,
+}
+
struct ProcessingFuture
{
inner: JoinHandle<()>,
@@ -155,12 +164,13 @@ struct ProcessingFuture
impl ProxyRequest
{
- pub fn new(
+ fn new(
connection_uuid: Uuid,
authority: Option,
client_request: Request,
client_response: SendResponse,
server_stream: &mut client::SendRequest,
+ processing_control: Arc,
ui: &Sender,
) -> Result
{
@@ -203,7 +213,7 @@ impl ProxyRequest
// Request processor supports asynchronous message processing while the proxide is busy proxying data between
// the client and the server.
- let request_processor = ProcessingFuture::spawn(uuid, &client_head, ui);
+ let request_processor = ProcessingFuture::spawn(uuid, &client_head, processing_control, ui);
let server_request = Request::from_parts(client_head, ());
@@ -464,7 +474,12 @@ fn is_fatal_error(r: &Result) -> bool
impl ProcessingFuture
{
- fn spawn(uuid: Uuid, client_head: &http::request::Parts, ui: &Sender) -> Self
+ fn spawn(
+ uuid: Uuid,
+ client_head: &http::request::Parts,
+ processing_control: Arc,
+ ui: &Sender,
+ ) -> Self
{
let mut tasks: JoinSet>> =
JoinSet::new();
@@ -473,7 +488,10 @@ impl ProcessingFuture
if let Ok(thread_id) = crate::connection::ClientThreadId::try_from(&client_head.headers) {
let ui_clone = ui.clone();
tasks.spawn(ProcessingFuture::capture_client_callstack(
- uuid, thread_id, ui_clone,
+ uuid,
+ thread_id,
+ processing_control.clone(),
+ ui_clone,
));
}
@@ -495,18 +513,33 @@ impl ProcessingFuture
async fn capture_client_callstack(
uuid: Uuid,
- _client_thread_id: ClientThreadId,
+ client_thread_id: ClientThreadId,
+ processing_control: Arc,
ui: Sender,
) -> std::result::Result<(), Box>
{
- // TODO: Try to capture the callstack
- ui.send(SessionEvent::ClientCallstackProcessed(
- ClientCallstackProcessedEvent {
- uuid,
- callstack: ClientCallstack::Unsupported,
- },
- ))?;
- Ok(())
+ // Capturing the callstacks is a very expensive operation
+ // Capturing is throttled with the semaphore in processing_control
+ match processing_control.try_request_capture_callstack_permit() {
+ Ok(_) => {
+ // TODO Callstack capture: Add support for other operating systems.
+ if cfg!(target_os = "linux") {
+ Ok(capture_client_callstack_rstack(uuid, client_thread_id, ui).await?)
+ } else {
+ Ok(capture_client_callstack_unsupported(uuid, client_thread_id, ui).await?)
+ }
+ }
+ Err(TryAcquireError::NoPermits) => {
+ ui.send(SessionEvent::ClientCallstackProcessed(
+ ClientCallstackProcessedEvent {
+ uuid,
+ callstack: ClientCallstack::Throttled,
+ },
+ ))?;
+ Ok(())
+ }
+ Err(e) => Err(Box::from(e)),
+ }
}
}
@@ -522,3 +555,69 @@ impl Future for ProcessingFuture
}
}
}
+
+impl ProcessingControl
+{
+ fn new() -> Arc
+ {
+ let parallel_callstack_capture_limit = if cfg!(not(test)) { 5 } else { 1 };
+ Arc::new(Self {
+ callstack_capture_limiter: Semaphore::new(parallel_callstack_capture_limit),
+ })
+ }
+
+ /// Requests permissions to capture a cleint callstack.
+ fn try_request_capture_callstack_permit(&self) -> Result, TryAcquireError>
+ {
+ self.callstack_capture_limiter.try_acquire()
+ }
+}
+
+#[cfg(target_os = "linux")]
+async fn capture_client_callstack_rstack(
+ uuid: Uuid,
+ client_thread_id: ClientThreadId,
+ ui: Sender,
+) -> std::result::Result<(), Box>
+{
+ if client_thread_id.process_id != std::process::id() {
+ capture_client_callstack_unsupported(uuid, client_thread_id, ui).await
+ } else {
+ // The caller requested trace from the process itself.
+ // This should only happen in unit tests.
+ // Process cannot capture callstack from itself which is why the operation is delegated to rstack_launcher
+ // helper library available in tests.
+
+ #[cfg(test)]
+ {
+ let thread = rstack_launcher::capture_self(client_thread_id.thread_id)?;
+ ui.send(SessionEvent::ClientCallstackProcessed(
+ ClientCallstackProcessedEvent {
+ uuid,
+ callstack: ClientCallstack::Callstack(callstack::Thread::from(&thread)),
+ },
+ ))?;
+ }
+
+ #[cfg(not(test))]
+ {
+ capture_client_callstack_unsupported(uuid, client_thread_id, ui).await?;
+ }
+ Ok(())
+ }
+}
+
+async fn capture_client_callstack_unsupported(
+ uuid: Uuid,
+ _client_thread_id: ClientThreadId,
+ ui: Sender,
+) -> std::result::Result<(), Box>
+{
+ ui.send(SessionEvent::ClientCallstackProcessed(
+ ClientCallstackProcessedEvent {
+ uuid,
+ callstack: ClientCallstack::Unsupported,
+ },
+ ))?;
+ Ok(())
+}
diff --git a/src/main.rs b/src/main.rs
index c373346..e031d40 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@ use crossterm::{
};
use log::error;
use snafu::{ResultExt, Snafu};
+use std::env;
use std::fs::File;
use std::io::stdout;
use std::io::Read;
@@ -16,6 +17,7 @@ use std::net::SocketAddr;
use std::path::Path;
use std::sync::mpsc::Sender;
use std::sync::Arc;
+use std::time::Duration;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::oneshot;
@@ -89,6 +91,13 @@ pub struct ProxyFilter
fn main()
{
+ std::thread::sleep(Duration::from_secs(60));
+
+ // Launched to capture stack?
+ if env::args_os().len() == 2 && env::args_os().any(|p| p == "child") {
+ return;
+ }
+
match proxide_main() {
Ok(_) => (),
Err(e) => {
@@ -430,6 +439,7 @@ mod test
use tokio::time::Instant;
use crate::session::events::SessionEvent;
+ use crate::session::ClientCallstack;
use crate::ConnectionOptions;
lazy_static! {
@@ -570,15 +580,22 @@ mod test
// UI channel should be constantly receiving client callstack events.
// The generator includes the process id and the thread id in the messages it sends.
- let mut client_callstack_received = false;
+ let mut client_callstack_received: Option = None;
let timeout_at = Instant::now().add(Duration::from_secs(30));
while let Some(message) = tokio::select! {
result = message_rx.recv() => result,
_t = tokio::time::sleep( Duration::from_secs( 30 ) ) => panic!( "Timeout" ),
error = error_monitor.recv() => panic!( "{:?}", error ),
} {
- if let SessionEvent::ClientCallstackProcessed(..) = message {
- client_callstack_received = true;
+ // Try to collect a valid callstack.
+ // The capture process has a throttling mechanism which may skip some captures.
+ if let SessionEvent::ClientCallstackProcessed(event) = message {
+ match event.callstack {
+ ClientCallstack::Callstack(thread) => client_callstack_received = Some(thread),
+ ClientCallstack::Throttled => {}
+ ClientCallstack::Unsupported => break,
+ ClientCallstack::Error(error) => panic!("{:?}", error),
+ }
break;
} else if Instant::now() > timeout_at {
panic!("Timeout")
@@ -586,7 +603,18 @@ mod test
}
// Ensure the ui channel was not closed prematurely.
- assert!(client_callstack_received);
+ #[cfg(target_os = "linux")]
+ {
+ let client_callstack_received =
+ client_callstack_received.expect("Client callstack unavailable.");
+ assert_eq!(client_callstack_received.name(), "grpc-generator");
+ }
+
+ // Verify callstack 1with tne new supported OS as well.
+ #[cfg(not(target_os = "linux"))]
+ {
+ assert!(client_callstack_received.is_none());
+ }
let mut server = tester.stop_generator().expect("Stopping generator failed.");
abort_tx.send(()).expect("Stopping proxide failed.");
diff --git a/src/session.rs b/src/session.rs
index 971a2ed..09da084 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -6,6 +6,7 @@ use std::collections::HashMap;
use std::net::SocketAddr;
use uuid::Uuid;
+pub mod callstack;
pub mod events;
pub mod serialization;
@@ -132,6 +133,22 @@ pub enum ClientCallstack
{
/// Proxide does not support callstack capture on the current platform/operating system.
Unsupported,
+
+ /// The maximum number of parallel callstack captures was reached.
+ Throttled,
+
+ /// Captured client thread with its callstack.
+ Callstack(crate::session::callstack::Thread),
+
+ /// An error occurred during the capture process.
+ Error(ClientCallstackError),
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
+pub enum ClientCallstackError
+{
+ /// An internal error to proxide occurred while capturing or processing the callstack.
+ Internal(String),
}
impl IndexedVec
diff --git a/src/session/callstack.rs b/src/session/callstack.rs
new file mode 100644
index 0000000..e939e50
--- /dev/null
+++ b/src/session/callstack.rs
@@ -0,0 +1,131 @@
+use serde::{Deserialize, Serialize};
+
+/// UI visualization types for callstack captures.
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
+pub struct Thread
+{
+ /// Identity of the thread.
+ id: i64,
+
+ /// Name of the thread.
+ name: String,
+
+ /// Captured stack frames of the thread.
+ frames: Vec,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
+pub struct Frame
+{
+ symbols: Vec,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
+pub struct Symbol
+{
+ name: String,
+}
+
+impl Thread
+{
+ pub fn id(&self) -> i64
+ {
+ self.id
+ }
+
+ pub fn name(&self) -> &str
+ {
+ &self.name
+ }
+
+ pub fn frames(&self) -> &[Frame]
+ {
+ &self.frames
+ }
+}
+
+impl Frame
+{
+ pub fn symbols(&self) -> &[Symbol]
+ {
+ &self.symbols
+ }
+}
+
+impl Symbol
+{
+ pub fn name(&self) -> &str
+ {
+ &self.name
+ }
+}
+
+#[cfg(target_os = "linux")]
+impl From<&rstack::Thread> for Thread
+{
+ fn from(value: &rstack::Thread) -> Self
+ {
+ Self {
+ id: value.id() as i64,
+ name: value.name().unwrap_or("").to_string(),
+ frames: value.frames().iter().map(Frame::from).collect(),
+ }
+ }
+}
+
+#[cfg(target_os = "linux")]
+impl From<&rstack::Frame> for Frame
+{
+ fn from(value: &rstack::Frame) -> Self
+ {
+ Frame {
+ symbols: value.symbol().iter().map(|s| Symbol::from(*s)).collect(),
+ }
+ }
+}
+
+#[cfg(target_os = "linux")]
+impl From<&rstack::Symbol> for Symbol
+{
+ fn from(value: &rstack::Symbol) -> Self
+ {
+ Self {
+ name: value.name().to_string(),
+ }
+ }
+}
+
+#[cfg(all( target_os = "linux", test))]
+impl From<&rstack_self::Thread> for Thread
+{
+ fn from(value: &rstack_self::Thread) -> Self
+ {
+ Self {
+ id: value.id() as i64,
+ name: value.name().to_string(),
+ frames: value.frames().iter().map(Frame::from).collect(),
+ }
+ }
+}
+
+#[cfg(all( target_os = "linux", test))]
+impl From<&rstack_self::Frame> for Frame
+{
+ fn from(value: &rstack_self::Frame) -> Self
+ {
+ Self {
+ symbols: value.symbols().iter().map(Symbol::from).collect(),
+ }
+ }
+}
+#[cfg(all( target_os = "linux", test))]
+impl From<&rstack_self::Symbol> for Symbol
+{
+ fn from(value: &rstack_self::Symbol) -> Self
+ {
+ Symbol {
+ name: value.name().expect("Name missing").to_string(),
+ }
+ }
+}
diff --git a/src/ui/toast.rs b/src/ui/toast.rs
index a730170..d545230 100644
--- a/src/ui/toast.rs
+++ b/src/ui/toast.rs
@@ -51,7 +51,9 @@ impl PartialEq for FutureEvent
{
fn eq(&self, other: &Self) -> bool
{
- self.instant.eq(&other.instant)
+ // clippy reports an "unconditional recursion"false positive here in the pipeline with:
+ // "self.instant.eq(&other.instant)"
+ PartialEq::::eq(&self.instant, &other.instant)
}
}
diff --git a/src/ui/views/callstack_view.rs b/src/ui/views/callstack_view.rs
index ed843a9..cfff488 100644
--- a/src/ui/views/callstack_view.rs
+++ b/src/ui/views/callstack_view.rs
@@ -33,11 +33,18 @@ impl View for CallstackView
client_thread.process_id(),
client_thread.thread_id()
);
- let message = match request.request_data.client_callstack {
+ let message: String = match &request.request_data.client_callstack {
Some(ClientCallstack::Unsupported) => {
- "Callstack unavailable:\n* Unsupported operating system."
- }
- None => ".. (Pending)",
+ "Callstack unavailable:\n* Unsupported operating system.".to_string()
+ },
+ Some(ClientCallstack::Throttled) => {
+ "Callstack unavailable:\n* The maximum number of parallel callstack capture operations was reached.".to_string()
+ },
+ Some(ClientCallstack::Callstack( thread)) => message_from_thread( thread ),
+ Some(ClientCallstack::Error(error)) => {
+ format!("{:?}", error)
+ },
+ None => ".. (Pending)".to_string(),
};
let block = create_block(&title);
let request_data = Paragraph::new(message)
@@ -86,3 +93,19 @@ impl View for CallstackView
)
}
}
+
+fn message_from_thread(thread: &crate::session::callstack::Thread) -> String
+{
+ let title = format!("{} ({})", thread.name(), thread.id());
+ let callstack = thread
+ .frames()
+ .iter()
+ .flat_map(|f| f.symbols())
+ .map(|s| s.name())
+ .fold(String::default(), |mut acc, name| {
+ acc.push_str(name);
+ acc.push('\n');
+ acc
+ });
+ format!("{}\n\n{}", title, callstack)
+}
diff --git a/test/rstack-child/Cargo.lock b/test/rstack-child/Cargo.lock
new file mode 100644
index 0000000..6d855fd
--- /dev/null
+++ b/test/rstack-child/Cargo.lock
@@ -0,0 +1,260 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "antidote"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rstack"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7df9d3ebd4f17b52e6134efe2fa20021c80688cbe823d481a729a993b730493"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "log",
+ "unwind",
+]
+
+[[package]]
+name = "rstack-child"
+version = "0.1.0"
+dependencies = [
+ "rstack-self",
+]
+
+[[package]]
+name = "rstack-self"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd5030da3aba0ec731502f74ec38e63798eea6bc8b8ba5972129afe3eababd2"
+dependencies = [
+ "antidote",
+ "backtrace",
+ "bincode",
+ "lazy_static",
+ "libc",
+ "rstack",
+ "serde",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "serde"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unwind"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38290439f8459ba56c4bf15fc776463f495fefc4f0112f87a1a075540441b083"
+dependencies = [
+ "foreign-types",
+ "libc",
+ "unwind-sys",
+]
+
+[[package]]
+name = "unwind-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a81ba64bc45243d442e9bb2a362f303df152b5078c56ce4a0dc7d813c8df91"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
diff --git a/test/rstack-child/Cargo.toml b/test/rstack-child/Cargo.toml
new file mode 100644
index 0000000..15017b6
--- /dev/null
+++ b/test/rstack-child/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "rstack-child"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[target.'cfg(unix)'.dependencies]
+rstack-self = "0.3.0"
\ No newline at end of file
diff --git a/test/rstack-child/src/main.rs b/test/rstack-child/src/main.rs
new file mode 100644
index 0000000..ad5064f
--- /dev/null
+++ b/test/rstack-child/src/main.rs
@@ -0,0 +1,13 @@
+fn main() {
+ #[cfg(target_os = "linux")]
+ {
+ let err = rstack_self::child();
+ eprintln!("{:?}", err);
+ err.expect("Capturing callstack with rstack-self failed.");
+ }
+
+ #[cfg(not(target_os = "linux"))]
+ {
+ panic!("Unsupported operating system.");
+ }
+}
diff --git a/test/rstack-launcher/Cargo.lock b/test/rstack-launcher/Cargo.lock
new file mode 100644
index 0000000..6088009
--- /dev/null
+++ b/test/rstack-launcher/Cargo.lock
@@ -0,0 +1,312 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "antidote"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "escargot"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "768064bd3a0e2bedcba91dc87ace90beea91acc41b6a01a3ca8e9aa8827461bf"
+dependencies = [
+ "log",
+ "once_cell",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "os-id"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "510856ec55c552d86db0d675df95c32b87f28cfe1cdc47d3eba2342c39a0a5f6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rstack"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7df9d3ebd4f17b52e6134efe2fa20021c80688cbe823d481a729a993b730493"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "log",
+ "unwind",
+]
+
+[[package]]
+name = "rstack-launcher"
+version = "0.1.0"
+dependencies = [
+ "escargot",
+ "os-id",
+ "rstack-self",
+]
+
+[[package]]
+name = "rstack-self"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd5030da3aba0ec731502f74ec38e63798eea6bc8b8ba5972129afe3eababd2"
+dependencies = [
+ "antidote",
+ "backtrace",
+ "bincode",
+ "lazy_static",
+ "libc",
+ "rstack",
+ "serde",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "serde"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unwind"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38290439f8459ba56c4bf15fc776463f495fefc4f0112f87a1a075540441b083"
+dependencies = [
+ "foreign-types",
+ "libc",
+ "unwind-sys",
+]
+
+[[package]]
+name = "unwind-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a81ba64bc45243d442e9bb2a362f303df152b5078c56ce4a0dc7d813c8df91"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
diff --git a/test/rstack-launcher/Cargo.toml b/test/rstack-launcher/Cargo.toml
new file mode 100644
index 0000000..41f0b8f
--- /dev/null
+++ b/test/rstack-launcher/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "rstack-launcher"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+os-id = "3.0.1"
+
+[target.'cfg(unix)'.dependencies]
+rstack-self = "0.3.0"
+
+[build-dependencies]
+escargot = "0.5.8"
diff --git a/test/rstack-launcher/build.rs b/test/rstack-launcher/build.rs
new file mode 100644
index 0000000..9e663f4
--- /dev/null
+++ b/test/rstack-launcher/build.rs
@@ -0,0 +1,36 @@
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+
+fn main()
+{
+ #[cfg(target_os = "linux")]
+ {
+ let out_dir = std::env::var("OUT_DIR").expect("Output directory unavailable.");
+ let run = escargot::CargoBuild::new()
+ .current_release()
+ .current_target()
+ .manifest_path("../rstack-child/Cargo.toml")
+ .target_dir(&out_dir)
+ .run()
+ .expect("Compiling rstack-child failed.");
+
+ let child_template = r#"
+ fn launch_child() -> rstack_self::Result {
+ let exe = "PATH";
+ Ok(rstack_self::trace(&mut Command::new(exe))?)
+ }
+ "#;
+ let child_template = child_template.replace(
+ "PATH",
+ run.path().to_str().expect("Unexpected characters in path."),
+ );
+
+ let dest_path = Path::new(&out_dir).join("child.rs");
+ let mut f = File::create(&dest_path).expect("Opening child.rs failed.");
+ f.write_all(child_template.as_bytes())
+ .expect("Writing child.rs failed.");
+ }
+
+ println!("cargo:rerun-if-changed=build.rs");
+}
diff --git a/test/rstack-launcher/src/lib.rs b/test/rstack-launcher/src/lib.rs
new file mode 100644
index 0000000..b243fa9
--- /dev/null
+++ b/test/rstack-launcher/src/lib.rs
@@ -0,0 +1,92 @@
+use std::fmt::{Display, Formatter};
+use std::process::Command;
+
+include!(concat!(env!("OUT_DIR"), "/child.rs"));
+
+/// An error in launching the child.
+#[derive(Debug)]
+pub enum Error
+{
+ /// The error originates from rstack_self.
+ Rstack(rstack_self::Error),
+
+ /// The specified thread was not available.
+ ThreadNotFound,
+
+ /// Unsuportted operating system.
+ UnsupportedOperatingSystem,
+}
+
+/// The result type returned by methods in this crate.
+pub type Result = std::result::Result;
+
+/// Captures the callstack of a thread of the calling process.
+pub fn capture_self(thread_id: i64) -> Result
+{
+ #[cfg(target_os = "linux")]
+ {
+ let trace: rstack_self::Trace = launch_child()?;
+ match trace
+ .threads()
+ .into_iter()
+ .find(|&t| t.id() as i64 == thread_id)
+ {
+ Some(thread) => Ok(thread.clone()),
+ None => Err(Error::ThreadNotFound),
+ }
+ }
+
+ #[cfg(not(target_os = "linux"))]
+ Err(Error::UnsupportedOperatingSystem)
+}
+
+impl From for Error
+{
+ fn from(value: rstack_self::Error) -> Self
+ {
+ Self::Rstack(value)
+ }
+}
+
+impl Display for Error
+{
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result
+ {
+ match self {
+ Error::Rstack(error) => Ok(error.fmt(f)?),
+ Error::ThreadNotFound => write!(f, "Specified thread unavailable."),
+ Error::UnsupportedOperatingSystem => {
+ write!(f, "Capture not supported on the current operatins system.")
+ }
+ }
+ }
+}
+
+impl std::error::Error for Error {}
+
+#[cfg(test)]
+mod test
+{
+ use crate::capture_self;
+
+ #[test]
+ fn capturing_callstack_succeeds()
+ {
+ let thread_id = get_current_native_thread_id();
+ let callstack = capture_self(thread_id).expect("Capturing self failed");
+ assert_eq!(callstack.id() as i64, thread_id);
+ assert_eq!(callstack.name(), "test::capturing");
+ }
+
+ /// Gets the current native thread id.
+ fn get_current_native_thread_id() -> i64
+ {
+ #[cfg(not(target_os = "windows"))]
+ return os_id::thread::get_raw_id() as i64;
+
+ #[cfg(target_os = "windows")]
+ unsafe {
+ return windows::Win32::System::Threading::GetCurrentThreadId() as i64;
+ }
+ }
+}