|
| 1 | +# Copyright 2010 New Relic, Inc. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +import re |
| 16 | + |
| 17 | +from newrelic.api.datastore_trace import DatastoreTrace |
| 18 | +from newrelic.api.transaction import current_transaction |
| 19 | +from newrelic.common.object_wrapper import wrap_function_wrapper |
| 20 | + |
| 21 | +_redis_client_methods = ( |
| 22 | + "bgrewriteaof", |
| 23 | + "bgsave", |
| 24 | + "client_kill", |
| 25 | + "client_list", |
| 26 | + "client_getname", |
| 27 | + "client_setname", |
| 28 | + "config_get", |
| 29 | + "config_set", |
| 30 | + "config_resetstat", |
| 31 | + "config_rewrite", |
| 32 | + "dbsize", |
| 33 | + "debug_object", |
| 34 | + "echo", |
| 35 | + "flushall", |
| 36 | + "flushdb", |
| 37 | + "info", |
| 38 | + "lastsave", |
| 39 | + "object", |
| 40 | + "ping", |
| 41 | + "save", |
| 42 | + "sentinel", |
| 43 | + "sentinel_get_master_addr_by_name", |
| 44 | + "sentinel_master", |
| 45 | + "sentinel_masters", |
| 46 | + "sentinel_monitor", |
| 47 | + "sentinel_remove", |
| 48 | + "sentinel_sentinels", |
| 49 | + "sentinel_set", |
| 50 | + "sentinel_slaves", |
| 51 | + "shutdown", |
| 52 | + "slaveof", |
| 53 | + "slowlog_get", |
| 54 | + "slowlog_reset", |
| 55 | + "time", |
| 56 | + "append", |
| 57 | + "bitcount", |
| 58 | + "bitop", |
| 59 | + "bitpos", |
| 60 | + "decr", |
| 61 | + "delete", |
| 62 | + "dump", |
| 63 | + "exists", |
| 64 | + "expire", |
| 65 | + "expireat", |
| 66 | + "get", |
| 67 | + "getbit", |
| 68 | + "getrange", |
| 69 | + "getset", |
| 70 | + "incr", |
| 71 | + "incrby", |
| 72 | + "incrbyfloat", |
| 73 | + "keys", |
| 74 | + "mget", |
| 75 | + "mset", |
| 76 | + "msetnx", |
| 77 | + "move", |
| 78 | + "persist", |
| 79 | + "pexpire", |
| 80 | + "pexpireat", |
| 81 | + "psetex", |
| 82 | + "pttl", |
| 83 | + "randomkey", |
| 84 | + "rename", |
| 85 | + "renamenx", |
| 86 | + "restore", |
| 87 | + "set", |
| 88 | + "setbit", |
| 89 | + "setex", |
| 90 | + "setnx", |
| 91 | + "setrange", |
| 92 | + "strlen", |
| 93 | + "substr", |
| 94 | + "ttl", |
| 95 | + "type", |
| 96 | + "watch", |
| 97 | + "unwatch", |
| 98 | + "blpop", |
| 99 | + "brpop", |
| 100 | + "brpoplpush", |
| 101 | + "lindex", |
| 102 | + "linsert", |
| 103 | + "llen", |
| 104 | + "lpop", |
| 105 | + "lpush", |
| 106 | + "lpushx", |
| 107 | + "lrange", |
| 108 | + "lrem", |
| 109 | + "lset", |
| 110 | + "ltrim", |
| 111 | + "rpop", |
| 112 | + "rpoplpush", |
| 113 | + "rpush", |
| 114 | + "rpushx", |
| 115 | + "sort", |
| 116 | + "scan", |
| 117 | + "scan_iter", |
| 118 | + "sscan", |
| 119 | + "sscan_iter", |
| 120 | + "hscan", |
| 121 | + "hscan_inter", |
| 122 | + "zscan", |
| 123 | + "zscan_iter", |
| 124 | + "sadd", |
| 125 | + "scard", |
| 126 | + "sdiff", |
| 127 | + "sdiffstore", |
| 128 | + "sinter", |
| 129 | + "sinterstore", |
| 130 | + "sismember", |
| 131 | + "smembers", |
| 132 | + "smove", |
| 133 | + "spop", |
| 134 | + "srandmember", |
| 135 | + "srem", |
| 136 | + "sunion", |
| 137 | + "sunionstore", |
| 138 | + "zadd", |
| 139 | + "zcard", |
| 140 | + "zcount", |
| 141 | + "zincrby", |
| 142 | + "zinterstore", |
| 143 | + "zlexcount", |
| 144 | + "zrange", |
| 145 | + "zrangebylex", |
| 146 | + "zrangebyscore", |
| 147 | + "zrank", |
| 148 | + "zrem", |
| 149 | + "zremrangebylex", |
| 150 | + "zremrangebyrank", |
| 151 | + "zremrangebyscore", |
| 152 | + "zrevrange", |
| 153 | + "zrevrangebyscore", |
| 154 | + "zrevrank", |
| 155 | + "zscore", |
| 156 | + "zunionstore", |
| 157 | + "pfadd", |
| 158 | + "pfcount", |
| 159 | + "pfmerge", |
| 160 | + "hdel", |
| 161 | + "hexists", |
| 162 | + "hget", |
| 163 | + "hgetall", |
| 164 | + "hincrby", |
| 165 | + "hincrbyfloat", |
| 166 | + "hkeys", |
| 167 | + "hlen", |
| 168 | + "hset", |
| 169 | + "hsetnx", |
| 170 | + "hmset", |
| 171 | + "hmget", |
| 172 | + "hvals", |
| 173 | + "publish", |
| 174 | + "eval", |
| 175 | + "evalsha", |
| 176 | + "script_exists", |
| 177 | + "script_flush", |
| 178 | + "script_kill", |
| 179 | + "script_load", |
| 180 | + "setex", |
| 181 | + "lrem", |
| 182 | + "zadd", |
| 183 | +) |
| 184 | + |
| 185 | +_redis_multipart_commands = set(["client", "cluster", "command", "config", "debug", "sentinel", "slowlog", "script"]) |
| 186 | + |
| 187 | +_redis_operation_re = re.compile(r"[-\s]+") |
| 188 | + |
| 189 | + |
| 190 | +def _conn_attrs_to_dict(connection): |
| 191 | + return { |
| 192 | + "host": getattr(connection, "host", None), |
| 193 | + "port": getattr(connection, "port", None), |
| 194 | + "path": getattr(connection, "path", None), |
| 195 | + "db": getattr(connection, "db", None), |
| 196 | + } |
| 197 | + |
| 198 | + |
| 199 | +def _instance_info(kwargs): |
| 200 | + host = kwargs.get("host") or "localhost" |
| 201 | + port_path_or_id = str(kwargs.get("port") or kwargs.get("path", "unknown")) |
| 202 | + db = str(kwargs.get("db") or 0) |
| 203 | + |
| 204 | + return (host, port_path_or_id, db) |
| 205 | + |
| 206 | + |
| 207 | +async def _nr_Connection_send_command_wrapper_(wrapped, instance, args, kwargs): |
| 208 | + transaction = current_transaction() |
| 209 | + if not transaction: |
| 210 | + return await wrapped(*args, **kwargs) |
| 211 | + |
| 212 | + host, port_path_or_id, db = (None, None, None) |
| 213 | + |
| 214 | + try: |
| 215 | + dt = transaction.settings.datastore_tracer |
| 216 | + if dt.instance_reporting.enabled or dt.database_name_reporting.enabled: |
| 217 | + conn_kwargs = _conn_attrs_to_dict(instance) |
| 218 | + host, port_path_or_id, db = _instance_info(conn_kwargs) |
| 219 | + except Exception: |
| 220 | + pass |
| 221 | + |
| 222 | + transaction._nr_datastore_instance_info = (host, port_path_or_id, db) |
| 223 | + |
| 224 | + # Older Redis clients would when sending multi part commands pass |
| 225 | + # them in as separate arguments to send_command(). Need to therefore |
| 226 | + # detect those and grab the next argument from the set of arguments. |
| 227 | + |
| 228 | + operation = args[0].strip().lower() |
| 229 | + |
| 230 | + # If it's not a multi part command, there's no need to trace it, so |
| 231 | + # we can return early. |
| 232 | + |
| 233 | + if operation.split()[0] not in _redis_multipart_commands: |
| 234 | + return await wrapped(*args, **kwargs) |
| 235 | + |
| 236 | + # Convert multi args to single arg string |
| 237 | + |
| 238 | + if operation in _redis_multipart_commands and len(args) > 1: |
| 239 | + operation = "%s %s" % (operation, args[1].strip().lower()) |
| 240 | + |
| 241 | + operation = _redis_operation_re.sub("_", operation) |
| 242 | + |
| 243 | + with DatastoreTrace( |
| 244 | + product="Redis", target=None, operation=operation, host=host, port_path_or_id=port_path_or_id, database_name=db |
| 245 | + ): |
| 246 | + return await wrapped(*args, **kwargs) |
| 247 | + |
| 248 | + |
| 249 | +def instrument_aredis_connection(module): |
| 250 | + wrap_function_wrapper(module.Connection, "send_command", _nr_Connection_send_command_wrapper_) |
0 commit comments