Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / solr / solr4_ @ 9f85e0ae

Historique | Voir | Annoter | Télécharger (13,9 ko)

1 52191f68 antonio
#!/usr/bin/env python
2
#
3
# Copyright (c) 2013, Antonio Verni, me.verni@gmail.com
4
# 
5
# Permission is hereby granted, free of charge, to any person obtaining a
6
# copy of this software and associated documentation files (the "Software"),
7
# to deal in the Software without restriction, including without limitation
8
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
# and/or sell copies of the Software, and to permit persons to whom the
10
# Software is furnished to do so, subject to the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
# DEALINGS IN THE SOFTWARE.
22
#
23
# Munin plugin for monitoring a multicore solr 4.* installation via mbean.
24
# It calls:
25
# > http://localhost:8080/solr/admin/cores?action=STATUS&wt=json
26
# and
27
# > http://localhost:8080/solr/corename/admin/mbeans?stats=true&wt=json
28
# for each core to retrieve cores and data. Verify those urls on your instance.
29
#
30
# Configuration parameters:
31
# [solr_*]
32
#    host_port <host:port>
33
#    qpshandler_<handlerlabel> <handlerpath>
34
#    availableram <ramsize in bytes>
35
#
36
# Example:
37
#    host_port solrhost:8080 
38
#    qpshandler_select /select
39
#    availableram 3221225472
40
#
41
# Defined checks:
42
#    numdocs  
43
#    qps  
44
#    indexsize  
45
#    requesttimes  
46
#    documentcache  
47
#    fieldvaluecache  
48
#    filtercache  
49
#    queryresultcache  
50
#
51
# Installation example:
52
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_numdocs_core_1
53
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_requesttimes_select
54
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_qps_core_1_select
55
#
56
# Source repo: https://github.com/averni/munin-solr
57
58
import sys
59
import os
60
import httplib
61
import json
62
63
def parse_params():
64
    plugname = os.path.basename(sys.argv[0]).split('_', 2)[1:]
65
    params = {
66
        'type': plugname[0],
67
        'op': 'config' if sys.argv[-1] == 'config' else 'fetch',
68
        'core': plugname[1] if len(plugname) > 1 else '',
69
        'params': {}
70
    }
71
    if plugname[0] in[ 'qps', 'requesttimes']:
72
        data = params['core'].rsplit('_', 1)
73
        handler = data.pop()
74
        params['params'] = {
75
                'handler': os.environ.get('qpshandler_%s' % handler, '/select')
76
        }
77
        if not data:
78
            params['core'] = ''
79
        else:
80
            params['core'] = data[0]
81
    elif plugname[0] ==  'indexsize':
82
        params['params']['core'] = params['core']
83
    return params
84
85
#############################################################################
86
# Datasources
87
88
class CheckException(Exception):
89
    pass
90
91
class JSONReader:
92
    @classmethod
93
    def readValue(cls, struct, path):
94
        if not path[0] in struct:
95
            return -1
96
        obj = struct[path[0]]
97
        if not obj:
98
            return -1
99
        for k in path[1:]:
100
            obj = obj[k]
101
        return obj
102
103
class SolrCoresAdmin:
104
    def __init__(self, host):
105
        self.host = host
106
        self.data = None
107
108
    def fetchcores(self):
109
        uri = "/solr/admin/cores?action=STATUS&wt=json"
110
        conn = httplib.HTTPConnection(self.host)
111
        conn.request("GET", uri)
112
        res = conn.getresponse()
113
        data = res.read()
114
        if res.status != 200:
115
            raise CheckException("Cores status fetch failed: %s\n%s" %( str(res.status), res.read()))
116
        self.data = json.loads(data)
117
118
    def getCores(self):
119
        if not self.data:
120
            self.fetchcores()
121
        cores = JSONReader.readValue(self.data, ['status'])
122
        return cores.keys()
123
124
    def indexsize(self, core = None):
125
        if not self.data:
126
            self.fetchcores()
127
        if core:
