~derek-name/openvista-gtm-integration/bug611314-Munin_globals

« back to all changes in this revision

Viewing changes to scripts/usr/share/munin/plugins/ov_databases

  • Committer: Derek Veit
  • Date: 2012-02-28 21:57:46 UTC
  • Revision ID: derek.veit@medsphere.com-20120228215746-xjfqnlkcn9y6p5vc
Replace the openvista_databases_ Munin plugin with the ov_databases Munin plugin, which uses ovglobals and ovregions to provide graphs of regions and globals for all instances.
Add openvista and gtm group membership for running Munin plugins starting with "ov_".

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf8 -*-
 
3
 
 
4
# This plugin requires that PyMunin be separately installed on the system,
 
5
# available from https://github.com/aouyar/PyMunin
 
6
 
 
7
# Copyright (C) 2012 Medsphere Systems Corporation
 
8
#
 
9
# This program is free software; you can redistribute it and/or modify it
 
10
# solely under the terms of the GNU Affero General Public License version 3 as
 
11
# published by the Free Software Foundation.
 
12
 
13
# This program is distributed in the hope that it will be useful, but WITHOUT
 
14
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
15
# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 
16
# details.
 
17
 
18
# You should have received a copy of the GNU Affero General Public License
 
19
# along with this program.  If not, see <http://www.gnu.org/licenses>.
 
20
 
21
# You can contact Medsphere Systems Corporation headquarters at 1917 Palomar
 
22
# Oaks Way, Suite 200, Carlsbad, CA 92008 or at legal@medsphere.com.
 
23
 
 
24
 
 
25
"""
 
26
ov_databases
 
27
 
 
28
This Munin plugin will provide graphs showing the size of each OpenVista
 
29
database on the server.  Within each graph, the size of each region will be
 
30
shown as a component of the total.  The subtotals of journaled regions and
 
31
unjournaled regions will also be indicated.
 
32
 
 
33
It will also provide graphs for each database region showing the sizes of the
 
34
largest top-level globals, the space using for indexes and the directory, and
 
35
the free space.
 
36
 
 
37
Installation should normally be accomplished automatically by installing the
 
38
openvista-munin-plugins package.  Otherwise, the script must be copied into
 
39
/usr/share/munin/plugins/ and then a symbolic link must be created to that from
 
40
/etc/munin/plugins/.  The munin-node service should then be restarted.
 
41
 
 
42
The user running this script must be in the openvista group (e.g. for reading
 
43
the /opt/openvista directory) and in the gtm group (e.g. for running ovregions,
 
44
which runs mumps).  The munin user should be in the openvista and gtm groups,
 
45
and the plugin script name must have group membership configured for it in
 
46
/etc/munin/plugin-conf.d/openvista, e.g.:
 
47
[ov_*]
 
48
group openvista, gtm
 
49
 
 
50
"""
 
51
 
 
52
 
 
53
import glob
 
54
import os
 
55
import subprocess
 
56
import sys
 
57
import threading
 
58
import traceback
 
59
 
 
60
from pymunin import MuninGraph, MuninPlugin, muninMain
 
61
 
 
62
class MuninOpenVistaDatabasesPlugin(MuninPlugin):
 
63
    """Plugin for GT.M database size with breakdown for regions and globals."""
 
64
    plugin_name = 'ov_databases'
 
65
    isMultigraph = True
 
66
    category = 'OpenVista'
 
67
    
 
68
    def __init__(self, argv=(), env={}, debug=False):
 
69
        """Populate Munin Plugin with MuninGraph instances."""
 
70
        MuninPlugin.__init__(self, argv, env, debug)
 
71
        
 
72
        instance_names = get_instance_names()
 
73
        for instance_name in instance_names:
 
74
            self.add_instance_graph(instance_name)
 
75
    
 
76
    def add_instance_graph(self, instance_name):
 
77
        """Add a graph for one OpenVista instance."""
 
78
        graph_name = 'ov_databases_%s' % instance_name
 
79
        graph_title = 'OV Database files in %s' % instance_name
 
80
        graph_desc = ('The size of the OpenVista database files in %s' %
 
81
                      instance_name)
 
82
        
 
83
        journaled_regions = get_region_names(instance_name,
 
84
                                             include_unjournaled=False)
 
85
        unjournaled_regions = get_region_names(instance_name,
 
86
                                               include_journaled=False)
 
87
        
 
88
        fields = (
 
89
            [r.lower() for r in journaled_regions] +
 
90
            [r.lower() for r in unjournaled_regions] +
 
91
            ['journaled', 'unjournaled', 'all'])
 
