Projet

Général

Profil

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

root / plugins / solr / solr4_ @ 17f78427

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

1
#!/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
# Solr 4.* munin graph plugin
24
# Project repo: https://github.com/averni/munin-solr
25
#
26
# Plugin configuration parameters:
27
#
28
# [solr_*]
29
#    env.host_port <host:port>
30
#    env.url <default /solr>
31
#    env.qpshandler_<handlerlabel> <handlerpath>
32
#
33
# Example:
34
# [solr_*]
35
#    env.host_port solrhost:8080
36
#    env.url /solr
37
#    env.qpshandler_select /select
38
#
39
# Install plugins:
40
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_numdocs_core_1
41
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_requesttimes_select
42
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_qps
43
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_qps_core_1_select
44
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_indexsize
45
#    ln -s /usr/share/munin/plugins/solr_.py /etc/munin/plugins/solr_memory
46
#
47
#
48

    
49

    
50
import sys
51
import os
52
import httplib
53
import json
54

    
55
def parse_params():
56
    plugname = os.path.basename(sys.argv[0]).split('_', 2)[1:]
57
    params = {
58
        'type': plugname[0],
59
        'op': 'config' if sys.argv[-1] == 'config' else 'fetch',
60
        'core': plugname[1] if len(plugname) > 1 else '',
61
        'params': {}
62
    }
63
    if plugname[0] in[ 'qps', 'requesttimes']:
64
        data = params['core'].rsplit('_', 1)
65
        handler = data.pop()
66
        params['params'] = {
67
                'handler': os.environ.get('qpshandler_%s' % handler, 'standard')
68
        }
69
        if not data:
70
            params['core'] = ''
71
        else:
72
            params['core'] = data[0]
73
    elif plugname[0] ==  'indexsize':
74
        params['params']['core'] = params['core']
75
    return params
76

    
77
#############################################################################
78
# Datasources
79

    
80
class CheckException(Exception):
81
    pass
82

    
83
class JSONReader:
84
    @classmethod
85
    def readValue(cls, struct, path, convert = None):
86
        if not path[0] in struct:
87
            return -1
88
        obj = struct[path[0]]
89
        if not obj:
90
            return -1
91
        for k in path[1:]:
92
            obj = obj[k]
93
        if convert:
94
            return convert(obj)
95
        return obj
96

    
97
class SolrCoresAdmin:
98
    def __init__(self, host, solrurl):
99
        self.host = host
100
        self.solrurl = solrurl
101
        self.data = None
102

    
103
    def fetchcores(self):
104
        uri = os.path.join(self.solrurl, "admin/cores?action=STATUS&wt=json")
105
        conn = httplib.HTTPConnection(self.host)
106
        conn.request("GET", uri)
107
        res = conn.getresponse()
108
        data = res.read()
109
        if res.status != 200:
110
            raise CheckException("Cores status fetch failed: %s\n%s" %( str(res.status), res.read()))
111
        self.data = json.loads(data)
112

    
113
    def getCores(self):
114
        if not self.data:
115
            self.fetchcores()
116
        cores = JSONReader.readValue(self.data, ['status'])
117
        return cores.keys()
118

    
119
    def indexsize(self, core = None):
120
        if not self.data:
121
            self.fetchcores()
122
        if core:
123
            return {
124
                core: JSONReader.readValue(self.data, ['status', core, 'index', 'sizeInBytes'])
125
            }
126
        else:
127
            ret = {}
128
            for core in self.getCores():
129
                ret[core] = JSONReader.readValue(self.data, ['status', core, 'index', 'sizeInBytes'])
130
            return ret
131

    
132
class SolrCoreMBean:
133
    def __init__(self, host, solrurl, core):
134
        self.host = host
135
        self.data = None
136
        self.core = core
137
        self.solrurl = solrurl
138

    
139
    def _fetch(self):
140
        uri = os.path.join(self.solrurl, "%s/admin/mbeans?stats=true&wt=json" % self.core)
141
        conn = httplib.HTTPConnection(self.host)
142
        conn.request("GET", uri)
143
        res = conn.getresponse()
144
        data = res.read()
145
        if res.status != 200:
146
            raise CheckException("MBean fetch failed: %s\n%s" %( str(res.status), res.read()))
147
        raw_data = json.loads(data)
148
        data = {}
149
        self.data = {
150
            'solr-mbeans': data
151
        }
152
        key = None
153
        for pos, el in enumerate(raw_data['solr-mbeans']):
