Projet

Général

Profil

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

root / plugins / solr / solr4_ @ b9781d98

Historique | Voir | Annoter | Télécharger (15,8 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 ef960abc antonio
# Solr 4.* munin graph plugin
24
# Plugins configuration parameters:
25 52191f68 antonio
#
26
# [solr_*]
27 ef960abc antonio
#    env.host_port <host:port>
28
#    env.url <default /solr>
29
#    env.qpshandler_<handlerlabel> <handlerpath>
30 52191f68 antonio
#
31 ef960abc antonio
#    ex:
32
#        env.host_port solrhost:8080 
33
#        env.url /solr
34
#        env.qpshandler_select /select
35 52191f68 antonio
#
36 ef960abc antonio
# Install plugins:
37 52191f68 antonio
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_numdocs_core_1
38
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_requesttimes_select
39 ef960abc antonio
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_core_1_select
40
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_indexsize
41
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_memory
42 52191f68 antonio
#
43 ef960abc antonio
44 52191f68 antonio
45
import sys
46
import os
47
import httplib
48
import json
49
50
def parse_params():
51
    plugname = os.path.basename(sys.argv[0]).split('_', 2)[1:]
52
    params = {
53
        'type': plugname[0],
54
        'op': 'config' if sys.argv[-1] == 'config' else 'fetch',
55
        'core': plugname[1] if len(plugname) > 1 else '',
56
        'params': {}
57
    }
58
    if plugname[0] in[ 'qps', 'requesttimes']:
59
        data = params['core'].rsplit('_', 1)
60
        handler = data.pop()
61
        params['params'] = {
62 ef960abc antonio
                'handler': os.environ.get('qpshandler_%s' % handler, 'standard')
63 52191f68 antonio
        }
64
        if not data:
65
            params['core'] = ''
66
        else:
67
            params['core'] = data[0]
68
    elif plugname[0] ==  'indexsize':
69
        params['params']['core'] = params['core']
70
    return params
71
72
#############################################################################
73
# Datasources
74
75
class CheckException(Exception):
76
    pass
77
78
class JSONReader:
79
    @classmethod
80 ef960abc antonio
    def readValue(cls, struct, path, convert = None):
81 52191f68 antonio
        if not path[0] in struct:
82
            return -1
83
        obj = struct[path[0]]
84
        if not obj:
85
            return -1
86
        for k in path[1:]:
87
            obj = obj[k]
88 ef960abc antonio
        if convert:
89
            return convert(obj)
90 52191f68 antonio
        return obj
91
92
class SolrCoresAdmin:
93 ef960abc antonio
    def __init__(self, host, solrurl):
94 52191f68 antonio
        self.host = host
95 ef960abc antonio
        self.solrurl = solrurl
96 52191f68 antonio
        self.data = None
97
98
    def fetchcores(self):
99 ef960abc antonio
        uri = os.path.join(self.solrurl, "admin/cores?action=STATUS&wt=json")
100 52191f68 antonio
        conn = httplib.HTTPConnection(self.host)
101
        conn.request("GET", uri)
102
        res = conn.getresponse()
103
        data = res.read()
104
        if res.status != 200:
105
            raise CheckException("Cores status fetch failed: %s\n%s" %( str(res.status), res.read()))
106
        self.data = json.loads(data)
107
108
    def getCores(self):
109
        if not self.data:
110
            self.fetchcores()
111
        cores = JSONReader.readValue(self.data, ['status'])
112
        return cores.keys()
113
114
    def indexsize(self, core = None):
115
        if not self.data:
116
            self.fetchcores()
117
        if core:
118
            return {
119
                core: JSONReader.readValue(self.data, ['status', core, 'index', 'sizeInBytes'])
120
            }
121
        else:
122
            ret = {}
123
            for core in self.getCores():
124
                ret[core] = JSONReader.readValue(self.data, ['status', core, 'index', 'sizeInBytes'])
125
            return ret
126
127
class SolrCoreMBean:
128 ef960abc antonio
    def __init__(self, host, solrurl, core):
129 52191f68 antonio
        self.host = host
130
        self.data = None
131
        self.core = core
132 ef960abc antonio
        self.solrurl = solrurl
133 52191f68 antonio
134
    def _fetch(self):
135 ef960abc antonio
        uri = os.path.join(self.solrurl, "%s/admin/mbeans?stats=true&wt=json" % self.core)
136 52191f68 antonio
        conn = httplib.HTTPConnection(self.host)
137
        conn.request("GET", uri)
138
        res = conn.getresponse()
139
        data = res.read()
140
        if res.status != 200:
141
            raise CheckException("MBean fetch failed: %s\n%s" %( str(res.status), res.read()))
142
        raw_data = json.loads(data)
143
        data = {}
144
        self.data = {
145
            'solr-mbeans': data
146
        }
147
        key = None
148
        for pos, el in enumerate(raw_data['solr-mbeans']):
149
            if pos % 2 == 1:
150
                data[key] = el
151
            else:
152
                key = el
153 ef960abc antonio
        self._fetchSystem()
154
155
    def _fetchSystem(self):
156
        uri = os.path.join(self.solrurl, "%s/admin/system?stats=true&wt=json" % self.core)
157
        conn = httplib.HTTPConnection(self.host)
158
        conn.request("GET", uri)
159
        res = conn.getresponse()
160
        data = res.read()
161
        if res.status != 200:
162
            raise CheckException("System fetch failed: %s\n%s" %( str(res.status), res.read()))
163
        self.data['system'] = json.loads(data)
164
165
166
    def _readInt(self, path):
167
        return self._read(path, int)
168
169
    def _readFloat(self, path):
170
        return self._read(path, float)
171 52191f68 antonio
172 ef960abc antonio
    def _read(self, path, convert = None):
173 52191f68 antonio
        if self.data is None:
174
            self._fetch()
175 ef960abc antonio
        return JSONReader.readValue(self.data, path, convert)
176 52191f68 antonio
177
    def _readCache(self, cache):
178
        result = {}
179 ef960abc antonio
        for key, ftype in [('lookups', int), ('hits', int), ('inserts', int), ('evictions', int), ('hitratio', float)]:
180 52191f68 antonio
            path = ['solr-mbeans', 'CACHE', cache, 'stats', 'cumulative_%s' % key]
181 ef960abc antonio
            result[key] = self._read(path, ftype)
182
        result['size'] = self._readInt(['solr-mbeans', 'CACHE', cache, 'stats', 'size'])
183 52191f68 antonio
        return result
184
185
    def getCore(self):
186
        return self.core
187
188 ef960abc antonio
    def requestcount(self, handler):
189
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'requests']
190
        return self._readInt(path)
