You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/07/20 14:32:48 UTC

[skywalking-python] branch master updated: Add Redis Plugin (#44)

This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-python.git


The following commit(s) were added to refs/heads/master by this push:
     new f4dde90  Add Redis Plugin (#44)
f4dde90 is described below

commit f4dde902ee2cf3efb13f307910794dfb86ec1264
Author: huawei <al...@gmail.com>
AuthorDate: Mon Jul 20 22:32:25 2020 +0800

    Add Redis Plugin (#44)
---
 README.md                                  |   1 +
 setup.py                                   |   1 +
 skywalking/__init__.py                     |   1 +
 skywalking/plugins/sw_redis/__init__.py    |  54 ++++++++++++
 tests/plugin/sw_redis/__init__.py          |  16 ++++
 tests/plugin/sw_redis/docker-compose.yml   |  74 +++++++++++++++++
 tests/plugin/sw_redis/expected.data.yml    | 127 +++++++++++++++++++++++++++++
 tests/plugin/sw_redis/services/__init__.py |  16 ++++
 tests/plugin/sw_redis/services/consumer.py |  37 +++++++++
 tests/plugin/sw_redis/services/provider.py |  44 ++++++++++
 tests/plugin/sw_redis/test_redispy.py      |  43 ++++++++++
 11 files changed, 414 insertions(+)

diff --git a/README.md b/README.md
index 694f04c..b2fdc66 100755
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ Library | Plugin Name
 | [Flask](https://flask.palletsprojects.com/en/1.1.x/) | `sw_flask` |
 | [PyMySQL](https://pymysql.readthedocs.io/en/latest/) | `sw_pymysql` |
 | [Django](https://www.djangoproject.com/) | `sw_django` |
+| [redis-py](https://github.com/andymccurdy/redis-py/) | `sw_redis` |
 
 ## API
 
diff --git a/setup.py b/setup.py
index c83df15..6511078 100644
--- a/setup.py
+++ b/setup.py
@@ -45,6 +45,7 @@ setup(
             "testcontainers",
             "Werkzeug",
             "pymysql",
+            "redis",
         ],
     },
     classifiers=[
diff --git a/skywalking/__init__.py b/skywalking/__init__.py
index 3619b89..0892f53 100644
--- a/skywalking/__init__.py
+++ b/skywalking/__init__.py
@@ -28,6 +28,7 @@ class Component(Enum):
     Requests = 7002
     PyMysql = 7003
     Django = 7004
+    Redis = 7005
 
 
 class Layer(Enum):
diff --git a/skywalking/plugins/sw_redis/__init__.py b/skywalking/plugins/sw_redis/__init__.py
new file mode 100644
index 0000000..c140476
--- /dev/null
+++ b/skywalking/plugins/sw_redis/__init__.py
@@ -0,0 +1,54 @@
+#
+# 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.
+#
+import logging
+
+from skywalking import Layer, Component
+from skywalking.trace import tags
+from skywalking.trace.context import get_context
+from skywalking.trace.tags import Tag
+
+logger = logging.getLogger(__name__)
+
+
+def install():
+    # noinspection PyBroadException
+    try:
+        from redis.connection import Connection
+
+        _send_command = Connection.send_command
+
+        def _sw_send_command(this: Connection, *args, **kwargs):
+            peer = "%s:%s" % (this.host, this.port)
+            op = args[0]
+            context = get_context()
+            with context.new_exit_span(op="Redis/"+op or "/", peer=peer) as span:
+                span.layer = Layer.Cache
+                span.component = Component.Redis
+
+                try:
+                    res = _send_command(this, *args, **kwargs)
+                    span.tag(Tag(key=tags.DbType, val="Redis"))
+                    span.tag(Tag(key=tags.DbInstance, val=this.db))
+                    span.tag(Tag(key=tags.DbStatement, val=op))
+                except BaseException as e:
+                    span.raised()
+                    raise e
+                return res
+
+        Connection.send_command = _sw_send_command
+    except Exception:
+        logger.warning('failed to install plugin %s', __name__)
diff --git a/tests/plugin/sw_redis/__init__.py b/tests/plugin/sw_redis/__init__.py
new file mode 100644
index 0000000..b1312a0
--- /dev/null
+++ b/tests/plugin/sw_redis/__init__.py
@@ -0,0 +1,16 @@
+#
+# 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.
+#
diff --git a/tests/plugin/sw_redis/docker-compose.yml b/tests/plugin/sw_redis/docker-compose.yml
new file mode 100644
index 0000000..ba9fc19
--- /dev/null
+++ b/tests/plugin/sw_redis/docker-compose.yml
@@ -0,0 +1,74 @@
+#
+# 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.
+#
+
+version: '2.1'
+
+services:
+  collector:
+    extends:
+      service: collector
+      file: ../docker/docker-compose.base.yml
+
+  redis:
+    image:  redis:3.2.9-alpine
+    hostname: redis
+    ports:
+      - 6379:6379
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/6379"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+    networks:
+      - beyond
+
+  provider:
+    extends:
+      service: agent
+      file: ../docker/docker-compose.base.yml
+    ports:
+      - 9091:9091
+    volumes:
+      - ./services/provider.py:/app/provider.py
+    command: ['bash', '-c', 'pip install flask && pip install redis && python3 /app/provider.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+
+  consumer:
+    extends:
+      service: agent
+      file: ../docker/docker-compose.base.yml
+    ports:
+      - 9090:9090
+    volumes:
+      - ./services/consumer.py:/app/consumer.py
+    command: ['bash', '-c', 'pip install flask && python3 /app/consumer.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+      provider:
+        condition: service_healthy
+
+networks:
+  beyond:
diff --git a/tests/plugin/sw_redis/expected.data.yml b/tests/plugin/sw_redis/expected.data.yml
new file mode 100644
index 0000000..12686fd
--- /dev/null
+++ b/tests/plugin/sw_redis/expected.data.yml
@@ -0,0 +1,127 @@
+#
+# 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.
+#
+
+segmentItems:
+  - serviceName: provider
+    segmentSize: 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: Redis/SET
+            operationId: 0
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: Cache
+            tags:
+              - key: db.type
+                value: Redis
+              - key: db.instance
+                value: '0'
+              - key: db.statement
+                value: 'SET'
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7005
+            spanType: Exit
+            peer: redis:6379
+            skipAnalysis: false
+          - operationName: Redis/GET
+            operationId: 0
+            parentSpanId: 0
+            spanId: 2
+            spanLayer: Cache
+            tags:
+              - key: db.type
+                value: Redis
+              - key: db.instance
+                value: '0'
+              - key: db.statement
+                value: 'GET'
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7005
+            spanType: Exit
+            peer: redis:6379
+            skipAnalysis: false
+          - operationName: /users
+            operationId: 0
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            tags:
+              - key: http.method
+                value: POST
+              - key: url
+                value: http://provider:9091/users
+              - key: status.code
+                value: '200'
+            refs:
+              - parentEndpoint: /users
+                networkAddress: provider:9091
+                refType: CrossProcess
+                parentSpanId: 1
+                parentTraceSegmentId: not null
+                parentServiceInstance: not null
+                parentService: consumer
+                traceId: not null
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7001
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+  - serviceName: consumer
+    segmentSize: 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /users
+            operationId: 0
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: Http
+            tags:
+              - key: http.method
+                value: POST
+              - key: url
+                value: http://provider:9091/users
+              - key: status.code
+                value: '200'
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7002
+            spanType: Exit
+            peer: provider:9091
+            skipAnalysis: false
+          - operationName: /users
+            operationId: 0
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            tags:
+              - key: http.method
+                value: GET
+              - key: url
+                value: http://0.0.0.0:9090/users
+              - key: status.code
+                value: '200'
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7001
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
\ No newline at end of file
diff --git a/tests/plugin/sw_redis/services/__init__.py b/tests/plugin/sw_redis/services/__init__.py
new file mode 100644
index 0000000..b1312a0
--- /dev/null
+++ b/tests/plugin/sw_redis/services/__init__.py
@@ -0,0 +1,16 @@
+#
+# 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.
+#
diff --git a/tests/plugin/sw_redis/services/consumer.py b/tests/plugin/sw_redis/services/consumer.py
new file mode 100644
index 0000000..c942f91
--- /dev/null
+++ b/tests/plugin/sw_redis/services/consumer.py
@@ -0,0 +1,37 @@
+#
+# 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.
+#
+
+import requests
+
+from skywalking import agent, config
+
+if __name__ == '__main__':
+    config.service_name = 'consumer'
+    config.logging_level = 'DEBUG'
+    agent.start()
+
+    from flask import Flask, jsonify
+
+    app = Flask(__name__)
+
+    @app.route("/users", methods=["POST", "GET"])
+    def application():
+        res = requests.post("http://provider:9091/users")
+        return jsonify(res.json())
+
+    PORT = 9090
+    app.run(host='0.0.0.0', port=PORT, debug=True)
diff --git a/tests/plugin/sw_redis/services/provider.py b/tests/plugin/sw_redis/services/provider.py
new file mode 100644
index 0000000..0b1fb0f
--- /dev/null
+++ b/tests/plugin/sw_redis/services/provider.py
@@ -0,0 +1,44 @@
+#
+# 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.
+#
+
+import time
+
+from skywalking import agent, config
+
+if __name__ == '__main__':
+    config.service_name = 'provider'
+    config.logging_level = 'DEBUG'
+    agent.start()
+
+    from flask import Flask, jsonify
+    import redis
+
+    app = Flask(__name__)
+
+    @app.route("/users", methods=["POST", "GET"])
+    def application():
+        time.sleep(0.5)
+
+        r = redis.StrictRedis(host='redis', port=6379, db=0)
+
+        r.set('foo', 'bar')
+        r.get('foo')
+
+        return jsonify({"song": "Despacito", "artist": "Luis Fonsi"})
+
+    PORT = 9091
+    app.run(host='0.0.0.0', port=PORT, debug=True)
diff --git a/tests/plugin/sw_redis/test_redispy.py b/tests/plugin/sw_redis/test_redispy.py
new file mode 100644
index 0000000..dfeaaa6
--- /dev/null
+++ b/tests/plugin/sw_redis/test_redispy.py
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+
+import os
+import time
+import unittest
+from os.path import abspath, dirname
+
+from testcontainers.compose import DockerCompose
+
+from tests.plugin import BasePluginTest
+
+
+class TestPlugin(BasePluginTest):
+    @classmethod
+    def setUpClass(cls):
+        cls.compose = DockerCompose(filepath=dirname(abspath(__file__)))
+        cls.compose.start()
+
+        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users'))
+
+    def test_request_plugin(self):
+        time.sleep(3)
+
+        self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 'expected.data.yml'))
+
+
+if __name__ == '__main__':
+    unittest.main()