Visitar URL original
Add php-amqp plugin (#146) · apache/skywalking-php@83ec158 · GitHub
Skip to content

Commit 83ec158

Browse files
authored
Add php-amqp plugin (#146)
1 parent de311c9 commit 83ec158

15 files changed

Lines changed: 499 additions & 57 deletions

File tree

‎.github/workflows/rust.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ jobs:
9595

9696
- name: Install dependencies for Linux
9797
if: matrix.os == 'ubuntu-24.04'
98-
run: sudo apt-get update && sudo apt-get install -y llvm-18-dev libclang-18-dev protobuf-compiler libsasl2-dev
98+
run: sudo apt-get update && sudo apt-get install -y llvm-18-dev libclang-18-dev protobuf-compiler libsasl2-dev librabbitmq-dev
9999

100100
- name: Install protobuf for Macos
101101
if: matrix.os == 'macos-14'
@@ -110,7 +110,7 @@ jobs:
110110
bcmath, calendar, ctype, dom, exif, gettext, iconv, intl, json, mbstring,
111111
mysqli, mysqlnd, opcache, pdo, pdo_mysql, phar, posix, readline, redis,
112112
memcached, swoole-${{ matrix.flag.swoole_version }}, xml, xmlreader, xmlwriter,
113-
yaml, zip, mongodb, memcache
113+
yaml, zip, mongodb, memcache, amqp
114114
115115
- name: Setup php-fpm for Linux
116116
if: matrix.os == 'ubuntu-24.04'

‎Cargo.lock‎

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

‎Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ futures-util = "0.3.31"
6060
hostname = "0.4.1"
6161
libc = "0.2.174"
6262
once_cell = "1.21.3"
63-
phper = "0.17.2"
63+
phper = "0.17.5"
6464
prost = "0.14.1"
6565
rdkafka = { workspace = true, optional = true }
6666
skywalking = { version = "0.10.0", features = ["management"] }
@@ -81,7 +81,7 @@ fastcgi-client = "0.9.0"
8181
reqwest = { version = "0.12.22", features = ["trust-dns", "json", "stream"] }
8282

8383
[build-dependencies]
84-
phper-build = "0.15.3"
84+
phper-build = "0.15.5"
8585

8686
[lints.rust]
8787
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(phper_major_version, values("8"))'] }

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ SkyWalking PHP Agent requires SkyWalking 8.4+ and PHP 7.2+
3131
* [x] [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
3232
* [x] [Memcached](https://www.php.net/manual/en/book.memcached.php)
3333
* [x] [phpredis](https://github.com/phpredis/phpredis)
34-
* [ ] [php-amqp](https://github.com/php-amqp/php-amqp)
34+
* [x] [php-amqp](https://github.com/php-amqp/php-amqp) for Message Queuing Producer
3535
* [ ] [php-rdkafka](https://github.com/arnaud-lb/php-rdkafka)
3636
* [x] [predis](https://github.com/predis/predis)
3737
* [x] [php-amqplib](https://github.com/php-amqplib/php-amqplib) for Message Queuing Producer

‎dist-material/LICENSE‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -654,11 +654,11 @@ MulanPSL-2.0 licenses
654654
The following components are provided under the MulanPSL-2.0 License. See project link for details.
655655
The text of each license is also included in licenses/LICENSE-[project].txt.
656656

657-
https://crates.io/crates/phper/0.17.2 0.17.2 MulanPSL-2.0
658-
https://crates.io/crates/phper-alloc/0.16.1 0.16.1 MulanPSL-2.0
659-
https://crates.io/crates/phper-build/0.15.4 0.15.4 MulanPSL-2.0
660-
https://crates.io/crates/phper-macros/0.15.2 0.15.2 MulanPSL-2.0
661-
https://crates.io/crates/phper-sys/0.15.4 0.15.4 MulanPSL-2.0
657+
https://crates.io/crates/phper/0.17.5 0.17.5 MulanPSL-2.0
658+
https://crates.io/crates/phper-alloc/0.16.3 0.16.3 MulanPSL-2.0
659+
https://crates.io/crates/phper-build/0.15.6 0.15.6 MulanPSL-2.0
660+
https://crates.io/crates/phper-macros/0.15.3 0.15.3 MulanPSL-2.0
661+
https://crates.io/crates/phper-sys/0.15.6 0.15.6 MulanPSL-2.0
662662

663663
========================================================================
664664
Unicode-3.0 licenses

‎src/execute.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ fn infer_request_id(execute_data: &mut ExecuteData) -> Option<i64> {
321321
prev_execute_data_ptr = unsafe { (*prev_execute_data_ptr).prev_execute_data };
322322
continue;
323323
}
324-
let request = prev_execute_data.get_mut_parameter(0).as_mut_z_obj()?;
325-
match request.get_mut_property("fd").as_long() {
324+
let request = prev_execute_data.get_parameter(0).as_z_obj()?;
325+
match request.get_property("fd").as_long() {
326326
Some(fd) => return Some(fd),
327327
None => {
328328
error!("infer request id failed");

‎src/plugin/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16+
mod plugin_amqp;
1617
mod plugin_amqplib;
1718
mod plugin_curl;
1819
mod plugin_memcache;
@@ -48,6 +49,7 @@ static PLUGINS: Lazy<Vec<Box<DynPlugin>>> = Lazy::new(|| {
4849
Box::<plugin_predis::PredisPlugin>::default(),
4950
Box::<plugin_memcached::MemcachedPlugin>::default(),
5051
Box::<plugin_redis::RedisPlugin>::default(),
52+
Box::<plugin_amqp::AmqpPlugin>::default(),
5153
Box::<plugin_amqplib::AmqplibPlugin>::default(),
5254
Box::<plugin_mongodb::MongodbPlugin>::default(),
5355
Box::<plugin_memcache::MemcachePlugin>::default(),

‎src/plugin/plugin_amqp.rs‎

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
use super::{Plugin, log_exception};
17+
use crate::{
18+
component::COMPONENT_AMQP_PRODUCER_ID,
19+
context::{RequestContext, SW_HEADER},
20+
execute::{AfterExecuteHook, BeforeExecuteHook, get_this_mut, validate_num_args},
21+
tag::{TAG_MQ_BROKER, TAG_MQ_QUEUE, TAG_MQ_TOPIC},
22+
};
23+
use phper::{
24+
arrays::ZArray,
25+
objects::ZObj,
26+
values::{ExecuteData, ZVal},
27+
};
28+
use skywalking::{
29+
proto::v3::SpanLayer,
30+
trace::span::{HandleSpanObject, Span},
31+
};
32+
33+
#[derive(Default, Clone)]
34+
pub struct AmqpPlugin;
35+
36+
impl Plugin for AmqpPlugin {
37+
fn class_names(&self) -> Option<&'static [&'static str]> {
38+
Some(&["AMQPExchange"])
39+
}
40+
41+
fn function_name_prefix(&self) -> Option<&'static str> {
42+
None
43+
}
44+
45+
fn hook(
46+
&self, class_name: Option<&str>, function_name: &str,
47+
) -> Option<(
48+
Box<crate::execute::BeforeExecuteHook>,
49+
Box<crate::execute::AfterExecuteHook>,
50+
)> {
51+
match (class_name, function_name) {
52+
(Some(class_name @ "AMQPExchange"), function_name @ "publish") => {
53+
Some(self.hook_exchange_publish(class_name, function_name))
54+
}
55+
_ => None,
56+
}
57+
}
58+
}
59+
60+
impl AmqpPlugin {
61+
fn hook_exchange_publish(
62+
&self, class_name: &str, function_name: &str,
63+
) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
64+
let class_name = class_name.to_owned();
65+
let function_name = function_name.to_owned();
66+
(
67+
Box::new(move |request_id, execute_data| {
68+
validate_num_args(execute_data, 1)?;
69+
70+
let this = get_this_mut(execute_data)?;
71+
72+
let peer = Self::get_peer(this)?;
73+
74+
let exchange = this
75+
.call("getName", [])
76+
.ok()
77+
.and_then(|v| {
78+
v.as_z_str()
79+
.and_then(|s| s.to_str().ok())
80+
.map(ToOwned::to_owned)
81+
})
82+
.unwrap_or_default();
83+
84+
let routing_key = if execute_data.num_args() >= 2 {
85+
execute_data
86+
.get_parameter(1)
87+
.as_z_str()
88+
.and_then(|s| s.to_str().ok())
89+
.map(ToOwned::to_owned)
90+
.unwrap_or_default()
91+
} else {
92+
String::new()
93+
};
94+
95+
let span = Self::create_exit_span(
96+
request_id,
97+
&class_name,
98+
&function_name,
99+
&peer,
100+
&exchange,
101+
&routing_key,
102+
)?;
103+
104+
Self::inject_sw_header(request_id, execute_data, &peer)?;
105+
106+
Ok(Box::new(span))
107+
}),
108+
Box::new(move |_, span, _, _| {
109+
let mut span = span.downcast::<Span>().unwrap();
110+
log_exception(&mut *span);
111+
Ok(())
112+
}),
113+
)
114+
}
115+
116+
fn get_peer(this: &mut ZObj) -> crate::Result<String> {
117+
let mut channel = this.call("getChannel", [])?;
118+
let channel = channel
119+
.expect_mut_z_obj()
120+
.map_err(|e| anyhow::anyhow!("channel isn't object: {}", e))?;
121+
let mut connection = channel.call("getConnection", [])?;
122+
let connection = connection
123+
.expect_mut_z_obj()
124+
.map_err(|e| anyhow::anyhow!("connection isn't object: {}", e))?;
125+
let host = connection.call("getHost", [])?;
126+
let host = host
127+
.expect_z_str()
128+
.map_err(|e| anyhow::anyhow!("host isn't string: {}", e))?
129+
.to_str()?;
130+
let port = connection.call("getPort", [])?;
131+
let port = port.as_long().unwrap_or_default();
132+
Ok(format!("{}:{}", host, port))
133+
}
134+
135+
fn create_exit_span(
136+
request_id: Option<i64>, class_name: &str, function_name: &str, peer: &str, exchange: &str,
137+
routing_key: &str,
138+
) -> crate::Result<Span> {
139+
let mut span = RequestContext::try_with_global_ctx(request_id, |ctx| {
140+
Ok(ctx.create_exit_span(&format!("{}->{}", class_name, function_name), peer))
141+
})?;
142+
143+
let span_object = span.span_object_mut();
144+
span_object.set_span_layer(SpanLayer::Mq);
145+
span_object.component_id = COMPONENT_AMQP_PRODUCER_ID;
146+
span_object.add_tag(TAG_MQ_BROKER, peer);
147+
span_object.add_tag(TAG_MQ_TOPIC, exchange);
148+
span_object.add_tag(TAG_MQ_QUEUE, routing_key);
149+
150+
Ok(span)
151+
}
152+
153+
fn inject_sw_header(
154+
request_id: Option<i64>, execute_data: &mut ExecuteData, peer: &str,
155+
) -> crate::Result<()> {
156+
let sw_header = RequestContext::try_get_sw_header(request_id, peer)?;
157+
158+
let attributes = Self::ensure_attributes(execute_data)?;
159+
let headers = Self::ensure_headers(attributes)?;
160+
headers.insert(SW_HEADER, sw_header);
161+
162+
Ok(())
163+
}
164+
165+
fn ensure_attributes(
166+
execute_data: &mut ExecuteData,
167+
) -> crate::Result<&mut phper::arrays::ZArr> {
168+
// php-amqp parses publish() as `s|s!l!a/`, so the 4th `headers`
169+
// argument is only visible after the missing optional parameters are
170+
// materialized and `num_args` reflects that.
171+
execute_data.materialize_missing([
172+
ZVal::from(()),
173+
ZVal::from(()),
174+
ZVal::from(ZArray::new()),
175+
])?;
176+
177+
let attributes = execute_data.get_mut_parameter(3);
178+
if attributes.as_z_arr().is_none() {
179+
*attributes = ZArray::new().into();
180+
}
181+
182+
Ok(attributes
183+
.as_mut_z_arr()
184+
.ok_or_else(|| anyhow::anyhow!("attributes isn't array"))?)
185+
}
186+
187+
fn ensure_headers(
188+
attributes: &mut phper::arrays::ZArr,
189+
) -> crate::Result<&mut phper::arrays::ZArr> {
190+
let has_headers = attributes
191+
.get("headers")
192+
.and_then(|headers| headers.as_z_arr())
193+
.is_some();
194+
if !has_headers {
195+
attributes.insert("headers", ZArray::new());
196+
}
197+
198+
Ok(attributes
199+
.get_mut("headers")
200+
.and_then(|headers| headers.as_mut_z_arr())
201+
.ok_or_else(|| anyhow::anyhow!("headers isn't array"))?)
202+
}
203+
}

‎src/plugin/plugin_memcache.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl MemcachePlugin {
200200
.unwrap();
201201

202202
let key = style
203-
.get_mut_parameter(execute_data, 0)
203+
.get_parameter(execute_data, 0)
204204
.as_z_str()
205205
.and_then(|s| s.to_str().ok())
206206
.map(ToOwned::to_owned)

0 commit comments

Comments
 (0)