Skip to content

Commit 49b3dc0

Browse files
feat(profiling) add thread names (#2934)
1 parent 3ac327e commit 49b3dc0

File tree

9 files changed

+116
-21
lines changed

9 files changed

+116
-21
lines changed

.github/workflows/prof_asan.yml

+1-7
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,11 @@ jobs:
5959
/tmp/build-cargo/
6060
key: ${{ runner.os }}-cargo-asan-${{ hashFiles('**/Cargo.lock') }}
6161

62-
- name: Fix kernel mmap rnd bits
63-
# Asan in llvm 14 provided in ubuntu 22.04 is incompatible with
64-
# high-entropy ASLR in much newer kernels that GitHub runners are
65-
# using leading to random crashes: https://reviews.llvm.org/D148280
66-
# https://github.com/actions/runner-images/issues/9491#issuecomment-1989718917
67-
run: sysctl vm.mmap_rnd_bits=28
68-
6962
- name: Run phpt tests
7063
run: |
7164
set -eux
7265
switch-php nts-asan
7366
cd profiling/tests
7467
cp -v $(php-config --prefix)/lib/php/build/run-tests.php .
68+
export DD_PROFILING_OUTPUT_PPROF=/tmp/pprof
7569
php run-tests.php --show-diff --asan -d extension=datadog-profiling.so phpt

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

profiling/src/lib.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,27 @@ lazy_static! {
9393

9494
/// The Server API the profiler is running under.
9595
static ref SAPI: Sapi = {
96-
// Safety: sapi_module is initialized before minit and there should be
97-
// no concurrent threads.
98-
let sapi_module = unsafe { zend::sapi_module };
99-
if sapi_module.name.is_null() {
100-
panic!("the sapi_module's name is a null pointer");
101-
}
96+
#[cfg(not(test))]
97+
{
98+
// Safety: sapi_module is initialized before minit and there should be
99+
// no concurrent threads.
100+
let sapi_module = unsafe { zend::sapi_module };
101+
if sapi_module.name.is_null() {
102+
panic!("the sapi_module's name is a null pointer");
103+
}
102104

103-
// Safety: value has been checked for NULL; I haven't checked that the
104-
// engine ensures its length is less than `isize::MAX`, but it is a
105-
// risk I'm willing to take.
106-
let sapi_name = unsafe { CStr::from_ptr(sapi_module.name) };
107-
Sapi::from_name(sapi_name.to_string_lossy().as_ref())
105+
// Safety: value has been checked for NULL; I haven't checked that the
106+
// engine ensures its length is less than `isize::MAX`, but it is a
107+
// risk I'm willing to take.
108+
let sapi_name = unsafe { CStr::from_ptr(sapi_module.name) };
109+
Sapi::from_name(sapi_name.to_string_lossy().as_ref())
110+
}
111+
// When running `cargo test` we do not link against PHP, so `zend::sapi_name` is not
112+
// available and we just return `Sapi::Unkown`
113+
#[cfg(test)]
114+
{
115+
Sapi::Unknown
116+
}
108117
};
109118

110119
// Safety: PROFILER_NAME is a byte slice that satisfies the safety requirements.

profiling/src/profiling/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod uploader;
77
pub use interrupts::*;
88
pub use sample_type_filter::*;
99
pub use stack_walking::*;
10+
use thread_utils::get_current_thread_name;
1011
use uploader::*;
1112

1213
#[cfg(all(php_has_fibers, not(test)))]
@@ -1145,12 +1146,17 @@ impl Profiler {
11451146
/// * `n_extra_labels` - Reserve room for extra labels, such as when the
11461147
/// caller adds gc or exception labels.
11471148
fn common_labels(n_extra_labels: usize) -> Vec<Label> {
1148-
let mut labels = Vec::with_capacity(4 + n_extra_labels);
1149+
let mut labels = Vec::with_capacity(5 + n_extra_labels);
11491150
labels.push(Label {
11501151
key: "thread id",
11511152
value: LabelValue::Num(unsafe { libc::pthread_self() as i64 }, "id".into()),
11521153
});
11531154

1155+
labels.push(Label {
1156+
key: "thread name",
1157+
value: LabelValue::Str(get_current_thread_name().into()),
1158+
});
1159+
11541160
// SAFETY: this is set to a noop version if ddtrace wasn't found, and
11551161
// we're getting the profiling context on a PHP thread.
11561162
let context = unsafe { datadog_php_profiling_get_profiling_context.unwrap_unchecked()() };

profiling/src/profiling/thread_utils.rs

+73
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
#[cfg(php_zts)]
2+
use crate::sapi::Sapi;
3+
use crate::SAPI;
4+
#[cfg(php_zts)]
5+
use libc::c_char;
16
use libc::sched_yield;
27
use log::warn;
8+
use once_cell::sync::OnceCell;
39
use std::mem::MaybeUninit;
410
use std::thread::JoinHandle;
511
use std::time::{Duration, Instant};
@@ -69,3 +75,70 @@ pub fn join_timeout(handle: JoinHandle<()>, timeout: Duration, impact: &str) {
6975
std::panic::resume_unwind(err)
7076
}
7177
}
78+
79+
thread_local! {
80+
/// This is a cache for the thread name. It will not change after the thread has been
81+
/// created, as SAPI's do not change thread names and ext-pthreads / ext-parallel do not
82+
/// provide an interface for renaming a thread.
83+
static THREAD_NAME: OnceCell<String> = const { OnceCell::new() };
84+
}
85+
86+
pub fn get_current_thread_name() -> String {
87+
THREAD_NAME.with(|name| {
88+
name.get_or_init(|| {
89+
#[cfg(php_zts)]
90+
let mut thread_name = SAPI.to_string();
91+
#[cfg(not(php_zts))]
92+
let thread_name = SAPI.to_string();
93+
94+
#[cfg(php_zts)]
95+
{
96+
// So far, only FrankenPHP sets meaningful thread names
97+
if *SAPI == Sapi::FrankenPHP {
98+
let mut name = [0u8; 32];
99+
100+
let result = unsafe {
101+
libc::pthread_getname_np(
102+
libc::pthread_self(),
103+
name.as_mut_ptr() as *mut c_char,
104+
name.len(),
105+
)
106+
};
107+
108+
if result == 0 {
109+
// If successful, convert the result to a Rust String
110+
let cstr =
111+
unsafe { std::ffi::CStr::from_ptr(name.as_ptr() as *const c_char) };
112+
let str_slice: &str = cstr.to_str().unwrap_or_default();
113+
if !str_slice.is_empty() {
114+
thread_name.push_str(": ");
115+
thread_name.push_str(str_slice);
116+
}
117+
}
118+
}
119+
}
120+
121+
thread_name
122+
}).clone()
123+
})
124+
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use super::*;
129+
use libc::c_char;
130+
131+
#[test]
132+
fn test_get_current_thread_name() {
133+
unsafe {
134+
// When running `cargo test`, the thread name for this test will be set to
135+
// `profiling::thread_utils::tests:` which would interfer with this test
136+
libc::pthread_setname_np(
137+
#[cfg(target_os = "linux")]
138+
libc::pthread_self(),
139+
b"\0".as_ptr() as *const c_char,
140+
);
141+
}
142+
assert_eq!(get_current_thread_name(), "unknown");
143+
}
144+
}

profiling/tests/correctness/exceptions.json

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
{
1919
"key": "thread id",
2020
"values_regex": "^[0-9]+$"
21+
},
22+
{
23+
"key": "thread name",
24+
"values": [
25+
"cli"
26+
]
2127
}
2228
]
2329
}

profiling/tests/correctness/exceptions_zts.json

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
{
2323
"key": "thread id",
2424
"values_regex": "^[0-9]+$"
25+
},
26+
{
27+
"key": "thread name",
28+
"values": [
29+
"cli"
30+
]
2531
}
2632
]
2733
}

profiling/tests/phpt/exceptions_01.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ echo 'Done.';
4242
?>
4343
--EXPECTREGEX--
4444
.* Exception profiling initialized with sampling distance: 20
45-
.* Sent stack sample of 2 frames, 2 labels with Exception RuntimeException to profiler.
45+
.* Sent stack sample of 2 frames, 3 labels with Exception RuntimeException to profiler.
4646
.*Done\..*
4747
.*

profiling/tests/phpt/exceptions_zts_01.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ echo 'Done.';
5151
?>
5252
--EXPECTREGEX--
5353
.* Exception profiling initialized with sampling distance: 20
54-
.* Sent stack sample of 1 frames, 2 labels with Exception RuntimeException to profiler.
54+
.* Sent stack sample of 1 frames, 3 labels with Exception RuntimeException to profiler.
5555
.*Worker [0-9] exited
5656
.*Done..*
5757
.*

0 commit comments

Comments
 (0)