nagiosplugin-1.2.2/0000755000175100017510000000000012341114372013562 5ustar ckcore00000000000000nagiosplugin-1.2.2/setup.py0000644000175100017510000000363612341114271015302 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from setuptools import setup, find_packages import codecs import os import sys here = os.path.abspath(os.path.dirname(__file__)) longdesc = [] for readme in ['README.txt', 'HACKING.txt', 'CONTRIBUTORS.txt', 'HISTORY.txt']: with codecs.open(readme, encoding='utf-8') as f: longdesc.append(f.read()) with codecs.open('version.txt', encoding='ascii') as f: version = f.read().strip() if sys.version_info < (2, 7): extras_require = {'test': ['setuptools', 'unittest2', 'argparse']} else: extras_require = {'test': ['setuptools']} setup( name='nagiosplugin', version=version, description='Class library for writing Nagios (Icinga) plugins', long_description='\n\n'.join(longdesc), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Zope Public License', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Monitoring', ], keywords='Nagios Icinga plugin check monitoring', author='Christian Kauhaus', author_email='kc@gocept.com', url='http://projects.gocept.com/projects/nagiosplugin', download_url='http://pypi.python.org/pypi/nagiosplugin', license='ZPL-2.1', packages=find_packages('src'), package_dir={'': 'src'}, include_package_data=True, zip_safe=False, test_suite='nagiosplugin.tests', extras_require=extras_require, ) nagiosplugin-1.2.2/LICENSE.txt0000644000175100017510000000415012341114271015403 0ustar ckcore00000000000000Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nagiosplugin-1.2.2/PKG-INFO0000644000175100017510000002770012341114372014665 0ustar ckcore00000000000000Metadata-Version: 1.1 Name: nagiosplugin Version: 1.2.2 Summary: Class library for writing Nagios (Icinga) plugins Home-page: http://projects.gocept.com/projects/nagiosplugin Author: Christian Kauhaus Author-email: kc@gocept.com License: ZPL-2.1 Download-URL: http://pypi.python.org/pypi/nagiosplugin Description: The nagiosplugin library ======================== About ----- **nagiosplugin** is a Python class library which helps writing Nagios (or Icinga) compatible plugins easily in Python. It cares for much of the boilerplate code and default logic commonly found in Nagios checks, including: - Nagios 3 Plugin API compliant parameters and output formatting - Full Nagios range syntax support - Automatic threshold checking - Multiple independend measures - Custom status line to communicate the main point quickly - Long output and performance data - Timeout handling - Persistent "cookies" to retain state information between check runs - Resume log file processing at the point where the last run left - No dependencies beyond the Python standard library (except for Python 2.6). nagiosplugin runs on POSIX and Windows systems. It is compatible with Python 3.4, Python 3.3, Python 3.2, Python 2.7, and Python 2.6. Feedback and Suggestions ------------------------ nagiosplugin is primarily written and maintained by Christian Kauhaus . Feel free to contact the author for bugs, suggestions and patches. A public issue tracker can be found at http://projects.gocept.com/projects/nagiosplugin/issues. There is also a forum available at https://projects.gocept.com/projects/nagiosplugin/boards. License ------- The nagiosplugin package is released under the Zope Public License 2.1 (ZPL), a BSD-style Open Source license. Documentation ------------- Comprehensive documentation is `available online`_. The examples mentioned in the `tutorials`_ can also be found in the `nagiosplugin/examples` directory of the source distribution. .. _available online: http://pythonhosted.org/nagiosplugin/ .. _tutorials: http://pythonhosted.org/nagiosplugin/tutorial/ .. vim: set ft=rst: Development =========== Getting the source ------------------ The source can be obtained via mercurial from https://bitbucket.org/gocept/nagiosplugin:: hg clone https://bitbucket.org/gocept/nagiosplugin This package supports installation in a virtualenv using zc.buildout. Creating a build with zc.buildout --------------------------------- First, create a virtualenv if not already present:: virtualenv -p python3.2 . If you want to use Python 2.7, specify this Python version while creating the virtualenv:: virtualenv -p python2.7 . Sometimes, you will run into strange setuptools/distribute version conflicts. The best way around is to keep setuptools and distribute entirely out of the virtualenv:: virtualenv -p python3.2 --no-setuptools . `bootstrap.py` and zc.buildout will take care of all required dependencies. Then launch the usual buildout steps:: bin/python3.2 bootstrap.py bin/buildout Tests ----- When the buildout succeeds, run the unit test by invoking:: bin/test Our `build server`_ runs test against the trunk on a regular basis. .. image:: https://builds.gocept.com/job/nagiosplugin/badge/icon :target: https://builds.gocept.com/job/nagiosplugin/ .. _build server: https://builds.gocept.com/job/nagiosplugin/ nagiosplugin also includes support for coverage reports. It aims at 100% test coverage where possible. The preferred way to get a coverate report is to use :: bin/createcoverage to determine test coverage and open the report in a web browser. Alternatively, run :: bin/coverage run bin/test to get a purely text-based coverage report. You may run the supplied examples with the local interpreter:: bin/py src/nagiosplugin/examples/check_load.py Documentation ------------- The documentation uses Sphinx. Build the documentation (buildout should have been running before to install Sphinx etc.):: make -C doc html How to release -------------- To make a release, we prefer `zest.releaser`_. To make a release, follow the standard procedure, which usually boils down to:: fullrelease `nagiosplugin` tried to obey the semantic version numbering specification published on SemVer_ but adapts it a little bit to be `PEP 386`_ compliant. .. _zest.releaser: http://pypi.python.org/pypi/zest.releaser/ .. _SemVer: http://semver.org/ .. _PEP 386: http://www.python.org/dev/peps/pep-0386/ .. vim: set ft=rst sw=3 sts=3 et: Contributors ============ `nagiosplugin` has become what it is now with the help of many contributors from the community. We want to thank everyone who has invested time and energy to make `nagiosplugin` better: * Wolfgang Schnerring for thoughts on the design. * Thomas Lotze for improving the test infrastructure. * Christian Theune for comments and general feedback. * Michael Howitz and Andrei Chirila for the Python 3 port. * Birger Schmidt for bug reports. * Florian Lagg for Windows compatibility fixes * Jeff Goldschrafe for the Python 2.6 backport. * José Manuel Fardello for a logging fix. * Jordan Metzmeier for build fixes and Debian packaging. * Andrey Panfilov for a perfdata fix. * Mihai Limbășan for various output formatting fixes. .. vim: set ft=rst sw=3 sts=3 et: Release History =============== 1.2.2 (2014-05-27) ------------------ - Mention that nagiosplugin also runs with Python 3.4 (no code changes necessary). - Make name prefix in status output optional by allowing to assign None to Check.name. - Accept bare metric as return value from Resource.probe(). - Fix bug where Context.describe() was not used to obtain metric description (#13162). 1.2.1 (2014-03-19) ------------------ - Fix build failures with LANG=C (#13140). - Remove length limitation of perfdata labels (#13214). - Fix formatting of large integers as Metric values (#13287). - Range: allow simple numerals as argument to Range() (#12658). - Cookie: allow for empty state file specification (#12788). 1.2 (2013-11-08) ---------------- - New `Summary.empty` method is called if there are no results present (#11593). - Improve range violation wording (#11597). - Ensure that nagiosplugin install correctly with current setuptools (#12660). - Behave and do not attach anything to the root logger. - Add debugging topic guide. Explain how to disable the timeout when using pdb (#11592). 1.1 (2013-06-19) ---------------- - Identical to 1.1b1. 1.1b1 (2013-05-28) ------------------ - Made compatible with Python 2.6 (#12297). - Tutorial #3: check_users (#11539). - Minor documentation improvements. 1.0.0 (2013-02-05) ------------------ - LogTail returns lines as byte strings in Python 3 to avoid codec issues (#11564). - LogTail gives a line-based iterator instead of a file object (#11564). - Basic API docs for the most important classes (#11612). - Made compatible with Python 2.7 (#11533). - Made compatible with Python 3.3. 1.0.0b1 (2012-10-29) -------------------- - Improve error reporting for missing contexts. - Exit with code 3 if no metrics have been generated. - Improve default Summary.verbose() to list all threshold violations. - Move main source repository to https://bitbucket.org/gocept/nagiosplugin/ (#11561). 1.0.0a2 (2012-10-26) -------------------- - API docs for the most important classes (#7939). - Added two tutorials (#9425). - Fix packaging issues. 1.0.0a1 (2012-10-25) -------------------- - Completely reworked API. The new API is not compatible with the old 0.4 API so you must update your plugins. - Python 3 support. - The `Cookie` class is now basically a persistent dict and accepts key/value pairs. Cookie are stored as JSON files by default so they can be inspected by the system administrator (#9400). - New `LogTail` class which provides convenient access to constantly growing log files which are eventually rotated. 0.4.5 (2012-06-18) ------------------ - Windows port. `nagiosplugin` code now runs under pywin32 (#10899). - Include examples in egg release (#9901). 0.4.4 (2011-07-18) ------------------ Bugfix release to fix issues reported by users. - Improve Mac OS X compatibility (#8755). - Include examples in distribution (#8555). 0.4.3 (2010-12-17) ------------------ - Change __str__ representation of large numbers to avoid scientific notation. 0.4.2 (2010-10-11) ------------------ - Packaging issues. 0.4.1 (2010-09-21) ------------------ - Fix distribution to install correctly. - Documentation: tutorial and topic guides. 0.4 (2010-08-17) ---------------- - Initial public release. .. vim: set ft=rst sw=3 sts=3 spell spelllang=en: Keywords: Nagios Icinga plugin check monitoring Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Monitoring nagiosplugin-1.2.2/buildout.cfg0000644000175100017510000000207212341114271016071 0ustar ckcore00000000000000[buildout] parts = nagiosplugin createcoverage test sphinx doc allow-picked-versions = false develop = . newest = false package = nagiosplugin versions = versions [versions] argparse = 1.1 collective.xmltestreport = 1.3.1 coverage = 3.7.1 createcoverage = 1.2 docutils = 0.11 iw.recipe.cmd = 0.3 Jinja2 = 2.6 nose = 1.3.3 Pygments = 1.6 setuptools = 3.6 Sphinx = 1.2.2 tranchitella.recipe.nose = 0.1 unittest2 = 0.5.1 z3c.recipe.scripts = 1.0.1 zc.buildout = 2.2.1 zc.recipe.egg = 2.0.1 zope.exceptions = 4.0.7 zope.interface = 4.1.1 zope.testing = 4.1.3 [nagiosplugin] recipe = zc.recipe.egg eggs = ${buildout:package} [test] interpreter = py [test] recipe = tranchitella.recipe.nose eggs = ${buildout:package} [test] defaults = -d # generate a script that will build the html user docs [sphinx] recipe = zc.recipe.egg:script eggs = Sphinx nagiosplugin # update the user docs each time buildout is run [doc] recipe = iw.recipe.cmd on_install = true on_update = true cmds = make -C ${buildout:directory}/doc html [createcoverage] recipe = zc.recipe.egg eggs = createcoverage nagiosplugin-1.2.2/HACKING.txt0000644000175100017510000000456512341114271015377 0ustar ckcore00000000000000Development =========== Getting the source ------------------ The source can be obtained via mercurial from https://bitbucket.org/gocept/nagiosplugin:: hg clone https://bitbucket.org/gocept/nagiosplugin This package supports installation in a virtualenv using zc.buildout. Creating a build with zc.buildout --------------------------------- First, create a virtualenv if not already present:: virtualenv -p python3.2 . If you want to use Python 2.7, specify this Python version while creating the virtualenv:: virtualenv -p python2.7 . Sometimes, you will run into strange setuptools/distribute version conflicts. The best way around is to keep setuptools and distribute entirely out of the virtualenv:: virtualenv -p python3.2 --no-setuptools . `bootstrap.py` and zc.buildout will take care of all required dependencies. Then launch the usual buildout steps:: bin/python3.2 bootstrap.py bin/buildout Tests ----- When the buildout succeeds, run the unit test by invoking:: bin/test Our `build server`_ runs test against the trunk on a regular basis. .. image:: https://builds.gocept.com/job/nagiosplugin/badge/icon :target: https://builds.gocept.com/job/nagiosplugin/ .. _build server: https://builds.gocept.com/job/nagiosplugin/ nagiosplugin also includes support for coverage reports. It aims at 100% test coverage where possible. The preferred way to get a coverate report is to use :: bin/createcoverage to determine test coverage and open the report in a web browser. Alternatively, run :: bin/coverage run bin/test to get a purely text-based coverage report. You may run the supplied examples with the local interpreter:: bin/py src/nagiosplugin/examples/check_load.py Documentation ------------- The documentation uses Sphinx. Build the documentation (buildout should have been running before to install Sphinx etc.):: make -C doc html How to release -------------- To make a release, we prefer `zest.releaser`_. To make a release, follow the standard procedure, which usually boils down to:: fullrelease `nagiosplugin` tried to obey the semantic version numbering specification published on SemVer_ but adapts it a little bit to be `PEP 386`_ compliant. .. _zest.releaser: http://pypi.python.org/pypi/zest.releaser/ .. _SemVer: http://semver.org/ .. _PEP 386: http://www.python.org/dev/peps/pep-0386/ .. vim: set ft=rst sw=3 sts=3 et: nagiosplugin-1.2.2/bootstrap.py0000644000175100017510000001306612341114271016155 0ustar ckcore00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os import shutil import sys import tempfile from optparse import OptionParser tmpeggs = tempfile.mkdtemp() usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", help="use a specific zc.buildout version") parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used.")) parser.add_option("-f", "--find-links", help=("Specify a URL to search for buildout releases")) options, args = parser.parse_args() ###################################################################### # load/install setuptools to_reload = False try: import pkg_resources import setuptools except ImportError: ez = {} try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen # XXX use a more permanent ez_setup.py URL when available. exec(urlopen('https://bitbucket.org/pypa/setuptools/raw/0.7.2/ez_setup.py' ).read(), ez) setup_args = dict(to_dir=tmpeggs, download_delay=0) ez['use_setuptools'](**setup_args) if to_reload: reload(pkg_resources) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set cmd = [sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] find_links = os.environ.get( 'bootstrap-testing-find-links', options.find_links or ('http://downloads.buildout.org/' if options.accept_buildout_test_releases else None) ) if find_links: cmd.extend(['-f', find_links]) setuptools_path = ws.find( pkg_resources.Requirement.parse('setuptools')).location requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: raise Exception( "Failed to execute command:\n%s", repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if '=' not in a]: args.append('bootstrap') # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ['-c', options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) nagiosplugin-1.2.2/version.txt0000644000175100017510000000000612341114271016002 0ustar ckcore000000000000001.2.2 nagiosplugin-1.2.2/setup.cfg0000644000175100017510000000014712341114372015405 0ustar ckcore00000000000000[upload_docs] upload-dir = doc/_build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 nagiosplugin-1.2.2/README.txt0000644000175100017510000000340512341114271015260 0ustar ckcore00000000000000The nagiosplugin library ======================== About ----- **nagiosplugin** is a Python class library which helps writing Nagios (or Icinga) compatible plugins easily in Python. It cares for much of the boilerplate code and default logic commonly found in Nagios checks, including: - Nagios 3 Plugin API compliant parameters and output formatting - Full Nagios range syntax support - Automatic threshold checking - Multiple independend measures - Custom status line to communicate the main point quickly - Long output and performance data - Timeout handling - Persistent "cookies" to retain state information between check runs - Resume log file processing at the point where the last run left - No dependencies beyond the Python standard library (except for Python 2.6). nagiosplugin runs on POSIX and Windows systems. It is compatible with Python 3.4, Python 3.3, Python 3.2, Python 2.7, and Python 2.6. Feedback and Suggestions ------------------------ nagiosplugin is primarily written and maintained by Christian Kauhaus . Feel free to contact the author for bugs, suggestions and patches. A public issue tracker can be found at http://projects.gocept.com/projects/nagiosplugin/issues. There is also a forum available at https://projects.gocept.com/projects/nagiosplugin/boards. License ------- The nagiosplugin package is released under the Zope Public License 2.1 (ZPL), a BSD-style Open Source license. Documentation ------------- Comprehensive documentation is `available online`_. The examples mentioned in the `tutorials`_ can also be found in the `nagiosplugin/examples` directory of the source distribution. .. _available online: http://pythonhosted.org/nagiosplugin/ .. _tutorials: http://pythonhosted.org/nagiosplugin/tutorial/ .. vim: set ft=rst: nagiosplugin-1.2.2/src/0000755000175100017510000000000012341114372014351 5ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin/0000755000175100017510000000000012341114372017050 5ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin/performance.py0000644000175100017510000000425512341114271021727 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Performance data (perfdata) representation. :term:`Performance data` are created during metric evaluation in a context and are written into the *perfdata* section of the plugin's output. :class:`Performance` allows to create value objects that are passed between other nagiosplugin objects. For sake of consistency, performance data should represent their values in their respective base unit, so `Performance('size', 10000, 'B')` is better than `Performance('size', 10, 'kB')`. """ import collections import itertools import re def zap_none(val): if val is None: return '' return val def quote(label): if re.match(r'^\w+$', label): return label return "'%s'" % label class Performance(collections.namedtuple('Performance', [ 'label', 'value', 'uom', 'warn', 'crit', 'min', 'max'])): def __new__(cls, label, value, uom='', warn='', crit='', min='', max=''): """Create new performance data object. :param label: short identifier, results in graph titles for example (20 chars or less recommended) :param value: measured value (usually an int, float, or bool) :param uom: unit of measure -- use base units whereever possible :param warn: warning range :param crit: critical range :param min: known value minimum (None for no minimum) :param max: known value maximum (None for no maximum) """ if "'" in label or "=" in label: raise RuntimeError('label contains illegal characters', label) return super(cls, Performance).__new__( cls, label, value, zap_none(uom), zap_none(warn), zap_none(crit), zap_none(min), zap_none(max)) def __str__(self): """String representation conforming to the plugin API. Labels containing spaces or special characters will be quoted. """ out = ['%s=%s%s' % (quote(self.label), self.value, self.uom), str(self.warn), str(self.crit), str(self.min), str(self.max)] out = reversed(list( itertools.dropwhile(lambda x: x == '', reversed(out)))) return ';'.join(out) nagiosplugin-1.2.2/src/nagiosplugin/error.py0000644000175100017510000000133112341114271020547 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Exceptions with special meanings for nagiosplugin.""" class CheckError(RuntimeError): """Abort check execution. This exception should be raised if it becomes clear for a plugin that it is not able to determine the system status. Raising this exception will make the plugin display the exception's argument and exit with an UNKNOWN (3) status. """ pass class Timeout(RuntimeError): """Maximum check run time exceeded. This exception is raised internally by nagiosplugin if the check's run time takes longer than allowed. Check execution is aborted and the plugin exits with an UNKNOWN (3) status. """ pass nagiosplugin-1.2.2/src/nagiosplugin/range.py0000644000175100017510000000661012341114271020517 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt import collections class Range(collections.namedtuple('Range', 'invert start end')): """Represents a threshold range. The general format is "[@][start:][end]". "start:" may be omitted if start==0. "~:" means that start is negative infinity. If `end` is omitted, infinity is assumed. To invert the match condition, prefix the range expression with "@". See http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT for details. """ def __new__(cls, spec=''): """Creates a Range object according to `spec`. :param spec: may be either a string, an int, or another Range object. """ spec = spec or '' if isinstance(spec, Range): return super(cls, Range).__new__( cls, spec.invert, spec.start, spec.end) elif isinstance(spec, str): start, end, invert = cls._parse(spec) elif (isinstance(spec, int) or isinstance(spec, float)): start, end, invert = 0, spec, False else: raise TypeError('cannot recognize type of Range', spec) cls._verify(start, end) return super(cls, Range).__new__(cls, invert, start, end) @classmethod def _parse(cls, spec): invert = False if spec.startswith('@'): invert = True spec = spec[1:] if ':' in spec: start, end = spec.split(':') else: start, end = '', spec if start == '~': start = float('-inf') else: start = cls._parse_atom(start, 0) end = cls._parse_atom(end, float('inf')) return start, end, invert @staticmethod def _parse_atom(atom, default): if atom is '': return default if '.' in atom: return float(atom) return int(atom) @staticmethod def _verify(start, end): """Throws ValueError if the range is not consistent.""" if start > end: raise ValueError('start %s must not be greater than end %s' % ( start, end)) def match(self, value): """Decides if `value` is inside/outside the threshold. :returns: `True` if value is inside the bounds for non-inverted Ranges. Also available as `in` operator. """ if value < self.start: return False ^ self.invert if value > self.end: return False ^ self.invert return True ^ self.invert def __contains__(self, value): return self.match(value) def _format(self, omit_zero_start=True): result = [] if self.invert: result.append('@') if self.start == float('-inf'): result.append('~:') elif not omit_zero_start or self.start != 0: result.append(('%s:' % self.start)) if self.end != float('inf'): result.append(('%s' % self.end)) return ''.join(result) def __str__(self): """Human-readable range specification.""" return self._format() def __repr__(self): """Parseable range specification.""" return 'Range(%r)' % str(self) @property def violation(self): """Human-readable description why a value does not match.""" return 'outside range {0}'.format(self._format(False)) nagiosplugin-1.2.2/src/nagiosplugin/__init__.py0000644000175100017510000000101612341114271021155 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from .check import Check from .context import Context, ScalarContext from .cookie import Cookie from .error import CheckError, Timeout from .logtail import LogTail from .metric import Metric from .multiarg import MultiArg from .performance import Performance from .range import Range from .resource import Resource from .result import Result, Results from .runtime import Runtime, guarded from .state import Ok, Warn, Critical, Unknown from .summary import Summary nagiosplugin-1.2.2/src/nagiosplugin/check.py0000644000175100017510000001326412341114271020503 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Controller logic for check execution. This module contains the :class:`Check` class which orchestrates the the various stages of check execution. Interfacing with the outside system is done via a separate :class:`Runtime` object. When a check is called (using :meth:`Check.main` or :meth:`Check.__call__`), it probes all resources and evaluates the returned metrics to results and performance data. A typical usage pattern would be to populate a check with domain objects and then delegate control to it. """ from .context import Context, Contexts from .error import CheckError from .metric import Metric from .resource import Resource from .result import Result, Results from .runtime import Runtime from .state import Ok, Unknown from .summary import Summary import logging _log = logging.getLogger(__name__) class Check(object): def __init__(self, *objects): """Creates and configures a check. Specialized *objects* representing resources, contexts, summary, or results are passed to the the :meth:`add` method. Alternatively, objects can be added later manually. """ self.resources = [] self.contexts = Contexts() self.summary = Summary() self.results = Results() self.perfdata = [] self.name = '' self.add(*objects) def add(self, *objects): """Adds domain objects to a check. :param objects: one or more objects that are descendants from :class:`~nagiosplugin.resource.Resource`, :class:`~nagiosplugin.context.Context`, :class:`~nagiosplugin.summary.Summary`, or :class:`~nagiosplugin.result.Results`. """ for obj in objects: if isinstance(obj, Resource): self.resources.append(obj) if self.name == '': self.name = self.resources[0].name elif isinstance(obj, Context): self.contexts.add(obj) elif isinstance(obj, Summary): self.summary = obj elif isinstance(obj, Results): self.results = obj else: raise TypeError('cannot add type {0} to check'.format( type(obj)), obj) return self def _evaluate_resource(self, resource): try: metric = None metrics = resource.probe() if not metrics: _log.warning('resource %s did not produce any metric', resource.name) if isinstance(metrics, Metric): # resource returned a bare metric instead of list/generator metrics = [metrics] for metric in metrics: context = self.contexts[metric.context] metric = metric.replace(contextobj=context, resource=resource) self.results.add(metric.evaluate()) self.perfdata.append(str(metric.performance() or '')) except CheckError as e: self.results.add(Result(Unknown, str(e), metric)) def __call__(self): """Actually run the check. After a check has been called, the :attr:`results` and :attr:`perfdata` attributes are populated with the outcomes. In most cases, you should not use __call__ directly but invoke :meth:`main`, which delegates check execution to the :class:`Runtime` environment. """ for resource in self.resources: self._evaluate_resource(resource) self.perfdata = sorted([p for p in self.perfdata if p]) def main(self, verbose=1, timeout=10): """All-in-one control delegation to the runtime environment. Get a :class:`~nagiosplugin.runtime.Runtime` instance and perform all phases: run the check (via :meth:`__call__`), print results and exit the program with an appropriate status code. :param verbose: output verbosity level between 0 and 3 :param timeout: abort check execution with a :exc:`Timeout` exception after so many seconds (use 0 for no timeout) """ runtime = Runtime() runtime.execute(self, verbose, timeout) @property def state(self): """Overall check state. The most significant (=worst) state seen in :attr:`results` to far. :obj:`~nagiosplugin.state.Unknown` if no results have been collected yet. Corresponds with :attr:`exitcode`. Read-only property. """ try: return self.results.most_significant_state except ValueError: return Unknown @property def summary_str(self): """Status line summary string. The first line of output that summarizes that situation as perceived by the check. The string is usually queried from a :class:`Summary` object. Read-only property. """ if not self.results: return self.summary.empty() or '' elif self.state == Ok: return self.summary.ok(self.results) or '' return self.summary.problem(self.results) or '' @property def verbose_str(self): """Additional lines of output. Long text output if check runs in verbose mode. Also queried from :class:`~nagiosplugin.summary.Summary`. Read-only property. """ return self.summary.verbose(self.results) or '' @property def exitcode(self): """Overall check exit code according to the Nagios API. Corresponds with :attr:`state`. Read-only property. """ try: return int(self.results.most_significant_state) except ValueError: return 3 nagiosplugin-1.2.2/src/nagiosplugin/result.py0000644000175100017510000001427512341114271020747 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Outcomes from evaluating metrics in contexts. The :class:`Result` class is the base class for all evaluation results. The :class:`Results` class (plural form) provides a result container with access functions and iterators. Plugin authors may create their own :class:`Result` subclass to accomodate for special needs. :class:`~.context.Context` constructors accept custom Result subclasses in the `result_cls` parameter. """ import collections import numbers import warnings class Result(collections.namedtuple('Result', 'state hint metric')): """Evaluation outcome consisting of state and explanation. A Result object is typically emitted by a :class:`~nagiosplugin.context.Context` object and represents the outcome of an evaluation. It contains a :class:`~nagiosplugin.state.ServiceState` as well as an explanation. Plugin authors may subclass Result to implement specific features. """ def __new__(cls, state, hint=None, metric=None): """Creates a Result object. :param state: state object :param hint: reason why this result arose :param metric: reference to the :class:`~nagiosplugin.metric.Metric` from which this result was derived """ return tuple.__new__(cls, (state, hint, metric)) def __str__(self): """Textual result explanation. The result explanation is taken from :attr:`metric.description` (if a metric has been passed to the constructur), followed optionally by the value of :attr:`hint`. This method's output should consist only of a text for the reason but not for the result's state. The latter is rendered independently. :returns: result explanation or empty string """ if self.metric and self.metric.description: desc = self.metric.description else: desc = None if self.hint and desc: return '{0} ({1})'.format(desc, self.hint) elif self.hint: return self.hint elif desc: return desc else: return '' @property def resource(self): """Reference to the resource used to generate this result.""" if self.metric: return self.metric.resource @property def context(self): """Reference to the metric used to generate this result.""" if self.metric: return self.metric.contextobj class ScalarResult(Result): # pragma: no cover """Special-case result for evaluation in a ScalarContext. DEPRECATED: use Result instead. """ def __new__(cls, state, hint, metric): warnings.warn('ScalarResult is deprecated, use Result instead!', DeprecationWarning) return tuple.__new__(cls, (state, hint, metric)) class Results: """Container for result sets. Basically, this class manages a set of results and provides convenient access methods by index, name, or result state. It is meant to make queries in :class:`~.summary.Summary` implementations compact and readable. The constructor accepts an arbitrary number of result objects and adds them to the container. """ def __init__(self, *results): self.results = [] self.by_state = collections.defaultdict(list) self.by_name = {} if results: self.add(*results) def add(self, *results): """Adds more results to the container. Besides passing :class:`Result` objects in the constructor, additional results may be added after creating the container. :raises ValueError: if `result` is not a :class:`Result` object """ for result in results: if not isinstance(result, Result): raise ValueError( 'trying to add non-Result to Results container', result) self.results.append(result) self.by_state[result.state].append(result) try: self.by_name[result.metric.name] = result except AttributeError: pass return self def __iter__(self): """Iterates over all results. The iterator is sorted in order of decreasing state significance (unknown > critical > warning > ok). :returns: result object iterator """ for state in reversed(sorted(self.by_state)): for result in self.by_state[state]: yield result def __len__(self): """Number of results in this container.""" return len(self.results) def __getitem__(self, item): """Access result by index or name. If *item* is an integer, the *item*\ th element in the container is returned. If *item* is a string, it is used to look up a result with the given name. :returns: :class:`Result` object :raises KeyError: if no matching result is found """ if isinstance(item, numbers.Number): return self.results[item] return self.by_name[item] def __contains__(self, name): """Tests if a result with given name is present. :returns: boolean """ return name in self.by_name @property def most_significant_state(self): """The "worst" state found in all results. :returns: :obj:`~nagiosplugin.state.ServiceState` object :raises ValueError: if no results are present """ return max(self.by_state.keys()) @property def most_significant(self): """Returns list of results with most significant state. From all results present, a subset with the "worst" state is selected. :returns: list of :class:`Result` objects or empty list if no results are present """ try: return self.by_state[self.most_significant_state] except ValueError: return [] @property def first_significant(self): """Selects one of the results with most significant state. :returns: :class:`Result` object :raises IndexError: if no results are present """ return self.most_significant[0] nagiosplugin-1.2.2/src/nagiosplugin/multiarg.py0000644000175100017510000000132012341114271021240 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt class MultiArg(object): def __init__(self, args, fill=None, splitchar=','): if isinstance(args, list): self.args = args else: self.args = args.split(splitchar) self.fill = fill def __len__(self): return self.args.__len__() def __iter__(self): return self.args.__iter__() def __getitem__(self, key): try: return self.args.__getitem__(key) except IndexError: pass if self.fill is not None: return self.fill try: return self.args.__getitem__(-1) except IndexError: return None nagiosplugin-1.2.2/src/nagiosplugin/output.py0000644000175100017510000000550412341114271020764 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt import itertools def filter_output(output, filtered): """ Filters out characters from output """ for char in filtered: output = output.replace(char, '') return output class Output(object): ILLEGAL = '|' def __init__(self, logchan, verbose=0): self.logchan = logchan self.verbose = verbose self.status = '' self.out = [] self.warnings = [] self.longperfdata = [] def add(self, check): self.status = self.format_status(check) if self.verbose == 0: perfdata = self.format_perfdata(check) if perfdata: self.status += ' ' + perfdata else: self.add_longoutput(check.verbose_str) self.longperfdata.append(self.format_perfdata(check, 79)) def format_status(self, check): if check.name: name_prefix = check.name.upper() + ' ' else: name_prefix = '' summary_str = check.summary_str.strip() return self._screen_chars('{0}{1}{2}'.format( name_prefix, str(check.state).upper(), ' - ' + summary_str if summary_str else ''), 'status line') def format_perfdata(self, check, linebreak=None): if not check.perfdata: return '' lines = ['|'] for item, i in zip(check.perfdata, itertools.count()): if linebreak and len(lines[-1]) + len(item) >= linebreak: lines.append(item) else: lines[-1] += ' ' + self._screen_chars( item, 'perfdata {0}'.format(i)) return '\n'.join(lines) def add_longoutput(self, text): if isinstance(text, list) or isinstance(text, tuple): for line in text: self.add_longoutput(line) else: self.out.append(self._screen_chars(text, 'long output')) def __str__(self): output = [elem for elem in [self.status] + self.out + [self._screen_chars(self.logchan.stream.getvalue(), 'logging output')] + self.warnings + self.longperfdata if elem] return '\n'.join(output) + '\n' def _screen_chars(self, text, where): text = text.rstrip('\n') screened = filter_output(text, self.ILLEGAL) if screened != text: self.warnings.append(self._illegal_chars_warning( where, set(text) - set(screened))) return screened def _illegal_chars_warning(self, where, removed_chars): hex_chars = ', '.join('0x{0:x}'.format(ord(c)) for c in removed_chars) return 'warning: removed illegal characters ({0}) from {1}'.format( hex_chars, where) nagiosplugin-1.2.2/src/nagiosplugin/cookie.py0000644000175100017510000001106712341114271020676 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Persistent dict to remember state between invocations. Cookies are used to remember file positions, counters and the like between plugin invocations. It is not intended for substantial amounts of data. Cookies are serialized into JSON and saved to a state file. We prefer a plain text format to allow administrators to inspect and edit its content. See :class:`~nagiosplugin.logtail.LogTail` for an application of cookies to get only new lines of a continuously growing file. Cookies are locked exclusively so that at most one process at a time has access to it. Changes to the dict are not reflected in the file until :meth:`Cookie.commit` is called. It is recommended to use Cookie as context manager to get it opened and committed automatically. """ from .compat import UserDict, TemporaryFile from .platform import flock_exclusive import codecs import json import os import tempfile class Cookie(UserDict, object): def __init__(self, statefile=None): """Creates a persistent dict to keep state. After creation, a cookie behaves like a normal dict. :param statefile: file name to save the dict's contents .. note:: If `statefile` is empty or None, the Cookie will be oblivous, i.e., it will forget its contents on garbage collection. This makes it possible to explicitely throw away state between plugin runs (for example by a command line argument). """ super(Cookie, self).__init__() self.path = statefile self.fobj = None def __enter__(self): """Allows Cookie to be used as context manager. Opens the file and passes a dict-like object into the subordinate context. See :meth:`open` for details about opening semantics. When the context is left in the regular way (no exception raised), the cookie is :meth:`commit`\ ted to disk. :yields: open cookie """ self.open() return self def __exit__(self, exc_type, exc_val, exc_tb): if not exc_type: self.commit() self.close() def open(self): """Reads/creates the state file and initializes the dict. If the state file does not exist, it is touched into existence. An exclusive lock is acquired to ensure serialized access. If :meth:`open` fails to parse file contents, it truncates the file before raising an exception. This guarantees that plugins will not fail repeatedly when their state files get damaged. :returns: Cookie object (self) :raises ValueError: if the state file is corrupted or does not deserialize into a dict """ self.fobj = self._create_fobj() flock_exclusive(self.fobj) if os.fstat(self.fobj.fileno()).st_size: try: self.data = self._load() except ValueError: self.fobj.truncate(0) raise return self def _create_fobj(self): if not self.path: return TemporaryFile('w+', encoding='ascii', prefix='oblivious_cookie_') # mode='a+' has problems with mixed R/W operation on Mac OS X try: return codecs.open(self.path, 'r+', encoding='ascii') except IOError: return codecs.open(self.path, 'w+', encoding='ascii') def _load(self): self.fobj.seek(0) data = json.load(self.fobj) if not isinstance(data, dict): raise ValueError('format error: cookie does not contain dict', self.path, data) return data def close(self): """Closes a cookie and its underlying state file. This method has no effect if the cookie is already closed. Once the cookie is closed, any operation (like :meth:`commit`) will raise an exception. """ if not self.fobj: return self.fobj.close() self.fobj = None def commit(self): """Persists the cookie's dict items in the state file. The cookies content is serialized as JSON string and saved to the state file. The buffers are flushed to ensure that the new content is saved in a durable way. """ if not self.fobj: raise IOError('cannot commit closed cookie', self.path) self.fobj.seek(0) self.fobj.truncate() json.dump(self.data, self.fobj) self.fobj.write('\n') self.fobj.flush() os.fsync(self.fobj) nagiosplugin-1.2.2/src/nagiosplugin/runtime.py0000644000175100017510000000745712341114271021120 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Functions and classes to interface with the system. This module contains the :class:`Runtime` class that handles exceptions, timeouts and logging. Plugin authors should not use Runtime directly, but decorate the plugin's main function with :func:`~.runtime.guarded`. """ from __future__ import unicode_literals, print_function from .output import Output from .error import Timeout from .platform import with_timeout import io import logging import numbers import sys import functools import traceback def guarded(func): """Runs a function in a newly created runtime environment. A guarded function behaves correctly with respect to the Nagios plugin API if it aborts with an uncaught exception or a timeout. It exits with an *unknown* exit code and prints a traceback in a format acceptable by Nagios. This function should be used as a decorator for the plugin's `main` function. """ @functools.wraps(func) def wrapper(*args, **kwds): runtime = Runtime() try: return func(*args, **kwds) except Timeout as exc: runtime._handle_exception( 'Timeout: check execution aborted after {0}'.format( exc)) except Exception: runtime._handle_exception() return wrapper class Runtime(object): instance = None check = None _verbose = 1 timeout = None logchan = None output = None stdout = sys.stdout exitcode = 70 # EX_SOFTWARE def __new__(cls): if not cls.instance: cls.instance = super(Runtime, cls).__new__(cls) return cls.instance def __init__(self): rootlogger = logging.getLogger(__name__.split('.', 1)[0]) rootlogger.setLevel(logging.DEBUG) if not self.logchan: self.logchan = logging.StreamHandler(io.StringIO()) self.logchan.setFormatter(logging.Formatter( '%(message)s (%(filename)s:%(lineno)d)')) rootlogger.addHandler(self.logchan) if not self.output: self.output = Output(self.logchan) def _handle_exception(self, statusline=None): exc_type, value = sys.exc_info()[0:2] name = self.check.name.upper() + ' ' if self.check else '' self.output.status = '{0}UNKNOWN: {1}'.format( name, statusline or traceback.format_exception_only( exc_type, value)[0].strip()) if self.verbose > 0: self.output.add_longoutput(traceback.format_exc()) print('{0}'.format(self.output), end='', file=self.stdout) self.exitcode = 3 self.sysexit() @property def verbose(self): return self._verbose @verbose.setter def verbose(self, verbose): if isinstance(verbose, numbers.Number): self._verbose = int(verbose) else: self._verbose = len(verbose or []) if self._verbose >= 3: self.logchan.setLevel(logging.DEBUG) self._verbose = 3 elif self._verbose == 2: self.logchan.setLevel(logging.INFO) else: self.logchan.setLevel(logging.WARNING) self.output.verbose = self._verbose def run(self, check): check() self.output.add(check) self.exitcode = check.exitcode def execute(self, check, verbose=None, timeout=None): self.check = check if verbose is not None: self.verbose = verbose if timeout is not None: self.timeout = int(timeout) if self.timeout: with_timeout(self.timeout, self.run, check) else: self.run(check) print('{0}'.format(self.output), end='', file=self.stdout) self.sysexit() def sysexit(self): sys.exit(self.exitcode) nagiosplugin-1.2.2/src/nagiosplugin/resource.py0000644000175100017510000000244612341114271021255 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Domain model for data :term:`acquisition`. :class:`Resource` is the base class for the plugin's :term:`domain model`. It shoul model the relevant details of reality that a plugin is supposed to check. The :class:`~.check.Check` controller calls :meth:`Resource.probe` on all passed resource objects to acquire data. Plugin authors should subclass :class:`Resource` and write whatever methods are needed to get the interesting bits of information. The most important resource subclass should be named after the plugin itself. """ class Resource(object): """Abstract base class for custom domain models. Subclasses may add arguments to the constructor to parametrize information retrieval. """ @property def name(self): return self.__class__.__name__ def probe(self): """Query system state and return metrics. This is the only method called by the check controller. It should trigger all necessary actions and create metrics. :return: list of :class:`~nagiosplugin.metric.Metric` objects, or generator that emits :class:`~nagiosplugin.metric.Metric` objects, or single :class:`~nagiosplugin.metric.Metric` object """ return [] nagiosplugin-1.2.2/src/nagiosplugin/examples/0000755000175100017510000000000012341114372020666 5ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin/examples/__init__.py0000644000175100017510000000000012341114271022763 0ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin/examples/check_users.py0000755000175100017510000000775512341114271023555 0ustar ckcore00000000000000#!python # Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Nagios plugin to check number of logged in users.""" import argparse import logging import nagiosplugin import subprocess _log = logging.getLogger('nagiosplugin') class Users(nagiosplugin.Resource): """Domain model: system logins. The `Users` class is a model of system aspects relevant for this check. It determines the logged in users and counts them. """ who_cmd = 'who' def __init__(self): self.users = [] self.unique_users = set() def list_users(self): """Return list of logged in users. The user list is determined by invoking an external command defined in `who_cmd` (default: who) and parsing its output. The command is expected to produce one line per user with the user name at the beginning. """ _log.info('querying users with "%s" command', self.who_cmd) users = [] try: p = subprocess.Popen([self.who_cmd], stdout=subprocess.PIPE, stdin=subprocess.PIPE) for line in p.communicate()[0].splitlines(): _log.debug('who output: %s', line.strip()) users.append(line.split()[0].decode()) except OSError: raise nagiosplugin.CheckError( 'cannot determine number of users ({0} failed)'.format( self.who_cmd)) return users def probe(self): """Create check metric for user counts. This method returns two metrics: `total` is total number of user logins including users with multiple logins. `unique` counts only unique user id. This means that users with multiple logins are only counted once. """ self.users = self.list_users() self.unique_users = set(self.users) return [nagiosplugin.Metric('total', len(self.users), min=0), nagiosplugin.Metric('unique', len(self.unique_users), min=0)] class UsersSummary(nagiosplugin.Summary): """Create status line and long output. For the status line, the text snippets created by the contexts work quite well, so leave `ok` and `problem` with their default implementations. For the long output (-v) we wish to display *which* users are actually logged in. Note how we use the `resource` attribute in the resuls object to grab this piece of information from the domain model object. """ def verbose(self, results): super(UsersSummary, self).verbose(results) if 'total' in results: return 'users: ' + ', '.join(results['total'].resource.users) @nagiosplugin.guarded def main(): argp = argparse.ArgumentParser() argp.add_argument('-w', '--warning', metavar='RANGE', help='warning if total user count is outside RANGE'), argp.add_argument('-c', '--critical', metavar='RANGE', help='critical is total user count is outside RANGE') argp.add_argument('-W', '--warning-unique', metavar='RANGE', help='warning if unique user count is outside RANGE') argp.add_argument('-C', '--critical-unique', metavar='RANGE', help='critical if unique user count is outside RANGE') argp.add_argument('-v', '--verbose', action='count', default=0, help='increase output verbosity (use up to 3 times)') argp.add_argument('-t', '--timeout', default=10, help='abort execution after TIMEOUT seconds') args = argp.parse_args() check = nagiosplugin.Check( Users(), nagiosplugin.ScalarContext('total', args.warning, args.critical, fmt_metric='{value} users logged in'), nagiosplugin.ScalarContext( 'unique', args.warning_unique, args.critical_unique, fmt_metric='{value} unique users logged in'), UsersSummary()) check.main(args.verbose, args.timeout) if __name__ == '__main__': main() nagiosplugin-1.2.2/src/nagiosplugin/examples/check_world.py0000755000175100017510000000045512341114271023531 0ustar ckcore00000000000000#!python """Hello world Nagios check.""" import nagiosplugin class World(nagiosplugin.Resource): def probe(self): return [nagiosplugin.Metric('world', True, context='null')] def main(): check = nagiosplugin.Check(World()) check.main() if __name__ == '__main__': main() nagiosplugin-1.2.2/src/nagiosplugin/examples/check_load.py0000755000175100017510000000540312341114271023317 0ustar ckcore00000000000000#!python # Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Nagios/Icinga plugin to check system load.""" import argparse import logging import nagiosplugin import subprocess _log = logging.getLogger('nagiosplugin') # data acquisition class Load(nagiosplugin.Resource): """Domain model: system load. Determines the system load parameters and (optionally) cpu count. The `probe` method returns the three standard load average numbers. If `percpu` is true, the load average will be normalized. This check requires Linux-style /proc files to be present. """ def __init__(self, percpu=False): self.percpu = percpu def cpus(self): _log.info('counting cpus with "nproc"') cpus = int(subprocess.check_output(['nproc'])) _log.debug('found %i cpus in total', cpus) return cpus def probe(self): _log.info('reading load from /proc/loadavg') with open('/proc/loadavg') as loadavg: load = loadavg.readline().split()[0:3] _log.debug('raw load is %s', load) cpus = self.cpus() if self.percpu else 1 load = [float(l) / cpus for l in load] for i, period in enumerate([1, 5, 15]): yield nagiosplugin.Metric('load%d' % period, load[i], min=0, context='load') # data presentation class LoadSummary(nagiosplugin.Summary): """Status line conveying load information. We specialize the `ok` method to present all three figures in one handy tagline. In case of problems, the single-load texts from the contexts work well. """ def __init__(self, percpu): self.percpu = percpu def ok(self, results): qualifier = 'per cpu ' if self.percpu else '' return 'loadavg %sis %s' % (qualifier, ', '.join( str(results[r].metric) for r in ['load1', 'load5', 'load15'])) # runtime environment and data evaluation @nagiosplugin.guarded def main(): argp = argparse.ArgumentParser(description=__doc__) argp.add_argument('-w', '--warning', metavar='RANGE', default='', help='return warning if load is outside RANGE') argp.add_argument('-c', '--critical', metavar='RANGE', default='', help='return critical if load is outside RANGE') argp.add_argument('-r', '--percpu', action='store_true', default=False) argp.add_argument('-v', '--verbose', action='count', default=0, help='increase output verbosity (use up to 3 times)') args = argp.parse_args() check = nagiosplugin.Check( Load(args.percpu), nagiosplugin.ScalarContext('load', args.warning, args.critical), LoadSummary(args.percpu)) check.main(verbose=args.verbose) if __name__ == '__main__': main() nagiosplugin-1.2.2/src/nagiosplugin/examples/check_haproxy_log.py0000755000175100017510000001050012341114271024725 0ustar ckcore00000000000000#!python # Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """haproxy log check for request time and error rate. This check shows an advanced programming technique: we allow the user to define the thresholds dynamically. You can specify a list of conditions like: * the N1th percentile of t_tot must match range R1 * the N2th percentile of t_tot must match range R2 Implementation-wise, the command line parameter "percentiles" is used to compute both metric and context names. The default is to check for the 50th and 95th percentile. The `MultiArg` class is used to specify sets of thresholds. It has the nice property to fill up missing values so the user is free in how many thresholds he specifies. """ import argparse import itertools import nagiosplugin import numpy import re class HAProxyLog(nagiosplugin.Resource): """haproxy.log parser. Goes through a haproxy log file and extracts total request time (t_tot) and error status for each request. The error status is used to compute the error rate. """ r_logline = re.compile( r'haproxy.*: [0-9.:]+ \[\S+\] .* \d+/\d+/\d+/\d+/(\d+) (\d\d\d) ') def __init__(self, logfile, statefile, percentiles): self.logfile = logfile self.statefile = statefile self.percentiles = percentiles def parse_log(self): """Yields ttot and error status for each log line.""" cookie = nagiosplugin.Cookie(self.statefile) with nagiosplugin.LogTail(self.logfile, cookie) as lf: for line in lf: match = self.r_logline.search(line.decode()) if not match: continue ttot, stat = match.groups() err = not (stat.startswith('2') or stat.startswith('3')) yield int(ttot), err def probe(self): """Computes error rate and t_tot percentiles.""" d = numpy.fromiter(self.parse_log(), dtype=[ ('ttot', numpy.int32), ('err', numpy.uint16)]) requests = len(d['err']) metrics = [] if requests: for pct in self.percentiles: metrics.append(nagiosplugin.Metric( 'ttot%s' % pct, numpy.percentile( d['ttot'], int(pct)) / 1000.0, 's', 0)) error_rate = (100 * numpy.sum(d['err'] / requests) if requests else 0) metrics += [nagiosplugin.Metric('error_rate', error_rate, '%', 0, 100), nagiosplugin.Metric('request_total', requests, min=0, context='default')] return metrics def parse_args(): argp = argparse.ArgumentParser() argp.add_argument('logfile') argp.add_argument('--ew', '--error-warning', metavar='RANGE', default='') argp.add_argument('--ec', '--error-critical', metavar='RANGE', default='') argp.add_argument('--tw', '--ttot-warning', metavar='RANGE[,RANGE,...]', type=nagiosplugin.MultiArg, default='') argp.add_argument('--tc', '--ttot-critical', metavar='RANGE[,RANGE,...]', type=nagiosplugin.MultiArg, default='') argp.add_argument('-p', '--percentiles', metavar='N,N,...', default='50,95', help='check Nth percentiles of ' 'total time (default: %(default)s)') argp.add_argument('-v', '--verbose', action='count', default=0, help='increase output verbosity (use up to 3 times)') argp.add_argument('-t', '--timeout', default=30, help='abort execution after TIMEOUT seconds') argp.add_argument('-s', '--state-file', default='check_haproxy_log.state', help='cookie file to save last log file position ' '(default: "%(default)s")') return argp.parse_args() @nagiosplugin.guarded def main(): args = parse_args() percentiles = args.percentiles.split(',') check = nagiosplugin.Check( HAProxyLog(args.logfile, args.state_file, percentiles), nagiosplugin.ScalarContext('error_rate', args.ew, args.ec)) for pct, i in zip(percentiles, itertools.count()): check.add(nagiosplugin.ScalarContext( 'ttot%s' % pct, args.tw[i], args.tc[i], 'total time (%s.pct) is {valueunit}' % pct)) check.main(args.verbose, args.timeout) if __name__ == '__main__': main() nagiosplugin-1.2.2/src/nagiosplugin/compat.py0000644000175100017510000000152512341114271020706 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Python 2/3 compatibility wrappers. This module contains imports and functions that help mask Python 2/3 compatibility issues. """ import tempfile # UserDict try: from collections import UserDict except ImportError: from UserDict import UserDict # StringIO try: from io import StringIO except ImportError: from StringIO import StringIO # Python 2: TemporaryFile does not support the `encoding` parameter def TemporaryFile(mode='w+b', encoding=None, suffix='', prefix='tmp', dir=None): try: return tempfile.TemporaryFile( mode=mode, encoding=encoding, suffix=suffix, prefix=prefix, dir=dir) except TypeError: return tempfile.TemporaryFile( mode=mode, suffix=suffix, prefix=prefix, dir=dir) nagiosplugin-1.2.2/src/nagiosplugin/metric.py0000644000175100017510000000756712341114271020722 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Structured representation for data points. This module contains the :class:`Metric` class whose instances are passed as value objects between most of nagiosplugin's core classes. Typically, :class:`~.resource.Resource` objects emit a list of metrics as result of their :meth:`~.resource.Resource.probe` methods. """ import numbers import collections class Metric(collections.namedtuple( 'Metric', 'name value uom min max context contextobj resource')): """Single measured value. The value should be expressed in terms of base units, so Metric('swap', 10240, 'B') is better than Metric('swap', 10, 'kiB'). """ def __new__(cls, name, value, uom=None, min=None, max=None, context=None, contextobj=None, resource=None): """Creates new Metric instance. :param name: short internal identifier for the value -- appears also in the performance data :param value: data point, usually has a boolen or numeric type, but other types are also possible :param uom: :term:`unit of measure`, preferrably as ISO abbreviation like "s" :param min: minimum value or None if there is no known minimum :param max: maximum value or None if there is no known maximum :param context: name of the associated context (defaults to the metric's name if left out) :param contextobj: reference to the associated context object (set automatically by :class:`~nagiosplugin.check.Check`) :param resource: reference to the originating :class:`~nagiosplugin.resource.Resource` (set automatically by :class:`~nagiosplugin.check.Check`) """ return tuple. __new__(cls, ( name, value, uom, min, max, context or name, contextobj, resource)) def __str__(self): """Same as :attr:`valueunit`.""" return self.valueunit def replace(self, **attr): """Creates new instance with updated attributes.""" return self._replace(**attr) @property def description(self): """Human-readable, detailed string representation. Delegates to the :class:`~.context.Context` to format the value. :returns: :meth:`~.context.Context.describe` output or :attr:`valueunit` if no context has been associated yet """ if self.contextobj: return self.contextobj.describe(self) return str(self) @property def valueunit(self): """Compact string representation. This is just the value and the unit. If the value is a real number, express the value with a limited number of digits to improve readability. """ return '%s%s' % (self._human_readable_value, self.uom or '') @property def _human_readable_value(self): """Limit number of digits for floats.""" if (isinstance(self.value, numbers.Real) and not isinstance(self.value, numbers.Integral)): return '%.4g' % self.value return str(self.value) def evaluate(self): """Evaluates this instance according to the context. :return: :class:`~nagiosplugin.result.Result` object :raise RuntimeError: if no context has been associated yet """ if not self.contextobj: raise RuntimeError('no context set for metric', self.name) return self.contextobj.evaluate(self, self.resource) def performance(self): """Generates performance data according to the context. :return: :class:`~nagiosplugin.performance.Performance` object :raise RuntimeError: if no context has been associated yet """ if not self.contextobj: raise RuntimeError('no context set for metric', self.name) return self.contextobj.performance(self, self.resource) nagiosplugin-1.2.2/src/nagiosplugin/summary.py0000644000175100017510000000466212341114271021125 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Create status line from results. This module contains the :class:`Summary` class which serves as base class to get a status line from the check's :class:`~.result.Results`. A Summary object is used by :class:`~.check.Check` to obtain a suitable data :term:`presentation` depending on the check's overall state. Plugin authors may either stick to the default implementation or subclass it to adapt it to the check's domain. The status line is probably the most important piece of text returned from a check: It must lead directly to the problem in the most concise way. So while the default implementation is quite usable, plugin authors should consider subclassing to provide a specific implementation that gets the output to the point. """ from .state import Ok class Summary(object): """Creates a summary formtter object. This base class takes no parameters in its constructor, but subclasses may provide more elaborate constructors that accept parameters to influence output creation. """ def ok(self, results): """Formats status line when overall state is ok. The default implementation returns a string representation of the first result. :param results: :class:`~nagiosplugin.result.Results` container :returns: status line """ return str(results[0]) def problem(self, results): """Formats status line when overall state is not ok. The default implementation returns a string representation of te first significant result, i.e. the result with the "worst" state. :param results: :class:`~.result.Results` container :returns: status line """ return str(results.first_significant) def verbose(self, results): """Provides extra lines if verbose plugin execution is requested. The default implementation returns a list of all resources that are in a non-ok state. :param results: :class:`~.result.Results` container :returns: list of strings """ msgs = [] for result in results: if result.state == Ok: continue msgs.append('{0}: {1}'.format(result.state, result)) return msgs def empty(self): """Formats status line when the result set is empty. :returns: status line """ return 'no check results' nagiosplugin-1.2.2/src/nagiosplugin/state.py0000644000175100017510000000337212341114271020545 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Classes to represent check outcomes. This module defines :class:`ServiceState` which is the abstract base class for check outcomes. class for check outcomes. class for check outcomes. The four states defined by the :term:`Nagios plugin API` are represented as singleton subclasses. Note that the *warning* state is defined by the :class:`Warn` class. The class has not been named `Warning` to avoid being confused with the built-in Python exception of the same name. """ import collections import functools def worst(states): """Reduce list of *states* to the most significant state.""" return functools.reduce(lambda a, b: a if a > b else b, states, Ok) class ServiceState(collections.namedtuple('ServiceState', 'code text')): """Abstract base class for all states. Each state has two constant attributes: :attr:`text` is the short text representation which is printed for example at the beginning of the summary line. :attr:`code` is the corresponding exit code. """ def __str__(self): """Plugin-API compliant text representation.""" return self.text def __int__(self): """Plugin API compliant exit code.""" return self.code class Ok(ServiceState): def __new__(cls): return super(cls, Ok).__new__(cls, 0, 'ok') Ok = Ok() class Warn(ServiceState): def __new__(cls): return super(cls, Warn).__new__(cls, 1, 'warning') Warn = Warn() class Critical(ServiceState): def __new__(cls): return super(cls, Critical).__new__(cls, 2, 'critical') Critical = Critical() class Unknown(ServiceState): def __new__(cls): return super(cls, Unknown).__new__(cls, 3, 'unknown') Unknown = Unknown() nagiosplugin-1.2.2/src/nagiosplugin/tests/0000755000175100017510000000000012341114372020212 5ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin/tests/test_result.py0000644000175100017510000000746712341114271023155 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.result import Result, Results from nagiosplugin.state import Ok, Warn, Critical, Unknown import nagiosplugin try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class ResultTest(unittest.TestCase): def test_resorce_should_be_none_for_resourceless_metric(self): self.assertIsNone(Result(Ok).resource) def test_metric_resorce(self): res = object() m = nagiosplugin.Metric('foo', 1, resource=res) self.assertEqual(Result(Ok, metric=m).resource, res) def test_context_should_be_none_for_contextless_metric(self): self.assertIsNone(Result(Ok).context) def test_metric_context(self): ctx = object() m = nagiosplugin.Metric('foo', 1, contextobj=ctx) self.assertEqual(Result(Ok, metric=m).context, ctx) def test_str_metric_with_hint(self): self.assertEqual('2 (unexpected)', str(Result(Warn, 'unexpected', nagiosplugin.Metric('foo', 2)))) def test_str_metric_only(self): self.assertEqual( '3', str(Result(Warn, metric=nagiosplugin.Metric('foo', 3)))) def test_str_hint_only(self): self.assertEqual('how come?', str(Result(Warn, 'how come?'))) def test_str_empty(self): self.assertEqual('', str(Result(Warn))) class ResultsTest(unittest.TestCase): def test_lookup_by_metric_name(self): r = Results() result = Result(Ok, '', nagiosplugin.Metric('met1', 0)) r.add(result, Result(Ok, 'other')) self.assertEqual(r['met1'], result) def test_lookup_by_index(self): r = Results() result = Result(Ok, '', nagiosplugin.Metric('met1', 0)) r.add(Result(Ok, 'other'), result) self.assertEqual(r[1], result) def test_len(self): r = Results() r.add(Result(Ok), Result(Ok), Result(Ok)) self.assertEqual(3, len(r)) def test_iterate_in_order_of_descending_states(self): r = Results() r.add(Result(Warn), Result(Ok), Result(Critical), Result(Warn)) self.assertEqual([Critical, Warn, Warn, Ok], [result.state for result in r]) def test_most_significant_state_shoud_raise_valueerror_if_empty(self): with self.assertRaises(ValueError): Results().most_significant_state def test_most_significant_state(self): r = Results() r.add(Result(Ok)) self.assertEqual(Ok, r.most_significant_state) r.add(Result(Critical)) self.assertEqual(Critical, r.most_significant_state) r.add(Result(Warn)) self.assertEqual(Critical, r.most_significant_state) def test_most_significant_should_return_empty_set_if_empty(self): self.assertEqual([], Results().most_significant) def test_most_signigicant(self): r = Results() r.add(Result(Ok), Result(Warn), Result(Ok), Result(Warn)) self.assertEqual([Warn, Warn], [result.state for result in r.most_significant]) def test_first_significant(self): r = Results() r.add(Result(Critical), Result(Unknown, 'r1'), Result(Unknown, 'r2'), Result(Ok)) self.assertEqual(Result(Unknown, 'r1'), r.first_significant) def test_contains(self): results = Results() r1 = Result(Unknown, 'r1', nagiosplugin.Metric('m1', 1)) results.add(r1) self.assertTrue('m1' in results) self.assertFalse('m2' in results) def test_add_in_init(self): results = Results(Result(Unknown, 'r1'), Result(Unknown, 'r2')) self.assertEqual(2, len(results)) def test_add_should_fail_unless_result_passed(self): with self.assertRaises(ValueError): Results(True) nagiosplugin-1.2.2/src/nagiosplugin/tests/__init__.py0000644000175100017510000000002112341114271022312 0ustar ckcore00000000000000# python package nagiosplugin-1.2.2/src/nagiosplugin/tests/test_context.py0000644000175100017510000000443312341114271023311 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.context import Context, ScalarContext, Contexts import nagiosplugin try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class ContextTest(unittest.TestCase): def test_description_should_be_empty_by_default(self): c = Context('ctx') self.assertIsNone(c.describe(nagiosplugin.Metric('m', 0))) def test_fmt_template(self): m1 = nagiosplugin.Metric('foo', 1, 's', min=0) c = Context('describe_template', '{name} is {valueunit} (min {min})') self.assertEqual('foo is 1s (min 0)', c.describe(m1)) def test_fmt_callable(self): def format_metric(metric, context): return '{0} formatted by {1}'.format(metric.name, context.name) m1 = nagiosplugin.Metric('foo', 1, 's', min=0) c = Context('describe_callable', fmt_metric=format_metric) self.assertEqual('foo formatted by describe_callable', c.describe(m1)) class ScalarContextTest(unittest.TestCase): def test_state_ranges_values(self): test_cases = [ (1, nagiosplugin.Ok, None), (3, nagiosplugin.Warn, 'outside range 0:2'), (5, nagiosplugin.Critical, 'outside range 0:4'), ] c = ScalarContext('ctx', '0:2', '0:4') for value, exp_state, exp_reason in test_cases: m = nagiosplugin.Metric('time', value) self.assertEqual(nagiosplugin.Result(exp_state, exp_reason, m), c.evaluate(m, None)) def test_accept_none_warning_critical(self): c = ScalarContext('ctx') self.assertEqual(nagiosplugin.Range(), c.warning) self.assertEqual(nagiosplugin.Range(), c.critical) class ContextsTest(unittest.TestCase): def test_keyerror(self): ctx = Contexts() ctx.add(Context('foo')) with self.assertRaises(KeyError): ctx['bar'] def test_contains(self): ctx = Contexts() ctx.add(Context('foo')) self.assertTrue('foo' in ctx) self.assertFalse('bar' in ctx) def test_iter(self): ctx = Contexts() ctx.add(Context('foo')) # includes default contexts self.assertEqual(['default', 'foo', 'null'], sorted(list(ctx))) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_runtime.py0000644000175100017510000000567312341114271023317 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.runtime import Runtime, guarded from nagiosplugin.compat import StringIO import nagiosplugin import logging try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest def make_check(): class Check(object): summary_str = 'summary' verbose_str = 'long output' name = 'check' state = nagiosplugin.Ok exitcode = 0 perfdata = None def __call__(self): pass return Check() class RuntimeTestBase(unittest.TestCase): def setUp(self): Runtime.instance = None self.r = Runtime() self.r.sysexit = lambda: None self.r.stdout = StringIO() class RuntimeTest(RuntimeTestBase): def test_runtime_is_singleton(self): self.assertEqual(self.r, Runtime()) def test_run_sets_exitcode(self): self.r.run(make_check()) self.assertEqual(0, self.r.exitcode) def test_verbose(self): testcases = [(None, logging.WARNING, 0), (1, logging.WARNING, 1), ('vv', logging.INFO, 2), (3, logging.DEBUG, 3), ('vvvv', logging.DEBUG, 3)] for argument, exp_level, exp_verbose in testcases: self.r.verbose = argument self.assertEqual(exp_level, self.r.logchan.level) self.assertEqual(exp_verbose, self.r.verbose) def test_execute_uses_defaults(self): self.r.execute(make_check()) self.assertEqual(1, self.r.verbose) self.assertEqual(None, self.r.timeout) def test_execute_sets_verbose_and_timeout(self): self.r.execute(make_check(), 2, 10) self.assertEqual(2, self.r.verbose) self.assertEqual(10, self.r.timeout) class RuntimeExceptionTest(RuntimeTestBase): def setUp(self): super(RuntimeExceptionTest, self).setUp() def run_main_with_exception(self, exc): @guarded def main(): raise exc main() def test_handle_exception_set_exitcode_and_formats_output(self): self.run_main_with_exception(RuntimeError('problem')) self.assertEqual(3, self.r.exitcode) self.assertIn('UNKNOWN: RuntimeError: problem', self.r.stdout.getvalue()) def test_handle_exception_prints_no_traceback(self): self.r.verbose = 0 self.run_main_with_exception(RuntimeError('problem')) self.assertNotIn('Traceback', self.r.stdout.getvalue()) def test_handle_exception_verbose(self): self.r.verbose = 1 self.run_main_with_exception(RuntimeError('problem')) self.assertIn('Traceback', self.r.stdout.getvalue()) def test_handle_timeout_exception(self): self.run_main_with_exception(nagiosplugin.Timeout('1s')) self.assertIn('UNKNOWN: Timeout: check execution aborted after 1s', self.r.stdout.getvalue()) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_cookie.py0000644000175100017510000000646412341114271023104 0ustar ckcore00000000000000# vim: set fileencoding=utf-8 : # Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from __future__ import unicode_literals, print_function from nagiosplugin.cookie import Cookie import codecs import os import tempfile try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class CookieTest(unittest.TestCase): def setUp(self): self.tf = tempfile.NamedTemporaryFile(prefix='cookietest_') def tearDown(self): self.tf.close() def test_get_default_value_if_empty(self): with Cookie(self.tf.name) as c: self.assertEqual('default value', c.get('key', 'default value')) def test_get_file_contents(self): with open(self.tf.name, 'w') as f: f.write('{"hello": "world"}\n') with Cookie(self.tf.name) as c: self.assertEqual('world', c['hello']) def test_get_without_open_should_raise_keyerror(self): c = Cookie(self.tf.name) with self.assertRaises(KeyError): c['foo'] def test_exit_should_write_content(self): os.unlink(self.tf.name) with Cookie(self.tf.name) as c: c['hello'] = 'wörld' with open(self.tf.name) as f: self.assertEqual('{"hello": "w\\u00f6rld"}\n', f.read()) def test_should_not_commit_on_exception(self): try: with Cookie(self.tf.name) as c: c['foo'] = True raise RuntimeError() except RuntimeError: pass with open(self.tf.name) as f: self.assertEqual('', f.read()) def test_double_close_raises_no_exception(self): c = Cookie(self.tf.name) c.open() c.close() c.close() self.assertTrue(True) def test_close_within_with_block_fails(self): with self.assertRaises(IOError): with Cookie(self.tf.name) as c: c.close() def test_multiple_commit(self): c = Cookie(self.tf.name) c.open() c['key'] = 1 c.commit() with open(self.tf.name) as f: self.assertIn('"key": 1', f.read()) c['key'] = 2 c.commit() with open(self.tf.name) as f: self.assertIn('"key": 2', f.read()) c.close() def test_corrupted_cookie_should_raise(self): with open(self.tf.name, 'w') as f: f.write('{{{') c = Cookie(self.tf.name) with self.assertRaises(ValueError): c.open() c.close() def test_wrong_cookie_format(self): with open(self.tf.name, 'w') as f: f.write('[1, 2, 3]\n') c = Cookie(self.tf.name) with self.assertRaises(ValueError): c.open() c.close() def test_cookie_format_exception_truncates_file(self): with codecs.open(self.tf.name, 'w', 'utf-8') as f: f.write('{slö@@ä') c = Cookie(self.tf.name) try: c.open() except ValueError: pass finally: c.close() self.assertEqual(0, os.stat(self.tf.name).st_size) def test_oblivious_cookie(self): c = Cookie('') # the following method calls are not expected to perfom any function c.open() c['key'] = 1 c.commit() c.close() self.assertEqual(c['key'], 1) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_output.py0000644000175100017510000000670412341114271023170 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from __future__ import unicode_literals, print_function from nagiosplugin.output import Output import nagiosplugin import io import logging import unittest class FakeCheck: name = 'Fake' state = nagiosplugin.Ok summary_str = 'check summary' verbose_str = 'hello world\n' perfdata = ['foo=1m;2;3', 'bar=1s;2;3'] class OutputTest(unittest.TestCase): def setUp(self): self.logio = io.StringIO() self.logchan = logging.StreamHandler(self.logio) def test_add_longoutput_string(self): o = Output(self.logchan) o.add_longoutput('first line\nsecond line\n') self.assertEqual(str(o), 'first line\nsecond line\n') def test_add_longoutput_list(self): o = Output(self.logchan) o.add_longoutput(['first line', 'second line']) self.assertEqual(str(o), 'first line\nsecond line\n') def test_add_longoutput_tuple(self): o = Output(self.logchan) o.add_longoutput(('first line', 'second line')) self.assertEqual(str(o), 'first line\nsecond line\n') def test_str_should_append_log(self): o = Output(self.logchan) print('debug log output', file=self.logio) self.assertEqual('debug log output\n', str(o)) def test_empty_summary_perfdata(self): o = Output(self.logchan) check = FakeCheck() check.summary_str = '' check.perfdata = [] o.add(check) self.assertEqual('FAKE OK\n', str(o)) def test_empty_name(self): o = Output(self.logchan) check = FakeCheck() check.name = None check.perfdata = [] o.add(check) self.assertEqual('OK - check summary\n', str(o)) def test_add_check_singleline(self): o = Output(self.logchan) o.add(FakeCheck()) self.assertEqual("""\ FAKE OK - check summary | foo=1m;2;3 bar=1s;2;3 """, str(o)) def test_add_check_multiline(self): o = Output(self.logchan, verbose=1) o.add(FakeCheck()) self.assertEqual("""\ FAKE OK - check summary hello world | foo=1m;2;3 bar=1s;2;3 """, str(o)) def test_remove_illegal_chars(self): check = FakeCheck() check.summary_str = 'PIPE | STATUS' check.verbose_str = 'long pipe | output' check.perfdata = [] print('debug pipe | x', file=self.logio) o = Output(self.logchan, verbose=1) o.add(check) self.assertEqual("""\ FAKE OK - PIPE STATUS long pipe output debug pipe x warning: removed illegal characters (0x7c) from status line warning: removed illegal characters (0x7c) from long output warning: removed illegal characters (0x7c) from logging output """, str(o)) def test_perfdata_linebreak(self): check = FakeCheck() check.verbose_str = '' check.perfdata = ['duration=340.4ms;500;1000;0'] * 5 o = Output(self.logchan, verbose=1) o.add(check) self.assertEqual("""\ FAKE OK - check summary | duration=340.4ms;500;1000;0 duration=340.4ms;500;1000;0 duration=340.4ms;500;1000;0 duration=340.4ms;500;1000;0 duration=340.4ms;500;1000;0 """, str(o)) def test_log_output_precedes_perfdata(self): check = FakeCheck() check.perfdata = ['foo=1'] print('debug log output', file=self.logio) o = Output(self.logchan, verbose=1) o.add(check) self.assertEqual("""\ FAKE OK - check summary hello world debug log output | foo=1 """, str(o)) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_state.py0000644000175100017510000000116412341114271022743 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt import unittest from nagiosplugin.state import Ok, Warn, Unknown, Critical, worst class StateTest(unittest.TestCase): def test_str(self): self.assertEqual('ok', str(Ok)) def test_int(self): self.assertEqual(3, int(Unknown)) def test_cmp_less(self): self.assertTrue(Warn < Critical) def test_cmp_greater(self): self.assertTrue(Warn > Ok) def test_worst(self): self.assertEqual(Critical, worst([Ok, Critical, Warn])) def test_worst_of_emptyset_is_ok(self): self.assertEqual(Ok, worst([])) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_logtail.py0000644000175100017510000000324412341114271023257 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.logtail import LogTail import nagiosplugin import tempfile try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class LogTailTest(unittest.TestCase): def setUp(self): self.lf = tempfile.NamedTemporaryFile(prefix='log.') self.cf = tempfile.NamedTemporaryFile(prefix='cookie.') self.cookie = nagiosplugin.Cookie(self.cf.name) def tearDown(self): self.cf.close() self.lf.close() def test_empty_file(self): with LogTail(self.lf.name, self.cookie) as tail: self.assertEqual([], list(tail)) def test_successive_reads(self): self.lf.write(b'first line\n') self.lf.flush() with LogTail(self.lf.name, self.cookie) as tail: self.assertEqual(b'first line\n', next(tail)) self.lf.write(b'second line\n') self.lf.flush() with LogTail(self.lf.name, self.cookie) as tail: self.assertEqual(b'second line\n', next(tail)) # no write with LogTail(self.lf.name, self.cookie) as tail: with self.assertRaises(StopIteration): next(tail) def test_offer_same_content_again_after_exception(self): self.lf.write(b'first line\n') self.lf.flush() try: with LogTail(self.lf.name, self.cookie) as tail: self.assertEqual([b'first line\n'], list(tail)) raise RuntimeError() except RuntimeError: pass with LogTail(self.lf.name, self.cookie) as tail: self.assertEqual([b'first line\n'], list(tail)) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_performance.py0000644000175100017510000000131712341114271024124 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.performance import Performance try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class PerformanceTest(unittest.TestCase): def test_normal_label(self): self.assertEqual('d=10', str(Performance('d', 10))) def test_label_quoted(self): self.assertEqual("'d d'=10", str(Performance('d d', 10))) def test_label_must_not_contain_quotes(self): with self.assertRaises(RuntimeError): str(Performance("d'", 10)) def test_label_must_not_contain_equals(self): with self.assertRaises(RuntimeError): str(Performance("d=", 10)) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_examples.py0000644000175100017510000000274712341114271023451 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt import pkg_resources import re import subprocess import sys import os.path as p try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class ExamplesTest(unittest.TestCase): base = p.normpath(p.join(p.dirname(p.abspath(__file__)), '..', '..')) def _run_example(self, program, regexp): proc = subprocess.Popen([ sys.executable, pkg_resources.resource_filename( 'nagiosplugin.examples', program), '-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'PYTHONPATH': ':'.join(sys.path)}) out, err = proc.communicate() self.assertEqual(err.decode(), '') self.assertTrue(re.match(regexp, out.decode()) is not None, '"{0}" does not match "{1}"'.format( out.decode(), regexp)) self.assertEqual(0, proc.returncode) def test_check_load(self): if not sys.platform.startswith('linux'): # pragma: no cover self.skipTest('requires Linux') self._run_example('check_load.py', """\ LOAD OK - loadavg is [0-9., ]+ | load15=[0-9.]+;;;0 load1=[0-9.]+;;;0 load5=[0-9.]+;;;0 """) def test_check_users(self): self._run_example('check_users.py', """\ USERS OK - \\d+ users logged in users: .* | total=\\d+;;;0 unique=\\d+;;;0 """) def test_check_world(self): self._run_example('check_world.py', '^WORLD OK$') nagiosplugin-1.2.2/src/nagiosplugin/tests/test_range.py0000644000175100017510000000642112341114271022720 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.range import Range try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class RangeParseTest(unittest.TestCase): def test_empty_range_is_zero_to_infinity(self): r = Range('') self.assertFalse(r.invert) self.assertEqual(r.start, 0) self.assertEqual(r.end, float('inf')) def test_null_range(self): self.assertEqual(Range(), Range('')) self.assertEqual(Range(), Range(None)) def test_explicit_start_end(self): r = Range('0.5:4') self.assertFalse(r.invert) self.assertEqual(r.start, 0.5) self.assertEqual(r.end, 4) def test_fail_if_start_gt_end(self): self.assertRaises(ValueError, Range, '4:3') def test_int(self): r = Range(42) self.assertFalse(r.invert) self.assertEqual(r.start, 0) self.assertEqual(r.end, 42) def test_float(self): r = Range(0.12) self.assertFalse(r.invert) self.assertEqual(r.start, 0) self.assertEqual(r.end, 0.12) def test_spec_with_unknown_type_should_raise(self): with self.assertRaises(TypeError): Range([1, 2]) def test_omit_start(self): r = Range('5') self.assertFalse(r.invert) self.assertEqual(r.start, 0) self.assertEqual(r.end, 5) def test_omit_end(self): r = Range('7.7:') self.assertFalse(r.invert) self.assertEqual(r.start, 7.7) self.assertEqual(r.end, float('inf')) def test_start_is_neg_infinity(self): r = Range('~:5.5') self.assertFalse(r.invert) self.assertEqual(r.start, float('-inf')) self.assertEqual(r.end, 5.5) def test_invert(self): r = Range('@-9.1:2.6') self.assertTrue(r.invert) self.assertEqual(r.start, -9.1) self.assertEqual(r.end, 2.6) def test_range_from_range(self): orig = Range('@3:5') copy = Range(orig) self.assertEqual(copy, orig) def test_contains(self): r = Range('1.7:2.5') self.assertFalse(1.6 in r) self.assertTrue(1.7 in r) self.assertTrue(2.5 in r) self.assertFalse(2.6 in r) def test_repr(self): self.assertEqual("Range('2:3')", repr(Range('2:3'))) class RangeStrTest(unittest.TestCase): def test_empty(self): self.assertEqual('', str(Range())) def test_explicit_start_stop(self): self.assertEqual('1.5:5', str(Range('1.5:5'))) def test_omit_start(self): self.assertEqual('6.7', str('6.7')) def test_omit_end(self): self.assertEqual('-6.5:', str('-6.5:')) def test_neg_infinity(self): self.assertEqual('~:-3.0', str(Range('~:-3.0'))) def test_invert(self): self.assertEqual('@3:7', str(Range('@3:7'))) def test_large_number(self): self.assertEqual('2800000000', str(Range('2800000000'))) def test_violation_outside(self): self.assertEqual('outside range 2:3', Range('2:3').violation) def test_violation_greater_than(self): self.assertEqual('outside range 0:4', Range('4').violation) def test_violation_empty_range(self): self.assertEqual('outside range 0:', Range('').violation) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_check.py0000644000175100017510000001133012341114271022674 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.check import Check import nagiosplugin try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class FakeSummary(nagiosplugin.Summary): def ok(self, results): return "I'm feelin' good" def problem(self, results): return 'Houston, we have a problem' class R1_MetricDefaultContext(nagiosplugin.Resource): def probe(self): return [nagiosplugin.Metric('foo', 1, context='default')] class CheckTest(unittest.TestCase): def test_add_resource(self): c = Check() r1 = nagiosplugin.Resource() r2 = nagiosplugin.Resource() c.add(r1, r2) self.assertEqual([r1, r2], c.resources) def test_add_context(self): ctx = nagiosplugin.ScalarContext('ctx1', '', '') c = Check(ctx) self.assertIn(ctx.name, c.contexts) def test_add_summary(self): s = nagiosplugin.Summary() c = Check(s) self.assertEqual(s, c.summary) def test_add_results(self): r = nagiosplugin.Results() c = Check(r) self.assertEqual(r, c.results) def test_add_unknown_type_should_raise_typeerror(self): with self.assertRaises(TypeError): Check(object()) def test_check_should_accept_resource_returning_bare_metric(self): class R_ReturnsBareMetric(nagiosplugin.Resource): def probe(self): return nagiosplugin.Metric('foo', 0, context='default') res = R_ReturnsBareMetric() c = Check(res) c() self.assertIn(res, c.resources) def test_evaluate_resource_populates_results_perfdata(self): c = Check() c._evaluate_resource(R1_MetricDefaultContext()) self.assertEqual(1, len(c.results)) self.assertEqual('foo', c.results[0].metric.name) self.assertEqual(['foo=1'], c.perfdata) def test_evaluate_resource_looks_up_context(self): class R2_MetricCustomContext(nagiosplugin.Resource): def probe(self): return [nagiosplugin.Metric('bar', 2)] ctx = nagiosplugin.ScalarContext('bar', '1', '1') c = Check(ctx) c._evaluate_resource(R2_MetricCustomContext()) self.assertEqual(c.results[0].metric.contextobj, ctx) def test_evaluate_resource_catches_checkerror(self): class R3_Faulty(nagiosplugin.Resource): def probe(self): raise nagiosplugin.CheckError('problem') c = Check() c._evaluate_resource(R3_Faulty()) result = c.results[0] self.assertEqual(nagiosplugin.Unknown, result.state) self.assertEqual('problem', result.hint) def test_call_evaluates_resources_and_compacts_perfdata(self): class R4_NoPerfdata(nagiosplugin.Resource): def probe(self): return [nagiosplugin.Metric('m4', 4, context='null')] c = Check(R1_MetricDefaultContext(), R4_NoPerfdata()) c() self.assertEqual(['foo', 'm4'], [res.metric.name for res in c.results]) self.assertEqual(['foo=1'], c.perfdata) def test_first_resource_sets_name(self): class MyResource(nagiosplugin.Resource): pass c = Check() self.assertEqual('', c.name) c.add(MyResource()) self.assertEqual('MyResource', c.name) def test_set_explicit_name(self): c = Check() c.name = 'mycheck' c.add(nagiosplugin.Resource()) self.assertEqual('mycheck', c.name) def test_check_without_results_is_unkown(self): self.assertEqual(nagiosplugin.Unknown, Check().state) def test_default_summary_if_no_results(self): c = Check() self.assertEqual('no check results', c.summary_str) def test_state_if_resource_has_no_metrics(self): c = Check(nagiosplugin.Resource()) c() self.assertEqual(nagiosplugin.Unknown, c.state) self.assertEqual(3, c.exitcode) def test_summary_str_calls_ok_if_state_ok(self): c = Check(FakeSummary()) c._evaluate_resource(R1_MetricDefaultContext()) self.assertEqual("I'm feelin' good", c.summary_str) def test_summary_str_calls_problem_if_state_not_ok(self): c = Check(FakeSummary()) c.results.add(nagiosplugin.Result(nagiosplugin.Critical)) self.assertEqual('Houston, we have a problem', c.summary_str) def test_execute(self): def fake_execute(_runtime_obj, verbose, timeout): self.assertEqual(2, verbose) self.assertEqual(20, timeout) r = nagiosplugin.Runtime() r.execute = fake_execute Check().main(2, 20) def test_verbose_str(self): self.assertEqual('', Check().verbose_str) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_metric.py0000644000175100017510000000227112341114271023106 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.metric import Metric import nagiosplugin try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class MetricTest(unittest.TestCase): def test_description(self): self.assertEqual('time is 1s', Metric( 'time', 1, 's', contextobj=nagiosplugin.ScalarContext('ctx') ).description) def test_valueunit_float(self): self.assertEqual('1.302s', Metric('time', 1.30234876, 's').valueunit) def test_valueunit_scientific(self): self.assertEqual('1.3e+04s', Metric('time', 13000., 's').valueunit) def test_valueunit_should_not_use_scientific_for_large_ints(self): self.assertEqual('13000s', Metric('time', 13000, 's').valueunit) def test_valueunit_nonfloat(self): self.assertEqual('text', Metric('text', 'text').valueunit) def test_evaluate_fails_if_no_context(self): with self.assertRaises(RuntimeError): Metric('time', 1, 's').evaluate() def test_performance_fails_if_no_context(self): with self.assertRaises(RuntimeError): Metric('time', 1, 's').performance() nagiosplugin-1.2.2/src/nagiosplugin/tests/test_platform.py0000644000175100017510000000062612341114271023451 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.platform import with_timeout import nagiosplugin import time try: import unittest2 as unittest except ImportError: # pragma: no cover import unittest class PlatformTest(unittest.TestCase): def test_timeout(self): with self.assertRaises(nagiosplugin.Timeout): with_timeout(1, time.sleep, 2) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_summary.py0000644000175100017510000000211712341114271023317 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.summary import Summary import nagiosplugin import unittest class SummaryTest(unittest.TestCase): def test_ok_returns_first_result(self): results = nagiosplugin.Results( nagiosplugin.Result(nagiosplugin.Ok, 'result 1'), nagiosplugin.Result(nagiosplugin.Ok, 'result 2')) self.assertEqual('result 1', Summary().ok(results)) def test_problem_returns_first_significant(self): results = nagiosplugin.Results( nagiosplugin.Result(nagiosplugin.Ok, 'result 1'), nagiosplugin.Result(nagiosplugin.Critical, 'result 2')) self.assertEqual('result 2', Summary().problem(results)) def test_verbose(self): self.assertEqual( ['critical: reason1', 'warning: reason2'], Summary().verbose(nagiosplugin.Results( nagiosplugin.Result(nagiosplugin.Critical, 'reason1'), nagiosplugin.Result(nagiosplugin.Ok, 'ignore'), nagiosplugin.Result(nagiosplugin.Warn, 'reason2')))) nagiosplugin-1.2.2/src/nagiosplugin/tests/test_multiarg.py0000644000175100017510000000155612341114271023454 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt from nagiosplugin.multiarg import MultiArg import unittest class MultiargTest(unittest.TestCase): def test_len(self): m = MultiArg(['a', 'b']) self.assertEqual(2, len(m)) def test_iter(self): m = MultiArg(['a', 'b']) self.assertEqual(['a', 'b'], list(m)) def test_split(self): m = MultiArg('a,b') self.assertEqual(['a', 'b'], list(m)) def test_explicit_fill_element(self): m = MultiArg(['0', '1'], fill='extra') self.assertEqual('1', m[1]) self.assertEqual('extra', m[2]) def test_fill_with_last_element(self): m = MultiArg(['0', '1']) self.assertEqual('1', m[1]) self.assertEqual('1', m[2]) def test_fill_empty_multiarg_returns_none(self): self.assertEqual(None, MultiArg([])[0]) nagiosplugin-1.2.2/src/nagiosplugin/context.py0000644000175100017510000001602412341114271021107 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Metadata about metrics to perform data :term:`evaluation`. This module contains the :class:`Context` class, which is the base for all contexts. :class:`ScalarContext` is an important specialization to cover numeric contexts with warning and critical thresholds. The :class:`~.check.Check` controller selects a context for each :class:`~.metric.Metric` by matching the metric's `context` attribute with the context's `name`. The same context may be used for several metrics. Plugin authors may just use to :class:`ScalarContext` in the majority of cases. Sometimes is better to subclass :class:`Context` instead to implement custom evaluation or performance data logic. """ from .performance import Performance from .range import Range from .result import Result from .state import Ok, Warn, Critical class Context(object): def __init__(self, name, fmt_metric=None, result_cls=Result): """Creates generic context identified by `name`. Generic contexts just format associated metrics and evaluate always to :obj:`~nagiosplugin.state.Ok`. Metric formatting is controlled with the :attr:`fmt_metric` attribute. It can either be a string or a callable. See the :meth:`describe` method for how formatting is done. :param name: context name that is matched by the context attribute of :class:`~nagiosplugin.metric.Metric` :param fmt_metric: string or callable to convert context and associated metric to a human readable string :param result_cls: use this class (usually a :class:`~.result.Result` subclass) to represent the evaluation outcome """ self.name = name self.fmt_metric = fmt_metric self.result_cls = result_cls def evaluate(self, metric, resource): """Determines state of a given metric. This base implementation returns :class:`~nagiosplugin.state.Ok` in all cases. Plugin authors may override this method in subclasses to specialize behaviour. :param metric: associated metric that is to be evaluated :param resource: resource that produced the associated metric (may optionally be consulted) :returns: :class:`~.result.Result` object """ return self.result_cls(Ok, metric=metric) def performance(self, metric, resource): """Derives performance data from a given metric. This base implementation just returns none. Plugin authors may override this method in subclass to specialize behaviour. :param metric: associated metric from which performance data are derived :param resource: resource that produced the associated metric (may optionally be consulted) :returns: :class:`Perfdata` object or `None` """ return None def describe(self, metric): """Provides human-readable metric description. Formats the metric according to the :attr:`fmt_metric` attribute. If :attr:`fmt_metric` is a string, it is evaluated as format string with all metric attributes in the root namespace. If :attr:`fmt_metric` is callable, it is called with the metric and this context as arguments. If :attr:`fmt_metric` is not set, this default implementation does not return a description. Plugin authors may override this method in subclasses to control text output more tightly. :param metric: associated metric :returns: description string or None """ if not self.fmt_metric: return try: return self.fmt_metric(metric, self) except TypeError: return self.fmt_metric.format( name=metric.name, value=metric.value, uom=metric.uom, valueunit=metric.valueunit, min=metric.min, max=metric.max) class ScalarContext(Context): def __init__(self, name, warning=None, critical=None, fmt_metric='{name} is {valueunit}', result_cls=Result): """Ready-to-use :class:`Context` subclass for scalar values. ScalarContext models the common case where a single scalar is to be evaluated against a pair of warning and critical thresholds. :attr:`name`, :attr:`fmt_metric`, and :attr:`result_cls`, are described in the :class:`Context` base class. :param warning: Warning threshold as :class:`~nagiosplugin.range.Range` object or range string. :param critical: Critical threshold as :class:`~nagiosplugin.range.Range` object or range string. """ super(ScalarContext, self).__init__(name, fmt_metric, result_cls) self.warning = Range(warning) self.critical = Range(critical) def evaluate(self, metric, resource): """Compares metric with ranges and determines result state. The metric's value is compared to the instance's :attr:`warning` and :attr:`critical` ranges, yielding an appropropiate state depending on how the metric fits in the ranges. Plugin authors may override this method in subclasses to provide custom evaluation logic. :param metric: metric that is to be evaluated :param resource: not used :returns: :class:`~nagiosplugin.result.Result` object """ if not self.critical.match(metric.value): return self.result_cls(Critical, self.critical.violation, metric) elif not self.warning.match(metric.value): return self.result_cls(Warn, self.warning.violation, metric) else: return self.result_cls(Ok, None, metric) def performance(self, metric, resource): """Derives performance data. The metric's attributes are combined with the local :attr:`warning` and :attr:`critical` ranges to get a fully populated :class:`~nagiosplugin.performance.Performance` object. :param metric: metric from which performance data are derived :param resource: not used :returns: :class:`~nagiosplugin.performance.Performance` object """ return Performance(metric.name, metric.value, metric.uom, self.warning, self.critical, metric.min, metric.max) class Contexts: """Container for collecting all generated contexts.""" def __init__(self): self.by_name = dict( default=ScalarContext('default', '', ''), null=Context('null')) def add(self, context): self.by_name[context.name] = context def __getitem__(self, context_name): try: return self.by_name[context_name] except KeyError: raise KeyError('cannot find context', context_name, 'known contexts: {0}'.format( ', '.join(self.by_name.keys()))) def __contains__(self, context_name): return context_name in self.by_name def __iter__(self): return iter(self.by_name) nagiosplugin-1.2.2/src/nagiosplugin/logtail.py0000644000175100017510000000433312341114271021056 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Access previously unseen parts of a growing file. LogTail builds on :class:`~.cookie.Cookie` to access new lines of a continuosly growing log file. It should be used as context manager that provides an iterator over new lines to the subordinate context. LogTail saves the last file position into the provided cookie object. As the path to the log file is saved in the cookie, several LogTail instances may share the same cookie. """ import os class LogTail(object): def __init__(self, path, cookie): """Creates new LogTail context. :param path: path to the log file that is to be observed :param cookie: :class:`~.cookie.Cookie` object to save the last file position """ self.path = os.path.abspath(path) self.cookie = cookie self.logfile = None self.stat = None def _seek_if_applicable(self, fileinfo): self.stat = os.stat(self.path) if (self.stat.st_ino == fileinfo.get('inode', -1) and self.stat.st_size >= fileinfo.get('pos', 0)): self.logfile.seek(fileinfo['pos']) def __enter__(self): """Seeks to the last seen position and reads new lines. The last file position is read from the cookie. If the log file has not been changed since the last invocation, LogTail seeks to that position and reads new lines. Otherwise, the position saved in the cookie is reset and LogTail reads from the beginning. After leaving the subordinate context, the new position is saved in the cookie and the cookie is closed. :yields: new lines as bytes strings """ self.logfile = open(self.path, 'rb') self.cookie.open() self._seek_if_applicable(self.cookie.get(self.path, {})) line = self.logfile.readline() while len(line): yield line line = self.logfile.readline() def __exit__(self, exc_type, exc_val, exc_tb): if not exc_type: self.cookie[self.path] = dict( inode=self.stat.st_ino, pos=self.logfile.tell()) self.cookie.commit() self.cookie.close() self.logfile.close() nagiosplugin-1.2.2/src/nagiosplugin/platform/0000755000175100017510000000000012341114372020674 5ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin/platform/__init__.py0000644000175100017510000000050312341114271023001 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """Platform-specific services.""" import os platform = __import__('nagiosplugin.platform.{0}'.format(os.name), fromlist=['with_timeout', 'flock_exclusive']) with_timeout = platform.with_timeout flock_exclusive = platform.flock_exclusive nagiosplugin-1.2.2/src/nagiosplugin/platform/nt.py0000644000175100017510000000141112341114271021662 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """NT implementation of platform-specific services.""" import nagiosplugin import threading import msvcrt def with_timeout(t, func, *args, **kwargs): """Call `func` but terminate after `t` seconds. We use a thread here since NT systems don't have POSIX signals. """ func_thread = threading.Thread(target=func, args=args, kwargs=kwargs) func_thread.daemon = True # quit interpreter even if still running func_thread.start() func_thread.join(t) if func_thread.is_alive(): raise nagiosplugin.Timeout('{0}s'.format(t)) def flock_exclusive(fileobj): """Acquire exclusive lock for open file `fileobj`.""" msvcrt.locking(fileobj.fileno(), msvcrt.LK_LOCK, 2147483647) nagiosplugin-1.2.2/src/nagiosplugin/platform/posix.py0000644000175100017510000000117612341114271022413 0ustar ckcore00000000000000# Copyright (c) gocept gmbh & co. kg # See also LICENSE.txt """POSIX implementation of platform-specific services""" import nagiosplugin import fcntl import signal def with_timeout(t, func, *args, **kwargs): """Call `func` but terminate after `t` seconds.""" def timeout_handler(signum, frame): raise nagiosplugin.Timeout('{0}s'.format(t)) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(t) try: func(*args, **kwargs) finally: signal.alarm(0) def flock_exclusive(fileobj): """Acquire exclusive lock for open file `fileobj`.""" fcntl.flock(fileobj, fcntl.LOCK_EX) nagiosplugin-1.2.2/src/nagiosplugin.egg-info/0000755000175100017510000000000012341114372020542 5ustar ckcore00000000000000nagiosplugin-1.2.2/src/nagiosplugin.egg-info/SOURCES.txt0000644000175100017510000000335112341114365022432 0ustar ckcore00000000000000CONTRIBUTORS.txt HACKING.txt HISTORY.txt LICENSE.txt MANIFEST.in README.txt bootstrap.py buildout.cfg setup.cfg setup.py version.txt src/nagiosplugin/__init__.py src/nagiosplugin/check.py src/nagiosplugin/compat.py src/nagiosplugin/context.py src/nagiosplugin/cookie.py src/nagiosplugin/error.py src/nagiosplugin/logtail.py src/nagiosplugin/metric.py src/nagiosplugin/multiarg.py src/nagiosplugin/output.py src/nagiosplugin/performance.py src/nagiosplugin/range.py src/nagiosplugin/resource.py src/nagiosplugin/result.py src/nagiosplugin/runtime.py src/nagiosplugin/state.py src/nagiosplugin/summary.py src/nagiosplugin.egg-info/PKG-INFO src/nagiosplugin.egg-info/SOURCES.txt src/nagiosplugin.egg-info/dependency_links.txt src/nagiosplugin.egg-info/not-zip-safe src/nagiosplugin.egg-info/requires.txt src/nagiosplugin.egg-info/top_level.txt src/nagiosplugin/examples/__init__.py src/nagiosplugin/examples/check_haproxy_log.py src/nagiosplugin/examples/check_load.py src/nagiosplugin/examples/check_users.py src/nagiosplugin/examples/check_world.py src/nagiosplugin/platform/__init__.py src/nagiosplugin/platform/nt.py src/nagiosplugin/platform/posix.py src/nagiosplugin/tests/__init__.py src/nagiosplugin/tests/test_check.py src/nagiosplugin/tests/test_context.py src/nagiosplugin/tests/test_cookie.py src/nagiosplugin/tests/test_examples.py src/nagiosplugin/tests/test_logtail.py src/nagiosplugin/tests/test_metric.py src/nagiosplugin/tests/test_multiarg.py src/nagiosplugin/tests/test_output.py src/nagiosplugin/tests/test_performance.py src/nagiosplugin/tests/test_platform.py src/nagiosplugin/tests/test_range.py src/nagiosplugin/tests/test_result.py src/nagiosplugin/tests/test_runtime.py src/nagiosplugin/tests/test_state.py src/nagiosplugin/tests/test_summary.pynagiosplugin-1.2.2/src/nagiosplugin.egg-info/PKG-INFO0000644000175100017510000002770012341114365021647 0ustar ckcore00000000000000Metadata-Version: 1.1 Name: nagiosplugin Version: 1.2.2 Summary: Class library for writing Nagios (Icinga) plugins Home-page: http://projects.gocept.com/projects/nagiosplugin Author: Christian Kauhaus Author-email: kc@gocept.com License: ZPL-2.1 Download-URL: http://pypi.python.org/pypi/nagiosplugin Description: The nagiosplugin library ======================== About ----- **nagiosplugin** is a Python class library which helps writing Nagios (or Icinga) compatible plugins easily in Python. It cares for much of the boilerplate code and default logic commonly found in Nagios checks, including: - Nagios 3 Plugin API compliant parameters and output formatting - Full Nagios range syntax support - Automatic threshold checking - Multiple independend measures - Custom status line to communicate the main point quickly - Long output and performance data - Timeout handling - Persistent "cookies" to retain state information between check runs - Resume log file processing at the point where the last run left - No dependencies beyond the Python standard library (except for Python 2.6). nagiosplugin runs on POSIX and Windows systems. It is compatible with Python 3.4, Python 3.3, Python 3.2, Python 2.7, and Python 2.6. Feedback and Suggestions ------------------------ nagiosplugin is primarily written and maintained by Christian Kauhaus . Feel free to contact the author for bugs, suggestions and patches. A public issue tracker can be found at http://projects.gocept.com/projects/nagiosplugin/issues. There is also a forum available at https://projects.gocept.com/projects/nagiosplugin/boards. License ------- The nagiosplugin package is released under the Zope Public License 2.1 (ZPL), a BSD-style Open Source license. Documentation ------------- Comprehensive documentation is `available online`_. The examples mentioned in the `tutorials`_ can also be found in the `nagiosplugin/examples` directory of the source distribution. .. _available online: http://pythonhosted.org/nagiosplugin/ .. _tutorials: http://pythonhosted.org/nagiosplugin/tutorial/ .. vim: set ft=rst: Development =========== Getting the source ------------------ The source can be obtained via mercurial from https://bitbucket.org/gocept/nagiosplugin:: hg clone https://bitbucket.org/gocept/nagiosplugin This package supports installation in a virtualenv using zc.buildout. Creating a build with zc.buildout --------------------------------- First, create a virtualenv if not already present:: virtualenv -p python3.2 . If you want to use Python 2.7, specify this Python version while creating the virtualenv:: virtualenv -p python2.7 . Sometimes, you will run into strange setuptools/distribute version conflicts. The best way around is to keep setuptools and distribute entirely out of the virtualenv:: virtualenv -p python3.2 --no-setuptools . `bootstrap.py` and zc.buildout will take care of all required dependencies. Then launch the usual buildout steps:: bin/python3.2 bootstrap.py bin/buildout Tests ----- When the buildout succeeds, run the unit test by invoking:: bin/test Our `build server`_ runs test against the trunk on a regular basis. .. image:: https://builds.gocept.com/job/nagiosplugin/badge/icon :target: https://builds.gocept.com/job/nagiosplugin/ .. _build server: https://builds.gocept.com/job/nagiosplugin/ nagiosplugin also includes support for coverage reports. It aims at 100% test coverage where possible. The preferred way to get a coverate report is to use :: bin/createcoverage to determine test coverage and open the report in a web browser. Alternatively, run :: bin/coverage run bin/test to get a purely text-based coverage report. You may run the supplied examples with the local interpreter:: bin/py src/nagiosplugin/examples/check_load.py Documentation ------------- The documentation uses Sphinx. Build the documentation (buildout should have been running before to install Sphinx etc.):: make -C doc html How to release -------------- To make a release, we prefer `zest.releaser`_. To make a release, follow the standard procedure, which usually boils down to:: fullrelease `nagiosplugin` tried to obey the semantic version numbering specification published on SemVer_ but adapts it a little bit to be `PEP 386`_ compliant. .. _zest.releaser: http://pypi.python.org/pypi/zest.releaser/ .. _SemVer: http://semver.org/ .. _PEP 386: http://www.python.org/dev/peps/pep-0386/ .. vim: set ft=rst sw=3 sts=3 et: Contributors ============ `nagiosplugin` has become what it is now with the help of many contributors from the community. We want to thank everyone who has invested time and energy to make `nagiosplugin` better: * Wolfgang Schnerring for thoughts on the design. * Thomas Lotze for improving the test infrastructure. * Christian Theune for comments and general feedback. * Michael Howitz and Andrei Chirila for the Python 3 port. * Birger Schmidt for bug reports. * Florian Lagg for Windows compatibility fixes * Jeff Goldschrafe for the Python 2.6 backport. * José Manuel Fardello for a logging fix. * Jordan Metzmeier for build fixes and Debian packaging. * Andrey Panfilov for a perfdata fix. * Mihai Limbășan for various output formatting fixes. .. vim: set ft=rst sw=3 sts=3 et: Release History =============== 1.2.2 (2014-05-27) ------------------ - Mention that nagiosplugin also runs with Python 3.4 (no code changes necessary). - Make name prefix in status output optional by allowing to assign None to Check.name. - Accept bare metric as return value from Resource.probe(). - Fix bug where Context.describe() was not used to obtain metric description (#13162). 1.2.1 (2014-03-19) ------------------ - Fix build failures with LANG=C (#13140). - Remove length limitation of perfdata labels (#13214). - Fix formatting of large integers as Metric values (#13287). - Range: allow simple numerals as argument to Range() (#12658). - Cookie: allow for empty state file specification (#12788). 1.2 (2013-11-08) ---------------- - New `Summary.empty` method is called if there are no results present (#11593). - Improve range violation wording (#11597). - Ensure that nagiosplugin install correctly with current setuptools (#12660). - Behave and do not attach anything to the root logger. - Add debugging topic guide. Explain how to disable the timeout when using pdb (#11592). 1.1 (2013-06-19) ---------------- - Identical to 1.1b1. 1.1b1 (2013-05-28) ------------------ - Made compatible with Python 2.6 (#12297). - Tutorial #3: check_users (#11539). - Minor documentation improvements. 1.0.0 (2013-02-05) ------------------ - LogTail returns lines as byte strings in Python 3 to avoid codec issues (#11564). - LogTail gives a line-based iterator instead of a file object (#11564). - Basic API docs for the most important classes (#11612). - Made compatible with Python 2.7 (#11533). - Made compatible with Python 3.3. 1.0.0b1 (2012-10-29) -------------------- - Improve error reporting for missing contexts. - Exit with code 3 if no metrics have been generated. - Improve default Summary.verbose() to list all threshold violations. - Move main source repository to https://bitbucket.org/gocept/nagiosplugin/ (#11561). 1.0.0a2 (2012-10-26) -------------------- - API docs for the most important classes (#7939). - Added two tutorials (#9425). - Fix packaging issues. 1.0.0a1 (2012-10-25) -------------------- - Completely reworked API. The new API is not compatible with the old 0.4 API so you must update your plugins. - Python 3 support. - The `Cookie` class is now basically a persistent dict and accepts key/value pairs. Cookie are stored as JSON files by default so they can be inspected by the system administrator (#9400). - New `LogTail` class which provides convenient access to constantly growing log files which are eventually rotated. 0.4.5 (2012-06-18) ------------------ - Windows port. `nagiosplugin` code now runs under pywin32 (#10899). - Include examples in egg release (#9901). 0.4.4 (2011-07-18) ------------------ Bugfix release to fix issues reported by users. - Improve Mac OS X compatibility (#8755). - Include examples in distribution (#8555). 0.4.3 (2010-12-17) ------------------ - Change __str__ representation of large numbers to avoid scientific notation. 0.4.2 (2010-10-11) ------------------ - Packaging issues. 0.4.1 (2010-09-21) ------------------ - Fix distribution to install correctly. - Documentation: tutorial and topic guides. 0.4 (2010-08-17) ---------------- - Initial public release. .. vim: set ft=rst sw=3 sts=3 spell spelllang=en: Keywords: Nagios Icinga plugin check monitoring Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Monitoring nagiosplugin-1.2.2/src/nagiosplugin.egg-info/top_level.txt0000644000175100017510000000001512341114365023272 0ustar ckcore00000000000000nagiosplugin nagiosplugin-1.2.2/src/nagiosplugin.egg-info/requires.txt0000644000175100017510000000002312341114365023137 0ustar ckcore00000000000000 [test] setuptoolsnagiosplugin-1.2.2/src/nagiosplugin.egg-info/not-zip-safe0000644000175100017510000000000112341114271022766 0ustar ckcore00000000000000 nagiosplugin-1.2.2/src/nagiosplugin.egg-info/dependency_links.txt0000644000175100017510000000000112341114365024612 0ustar ckcore00000000000000 nagiosplugin-1.2.2/CONTRIBUTORS.txt0000644000175100017510000000204012341114271016252 0ustar ckcore00000000000000Contributors ============ `nagiosplugin` has become what it is now with the help of many contributors from the community. We want to thank everyone who has invested time and energy to make `nagiosplugin` better: * Wolfgang Schnerring for thoughts on the design. * Thomas Lotze for improving the test infrastructure. * Christian Theune for comments and general feedback. * Michael Howitz and Andrei Chirila for the Python 3 port. * Birger Schmidt for bug reports. * Florian Lagg for Windows compatibility fixes * Jeff Goldschrafe for the Python 2.6 backport. * José Manuel Fardello for a logging fix. * Jordan Metzmeier for build fixes and Debian packaging. * Andrey Panfilov for a perfdata fix. * Mihai Limbășan for various output formatting fixes. .. vim: set ft=rst sw=3 sts=3 et: nagiosplugin-1.2.2/HISTORY.txt0000644000175100017510000000644212341114271015470 0ustar ckcore00000000000000Release History =============== 1.2.2 (2014-05-27) ------------------ - Mention that nagiosplugin also runs with Python 3.4 (no code changes necessary). - Make name prefix in status output optional by allowing to assign None to Check.name. - Accept bare metric as return value from Resource.probe(). - Fix bug where Context.describe() was not used to obtain metric description (#13162). 1.2.1 (2014-03-19) ------------------ - Fix build failures with LANG=C (#13140). - Remove length limitation of perfdata labels (#13214). - Fix formatting of large integers as Metric values (#13287). - Range: allow simple numerals as argument to Range() (#12658). - Cookie: allow for empty state file specification (#12788). 1.2 (2013-11-08) ---------------- - New `Summary.empty` method is called if there are no results present (#11593). - Improve range violation wording (#11597). - Ensure that nagiosplugin install correctly with current setuptools (#12660). - Behave and do not attach anything to the root logger. - Add debugging topic guide. Explain how to disable the timeout when using pdb (#11592). 1.1 (2013-06-19) ---------------- - Identical to 1.1b1. 1.1b1 (2013-05-28) ------------------ - Made compatible with Python 2.6 (#12297). - Tutorial #3: check_users (#11539). - Minor documentation improvements. 1.0.0 (2013-02-05) ------------------ - LogTail returns lines as byte strings in Python 3 to avoid codec issues (#11564). - LogTail gives a line-based iterator instead of a file object (#11564). - Basic API docs for the most important classes (#11612). - Made compatible with Python 2.7 (#11533). - Made compatible with Python 3.3. 1.0.0b1 (2012-10-29) -------------------- - Improve error reporting for missing contexts. - Exit with code 3 if no metrics have been generated. - Improve default Summary.verbose() to list all threshold violations. - Move main source repository to https://bitbucket.org/gocept/nagiosplugin/ (#11561). 1.0.0a2 (2012-10-26) -------------------- - API docs for the most important classes (#7939). - Added two tutorials (#9425). - Fix packaging issues. 1.0.0a1 (2012-10-25) -------------------- - Completely reworked API. The new API is not compatible with the old 0.4 API so you must update your plugins. - Python 3 support. - The `Cookie` class is now basically a persistent dict and accepts key/value pairs. Cookie are stored as JSON files by default so they can be inspected by the system administrator (#9400). - New `LogTail` class which provides convenient access to constantly growing log files which are eventually rotated. 0.4.5 (2012-06-18) ------------------ - Windows port. `nagiosplugin` code now runs under pywin32 (#10899). - Include examples in egg release (#9901). 0.4.4 (2011-07-18) ------------------ Bugfix release to fix issues reported by users. - Improve Mac OS X compatibility (#8755). - Include examples in distribution (#8555). 0.4.3 (2010-12-17) ------------------ - Change __str__ representation of large numbers to avoid scientific notation. 0.4.2 (2010-10-11) ------------------ - Packaging issues. 0.4.1 (2010-09-21) ------------------ - Fix distribution to install correctly. - Documentation: tutorial and topic guides. 0.4 (2010-08-17) ---------------- - Initial public release. .. vim: set ft=rst sw=3 sts=3 spell spelllang=en: nagiosplugin-1.2.2/MANIFEST.in0000644000175100017510000000024612341114271015320 0ustar ckcore00000000000000include bootstrap.py include buildout.cfg include CONTRIBUTORS.txt include HACKING.txt include HISTORY.txt include LICENSE.txt include README.txt include version.txt