191
192 52191f68 antonio
    def qps(self, handler):
193
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'avgRequestsPerSecond']
194 ef960abc antonio
        return self._readFloat(path)
195 52191f68 antonio
196
    def requesttimes(self, handler):
197
        times = {}
198
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats']
199
        for perc in ['avgTimePerRequest', '75thPcRequestTime', '99thPcRequestTime']:
200 ef960abc antonio
            times[perc] = self._read(path + [perc], float)
201 52191f68 antonio
        return times
202
203
    def numdocs(self):
204
        path = ['solr-mbeans', 'CORE', 'searcher', 'stats', 'numDocs']
205 ef960abc antonio
        return self._readInt(path)
206 52191f68 antonio
207
    def documentcache(self):
208
        return self._readCache('documentCache')
209
210
    def filtercache(self):
211
        return self._readCache('filterCache')
212
213
    def fieldvaluecache(self):
214
        return self._readCache('fieldValueCache')
215
216
    def queryresultcache(self):
217
        return self._readCache('queryResultCache')
218
219 ef960abc antonio
    def memory(self):
220
        data = self._read(['system', 'jvm', 'memory', 'raw'])
221
        del data['used%']
222
        for k in data.keys():
223
            data[k] = int(data[k])
224
        return data
225
226 52191f68 antonio
#############################################################################
227
# Graph Templates
228
229
CACHE_GRAPH_TPL = """multigraph solr_{core}_{cacheType}_hit_rates
230
graph_category solr
231
graph_title Solr {core} {cacheName} Hit rates
232
graph_order lookups hits inserts
233
graph_scale no
234
graph_vlabel Hit Rate
235
graph_args -u 100 --rigid
236
lookups.label Cache lookups
237
lookups.graph no
238
lookups.min 0
239
lookups.type DERIVE
240
inserts.label Cache misses
241
inserts.min 0
242
inserts.draw STACK
243
inserts.cdef inserts,lookups,/,100,*
244
inserts.type DERIVE
245
hits.label Cache hits
246
hits.min 0
247
hits.draw AREA
248
hits.cdef hits,lookups,/,100,*
249
hits.type DERIVE
250
251
multigraph solr_{core}_{cacheType}_size
252
graph_title Solr {core} {cacheName} Size
253
graph_args -l 0
254
graph_category solr
255
graph_vlabel Size
256
size.label Size
257
size.draw LINE2
258
evictions.label Evictions
259
evictions.draw LINE2
260
261
"""
262
263 ef960abc antonio
QPSMAIN_GRAPH_TPL = """graph_title Solr {core} {handler} Request per second
264
graph_args --base 1000 -r --lower-limit 0
265
graph_scale no
266 52191f68 antonio
graph_vlabel request / second
267
graph_category solr
268 ef960abc antonio
graph_period second
269
graph_order {gorder}
270 52191f68 antonio
{cores_qps_graphs}"""
271
272
QPSCORE_GRAPH_TPL = """qps_{core}.label {core} Request per second
273 ef960abc antonio
qps_{core}.draw {gtype}
274
qps_{core}.type DERIVE
275
qps_{core}.min 0
276 52191f68 antonio
qps_{core}.graph yes"""
277
278
REQUESTTIMES_GRAPH_TPL = """multigraph {core}_requesttimes
279
graph_title Solr {core} {handler} Time per request
280
graph_args -l 0
281
graph_vlabel millis
282
graph_category solr
283
savgtimeperrequest_{core}.label {core} Avg time per request
284 ef960abc antonio
savgtimeperrequest_{core}.type GAUGE
285 52191f68 antonio
savgtimeperrequest_{core}.graph yes
286
s75thpcrequesttime_{core}.label {core} 75th perc
287 ef960abc antonio
s75thpcrequesttime_{core}.type GAUGE
288 52191f68 antonio
s75thpcrequesttime_{core}.graph yes
289
s99thpcrequesttime_{core}.label {core} 99th perc
290 ef960abc antonio
s99thpcrequesttime_{core}.type GAUGE
291 52191f68 antonio
s99thpcrequesttime_{core}.graph yes
292
"""
293
294
NUMDOCS_GRAPH_TPL = """graph_title Solr Docs %s
295
graph_vlabel docs
296
docs.label Docs
297
graph_category solr"""
298
299 ef960abc antonio
INDEXSIZE_GRAPH_TPL = """graph_args --base 1024 -l 0 
300 52191f68 antonio
graph_vlabel Bytes
301
graph_title Index Size
302
graph_category solr
303 ef960abc antonio
graph_info Solr Index Size.
304 52191f68 antonio
graph_order {cores}
305
{cores_config}
306 ef960abc antonio
xmx.label Xmx
307
xmx.colour ff0000
308 52191f68 antonio
"""
309
310
INDEXSIZECORE_GRAPH_TPL = """{core}.label {core}
311
{core}.draw STACK""" 
312
313 ef960abc antonio
MEMORYUSAGE_GRAPH_TPL = """graph_args --base 1024 -l 0 --upper-limit {availableram}
314
graph_vlabel Bytes
315
graph_title Solr memory usage
316
graph_category solr
317
graph_info Solr Memory Usage.
318
used.label Used
319
max.label Max
320
max.colour ff0000
321
"""
322
323 52191f68 antonio
#############################################################################
324
# Graph managment
325
326
class SolrMuninGraph:
327 ef960abc antonio
    def __init__(self, hostport, solrurl, params):
