RedisServer   A
last analyzed

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Importance

Changes 13
Bugs 0 Features 4
Metric Value
c 13
b 0
f 4
dl 0
loc 142
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __unicode__() 0 12 3
C stats() 0 43 7
A slowlog_len() 0 5 2
A slowlog_get() 0 12 3
A clean() 0 3 3
A connection() 0 17 1
1
import re
2
from datetime import datetime
3
from datetime import timedelta
4
5
import redis
6
from django.conf import settings
7
from django.core.exceptions import ValidationError
8
from django.core.validators import MaxValueValidator
9
from django.core.validators import MinValueValidator
10
from django.db import models
11
from django.utils.translation import ugettext_lazy as _
12
13
from .utils import PY3
14
from .utils import cached_property
15
16
try:
17
    from django.utils.datastructures import SortedDict as OrderedDict
18
except ImportError:
19
    from django.utils.datastructures import OrderedDict
20
21
REDISBOARD_DETAIL_FILTERS = [re.compile(name) for name in getattr(settings, 'REDISBOARD_DETAIL_FILTERS', (
22
    'aof_enabled', 'bgrewriteaof_in_progress', 'bgsave_in_progress',
23
    'changes_since_last_save', 'db.*', 'last_save_time', 'multiplexing_api',
24
    'total_commands_processed', 'total_connections_received', 'uptime_in_days',
25
    'uptime_in_seconds', 'vm_enabled', 'redis_version'
26
))]
27
REDISBOARD_DETAIL_TIMESTAMP_KEYS = getattr(settings, 'REDISBOARD_DETAIL_TIMESTAMP_KEYS', (
28
    'last_save_time',
29
))
30
REDISBOARD_DETAIL_SECONDS_KEYS = getattr(settings, 'REDISBOARD_DETAIL_SECONDS_KEYS', (
31
    'uptime_in_seconds',
32
))
33
34
REDISBOARD_SLOWLOG_LEN = getattr(settings, 'REDISBOARD_SLOWLOG_LEN', 10)
35
36
REDISBOARD_SOCKET_TIMEOUT = getattr(settings, 'REDISBOARD_SOCKET_TIMEOUT', None)
37
REDISBOARD_SOCKET_CONNECT_TIMEOUT = getattr(settings, 'REDISBOARD_SOCKET_CONNECT_TIMEOUT', None)
38
REDISBOARD_SOCKET_KEEPALIVE = getattr(settings, 'REDISBOARD_SOCKET_KEEPALIVE', None)
39
REDISBOARD_SOCKET_KEEPALIVE_OPTIONS = getattr(settings, 'REDISBOARD_SOCKET_KEEPALIVE_OPTIONS', None)
40
41
42
def prettify(key, value):
43
    if key in REDISBOARD_DETAIL_SECONDS_KEYS:
44
        return key, timedelta(seconds=value)
45
    elif key in REDISBOARD_DETAIL_TIMESTAMP_KEYS:
46
        return key, datetime.fromtimestamp(value)
47
    else:
48
        return key, value
49
50
51
class RedisServer(models.Model):
52
    class Meta:
53
        unique_together = ('hostname', 'port')
54
        verbose_name = _("Redis Server")
55
        verbose_name_plural = _("Redis Servers")
56
        permissions = (
57
            ("can_inspect", "Can inspect redis servers"),
58
        )
59
60
    label = models.CharField(
61
        _('Label'),
62
        max_length=50,
63
        blank=True,
64
        null=True,
65
    )
66
67
    hostname = models.CharField(
68
        _("Hostname"),
69
        max_length=250,
70
        help_text=_('This can also be the absolute path to a redis socket')
71
    )
72
73
    port = models.IntegerField(_("Port"), validators=[
74
        MaxValueValidator(65535), MinValueValidator(1)
75
    ], default=6379, blank=True, null=True)
76
    password = models.CharField(_("Password"), max_length=250,
77
                                null=True, blank=True)
