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/17 01:31:56 UTC

[skywalking-php] branch master updated: Add Predis plugin. (#21)

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 6663f1a  Add Predis plugin. (#21)
6663f1a is described below

commit 6663f1ae573a88e22636ac4bce33a83b6046d6e6
Author: jmjoy <jm...@apache.org>
AuthorDate: Mon Oct 17 09:31:51 2022 +0800

    Add Predis plugin. (#21)
    
    * Add Predis plugin.
---
 README.md                                          |   2 +-
 .../service-agent/php-agent/Supported-list.md      |   2 +
 src/component.rs                                   |   1 +
 src/plugin/mod.rs                                  |   2 +
 src/plugin/plugin_mysqli.rs                        |  14 +--
 src/plugin/plugin_predis.rs                        | 130 +++++++++++++++++++++
 tests/data/expected_context.yaml                   |  75 +++++++++++-
 tests/e2e.rs                                       |   9 ++
 src/component.rs => tests/php/fpm/predis.php       |  25 ++--
 9 files changed, 242 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md
index 0144013..bd77675 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ SkyWalking PHP Agent requires SkyWalking 8.4+ and PHP 7.0+
   * [ ] [phpredis](https://github.com/phpredis/phpredis)
   * [ ] [php-amqp](https://github.com/php-amqp/php-amqp)
   * [ ] [php-rdkafka](https://github.com/arnaud-lb/php-rdkafka)
-  * [ ] [predis](https://github.com/predis/predis)
+  * [x] [predis](https://github.com/predis/predis)
 
 * Swoole Ecosystem
   * [ ] [Coroutine\Http\Client](https://wiki.swoole.com/#/coroutine_client/http_client)
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 d9d52ee..56f9ccf 100644
--- a/docs/en/setup/service-agent/php-agent/Supported-list.md
+++ b/docs/en/setup/service-agent/php-agent/Supported-list.md
@@ -14,3 +14,5 @@ The following plugins provide the distributed tracing capability.
 * [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
 
 ## Support PHP library
+
+* [predis](https://github.com/predis/predis)
diff --git a/src/component.rs b/src/component.rs
index f51b122..3e5c93f 100644
--- a/src/component.rs
+++ b/src/component.rs
@@ -21,3 +21,4 @@ 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;
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index badaf97..411fcec 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -16,6 +16,7 @@
 mod plugin_curl;
 mod plugin_mysqli;
 mod plugin_pdo;
+mod plugin_predis;
 mod plugin_swoole;
 
 use crate::execute::{AfterExecuteHook, BeforeExecuteHook};
@@ -29,6 +30,7 @@ static PLUGINS: Lazy<Vec<Box<DynPlugin>>> = Lazy::new(|| {
         Box::new(plugin_mysqli::MySQLImprovedPlugin::default()),
         Box::new(plugin_swoole::SwooleServerPlugin::default()),
         Box::new(plugin_swoole::SwooleHttpResponsePlugin::default()),
+        Box::new(plugin_predis::PredisPlugin::default()),
     ]
 });
 
diff --git a/src/plugin/plugin_mysqli.rs b/src/plugin/plugin_mysqli.rs
index b04883d..e40ea98 100644
--- a/src/plugin/plugin_mysqli.rs
+++ b/src/plugin/plugin_mysqli.rs
@@ -13,19 +13,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use anyhow::Context;
-use dashmap::DashMap;
-use once_cell::sync::Lazy;
-use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
-use tracing::debug;
-
+use super::Plugin;
 use crate::{
     component::COMPONENT_PHP_MYSQLI_ID,
     context::RequestContext,
     execute::{get_this_mut, AfterExecuteHook, BeforeExecuteHook, Noop},
 };
-
-use super::Plugin;
+use anyhow::Context;
+use dashmap::DashMap;
+use once_cell::sync::Lazy;
+use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
+use tracing::debug;
 
 static MYSQL_MAP: Lazy<DashMap<u32, MySQLInfo>> = Lazy::new(Default::default);
 
diff --git a/src/plugin/plugin_predis.rs b/src/plugin/plugin_predis.rs
new file mode 100644
index 0000000..79aceda
--- /dev/null
+++ b/src/plugin/plugin_predis.rs
@@ -0,0 +1,130 @@
+// 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.
+
+// TODO Need to be improved.
+
+use super::Plugin;
+use crate::{
+    component::COMPONENT_PHP_PREDIS_ID,
+    context::RequestContext,
+    execute::{get_this_mut, validate_num_args, AfterExecuteHook, BeforeExecuteHook},
+};
+use anyhow::Context;
+use phper::arrays::ZArr;
+use skywalking::{skywalking_proto::v3::SpanLayer, trace::span::Span};
+
+#[derive(Default, Clone)]
+pub struct PredisPlugin;
+
+impl Plugin for PredisPlugin {
+    fn class_names(&self) -> Option<&'static [&'static str]> {
+        Some(&["Predis\\Connection\\AbstractConnection"])
+    }
+
+    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 @ "Predis\\Connection\\AbstractConnection"), "executeCommand") => {
+                Some(self.hook_predis_execute_command(class_name))
+            }
+            _ => None,
+        }
+    }
+}
+
+impl PredisPlugin {
+    fn hook_predis_execute_command(
+        &self, class_name: &str,
+    ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
+        let class_name = class_name.to_owned();
+        (
+            Box::new(move |_, execute_data| {
+                validate_num_args(execute_data, 1)?;
+
+                let this = get_this_mut(execute_data)?;
+                let parameters = this.get_mut_property("parameters").expect_mut_z_obj()?;
+                let parameters = parameters
+                    .get_mut_property("parameters")
+                    .expect_mut_z_arr()?;
+                let host = parameters
+                    .get_mut("host")
+                    .context("host not found")?
+                    .expect_z_str()?
+                    .to_str()?;
+                let port = parameters
+                    .get_mut("port")
+                    .context("port not found")?
+                    .expect_long()?;
+                let peer = format!("{}:{}", host, port);
+
+                let command = execute_data.get_parameter(0).expect_mut_z_obj()?;
+
+                let id = command.call("getid", []).context("call getId failed")?;
+                let id = id.expect_z_str()?.to_str()?;
+
+                let mut arguments = command
+                    .call("getarguments", [])
+                    .context("call getArguments failed")?;
+                let arguments = arguments.expect_mut_z_arr()?;
+
+                let mut span = RequestContext::try_with_global_ctx(None, |ctx| {
+                    Ok(ctx.create_exit_span(&format!("{}->{}", class_name, id), &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));
+                });
+
+                Ok(Box::new(span))
+            }),
+            Box::new(move |_, span, _, return_value| {
+                let mut span = span.downcast::<Span>().unwrap();
+
+                let typ = return_value.get_type_info();
+                if typ.is_null() || typ.is_false() {
+                    span.with_span_object_mut(|span| span.is_error = true);
+                }
+
+                Ok(())
+            }),
+        )
+    }
+}
+
+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/tests/data/expected_context.yaml b/tests/data/expected_context.yaml
index f012b67..2a6fc70 100644
--- a/tests/data/expected_context.yaml
+++ b/tests/data/expected_context.yaml
@@ -15,7 +15,7 @@
 
 segmentItems:
   - serviceName: skywalking-agent-test-1
-    segmentSize: 8
+    segmentSize: 9
     segments:
       - segmentId: "not null"
         spans:
@@ -342,6 +342,79 @@ segmentItems:
               - { key: url, value: /pdo.php }
               - { key: http.method, value: GET }
               - { key: http.status_code, value: "200" }
+      - segmentId: "not null"
+        spans:
+          - operationName: "Predis\\Connection\\AbstractConnection->AUTH"
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: Cache
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 8006
+            isError: false
+            spanType: Exit
+            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"
+            parentSpanId: 0
+            spanId: 2
+            spanLayer: Cache
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 8006
+            isError: false
+            spanType: Exit
+            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"
+            parentSpanId: 0
+            spanId: 3
+            spanLayer: Cache
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 8006
+            isError: false
+            spanType: Exit
+            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"
+            parentSpanId: 0
+            spanId: 4
+            spanLayer: Cache
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 8006
+            isError: true
+            spanType: Exit
+            peer: 127.0.0.1:6379
+            skipAnalysis: false
+            tags:
+              - { key: db.type, value: redis }
+              - { key: redis.command, value: GET not-exists }
+          - operationName: GET:/predis.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: /predis.php }
+              - { key: http.method, value: GET }
+              - { key: http.status_code, value: "200" }
       - segmentId: 'not null'
         spans:
           - operationName: mysqli->query
diff --git a/tests/e2e.rs b/tests/e2e.rs
index 22a2c17..cbdaf9c 100644
--- a/tests/e2e.rs
+++ b/tests/e2e.rs
@@ -51,6 +51,7 @@ async fn run_e2e() {
     request_fpm_pdo().await;
     request_fpm_mysqli().await;
     request_swoole_curl().await;
+    request_fpm_predis().await;
     sleep(Duration::from_secs(3)).await;
     request_collector_validate().await;
 }
@@ -79,6 +80,14 @@ async fn request_fpm_mysqli() {
     .await;
 }
 
+async fn request_fpm_predis() {
+    request_common(
+        HTTP_CLIENT.get(format!("http://{}/predis.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/predis.php
similarity index 66%
copy from src/component.rs
copy to tests/php/fpm/predis.php
index f51b122..98375b5 100644
--- a/src/component.rs
+++ b/tests/php/fpm/predis.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,13 +13,20 @@
 // 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";
 
-//! Component ID
-//!
-//! <https://github.com/apache/skywalking/blob/014861535015745ae3f7b99acd7d14500b3b3927/oap-server/server-starter/src/main/resources/component-libraries.yml>
+{
+    $client = new Predis\Client();
+    $client->auth('password');
+    $client->set('foo', 'bar');
+    $value = $client->get('foo');
+    Assert::same($value, 'bar');
+    $client->get('not-exists');
+}
 
-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;
+echo "ok";