Add Memcache plugin. (#93)
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 4ca62e4..bcfbcad 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -119,7 +119,7 @@
bcmath, calendar, ctype, dom, exif, gettext, iconv, intl, json, mbstring,
mysqli, mysqlnd, opcache, pdo, pdo_mysql, phar, posix, readline, redis,
memcached, swoole-${{ matrix.flag.swoole_version }}, xml, xmlreader, xmlwriter,
- yaml, zip, mongodb
+ yaml, zip, mongodb, memcache
- name: Setup php-fpm for Linux
if: matrix.os == 'ubuntu-20.04'
diff --git a/Cargo.lock b/Cargo.lock
index eedb817..59476f4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1094,6 +1094,15 @@
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
name = "matches"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1719,8 +1728,17 @@
dependencies = [
"aho-corasick",
"memchr",
- "regex-automata",
- "regex-syntax",
+ "regex-automata 0.3.4",
+ "regex-syntax 0.7.4",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
]
[[package]]
@@ -1731,11 +1749,17 @@
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax",
+ "regex-syntax 0.7.4",
]
[[package]]
name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
@@ -2540,10 +2564,14 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
+ "matchers",
"nu-ansi-term",
+ "once_cell",
+ "regex",
"sharded-slab",
"smallvec",
"thread_local",
+ "tracing",
"tracing-core",
"tracing-log",
]
diff --git a/Cargo.toml b/Cargo.toml
index b190ec0..51c23e1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,7 +56,7 @@
tokio-stream = "0.1.14"
tonic = { version = "0.8.3", features = ["tls"] }
tracing = { version = "0.1.37", features = ["attributes"] }
-tracing-subscriber = "0.3.17"
+tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
url = "2.4.0"
[dev-dependencies]
diff --git a/README.md b/README.md
index 651bb8d..a256adb 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@
* [x] [predis](https://github.com/predis/predis)
* [x] [php-amqplib](https://github.com/php-amqplib/php-amqplib) for Message Queuing Producer
* [x] [MongoDB](https://www.php.net/manual/en/set.mongodb.php)
+ * [x] [Memcache](https://www.php.net/manual/en/book.memcache.php)
* Swoole Ecosystem
diff --git a/dist-material/LICENSE b/dist-material/LICENSE
index 93f09fe..0631fc3 100644
--- a/dist-material/LICENSE
+++ b/dist-material/LICENSE
@@ -392,6 +392,7 @@
https://crates.io/crates/rand_core/0.6.4 0.6.4 Apache-2.0 OR MIT
https://crates.io/crates/regex/1.9.1 1.9.1 Apache-2.0 OR MIT
https://crates.io/crates/regex-automata/0.3.4 0.3.4 Apache-2.0 OR MIT
+ https://crates.io/crates/regex-syntax/0.6.29 0.6.29 Apache-2.0 OR MIT
https://crates.io/crates/regex-syntax/0.7.4 0.7.4 Apache-2.0 OR MIT
https://crates.io/crates/reqwest/0.11.18 0.11.18 Apache-2.0 OR MIT
https://crates.io/crates/resolv-conf/0.7.0 0.7.0 Apache-2.0 OR MIT
@@ -530,6 +531,7 @@
https://crates.io/crates/http-body/0.4.5 0.4.5 MIT
https://crates.io/crates/hyper/0.14.27 0.14.27 MIT
https://crates.io/crates/is-terminal/0.4.9 0.4.9 MIT
+ https://crates.io/crates/matchers/0.1.0 0.1.0 MIT
https://crates.io/crates/matches/0.1.10 0.1.10 MIT
https://crates.io/crates/mio/0.8.8 0.8.8 MIT
https://crates.io/crates/nom/7.1.3 7.1.3 MIT
@@ -592,6 +594,7 @@
https://crates.io/crates/globset/0.4.12 0.4.12 MIT OR Unlicense
https://crates.io/crates/ignore/0.4.20 0.4.20 MIT OR Unlicense
https://crates.io/crates/memchr/2.5.0 2.5.0 MIT OR Unlicense
+ https://crates.io/crates/regex-automata/0.1.10 0.1.10 MIT OR Unlicense
https://crates.io/crates/same-file/1.0.6 1.0.6 MIT OR Unlicense
https://crates.io/crates/walkdir/2.3.3 2.3.3 MIT OR Unlicense
https://crates.io/crates/winapi-util/0.1.5 0.1.5 MIT OR Unlicense
diff --git a/dist-material/licenses/LICENSE-matchers.txt b/dist-material/licenses/LICENSE-matchers.txt
new file mode 100644
index 0000000..5858b17
--- /dev/null
+++ b/dist-material/licenses/LICENSE-matchers.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Eliza Weisman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dist-material/licenses/LICENSE-regex-automata.txt b/dist-material/licenses/LICENSE-regex-automata.txt
new file mode 100644
index 0000000..39d4bdb
--- /dev/null
+++ b/dist-material/licenses/LICENSE-regex-automata.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2014 The Rust Project Developers
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
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 9ad8476..c548229 100644
--- a/docs/en/setup/service-agent/php-agent/Supported-list.md
+++ b/docs/en/setup/service-agent/php-agent/Supported-list.md
@@ -15,6 +15,7 @@
* [Memcached](https://www.php.net/manual/en/book.memcached.php)
* [phpredis](https://github.com/phpredis/phpredis)
* [MongoDB](https://www.php.net/manual/en/set.mongodb.php)
+* [Memcache](https://www.php.net/manual/en/book.memcache.php)
## Supported PHP library
diff --git a/src/module.rs b/src/module.rs
index 37b82b5..93f0cc6 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -36,7 +36,7 @@
time::SystemTime,
};
use tracing::{debug, error, info, metadata::LevelFilter};
-use tracing_subscriber::FmtSubscriber;
+use tracing_subscriber::{EnvFilter, FmtSubscriber};
static IS_ENABLE: Lazy<bool> = Lazy::new(|| {
if !ini_get::<bool>(SKYWALKING_AGENT_ENABLE) {
@@ -247,8 +247,10 @@
let file = open_options.open(path)?;
+ let filter = EnvFilter::new(format!("info,skywalking_agent={}", log_level));
+
let subscriber = FmtSubscriber::builder()
- .with_max_level(log_level)
+ .with_env_filter(filter)
.with_ansi(false)
.with_writer(file)
.finish();
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index a9264de..dbbcdd6 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -15,6 +15,7 @@
mod plugin_amqplib;
mod plugin_curl;
+mod plugin_memcache;
mod plugin_memcached;
mod plugin_mongodb;
mod plugin_mysqli;
@@ -22,6 +23,7 @@
mod plugin_predis;
mod plugin_redis;
mod plugin_swoole;
+mod style;
use crate::execute::{AfterExecuteHook, BeforeExecuteHook};
use once_cell::sync::Lazy;
@@ -43,6 +45,7 @@
Box::<plugin_redis::RedisPlugin>::default(),
Box::<plugin_amqplib::AmqplibPlugin>::default(),
Box::<plugin_mongodb::MongodbPlugin>::default(),
+ Box::<plugin_memcache::MemcachePlugin>::default(),
]
});
diff --git a/src/plugin/plugin_memcache.rs b/src/plugin/plugin_memcache.rs
new file mode 100644
index 0000000..84ab071
--- /dev/null
+++ b/src/plugin/plugin_memcache.rs
@@ -0,0 +1,319 @@
+// 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::{log_exception, style::ApiStyle, Plugin};
+use crate::{
+ component::COMPONENT_PHP_MEMCACHED_ID,
+ context::RequestContext,
+ execute::{AfterExecuteHook, BeforeExecuteHook, Noop},
+ tag::{CacheOp, TAG_CACHE_CMD, TAG_CACHE_KEY, TAG_CACHE_OP, TAG_CACHE_TYPE},
+};
+use dashmap::DashMap;
+use once_cell::sync::Lazy;
+use phper::{
+ arrays::IterKey,
+ objects::ZObj,
+ values::{ExecuteData, ZVal},
+};
+use skywalking::{
+ proto::v3::SpanLayer,
+ trace::span::{HandleSpanObject, Span},
+};
+use std::{any::Any, collections::HashMap};
+use tracing::{debug, error, instrument, warn};
+
+static PEER_MAP: Lazy<DashMap<u32, String>> = Lazy::new(Default::default);
+
+/// The method parameters is empty.
+static MEMCACHE_EMPTY_METHOD_MAPPING: Lazy<HashMap<&str, TagInfo<'static>>> =
+ Lazy::new(|| [("flush", TagInfo::new(None, None))].into_iter().collect());
+
+/// The method first parameter is key.
+static MEMCACHE_KEY_METHOD_MAPPING: Lazy<HashMap<&str, TagInfo<'static>>> = Lazy::new(|| {
+ [
+ ("set", TagInfo::new(Some("set"), Some(CacheOp::Write))),
+ ("add", TagInfo::new(Some("add"), Some(CacheOp::Write))),
+ (
+ "replace",
+ TagInfo::new(Some("replace"), Some(CacheOp::Write)),
+ ),
+ ("get", TagInfo::new(Some("get"), Some(CacheOp::Read))),
+ ("delete", TagInfo::new(Some("delete"), Some(CacheOp::Write))),
+ (
+ "increment",
+ TagInfo::new(Some("increment"), Some(CacheOp::Write)),
+ ),
+ (
+ "decrement",
+ TagInfo::new(Some("decrement"), Some(CacheOp::Write)),
+ ),
+ ]
+ .into_iter()
+ .collect()
+});
+
+struct TagInfo<'a> {
+ cmd: Option<&'a str>,
+ op: Option<CacheOp>,
+}
+
+impl<'a> TagInfo<'a> {
+ #[inline]
+ fn new(cmd: Option<&'a str>, op: Option<CacheOp>) -> Self {
+ Self { cmd, op }
+ }
+}
+
+#[derive(Default, Clone)]
+pub struct MemcachePlugin;
+
+impl Plugin for MemcachePlugin {
+ fn class_names(&self) -> Option<&'static [&'static str]> {
+ Some(&["Memcache", "MemcachePool"])
+ }
+
+ fn function_name_prefix(&self) -> Option<&'static str> {
+ Some("memcache_")
+ }
+
+ fn hook(
+ &self, class_name: Option<&str>, function_name: &str,
+ ) -> Option<(
+ Box<crate::execute::BeforeExecuteHook>,
+ Box<crate::execute::AfterExecuteHook>,
+ )> {
+ let lowercase_function_name = function_name.to_ascii_lowercase();
+ let function_name = function_name.to_owned();
+
+ match (class_name, &*lowercase_function_name) {
+ (Some("Memcache" | "MemcachePool"), "connect" | "addserver" | "close") => {
+ Some(self.hook_memcache_server(
+ class_name.map(ToOwned::to_owned),
+ function_name,
+ ApiStyle::OO,
+ ))
+ }
+ (None, "memcache_add_server" | "memcache_close") => {
+ Some(self.hook_memcache_server(None, function_name, ApiStyle::Procedural))
+ }
+ (Some("Memcache" | "MemcachePool"), f)
+ if MEMCACHE_EMPTY_METHOD_MAPPING.contains_key(f) =>
+ {
+ Some(self.hook_memcache_empty_methods(
+ class_name.map(ToOwned::to_owned),
+ function_name,
+ ApiStyle::OO,
+ ))
+ }
+ (None, f) if MEMCACHE_EMPTY_METHOD_MAPPING.contains_key(&f["memcache_".len()..]) => {
+ Some(self.hook_memcache_empty_methods(None, function_name, ApiStyle::Procedural))
+ }
+ (Some("Memcache" | "MemcachePool"), f)
+ if MEMCACHE_KEY_METHOD_MAPPING.contains_key(f) =>
+ {
+ Some(self.hook_memcache_key_methods(
+ class_name.map(ToOwned::to_owned),
+ function_name,
+ ApiStyle::OO,
+ ))
+ }
+ (None, f) if MEMCACHE_KEY_METHOD_MAPPING.contains_key(&f["memcache_".len()..]) => {
+ Some(self.hook_memcache_key_methods(None, function_name, ApiStyle::Procedural))
+ }
+ _ => None,
+ }
+ }
+}
+
+impl MemcachePlugin {
+ fn hook_memcache_server(
+ &self, class_name: Option<String>, function_name: String, style: ApiStyle,
+ ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
+ (
+ Box::new(move |_, execute_data| {
+ let this = style.get_this_mut(execute_data)?;
+ let handle = this.handle();
+ PEER_MAP.remove(&handle);
+
+ debug!(
+ handle,
+ ?class_name,
+ function_name,
+ "remove peers cache when server added"
+ );
+
+ Ok(Box::new(()))
+ }),
+ Noop::noop(),
+ )
+ }
+
+ #[instrument(skip_all)]
+ fn hook_memcache_empty_methods(
+ &self, class_name: Option<String>, function_name: String, style: ApiStyle,
+ ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
+ (
+ Box::new(move |request_id, execute_data| {
+ let tag_info = MEMCACHE_EMPTY_METHOD_MAPPING
+ .get(&*get_tag_key(class_name.as_deref(), &function_name))
+ .unwrap();
+
+ let this = style.get_this_mut(execute_data)?;
+ let peer = get_peer(this);
+
+ let span = create_exit_span(
+ style,
+ request_id,
+ class_name.as_deref(),
+ &function_name,
+ &peer,
+ tag_info,
+ None,
+ )?;
+
+ Ok(Box::new(span))
+ }),
+ Box::new(after_hook),
+ )
+ }
+
+ #[instrument(skip_all)]
+ fn hook_memcache_key_methods(
+ &self, class_name: Option<String>, function_name: String, style: ApiStyle,
+ ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
+ (
+ Box::new(move |request_id, execute_data| {
+ let tag_info = MEMCACHE_KEY_METHOD_MAPPING
+ .get(&*get_tag_key(class_name.as_deref(), &function_name))
+ .unwrap();
+
+ let key = style
+ .get_mut_parameter(execute_data, 0)
+ .as_z_str()
+ .and_then(|s| s.to_str().ok())
+ .map(ToOwned::to_owned)
+ .unwrap_or_default();
+
+ let this = style.get_this_mut(execute_data)?;
+ let peer = get_peer(this);
+
+ let span = create_exit_span(
+ style,
+ request_id,
+ class_name.as_deref(),
+ &function_name,
+ &peer,
+ tag_info,
+ Some(&key),
+ )?;
+
+ Ok(Box::new(span))
+ }),
+ Box::new(after_hook),
+ )
+ }
+}
+
+#[instrument(skip_all)]
+fn after_hook(
+ _: Option<i64>, span: Box<dyn Any>, _: &mut ExecuteData, return_value: &mut ZVal,
+) -> crate::Result<()> {
+ let mut span = span.downcast::<Span>().expect("Downcast to Span failed");
+
+ if let Some(b) = return_value.as_bool() {
+ if !b {
+ span.span_object_mut().is_error = true;
+ }
+ }
+
+ log_exception(&mut *span);
+
+ Ok(())
+}
+
+fn create_exit_span(
+ style: ApiStyle, request_id: Option<i64>, class_name: Option<&str>, function_name: &str,
+ remote_peer: &str, tag_info: &TagInfo<'_>, key: Option<&str>,
+) -> anyhow::Result<Span> {
+ RequestContext::try_with_global_ctx(request_id, |ctx| {
+ let mut span = ctx.create_exit_span(
+ &style.generate_peer_name(class_name, function_name),
+ remote_peer,
+ );
+
+ let span_object = span.span_object_mut();
+ span_object.set_span_layer(SpanLayer::Cache);
+ span_object.component_id = COMPONENT_PHP_MEMCACHED_ID;
+ span_object.add_tag(TAG_CACHE_TYPE, "memcache");
+ if let Some(cmd) = tag_info.cmd {
+ span_object.add_tag(TAG_CACHE_CMD, cmd);
+ }
+ if let Some(op) = &tag_info.op {
+ span_object.add_tag(TAG_CACHE_OP, op.to_string());
+ };
+ if let Some(key) = key {
+ span_object.add_tag(TAG_CACHE_KEY, key)
+ }
+
+ Ok(span)
+ })
+}
+
+fn get_peer(this: &mut ZObj) -> String {
+ let handle = this.handle();
+
+ PEER_MAP
+ .entry(handle)
+ .or_insert_with(|| {
+ debug!(
+ handle,
+ "start to call {:?}::getExtendedStats method",
+ this.get_class().get_name()
+ );
+ let stats = match this.call("getExtendedStats", []) {
+ Ok(stats) => stats,
+ Err(err) => {
+ error!(
+ ?err,
+ "call {:?}::getExtendedStats method failed",
+ this.get_class().get_name()
+ );
+ return "".to_owned();
+ }
+ };
+
+ stats
+ .as_z_arr()
+ .map(|arr| {
+ arr.iter()
+ .map(|(key, _)| match key {
+ IterKey::Index(i) => i.to_string(),
+ IterKey::ZStr(s) => s.to_str().unwrap_or_default().to_string(),
+ })
+ .collect::<Vec<_>>()
+ .join(",")
+ })
+ .unwrap_or_default()
+ })
+ .value()
+ .clone()
+}
+
+fn get_tag_key(class_name: Option<&str>, function_name: &str) -> String {
+ match class_name {
+ Some(_) => function_name.to_ascii_lowercase(),
+ None => function_name["memcache_".len()..].to_string(),
+ }
+}
diff --git a/src/plugin/style.rs b/src/plugin/style.rs
new file mode 100644
index 0000000..d06b828
--- /dev/null
+++ b/src/plugin/style.rs
@@ -0,0 +1,68 @@
+// 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 crate::execute::{get_this_mut, validate_num_args};
+use anyhow::Context;
+use phper::{
+ objects::ZObj,
+ values::{ExecuteData, ZVal},
+};
+
+/// Api style.
+#[derive(Clone, Copy)]
+pub enum ApiStyle {
+ /// Object-oriented.
+ OO,
+ /// Procedural.
+ Procedural,
+}
+
+impl ApiStyle {
+ pub fn get_this_mut(self, execute_data: &mut ExecuteData) -> anyhow::Result<&mut ZObj> {
+ match self {
+ ApiStyle::OO => get_this_mut(execute_data),
+ ApiStyle::Procedural => execute_data
+ .get_mut_parameter(0)
+ .as_mut_z_obj()
+ .context("first argument isn't object"),
+ }
+ }
+
+ pub fn get_mut_parameter(self, execute_data: &mut ExecuteData, index: usize) -> &mut ZVal {
+ let index = match self {
+ ApiStyle::OO => index,
+ ApiStyle::Procedural => index + 1,
+ };
+ execute_data.get_mut_parameter(index)
+ }
+
+ #[allow(dead_code)]
+ pub fn validate_num_args(
+ self, execute_data: &mut ExecuteData, num: usize,
+ ) -> anyhow::Result<()> {
+ let num = match self {
+ ApiStyle::OO => num,
+ ApiStyle::Procedural => num + 1,
+ };
+ validate_num_args(execute_data, num)
+ }
+
+ pub fn generate_peer_name(self, class_name: Option<&str>, function_name: &str) -> String {
+ match self {
+ ApiStyle::OO => format!("{}->{}", class_name.unwrap_or_default(), function_name),
+ ApiStyle::Procedural => function_name.to_owned(),
+ }
+ }
+}
diff --git a/tests/data/expected_context.yaml b/tests/data/expected_context.yaml
index 3b45f41..3729f39 100644
--- a/tests/data/expected_context.yaml
+++ b/tests/data/expected_context.yaml
@@ -15,7 +15,7 @@
segmentItems:
- serviceName: skywalking-agent-test-1
- segmentSize: 18
+ segmentSize: 19
segments:
- segmentId: "not null"
spans:
@@ -1253,6 +1253,151 @@
- { key: url, value: "http://127.0.0.1:9011/mongodb.php" }
- { key: http.method, value: GET }
- { key: http.status_code, value: "200" }
+ - segmentId: "not null"
+ spans:
+ - operationName: MemcachePool->set
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: foo }
+ - operationName: MemcachePool->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: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: bar }
+ - operationName: MemcachePool->get
+ 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: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: foo }
+ - operationName: MemcachePool->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: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: bar }
+ - operationName: MemcachePool->get
+ parentSpanId: 0
+ spanId: 5
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: true
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: not-exists }
+ - operationName: memcache_set
+ parentSpanId: 0
+ spanId: 6
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: foo }
+ - operationName: memcache_get
+ parentSpanId: 0
+ spanId: 7
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: foo }
+ - operationName: memcache_get
+ parentSpanId: 0
+ spanId: 8
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: true
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: not-exists }
+ - operationName: GET:/memcache.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: "http://127.0.0.1:9011/memcache.php" }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: "200" }
- serviceName: skywalking-agent-test-2
segmentSize: 1
segments:
@@ -1375,7 +1520,7 @@
- { key: http.method, value: GET }
- { key: http.status_code, value: "200" }
- serviceName: skywalking-agent-test-2-swoole
- segmentSize: 9
+ segmentSize: 10
segments:
- segmentId: "not null"
spans:
@@ -1779,3 +1924,52 @@
- { key: url, value: "http://127.0.0.1:9502/mongodb" }
- { key: http.method, value: GET }
- { key: http.status_code, value: "200" }
+ - segmentId: "not null"
+ spans:
+ - operationName: MemcachePool->set
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 20
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:11211
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: foo000 }
+ - operationName: MemcachePool->get
+ 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: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: foo000 }
+ - operationName: GET:/memcache
+ 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: "http://127.0.0.1:9502/memcache" }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: "200" }
diff --git a/tests/e2e.rs b/tests/e2e.rs
index 90ad6e3..9c8854b 100644
--- a/tests/e2e.rs
+++ b/tests/e2e.rs
@@ -57,6 +57,7 @@
request_fpm_redis().await;
request_fpm_rabbitmq().await;
request_fpm_mongodb().await;
+ request_fpm_memcache().await;
request_swoole_curl().await;
request_swoole_2_curl().await;
request_swoole_2_pdo().await;
@@ -65,6 +66,7 @@
request_swoole_2_redis().await;
request_swoole_2_predis().await;
request_swoole_2_mongodb().await;
+ request_swoole_2_memcache().await;
sleep(Duration::from_secs(3)).await;
request_collector_validate().await;
}
@@ -150,6 +152,14 @@
.await;
}
+async fn request_fpm_memcache() {
+ request_common(
+ HTTP_CLIENT.get(format!("http://{}/memcache.php", PROXY_SERVER_1_ADDRESS)),
+ "ok",
+ )
+ .await;
+}
+
async fn request_swoole_curl() {
request_common(
HTTP_CLIENT.get(format!("http://{}/curl", SWOOLE_SERVER_1_ADDRESS)),
@@ -214,6 +224,14 @@
.await;
}
+async fn request_swoole_2_memcache() {
+ request_common(
+ HTTP_CLIENT.get(format!("http://{}/memcache", SWOOLE_SERVER_2_ADDRESS)),
+ "ok",
+ )
+ .await;
+}
+
async fn request_collector_validate() {
request_common(
HTTP_CLIENT
diff --git a/tests/php/fpm/memcache.php b/tests/php/fpm/memcache.php
new file mode 100644
index 0000000..c3d9b78
--- /dev/null
+++ b/tests/php/fpm/memcache.php
@@ -0,0 +1,46 @@
+<?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.
+// 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 Webmozart\Assert\Assert;
+
+require_once dirname(__DIR__) . "/vendor/autoload.php";
+
+{
+ $mc = new Memcache();
+ $mc->connect("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...");
+
+ Assert::false($mc->get("not-exists"));
+}
+
+{
+ $mc = memcache_connect("127.0.0.1", 11211);
+
+ memcache_set($mc, "foo", "Hello!!");
+
+ Assert::same(memcache_get($mc, "foo"), 'Hello!!');
+
+ Assert::false(memcache_get($mc, "not-exists"));
+}
+
+echo "ok";
diff --git a/tests/php/swoole/main.2.php b/tests/php/swoole/main.2.php
index 9d8f0d8..8117da8 100644
--- a/tests/php/swoole/main.2.php
+++ b/tests/php/swoole/main.2.php
@@ -106,6 +106,16 @@
}
break;
+ case '/memcache':
+ {
+ $mc = new Memcache();
+ $mc->addServer("127.0.0.1", 11211);
+
+ $mc->set("foo000", "bar000");
+ Assert::same($mc->get("foo000"), 'bar000');
+ }
+ break;
+
default:
throw new DomainException("Unknown operation");
}