154
            if pos % 2 == 1:
155
                data[key] = el
156
            else:
157
                key = el
158
        self._fetchSystem()
159

    
160
    def _fetchSystem(self):
161
        uri = os.path.join(self.solrurl, "%s/admin/system?stats=true&wt=json" % self.core)
162
        conn = httplib.HTTPConnection(self.host)
163
        conn.request("GET", uri)
164
        res = conn.getresponse()
165
        data = res.read()
166
        if res.status != 200:
167
            raise CheckException("System fetch failed: %s\n%s" %( str(res.status), res.read()))
168
        self.data['system'] = json.loads(data)
169

    
170

    
171
    def _readInt(self, path):
172
        return self._read(path, int)
173

    
174
    def _readFloat(self, path):
175
        return self._read(path, float)
176

    
177
    def _read(self, path, convert = None):
178
        if self.data is None:
179
            self._fetch()
180
        return JSONReader.readValue(self.data, path, convert)
181

    
182
    def _readCache(self, cache):
183
        result = {}
184
        for key, ftype in [('lookups', int), ('hits', int), ('inserts', int), ('evictions', int), ('hitratio', float)]:
185
            path = ['solr-mbeans', 'CACHE', cache, 'stats', 'cumulative_%s' % key]
186
            result[key] = self._read(path, ftype)
187
        result['size'] = self._readInt(['solr-mbeans', 'CACHE', cache, 'stats', 'size'])
188
        return result
189

    
190
    def getCore(self):
191
        return self.core
192

    
193
    def requestcount(self, handler):
194
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'requests']
195
        return self._readInt(path)
196

    
197
    def qps(self, handler):
198
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'avgRequestsPerSecond']
199
        return self._readFloat(path)
200

    
201
    def requesttimes(self, handler):
202
        times = {}
203
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats']
204
        for perc in ['avgTimePerRequest', '75thPcRequestTime', '99thPcRequestTime']:
205
            times[perc] = self._read(path + [perc], float)
206
        return times
207

    
208
    def numdocs(self):
209
        path = ['solr-mbeans', 'CORE', 'searcher', 'stats', 'numDocs']
210
        return self._readInt(path)
211

    
212
    def documentcache(self):
213
        return self._readCache('documentCache')
214

    
215
    def filtercache(self):
216
        return self._readCache('filterCache')
217

    
218
    def fieldvaluecache(self):
219
        return self._readCache('fieldValueCache')
220

    
221
    def queryresultcache(self):
222
        return self._readCache('queryResultCache')
223

    
224
    def memory(self):
225
        data = self._read(['system', 'jvm', 'memory', 'raw'])
226
        del data['used%']
227
        for k in data.keys():
228
            data[k] = int(data[k])
229
        return data
