You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by he...@apache.org on 2022/10/27 08:09:30 UTC
[skywalking-php] branch master updated: Add plugin for memcached. (#27)
This is an automated email from the ASF dual-hosted git repository.
heyanlong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-php.git
The following commit(s) were added to refs/heads/master by this push:
new e144dea Add plugin for memcached. (#27)
e144dea is described below
commit e144deae28b07ba0d6625780ddfc749fc289e8e4
Author: jmjoy <jm...@apache.org>
AuthorDate: Thu Oct 27 16:09:24 2022 +0800
Add plugin for memcached. (#27)
* Add plugin for memcached.
---
.github/workflows/rust.yml | 2 +-
README.md | 2 +-
Vagrantfile | 1 +
docker-compose.yml | 5 +
.../service-agent/php-agent/Supported-list.md | 1 +
src/component.rs | 1 +
src/plugin/mod.rs | 2 +
src/plugin/plugin_memcached.rs | 248 +++++++++++++++++++++
tests/data/expected_context.yaml | 107 ++++++++-
tests/e2e.rs | 11 +-
src/component.rs => tests/php/fpm/memcached.php | 35 ++-
11 files changed, 402 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index aa732ec..0430487 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -85,7 +85,7 @@ jobs:
tools: php-config, composer:v2
extensions: >
bcmath, calendar, ctype, dom, exif, gettext, iconv, intl, json, mbstring,
- mysqli, mysqlnd, opcache, pdo, pdo_mysql, phar, posix, readline,
+ mysqli, mysqlnd, opcache, pdo, pdo_mysql, phar, posix, readline, memcached,
swoole-${{ matrix.version.swoole }}, xml, xmlreader, xmlwriter, yaml, zip
- name: Setup php-fpm for Linux
diff --git a/README.md b/README.md
index 66bbf84..cc757df 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ SkyWalking PHP Agent requires SkyWalking 8.4+ and PHP 7.2+
* [x] [cURL](https://www.php.net/manual/en/book.curl.php#book.curl)
* [x] [PDO](https://www.php.net/manual/en/book.pdo.php)
* [x] [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
- * [ ] [Memcached](https://www.php.net/manual/en/book.memcached.php)
+ * [x] [Memcached](https://www.php.net/manual/en/book.memcached.php)
* [ ] [phpredis](https://github.com/phpredis/phpredis)
* [ ] [php-amqp](https://github.com/php-amqp/php-amqp)
* [ ] [php-rdkafka](https://github.com/arnaud-lb/php-rdkafka)
diff --git a/Vagrantfile b/Vagrantfile
index c24e060..af9fb2c 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -22,6 +22,7 @@ Vagrant.configure("2") do |config|
config.vm.network "forwarded_port", guest: 12800, host: 12800
config.vm.network "forwarded_port", guest: 3306, host: 3306
config.vm.network "forwarded_port", guest: 6379, host: 6379
+ config.vm.network "forwarded_port", guest: 11211, host: 11211
config.vm.synced_folder ".", "/vagrant"
diff --git a/docker-compose.yml b/docker-compose.yml
index 69a3a24..99fd301 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -38,3 +38,8 @@ services:
- "6379:6379"
environment:
- REDIS_PASSWORD=password
+
+ memcached:
+ image: memcached:1.6.17
+ ports:
+ - "11211:11211"
diff --git a/docs/en/setup/service-agent/php-agent/Supported-list.md b/docs/en/setup/service-agent/php-agent/Supported-list.md
index 56f9ccf..d4227e7 100644
--- a/docs/en/setup/service-agent/php-agent/Supported-list.md
+++ b/docs/en/setup/service-agent/php-agent/Supported-list.md
@@ -12,6 +12,7 @@ The following plugins provide the distributed tracing capability.
* [cURL](https://www.php.net/manual/en/book.curl.php#book.curl)
* [PDO](https://www.php.net/manual/en/book.pdo.php)
* [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
+* [Memcached](https://www.php.net/manual/en/book.memcached.php)
## Support PHP library
diff --git a/src/component.rs b/src/component.rs
index 3e5c93f..cb6ab19 100644
--- a/src/component.rs
+++ b/src/component.rs
@@ -22,3 +22,4 @@ pub const COMPONENT_PHP_CURL_ID: i32 = 8002;
pub const COMPONENT_PHP_PDO_ID: i32 = 8003;
pub const COMPONENT_PHP_MYSQLI_ID: i32 = 8004;
pub const COMPONENT_PHP_PREDIS_ID: i32 = 8006;
+pub const COMPONENT_PHP_MEMCACHED_ID: i32 = 20;
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index 411fcec..5b16556 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -14,6 +14,7 @@
// limitations under the License.
mod plugin_curl;
+mod plugin_memcached;
mod plugin_mysqli;
mod plugin_pdo;
mod plugin_predis;
@@ -31,6 +32,7 @@ static PLUGINS: Lazy<Vec<Box<DynPlugin>>> = Lazy::new(|| {
Box::new(plugin_swoole::SwooleServerPlugin::default()),
Box::new(plugin_swoole::SwooleHttpResponsePlugin::default()),
Box::new(plugin_predis::PredisPlugin::default()),
+ Box::new(plugin_memcached::MemcachedPlugin::default()),
]
});
diff --git a/src/plugin/plugin_memcached.rs b/src/plugin/plugin_memcached.rs
new file mode 100644
index 0000000..969a756
--- /dev/null
+++ b/src/plugin/plugin_memcached.rs
@@ -0,0 +1,248 @@
+// 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;
+use crate::{
+ component::COMPONENT_PHP_MEMCACHED_ID,
+ context::RequestContext,
+ execute::{get_this_mut, AfterExecuteHook, BeforeExecuteHook},
+};
+use anyhow::{bail, Context};
+use once_cell::sync::Lazy;
+use phper::{functions::call, values::ExecuteData};
+use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
+use tracing::{debug, warn};
+
+static MEC_KEYS_COMMANDS: Lazy<Vec<String>> = Lazy::new(|| {
+ [
+ "set",
+ "setByKey",
+ "setMulti",
+ "setMultiByKey",
+ "add",
+ "addByKey",
+ "replace",
+ "replaceByKey",
+ "append",
+ "appendByKey",
+ "prepend",
+ "prependByKey",
+ "get",
+ "getByKey",
+ "getMulti",
+ "getMultiByKey",
+ "getAllKeys",
+ "delete",
+ "deleteByKey",
+ "deleteMulti",
+ "deleteMultiByKey",
+ "increment",
+ "incrementByKey",
+ "decrement",
+ "decrementByKey",
+ "getStats",
+ "isPersistent",
+ "isPristine",
+ "flush",
+ "flushBuffers",
+ "getDelayed",
+ "getDelayedByKey",
+ "fetch",
+ "fetchAll",
+ "addServer",
+ "addServers",
+ "getOption",
+ "setOption",
+ "setOptions",
+ "getResultCode",
+ "getServerList",
+ "resetServerList",
+ "getVersion",
+ "quit",
+ "setSaslAuthData",
+ "touch",
+ "touchByKey",
+ ]
+ .into_iter()
+ .map(str::to_ascii_lowercase)
+ .collect()
+});
+
+static MEC_STR_KEYS_COMMANDS: Lazy<Vec<String>> = Lazy::new(|| {
+ [
+ "set",
+ "setByKey",
+ "setMulti",
+ "setMultiByKey",
+ "add",
+ "addByKey",
+ "replace",
+ "replaceByKey",
+ "append",
+ "appendByKey",
+ "prepend",
+ "prependByKey",
+ "get",
+ "getByKey",
+ "getMulti",
+ "getMultiByKey",
+ "getAllKeys",
+ "delete",
+ "deleteByKey",
+ "deleteMulti",
+ "deleteMultiByKey",
+ "increment",
+ "incrementByKey",
+ "decrement",
+ "decrementByKey",
+ ]
+ .into_iter()
+ .map(str::to_ascii_lowercase)
+ .collect()
+});
+
+#[derive(Default, Clone)]
+pub struct MemcachedPlugin;
+
+impl Plugin for MemcachedPlugin {
+ fn class_names(&self) -> Option<&'static [&'static str]> {
+ Some(&["Memcached"])
+ }
+
+ fn function_name_prefix(&self) -> Option<&'static str> {
+ None
+ }
+
+ fn hook(
+ &self, class_name: Option<&str>, function_name: &str,
+ ) -> Option<(
+ Box<crate::execute::BeforeExecuteHook>,
+ Box<crate::execute::AfterExecuteHook>,
+ )> {
+ match (class_name, function_name) {
+ (Some(class_name @ "Memcached"), f)
+ if MEC_KEYS_COMMANDS.contains(&f.to_ascii_lowercase()) =>
+ {
+ Some(self.hook_memcached_methods(class_name, function_name))
+ }
+ _ => None,
+ }
+ }
+}
+
+impl MemcachedPlugin {
+ fn hook_memcached_methods(
+ &self, class_name: &str, function_name: &str,
+ ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
+ let class_name = class_name.to_owned();
+ let function_name = function_name.to_owned();
+ (
+ Box::new(move |request_id, execute_data| {
+ let peer = if MEC_STR_KEYS_COMMANDS.contains(&function_name.to_ascii_lowercase()) {
+ let mut f = || {
+ let key = {
+ let key = execute_data.get_parameter(0);
+ if !key.get_type_info().is_string() {
+ // The `*Multi` methods will failed here.
+ bail!("The argument key of {} isn't string", &function_name);
+ }
+ key.clone()
+ };
+ let this = get_this_mut(execute_data)?;
+ let info = this.call(&"getServerByKey".to_ascii_lowercase(), [key])?;
+ let info = info.as_z_arr().context("Server isn't array")?;
+ let host = info
+ .get("host")
+ .context("Server host not exists")?
+ .as_z_str()
+ .context("Server host isn't string")?
+ .to_str()?;
+ let port = info
+ .get("port")
+ .context("Server port not exists")?
+ .as_long()
+ .context("Server port isn't long")?;
+ Ok::<_, anyhow::Error>(format!("{}:{}", host, port))
+ };
+ match f() {
+ Ok(peer) => peer,
+ Err(err) => {
+ warn!(?err, "Get peer failed");
+ "".to_owned()
+ }
+ }
+ } else {
+ "".to_owned()
+ };
+
+ debug!(peer, "Get memcached peer");
+
+ let span = RequestContext::try_with_global_ctx(request_id, |ctx| {
+ let mut span =
+ ctx.create_exit_span(&format!("{}->{}", class_name, function_name), &peer);
+ span.with_span_object_mut(|obj| {
+ obj.set_span_layer(SpanLayer::Cache);
+ obj.component_id = COMPONENT_PHP_MEMCACHED_ID;
+ obj.add_tag("db.type", "memcached");
+
+ match get_command(execute_data, &function_name) {
+ Ok(cmd) => {
+ obj.add_tag("memcached.command", cmd);
+ }
+ Err(err) => {
+ warn!(?err, "get command failed");
+ }
+ }
+ });
+ Ok(span)
+ })?;
+
+ Ok(Box::new(span) as _)
+ }),
+ Box::new(|_, span, _, return_value| {
+ let mut span = span.downcast::<Span>().unwrap();
+ if let Some(b) = return_value.as_bool() {
+ if !b {
+ span.with_span_object_mut(|span| {
+ span.is_error = true;
+ });
+ }
+ }
+ Ok(())
+ }),
+ )
+ }
+}
+
+fn get_command(execute_data: &mut ExecuteData, function_name: &str) -> anyhow::Result<String> {
+ let num_args = execute_data.num_args();
+ let mut items = Vec::with_capacity(num_args + 1);
+ items.push(function_name.to_owned());
+
+ for i in 0..num_args {
+ let parameter = execute_data.get_parameter(i);
+ let s = if parameter.get_type_info().is_array() {
+ let result = call("json_encode", [parameter.clone()])?;
+ result.expect_z_str()?.to_str()?.to_string()
+ } else {
+ let mut parameter = parameter.clone();
+ parameter.convert_to_string();
+ parameter.expect_z_str()?.to_str()?.to_string()
+ };
+ items.push(s)
+ }
+
+ Ok(items.join(" "))
+}
diff --git a/tests/data/expected_context.yaml b/tests/data/expected_context.yaml
index a05679e..f5ca94a 100644
--- a/tests/data/expected_context.yaml
+++ b/tests/data/expected_context.yaml
@@ -15,7 +15,7 @@
segmentItems:
- serviceName: skywalking-agent-test-1
- segmentSize: 9
+ segmentSize: 10
segments:
- segmentId: "not null"
spans:
@@ -576,6 +576,111 @@ segmentItems:
- {key: url, value: /mysqli.php}
- {key: http.method, value: GET}
- {key: http.status_code, value: '200'}
+ - segmentId: 'not null'
+ spans:
+ - operationName: Memcached->addServer
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: ""
+ skipAnalysis: false
+ tags:
+ - { key: db.type, value: memcached }
+ - { key: memcached.command, value: addServer 127.0.0.1 11211 }
+ - operationName: Memcached->set
+ parentSpanId: 0
+ spanId: 2
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: db.type, value: memcached }
+ - { key: memcached.command, value: set foo Hello! }
+ - operationName: Memcached->set
+ parentSpanId: 0
+ spanId: 3
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: db.type, value: memcached }
+ - { key: memcached.command, value: set bar Memcached... }
+ - operationName: Memcached->get
+ parentSpanId: 0
+ spanId: 4
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: db.type, value: memcached }
+ - { key: memcached.command, value: get foo }
+ - operationName: Memcached->get
+ parentSpanId: 0
+ spanId: 5
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: db.type, value: memcached }
+ - { key: memcached.command, value: get bar }
+ - operationName: Memcached->setMulti
+ parentSpanId: 0
+ spanId: 6
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: ""
+ skipAnalysis: false
+ tags:
+ - { key: db.type, value: memcached }
+ - {
+ key: memcached.command,
+ value:
+ 'setMulti {"key1":"value1","key2":"value2","key3":"value3"}',
+ }
+ - operationName: GET:/memcached.php
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 8001
+ isError: false
+ spanType: Entry
+ peer: ""
+ skipAnalysis: false
+ tags:
+ - { key: url, value: /memcached.php }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: "200" }
- serviceName: skywalking-agent-test-2
segmentSize: 1
segments:
diff --git a/tests/e2e.rs b/tests/e2e.rs
index cbdaf9c..a15671b 100644
--- a/tests/e2e.rs
+++ b/tests/e2e.rs
@@ -49,9 +49,10 @@ async fn e2e() {
async fn run_e2e() {
request_fpm_curl().await;
request_fpm_pdo().await;
+ request_fpm_predis().await;
request_fpm_mysqli().await;
+ request_fpm_memcached().await;
request_swoole_curl().await;
- request_fpm_predis().await;
sleep(Duration::from_secs(3)).await;
request_collector_validate().await;
}
@@ -88,6 +89,14 @@ async fn request_fpm_predis() {
.await;
}
+async fn request_fpm_memcached() {
+ request_common(
+ HTTP_CLIENT.get(format!("http://{}/memcached.php", PROXY_SERVER_1_ADDRESS)),
+ "ok",
+ )
+ .await;
+}
+
async fn request_swoole_curl() {
request_common(
HTTP_CLIENT.get(format!("http://{}/curl", SWOOLE_SERVER_1_ADDRESS)),
diff --git a/src/component.rs b/tests/php/fpm/memcached.php
similarity index 59%
copy from src/component.rs
copy to tests/php/fpm/memcached.php
index 3e5c93f..dd59904 100644
--- a/src/component.rs
+++ b/tests/php/fpm/memcached.php
@@ -1,3 +1,5 @@
+<?php
+
// 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.
@@ -11,14 +13,29 @@
// 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..
+// limitations under the License.
+
+
+use Webmozart\Assert\Assert;
+
+require_once dirname(__DIR__) . "/vendor/autoload.php";
+
+{
+ $mc = new Memcached();
+ $mc->addServer("127.0.0.1", 11211);
+
+ $mc->set("foo", "Hello!");
+ $mc->set("bar", "Memcached...");
+
+ Assert::same($mc->get("foo"), 'Hello!');
+ Assert::same($mc->get("bar"), "Memcached...");
-//! Component ID
-//!
-//! <https://github.com/apache/skywalking/blob/014861535015745ae3f7b99acd7d14500b3b3927/oap-server/server-starter/src/main/resources/component-libraries.yml>
+ $items = array(
+ 'key1' => 'value1',
+ 'key2' => 'value2',
+ 'key3' => 'value3'
+ );
+ $mc->setMulti($items);
+}
-pub const COMPONENT_PHP_ID: i32 = 8001;
-pub const COMPONENT_PHP_CURL_ID: i32 = 8002;
-pub const COMPONENT_PHP_PDO_ID: i32 = 8003;
-pub const COMPONENT_PHP_MYSQLI_ID: i32 = 8004;
-pub const COMPONENT_PHP_PREDIS_ID: i32 = 8006;
+echo "ok";