128
            return {
129
                core: JSONReader.readValue(self.data, ['status', core, 'index', 'sizeInBytes'])
130
            }
131
        else:
132
            ret = {}
133
            for core in self.getCores():
134
                ret[core] = JSONReader.readValue(self.data, ['status', core, 'index', 'sizeInBytes'])
135
            return ret
136
137
class SolrCoreMBean:
138
    def __init__(self, host, core):
139
        self.host = host
140
        self.data = None
141
        self.core = core
142
143
    def _fetch(self):
144
        uri = "/solr/%s/admin/mbeans?stats=true&wt=json" % self.core
145
        conn = httplib.HTTPConnection(self.host)
146
        conn.request("GET", uri)
147
        res = conn.getresponse()
148
        data = res.read()
149
        if res.status != 200:
150
            raise CheckException("MBean fetch failed: %s\n%s" %( str(res.status), res.read()))
151
        raw_data = json.loads(data)
152
        data = {}
153
        self.data = {
154
            'solr-mbeans': data
155
        }
156
        key = None
157
        for pos, el in enumerate(raw_data['solr-mbeans']):
158
            if pos % 2 == 1:
159
                data[key] = el
160
            else:
161
                key = el
162
163
    def _read(self, path):
164
        if self.data is None:
165
            self._fetch()
166
        return JSONReader.readValue(self.data, path)
167
168
    def _readCache(self, cache):
169
        result = {}
170
        for key in ['lookups', 'hits', 'inserts', 'evictions', 'hitratio']:
171
            path = ['solr-mbeans', 'CACHE', cache, 'stats', 'cumulative_%s' % key]
172
            result[key] = self._read(path)
173
        result['size'] = self._read(['solr-mbeans', 'CACHE', cache, 'stats', 'size'])
174
        return result
175
176
    def getCore(self):
177
        return self.core
178
179
    def qps(self, handler):
180
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'avgRequestsPerSecond']
181
        return self._read(path)
182
183
    def requesttimes(self, handler):
184
        times = {}
185
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats']
186
        for perc in ['avgTimePerRequest', '75thPcRequestTime', '99thPcRequestTime']:
187
            times[perc] = self._read(path + [perc])
188
        return times
189
190
    def numdocs(self):
191
        path = ['solr-mbeans', 'CORE', 'searcher', 'stats', 'numDocs']
192
        return self._read(path)
193
194
    def documentcache(self):
195
        return self._readCache('documentCache')
196
197
    def filtercache(self):
198
        return self._readCache('filterCache')
199
200
    def fieldvaluecache(self):
201
        return self._readCache('fieldValueCache')
202
203
    def queryresultcache(self):
204
        return self._readCache('queryResultCache')