230

    
231
#############################################################################
232
# Graph Templates
233

    
234
CACHE_GRAPH_TPL = """multigraph solr_{core}_{cacheType}_hit_rates
235
graph_category search
236
graph_title Solr {core} {cacheName} Hit rates
237
graph_order lookups hits inserts
238
graph_scale no
239
graph_vlabel Hit Rate
240
graph_args -u 100 --rigid
241
lookups.label Cache lookups
242
lookups.graph no
243
lookups.min 0
244
lookups.type DERIVE
245
inserts.label Cache misses
246
inserts.min 0
247
inserts.draw STACK
248
inserts.cdef inserts,lookups,/,100,*
249
inserts.type DERIVE
250
hits.label Cache hits
251
hits.min 0
252
hits.draw AREA
253
hits.cdef hits,lookups,/,100,*
254
hits.type DERIVE
255

    
256
multigraph solr_{core}_{cacheType}_size
257
graph_title Solr {core} {cacheName} Size
258
graph_args -l 0
259
graph_category search
260
graph_vlabel Size
261
size.label Size
262
size.draw LINE2
263
evictions.label Evictions
264
evictions.draw LINE2
265

    
266
"""
267

    
268
QPSMAIN_GRAPH_TPL = """graph_title Solr {core} {handler} Request per second
269
graph_args --base 1000 -r --lower-limit 0
270
graph_scale no
271
graph_vlabel request / second
272
graph_category search
273
graph_period second
274
graph_order {gorder}
275
{cores_qps_graphs}"""
276

    
277
QPSCORE_GRAPH_TPL = """qps_{core}.label {core} Request per second
278
qps_{core}.draw {gtype}
279
qps_{core}.type DERIVE
280
qps_{core}.min 0
281
qps_{core}.graph yes"""
282

    
283
REQUESTTIMES_GRAPH_TPL = """multigraph {core}_requesttimes
284
graph_title Solr {core} {handler} Time per request
285
graph_args -l 0
286
graph_vlabel millis
287
graph_category search
288
savgtimeperrequest_{core}.label {core} Avg time per request
289
savgtimeperrequest_{core}.type GAUGE
290
savgtimeperrequest_{core}.graph yes
291
s75thpcrequesttime_{core}.label {core} 75th perc
292
s75thpcrequesttime_{core}.type GAUGE
293
s75thpcrequesttime_{core}.graph yes
294
s99thpcrequesttime_{core}.label {core} 99th perc
295
s99thpcrequesttime_{core}.type GAUGE
296
s99thpcrequesttime_{core}.graph yes
297
"""
298

    
299
NUMDOCS_GRAPH_TPL = """graph_title Solr Docs %s
300
graph_vlabel docs
301
docs.label Docs
302
graph_category search"""
303

    
304
INDEXSIZE_GRAPH_TPL = """graph_args --base 1024 -l 0
305
graph_vlabel Bytes
306
graph_title Index Size
307
graph_category search
308
graph_info Solr Index Size.
309
graph_order {cores}
310
{cores_config}
311
xmx.label Xmx
312
xmx.colour ff0000
313
"""
314

    
315
INDEXSIZECORE_GRAPH_TPL = """{core}.label {core}
316
{core}.draw STACK"""
317

    
318
MEMORYUSAGE_GRAPH_TPL = """graph_args --base 1024 -l 0 --upper-limit {availableram}
319
graph_vlabel Bytes
320
graph_title Solr memory usage
321
graph_category search
322
graph_info Solr Memory Usage.
323
used.label Used
324
max.label Max
325
max.colour ff0000
326
"""
327

    
328
#############################################################################
329
# Graph management
330

    
331
class SolrMuninGraph:
332
    def __init__(self, hostport, solrurl, params):
333
        self.solrcoresadmin = SolrCoresAdmin(hostport, solrurl)
334
        self.hostport = hostport
335
        self.solrurl = solrurl
336
        self.params = params
337

    
338
    def _getMBean(self, core):
339
        return SolrCoreMBean(self.hostport, self.solrurl, core)
340

    
341
    def _cacheConfig(self, cacheType, cacheName):
342
        return CACHE_GRAPH_TPL.format(core=self.params['core'], cacheType=cacheType, cacheName=cacheName)
343

    
344
    def _format4Value(self, value):
345
        if isinstance(value, basestring):
346
            return "%s"
347
        if isinstance(value, int):
348
            return "%d"
349
        if isinstance(value, float):
350
            return "%.6f"
351
        return "%s"
352

    
353
    def _cacheFetch(self, cacheType, fields = None):
354
        fields = fields or ['size', 'lookups', 'hits', 'inserts', 'evictions']
355
        hits_fields = ['lookups', 'hits', 'inserts']
356
        size_fields = ['size', 'evictions']
357
        results = []
358
        solrmbean = self._getMBean(self.params['core'])
359
        data = getattr(solrmbean, cacheType)()
360
        results.append('multigraph solr_{core}_{cacheType}_hit_rates'.format(core=self.params['core'], cacheType=cacheType))
361
        for label in hits_fields:
362
            vformat = self._format4Value(data[label])
363
            results.append(("%s.value " + vformat) % (label, data[label]))
364
        results.append('multigraph solr_{core}_{cacheType}_size'.format(core=self.params['core'], cacheType=cacheType))
365
        for label in size_fields:
366
            results.append("%s.value %d" % (label, data[label]))
367
        return "\n".join(results)
368

    
369
    def config(self, mtype):
370
        if not mtype or not hasattr(self, '%sConfig' % mtype):
371
            raise CheckException("Unknown check %s" % mtype)
372
        return getattr(self, '%sConfig' % mtype)()
373

    
374
    def fetch(self, mtype):
375
        if not hasattr(self, params['type']):
376
            return None
377
        return getattr(self, params['type'])()
378

    
379
    def _getCores(self):
380
        if self.params['core']:
381
            cores = [self.params['core']]
382
        else:
383
            cores = sorted(self.solrcoresadmin.getCores())
384
        return cores
385

    
386
    def qpsConfig(self):
387
        cores = self._getCores()
388
        graph = [QPSCORE_GRAPH_TPL.format(core=c, gtype='LINESTACK1') for pos,c in enumerate(cores) ]