78
79
    sampling_threshold = models.IntegerField(
80
        _("Sampling threshold"),
81
        default=1000,
82
        help_text=_("Number of keys after which only a sample (of random keys) is shown on the inspect page.")
83
    )
84
    sampling_size = models.IntegerField(
85
        _("Sampling size"),
86
        default=200,
87
        help_text=_("Number of random keys shown when sampling is used. Note that each key translates to a RANDOMKEY call in redis.")
88
    )
89
90
    def clean(self):
91
        if not self.hostname.startswith('/') and not self.port:
92
            raise ValidationError(_('Please provide either a hostname AND a port or the path to a redis socket'))
93
94
    @cached_property
95
    def connection(self):
96
        if self.hostname.startswith('/'):
97
            unix_socket_path = self.hostname
98
            hostname = None
99
        else:
100
            hostname = self.hostname
101
            unix_socket_path = None
102
        return redis.Redis(
103
            host=hostname,
104
            port=self.port,
105
            password=self.password,
106
            unix_socket_path=unix_socket_path,
107
            socket_timeout=REDISBOARD_SOCKET_TIMEOUT,
108
            socket_connect_timeout=REDISBOARD_SOCKET_CONNECT_TIMEOUT,
109
            socket_keepalive=REDISBOARD_SOCKET_KEEPALIVE,
110
            socket_keepalive_options=REDISBOARD_SOCKET_KEEPALIVE_OPTIONS,
111
        )
112
113
    @connection.deleter
114
    def connection(self, value):
115
        value.connection_pool.disconnect()
116
117
    @cached_property
118
    def stats(self):
119
        try:
120
            conn = self.connection
121
            info = conn.info()
122
            slowlog = conn.slowlog_get()
123
            slowlog_len = conn.slowlog_len()
124
            return {
125
                'status': 'UP',
126
                'details': info,
127
                'memory': "%s (peak: %s)" % (
128
                    info['used_memory_human'],
129
                    info.get('used_memory_peak_human', 'n/a')
130
                ),
131
                'clients': info['connected_clients'],
132
                'brief_details': OrderedDict(
133
                    prettify(k, v)
134
                    for name in REDISBOARD_DETAIL_FILTERS
135
                    for k, v in (info.items() if PY3 else info.iteritems())
136
                    if name.match(k)
137
                ),
138
                'slowlog': slowlog,
139
                'slowlog_len': slowlog_len,
140
            }
141
        except redis.exceptions.ConnectionError:
142
            return {
143
                'status': 'DOWN',
144
                'clients': 'n/a',
145
                'memory': 'n/a',
146
                'details': {},
147
                'brief_details': {},
148
                'slowlog': [],
149
                'slowlog_len': 0,
150
            }
151
        except redis.exceptions.ResponseError as exc:
152
            return {
153
                'status': 'ERROR: %s' % exc.args,
154
                'clients': 'n/a',
155
                'memory': 'n/a',
156
                'details': {},
157
                'brief_details': {},
158
                'slowlog': [],
159
                'slowlog_len': 0,
160
            }
161
162
    def __unicode__(self):
163
        if self.label:
164
            label = '%s (%%s)' % self.label
165
        else:
166
            label = '%s'
167
168
        if self.port:
169
            label = label % ('%s:%s' % (self.hostname, self.port))
170
        else:
171
            label = label % self.hostname
172
173
        return label
174
175
    def slowlog_len(self):
176
        try:
177
            return self.connection.slowlog_len()
178
        except redis.exceptions.ConnectionError:
179
            return 0
180
181
    def slowlog_get(self, limit=REDISBOARD_SLOWLOG_LEN):
182
        try:
183
            for slowlog in self.connection.slowlog_get(REDISBOARD_SLOWLOG_LEN):
184
                yield dict(
185
                    id=slowlog['id'],
186
                    ts=datetime.fromtimestamp(slowlog['start_time']),
187
                    duration=slowlog['duration'],
188
                    command=slowlog['command'],
189
                )
190
191
        except redis.exceptions.ConnectionError:
192
            pass
193