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
#!/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
# Plugins configuration parameters:
25
#
26
# [solr_*]
27
#    env.host_port <host:port>
28
#    env.url <default /solr>
29
#    env.qpshandler_<handlerlabel> <handlerpath>
30
#
31
#    ex:
32
#        env.host_port solrhost:8080 
33
#        env.url /solr
34
#        env.qpshandler_select /select
35
#
36
# Install plugins:
37
#    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
#    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
#
43

    
44

    
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
                'handler': os.environ.get('qpshandler_%s' % handler, 'standard')
63
        }
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
    def readValue(cls, struct, path, convert = None):
81
        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
        if convert:
89
            return convert(obj)
90
        return obj
91

    
92
class SolrCoresAdmin:
93
    def __init__(self, host, solrurl):
94
        self.host = host
95
        self.solrurl = solrurl
96
        self.data = None
97

    
98
    def fetchcores(self):
99
        uri = os.path.join(self.solrurl, "admin/cores?action=STATUS&wt=json")
100
        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
    def __init__(self, host, solrurl, core):
129
        self.host = host
130
        self.data = None
131
        self.core = core
132
        self.solrurl = solrurl
133

    
134
    def _fetch(self):
135
        uri = os.path.join(self.solrurl, "%s/admin/mbeans?stats=true&wt=json" % self.core)
136
        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
        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

    
172
    def _read(self, path, convert = None):
173
        if self.data is None:
174
            self._fetch()
175
        return JSONReader.readValue(self.data, path, convert)
176

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

    
185
    def getCore(self):
186
        return self.core
187

    
188
    def requestcount(self, handler):
189
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'requests']
190
        return self._readInt(path)
191

    
192
    def qps(self, handler):
193
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats', 'avgRequestsPerSecond']
194
        return self._readFloat(path)
195

    
196
    def requesttimes(self, handler):
197
        times = {}
198
        path = ['solr-mbeans', 'QUERYHANDLER', handler, 'stats']
199
        for perc in ['avgTimePerRequest', '75thPcRequestTime', '99thPcRequestTime']:
200
            times[perc] = self._read(path + [perc], float)
201
        return times
202

    
203
    def numdocs(self):
204
        path = ['solr-mbeans', 'CORE', 'searcher', 'stats', 'numDocs']
205
        return self._readInt(path)
206

    
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
    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
#############################################################################
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
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
graph_vlabel request / second
267
graph_category solr
268
graph_period second
269
graph_order {gorder}
270
{cores_qps_graphs}"""
271

    
272
QPSCORE_GRAPH_TPL = """qps_{core}.label {core} Request per second
273
qps_{core}.draw {gtype}
274
qps_{core}.type DERIVE
275
qps_{core}.min 0
276
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
savgtimeperrequest_{core}.type GAUGE
285
savgtimeperrequest_{core}.graph yes
286
s75thpcrequesttime_{core}.label {core} 75th perc
287
s75thpcrequesttime_{core}.type GAUGE
288
s75thpcrequesttime_{core}.graph yes
289
s99thpcrequesttime_{core}.label {core} 99th perc
290
s99thpcrequesttime_{core}.type GAUGE
291
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
INDEXSIZE_GRAPH_TPL = """graph_args --base 1024 -l 0 
300
graph_vlabel Bytes
301
graph_title Index Size
302
graph_category solr
303
graph_info Solr Index Size.
304
graph_order {cores}
305
{cores_config}
306
xmx.label Xmx
307
xmx.colour ff0000
308
"""
309

    
310
INDEXSIZECORE_GRAPH_TPL = """{core}.label {core}
311
{core}.draw STACK""" 
312

    
313
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
#############################################################################
324
# Graph managment
325

    
326
class SolrMuninGraph:
327
    def __init__(self, hostport, solrurl, params):
328
        self.solrcoresadmin = SolrCoresAdmin(hostport, solrurl)
329
        self.hostport = hostport
330
        self.solrurl = solrurl
331
        self.params = params
332

    
333
    def _getMBean(self, core):
334
        return SolrCoreMBean(self.hostport, self.solrurl, core)
335

    
336
    def _cacheConfig(self, cacheType, cacheName):
337
        return CACHE_GRAPH_TPL.format(core=self.params['core'], cacheType=cacheType, cacheName=cacheName)
338

    
339
    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
    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
            vformat = self._format4Value(data[label])
358
            results.append(("%s.value " + vformat) % (label, data[label]))
359
        results.append('multigraph solr_{core}_{cacheType}_size'.format(core=self.params['core'], cacheType=cacheType))
360
        for label in size_fields:
361
            results.append("%s.value %d" % (label, data[label]))
362
        return "\n".join(results)
363

    
364
    def config(self, mtype):
365
        if not mtype or not hasattr(self, '%sConfig' % mtype):
366
            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
        graph = [QPSCORE_GRAPH_TPL.format(core=c, gtype='LINESTACK1') for pos,c in enumerate(cores) ]
384
        return QPSMAIN_GRAPH_TPL.format(
385
            cores_qps_graphs='\n'.join(graph), 
386
            handler=self.params['params']['handler'], 
387
            core=self.params['core'], 
388
            cores_qps_cdefs='%s,%s' % (','.join(map(lambda x: 'qps_%s' % x, cores)),','.join(['+']*(len(cores)-1))), 
389
            gorder=','.join(cores)
390
        )
391

    
392
    def qps(self):
393
        results = []
394
        cores = self._getCores()
395
        for c in cores:
396
            mbean = self._getMBean(c)
397
            results.append('qps_%s.value %d' % (c, mbean.requestcount(self.params['params']['handler'])))
398
        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
                results.append('s%s_%s.value %.5f' % (k.lower(), c, time))
413
        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
        return 'docs.value %d' % mbean.numdocs(**self.params['params'])
421

    
422
    def indexsizeConfig(self):
423
        cores = self._getCores()
424
        graph = [ INDEXSIZECORE_GRAPH_TPL.format(core=c) for c in cores]
425
        return INDEXSIZE_GRAPH_TPL.format(cores=" ".join(cores), cores_config="\n".join(graph))
426

    
427
    def indexsize(self):
428
        results = []
429
        for c, size in self.solrcoresadmin.indexsize(**self.params['params']).items():
430
            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
        return "\n".join(results)
436

    
437
    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
    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
    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'])
483