Skip to content

Commit 477a90f

Browse files
authored
Various performance-related improvements (#675)
This fixes various allocations and single-threadedness that I noticed while profiling https://github.com/mstange/reliost which uses wholesym's query_api method to process the JSON symbolication requests.
2 parents a603f90 + d2d99fc commit 477a90f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2056
-1059
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

samply-api/src/api_file_path.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use std::borrow::Cow;
2+
13
use samply_symbols::SourceFilePath;
24

3-
pub fn to_api_file_path(file_path: &SourceFilePath) -> String {
4-
match file_path.mapped_path() {
5-
Some(mapped_path) => mapped_path.to_special_path_str(),
6-
None => file_path.raw_path().to_owned(),
7-
}
5+
pub fn to_api_file_path<'a>(file_path: &'a SourceFilePath<'_>) -> Cow<'a, str> {
6+
file_path
7+
.special_path_str()
8+
.unwrap_or_else(|| file_path.raw_path().into())
89
}

samply-api/src/asm/mod.rs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,17 @@ use samply_symbols::{
55
object, CodeByteReadingError, CodeId, FileAndPathHelper, FileAndPathHelperError, LibraryInfo,
66
LookupAddress, SymbolManager,
77
};
8-
use serde_json::json;
98
use yaxpeax_arch::{Arch, DecodeError, LengthedInstruction, Reader, U8Reader};
109
use yaxpeax_x86::amd64::{Opcode, Operand};
1110

1211
use self::response_json::Response;
1312
use crate::asm::response_json::DecodedInstruction;
1413

15-
mod request_json;
16-
mod response_json;
14+
pub mod request_json;
15+
pub mod response_json;
1716

1817
#[derive(thiserror::Error, Debug)]
19-
enum AsmError {
20-
#[error("Couldn't parse request: {0}")]
21-
ParseRequestErrorSerde(#[from] serde_json::error::Error),
22-
18+
pub enum AsmError {
2319
#[error("An error occurred when loading the binary: {0}")]
2420
LoadBinaryError(#[from] samply_symbols::Error),
2521

@@ -49,20 +45,15 @@ impl<'a, H: FileAndPathHelper> AsmApi<'a, H> {
4945
Self { symbol_manager }
5046
}
5147

52-
pub async fn query_api_json(&self, request_json: &str) -> String {
53-
match self.query_api_fallible_json(request_json).await {
54-
Ok(response_json) => response_json,
55-
Err(err) => json!({ "error": err.to_string() }).to_string(),
56-
}
57-
}
58-
59-
async fn query_api_fallible_json(&self, request_json: &str) -> Result<String, AsmError> {
48+
pub async fn query_api_json(
49+
&self,
50+
request_json: &str,
51+
) -> Result<response_json::Response, crate::Error> {
6052
let request: request_json::Request = serde_json::from_str(request_json)?;
61-
let response = self.query_api(&request).await?;
62-
Ok(serde_json::to_string(&response)?)
53+
Ok(self.query_api(&request).await?)
6354
}
6455

65-
async fn query_api(
56+
pub async fn query_api(
6657
&self,
6758
request: &request_json::Request,
6859
) -> Result<response_json::Response, AsmError> {

samply-api/src/error.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,44 @@
1+
use serde::Serialize;
12
use thiserror::Error;
23

34
#[derive(Error, Debug)]
45
pub enum Error {
6+
#[error("Unrecognized URL: {0}")]
7+
UnrecognizedUrl(String),
8+
9+
#[error("Couldn't parse request: {0}")]
10+
ParseRequestErrorSerde(#[from] serde_json::error::Error),
11+
12+
#[error("Malformed request JSON: {0}")]
13+
ParseRequestErrorContents(&'static str),
14+
515
#[error("{0}")]
616
Symbols(
717
#[from]
818
#[source]
919
samply_symbols::Error,
1020
),
1121

12-
#[error("Couldn't parse request: {0}")]
13-
ParseRequestErrorSerde(#[from] serde_json::error::Error),
22+
#[error("{0}")]
23+
Source(
24+
#[from]
25+
#[source]
26+
super::source::SourceError,
27+
),
1428

15-
#[error("Malformed request JSON: {0}")]
16-
ParseRequestErrorContents(&'static str),
29+
#[error("{0}")]
30+
Asm(
31+
#[from]
32+
#[source]
33+
super::asm::AsmError,
34+
),
35+
}
36+
37+
impl Serialize for Error {
38+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39+
where
40+
S: serde::Serializer,
41+
{
42+
self.to_string().serialize(serializer)
43+
}
1744
}

samply-api/src/hex.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,6 @@ where
66
serializer.collect_str(&format_args!("{field:#x}"))
77
}
88

9-
pub fn as_optional_hex_string<S, T>(
10-
field: &Option<T>,
11-
serializer: S,
12-
) -> std::result::Result<S::Ok, S::Error>
13-
where
14-
S: serde::Serializer,
15-
T: std::fmt::LowerHex,
16-
{
17-
match field {
18-
Some(field) => serializer.collect_str(&format_args!("{field:#x}")),
19-
None => serializer.serialize_none(),
20-
}
21-
}
22-
239
pub fn from_prefixed_hex_str<'de, D>(deserializer: D) -> Result<u32, D::Error>
2410
where
2511
D: serde::Deserializer<'de>,

samply-api/src/lib.rs

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
//! let symbol_manager = SymbolManager::with_helper(helper);
2929
//! let api = samply_api::Api::new(&symbol_manager);
3030
//!
31-
//! api.query_api(
31+
//! let api_result = api.query_api(
3232
//! "/symbolicate/v5",
3333
//! r#"{
3434
//! "memoryMap": [
@@ -46,7 +46,8 @@
4646
//! ]
4747
//! ]
4848
//! }"#,
49-
//! ).await
49+
//! ).await;
50+
//! serde_json::to_string(&api_result).unwrap()
5051
//! }
5152
//!
5253
//! struct ExampleHelper {
@@ -147,7 +148,7 @@ use debugid::DebugId;
147148
pub use samply_symbols;
148149
pub use samply_symbols::debugid;
149150
use samply_symbols::{FileAndPathHelper, SymbolManager};
150-
use serde_json::json;
151+
use serde::Serialize;
151152
use source::SourceApi;
152153
use symbolicate::SymbolicateApi;
153154

@@ -158,6 +159,8 @@ mod hex;
158159
mod source;
159160
mod symbolicate;
160161

162+
pub use error::Error;
163+
161164
pub(crate) fn to_debug_id(breakpad_id: &str) -> Result<DebugId, samply_symbols::Error> {
162165
// Only accept breakpad IDs with the right syntax, and which aren't all-zeros.
163166
match DebugId::from_breakpad(breakpad_id) {
@@ -168,12 +171,57 @@ pub(crate) fn to_debug_id(breakpad_id: &str) -> Result<DebugId, samply_symbols::
168171
}
169172
}
170173

174+
pub enum QueryApiJsonResult<H: FileAndPathHelper> {
175+
SymbolicateResponse(symbolicate::response_json::Response<H>),
176+
SourceResponse(source::response_json::Response),
177+
AsmResponse(asm::response_json::Response),
178+
Err(Error),
179+
}
180+
181+
impl<H: FileAndPathHelper> From<symbolicate::response_json::Response<H>> for QueryApiJsonResult<H> {
182+
fn from(value: symbolicate::response_json::Response<H>) -> Self {
183+
QueryApiJsonResult::SymbolicateResponse(value)
184+
}
185+
}
186+
187+
impl<H: FileAndPathHelper> From<source::response_json::Response> for QueryApiJsonResult<H> {
188+
fn from(value: source::response_json::Response) -> Self {
189+
QueryApiJsonResult::SourceResponse(value)
190+
}
191+
}
192+
193+
impl<H: FileAndPathHelper> From<asm::response_json::Response> for QueryApiJsonResult<H> {
194+
fn from(value: asm::response_json::Response) -> Self {
195+
QueryApiJsonResult::AsmResponse(value)
196+
}
197+
}
198+
199+
impl<H: FileAndPathHelper> From<Error> for QueryApiJsonResult<H> {
200+
fn from(value: Error) -> Self {
201+
QueryApiJsonResult::Err(value)
202+
}
203+
}
204+
205+
impl<H: FileAndPathHelper> Serialize for QueryApiJsonResult<H> {
206+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
207+
where
208+
S: serde::Serializer,
209+
{
210+
match self {
211+
QueryApiJsonResult::SymbolicateResponse(response) => response.serialize(serializer),
212+
QueryApiJsonResult::SourceResponse(response) => response.serialize(serializer),
213+
QueryApiJsonResult::AsmResponse(response) => response.serialize(serializer),
214+
QueryApiJsonResult::Err(error) => error.serialize(serializer),
215+
}
216+
}
217+
}
218+
171219
#[derive(Clone, Copy)]
172220
pub struct Api<'a, H: FileAndPathHelper> {
173221
symbol_manager: &'a SymbolManager<H>,
174222
}
175223

176-
impl<'a, H: FileAndPathHelper> Api<'a, H> {
224+
impl<'a, H: FileAndPathHelper + 'static> Api<'a, H> {
177225
/// Create a [`Api`] instance which uses the provided [`SymbolManager`].
178226
pub fn new(symbol_manager: &'a SymbolManager<H>) -> Self {
179227
Self { symbol_manager }
@@ -194,18 +242,34 @@ impl<'a, H: FileAndPathHelper> Api<'a, H> {
194242
/// symbol information for that address.
195243
/// - `/asm/v1`: Experimental API. Symbolicates an address and lets you read one of the files in the
196244
/// symbol information for that address.
197-
pub async fn query_api(self, request_url: &str, request_json_data: &str) -> String {
245+
pub async fn query_api(
246+
self,
247+
request_url: &str,
248+
request_json_data: &str,
249+
) -> QueryApiJsonResult<H> {
250+
self.query_api_fallible(request_url, request_json_data)
251+
.await
252+
.unwrap_or_else(|e| e.into())
253+
}
254+
pub async fn query_api_fallible(
255+
self,
256+
request_url: &str,
257+
request_json_data: &str,
258+
) -> Result<QueryApiJsonResult<H>, Error> {
198259
if request_url == "/symbolicate/v5" {
199260
let symbolicate_api = SymbolicateApi::new(self.symbol_manager);
200-
symbolicate_api.query_api_json(request_json_data).await
261+
Ok(symbolicate_api
262+
.query_api_json(request_json_data)
263+
.await?
264+
.into())
201265
} else if request_url == "/source/v1" {
202266
let source_api = SourceApi::new(self.symbol_manager);
203-
source_api.query_api_json(request_json_data).await
267+
Ok(source_api.query_api_json(request_json_data).await?.into())
204268
} else if request_url == "/asm/v1" {
205269
let asm_api = AsmApi::new(self.symbol_manager);
206-
asm_api.query_api_json(request_json_data).await
270+
Ok(asm_api.query_api_json(request_json_data).await?.into())
207271
} else {
208-
json!({ "error": format!("Unrecognized URL {request_url}") }).to_string()
272+
Err(Error::UnrecognizedUrl(request_url.into()))
209273
}
210274
}
211275
}

samply-api/src/source/mod.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
use samply_symbols::{
22
FileAndPathHelper, FileAndPathHelperError, LibraryInfo, LookupAddress, SymbolManager,
33
};
4-
use serde_json::json;
54

65
use crate::api_file_path::to_api_file_path;
7-
use crate::to_debug_id;
6+
use crate::{to_debug_id, Error};
87

9-
mod request_json;
10-
mod response_json;
8+
pub mod request_json;
9+
pub mod response_json;
1110

1211
#[derive(thiserror::Error, Debug)]
13-
enum SourceError {
14-
#[error("Couldn't parse request: {0}")]
15-
ParseRequestErrorSerde(#[from] serde_json::error::Error),
16-
12+
pub enum SourceError {
1713
#[error("Could not obtain symbols for the requested library: {0}")]
1814
NoSymbols(#[from] samply_symbols::Error),
1915

@@ -37,20 +33,15 @@ impl<'a, H: FileAndPathHelper> SourceApi<'a, H> {
3733
Self { symbol_manager }
3834
}
3935

40-
pub async fn query_api_json(&self, request_json: &str) -> String {
41-
match self.query_api_fallible_json(request_json).await {
42-
Ok(response_json) => response_json,
43-
Err(err) => json!({ "error": err.to_string() }).to_string(),
44-
}
45-
}
46-
47-
async fn query_api_fallible_json(&self, request_json: &str) -> Result<String, SourceError> {
36+
pub async fn query_api_json(
37+
&self,
38+
request_json: &str,
39+
) -> Result<response_json::Response, Error> {
4840
let request: request_json::Request = serde_json::from_str(request_json)?;
49-
let response = self.query_api(&request).await?;
50-
Ok(serde_json::to_string(&response)?)
41+
Ok(self.query_api(&request).await?)
5142
}
5243

53-
async fn query_api(
44+
pub async fn query_api(
5445
&self,
5546
request: &request_json::Request,
5647
) -> Result<response_json::Response, SourceError> {
@@ -82,6 +73,7 @@ impl<'a, H: FileAndPathHelper> SourceApi<'a, H> {
8273
let source_file_path = frames
8374
.into_iter()
8475
.filter_map(|frame| frame.file_path)
76+
.map(|path| symbol_map.resolve_source_file_path(path))
8577
.find(|file_path| to_api_file_path(file_path) == *requested_file)
8678
.ok_or(SourceError::InvalidPath)?;
8779

samply-api/src/symbolicate/looked_up_addresses.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
use std::collections::BTreeMap;
22

3-
use samply_symbols::FrameDebugInfo;
3+
use samply_symbols::{FrameDebugInfo, SourceFilePath, SourceFilePathHandle};
4+
5+
pub trait PathResolver {
6+
fn resolve_source_file_path(&self, handle: SourceFilePathHandle) -> SourceFilePath<'_>;
7+
}
8+
9+
impl PathResolver for () {
10+
fn resolve_source_file_path(&self, _handle: SourceFilePathHandle) -> SourceFilePath<'_> {
11+
unreachable!()
12+
}
13+
}
414

515
pub struct AddressResult {
616
pub symbol_address: u32,

0 commit comments

Comments
 (0)