205
206
#############################################################################
207
# Graph Templates
208
209
CACHE_GRAPH_TPL = """multigraph solr_{core}_{cacheType}_hit_rates
210
graph_category solr
211
graph_title Solr {core} {cacheName} Hit rates
212
graph_order lookups hits inserts
213
graph_scale no
214
graph_vlabel Hit Rate
215
graph_args -u 100 --rigid
216
lookups.label Cache lookups
217
lookups.graph no
218
lookups.min 0
219
lookups.type DERIVE
220
inserts.label Cache misses
221
inserts.min 0
222
inserts.draw STACK
223
inserts.cdef inserts,lookups,/,100,*
224
inserts.type DERIVE
225
hits.label Cache hits
226
hits.min 0
227
hits.draw AREA
228
hits.cdef hits,lookups,/,100,*
229
hits.type DERIVE
230
231
multigraph solr_{core}_{cacheType}_size
232
graph_title Solr {core} {cacheName} Size
233
graph_args -l 0
234
graph_category solr
235
graph_vlabel Size
236
size.label Size
237
size.draw LINE2
238
evictions.label Evictions
239
evictions.draw LINE2
240
241
"""
242
243
QPSMAIN_GRAPH_TPL = """graph_title Solr {core} {handler} Request per second"
244
graph_args -l 0
245
graph_vlabel request / second
246
graph_category solr
247
{cores_qps_graphs}"""
248
249
QPSCORE_GRAPH_TPL = """qps_{core}.label {core} Request per second
250
qps_{core}.type LINESTACK1
251
qps_{core}.graph yes"""
252
253
REQUESTTIMES_GRAPH_TPL = """multigraph {core}_requesttimes
254
graph_title Solr {core} {handler} Time per request
255
graph_args -l 0
256
graph_vlabel millis
257
graph_category solr
258
savgtimeperrequest_{core}.label {core} Avg time per request
259
savgtimeperrequest_{core}.type gauge
260
savgtimeperrequest_{core}.graph yes
261
s75thpcrequesttime_{core}.label {core} 75th perc
262
s75thpcrequesttime_{core}.type gauge
263
s75thpcrequesttime_{core}.graph yes
264
s99thpcrequesttime_{core}.label {core} 99th perc
265
s99thpcrequesttime_{core}.type gauge
266
s99thpcrequesttime_{core}.graph yes
267
268
"""
269
270
NUMDOCS_GRAPH_TPL = """graph_title Solr Docs %s
271
graph_vlabel docs
272
docs.label Docs
273
graph_category solr"""
274
275
INDEXSIZE_GRAPH_TPL = """graph_args --base 1024 -l 0 --upper-limit {availableram}
276
graph_vlabel Bytes
277
graph_title Index Size
278
graph_category solr
279
graph_info Solr Index Memory Usage.
280
graph_order {cores}
281
{cores_config}
282
"""
283
284
INDEXSIZECORE_GRAPH_TPL = """{core}.label {core}
285
{core}.draw STACK""" 
286
287
#############################################################################
288
# Graph managment
289
CHECKS_DEFINED = [
290
    'numdocs',
291
    'qps',
292
    'indexsize',
293
    'requesttimes',
294
    'documentcache',
295
    'fieldvaluecache',
296
    'filtercache',
297
    'queryresultcache'
298
]
299
300
class SolrMuninGraph:
301
    def __init__(self, hostport, solrmbean):
302
        self.solrcoresadmin = SolrCoresAdmin(hostport)
303
        self.hostport = hostport
304
        self.params = params
305
306
    def _getMBean(self, core):
307
        return SolrCoreMBean(self.hostport, core)
308
309
    def _cacheConfig(self, cacheType, cacheName):
310
        return CACHE_GRAPH_TPL.format(core=self.params['core'], cacheType=cacheType, cacheName=cacheName)
311
312
    def _cacheFetch(self, cacheType, fields = None):
313
        fields = fields or ['size', 'lookups', 'hits', 'inserts', 'evictions']
314
        hits_fields = ['lookups', 'hits', 'inserts']
315
        size_fields = ['size', 'evictions']
316
        results = []
317
        solrmbean = self._getMBean(self.params['core'])
318
        data = getattr(solrmbean, cacheType)()
319
        results.append('multigraph solr_{core}_{cacheType}_hit_rates'.format(core=self.params['core'], cacheType=cacheType))
320
        for label in hits_fields:
321
            results.append("%s.value %s" % (label, data[label]))
322
        results.append('multigraph solr_{core}_{cacheType}_size'.format(core=self.params['core'], cacheType=cacheType))
323
        for label in size_fields:
324
            results.append("%s.value %s" % (label, data[label]))
325
        return "\n".join(results)
326
327
    def config(self, mtype):
328
        if not mtype:
329
            raise CheckException("""Check missing. Available checks: \n\t%s""" % '\n\t'.join(CHECKS_DEFINED))
330
        if not hasattr(self, '%sConfig' % mtype):
331
            raise CheckException("Unknown check %s" % mtype)
332
        return getattr(self, '%sConfig' % mtype)()
333
334
    def fetch(self, mtype):
335
        if not hasattr(self, params['type']):
336
            return None
337
        return getattr(self, params['type'])()