328
        self.solrcoresadmin = SolrCoresAdmin(hostport, solrurl)
329 52191f68 antonio
        self.hostport = hostport
330 ef960abc antonio
        self.solrurl = solrurl
331 52191f68 antonio
        self.params = params
332
333
    def _getMBean(self, core):
334 ef960abc antonio
        return SolrCoreMBean(self.hostport, self.solrurl, core)
335 52191f68 antonio
336
    def _cacheConfig(self, cacheType, cacheName):
337
        return CACHE_GRAPH_TPL.format(core=self.params['core'], cacheType=cacheType, cacheName=cacheName)
338
339 b9781d98 antonio
    def _format4Value(self, value):
340
        if isinstance(value, basestring):
341
            return "%s"
342
        if isinstance(value, int):
343
            return "%d"
344
        if isinstance(value, float):
345
            return "%.6f"
346
        return "%s"
347
348 52191f68 antonio
    def _cacheFetch(self, cacheType, fields = None):
349
        fields = fields or ['size', 'lookups', 'hits', 'inserts', 'evictions']
350
        hits_fields = ['lookups', 'hits', 'inserts']
351
        size_fields = ['size', 'evictions']
352
        results = []
353
        solrmbean = self._getMBean(self.params['core'])
354
        data = getattr(solrmbean, cacheType)()
