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}