338
339
    def _getCores(self):
340
        if self.params['core']:
341
            cores = [self.params['core']]
342
        else:
343
            cores = sorted(self.solrcoresadmin.getCores())
344
        return cores
345
346
    def qpsConfig(self):
347
        cores = self._getCores()
348
        graph = [QPSCORE_GRAPH_TPL.format(core=c) for c in cores ]
349
        return QPSMAIN_GRAPH_TPL.format(
350
            cores_qps_graphs='\n'.join(graph), 
351
            handler=self.params['params']['handler'], 
352
            core=self.params['core'], 
353
            cores_qps_cdefs='%s,%s' % (','.join(map(lambda x: 'qps_%s' % x, cores)),','.join(['+']*(len(cores)-1)))
354
        )
355
356
    def qps(self):
357
        results = []
358
        cores = self._getCores()
359
        for c in cores:
360
            mbean = self._getMBean(c)
361
            results.append('qps_%s.value %s' % (c, mbean.qps(self.params['params']['handler'])))
362
        return '\n'.join(results)
363
364
    def requesttimesConfig(self):
365
        cores = self._getCores()
366
        graphs = [REQUESTTIMES_GRAPH_TPL.format(core=c, handler=self.params['params']['handler']) for c in cores ]
367
        return '\n'.join(graphs)
368
369
    def requesttimes(self):
370
        cores = self._getCores()
371
        results = []
372
        for c in cores:
373
            mbean = self._getMBean(c)
374
            results.append('multigraph {core}_requesttimes'.format(core=c))
375
            for k, time in mbean.requesttimes(self.params['params']['handler']).items():
376
                results.append('s%s_%s.value %s' % (k.lower(), c, time))
377
        return '\n'.join(results)
378
379
    def numdocsConfig(self):
380
        return NUMDOCS_GRAPH_TPL % self.params['core']
381
382
    def numdocs(self):
383
        mbean = self._getMBean(self.params['core'])
384
        return 'docs.value %s' % mbean.numdocs(**self.params['params'])
385
386
    def indexsizeConfig(self):
387
        cores = self._getCores()
388
        availableram = os.environ.get('availableram', 16868532224)
389
        graph = [ INDEXSIZECORE_GRAPH_TPL.format(core=c) for c in cores]
390
        return INDEXSIZE_GRAPH_TPL.format(cores=" ".join(cores), cores_config="\n".join(graph), availableram=availableram)
391
392
    def indexsize(self):
393
        results = []
394
        for c, size in self.solrcoresadmin.indexsize(**self.params['params']).items():
395
            results.append("%s.value %s" % (c, size))
396
        return "\n".join(results)
397
398
    def documentcacheConfig(self):
399
        return self._cacheConfig('documentcache', 'Document Cache')
400
401
    def documentcache(self):
402
        return self._cacheFetch('documentcache')
403
404
    def filtercacheConfig(self):
405
        return self._cacheConfig('filtercache', 'Filter Cache')
406
407
    def filtercache(self):
408
        return self._cacheFetch('filtercache')
409
410
    def fieldvaluecacheConfig(self):
411
        return self._cacheConfig('fieldvaluecache', 'Field Value Cache')
412
413
    def fieldvaluecache(self):
414
        return self._cacheFetch('fieldvaluecache')
415
416
    def queryresultcacheConfig(self):
417
        return self._cacheConfig('queryresultcache', 'Query Cache')
418
419
    def queryresultcache(self):
420
        return self._cacheFetch('queryresultcache')
421
422
if __name__ == '__main__':
423
    params = parse_params()
424
    SOLR_HOST_PORT = os.environ.get('host_port', 'localhost:8080').replace('http://', '')
425
    mb = SolrMuninGraph(SOLR_HOST_PORT, params)
426
    try:
427
        if hasattr(mb, params['op']):
428
            print getattr(mb,  params['op'])(params['type'])
429
    except Exception, ex:
430
        print "ERROR: %s" % ex
431
        exit(1)