355
        results.append('multigraph solr_{core}_{cacheType}_hit_rates'.format(core=self.params['core'], cacheType=cacheType))
356
        for label in hits_fields:
357 b9781d98 antonio
            vformat = self._format4Value(data[label])
358
            results.append(("%s.value " + vformat) % (label, data[label]))
359 52191f68 antonio
        results.append('multigraph solr_{core}_{cacheType}_size'.format(core=self.params['core'], cacheType=cacheType))
360
        for label in size_fields:
361 ef960abc antonio
            results.append("%s.value %d" % (label, data[label]))
362 52191f68 antonio
        return "\n".join(results)
363
364
    def config(self, mtype):
365 ef960abc antonio
        if not mtype or not hasattr(self, '%sConfig' % mtype):
366 52191f68 antonio
            raise CheckException("Unknown check %s" % mtype)
367
        return getattr(self, '%sConfig' % mtype)()
368
369
    def fetch(self, mtype):
370
        if not hasattr(self, params['type']):
371
            return None
372
        return getattr(self, params['type'])()
373
374
    def _getCores(self):
375
        if self.params['core']:
376
            cores = [self.params['core']]
377
        else:
378
            cores = sorted(self.solrcoresadmin.getCores())
379
        return cores
380
381
    def qpsConfig(self):
382
        cores = self._getCores()
383 ef960abc antonio
        graph = [QPSCORE_GRAPH_TPL.format(core=c, gtype='LINESTACK1') for pos,c in enumerate(cores) ]
384 52191f68 antonio
        return QPSMAIN_GRAPH_TPL.format(
385
            cores_qps_graphs='\n'.join(graph), 
386
            handler=self.params['params']['handler'], 
387
            core=self.params['core'], 
388 ef960abc antonio
            cores_qps_cdefs='%s,%s' % (','.join(map(lambda x: 'qps_%s' % x, cores)),','.join(['+']*(len(cores)-1))), 
389
            gorder=','.join(cores)
390 52191f68 antonio
        )
391
392
    def qps(self):
393
        results = []
394
        cores = self._getCores()
395
        for c in cores:
396
            mbean = self._getMBean(c)
397 ef960abc antonio
            results.append('qps_%s.value %d' % (c, mbean.requestcount(self.params['params']['handler'])))