92
        
 
93
        graph = MuninGraph(graph_title,
 
94
                           self.category,
 
95
                           info=graph_desc,
 
96
                           args='--base 1024 -l 0',
 
97
                           vlabel='Bytes',
 
98
                           order=' '.join(fields))
 
99
        
 
100
        region_desc = 'The size of the %s.dat file'
 
101
        total_desc = 'The total size of all%s regions'
 
102
        
 
103
        integ_file = get_integ_file(instance_name)
 
104
        
 
105
        subgraphs = {}
 
106
        
 
107
        draw = 'AREA'
 
108
        for region in journaled_regions + unjournaled_regions:
 
109
            graph.addField(name=region.lower(),
 
110
                           label=region,
 
111
                           draw=draw,
 
112
                           info=region_desc % region.lower())
 
113
            subgraph_name = region
 
114
            if integ_file:
 
115
                top_globals, globals_totals = get_globals_sizes(
 
116
                    instance_name,
 
117
                    region,
 
118
                    integ_file)
 
119
                subgraphs[subgraph_name] = self.get_region_graph(
 
120
                    instance_name,
 
121
                    region,
 
122
                    top_globals)
 
123
            draw = 'STACK'
 
124
        
 
125
        graph.addField(name='journaled',
 
126
                       label='Journaled',
 
127
                       draw='LINE2',
 
128
                       info=total_desc % ' journaled')
 
129
        
 
130
        graph.addField(name='unjournaled',
 
131
                       label='Unjournaled',
 
132
                       draw='LINE2',
 
133
                       info=total_desc % ' unjournaled')
 
134
        
 
135
        graph.addField(name='all',
 
136
                       label='Total',
 
137
                       draw='LINE2',
 
138
                       info=total_desc % '')
 
139
        
 
140
        self.appendGraph(graph_name, graph)
 
141
        for subgraph_name, graph in subgraphs.iteritems():
 
142
            self.appendSubgraph(graph_name, subgraph_name, graph)
 
143
    
 
144
    def get_region_graph(self,
 
145
                         instance_name,
 
146
                         region,
 
147
                         top_globals):
 
148
        """Return a MuninGraph of the region's global sizes and totals. """
 
149
        
 
150
        fields = ([k[1:] for k, v in top_globals] +
 
151
                  ['other_globals', 'directory', 'index', 'free', 'total'])
 
152
        
 
153
        # define the graph
 
154
        region_subgraph = MuninGraph(
 
155
            region,
 
156
            self.category,
 
157
            info='Global sizes within the %s %s region' % (instance_name,
 
158
                                                           region),
 
159
            args='--base 1024 -l 0',
 
160
            vlabel='Bytes',
 
161
            order=' '.join(fields))
 
162
        
 
163
        # add fields to the graph
 
164
        global_name_desc = 'Size of the %s global'
 
165
        draw = 'AREA'
 
166
        for global_name, size in top_globals:
 
167
            region_subgraph.addField(name=global_name[1:],
 
168
                                     label=global_name,
 
169
                                     draw=draw,
 
170
                                     info=global_name_desc % global_name)
 
171
            draw = 'STACK'
 
172
        region_subgraph.addField(name='other_globals',
 
173
                                 label='Other globals',
 
174
                                 draw=draw,
 
175
                                 info='Total size of all other globals')
 
176
        region_subgraph.addField(name='directory',
 
177
                                 label='Directory',
 
178
                                 draw='STACK',
 
179
                                 info='Size of the database directory')
 
180
        region_subgraph.addField(name='index',
 
181
                                 label='Index',
 
182
                                 draw='STACK',
 
183
                                 info='Size of the database index')
 
184
        region_subgraph.addField(name='free',
 
185
                                 label='Free',
 
186
                                 draw='STACK',
 
187
                                 info='Allocated but unused space')
 
188
        region_subgraph.addField(name='total',
 
189
                                 label='Total',
 
190
                                 draw='LINE2',
 
191
                                 info='Total database size')
 
192
        return region_subgraph
 
193
    
 
194
    def retrieveVals(self):
 
195
        """Retrieve values for graphs."""
 
196
        instance_names = get_instance_names()
 
197
        for instance_name in instance_names:
 
198
            graph_name = 'ov_databases_%s' % instance_name
 
199
            
 
200
            journaled_regions = get_region_names(instance_name,
 
201
                                                 include_unjournaled=False)
 
202
            journaled_total = self.set_regions_values(graph_name,
 
203
                                                      instance_name,
 
204
                                                      journaled_regions)
 
205
            self.setGraphVal(graph_name, 'journaled', journaled_total)
 
206
            
 
207
            unjournaled_regions = get_region_names(instance_name,
 
208
                                                   include_journaled=False)
 