389
        return QPSMAIN_GRAPH_TPL.format(
390
            cores_qps_graphs='\n'.join(graph),
391
            handler=self.params['params']['handler'],
392
            core=self.params['core'],
393
            cores_qps_cdefs='%s,%s' % (','.join(map(lambda x: 'qps_%s' % x, cores)),','.join(['+']*(len(cores)-1))),
394
            gorder=','.join(cores)
395
        )
396

    
397
    def qps(self):
398
        results = []
399
        cores = self._getCores()
400
        for c in cores:
401
            mbean = self._getMBean(c)
402
            results.append('qps_%s.value %d' % (c, mbean.requestcount(self.params['params']['handler'])))
403
        return '\n'.join(results)
404

    
405
    def requesttimesConfig(self):
406
        cores = self._getCores()
407
        graphs = [REQUESTTIMES_GRAPH_TPL.format(core=c, handler=self.params['params']['handler']) for c in cores ]
408
        return '\n'.join(graphs)
409

    
410
    def requesttimes(self):
411
        cores = self._getCores()
412
        results = []
413
        for c in cores:
414
            mbean = self._getMBean(c)
415
            results.append('multigraph {core}_requesttimes'.format(core=c))
416
            for k, time in mbean.requesttimes(self.params['params']['handler']).items():
417
                results.append('s%s_%s.value %.5f' % (k.lower(), c, time))
418
        return '\n'.join(results)
419

    
420
    def numdocsConfig(self):
421
        return NUMDOCS_GRAPH_TPL % self.params['core']
422

    
423
    def numdocs(self):
424
        mbean = self._getMBean(self.params['core'])
425
        return 'docs.value %d' % mbean.numdocs(**self.params['params'])
426

    
427
    def indexsizeConfig(self):
428
        cores = self._getCores()
429
        graph = [ INDEXSIZECORE_GRAPH_TPL.format(core=c) for c in cores]
430
        return INDEXSIZE_GRAPH_TPL.format(cores=" ".join(cores), cores_config="\n".join(graph))
431

    
432
    def indexsize(self):
433
        results = []
434
        for c, size in self.solrcoresadmin.indexsize(**self.params['params']).items():
435
            results.append("%s.value %d" % (c, size))
436
        cores = self._getCores()
437
        mbean = self._getMBean(cores[0])
438
        memory = mbean.memory()
439
        results.append('xmx.value %d' % memory['max'])
440
        return "\n".join(results)
441

    
442
    def memoryConfig(self):
443
        cores = self._getCores()
444
        mbean = self._getMBean(cores[0])
445
        memory = mbean.memory()
446
        return MEMORYUSAGE_GRAPH_TPL.format(availableram=memory['max'] * 1.05)
447

    
448
    def memory(self):
449
        results = []
450
        cores = self._getCores()
451
        mbean = self._getMBean(cores[0])
452
        memory = mbean.memory()
453
        return '\n'.join(['used.value %d' % memory['used'], 'max.value %d' % memory['max']])
454

    
455
    def documentcacheConfig(self):
456
        return self._cacheConfig('documentcache', 'Document Cache')
457

    
458
    def documentcache(self):
459
        return self._cacheFetch('documentcache')
460

    
461
    def filtercacheConfig(self):
462
        return self._cacheConfig('filtercache', 'Filter Cache')
463

    
464
    def filtercache(self):
465
        return self._cacheFetch('filtercache')
466

    
467
    def fieldvaluecacheConfig(self):
468
        return self._cacheConfig('fieldvaluecache', 'Field Value Cache')
469

    
470
    def fieldvaluecache(self):
471
        return self._cacheFetch('fieldvaluecache')
472

    
473
    def queryresultcacheConfig(self):
474
        return self._cacheConfig('queryresultcache', 'Query Cache')
475

    
476
    def queryresultcache(self):
477
        return self._cacheFetch('queryresultcache')
478

    
479
if __name__ == '__main__':
480
    params = parse_params()
481
    SOLR_HOST_PORT = os.environ.get('host_port', 'localhost:8080').replace('http://', '')
482
    SOLR_URL  = os.environ.get('url', '/solr')
483
    if SOLR_URL[0] != '/':
484
        SOLR_URL = '/' + SOLR_URL
485
    mb = SolrMuninGraph(SOLR_HOST_PORT, SOLR_URL, params)
486
    if hasattr(mb, params['op']):
487
        print getattr(mb,  params['op'])(params['type'])
488