398 52191f68 antonio
        return '\n'.join(results)
399
400
    def requesttimesConfig(self):
401
        cores = self._getCores()
402
        graphs = [REQUESTTIMES_GRAPH_TPL.format(core=c, handler=self.params['params']['handler']) for c in cores ]
403
        return '\n'.join(graphs)
404
405
    def requesttimes(self):
406
        cores = self._getCores()
407
        results = []
408
        for c in cores:
409
            mbean = self._getMBean(c)
410
            results.append('multigraph {core}_requesttimes'.format(core=c))
411
            for k, time in mbean.requesttimes(self.params['params']['handler']).items():
412 ef960abc antonio
                results.append('s%s_%s.value %.5f' % (k.lower(), c, time))
413 52191f68 antonio
        return '\n'.join(results)
414
415
    def numdocsConfig(self):
416
        return NUMDOCS_GRAPH_TPL % self.params['core']
417
418
    def numdocs(self):
419
        mbean = self._getMBean(self.params['core'])
420 ef960abc antonio
        return 'docs.value %d' % mbean.numdocs(**self.params['params'])
421 52191f68 antonio
422
    def indexsizeConfig(self):
423
        cores = self._getCores()
424
        graph = [ INDEXSIZECORE_GRAPH_TPL.format(core=c) for c in cores]
425 ef960abc antonio
        return INDEXSIZE_GRAPH_TPL.format(cores=" ".join(cores), cores_config="\n".join(graph))
426 52191f68 antonio
427
    def indexsize(self):
428
        results = []
429
        for c, size in self.solrcoresadmin.indexsize(**self.params['params']).items():
430 ef960abc antonio
            results.append("%s.value %d" % (c, size))
431
        cores = self._getCores()
432
        mbean = self._getMBean(cores[0])
433
        memory = mbean.memory()
434
        results.append('xmx.value %d' % memory['max'])
435 52191f68 antonio
        return "\n".join(results)
436
437 ef960abc antonio
    def memoryConfig(self):
438
        cores = self._getCores()
439
        mbean = self._getMBean(cores[0])
440
        memory = mbean.memory()
441
        return MEMORYUSAGE_GRAPH_TPL.format(availableram=memory['max'] * 1.05)
442
443
    def memory(self):
444
        results = []
445
        cores = self._getCores()
446
        mbean = self._getMBean(cores[0])
447
        memory = mbean.memory()
448
        return '\n'.join(['used.value %d' % memory['used'], 'max.value %d' % memory['max']])
449
450 52191f68 antonio
    def documentcacheConfig(self):
451
        return self._cacheConfig('documentcache', 'Document Cache')
452
453
    def documentcache(self):
454
        return self._cacheFetch('documentcache')
455
456
    def filtercacheConfig(self):
457
        return self._cacheConfig('filtercache', 'Filter Cache')
458
459
    def filtercache(self):
460
        return self._cacheFetch('filtercache')
461
462
    def fieldvaluecacheConfig(self):
463
        return self._cacheConfig('fieldvaluecache', 'Field Value Cache')
464
465
    def fieldvaluecache(self):
466
        return self._cacheFetch('fieldvaluecache')
467
468
    def queryresultcacheConfig(self):
469
        return self._cacheConfig('queryresultcache', 'Query Cache')
470
471
    def queryresultcache(self):
472
        return self._cacheFetch('queryresultcache')
473
474
if __name__ == '__main__':
475
    params = parse_params()
476
    SOLR_HOST_PORT = os.environ.get('host_port', 'localhost:8080').replace('http://', '')
477 ef960abc antonio
    SOLR_URL  = os.environ.get('url', '/solr')
478
    if SOLR_URL[0] != '/':
479
        SOLR_URL = '/' + SOLR_URL 
480
    mb = SolrMuninGraph(SOLR_HOST_PORT, SOLR_URL, params)
481
    if hasattr(mb, params['op']):
482
        print getattr(mb,  params['op'])(params['type'])