blob: 452bb7c67178b03c4567837b0c9baf49ed3aaa95 [file]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{Plugin, log_exception};
use crate::{
component::COMPONENT_KAFKA_PRODUCER_ID,
context::RequestContext,
execute::{AfterExecuteHook, BeforeExecuteHook, get_this_mut, validate_num_args},
tag::{TAG_MQ_BROKER, TAG_MQ_QUEUE, TAG_MQ_TOPIC},
};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use phper::{objects::ZObj, sys};
use skywalking::{
proto::v3::SpanLayer,
trace::span::{HandleSpanObject, Span},
};
/// Maps Producer object handle -> broker string.
static PRODUCER_BROKERS: Lazy<DashMap<u32, String>> = Lazy::new(DashMap::new);
/// Maps Topic object handle -> broker string.
static TOPIC_BROKERS: Lazy<DashMap<u32, String>> = Lazy::new(DashMap::new);
/// Maps object handle -> original dtor.
static DTOR_MAP: Lazy<DashMap<u32, sys::zend_object_dtor_obj_t>> = Lazy::new(DashMap::new);
#[derive(Default, Clone)]
pub struct RdkafkaPlugin;
impl Plugin for RdkafkaPlugin {
fn class_names(&self) -> Option<&'static [&'static str]> {
Some(&["RdKafka", "RdKafka\\ProducerTopic"])
}
fn function_name_prefix(&self) -> Option<&'static str> {
None
}
fn hook(
&self, class_name: Option<&str>, function_name: &str,
) -> Option<(Box<BeforeExecuteHook>, Box<AfterExecuteHook>)> {
tracing::debug!(?class_name, function_name, "rdkafka plugin hook called");
match (class_name, function_name) {
(Some("RdKafka"), "addBrokers") => Some(self.hook_add_brokers()),
(Some("RdKafka"), "newTopic") => Some(self.hook_new_topic()),
(Some("RdKafka\\ProducerTopic"), "producev") => Some(self.hook_producev()),
_ => None,
}
}
}
impl RdkafkaPlugin {
fn hook_add_brokers(&self) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
(
Box::new(move |_request_id, execute_data| {
validate_num_args(execute_data, 1)?;
let this = get_this_mut(execute_data)?;
let handle = this.handle();
hack_dtor(this, Some(producer_dtor));
let broker_list = execute_data.get_parameter(0).expect_str()?.to_owned();
PRODUCER_BROKERS.insert(handle, broker_list);
Ok(Box::new(()))
}),
Box::new(|_, _, _, _| Ok(())),
)
}
fn hook_new_topic(&self) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
(
Box::new(move |_request_id, execute_data| {
let this = get_this_mut(execute_data)?;
Ok(Box::new(this.handle()))
}),
Box::new(move |_, data, _, return_value| {
let producer_handle = *data.downcast::<u32>().unwrap();
if let Some(topic) = return_value.as_mut_z_obj() {
let class_name = topic.get_class().get_name().to_str()?;
if class_name == "RdKafka\\ProducerTopic" {
hack_dtor(topic, Some(producer_topic_dtor));
if let Some(broker) = PRODUCER_BROKERS.get(&producer_handle) {
TOPIC_BROKERS.insert(topic.handle(), broker.clone());
}
}
}
Ok(())
}),
)
}
fn hook_producev(&self) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
let class_name = "RdKafka\\ProducerTopic".to_owned();
let function_name = "producev".to_owned();
(
Box::new(move |request_id, execute_data| {
validate_num_args(execute_data, 1)?;
let this = get_this_mut(execute_data)?;
let topic_name = this
.call("getName", [])
.ok()
.and_then(|v| {
v.as_z_str()
.and_then(|s| s.to_str().ok())
.map(ToOwned::to_owned)
})
.unwrap_or_default();
let topic_handle = this.handle();
let broker = TOPIC_BROKERS
.get(&topic_handle)
.map(|b| b.clone())
.unwrap_or_default();
let span = Self::create_exit_span(
request_id,
&class_name,
&function_name,
&broker,
&topic_name,
)?;
// TODO: rdkafka extension call parameter injection is
// difficult, will implement later.
Ok(Box::new(span))
}),
Box::new(move |_, span, _, _| {
let mut span = span.downcast::<Span>().unwrap();
log_exception(&mut *span);
Ok(())
}),
)
}
fn create_exit_span(
request_id: Option<i64>, class_name: &str, function_name: &str, peer: &str, topic: &str,
) -> crate::Result<Span> {
let mut span = RequestContext::try_with_global_ctx(request_id, |ctx| {
Ok(ctx.create_exit_span(&format!("{}->{}", class_name, function_name), peer))
})?;
let span_object = span.span_object_mut();
span_object.set_span_layer(SpanLayer::Mq);
span_object.component_id = COMPONENT_KAFKA_PRODUCER_ID;
span_object.add_tag(TAG_MQ_BROKER, peer);
span_object.add_tag(TAG_MQ_TOPIC, topic);
span_object.add_tag(TAG_MQ_QUEUE, "");
Ok(span)
}
}
fn hack_dtor(this: &mut ZObj, new_dtor: sys::zend_object_dtor_obj_t) {
assert!(new_dtor.is_some(), "new_dtor should not be null");
let handle = this.handle();
unsafe {
let ori_dtor = (*(*this.as_mut_ptr()).handlers).dtor_obj;
DTOR_MAP.insert(handle, ori_dtor);
(*((*this.as_mut_ptr()).handlers as *mut sys::zend_object_handlers)).dtor_obj = new_dtor;
}
}
unsafe extern "C" fn producer_dtor(object: *mut sys::zend_object) {
unsafe {
let handle = ZObj::from_ptr(object).handle();
PRODUCER_BROKERS.remove(&handle);
if let Some((_, Some(dtor))) = DTOR_MAP.remove(&handle) {
dtor(object);
}
}
}
unsafe extern "C" fn producer_topic_dtor(object: *mut sys::zend_object) {
unsafe {
let handle = ZObj::from_ptr(object).handle();
TOPIC_BROKERS.remove(&handle);
if let Some((_, Some(dtor))) = DTOR_MAP.remove(&handle) {
dtor(object);
}
}
}