209
            unjournaled_total = self.set_regions_values(graph_name,
 
210
                                                        instance_name,
 
211
                                                        unjournaled_regions)
 
212
            self.setGraphVal(graph_name, 'unjournaled', unjournaled_total)
 
213
            
 
214
            all_total = journaled_total + unjournaled_total
 
215
            self.setGraphVal(graph_name, 'all', all_total)
 
216
    
 
217
    def set_regions_values(self, graph_name, instance_name, regions):
 
218
        """Set the values for the regions and return their total."""
 
219
        root_dir = '/opt/openvista'
 
220
        total = 0
 
221
        for region in regions:
 
222
            filename = os.path.join(root_dir,
 
223
                                    instance_name,
 
224
                                    'globals',
 
225
                                    region.lower() + '.dat')
 
226
            if os.path.isfile(filename):
 
227
                size = os.stat(filename).st_size
 
228
            else:
 
229
                size = 0
 
230
            field_name = region.lower()
 
231
            self.setGraphVal(graph_name, field_name, size)
 
232
            self.set_subgraph_values(graph_name, instance_name, region)
 
233
            total += size
 
234
        return total
 
235
    
 
236
    def set_subgraph_values(self, parent_graph_name, instance_name, region):
 
237
        """Set the values for the region's globals and its other totals."""
 
238
        
 
239
        subgraph_name = region
 
240
        
 
241
        integ_file = get_integ_file(instance_name)
 
242
        if not integ_file:
 
243
            return
 
244
        
 
245
        top_globals, globals_totals = get_globals_sizes(instance_name,
 
246
                                                        region,
 
247
                                                        integ_file)
 
248
        
 
249
        for global_name, size in top_globals:
 
250
            field_name = global_name[1:]
 
251
            self.setSubgraphVal(parent_graph_name,
 
252
                                subgraph_name,
 
253
                                field_name,
 
254
                                size)
 
255
        
 
256
        for field_name, key in [
 
257
                ('other_globals', 'Other globals'),
 
258
                ('directory', 'Directory'),
 
259
                ('index', 'Index'),
 
260
                ('free', 'Free'),
 
261
                ('total', 'Total'),
 
262
                ]:
 
263
            self.setSubgraphVal(parent_graph_name,
 
264
                                subgraph_name,
 
265
                                field_name,
 
266
                                globals_totals[key])
 
267
 
 
268
 
 
269
def get_integ_file(instance_name):
 
270
    """Get the filename of the latest mupip integ for the instance."""
 
271
    
 
272
    root = '/opt/openvista'
 
273
    instance_dir = os.path.join(root, instance_name)
 
274
    
 
275
    integ_report_file_glob = os.path.join(instance_dir,
 
276
                                          'tmp',
 
277
                                          'mupip_integ_reports',
 
278
                                          'integ_report_*')
 
279
    
 
280
    try:
 
281
        integ_report_files = glob.iglob(integ_report_file_glob)
 
282
    except AttributeError:
 
283
        # Python <2.5
 
284
        integ_report_files = glob.glob(integ_report_file_glob)
 
285
    
 
286
    try:
 
287
        integ_report_file = max(integ_report_files)
 
288
    except ValueError:
 
289
        # no files matched the glob
 
290
        integ_report_file = None
 
291
    
 
292
    return integ_report_file
 
293
 
 
294
def get_globals_sizes(instance_name, region, integ_file):
 
295
    """Return the sizes of top level globals in a region."""
 
296
    
 
297
    args = ['/usr/bin/ovglobals', 'list',
 
298
                                  '-r', region,
 
299
                                  '-f', integ_file,
 
300
                                  instance_name]
 
301
    stdout, stderr = run_cmd(args)
 
302
    
 
303
    globals_sizes = {}
 
304
    globals_totals = {}
 
305
    for line in stdout.splitlines():
 
306
        line_parts = line.split(None, 1)
 
307
        if len(line_parts) != 2:
 
308
            continue
 
309
        key, size = line_parts
 
310
        if key.startswith('^'):
 
311
            globals_sizes[key] = size
 
312
        else:
 
313
            globals_totals[key] = size
 
314
    
 
315
    global_sizes_list = sorted(globals_sizes.iteritems(),
 
316
                               key=lambda p: int(p[1]),
 
317
                               reverse=True)
 
318
    top_globals = global_sizes_list[0:10]
 
319
    other_globals = global_sizes_list[10:]
 
320
    
 
321
    globals_totals['Other globals'] = str(sum(int(v) for k, v in other_globals))
 
322
    
 
323
    return top_globals, globals_totals
 
