Inspect Redis internals using UNIX socket and Python script without external dependencies.
Look at the end of this blog post for additional information, including protocol specification.
Python script
This is the most straightforward implementation without external dependencies.
#!/usr/bin/env python3 # Source: # https://sleeplessbeastie.eu/2019/05/01/how-to-inspect-redis-internals-using-unix-socket-and-python-script/ import sys import socket class RedisCLI: # Indicate request size REQUEST_SIZE = '*' # Indicate element size REQUEST_ELEMENT_SIZE = '$' # Indicate the terminator REQUEST_TERMINATOR = '\r\n' # Socket buffer size SOCKET_BUFFER_SIZE = 8 def __init__(self, socket_path="/var/run/redis/redis.sock", socket_timeout=5.0, password=''): self.socket_path = socket_path self.socket_timeout = socket_timeout self.password = password def set_socket_timeout(self): self.socket.settimeout(self.socket_timeout) def connect(self): try: self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_socket_timeout() self.socket.connect(self.socket_path) except: print(sys.exc_info()[1]) sys.exit(1) def read_raw(self): reply = [] while True: reply_part = self.socket.recv(self.SOCKET_BUFFER_SIZE) reply.append(reply_part.decode()) if len(reply_part) < self.SOCKET_BUFFER_SIZE: break return "".join(reply) def read(self): return self.resp_parse(self.read_raw()) def resp_parse(self, reply): first_line = reply.splitlines()[0] data_type = first_line[0] data = first_line[1:] if data_type == '-': response = ' '.join(('(error)', data)) elif data_type == '+': response = '' elif data_type == ':': response = int(data) elif data_type == "$": response = '\r\n'.join(reply.split('\r\n')[1:])[:int(data)] elif data_type == '*': response = [self.resp_parse(''.join(('$', '\r\n'.join(reply.split('\r\n')[1:]).split('$')[i]))) for i in range(1, int(data) + 1)] else: response = '(error) parse error' return response def send(self, command): self.socket.send(self.resp_create(command)) def close(self): self.socket.close() def resp_create(self, command): elements = command.split(" ") resp = ''.join((self.REQUEST_SIZE, str(len(elements)), self.REQUEST_TERMINATOR)) for element in elements: resp = ''.join((resp, self.REQUEST_ELEMENT_SIZE, str(len(element)), self.REQUEST_TERMINATOR, element, self.REQUEST_TERMINATOR)) return resp.encode() def execute(self, command): try: if self.password: self.send(' '.join(('AUTH', self.password))) reply = self.read_raw() if reply[0] != '+': raise Exception(reply) self.send(command) reply = self.read() if isinstance(reply, str): return '\n'.join(reply.splitlines()) else: return reply except Exception: print(sys.exc_info()[1]) sys.exit(2) def request(self, command): self.connect() data = self.execute(command) self.close() return data redis_connection = RedisCLI(socket_path="/var/run/redis/redis-local.sock", password='x2G7xn-eR13rrLT') print("---- CPU usage") print(redis_connection.request("INFO CPU")) print() print("---- Client list") print(redis_connection.request("CLIENT LIST")) print() print("---- BIND config setting") print(redis_connection.request("CONFIG GET bind")) print() print("---- Available commands") print(redis_connection.request("COMMAND"))
Sample usage
This script returns some sample internals like CPU usage, client list, BIND configuration setting, and available commands. You can easily extend it to analyze and report data using the desired format.
---- CPU usage # CPU used_cpu_sys:64.80 used_cpu_user:22.84 used_cpu_sys_children:0.00 used_cpu_user_children:0.00 ---- Client list id=82 addr=127.0.0.1:42714 fd=9 name= age=90793 idle=90793 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=command id=568 addr=/var/run/redis/redis-local.sock:0 fd=10 name= age=82717 idle=82717 flags=U db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=info id=1573 addr=/var/run/redis/redis-local.sock:0 fd=11 name= age=0 idle=0 flags=U db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client ---- BIND config setting ['bind', '127.0.0.1 ::1'] ---- Available commands ['bitpos', 'post', 'zrange', 'lset', 'incrby', 'unlink', 'swapdb', 'sunionstore', 'debug', 'sync', 'scard', 'georadiusbymember_ro', 'cluster', 'zscore', 'hvals', 'sdiff', 'geopos', 'blpop', 'sunion', 'msetnx', 'substr', 'bgrewriteaof', 'save', 'lpushx', 'eval', 'flushdb', 'sort', 'brpop', 'zscan', 'hset', 'append', 'rpop', 'sadd', 'renamenx', 'georadius', 'hexists', 'bgsave', 'psync', 'auth', 'expireat', 'bitcount', 'sdiffstore', 'migrate', 'setnx', 'touch', 'script', 'zrevrangebyscore', 'set', 'hincrbyfloat', 'shutdown', 'publish', 'mset', 'type', 'lrange', 'latency', 'hget', 'select', 'zrem', 'del', 'incrbyfloat', 'geoadd', 'bitfield', 'psetex', 'getbit', 'dbsize', 'host:', 'incr', 'srandmember', 'sinterstore', 'watch', 'punsubscribe', 'zrevrangebylex', 'zrevrange', 'exists', 'randomkey', 'role', 'zunionstore', 'unsubscribe', 'replconf', 'pubsub', 'monitor', 'scan', 'lpop', 'psubscribe', 'pttl', 'dump', 'zrangebylex', 'zremrangebyrank', 'hdel', 'hstrlen', 'asking', 'lindex', 'hmget', 'zrevrank', 'expire', 'rename', 'zrank', 'zcard', 'setbit', 'pfadd', 'hlen', 'keys', 'geohash', 'discard', 'client', 'hincrby', 'spop', 'pfcount', 'georadius_ro', 'get', 'linsert', 'hscan', 'pfselftest', 'command', 'ltrim', 'ping', 'geodist', 'zremrangebyscore', 'pexpire', 'flushall', 'llen', 'memory', 'module', 'unwatch', 'slaveof', 'move', 'subscribe', 'hgetall', 'smembers', 'lastsave', 'pfdebug', 'zincrby', 'exec', 'decrby', 'sismember', 'zinterstore', 'config', 'pexpireat', 'pfmerge', 'mget', 'rpush', 'multi', 'slowlog', 'rpushx', 'hsetnx', 'time', 'readwrite', 'readonly', 'zlexcount', 'decr', 'sinter', 'georadiusbymember', 'object', 'zremrangebylex', 'hkeys', 'restore', 'sscan', 'brpoplpush', 'srem', 'ttl', 'echo', 'setex', 'lrem', 'evalsha', 'restore-asking', 'zrangebyscore', 'strlen', 'getset', 'zadd', 'bitop', 'lpush', 'rpoplpush', 'info', 'setrange', 'smove', 'getrange', 'zcount', 'persist', 'hmset', 'wait']
Additional information
- Redis Protocol specification
- The Python interface to the Redis key-value store
- Source code for Redis Python Client
It was an enjoyable experience! Source code for Redis Python Client helped me a lot.