Skip to content

Commit b067c30

Browse files
committed
perf: reduce memory usage in profiling
Utilizes ThinStr in the ZendFrame to shrink memory.
1 parent 0fff208 commit b067c30

File tree

9 files changed

+178
-284
lines changed

9 files changed

+178
-284
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

profiling/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ chrono = { version = "0.4" }
2121
crossbeam-channel = { version = "0.5", default-features = false, features = ["std"] }
2222
datadog-alloc = { git = "https://github.com/DataDog/libdatadog", tag = "v10.0.0" }
2323
datadog-profiling = { git = "https://github.com/DataDog/libdatadog", tag = "v10.0.0" }
24+
datadog-thin-str = { version = "1", path = "../thin-str", features = ["std"] }
2425
ddcommon = { git = "https://github.com/DataDog/libdatadog", tag = "v10.0.0" }
2526
env_logger = { version = "0.11", default-features = false }
2627
indexmap = { version = "2.2" }

profiling/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ mod logging;
66
mod pcntl;
77
pub mod profiling;
88
mod sapi;
9-
mod thin_str;
109
mod wall_time;
10+
mod well_known;
1111

1212
#[cfg(php_run_time_cache)]
1313
mod string_set;

profiling/src/profiling/mod.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::bindings::ddog_php_prof_get_active_fiber_test as ddog_php_prof_get_ac
1717

1818
use crate::bindings::{datadog_php_profiling_get_profiling_context, zend_execute_data};
1919
use crate::config::SystemSettings;
20+
use crate::well_known::WellKnown;
2021
use crate::{CLOCKS, TAGS};
2122
use chrono::Utc;
2223
use core::{ptr, str};
@@ -26,6 +27,7 @@ use datadog_profiling::api::{
2627
};
2728
use datadog_profiling::exporter::Tag;
2829
use datadog_profiling::internal::Profile as InternalProfile;
30+
use datadog_thin_str::ThinString;
2931
use log::{debug, info, trace, warn};
3032
use once_cell::sync::OnceCell;
3133
use std::borrow::Cow;
@@ -49,6 +51,8 @@ use datadog_profiling::api::UpscalingInfo;
4951

5052
#[cfg(feature = "exception_profiling")]
5153
use crate::exception::EXCEPTION_PROFILING_INTERVAL;
54+
#[cfg(feature = "timeline")]
55+
use crate::timeline::IncludeType;
5256

5357
const UPLOAD_PERIOD: Duration = Duration::from_secs(67);
5458

@@ -514,9 +518,6 @@ pub enum UploadMessage {
514518
Upload(Box<UploadRequest>),
515519
}
516520

517-
#[cfg(feature = "timeline")]
518-
const COW_EVAL: Cow<str> = Cow::Borrowed("[eval]");
519-
520521
const DDPROF_TIME: &str = "ddprof_time";
521522
const DDPROF_UPLOAD: &str = "ddprof_upload";
522523

@@ -907,14 +908,14 @@ impl Profiler {
907908
}];
908909

