4
# This plugin requires that PyMunin be separately installed on the system,
5
# available from https://github.com/aouyar/PyMunin
7
# Copyright (C) 2012 Medsphere Systems Corporation
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.
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
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>.
21
# You can contact Medsphere Systems Corporation headquarters at 1917 Palomar
22
# Oaks Way, Suite 200, Carlsbad, CA 92008 or at legal@medsphere.com.
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.
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
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.
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.:
60
from pymunin import MuninGraph, MuninPlugin, muninMain
62
class MuninOpenVistaDatabasesPlugin(MuninPlugin):
63
"""Plugin for GT.M database size with breakdown for regions and globals."""
64
plugin_name = 'ov_databases'
66
category = 'OpenVista'
68
def __init__(self, argv=(), env={}, debug=False):
69
"""Populate Munin Plugin with MuninGraph instances."""
70
MuninPlugin.__init__(self, argv, env, debug)
72
instance_names = get_instance_names()
73
for instance_name in instance_names:
74
self.add_instance_graph(instance_name)
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' %
83
journaled_regions = get_region_names(instance_name,
84
include_unjournaled=False)
85
unjournaled_regions = get_region_names(instance_name,
86
include_journaled=False)
89
[r.lower() for r in journaled_regions] +
90
[r.lower() for r in unjournaled_regions] +
91
['journaled', 'unjournaled', 'all'])
93
graph = MuninGraph(graph_title,
96
args='--base 1024 -l 0',
98
order=' '.join(fields))
100
region_desc = 'The size of the %s.dat file'
101
total_desc = 'The total size of all%s regions'
103
integ_file = get_integ_file(instance_name)
108
for region in journaled_regions + unjournaled_regions:
109
graph.addField(name=region.lower(),
112
info=region_desc % region.lower())
113
subgraph_name = region
115
top_globals, globals_totals = get_globals_sizes(
119
subgraphs[subgraph_name] = self.get_region_graph(
125
graph.addField(name='journaled',
128
info=total_desc % ' journaled')
130
graph.addField(name='unjournaled',
133
info=total_desc % ' unjournaled')
135
graph.addField(name='all',
138
info=total_desc % '')
140
self.appendGraph(graph_name, graph)
141
for subgraph_name, graph in subgraphs.iteritems():
142
self.appendSubgraph(graph_name, subgraph_name, graph)
144
def get_region_graph(self,
148
"""Return a MuninGraph of the region's global sizes and totals. """
150
fields = ([k[1:] for k, v in top_globals] +
151
['other_globals', 'directory', 'index', 'free', 'total'])
154
region_subgraph = MuninGraph(
157
info='Global sizes within the %s %s region' % (instance_name,
159
args='--base 1024 -l 0',
161
order=' '.join(fields))
163
# add fields to the graph
164
global_name_desc = 'Size of the %s global'
166
for global_name, size in top_globals:
167
region_subgraph.addField(name=global_name[1:],
170
info=global_name_desc % global_name)
172
region_subgraph.addField(name='other_globals',
173
label='Other globals',
175
info='Total size of all other globals')
176
region_subgraph.addField(name='directory',
179
info='Size of the database directory')
180
region_subgraph.addField(name='index',
183
info='Size of the database index')
184
region_subgraph.addField(name='free',
187
info='Allocated but unused space')
188
region_subgraph.addField(name='total',
191
info='Total database size')
192
return region_subgraph
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
200
journaled_regions = get_region_names(instance_name,
201
include_unjournaled=False)
202
journaled_total = self.set_regions_values(graph_name,
205
self.setGraphVal(graph_name, 'journaled', journaled_total)
207
unjournaled_regions = get_region_names(instance_name,
208
include_journaled=False)
209
unjournaled_total = self.set_regions_values(graph_name,
212
self.setGraphVal(graph_name, 'unjournaled', unjournaled_total)
214
all_total = journaled_total + unjournaled_total
215
self.setGraphVal(graph_name, 'all', all_total)
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'
221
for region in regions:
222
filename = os.path.join(root_dir,
225
region.lower() + '.dat')
226
if os.path.isfile(filename):
227
size = os.stat(filename).st_size
230
field_name = region.lower()
231
self.setGraphVal(graph_name, field_name, size)
232
self.set_subgraph_values(graph_name, instance_name, region)
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."""
239
subgraph_name = region
241
integ_file = get_integ_file(instance_name)
245
top_globals, globals_totals = get_globals_sizes(instance_name,
249
for global_name, size in top_globals:
250
field_name = global_name[1:]
251
self.setSubgraphVal(parent_graph_name,
256
for field_name, key in [
257
('other_globals', 'Other globals'),
258
('directory', 'Directory'),
263
self.setSubgraphVal(parent_graph_name,
269
def get_integ_file(instance_name):
270
"""Get the filename of the latest mupip integ for the instance."""
272
root = '/opt/openvista'
273
instance_dir = os.path.join(root, instance_name)
275
integ_report_file_glob = os.path.join(instance_dir,
277
'mupip_integ_reports',
281
integ_report_files = glob.iglob(integ_report_file_glob)
282
except AttributeError:
284
integ_report_files = glob.glob(integ_report_file_glob)
287
integ_report_file = max(integ_report_files)
289
# no files matched the glob
290
integ_report_file = None
292
return integ_report_file
294
def get_globals_sizes(instance_name, region, integ_file):
295
"""Return the sizes of top level globals in a region."""
297
args = ['/usr/bin/ovglobals', 'list',
301
stdout, stderr = run_cmd(args)
305
for line in stdout.splitlines():
306
line_parts = line.split(None, 1)
307
if len(line_parts) != 2:
309
key, size = line_parts
310
if key.startswith('^'):
311
globals_sizes[key] = size
313
globals_totals[key] = size
315
global_sizes_list = sorted(globals_sizes.iteritems(),
316
key=lambda p: int(p[1]),
318
top_globals = global_sizes_list[0:10]
319
other_globals = global_sizes_list[10:]
321
globals_totals['Other globals'] = str(sum(int(v) for k, v in other_globals))
323
return top_globals, globals_totals
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()
339
def get_instance_names():
340
"""Return a list of the existing instance names."""
343
root_dir = '/opt/openvista'
345
root_subdirectories = (name for name in os.listdir(root_dir) if
346
os.path.isdir(os.path.join(root_dir, name)))
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):
352
gtm_path = os.path.join(root_dir, directory_name, u'gtm')
353
if not os.path.lexists(gtm_path):
355
instance_names.append(directory_name)
357
return instance_names
359
def is_valid_instance_name(name):
360
"""Return True if the name is valid as an OpenVista instance name."""
362
if any(c in name for c in [u' ', u'(', u')', u'/']):
371
"""(Substitute for the Python 2.5 built-in function.)"""
372
for element in iterable:
378
"""(Substitute for the Python 2.5 built-in function.)"""
379
for element in iterable:
384
def run_cmd(args, timeout=None, raise_retcode=True, pipe_input=None, **kwargs):
385
"""Run a command and return its output."""
387
p = subprocess.Popen(
389
stdout=subprocess.PIPE,
390
stderr=subprocess.PIPE,
393
if timeout is not None:
395
print('Terminating %s (pid %d) for timeout after %d seconds.' %
396
(repr(args), p.pid, timeout))
398
terminator = threading.Timer(timeout, terminate)
401
std_out, std_err = p.communicate(input=pipe_input)
403
if timeout is not None:
406
std_out = std_out.rstrip()
407
std_err = std_err.rstrip()
409
retcode = p.returncode
410
if retcode and raise_retcode:
411
raise CommandError(retcode, std_err)
413
return std_out, std_err
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
423
retcode = muninMain(MuninOpenVistaDatabasesPlugin)
425
traceback.print_exc(file=sys.stdout)
426
#sys.exit(retcode or 1)
427
#from pprint import pprint as pp
432
class CommandError(Exception):
433
def __init__(self, retcode, std_err):
434
self.retcode = retcode
435
self.std_err = std_err
437
return '%d, %s' % (self.retcode, self.std_err)
440
if __name__ == "__main__":