324
 
 
325
def get_region_names(instance_name,
 
326
                     include_journaled=True,
 
327
                     include_unjournaled=True):
 
328
    """Return a list of the regions in the named OpenVista instance."""
 
329
    args = ['ovregions', 'list', instance_name]
 
330
    if not include_journaled:
 
331
        args.extend(['-j', 'no'])
 
332
    if not include_unjournaled:
 
333
        args.extend(['-j', 'yes'])
 
334
    args.append(instance_name)
 
335
    stdout, stderr = run_cmd(args)
 
336
    region_names = stdout.splitlines()
 
337
    return region_names
 
338
 
 
339
def get_instance_names():
 
340
    """Return a list of the existing instance names."""
 
341
    instance_names = []
 
342
    
 
343
    root_dir = '/opt/openvista'
 
344
    
 
345
    root_subdirectories = (name for name in os.listdir(root_dir) if
 
346
                          os.path.isdir(os.path.join(root_dir, name)))
 
347
    
 
348
    for directory_name in root_subdirectories:
 
349
        # exclude if not a valid OpenVista instance name
 
350
        if not is_valid_instance_name(directory_name):
 
351
            continue
 
352
        gtm_path = os.path.join(root_dir, directory_name, u'gtm')
 
353
        if not os.path.lexists(gtm_path):
 
354
            continue
 
355
        instance_names.append(directory_name)
 
356
    
 
357
    return instance_names
 
358
 
 
359
def is_valid_instance_name(name):
 
360
    """Return True if the name is valid as an OpenVista instance name."""
 
361
    
 
362
    if any(c in name for c in [u' ', u'(', u')', u'/']):
 
363
        return False
 
364
    else:
 
365
        return True
 
366
 
 
367
try:
 
368
    all([])
 
369
except NameError:
 
370
    def all(iterable):
 
371
        """(Substitute for the Python 2.5 built-in function.)"""
 
372
        for element in iterable:
 
373
            if not element:
 
374
                return False
 
375
        return True
 
376
    
 
377
    def any(iterable):
 
378
        """(Substitute for the Python 2.5 built-in function.)"""
 
379
        for element in iterable:
 
380
            if element:
 
381
                return True
 
382
        return False
 
383
 
 
384
def run_cmd(args, timeout=None, raise_retcode=True, pipe_input=None, **kwargs):
 
385
    """Run a command and return its output."""
 
386
    
 
387
    p = subprocess.Popen(
 
388
        args,
 
389
        stdout=subprocess.PIPE,
 
390
        stderr=subprocess.PIPE,
 
391
        **kwargs)
 
392
    
 
393
    if timeout is not None:
 
394
        def terminate():
 
395
            print('Terminating %s (pid %d) for timeout after %d seconds.' %
 
396
                   (repr(args), p.pid, timeout))
 
397
            p.terminate()
 
398
        terminator = threading.Timer(timeout, terminate)
 
399
        terminator.start()
 
400
    
 
401
    std_out, std_err = p.communicate(input=pipe_input)
 
402
    
 
403
    if timeout is not None:
 
404
        terminator.cancel()
 
405
    
 
406
    std_out = std_out.rstrip()
 
407
    std_err = std_err.rstrip()
 
408
    
 
409
    retcode = p.returncode
 
410
    if retcode and raise_retcode:
 
411
        raise CommandError(retcode, std_err)
 
412
    
 
413
    return std_out, std_err
 
414
 
 
415
def main():
 
416
    #sys.exit(muninMain(MuninOpenVistaDatabasesPlugin))
 
417
    # When running munin-update or testing with `telnet localhost 4949`, the
 
418
    # normal behavior if an exception occurs would be to print "# Bad exit" and
 
419
    # no Python traceback would be shown.  This try/except block will get the
 
420
    # traceback and print it to stdout so that it can be useful, and then
 
421
    # return non-zero.
 
422
    try:
 
423
        retcode = muninMain(MuninOpenVistaDatabasesPlugin)
 
424
    except Exception, e:
 
425
        traceback.print_exc(file=sys.stdout)
 
426
        #sys.exit(retcode or 1)
 
427
    #from pprint import pprint as pp
 
428
    #pp(os.environ)
 
429
    #sys.exit(retcode)
 
430
 
 
431
 
 
432
class CommandError(Exception):
 
433
    def __init__(self, retcode, std_err):
 
434
        self.retcode = retcode
 
435
        self.std_err = std_err
 
436
    def __str__(self):
 
437
        return '%d, %s' % (self.retcode, self.std_err)
 
438
 
 
439
 
 
440
if __name__ == "__main__":
 
441
    main()
 
442