Skip to content

Commit 0975799

Browse files
poc
1 parent 49b3dc0 commit 0975799

File tree

5 files changed

+106
-1
lines changed

5 files changed

+106
-1
lines changed

profiling/build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ fn cfg_php_feature_flags(vernum: u64) {
357357
}
358358
if vernum >= 80400 {
359359
println!("cargo:rustc-cfg=php_frameless");
360+
println!("cargo:rustc-cfg=php_opcache_restart_hook");
360361
}
361362
}
362363

profiling/src/bindings/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub type VmGcCollectCyclesFn = unsafe extern "C" fn() -> i32;
2020
#[cfg(feature = "timeline")]
2121
pub type VmZendCompileFile =
2222
unsafe extern "C" fn(*mut zend_file_handle, i32) -> *mut _zend_op_array;
23+
#[cfg(all(feature = "timeline", php_opcache_restart_hook))]
24+
pub type VmZendAccelScheduleRestartHook = unsafe extern "C" fn(i32);
2325
#[cfg(all(feature = "timeline", php_zend_compile_string_has_position))]
2426
pub type VmZendCompileString = unsafe extern "C" fn(
2527
*mut zend_string,

profiling/src/profiling/mod.rs

+44
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,50 @@ impl Profiler {
10511051
}
10521052
}
10531053

1054+
/// This function can be called to collect an opcache restart
1055+
#[cfg(all(feature = "timeline", php_opcache_restart_hook))]
1056+
pub(crate) fn collect_opcache_restart(
1057+
&self,
1058+
now: i64,
1059+
file: String,
1060+
line: u32,
1061+
reason: &'static str,
1062+
) {
1063+
let mut labels = Profiler::common_labels(2);
1064+
1065+
labels.push(Label {
1066+
key: "event",
1067+
value: LabelValue::Str("opcache_restart".into()),
1068+
});
1069+
labels.push(Label {
1070+
key: "reason",
1071+
value: LabelValue::Str(reason.into()),
1072+
});
1073+
1074+
let n_labels = labels.len();
1075+
1076+
match self.prepare_and_send_message(
1077+
vec![ZendFrame {
1078+
function: "[opcache restart]".into(),
1079+
file: Some(file),
1080+
line,
1081+
}],
1082+
SampleValues {
1083+
timeline: 1,
1084+
..Default::default()
1085+
},
1086+
labels,
1087+
now,
1088+
) {
1089+
Ok(_) => {
1090+
trace!("Sent event 'idle' with {n_labels} labels to profiler.")
1091+
}
1092+
Err(err) => {
1093+
warn!("Failed to send event 'idle' with {n_labels} labels to profiler: {err}")
1094+
}
1095+
}
1096+
}
1097+
10541098
/// This function can be called to collect any kind of inactivity that is happening
10551099
#[cfg(feature = "timeline")]
10561100
pub fn collect_idle(&self, now: i64, duration: i64, reason: &'static str) {

profiling/src/profiling/thread_utils.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ pub fn get_current_thread_name() -> String {
119119
}
120120

121121
thread_name
122-
}).clone()
122+
})
123+
.clone()
123124
})
124125
}
125126

profiling/src/timeline.rs

+57
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ static mut PREV_ZEND_COMPILE_STRING: Option<zend::VmZendCompileString> = None;
2424
/// The engine's original (or neighbouring extensions) `zend_compile_file()` function
2525
static mut PREV_ZEND_COMPILE_FILE: Option<zend::VmZendCompileFile> = None;
2626

27+
/// The engine's original (or neighbouring extensions) `zend_accel_schedule_restart_hook()`
28+
/// function
29+
#[cfg(php_opcache_restart_hook)]
30+
static mut PREV_ZEND_ACCEL_SCHEDULE_RESTART_HOOK: Option<zend::VmZendAccelScheduleRestartHook> =
31+
None;
32+
2733
static mut SLEEP_HANDLER: InternalFunctionHandler = None;
2834
static mut USLEEP_HANDLER: InternalFunctionHandler = None;
2935
static mut TIME_NANOSLEEP_HANDLER: InternalFunctionHandler = None;
@@ -192,12 +198,63 @@ unsafe extern "C" fn ddog_php_prof_zend_error_observer(
192198
}
193199
}
194200

201+
/// Will be called by the opcache extension when a restart is scheduled. The `reason` is this enum:
202+
/// ```C
203+
/// typedef enum _zend_accel_restart_reason {
204+
/// ACCEL_RESTART_OOM, /* restart because of out of memory */
205+
/// ACCEL_RESTART_HASH, /* restart because of hash overflow */
206+
/// ACCEL_RESTART_USER /* restart scheduled by opcache_reset() */
207+
/// } zend_accel_restart_reason;
208+
/// ```
209+
#[no_mangle]
210+
#[cfg(php_opcache_restart_hook)]
211+
unsafe extern "C" fn ddog_php_prof_zend_accel_schedule_restart_hook(reason: i32) {
212+
let timeline_enabled = REQUEST_LOCALS.with(|cell| {
213+
cell.try_borrow()
214+
.map(|locals| locals.system_settings().profiling_timeline_enabled)
215+
.unwrap_or(false)
216+
});
217+
218+
if timeline_enabled {
219+
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
220+
if let Some(profiler) = Profiler::get() {
221+
let now = now.as_nanos() as i64;
222+
let file = unsafe {
223+
zend::zai_str_from_zstr(zend::zend_get_executed_filename_ex().as_mut())
224+
.into_string()
225+
};
226+
profiler.collect_opcache_restart(
227+
now,
228+
file,
229+
zend::zend_get_executed_lineno(),
230+
match reason {
231+
0 => "out of memory",
232+
1 => "hash overflow",
233+
2 => "`opcache_restart()` called",
234+
_ => "unknown",
235+
},
236+
);
237+
}
238+
}
239+
240+
if let Some(prev) = PREV_ZEND_ACCEL_SCHEDULE_RESTART_HOOK {
241+
prev(reason);
242+
}
243+
}
244+
195245
/// This functions needs to be called in MINIT of the module
196246
pub fn timeline_minit() {
197247
unsafe {
198248
#[cfg(zend_error_observer)]
199249
zend::zend_observer_error_register(Some(ddog_php_prof_zend_error_observer));
200250

251+
#[cfg(php_opcache_restart_hook)]
252+
{
253+
PREV_ZEND_ACCEL_SCHEDULE_RESTART_HOOK = zend::zend_accel_schedule_restart_hook;
254+
zend::zend_accel_schedule_restart_hook =
255+
Some(ddog_php_prof_zend_accel_schedule_restart_hook);
256+
}
257+
201258
// register our function in the `gc_collect_cycles` pointer
202259
PREV_GC_COLLECT_CYCLES = zend::gc_collect_cycles;
203260
zend::gc_collect_cycles = Some(ddog_php_prof_gc_collect_cycles);

0 commit comments

Comments
 (0)