Completed
Pull Request — master (#26)
by
unknown
01:23
created

RedisServer.connection()   A

Complexity

Conditions 1

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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