samply_api/
lib.rs

1//! This crate implements a JSON API for profiler symbolication with the help of
2//! local symbol files. It exposes a single type called `API`, and uses the
3//! `samply-symbols` crate for its implementation.
4//!
5//! Just like the `samply-symbols` crate, this crate does not contain any direct
6//! file access. It is written in such a way that it can be compiled to
7//! WebAssembly, with all file access being mediated via a `FileAndPathHelper`
8//! trait.
9//!
10//! Do not use this crate directly unless you have to. Instead, use
11//! [`wholesym`](https://docs.rs/wholesym), which provides a much more ergonomic Rust API.
12//! `wholesym` exposes the JSON API functionality via [`SymbolManager::query_json_api`](https://docs.rs/wholesym/latest/wholesym/struct.SymbolManager.html#method.query_json_api).
13//!
14//! ## Example
15//!
16//! ```rust
17//! use samply_api::samply_symbols::{
18//!     FileContents, FileAndPathHelper, FileAndPathHelperResult, OptionallySendFuture,
19//!     CandidatePathInfo, FileLocation, LibraryInfo, SymbolManager,
20//! };
21//! use samply_api::samply_symbols::debugid::{CodeId, DebugId};
22//!
23//! async fn run_query() -> String {
24//!     let this_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
25//!     let helper = ExampleHelper {
26//!         artifact_directory: this_dir.join("..").join("fixtures").join("win64-ci")
27//!     };
28//!     let symbol_manager = SymbolManager::with_helper(helper);
29//!     let api = samply_api::Api::new(&symbol_manager);
30//!
31//!     api.query_api(
32//!         "/symbolicate/v5",
33//!         r#"{
34//!             "memoryMap": [
35//!               [
36//!                 "firefox.pdb",
37//!                 "AA152DEB2D9B76084C4C44205044422E1"
38//!               ]
39//!             ],
40//!             "stacks": [
41//!               [
42//!                 [0, 204776],
43//!                 [0, 129423],
44//!                 [0, 244290],
45//!                 [0, 244219]
46//!               ]
47//!             ]
48//!           }"#,
49//!     ).await
50//! }
51//!
52//! struct ExampleHelper {
53//!     artifact_directory: std::path::PathBuf,
54//! }
55//!
56//! impl FileAndPathHelper for ExampleHelper {
57//!     type F = Vec<u8>;
58//!     type FL = ExampleFileLocation;
59//!
60//!     fn get_candidate_paths_for_debug_file(
61//!         &self,
62//!         library_info: &LibraryInfo,
63//!     ) -> FileAndPathHelperResult<Vec<CandidatePathInfo<ExampleFileLocation>>> {
64//!         if let Some(debug_name) = library_info.debug_name.as_deref() {
65//!             Ok(vec![CandidatePathInfo::SingleFile(ExampleFileLocation(
66//!                 self.artifact_directory.join(debug_name),
67//!             ))])
68//!         } else {
69//!             Ok(vec![])
70//!         }
71//!     }
72//!
73//!     fn get_candidate_paths_for_binary(
74//!         &self,
75//!         library_info: &LibraryInfo,
76//!     ) -> FileAndPathHelperResult<Vec<CandidatePathInfo<ExampleFileLocation>>> {
77//!         if let Some(name) = library_info.name.as_deref() {
78//!             Ok(vec![CandidatePathInfo::SingleFile(ExampleFileLocation(
79//!                 self.artifact_directory.join(name),
80//!             ))])
81//!         } else {
82//!             Ok(vec![])
83//!         }
84//!     }
85//!
86//!    fn get_dyld_shared_cache_paths(
87//!        &self,
88//!        _arch: Option<&str>,
89//!    ) -> FileAndPathHelperResult<Vec<ExampleFileLocation>> {
90//!        Ok(vec![])
91//!    }
92//!
93//!     fn load_file(
94//!         &self,
95//!         location: ExampleFileLocation,
96//!     ) -> std::pin::Pin<Box<dyn OptionallySendFuture<Output = FileAndPathHelperResult<Self::F>> + '_>> {
97//!         Box::pin(async move { Ok(std::fs::read(&location.0)?) })
98//!     }
99//! }
100//!
101//! #[derive(Clone, Debug)]
102//! struct ExampleFileLocation(std::path::PathBuf);
103//!
104//! impl std::fmt::Display for ExampleFileLocation {
105//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106//!         self.0.to_string_lossy().fmt(f)
107//!     }
108//! }
109//!
110//! impl FileLocation for ExampleFileLocation {
111//!     fn location_for_dyld_subcache(&self, suffix: &str) -> Option<Self> {
112//!         let mut filename = self.0.file_name().unwrap().to_owned();
113//!         filename.push(suffix);
114//!         Some(Self(self.0.with_file_name(filename)))
115//!     }
116//!
117//!     fn location_for_external_object_file(&self, object_file: &str) -> Option<Self> {
118//!         Some(Self(object_file.into()))
119//!     }
120//!
121//!     fn location_for_pdb_from_binary(&self, pdb_path_in_binary: &str) -> Option<Self> {
122//!         Some(Self(pdb_path_in_binary.into()))
123//!     }
124//!
125//!     fn location_for_source_file(&self, source_file_path: &str) -> Option<Self> {
126//!         Some(Self(source_file_path.into()))
127//!     }
128//!
129//!     fn location_for_breakpad_symindex(&self) -> Option<Self> {
130//!         Some(Self(self.0.with_extension("symindex")))
131//!     }
132//!
133//!     fn location_for_dwo(&self, _comp_dir: &str, path: &str) -> Option<Self> {
134//!         Some(Self(std::path::Path::new(path).into()))
135//!     }
136//!
137//!     fn location_for_dwp(&self) -> Option<Self> {
138//!         let mut s = self.0.as_os_str().to_os_string();
139//!         s.push(".dwp");
140//!         Some(Self(s.into()))
141//!     }
142//! }
143//! ```
144
145use asm::AsmApi;
146use debugid::DebugId;
147pub use samply_symbols;
148pub use samply_symbols::debugid;
149use samply_symbols::{FileAndPathHelper, SymbolManager};
150use serde_json::json;
151use source::SourceApi;
152use symbolicate::SymbolicateApi;
153
154mod api_file_path;
155mod asm;
156mod error;
157mod hex;
158mod source;
159mod symbolicate;
160
161pub(crate) fn to_debug_id(breakpad_id: &str) -> Result<DebugId, samply_symbols::Error> {
162    // Only accept breakpad IDs with the right syntax, and which aren't all-zeros.
163    match DebugId::from_breakpad(breakpad_id) {
164        Ok(debug_id) if !debug_id.is_nil() => Ok(debug_id),
165        _ => Err(samply_symbols::Error::InvalidBreakpadId(
166            breakpad_id.to_string(),
167        )),
168    }
169}
170
171#[derive(Clone, Copy)]
172pub struct Api<'a, H: FileAndPathHelper> {
173    symbol_manager: &'a SymbolManager<H>,
174}
175
176impl<'a, H: FileAndPathHelper> Api<'a, H> {
177    /// Create a [`Api`] instance which uses the provided [`SymbolManager`].
178    pub fn new(symbol_manager: &'a SymbolManager<H>) -> Self {
179        Self { symbol_manager }
180    }
181
182    /// This is the main API of this crate.
183    /// It implements the "Tecken" JSON API, which is also used by the Mozilla symbol server.
184    /// It's intended to be used as a drop-in "local symbol server" which gathers its data
185    /// directly from file artifacts produced during compilation (rather than consulting
186    /// e.g. a database).
187    /// The caller needs to implement the `FileAndPathHelper` trait to provide file system access.
188    /// The return value is a JSON string.
189    ///
190    /// The following "URLs" are supported:
191    ///  - `/symbolicate/v5`: This API is documented at <https://tecken.readthedocs.io/en/latest/symbolication.html>.
192    ///    The returned data has two extra fields: inlines (per address) and module_errors (per job).
193    ///  - `/source/v1`: Experimental API. Symbolicates an address and lets you read one of the files in the
194    ///    symbol information for that address.
195    ///  - `/asm/v1`: Experimental API. Symbolicates an address and lets you read one of the files in the
196    ///    symbol information for that address.
197    pub async fn query_api(self, request_url: &str, request_json_data: &str) -> String {
198        if request_url == "/symbolicate/v5" {
199            let symbolicate_api = SymbolicateApi::new(self.symbol_manager);
200            symbolicate_api.query_api_json(request_json_data).await
201        } else if request_url == "/source/v1" {
202            let source_api = SourceApi::new(self.symbol_manager);
203            source_api.query_api_json(request_json_data).await
204        } else if request_url == "/asm/v1" {
205            let asm_api = AsmApi::new(self.symbol_manager);
206            asm_api.query_api_json(request_json_data).await
207        } else {
208            json!({ "error": format!("Unrecognized URL {request_url}") }).to_string()
209        }
210    }
211}