Adapt virtual cache. (#32)
diff --git a/src/plugin/plugin_memcached.rs b/src/plugin/plugin_memcached.rs
index 969a756..3c0716c 100644
--- a/src/plugin/plugin_memcached.rs
+++ b/src/plugin/plugin_memcached.rs
@@ -13,106 +13,131 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use std::{any::Any, collections::HashMap};
+
use super::Plugin;
use crate::{
component::COMPONENT_PHP_MEMCACHED_ID,
context::RequestContext,
+ exception_frame::ExceptionFrame,
execute::{get_this_mut, AfterExecuteHook, BeforeExecuteHook},
+ tag::{CacheOp, TAG_CACHE_CMD, TAG_CACHE_KEY, TAG_CACHE_OP, TAG_CACHE_TYPE},
};
-use anyhow::{bail, Context};
+use anyhow::Context;
use once_cell::sync::Lazy;
-use phper::{functions::call, values::ExecuteData};
+use phper::{
+ objects::ZObj,
+ values::{ExecuteData, ZVal},
+};
use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
-use tracing::{debug, warn};
+use tracing::{debug, instrument, warn};
-static MEC_KEYS_COMMANDS: Lazy<Vec<String>> = Lazy::new(|| {
+/// The method parameters is empty.
+static MEMCACHE_EMPTY_METHOD_MAPPING: Lazy<HashMap<&str, TagInfo<'static>>> = 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",
+ ("getallkeys", TagInfo::new(None, None)),
+ ("getstats", TagInfo::new(Some("stats"), None)),
+ ("flush", TagInfo::new(None, None)),
+ ("getversion", TagInfo::new(Some("version"), None)),
]
.into_iter()
- .map(str::to_ascii_lowercase)
.collect()
});
-static MEC_STR_KEYS_COMMANDS: Lazy<Vec<String>> = Lazy::new(|| {
+/// The method first parameter is key.
+static MEMCACHE_KEY_METHOD_MAPPING: Lazy<HashMap<&str, TagInfo<'static>>> = 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",
+ ("set", TagInfo::new(Some("set"), Some(CacheOp::Write))),
+ ("setmulti", TagInfo::new(Some("set"), Some(CacheOp::Write))),
+ ("add", TagInfo::new(Some("add"), Some(CacheOp::Write))),
+ (
+ "replace",
+ TagInfo::new(Some("replace"), Some(CacheOp::Write)),
+ ),
+ ("append", TagInfo::new(Some("append"), Some(CacheOp::Write))),
+ (
+ "prepend",
+ TagInfo::new(Some("prepend"), Some(CacheOp::Write)),
+ ),
+ ("get", TagInfo::new(Some("get"), Some(CacheOp::Read))),
+ ("getmulti", TagInfo::new(Some("get"), Some(CacheOp::Read))),
+ ("delete", TagInfo::new(Some("delete"), Some(CacheOp::Write))),
+ (
+ "deletemulti",
+ TagInfo::new(Some("deleteMulti"), Some(CacheOp::Write)),
+ ),
+ (
+ "increment",
+ TagInfo::new(Some("increment"), Some(CacheOp::Write)),
+ ),
+ (
+ "decrement",
+ TagInfo::new(Some("decrement"), Some(CacheOp::Write)),
+ ),
]
.into_iter()
- .map(str::to_ascii_lowercase)
.collect()
});
+/// The method first parameter is server key and second parameter is key.
+static MEMCACHE_SERVER_KEY_METHOD_MAPPING: Lazy<HashMap<&str, TagInfo<'static>>> =
+ Lazy::new(|| {
+ [
+ ("setByKey", TagInfo::new(Some("set"), Some(CacheOp::Write))),
+ (
+ "setMultiByKey",
+ TagInfo::new(Some("set"), Some(CacheOp::Write)),
+ ),
+ ("addByKey", TagInfo::new(Some("add"), Some(CacheOp::Write))),
+ (
+ "replaceByKey",
+ TagInfo::new(Some("replace"), Some(CacheOp::Write)),
+ ),
+ (
+ "appendByKey",
+ TagInfo::new(Some("append"), Some(CacheOp::Write)),
+ ),
+ (
+ "prependByKey",
+ TagInfo::new(Some("prepend"), Some(CacheOp::Write)),
+ ),
+ ("getByKey", TagInfo::new(Some("get"), Some(CacheOp::Read))),
+ (
+ "getMultiByKey",
+ TagInfo::new(Some("get"), Some(CacheOp::Read)),
+ ),
+ (
+ "deleteByKey",
+ TagInfo::new(Some("delete"), Some(CacheOp::Write)),
+ ),
+ (
+ "deleteMultiByKey",
+ TagInfo::new(Some("deleteMulti"), Some(CacheOp::Write)),
+ ),
+ (
+ "incrementByKey",
+ TagInfo::new(Some("increment"), Some(CacheOp::Write)),
+ ),
+ (
+ "decrementByKey",
+ TagInfo::new(Some("decrement"), Some(CacheOp::Write)),
+ ),
+ ]
+ .into_iter()
+ .collect()
+ });
+
+struct TagInfo<'a> {
+ cmd: Option<&'a str>,
+ op: Option<CacheOp>,
+}
+
+impl<'a> TagInfo<'a> {
+ fn new(cmd: Option<&'a str>, op: Option<CacheOp>) -> Self {
+ Self { cmd, op }
+ }
+}
+
#[derive(Default, Clone)]
pub struct MemcachedPlugin;
@@ -133,9 +158,19 @@
)> {
match (class_name, function_name) {
(Some(class_name @ "Memcached"), f)
- if MEC_KEYS_COMMANDS.contains(&f.to_ascii_lowercase()) =>
+ if MEMCACHE_EMPTY_METHOD_MAPPING.contains_key(&*f.to_ascii_lowercase()) =>
{
- Some(self.hook_memcached_methods(class_name, function_name))
+ Some(self.hook_memcached_empty_methods(class_name, function_name))
+ }
+ (Some(class_name @ "Memcached"), f)
+ if MEMCACHE_KEY_METHOD_MAPPING.contains_key(&*f.to_ascii_lowercase()) =>
+ {
+ Some(self.hook_memcached_key_methods(class_name, function_name))
+ }
+ (Some(class_name @ "Memcached"), f)
+ if MEMCACHE_SERVER_KEY_METHOD_MAPPING.contains_key(&*f.to_ascii_lowercase()) =>
+ {
+ Some(self.hook_memcached_server_key_methods(class_name, function_name))
}
_ => None,
}
@@ -143,106 +178,214 @@
}
impl MemcachedPlugin {
- fn hook_memcached_methods(
+ #[instrument(skip_all)]
+ fn hook_memcached_empty_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, _| {
+ let tag_info = MEMCACHE_EMPTY_METHOD_MAPPING
+ .get(&*function_name.to_ascii_lowercase())
+ .unwrap();
+
+ let span =
+ create_exit_span(request_id, &class_name, &function_name, "", tag_info, None)?;
+
+ Ok(Box::new(span))
+ }),
+ Box::new(after_hook),
+ )
+ }
+
+ #[instrument(skip_all)]
+ fn hook_memcached_key_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()
- }
+ let key = {
+ let key = execute_data.get_parameter(0);
+ if key.get_type_info().is_string() {
+ Some(key.clone())
+ } else {
+ // The `*Multi` methods will failed here.
+ warn!("The argument key of {} isn't string", &function_name);
+ None
}
- } else {
- "".to_owned()
};
+ let key_str = key
+ .as_ref()
+ .and_then(|key| key.as_z_str())
+ .and_then(|key| key.to_str().ok())
+ .map(ToOwned::to_owned);
+
+ let this = get_this_mut(execute_data)?;
+
+ let peer = key.map(|key| get_peer(this, key)).unwrap_or_default();
+
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");
+ let tag_info = MEMCACHE_KEY_METHOD_MAPPING
+ .get(&*function_name.to_ascii_lowercase())
+ .unwrap();
- match get_command(execute_data, &function_name) {
- Ok(cmd) => {
- obj.add_tag("memcached.command", cmd);
- }
- Err(err) => {
- warn!(?err, "get command failed");
- }
- }
- });
- Ok(span)
- })?;
+ let span = create_exit_span(
+ request_id,
+ &class_name,
+ &function_name,
+ &peer,
+ tag_info,
+ key_str.as_deref(),
+ )?;
- Ok(Box::new(span) as _)
+ Ok(Box::new(span))
}),
- 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;
- });
+ Box::new(after_hook),
+ )
+ }
+
+ #[instrument(skip_all)]
+ fn hook_memcached_server_key_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 server_key = {
+ let server_key = execute_data.get_parameter(0);
+ if server_key.get_type_info().is_string() {
+ Some(server_key.clone())
+ } else {
+ // The `*Multi` methods will failed here.
+ warn!(function_name, "The argument server_key isn't string");
+ None
}
- }
- Ok(())
+ };
+
+ let key = execute_data
+ .get_parameter(1)
+ .as_z_str()
+ .and_then(|key| key.to_str().ok())
+ .map(ToOwned::to_owned);
+
+ let this = get_this_mut(execute_data)?;
+
+ let peer = server_key
+ .map(|server_key| get_peer(this, server_key))
+ .unwrap_or_default();
+
+ debug!(peer, "Get memcached peer");
+
+ let tag_info = MEMCACHE_SERVER_KEY_METHOD_MAPPING
+ .get(&*function_name.to_ascii_lowercase())
+ .unwrap();
+
+ let span = create_exit_span(
+ request_id,
+ &class_name,
+ &function_name,
+ &peer,
+ tag_info,
+ key.as_deref(),
+ )?;
+
+ Ok(Box::new(span))
}),
+ Box::new(after_hook),
)
}
}
-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());
+#[instrument(skip_all)]
+fn after_hook(
+ _: Option<i64>, span: Box<dyn Any>, execute_data: &mut ExecuteData, return_value: &mut ZVal,
+) -> anyhow::Result<()> {
+ let mut span = span.downcast::<Span>().expect("Downcast to Span failed");
+ if let Some(b) = return_value.as_bool() {
+ if !b {
+ span.with_span_object_mut(|span| {
+ span.is_error = true;
+ });
- 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)
+ let this = get_this_mut(execute_data)?;
+ let code = this.call(&"getResultCode".to_ascii_lowercase(), [])?;
+ let code = code.as_long().context("ResultCode isn't int")?;
+ debug!(code, "get memcached result code");
+
+ if code != 0 {
+ let message = this.call(&"getResultMessage".to_ascii_lowercase(), [])?;
+ let message = message
+ .as_z_str()
+ .context("ResultMessage isn't string")?
+ .to_str()?;
+ debug!(message, "get memcached result message");
+
+ span.add_log([
+ ("ResultCode", code.to_string()),
+ ("ResultMessage", message.to_owned()),
+ ]);
+ }
+ }
}
+ Ok(())
+}
- Ok(items.join(" "))
+fn create_exit_span<'a>(
+ request_id: Option<i64>, class_name: &str, function_name: &str, remote_peer: &str,
+ tag_info: &TagInfo<'a>, key: Option<&str>,
+) -> anyhow::Result<Span> {
+ RequestContext::try_with_global_ctx(request_id, |ctx| {
+ let mut span =
+ ctx.create_exit_span(&format!("{}->{}", class_name, function_name), remote_peer);
+
+ span.with_span_object_mut(|obj| {
+ obj.set_span_layer(SpanLayer::Cache);
+ obj.component_id = COMPONENT_PHP_MEMCACHED_ID;
+ obj.add_tag(TAG_CACHE_TYPE, "memcache");
+
+ if let Some(cmd) = &tag_info.cmd {
+ obj.add_tag(TAG_CACHE_CMD, cmd);
+ }
+ if let Some(op) = &tag_info.op {
+ obj.add_tag(TAG_CACHE_OP, op.to_string());
+ };
+ if let Some(key) = key {
+ obj.add_tag(TAG_CACHE_KEY, key)
+ }
+ });
+
+ Ok(span)
+ })
+}
+
+fn get_peer(this: &mut ZObj, key: ZVal) -> String {
+ let f = || {
+ let info = {
+ let _e = ExceptionFrame::new();
+ 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))
+ };
+ f().unwrap_or_else(|err| {
+ warn!(?err, "Get peer failed");
+ "".to_owned()
+ })
}
diff --git a/src/plugin/plugin_pdo.rs b/src/plugin/plugin_pdo.rs
index 2a349bc..9febec4 100644
--- a/src/plugin/plugin_pdo.rs
+++ b/src/plugin/plugin_pdo.rs
@@ -19,6 +19,7 @@
context::RequestContext,
exception_frame::ExceptionFrame,
execute::{get_this_mut, validate_num_args, AfterExecuteHook, BeforeExecuteHook, Noop},
+ tag::{TAG_DB_STATEMENT, TAG_DB_TYPE},
};
use anyhow::Context;
use dashmap::DashMap;
@@ -120,7 +121,7 @@
if execute_data.num_args() >= 1 {
if let Some(statement) = execute_data.get_parameter(0).as_z_str() {
- span.add_tag("db.statement", statement.to_str()?);
+ span.add_tag(TAG_DB_STATEMENT, statement.to_str()?);
}
}
@@ -146,7 +147,7 @@
})?;
if let Some(query) = this.get_property("queryString").as_z_str() {
- span.add_tag("db.statement", query.to_str()?);
+ span.add_tag(TAG_DB_STATEMENT, query.to_str()?);
} else {
warn!("PDOStatement queryString is empty");
}
@@ -256,7 +257,7 @@
span.with_span_object_mut(|obj| {
obj.set_span_layer(SpanLayer::Database);
obj.component_id = COMPONENT_PHP_PDO_ID;
- obj.add_tag("db.type", &dsn.db_type);
+ obj.add_tag(TAG_DB_TYPE, &dsn.db_type);
obj.add_tag("db.data_source", &dsn.data_source);
});
Ok(span)
diff --git a/src/plugin/plugin_predis.rs b/src/plugin/plugin_predis.rs
index 772dd3b..918a954 100644
--- a/src/plugin/plugin_predis.rs
+++ b/src/plugin/plugin_predis.rs
@@ -20,10 +20,116 @@
component::COMPONENT_PHP_PREDIS_ID,
context::RequestContext,
execute::{get_this_mut, validate_num_args, AfterExecuteHook, BeforeExecuteHook},
+ tag::{TAG_CACHE_CMD, TAG_CACHE_KEY, TAG_CACHE_OP, TAG_CACHE_TYPE},
};
use anyhow::Context;
-use phper::arrays::ZArr;
+use once_cell::sync::Lazy;
use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
+use std::collections::HashSet;
+use tracing::debug;
+
+pub static REDIS_READ_COMMANDS: Lazy<HashSet<&str>> = Lazy::new(|| {
+ [
+ "BLPOP",
+ "BRPOP",
+ "GET",
+ "GETBIT",
+ "GETRANGE",
+ "HEXISTS",
+ "HGET",
+ "HGETALL",
+ "HKEYS",
+ "HLEN",
+ "HMGET",
+ "HSCAN",
+ "HSTRLEN",
+ "HVALS",
+ "KEYS",
+ "LGET",
+ "LGETRANGE",
+ "LLEN",
+ "LRANGE",
+ "LSIZE",
+ "MGET",
+ "SCONTAINS",
+ "SGETMEMBERS",
+ "SISMEMBER",
+ "SMEMBERS",
+ "SSCAN",
+ "SSIZE",
+ "STRLEN",
+ "ZCOUNT",
+ "ZRANGE",
+ "ZRANGEBYLEX",
+ "ZRANGEBYSCORE",
+ "ZSCAN",
+ "ZSIZE",
+ ]
+ .into_iter()
+ .collect()
+});
+
+pub static REDIS_WRITE_COMMANDS: Lazy<HashSet<&str>> = Lazy::new(|| {
+ [
+ "APPEND",
+ "BRPOPLPUSH",
+ "DECR",
+ "DECRBY",
+ "DEL",
+ "DELETE",
+ "HDEL",
+ "HINCRBY",
+ "HINCRBYFLOAT",
+ "HMSET",
+ "HSET",
+ "HSETNX",
+ "INCR",
+ "INCRBY",
+ "INCRBYFLOAT",
+ "LINSERT",
+ "LPUSH",
+ "LPUSHX",
+ "LREM",
+ "LREMOVE",
+ "LSET",
+ "LTRIM",
+ "LISTTRIM",
+ "MSET",
+ "MSETNX",
+ "PSETEX",
+ "RPOPLPUSH",
+ "RPUSH",
+ "RPUSHX",
+ "RANDOMKEY",
+ "SADD",
+ "SINTER",
+ "SINTERSTORE",
+ "SMOVE",
+ "SRANDMEMBER",
+ "SREM",
+ "SREMOVE",
+ "SET",
+ "SETBIT",
+ "SETEX",
+ "SETNX",
+ "SETRANGE",
+ "SETTIMEOUT",
+ "SORT",
+ "UNLINK",
+ "ZADD",
+ "ZDELETE",
+ "ZDELETERANGEBYRANK",
+ "ZDELETERANGEBYSCORE",
+ "ZINCRBY",
+ "ZREM",
+ "ZREMRANGEBYRANK",
+ "ZREMRANGEBYSCORE",
+ "ZREMOVE",
+ "ZREMOVERANGEBYSCORE",
+ ]
+ .into_iter()
+ .collect()
+});
#[derive(Default, Clone)]
pub struct PredisPlugin;
@@ -44,9 +150,10 @@
Box<crate::execute::AfterExecuteHook>,
)> {
match (class_name, function_name) {
- (Some(class_name @ "Predis\\Connection\\AbstractConnection"), "executeCommand") => {
- Some(self.hook_predis_execute_command(class_name))
- }
+ (
+ Some(class_name @ "Predis\\Connection\\AbstractConnection"),
+ function_name @ "executeCommand",
+ ) => Some(self.hook_predis_execute_command(class_name, function_name)),
_ => None,
}
}
@@ -54,9 +161,10 @@
impl PredisPlugin {
fn hook_predis_execute_command(
- &self, class_name: &str,
+ &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| {
validate_num_args(execute_data, 1)?;
@@ -77,25 +185,56 @@
.expect_long()?;
let peer = format!("{}:{}", host, port);
+ let handle = this.handle();
let command = execute_data.get_parameter(0).expect_mut_z_obj()?;
+ let command_class_name = command
+ .get_class()
+ .get_name()
+ .to_str()
+ .map(ToOwned::to_owned)
+ .unwrap_or_default();
let id = command.call("getid", []).context("call getId failed")?;
- let id = id.expect_z_str()?.to_str()?;
+ let cmd = id.expect_z_str()?.to_str()?.to_ascii_uppercase();
let mut arguments = command
.call("getarguments", [])
.context("call getArguments failed")?;
let arguments = arguments.expect_mut_z_arr()?;
+ let op = if REDIS_READ_COMMANDS.contains(&*cmd) {
+ Some("read")
+ } else if REDIS_WRITE_COMMANDS.contains(&*cmd) {
+ Some("write")
+ } else {
+ None
+ };
+
+ let key = op
+ .and_then(|_| arguments.get(0))
+ .and_then(|arg| arg.as_z_str())
+ .and_then(|s| s.to_str().ok());
+
+ debug!(handle, cmd, key, op, "call redis command");
+
let mut span = RequestContext::try_with_global_ctx(request_id, |ctx| {
- Ok(ctx.create_exit_span(&format!("{}->{}", class_name, id), &peer))
+ Ok(ctx.create_exit_span(
+ &format!("{}->{}({})", class_name, function_name, command_class_name),
+ &peer,
+ ))
})?;
span.with_span_object_mut(|span| {
span.set_span_layer(SpanLayer::Cache);
span.component_id = COMPONENT_PHP_PREDIS_ID;
- span.add_tag("db.type", "redis");
- span.add_tag("redis.command", generate_command(id, arguments));
+ span.add_tag(TAG_CACHE_TYPE, "redis");
+ span.add_tag(TAG_CACHE_CMD, cmd);
+ if let Some(op) = op {
+ span.add_tag(TAG_CACHE_OP, op);
+ };
+ if let Some(key) = key {
+ span.add_tag(TAG_CACHE_KEY, key)
+ }
});
Ok(Box::new(span))
@@ -113,18 +252,3 @@
)
}
}
-
-fn generate_command(id: &str, arguments: &mut ZArr) -> String {
- let mut ss = Vec::with_capacity(arguments.len() + 1);
- ss.push(id);
-
- for (_, argument) in arguments.iter() {
- if let Some(value) = argument.as_z_str().and_then(|s| s.to_str().ok()) {
- ss.push(value);
- } else if argument.as_z_arr().is_some() {
- break;
- }
- }
-
- ss.join(" ")
-}
diff --git a/src/plugin/plugin_redis.rs b/src/plugin/plugin_redis.rs
index e376d07..1a29307 100644
--- a/src/plugin/plugin_redis.rs
+++ b/src/plugin/plugin_redis.rs
@@ -13,8 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::{any::Any, collections::HashSet};
-
use super::Plugin;
use crate::{
component::COMPONENT_PHP_REDIS_ID,
@@ -32,123 +30,128 @@
values::{ExecuteData, ZVal},
};
use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
+use std::{any::Any, collections::HashMap};
use tracing::{debug, warn};
static PEER_MAP: Lazy<DashMap<u32, Peer>> = Lazy::new(Default::default);
static FREE_MAP: Lazy<DashMap<u32, sys::zend_object_free_obj_t>> = Lazy::new(Default::default);
-static REDIS_READ_COMMANDS: Lazy<HashSet<String>> = Lazy::new(|| {
+static REDIS_READ_MAPPING: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
[
- "blPop",
- "brPop",
- "get",
- "getBit",
- "getKeys",
- "getMultiple",
- "getRange",
- "hExists",
- "hGet",
- "hGetAll",
- "hKeys",
- "hLen",
- "hMGet",
- "hScan",
- "hStrLen",
- "hVals",
- "keys",
- "lGet",
- "lGetRange",
- "lLen",
- "lRange",
- "lSize",
- "mGet",
- "sContains",
- "sGetMembers",
- "sIsMember",
- "sMembers",
- "sScan",
- "sSize",
- "strLen",
- "zCount",
- "zRange",
- "zRangeByLex",
- "zRangeByScore",
- "zScan",
- "zSize",
+ ("blpop", "BLPOP"),
+ ("brpop", "BRPOP"),
+ ("get", "GET"),
+ ("getbit", "GETBIT"),
+ ("getkeys", "KEYS"),
+ ("getmultiple", "MGET"),
+ ("getrange", "GETRANGE"),
+ ("hexists", "HEXISTS"),
+ ("hget", "HGET"),
+ ("hgetall", "HGETALL"),
+ ("hkeys", "HKEYS"),
+ ("hlen", "HLEN"),
+ ("hmget", "HMGET"),
+ ("hscan", "HSCAN"),
+ ("hstrlen", "HSTRLEN"),
+ ("hvals", "HVALS"),
+ ("keys", "KEYS"),
+ ("lget", "LGET"),
+ ("lgetrange", "LGETRANGE"),
+ ("llen", "LLEN"),
+ ("lrange", "LRANGE"),
+ ("lsize", "LSIZE"),
+ ("mget", "MGET"),
+ ("mget", "MGET"),
+ ("scontains", "SCONTAINS"),
+ ("sgetmembers", "SGETMEMBERS"),
+ ("sismember", "SISMEMBER"),
+ ("smembers", "SMEMBERS"),
+ ("sscan", "SSCAN"),
+ ("ssize", "SSIZE"),
+ ("strlen", "STRLEN"),
+ ("substr", "GETRANGE"),
+ ("zcount", "ZCOUNT"),
+ ("zrange", "ZRANGE"),
+ ("zrangebylex", "ZRANGEBYLEX"),
+ ("zrangebyscore", "ZRANGEBYSCORE"),
+ ("zscan", "ZSCAN"),
+ ("zsize", "ZSIZE"),
]
.into_iter()
- .map(str::to_ascii_lowercase)
.collect()
});
-static REDIS_WRITE_COMMANDS: Lazy<HashSet<String>> = Lazy::new(|| {
+static REDIS_WRITE_MAPPING: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
[
- "append",
- "bRPopLPush",
- "decr",
- "decrBy",
- "del",
- "delete",
- "hDel",
- "hIncrBy",
- "hIncrByFloat",
- "hMSet",
- "hSet",
- "hSetNx",
- "incr",
- "incrBy",
- "incrByFloat",
- "lInsert",
- "lPush",
- "lPushx",
- "lRem",
- "lRemove",
- "lSet",
- "lTrim",
- "listTrim",
- "mSet",
- "mSetNX",
- "pSetEx",
- "rPopLPush",
- "rPush",
- "rPushX",
- "randomKey",
- "sAdd",
- "sInter",
- "sInterStore",
- "sMove",
- "sRandMember",
- "sRem",
- "sRemove",
- "set",
- "setBit",
- "setEx",
- "setNx",
- "setRange",
- "setTimeout",
- "sort",
- "unlink",
- "zAdd",
- "zDelete",
- "zDeleteRangeByRank",
- "zDeleteRangeByScore",
- "zIncrBy",
- "zRem",
- "zRemRangeByRank",
- "zRemRangeByScore",
- "zRemove",
- "zRemoveRangeByScore",
+ ("append", "APPEND"),
+ ("brpoplpush", "BRPOPLPUSH"),
+ ("decr", "DECR"),
+ ("decrby", "DECRBY"),
+ ("del", "DEL"),
+ ("delete", "DEL"),
+ ("hdel", "HDEL"),
+ ("hincrby", "HINCRBY"),
+ ("hincrbyfloat", "HINCRBYFLOAT"),
+ ("hmset", "HMSET"),
+ ("hset", "HSET"),
+ ("hsetnx", "HSETNX"),
+ ("incr", "INCR"),
+ ("incrby", "INCRBY"),
+ ("incrbyfloat", "INCRBYFLOAT"),
+ ("linsert", "LINSERT"),
+ ("lpush", "LPUSH"),
+ ("lpushx", "LPUSHX"),
+ ("lrem", "LREM"),
+ ("lremove", "LREMOVE"),
+ ("lset", "LSET"),
+ ("ltrim", "LTRIM"),
+ ("listtrim", "LISTTRIM"),
+ ("mset", "MSET"),
+ ("msetnx", "MSETNX"),
+ ("psetex", "PSETEX"),
+ ("rpoplpush", "RPOPLPUSH"),
+ ("rpush", "RPUSH"),
+ ("rpushx", "RPUSHX"),
+ ("randomkey", "RANDOMKEY"),
+ ("sadd", "SADD"),
+ ("sinter", "SINTER"),
+ ("sinterstore", "SINTERSTORE"),
+ ("smove", "SMOVE"),
+ ("srandmember", "SRANDMEMBER"),
+ ("srem", "SREM"),
+ ("sremove", "SREMOVE"),
+ ("set", "SET"),
+ ("setbit", "SETBIT"),
+ ("setex", "SETEX"),
+ ("setnx", "SETNX"),
+ ("setrange", "SETRANGE"),
+ ("settimeout", "SETTIMEOUT"),
+ ("sort", "SORT"),
+ ("unlink", "UNLINK"),
+ ("zadd", "ZADD"),
+ ("zdelete", "ZDELETE"),
+ ("zdeleterangebyrank", "ZDELETERANGEBYRANK"),
+ ("zdeleterangebyscore", "ZDELETERANGEBYSCORE"),
+ ("zincrby", "ZINCRBY"),
+ ("zrem", "ZREM"),
+ ("zremrangebyrank", "ZREMRANGEBYRANK"),
+ ("zremrangebyscore", "ZREMRANGEBYSCORE"),
+ ("zremove", "ZREMOVE"),
+ ("zremoverangebyscore", "ZREMOVERANGEBYSCORE"),
]
.into_iter()
- .map(str::to_ascii_lowercase)
.collect()
});
-static REDIS_ALL_COMMANDS: Lazy<HashSet<String>> = Lazy::new(|| {
- let mut commands = HashSet::new();
- commands.extend(REDIS_READ_COMMANDS.iter().map(Clone::clone));
- commands.extend(REDIS_WRITE_COMMANDS.iter().map(Clone::clone));
+static REDIS_OTHER_MAPPING: Lazy<HashMap<&str, &str>> =
+ Lazy::new(|| [("auth", "AUTH")].into_iter().collect());
+
+static REDIS_ALL_MAPPING: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
+ let mut commands = HashMap::with_capacity(REDIS_READ_MAPPING.len() + REDIS_WRITE_MAPPING.len());
+ commands.extend(REDIS_READ_MAPPING.iter());
+ commands.extend(REDIS_WRITE_MAPPING.iter());
+ commands.extend(REDIS_OTHER_MAPPING.iter());
commands
});
@@ -177,7 +180,7 @@
Some(self.hook_redis_connect(class_name, function_name))
}
(Some(class_name @ "Redis"), f)
- if REDIS_ALL_COMMANDS.contains(&f.to_ascii_lowercase()) =>
+ if REDIS_ALL_MAPPING.contains_key(&*f.to_ascii_lowercase()) =>
{
Some(self.hook_redis_methods(class_name, function_name))
}
@@ -283,17 +286,21 @@
.map(|r| r.value().addr.clone())
.unwrap_or_default();
- let key = execute_data
- .get_parameter(0)
- .as_z_str()
- .and_then(|s| s.to_str().ok());
- let op = if REDIS_READ_COMMANDS.contains(&function_name.to_ascii_lowercase()) {
- "read"
+ let function_name_key = &*function_name.to_ascii_lowercase();
+
+ let op = if REDIS_READ_MAPPING.contains_key(function_name_key) {
+ Some("read")
+ } else if REDIS_WRITE_MAPPING.contains_key(function_name_key) {
+ Some("write")
} else {
- "write"
+ None
};
- debug!(handle, function_name, key, op, "call redis command");
+ let key = op
+ .and_then(|_| execute_data.get_parameter(0).as_z_str())
+ .and_then(|s| s.to_str().ok());
+
+ debug!(handle, cmd = function_name, key, op, "call redis command");
let mut span = RequestContext::try_with_global_ctx(request_id, |ctx| {
Ok(ctx.create_exit_span(&format!("{}->{}", class_name, function_name), &peer))
@@ -303,8 +310,13 @@
span.set_span_layer(SpanLayer::Cache);
span.component_id = COMPONENT_PHP_REDIS_ID;
span.add_tag(TAG_CACHE_TYPE, "redis");
- span.add_tag(TAG_CACHE_CMD, function_name);
- span.add_tag(TAG_CACHE_OP, op);
+ span.add_tag(
+ TAG_CACHE_CMD,
+ REDIS_ALL_MAPPING.get(function_name_key).unwrap(),
+ );
+ if let Some(op) = op {
+ span.add_tag(TAG_CACHE_OP, op);
+ }
if let Some(key) = key {
span.add_tag(TAG_CACHE_KEY, key)
}
diff --git a/src/tag.rs b/src/tag.rs
index 69ca800..7b0abd1 100644
--- a/src/tag.rs
+++ b/src/tag.rs
@@ -18,8 +18,31 @@
//! Virtual Cache
//!
//! https://skywalking.apache.org/docs/main/next/en/setup/service-agent/virtual-cache/
+//!
+//! Virtual Database
+//!
+//! https://skywalking.apache.org/docs/main/next/en/setup/service-agent/virtual-database/
+
+use std::fmt::Display;
pub const TAG_CACHE_TYPE: &str = "cache.type";
pub const TAG_CACHE_OP: &str = "cache.op";
pub const TAG_CACHE_CMD: &str = "cache.cmd";
pub const TAG_CACHE_KEY: &str = "cache.key";
+
+pub enum CacheOp {
+ Read,
+ Write,
+}
+
+impl Display for CacheOp {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Read => write!(f, "read"),
+ Self::Write => write!(f, "write"),
+ }
+ }
+}
+
+pub const TAG_DB_STATEMENT: &str = "db.statement";
+pub const TAG_DB_TYPE: &str = "db.type";
diff --git a/tests/data/expected_context.yaml b/tests/data/expected_context.yaml
index f366211..e4db16c 100644
--- a/tests/data/expected_context.yaml
+++ b/tests/data/expected_context.yaml
@@ -454,7 +454,7 @@
- { key: http.status_code, value: "200" }
- segmentId: "not null"
spans:
- - operationName: "Predis\\Connection\\AbstractConnection->AUTH"
+ - operationName: "Predis\\Connection\\AbstractConnection->executeCommand(Predis\\Command\\Redis\\AUTH)"
parentSpanId: 0
spanId: 1
spanLayer: Cache
@@ -466,9 +466,9 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - { key: db.type, value: redis }
- - { key: redis.command, value: AUTH password }
- - operationName: "Predis\\Connection\\AbstractConnection->SET"
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: AUTH }
+ - operationName: "Predis\\Connection\\AbstractConnection->executeCommand(Predis\\Command\\Redis\\SET)"
parentSpanId: 0
spanId: 2
spanLayer: Cache
@@ -480,9 +480,11 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - { key: db.type, value: redis }
- - { key: redis.command, value: SET foo bar }
- - operationName: "Predis\\Connection\\AbstractConnection->GET"
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: SET }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: foo }
+ - operationName: "Predis\\Connection\\AbstractConnection->executeCommand(Predis\\Command\\Redis\\GET)"
parentSpanId: 0
spanId: 3
spanLayer: Cache
@@ -494,9 +496,11 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - { key: db.type, value: redis }
- - { key: redis.command, value: GET foo }
- - operationName: "Predis\\Connection\\AbstractConnection->GET"
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: GET }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: foo }
+ - operationName: "Predis\\Connection\\AbstractConnection->executeCommand(Predis\\Command\\Redis\\GET)"
parentSpanId: 0
spanId: 4
spanLayer: Cache
@@ -508,8 +512,10 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - { key: db.type, value: redis }
- - { key: redis.command, value: GET not-exists }
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: GET }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: not-exists }
- operationName: GET:/predis.php
parentSpanId: -1
spanId: 0
@@ -578,7 +584,7 @@
- {key: http.status_code, value: '200'}
- segmentId: 'not null'
spans:
- - operationName: Memcached->addServer
+ - operationName: Memcached->set
parentSpanId: 0
spanId: 1
spanLayer: Cache
@@ -587,11 +593,13 @@
componentId: 20
isError: false
spanType: Exit
- peer: ""
+ peer: 127.0.0.1:11211
skipAnalysis: false
tags:
- - { key: db.type, value: memcached }
- - { key: memcached.command, value: addServer 127.0.0.1 11211 }
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: foo }
- operationName: Memcached->set
parentSpanId: 0
spanId: 2
@@ -604,9 +612,11 @@
peer: 127.0.0.1:11211
skipAnalysis: false
tags:
- - { key: db.type, value: memcached }
- - { key: memcached.command, value: set foo Hello! }
- - operationName: Memcached->set
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: bar }
+ - operationName: Memcached->get
parentSpanId: 0
spanId: 3
spanLayer: Cache
@@ -618,8 +628,10 @@
peer: 127.0.0.1:11211
skipAnalysis: false
tags:
- - { key: db.type, value: memcached }
- - { key: memcached.command, value: set bar Memcached... }
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: foo }
- operationName: Memcached->get
parentSpanId: 0
spanId: 4
@@ -632,25 +644,13 @@
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 }
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: get }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: bar }
- operationName: Memcached->setMulti
parentSpanId: 0
- spanId: 6
+ spanId: 5
spanLayer: Cache
startTime: gt 0
endTime: gt 0
@@ -660,12 +660,29 @@
peer: ""
skipAnalysis: false
tags:
- - { key: db.type, value: memcached }
- - {
- key: memcached.command,
- value:
- 'setMulti {"key1":"value1","key2":"value2","key3":"value3"}',
- }
+ - { key: cache.type, value: memcache }
+ - { key: cache.cmd, value: set }
+ - { key: cache.op, value: write }
+ - operationName: Memcached->get
+ parentSpanId: 0
+ spanId: 6
+ 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 }
+ logs:
+ - logEvent:
+ - { key: ResultCode, value: "16" }
+ - { key: ResultMessage, value: NOT FOUND }
- operationName: GET:/memcached.php
parentSpanId: -1
spanId: 0
@@ -697,7 +714,7 @@
tags:
- key: cache.type
value: redis
- - operationName: Redis->mset
+ - operationName: Redis->auth
parentSpanId: 0
spanId: 2
spanLayer: Cache
@@ -709,13 +726,9 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - key: cache.type
- value: redis
- - key: cache.cmd
- value: mset
- - key: cache.op
- value: write
- - operationName: Redis->get
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: AUTH }
+ - operationName: Redis->mset
parentSpanId: 0
spanId: 3
spanLayer: Cache
@@ -727,14 +740,9 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - key: cache.type
- value: redis
- - key: cache.cmd
- value: get
- - key: cache.op
- value: read
- - key: cache.key
- value: key0
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: MSET }
+ - { key: cache.op, value: write }
- operationName: Redis->get
parentSpanId: 0
spanId: 4
@@ -747,14 +755,26 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - key: cache.type
- value: redis
- - key: cache.cmd
- value: get
- - key: cache.op
- value: read
- - key: cache.key
- value: key1
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: GET }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: key0 }
+ - operationName: Redis->get
+ parentSpanId: 0
+ spanId: 5
+ spanLayer: Cache
+ startTime: gt 0
+ endTime: gt 0
+ componentId: 7
+ isError: false
+ spanType: Exit
+ peer: 127.0.0.1:6379
+ skipAnalysis: false
+ tags:
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: GET }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: key1 }
- operationName: GET:/redis.succ.php
parentSpanId: -1
spanId: 0
@@ -784,8 +804,7 @@
peer: "127.0.0.1:6379"
skipAnalysis: false
tags:
- - key: cache.type
- value: redis
+ - { key: cache.type, value: redis }
- operationName: Redis->set
parentSpanId: 0
spanId: 2
@@ -798,14 +817,10 @@
peer: 127.0.0.1:6379
skipAnalysis: false
tags:
- - key: cache.type
- value: redis
- - key: cache.cmd
- value: set
- - key: cache.op
- value: write
- - key: cache.key
- value: foo
+ - { key: cache.type, value: redis }
+ - { key: cache.cmd, value: SET }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: foo }
logs:
- logEvent:
- { key: Exception Class, value: RedisException }
diff --git a/tests/php/fpm/memcached.php b/tests/php/fpm/memcached.php
index dd59904..da5c849 100644
--- a/tests/php/fpm/memcached.php
+++ b/tests/php/fpm/memcached.php
@@ -30,12 +30,13 @@
Assert::same($mc->get("foo"), 'Hello!');
Assert::same($mc->get("bar"), "Memcached...");
- $items = array(
+ $mc->setMulti(array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3'
- );
- $mc->setMulti($items);
+ ));
+
+ Assert::false($mc->get("not-exists"));
}
echo "ok";