909910
#[cfg(feature = "timeline")]
910-
pub fn collect_compile_string(&self, now: i64, duration: i64, filename: String, line: u32) {
911+
pub fn collect_compile_string(&self, now: i64, duration: i64, filename: ThinString, line: u32) {
911912
let mut labels = Profiler::common_labels(Self::TIMELINE_COMPILE_FILE_LABELS.len());
912913
labels.extend_from_slice(Self::TIMELINE_COMPILE_FILE_LABELS);
913914
let n_labels = labels.len();
914915

915916
match self.prepare_and_send_message(
916917
vec![ZendFrame {
917-
function: COW_EVAL,
918+
function: WellKnown::Eval.into(),
918919
file: Some(filename),
919920
line,
920921
}],
@@ -942,7 +943,7 @@ impl Profiler {
942943
now: i64,
943944
duration: i64,
944945
filename: String,
945-
include_type: &str,
946+
include_type: IncludeType,
946947
) {
947948
let mut labels = Profiler::common_labels(Self::TIMELINE_COMPILE_FILE_LABELS.len() + 1);
948949
labels.extend_from_slice(Self::TIMELINE_COMPILE_FILE_LABELS);
@@ -955,7 +956,11 @@ impl Profiler {
955956

956957
match self.prepare_and_send_message(
957958
vec![ZendFrame {
958-
function: format!("[{include_type}]").into(),
959+
function: match include_type {
960+
IncludeType::Include => WellKnown::Include.into(),
961+
IncludeType::Require => WellKnown::Require.into(),
962+
IncludeType::Unknown => WellKnown::UnknownIncludeType.into(),
963+
},
959964
file: None,
960965
line: 0,
961966
}],
@@ -984,7 +989,7 @@ impl Profiler {
984989

985990
labels.push(Label {
986991
key: "event",
987-
value: LabelValue::Str(std::borrow::Cow::Borrowed(event)),
992+
value: LabelValue::Str(Cow::Borrowed(event)),
988993
});
989994

990995
let n_labels = labels.len();
@@ -1013,7 +1018,7 @@ impl Profiler {
10131018

10141019
/// This function can be called to collect any fatal errors
10151020
#[cfg(feature = "timeline")]
1016-
pub fn collect_fatal(&self, now: i64, file: String, line: u32, message: String) {
1021+
pub fn collect_fatal(&self, now: i64, file: ThinString, line: u32, message: String) {
10171022
let mut labels = Profiler::common_labels(2);
10181023

10191024
labels.push(Label {
@@ -1029,7 +1034,7 @@ impl Profiler {
10291034

10301035
match self.prepare_and_send_message(
10311036
vec![ZendFrame {
1032-
function: "[fatal]".into(),
1037+
function: WellKnown::Fatal.into(),
10331038
file: Some(file),
10341039
line,
10351040
}],
@@ -1056,7 +1061,7 @@ impl Profiler {
10561061
pub(crate) fn collect_opcache_restart(
10571062
&self,
10581063
now: i64,
1059-
file: String,
1064+
file: ThinString,
10601065
line: u32,
10611066
reason: &'static str,
10621067
) {
@@ -1109,7 +1114,7 @@ impl Profiler {
11091114

11101115
match self.prepare_and_send_message(
11111116
vec![ZendFrame {
1112-
function: "[idle]".into(),
1117+
function: WellKnown::Idle.into(),
11131118
file: None,
11141119
line: 0,
11151120
}],
@@ -1165,7 +1170,7 @@ impl Profiler {
11651170

11661171
match self.prepare_and_send_message(
11671172
vec![ZendFrame {
1168-
function: "[gc]".into(),
1173+
function: WellKnown::Gc.into(),
11691174
file: None,
11701175
line: 0,
11711176
}],
@@ -1233,7 +1238,7 @@ impl Profiler {
12331238
if let Some(functionname) = extract_function_name(func) {
12341239
labels.push(Label {
12351240
key: "fiber",
1236-
value: LabelValue::Str(functionname.into()),
1241+
value: LabelValue::Str(Cow::from(functionname)),
12371242
});
12381243
}
12391244
}
@@ -1287,12 +1292,16 @@ mod tests {
12871292
use super::*;
12881293
use crate::config::AgentEndpoint;
12891294
use datadog_profiling::exporter::Uri;
1295+
use datadog_thin_str::ConstStorage;
12901296
use log::LevelFilter;
12911297

12921298
fn get_frames() -> Vec<ZendFrame> {
1299+
static FOOBAR: ConstStorage<8> = ConstStorage::from_str("foobar()");
1300+
static FOOBAR_PHP: ConstStorage<10> = ConstStorage::from_str("foobar.php");
1301+
12931302
vec![ZendFrame {
1294-
function: "foobar()".into(),
1295-
file: Some("foobar.php".into()),
1303+
function: ThinString::from(&FOOBAR),
1304+
file: Some(ThinString::from(&FOOBAR_PHP)),
12961305
line: 42,
12971306
}]
12981307
}

profiling/src/profiling/stack_walking.rs

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
use crate::bindings::{zai_str_from_zstr, zend_execute_data, zend_function};
2-
use std::borrow::Cow;
2+
use crate::well_known::WellKnown;
3+
use datadog_alloc::Global;
4+
use datadog_thin_str::ThinString;
5+
use std::ops::Deref;
36
use std::str::Utf8Error;
47

58
#[cfg(php_frameless)]
69
use crate::bindings::zend_flf_functions;
10+
711
#[cfg(php_frameless)]
812
use crate::bindings::{
913
ZEND_FRAMELESS_ICALL_0, ZEND_FRAMELESS_ICALL_1, ZEND_FRAMELESS_ICALL_2, ZEND_FRAMELESS_ICALL_3,
1014
};
1115

12-
const COW_PHP_OPEN_TAG: Cow<str> = Cow::Borrowed("<?php");
13-
const COW_TRUNCATED: Cow<str> = Cow::Borrowed("[truncated]");
14-
1516
#[derive(Default, Debug)]
1617
pub struct ZendFrame {
1718
// Most tools don't like frames that don't have function names, so use a
1819
// fake name if you need to like "<?php".
19-
pub function: Cow<'static, str>,
20-
pub file: Option<String>,
20+
pub function: ThinString,
21+
pub file: Option<ThinString>,
2122
pub line: u32, // use 0 for no line info
2223
}
2324

@@ -30,7 +31,7 @@ pub struct ZendFrame {
3031
/// Namespaces are part of the class_name or function_name respectively.
3132
/// Closures and anonymous classes get reformatted by the backend (or maybe
3233
/// frontend, either way it's not our concern, at least not right now).
33-
pub fn extract_function_name(func: &zend_function) -> Option<String> {
34+
pub fn extract_function_name(func: &zend_function) -> Option<ThinString> {
3435
let method_name: &[u8] = func.name().unwrap_or(b"");
3536

3637
/* The top of the stack seems to reasonably often not have a function, but
@@ -59,15 +60,17 @@ pub fn extract_function_name(func: &zend_function) -> Option<String> {
5960

6061
buffer.extend_from_slice(method_name);
6162

62-
Some(String::from_utf8_lossy(buffer.as_slice()).into_owned())
63+
let lossy = String::from_utf8_lossy(buffer.as_slice());
64+
Some(ThinString::from_str_in(&lossy, Global))
6365
}
6466

65-
unsafe fn extract_file_and_line(execute_data: &zend_execute_data) -> (Option<String>, u32) {
67+
unsafe fn extract_file_and_line(execute_data: &zend_execute_data) -> (Option<ThinString>, u32) {
6668
// This should be Some, just being cautious.
6769
match execute_data.func.as_ref() {
6870
Some(func) if !func.is_internal() => {
6971
// Safety: zai_str_from_zstr will return a valid ZaiStr.
70-
let file = zai_str_from_zstr(func.op_array.filename.as_mut()).into_string();
72+
let file_lossy = zai_str_from_zstr(func.op_array.filename.as_mut()).into_string_lossy();
73+
let file = ThinString::from_str_in(file_lossy.deref(), Global);
7174
let lineno = match execute_data.opline.as_ref() {
7275
Some(opline) => opline.lineno,
7376
None => 0,
@@ -82,9 +85,10 @@ unsafe fn extract_file_and_line(execute_data: &zend_execute_data) -> (Option<Str
8285
mod detail {
8386
use super::*;
8487
use crate::string_set::StringSet;
85-
use crate::thin_str::ThinStr;
88+
use datadog_thin_str::ThinStr;
8689
use log::{debug, trace};
8790
use std::cell::RefCell;
91+
use std::ops::Deref;
8892
use std::ptr::NonNull;
8993

9094
struct StringCache<'a> {
@@ -100,9 +104,9 @@ mod detail {
100104
/// string in the slot currently, then create one by calling the
101105
/// provided function, store it in the string cache and cache slot,
102106
/// and return it.
103-
fn get_or_insert<F>(&mut self, slot: usize, f: F) -> Option<String>
107+
fn get_or_insert<F>(&mut self, slot: usize, f: F) -> Option<ThinString>
104108
where
105-
F: FnOnce() -> Option<String>,
109+
F: FnOnce() -> Option<ThinString>,
106110
{
107111
debug_assert!(slot < self.cache_slots.len());
108112
let cached = unsafe { self.cache_slots.get_unchecked_mut(slot) };
@@ -116,7 +120,7 @@ mod detail {
116120
// so this ThinStr points into the same string set that
117121
// created it.
118122
let str = unsafe { self.string_set.get_thin_str(thin_str) };
119-
Some(str.to_string())
123+
Some(ThinString::from_str_in(str, Global))
120124
}
121125
None => {
122126
let string = f()?;
@@ -221,7 +225,7 @@ mod detail {
221225
&**zend_flf_functions.offset(opline.extended_value as isize)
222226
};
223227
samples.push(ZendFrame {
224-
function: extract_function_name(func).map(Cow::Owned).unwrap(),
228+
function: extract_function_name(func).unwrap(),
225229
file: None,
226230
line: 0,
227231
});
@@ -240,7 +244,7 @@ mod detail {
240244
*/
241245
if samples.len() == max_depth - 1 {
242246
samples.push(ZendFrame {
243-
function: COW_TRUNCATED,
247+
function: ThinString::from(WellKnown::Truncated),
244248
file: None,
245249
line: 0,
246250
});
@@ -292,15 +296,15 @@ mod detail {
292296
let mut stats = cell.borrow_mut();
293297
stats.not_applicable += 1;
294298
});
295-
let function = extract_function_name(func).map(Cow::Owned);
299+
let function = extract_function_name(func);
296300
let (file, line) = extract_file_and_line(execute_data);
297301
(function, file, line)
298302
}
299303
};
300304

301305
if function.is_some() || file.is_some() {
302306
Some(ZendFrame {
303-
function: function.unwrap_or(COW_PHP_OPEN_TAG),
307+
function: function.unwrap_or(WellKnown::PhpOpenTag.into()),
304308
file,
305309
line,
306310
})
@@ -312,16 +316,16 @@ mod detail {
312316
fn handle_function_cache_slot(
313317
func: &zend_function,
314318
string_cache: &mut StringCache,
315-
) -> Option<Cow<'static, str>> {
319+
) -> Option<ThinString> {
316320
let fname = string_cache.get_or_insert(0, || extract_function_name(func))?;
317-
Some(Cow::Owned(fname))
321+
Some(ThinString::from_str_in(&fname, Global))
318322
}
319323

320324
unsafe fn handle_file_cache_slot(
321325
execute_data: &zend_execute_data,
322326
string_cache: &mut StringCache,
323-
) -> (Option<String>, u32) {
324-
let option = string_cache.get_or_insert(1, || -> Option<String> {
327+
) -> (Option<ThinString>, u32) {
328+
let option = string_cache.get_or_insert(1, || -> Option<ThinString> {
325329
unsafe {
326330
// Safety: if we have cache slots, we definitely have a func.
327331
let func = &*execute_data.func;
@@ -330,7 +334,9 @@ mod detail {
330334
};
331335

332336
// SAFETY: calling C function with correct args.
333-
let file = zai_str_from_zstr(func.op_array.filename.as_mut()).into_string();
337+
let file_lossy =
338+
zai_str_from_zstr(func.op_array.filename.as_mut()).into_string_lossy();
339+
let file = ThinString::from_str_in(file_lossy.deref(), Global);
334340
Some(file)
335341
}
336342
});
@@ -341,7 +347,7 @@ mod detail {
341347
Some(opline) => opline.lineno,
342348
None => 0,
343349
};
344-
(Some(filename), lineno)
350+
(Some(ThinString::from_str_in(&filename, Global)), lineno)
345351
}
346352
None => (None, 0),
347353
}
@@ -380,7 +386,7 @@ mod detail {
380386
*/
381387
if samples.len() == max_depth - 1 {
382388
samples.push(ZendFrame {
383-
function: COW_TRUNCATED,
389+
function: WellKnown::Truncated.into(),
384390
file: None,
385391
line: 0,
386392
});
@@ -401,7 +407,7 @@ mod detail {
401407
// Only create a new frame if there's file or function info.
402408
if file.is_some() || function.is_some() {
403409
// If there's no function name, use a fake name.
404-
let function = function.map(Cow::Owned).unwrap_or(COW_PHP_OPEN_TAG);
410+
let function = function.unwrap_or(WellKnown::PhpOpenTag.into());
405411
return Some(ZendFrame {
406412
function,
407413
file,
@@ -415,6 +421,17 @@ mod detail {
415421

416422
pub use detail::*;
417423

424+
#[cfg(test)]
425+
mod size_tests {
426+
use super::*;
427+
use core::mem::size_of;
428+
429+
#[test]
430+
fn test_frame_size() {
431+
assert_eq!(size_of::<ZendFrame>(), size_of::<usize>() * 3);
432+
}
433+
}
434+
418435
#[cfg(all(test, stack_walking_tests))]
419436
mod tests {
420437
use super::*;

0 commit comments

Comments
 (0)