├── .gitignore ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bin └── munin-node.py ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── munin ├── __init__.py ├── cassandra.py ├── ddwrt.py ├── gearman.py ├── memcached.py ├── mongodb.py ├── mysql.py ├── nginx.py ├── pgbouncer.py ├── postgres.py ├── redis.py └── riak.py ├── plugins ├── aws_elb_latency ├── aws_elb_requests ├── aws_sqs_queue_length_ ├── cassandra_cfcounts ├── cassandra_key_cache_ratio ├── cassandra_latency ├── cassandra_load ├── cassandra_pending ├── cpu ├── ddwrt_wl_rate ├── ddwrt_wl_signal ├── gearman_connections ├── gearman_queues ├── hookbox ├── indextank_index_size ├── loadavg ├── memcached_bytes ├── memcached_connections ├── memcached_curr_bytes ├── memcached_curr_items ├── memcached_items ├── memcached_queries ├── mongodb_flush_avg ├── mongodb_heap_usage ├── mongodb_index_misses ├── mongodb_lock_ratio ├── mongodb_lock_time ├── mongodb_memory ├── mongodb_objects_ ├── mongodb_ops ├── mongodb_page_faults ├── mongodb_queues ├── mongodb_replset_lag ├── mongodb_size_ ├── mysql_connections ├── mysql_dbrows_ ├── mysql_dbsize_ ├── mysql_replication ├── nginx_connections ├── nginx_requests ├── path_size ├── pgbouncer_pools_cl_ ├── pgbouncer_pools_sv_ ├── pgbouncer_stats_avg_bytes_ ├── pgbouncer_stats_avg_query_ ├── pgbouncer_stats_avg_req_ ├── postgres_block_read_ ├── postgres_commits_ ├── postgres_connections ├── postgres_locks ├── postgres_queries_ ├── postgres_space_ ├── postgres_table_sizes ├── rabbitmq-throughput ├── redis_active_connections ├── redis_commands ├── redis_connects ├── redis_used_memory ├── request_time ├── riak_ops └── tc_size └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Samuel Stauffer 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Team Awesome Productions Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | Except as contained in this notice, the name(s) of the above 16 | copyright holders shall not be used in advertising or otherwise 17 | to promote the sale, use or other dealings in this Software 18 | without prior written authorization. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include MANIFEST.in 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | Description 3 | =========== 4 | 5 | This library provides helper classes for writing plugins for the server 6 | monitoring tool Munin. It also comes with some prebuilt plugins for 7 | various services including PostgreSQL, Memcached, and Nginx. 8 | -------------------------------------------------------------------------------- /bin/munin-node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import socket 5 | import SocketServer 6 | import sys 7 | import threading 8 | import time 9 | from subprocess import Popen, PIPE 10 | 11 | PLUGIN_PATH = "/etc/munin/plugins" 12 | 13 | def parse_args(): 14 | from optparse import OptionParser 15 | parser = OptionParser() 16 | parser.add_option("-p", "--pluginpath", dest="plugin_path", 17 | help="path to plugins", default=PLUGIN_PATH) 18 | (options, args) = parser.parse_args() 19 | return options, args 20 | 21 | 22 | def execute_plugin(path, cmd=""): 23 | args = [path] 24 | if cmd: 25 | args.append(cmd) 26 | p = Popen(args, stdout=PIPE) 27 | output = p.communicate()[0] 28 | return output 29 | 30 | if os.name == 'posix': 31 | def become_daemon(our_home_dir='.', out_log='/dev/null', 32 | err_log='/dev/null', umask=022): 33 | "Robustly turn into a UNIX daemon, running in our_home_dir." 34 | # First fork 35 | try: 36 | if os.fork() > 0: 37 | sys.exit(0) # kill off parent 38 | except OSError, e: 39 | sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror)) 40 | sys.exit(1) 41 | os.setsid() 42 | os.chdir(our_home_dir) 43 | os.umask(umask) 44 | 45 | # Second fork 46 | try: 47 | if os.fork() > 0: 48 | os._exit(0) 49 | except OSError, e: 50 | sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror)) 51 | os._exit(1) 52 | 53 | si = open('/dev/null', 'r') 54 | so = open(out_log, 'a+', 0) 55 | se = open(err_log, 'a+', 0) 56 | os.dup2(si.fileno(), sys.stdin.fileno()) 57 | os.dup2(so.fileno(), sys.stdout.fileno()) 58 | os.dup2(se.fileno(), sys.stderr.fileno()) 59 | # Set custom file descriptors so that they get proper buffering. 60 | sys.stdout, sys.stderr = so, se 61 | else: 62 | def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=022): 63 | """ 64 | If we're not running under a POSIX system, just simulate the daemon 65 | mode by doing redirections and directory changing. 66 | """ 67 | os.chdir(our_home_dir) 68 | os.umask(umask) 69 | sys.stdin.close() 70 | sys.stdout.close() 71 | sys.stderr.close() 72 | if err_log: 73 | sys.stderr = open(err_log, 'a', 0) 74 | else: 75 | sys.stderr = NullDevice() 76 | if out_log: 77 | sys.stdout = open(out_log, 'a', 0) 78 | else: 79 | sys.stdout = NullDevice() 80 | 81 | class NullDevice: 82 | "A writeable object that writes to nowhere -- like /dev/null." 83 | def write(self, s): 84 | pass 85 | 86 | class MuninRequestHandler(SocketServer.StreamRequestHandler): 87 | def handle(self): 88 | # self.rfile is a file-like object created by the handler; 89 | # we can now use e.g. readline() instead of raw recv() calls 90 | plugins = [] 91 | for x in os.listdir(self.server.options.plugin_path): 92 | if x.startswith('.'): 93 | continue 94 | fullpath = os.path.join(self.server.options.plugin_path, x) 95 | if not os.path.isfile(fullpath): 96 | continue 97 | plugins.append(x) 98 | 99 | node_name = socket.gethostname().split('.')[0] 100 | self.wfile.write("# munin node at %s\n" % node_name) 101 | while True: 102 | line = self.rfile.readline() 103 | if not line: 104 | break 105 | line = line.strip() 106 | 107 | cmd = line.split(' ', 1) 108 | plugin = (len(cmd) > 1) and cmd[1] or None 109 | 110 | if cmd[0] == "list": 111 | self.wfile.write("%s\n" % " ".join(plugins)) 112 | elif cmd[0] == "nodes": 113 | self.wfile.write("nodes\n%s\n.\n" % (node_name)) 114 | elif cmd[0] == "version": 115 | self.wfile.write("munins node on chatter1 version: 1.2.6\n") 116 | elif cmd[0] in ("fetch", "config"): 117 | if plugin not in plugins: 118 | self.wfile.write("# Unknown service\n.\n") 119 | continue 120 | c = (cmd[0] == "config") and "config" or "" 121 | out = execute_plugin(os.path.join(self.server.options.plugin_path, plugin), c) 122 | self.wfile.write(out) 123 | if out and out[-1] != "\n": 124 | self.wfile.write("\n") 125 | self.wfile.write(".\n") 126 | elif cmd[0] == "quit": 127 | break 128 | else: 129 | self.wfile.write("# Unknown command. Try list, nodes, config, fetch, version or quit\n") 130 | 131 | 132 | class MuninServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 133 | pass 134 | 135 | if __name__ == "__main__": 136 | HOST, PORT = "0.0.0.0", 4949 137 | if sys.version_info[:3] >= (2, 6, 0): 138 | server = MuninServer((HOST, PORT), MuninRequestHandler, bind_and_activate=False) 139 | server.allow_reuse_address = True 140 | server.server_bind() 141 | server.server_activate() 142 | else: 143 | server = MuninServer((HOST, PORT), MuninRequestHandler) 144 | ip, port = server.server_address 145 | options, args = parse_args() 146 | options.plugin_path = os.path.abspath(options.plugin_path) 147 | server.options = options 148 | 149 | become_daemon() 150 | server.serve_forever() 151 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | 9 | # Internal variables. 10 | PAPEROPT_a4 = -D latex_paper_size=a4 11 | PAPEROPT_letter = -D latex_paper_size=letter 12 | ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 13 | 14 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 15 | 16 | help: 17 | @echo "Please use \`make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " dirhtml to make HTML files named index.html in directories" 20 | @echo " pickle to make pickle files" 21 | @echo " json to make JSON files" 22 | @echo " htmlhelp to make HTML files and a HTML help project" 23 | @echo " qthelp to make HTML files and a qthelp project" 24 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 25 | @echo " changes to make an overview of all changed/added/deprecated items" 26 | @echo " linkcheck to check all external links for integrity" 27 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 28 | 29 | clean: 30 | -rm -rf _build/* 31 | 32 | html: 33 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html 34 | @echo 35 | @echo "Build finished. The HTML pages are in _build/html." 36 | 37 | dirhtml: 38 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml 39 | @echo 40 | @echo "Build finished. The HTML pages are in _build/dirhtml." 41 | 42 | pickle: 43 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle 44 | @echo 45 | @echo "Build finished; now you can process the pickle files." 46 | 47 | json: 48 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json 49 | @echo 50 | @echo "Build finished; now you can process the JSON files." 51 | 52 | htmlhelp: 53 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp 54 | @echo 55 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 56 | ".hhp project file in _build/htmlhelp." 57 | 58 | qthelp: 59 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp 60 | @echo 61 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 62 | ".qhcp project file in _build/qthelp, like this:" 63 | @echo "# qcollectiongenerator _build/qthelp/python-munin.qhcp" 64 | @echo "To view the help file:" 65 | @echo "# assistant -collectionFile _build/qthelp/python-munin.qhc" 66 | 67 | latex: 68 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex 69 | @echo 70 | @echo "Build finished; the LaTeX files are in _build/latex." 71 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 72 | "run these through (pdf)latex." 73 | 74 | changes: 75 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes 76 | @echo 77 | @echo "The overview file is in _build/changes." 78 | 79 | linkcheck: 80 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck 81 | @echo 82 | @echo "Link check complete; look for any errors in the above output " \ 83 | "or in _build/linkcheck/output.txt." 84 | 85 | doctest: 86 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest 87 | @echo "Testing of doctests in the sources finished, look at the " \ 88 | "results in _build/doctest/output.txt." 89 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-munin documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Jul 27 14:30:15 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.append(os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # Add any Sphinx extension module names here, as strings. They can be extensions 24 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 25 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] 26 | try: 27 | from github.tools import sphinx 28 | except ImportError: 29 | pass 30 | else: 31 | extensions.append('github.tools.sphinx') 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'python-munin' 47 | copyright = u'2009, Samuel Stauffer' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '1.4' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '1.4' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of documents that shouldn't be included in the build. 69 | #unused_docs = [] 70 | 71 | # List of directories, relative to source directory, that shouldn't be searched 72 | # for source files. 73 | exclude_trees = ['_build'] 74 | 75 | # The reST default role (used for this markup: `text`) to use for all documents. 76 | #default_role = None 77 | 78 | # If true, '()' will be appended to :func: etc. cross-reference text. 79 | #add_function_parentheses = True 80 | 81 | # If true, the current module name will be prepended to all description 82 | # unit titles (such as .. function::). 83 | #add_module_names = True 84 | 85 | # If true, sectionauthor and moduleauthor directives will be shown in the 86 | # output. They are ignored by default. 87 | #show_authors = False 88 | 89 | # The name of the Pygments (syntax highlighting) style to use. 90 | pygments_style = 'sphinx' 91 | 92 | # A list of ignored prefixes for module index sorting. 93 | #modindex_common_prefix = [] 94 | 95 | 96 | # -- Options for HTML output --------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. Major themes that come with 99 | # Sphinx are currently 'default' and 'sphinxdoc'. 100 | html_theme = 'default' 101 | 102 | # Theme options are theme-specific and customize the look and feel of a theme 103 | # further. For a list of options available for each theme, see the 104 | # documentation. 105 | #html_theme_options = {} 106 | 107 | # Add any paths that contain custom themes here, relative to this directory. 108 | #html_theme_path = [] 109 | 110 | # The name for this set of Sphinx documents. If None, it defaults to 111 | # " v documentation". 112 | #html_title = None 113 | 114 | # A shorter title for the navigation bar. Default is the same as html_title. 115 | #html_short_title = None 116 | 117 | # The name of an image file (relative to this directory) to place at the top 118 | # of the sidebar. 119 | #html_logo = None 120 | 121 | # The name of an image file (within the static path) to use as favicon of the 122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 123 | # pixels large. 124 | #html_favicon = None 125 | 126 | # Add any paths that contain custom static files (such as style sheets) here, 127 | # relative to this directory. They are copied after the builtin static files, 128 | # so a file named "default.css" will overwrite the builtin "default.css". 129 | html_static_path = [] # ['_static'] 130 | 131 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 132 | # using the given strftime format. 133 | #html_last_updated_fmt = '%b %d, %Y' 134 | 135 | # If true, SmartyPants will be used to convert quotes and dashes to 136 | # typographically correct entities. 137 | #html_use_smartypants = True 138 | 139 | # Custom sidebar templates, maps document names to template names. 140 | #html_sidebars = {} 141 | 142 | # Additional templates that should be rendered to pages, maps page names to 143 | # template names. 144 | #html_additional_pages = {} 145 | 146 | # If false, no module index is generated. 147 | #html_use_modindex = True 148 | 149 | # If false, no index is generated. 150 | #html_use_index = True 151 | 152 | # If true, the index is split into individual pages for each letter. 153 | #html_split_index = False 154 | 155 | # If true, links to the reST sources are added to the pages. 156 | #html_show_sourcelink = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = '' 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'python-munindoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'python-munin.tex', u'python-munin Documentation', 182 | u'Samuel Stauffer', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # Additional stuff for the LaTeX preamble. 194 | #latex_preamble = '' 195 | 196 | # Documents to append as an appendix to all manuals. 197 | #latex_appendices = [] 198 | 199 | # If false, no module index is generated. 200 | #latex_use_modindex = True 201 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | python-munin 3 | ============ 4 | 5 | This library provides helper classes for writing plugins for the server 6 | monitoring tool Munin. It also comes with some prebuilt plugins for 7 | various services including PostgreSQL, Memcached, and Nginx. 8 | 9 | See http://munin.projects.linpro.no/ for more about Munin. 10 | 11 | Source 12 | ====== 13 | 14 | You can find the latest version of python-munin at 15 | http://github.com/samuel/python-munin 16 | 17 | Example 18 | ======= 19 | 20 | Example plugin (loadavg):: 21 | 22 | #!/usr/bin/env python 23 | 24 | import os 25 | from munin import MuninPlugin 26 | 27 | class LoadAVGPlugin(MuninPlugin): 28 | title = "Load average" 29 | args = "--base 1000 -l 0" 30 | vlabel = "load" 31 | scale = False 32 | category = "system" 33 | 34 | @property 35 | def fields(self): 36 | warning = os.environ.get('load_warn', 10) 37 | critical = os.environ.get('load_crit', 120) 38 | return [("load", dict( 39 | label = "load", 40 | info = 'The load average of the machine describes how many processes are in the run-queue (scheduled to run "immediately").', 41 | type = "GAUGE", 42 | min = "0", 43 | warning = str(warning), 44 | critical = str(critical)))] 45 | 46 | def execute(self): 47 | if os.path.exists("/proc/loadavg"): 48 | loadavg = open("/proc/loadavg", "r").read().strip().split(' ') 49 | else: 50 | from subprocess import Popen, PIPE 51 | output = Popen(["uptime"], stdout=PIPE).communicate()[0] 52 | loadavg = output.rsplit(':', 1)[1].strip().split(' ')[:3] 53 | return dict(load=loadavg[1]) 54 | 55 | if __name__ == "__main__": 56 | LoadAVGPlugin().run() 57 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . 7 | if NOT "%PAPER%" == "" ( 8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 9 | ) 10 | 11 | if "%1" == "" goto help 12 | 13 | if "%1" == "help" ( 14 | :help 15 | echo.Please use `make ^` where ^ is one of 16 | echo. html to make standalone HTML files 17 | echo. dirhtml to make HTML files named index.html in directories 18 | echo. pickle to make pickle files 19 | echo. json to make JSON files 20 | echo. htmlhelp to make HTML files and a HTML help project 21 | echo. qthelp to make HTML files and a qthelp project 22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 23 | echo. changes to make an overview over all changed/added/deprecated items 24 | echo. linkcheck to check all external links for integrity 25 | echo. doctest to run all doctests embedded in the documentation if enabled 26 | goto end 27 | ) 28 | 29 | if "%1" == "clean" ( 30 | for /d %%i in (_build\*) do rmdir /q /s %%i 31 | del /q /s _build\* 32 | goto end 33 | ) 34 | 35 | if "%1" == "html" ( 36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html 37 | echo. 38 | echo.Build finished. The HTML pages are in _build/html. 39 | goto end 40 | ) 41 | 42 | if "%1" == "dirhtml" ( 43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml 44 | echo. 45 | echo.Build finished. The HTML pages are in _build/dirhtml. 46 | goto end 47 | ) 48 | 49 | if "%1" == "pickle" ( 50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle 51 | echo. 52 | echo.Build finished; now you can process the pickle files. 53 | goto end 54 | ) 55 | 56 | if "%1" == "json" ( 57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json 58 | echo. 59 | echo.Build finished; now you can process the JSON files. 60 | goto end 61 | ) 62 | 63 | if "%1" == "htmlhelp" ( 64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp 65 | echo. 66 | echo.Build finished; now you can run HTML Help Workshop with the ^ 67 | .hhp project file in _build/htmlhelp. 68 | goto end 69 | ) 70 | 71 | if "%1" == "qthelp" ( 72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp 73 | echo. 74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 75 | .qhcp project file in _build/qthelp, like this: 76 | echo.^> qcollectiongenerator _build\qthelp\python-munin.qhcp 77 | echo.To view the help file: 78 | echo.^> assistant -collectionFile _build\qthelp\python-munin.ghc 79 | goto end 80 | ) 81 | 82 | if "%1" == "latex" ( 83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex 84 | echo. 85 | echo.Build finished; the LaTeX files are in _build/latex. 86 | goto end 87 | ) 88 | 89 | if "%1" == "changes" ( 90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes 91 | echo. 92 | echo.The overview file is in _build/changes. 93 | goto end 94 | ) 95 | 96 | if "%1" == "linkcheck" ( 97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck 98 | echo. 99 | echo.Link check complete; look for any errors in the above output ^ 100 | or in _build/linkcheck/output.txt. 101 | goto end 102 | ) 103 | 104 | if "%1" == "doctest" ( 105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest 106 | echo. 107 | echo.Testing of doctests in the sources finished, look at the ^ 108 | results in _build/doctest/output.txt. 109 | goto end 110 | ) 111 | 112 | :end 113 | -------------------------------------------------------------------------------- /munin/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = "1.4" 3 | 4 | import os 5 | import sys 6 | import socket 7 | from decimal import Decimal 8 | 9 | class MuninPlugin(object): 10 | title = "" 11 | args = None 12 | vlabel = None 13 | info = None 14 | category = None 15 | fields = [] 16 | 17 | def __init__(self): 18 | if 'GRAPH_TITLE' in os.environ: 19 | self.title = os.environ['GRAPH_TITLE'] 20 | if 'GRAPH_CATEGORY' in os.environ: 21 | self.category = os.environ['GRAPH_CATEGORY'] 22 | super(MuninPlugin, self).__init__() 23 | 24 | def autoconf(self): 25 | return False 26 | 27 | def config(self): 28 | conf = [] 29 | for k in ('title', 'category', 'args', 'vlabel', 'info', 'scale', 'order'): 30 | v = getattr(self, k, None) 31 | if v is not None: 32 | if isinstance(v, bool): 33 | v = v and "yes" or "no" 34 | elif isinstance(v, (tuple, list)): 35 | v = " ".join(v) 36 | conf.append('graph_%s %s' % (k, v)) 37 | 38 | for field_name, field_args in self.fields: 39 | for arg_name, arg_value in field_args.iteritems(): 40 | conf.append('%s.%s %s' % (field_name, arg_name, arg_value)) 41 | 42 | print "\n".join(conf) 43 | 44 | def suggest(self): 45 | sys.exit(1) 46 | 47 | def run(self): 48 | cmd = ((len(sys.argv) > 1) and sys.argv[1] or None) or "execute" 49 | if cmd == "execute": 50 | values = self.execute() 51 | if values: 52 | for k, v in values.items(): 53 | print "%s.value %s" % (k, v) 54 | elif cmd == "autoconf": 55 | try: 56 | ac = self.autoconf() 57 | except Exception, exc: 58 | print "no (%s)" % str(exc) 59 | sys.exit(1) 60 | if not ac: 61 | print "no" 62 | sys.exit(1) 63 | print "yes" 64 | elif cmd == "config": 65 | self.config() 66 | elif cmd == "suggest": 67 | self.suggest() 68 | sys.exit(0) 69 | 70 | class MuninClient(object): 71 | def __init__(self, host, port=4949): 72 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 73 | self.sock.connect((host, port)) 74 | self.sock.recv(4096) # welcome, TODO: receive all 75 | 76 | def _command(self, cmd, term): 77 | self.sock.send("%s\n" % cmd) 78 | buf = "" 79 | while term not in buf: 80 | buf += self.sock.recv(4096) 81 | return buf.split(term)[0] 82 | 83 | def list(self): 84 | return self._command('list', '\n').split(' ') 85 | 86 | def fetch(self, service): 87 | data = self._command("fetch %s" % service, ".\n") 88 | if data.startswith('#'): 89 | raise Exception(data[2:]) 90 | values = {} 91 | for line in data.split('\n'): 92 | if line: 93 | k, v = line.split(' ', 1) 94 | values[k.split('.')[0]] = Decimal(v) 95 | return values 96 | -------------------------------------------------------------------------------- /munin/cassandra.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | import os 4 | import re 5 | import socket 6 | import time 7 | from subprocess import Popen, PIPE 8 | 9 | from munin import MuninPlugin 10 | 11 | space_re = re.compile(r"\s+") 12 | 13 | class MuninCassandraPlugin(MuninPlugin): 14 | category = "Cassandra" 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(MuninCassandraPlugin, self).__init__(*args, **kwargs) 18 | self.nodetool_path = os.environ["NODETOOL_PATH"] 19 | self.host = socket.gethostname() 20 | self.keyspaces = [x for x in os.environ.get('CASSANDRA_KEYSPACE', '').split(',') if x] 21 | 22 | def execute_nodetool(self, cmd): 23 | p = Popen([self.nodetool_path, "-host", self.host, cmd], stdout=PIPE) 24 | output = p.communicate()[0] 25 | return output 26 | 27 | def parse_cfstats(self, text): 28 | text = text.strip().split('\n') 29 | cfstats = {} 30 | cf = None 31 | for line in text: 32 | line = line.strip() 33 | if not line or line.startswith('-'): 34 | continue 35 | 36 | name, value = line.strip().split(': ', 1) 37 | if name == "Keyspace": 38 | ks = {'cf': {}} 39 | cf = None 40 | cfstats[value] = ks 41 | elif name == "Column Family": 42 | cf = {} 43 | ks['cf'][value] = cf 44 | elif cf is None: 45 | ks[name] = value 46 | else: 47 | cf[name] = value 48 | return cfstats 49 | 50 | def cfstats(self): 51 | return self.parse_cfstats(self.execute_nodetool("cfstats")) 52 | 53 | def cinfo(self): 54 | text = self.execute_nodetool("info") 55 | lines = text.strip().split('\n') 56 | token = lines[0] 57 | info = {} 58 | for l in lines[1:]: 59 | name, value = l.split(':') 60 | info[name.strip()] = value.strip() 61 | l_num, l_units = info['Load'].split(' ', 1) 62 | l_num = float(l_num) 63 | if l_units == "KB": 64 | scale = 1024 65 | elif l_units == "MB": 66 | scale = 1024*1024 67 | elif l_units == "GB": 68 | scale = 1024*1024*1024 69 | elif l_units == "TB": 70 | scale = 1024*1024*1024*1024 71 | info['Load'] = int(l_num * scale) 72 | info['token'] = token 73 | return info 74 | 75 | def tpstats(self): 76 | out = self.execute_nodetool("tpstats") 77 | tpstats = {} 78 | for line in out.strip().split('\n')[1:]: 79 | name, active, pending, completed = space_re.split(line) 80 | tpstats[name] = dict(active=int(active), pending=int(pending), completed=int(completed)) 81 | return tpstats 82 | -------------------------------------------------------------------------------- /munin/ddwrt.py: -------------------------------------------------------------------------------- 1 | 2 | # https://192.168.1.10/Info.live.htm 3 | 4 | import os 5 | import re 6 | import urllib2 7 | from munin import MuninPlugin 8 | 9 | class DDWrtPlugin(MuninPlugin): 10 | category = "Wireless" 11 | 12 | def __init__(self): 13 | super(DDWrtPlugin, self).__init__() 14 | self.root_url = os.environ.get('DDWRT_URL') or "http://192.168.1.1" 15 | self.url = self.root_url + "/Info.live.htm" 16 | 17 | def get_info(self): 18 | res = urllib2.urlopen(self.url) 19 | text = res.read() 20 | return dict( 21 | x[1:-1].split('::') 22 | for x in text.split('\n') 23 | ) 24 | -------------------------------------------------------------------------------- /munin/gearman.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import socket 6 | from munin import MuninPlugin 7 | 8 | worker_re = re.compile(r'^(?P\d+) (?P[\d\.]+) (?P[^\s]+) :\s?(?P.*)$') 9 | 10 | class MuninGearmanPlugin(MuninPlugin): 11 | category = "Gearman" 12 | 13 | def __init__(self): 14 | super(MuninGearmanPlugin, self).__init__() 15 | addr = os.environ.get('GM_SERVER') or "127.0.0.1" 16 | port = int(addr.split(':')[-1]) if ':' in addr else 4730 17 | host = addr.split(':')[0] 18 | self.addr = (host, port) 19 | self._sock = None 20 | 21 | def connect(self): 22 | if not self._sock: 23 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 24 | self._sock.connect(self.addr) 25 | return self._sock 26 | 27 | def disconnect(self): 28 | if self._sock: 29 | self._sock.close() 30 | 31 | def get_workers(self): 32 | sock = self.connect() 33 | sock.send("workers\n") 34 | buf = "" 35 | while ".\n" not in buf: 36 | buf += sock.recv(8192) 37 | 38 | info = [] 39 | for l in buf.split('\n'): 40 | if l.strip() == '.': 41 | break 42 | m = worker_re.match(l) 43 | i = m.groupdict() 44 | i['abilities'] = [x for x in i['abilities'].split(' ') if x] 45 | info.append(i) 46 | return info 47 | 48 | def get_status(self): 49 | sock = self.connect() 50 | sock.send("status\n") 51 | buf = "" 52 | while ".\n" not in buf: 53 | buf += sock.recv(8192) 54 | 55 | info = {} 56 | for l in buf.split('\n'): 57 | l = l.strip() 58 | if l == '.': 59 | break 60 | counts = l.split('\t') 61 | info[counts[0]] = dict( 62 | total = int(counts[1]), 63 | running = int(counts[2]), 64 | workers = int(counts[3]), 65 | ) 66 | return info 67 | -------------------------------------------------------------------------------- /munin/memcached.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import socket 5 | from munin import MuninPlugin 6 | 7 | class MuninMemcachedPlugin(MuninPlugin): 8 | category = "Memcached" 9 | 10 | def autoconf(self): 11 | try: 12 | self.get_stats() 13 | except socket.error: 14 | return False 15 | return True 16 | 17 | def get_stats(self): 18 | host = os.environ.get('MEMCACHED_HOST') or '127.0.0.1' 19 | port = int(os.environ.get('MEMCACHED_PORT') or '11211') 20 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 21 | s.connect((host, port)) 22 | s.send("stats\n") 23 | buf = "" 24 | while 'END\r\n' not in buf: 25 | buf += s.recv(1024) 26 | stats = (x.split(' ', 2) for x in buf.split('\r\n')) 27 | stats = dict((x[1], x[2]) for x in stats if x[0] == 'STAT') 28 | s.close() 29 | return stats 30 | 31 | def execute(self): 32 | stats = self.get_stats() 33 | values = {} 34 | for k, v in self.fields: 35 | try: 36 | value = stats[k] 37 | except KeyError: 38 | value = "U" 39 | values[k] = value 40 | return values 41 | -------------------------------------------------------------------------------- /munin/mongodb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | from munin import MuninPlugin 6 | 7 | class MuninMongoDBPlugin(MuninPlugin): 8 | dbname_in_args = False 9 | category = "MongoDB" 10 | 11 | def __init__(self): 12 | super(MuninMongoDBPlugin, self).__init__() 13 | 14 | self.dbname = None 15 | if self.dbname_in_args: 16 | self.dbname = sys.argv[0].rsplit('_', 1)[-1] 17 | if not self.dbname: 18 | self.dbname = os.environ.get('MONGODB_DATABASE') 19 | 20 | host = os.environ.get('MONGODB_SERVER') or 'localhost' 21 | if ':' in host: 22 | host, port = host.split(':') 23 | port = int(port) 24 | else: 25 | port = 27017 26 | self.server = (host, port) 27 | 28 | @property 29 | def connection(self): 30 | if not hasattr(self, '_connection'): 31 | import pymongo 32 | self._connection = pymongo.Connection(self.server[0], self.server[1], slave_okay=True) 33 | return self._connection 34 | 35 | @property 36 | def db(self): 37 | if not hasattr(self, '_db'): 38 | self._db = getattr(self.connection, self.dbname) 39 | return self._db 40 | 41 | def autoconf(self): 42 | return bool(self.connection) 43 | -------------------------------------------------------------------------------- /munin/mysql.py: -------------------------------------------------------------------------------- 1 | 2 | import os, sys, re 3 | from ConfigParser import SafeConfigParser 4 | from munin import MuninPlugin 5 | 6 | class MuninMySQLPlugin(MuninPlugin): 7 | dbname_in_args = False 8 | category = "MySQL" 9 | 10 | def __init__(self): 11 | super(MuninMySQLPlugin, self).__init__() 12 | 13 | self.dbname = ((sys.argv[0].rsplit('_', 1)[-1] if self.dbname_in_args else None) 14 | or os.environ.get('DATABASE') or self.default_table) 15 | 16 | self.conninfo = dict( 17 | user = "root", 18 | host = "localhost", 19 | ) 20 | 21 | cnfpath = "" 22 | 23 | m = re.findall(r"--defaults-file=([^\s]+)", os.environ.get("mysqlopts") or "") 24 | if m: 25 | cnfpath = m[0] 26 | 27 | if not cnfpath: 28 | m = re.findall(r"mysql_read_default_file=([^\s;:]+)", os.environ.get("mysqlconnection") or "") 29 | if m: 30 | cnfpath = m[0] 31 | 32 | if cnfpath: 33 | cnf = SafeConfigParser() 34 | cnf.read([cnfpath]) 35 | for section in ["client", "munin"]: 36 | if not cnf.has_section(section): 37 | continue 38 | for connkey, opt in [("user", "user"), ("passwd", "password"), ("host", "host"), ("port", "port")]: 39 | if cnf.has_option(section, opt): 40 | self.conninfo[connkey] = cnf.get(section, opt) 41 | 42 | for k in ('user', 'passwd', 'host', 'port'): 43 | # Use lowercase because that's what the existing mysql plugins do 44 | v = os.environ.get(k) 45 | if v: 46 | self.conninfo[k] = v 47 | 48 | def connection(self): 49 | if not hasattr(self, '_connection'): 50 | import MySQLdb 51 | self._connection = MySQLdb.connect(**self.conninfo) 52 | return self._connection 53 | 54 | def cursor(self): 55 | return self.connection().cursor() 56 | 57 | def autoconf(self): 58 | return bool(self.connection()) 59 | -------------------------------------------------------------------------------- /munin/nginx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import urllib 6 | from munin import MuninPlugin 7 | 8 | class MuninNginxPlugin(MuninPlugin): 9 | category = "Nginx" 10 | 11 | status_re = re.compile( 12 | r"Active connections:\s+(?P\d+)\s+" 13 | r"server accepts handled requests\s+" 14 | r"(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+" 15 | r"Reading: (?P\d+) Writing: (?P\d+) Waiting: (?P\d+)") 16 | 17 | def __init__(self): 18 | super(MuninNginxPlugin, self).__init__() 19 | self.url = os.environ.get('NX_STATUS_URL') or "http://localhost/nginx_status" 20 | 21 | def autoconf(self): 22 | return bool(self.get_status()) 23 | 24 | def get_status(self): 25 | return self.status_re.search(urllib.urlopen(self.url).read()).groupdict() 26 | -------------------------------------------------------------------------------- /munin/pgbouncer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from munin.postgres import MuninPostgresPlugin 3 | 4 | class MuninPgBouncerPlugin(MuninPostgresPlugin): 5 | dbname_in_args = False 6 | default_table = "pgbouncer" 7 | category = "PgBouncer" 8 | 9 | def __init__(self, *args, **kwargs): 10 | super(MuninPgBouncerPlugin, self).__init__(*args, **kwargs) 11 | self.dbwatched = sys.argv[0].rsplit('_', 1)[-1] 12 | 13 | def connection(self): 14 | if not hasattr(self, '_connection'): 15 | import psycopg2 16 | self._connection = psycopg2.connect(self.dsn) 17 | self._connection.set_isolation_level(0) 18 | return self._connection 19 | 20 | def execute(self): 21 | cursor = self.cursor() 22 | cursor.execute(self.command) 23 | columns = [column[0] for column in cursor.description] 24 | 25 | totals = dict.fromkeys((field[0] for field in self.fields), 0) 26 | for row in cursor: 27 | row_dict = dict(zip(columns, row)) 28 | if row_dict['database'] in (self.dbwatched, self.dbwatched + '\x00'): 29 | for field in self.fields: 30 | totals[field[0]] += row_dict[field[0]] 31 | 32 | return dict((field[0], totals[field[0]]) for field in self.fields) 33 | 34 | -------------------------------------------------------------------------------- /munin/postgres.py: -------------------------------------------------------------------------------- 1 | 2 | import os, sys 3 | from munin import MuninPlugin 4 | 5 | class MuninPostgresPlugin(MuninPlugin): 6 | dbname_in_args = False 7 | category = "PostgreSQL" 8 | default_table = "template1" 9 | 10 | def __init__(self): 11 | super(MuninPostgresPlugin, self).__init__() 12 | 13 | self.dbname = ((sys.argv[0].rsplit('_', 1)[-1] if self.dbname_in_args else None) 14 | or os.environ.get('PGDATABASE') or self.default_table) 15 | dsn = ["dbname='%s'" % self.dbname] 16 | for k in ('user', 'password', 'host', 'port'): 17 | v = os.environ.get('DB%s' % k.upper()) 18 | if v: 19 | dsn.append("db%s='%s'" % (k, v)) 20 | self.dsn = ' '.join(dsn) 21 | 22 | def connection(self): 23 | if not hasattr(self, '_connection'): 24 | import psycopg2 25 | self._connection = psycopg2.connect(self.dsn) 26 | return self._connection 27 | 28 | def cursor(self): 29 | return self.connection().cursor() 30 | 31 | def autoconf(self): 32 | return bool(self.connection()) 33 | 34 | def tables(self): 35 | if not hasattr(self, '_tables'): 36 | c = self.cursor() 37 | c.execute( 38 | "SELECT c.relname FROM pg_catalog.pg_class c" 39 | " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace" 40 | " WHERE c.relkind IN ('r','')" 41 | " AND n.nspname NOT IN ('pg_catalog', 'pg_toast')" 42 | " AND pg_catalog.pg_table_is_visible(c.oid)") 43 | self._tables = [r[0] for r in c.fetchall()] 44 | return self._tables 45 | -------------------------------------------------------------------------------- /munin/redis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import socket 5 | from munin import MuninPlugin 6 | 7 | class MuninRedisPlugin(MuninPlugin): 8 | category = "Redis" 9 | 10 | def autoconf(self): 11 | try: 12 | self.get_info() 13 | except socket.error: 14 | return False 15 | return True 16 | 17 | def get_info(self): 18 | host = os.environ.get('REDIS_HOST') or '127.0.0.1' 19 | port = int(os.environ.get('REDIS_PORT') or '6379') 20 | if host.startswith('/'): 21 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 22 | s.connect(host) 23 | else: 24 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 25 | s.connect((host, port)) 26 | s.send("*1\r\n$4\r\ninfo\r\n") 27 | buf = "" 28 | while '\r\n' not in buf: 29 | buf += s.recv(1024) 30 | l, buf = buf.split('\r\n', 1) 31 | if l[0] != "$": 32 | s.close() 33 | raise Exception("Protocol error") 34 | remaining = int(l[1:]) - len(buf) 35 | if remaining > 0: 36 | buf += s.recv(remaining) 37 | s.close() 38 | return dict(x.split(':', 1) for x in buf.split('\r\n') if ':' in x) 39 | 40 | def execute(self): 41 | stats = self.get_info() 42 | values = {} 43 | for k, v in self.fields: 44 | try: 45 | value = stats[k] 46 | except KeyError: 47 | value = "U" 48 | values[k] = value 49 | return values 50 | -------------------------------------------------------------------------------- /munin/riak.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | try: 4 | import json 5 | except ImportError: 6 | import simplejson as json 7 | import os 8 | import sys 9 | import urllib2 10 | from munin import MuninPlugin 11 | 12 | class MuninRiakPlugin(MuninPlugin): 13 | category = "Riak" 14 | 15 | def __init__(self): 16 | super(MuninRiakPlugin, self).__init__() 17 | 18 | host = os.environ.get('RIAK_HOST') or 'localhost' 19 | if ':' in host: 20 | host, port = host.split(':') 21 | port = int(port) 22 | else: 23 | port = 8098 24 | self.host = "%s:%s" % (host, port) 25 | 26 | def get_status(self): 27 | res = urllib2.urlopen("http://%s/stats" % (self.host)) 28 | return json.loads(res.read()) 29 | -------------------------------------------------------------------------------- /plugins/aws_elb_latency: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime 4 | import os 5 | import sys 6 | 7 | import boto 8 | from boto.ec2.cloudwatch import CloudWatchConnection 9 | 10 | from munin import MuninPlugin 11 | 12 | class AWSCloudWatchELBLatencyPlugin(MuninPlugin): 13 | category = "AWS" 14 | args = "-l 0 --base 1000" 15 | vlabel = "Seconds" 16 | info = "Show latency for requests" 17 | 18 | @property 19 | def title(self): 20 | return "Seconds of latency for ELBs" 21 | 22 | @property 23 | def fields(self): 24 | return [ 25 | ("maximum", dict( 26 | label = "Maximum latency", 27 | type = "GAUGE", 28 | )), 29 | ("minimum", dict( 30 | label = "Minimum latency", 31 | type = "GAUGE", 32 | )), 33 | ("average", dict( 34 | label = "Average latency", 35 | type = "GAUGE", 36 | )), 37 | ] 38 | 39 | def __init__(self): 40 | self.api_key = os.environ['AWS_KEY'] 41 | self.secret_key = os.environ['AWS_SECRET'] 42 | 43 | def execute(self): 44 | minutes = 5 45 | end_date = datetime.datetime.utcnow() 46 | start_date = end_date - datetime.timedelta(minutes=minutes) 47 | cw = CloudWatchConnection(self.api_key, self.secret_key) 48 | metrics = cw.get_metric_statistics(5*60, start_date, end_date, "Latency", "AWS/ELB", ["Average", "Minimum", "Maximum"]) 49 | m = metrics[0] 50 | return dict( 51 | maximum = m['Maximum'], 52 | minimum = m['Minimum'], 53 | average = m['Average'], 54 | ) 55 | 56 | if __name__ == "__main__": 57 | AWSCloudWatchELBLatencyPlugin().run() 58 | -------------------------------------------------------------------------------- /plugins/aws_elb_requests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime 4 | import os 5 | import sys 6 | 7 | import boto 8 | from boto.ec2.cloudwatch import CloudWatchConnection 9 | 10 | from munin import MuninPlugin 11 | 12 | class AWSCloudWatchELBRequestsPlugin(MuninPlugin): 13 | category = "AWS" 14 | args = "-l 0 --base 1000" 15 | vlabel = "Requests/sec" 16 | info = "Show number of requests per second" 17 | 18 | @property 19 | def title(self): 20 | return "Requests/sec for ELBs '%s'" % ",".join(self.elb_names) 21 | 22 | @property 23 | def fields(self): 24 | return [ 25 | (n, dict( 26 | label = "requests on ELB %s" % n, 27 | type = "ABSOLUTE", 28 | )) for n in self.elb_names 29 | ] 30 | 31 | def __init__(self): 32 | self.api_key = os.environ['AWS_KEY'] 33 | self.secret_key = os.environ['AWS_SECRET'] 34 | self.elb_names = (sys.argv[0].rsplit('_', 1)[-1] or os.environ['ELB_NAME']).split(',') 35 | 36 | def execute(self): 37 | minutes = 5 38 | end_date = datetime.datetime.utcnow() 39 | start_date = end_date - datetime.timedelta(minutes=minutes) 40 | cw = CloudWatchConnection(self.api_key, self.secret_key) 41 | return dict( 42 | (n, sum(x['Sum'] for x in cw.get_metric_statistics(60, start_date, end_date, "RequestCount", "AWS/ELB", ["Sum"]))) 43 | for n in self.elb_names 44 | ) 45 | 46 | if __name__ == "__main__": 47 | AWSCloudWatchELBRequestsPlugin().run() 48 | -------------------------------------------------------------------------------- /plugins/aws_sqs_queue_length_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | import boto 7 | from boto.sqs.connection import SQSConnection 8 | 9 | from munin import MuninPlugin 10 | 11 | class AWSSQSQueueLengthPlugin(MuninPlugin): 12 | category = "AWS" 13 | args = "-l 0 --base 1000" 14 | vlabel = "Messages" 15 | info = "Show number of messages in an SQS queue" 16 | 17 | @property 18 | def title(self): 19 | return "Length of AWS SQS queues '%s'" % ",".join(self.queues) 20 | 21 | @property 22 | def fields(self): 23 | return [ 24 | (q, dict( 25 | label = "messages in %s" % q, 26 | type = "GAUGE", 27 | min = "0", 28 | )) for q in self.queues 29 | ] 30 | 31 | def __init__(self): 32 | self.api_key = os.environ['AWS_KEY'] 33 | self.secret_key = os.environ['AWS_SECRET'] 34 | self.queues = (sys.argv[0].rsplit('_', 1)[-1] or os.environ['SQS_QUEUES']).split(',') 35 | 36 | def execute(self): 37 | conn = SQSConnection(self.api_key, self.secret_key) 38 | return dict( 39 | (qname, conn.get_queue(qname).count()) 40 | for qname in self.queues 41 | ) 42 | 43 | if __name__ == "__main__": 44 | AWSSQSQueueLengthPlugin().run() 45 | -------------------------------------------------------------------------------- /plugins/cassandra_cfcounts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin.cassandra import MuninCassandraPlugin 5 | 6 | class CassandraCFCountsPlugin(MuninCassandraPlugin): 7 | title = "read/write rate" 8 | args = "--base 1000 -l 0" 9 | vlabel = "ops per ${graph_period}" 10 | 11 | @property 12 | def fields(self): 13 | fs = [] 14 | cfstats = self.cfstats() 15 | for kf, kfstats in cfstats.items(): 16 | if not self.keyspaces or kf not in self.keyspaces: 17 | continue 18 | for cf, cfstats in kfstats['cf'].items(): 19 | name = "%s_%s_reads" % (kf, cf) 20 | label = "%s.%s reads" % (kf, cf) 21 | fs.append((name, dict( 22 | label = label, 23 | info = label, 24 | type = "DERIVE", 25 | min = "0", 26 | ))) 27 | name = "%s_%s_writes" % (kf, cf) 28 | label = "%s.%s writes" % (kf, cf) 29 | fs.append((name, dict( 30 | label = label, 31 | info = label, 32 | type = "DERIVE", 33 | min = "0", 34 | ))) 35 | return fs 36 | 37 | def execute(self): 38 | cfstats = self.cfstats() 39 | values = {} 40 | for kf, kfstats in cfstats.items(): 41 | if not self.keyspaces or kf not in self.keyspaces: 42 | continue 43 | for cf, cfstats in kfstats['cf'].items(): 44 | name = "%s_%s" % (kf, cf) 45 | values["%s_reads" % name] = cfstats['Read Count'] 46 | values["%s_writes" % name] = cfstats['Write Count'] 47 | return values 48 | 49 | if __name__ == "__main__": 50 | CassandraCFCountsPlugin().run() 51 | -------------------------------------------------------------------------------- /plugins/cassandra_key_cache_ratio: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin.cassandra import MuninCassandraPlugin 5 | 6 | class CassandraKeyCacheRatioPlugin(MuninCassandraPlugin): 7 | title = "key cache hit ratio" 8 | args = "--base 1000 -l 0" 9 | vlabel = "ratio" 10 | scale = False 11 | 12 | @property 13 | def fields(self): 14 | fs = [] 15 | cfstats = self.cfstats() 16 | for kf, kfstats in cfstats.items(): 17 | if not self.keyspaces or kf not in self.keyspaces: 18 | continue 19 | for cf, cfstats in kfstats['cf'].items(): 20 | name = "%s_%s" % (kf, cf) 21 | label = "%s.%s" % (kf, cf) 22 | fs.append((name, dict( 23 | label = label, 24 | info = label, 25 | type = "GAUGE", 26 | max = "1", 27 | min = "0", 28 | ))) 29 | return fs 30 | 31 | def execute(self): 32 | cfstats = self.cfstats() 33 | values = {} 34 | for kf, kfstats in cfstats.items(): 35 | if not self.keyspaces or kf not in self.keyspaces: 36 | continue 37 | for cf, cfstats in kfstats['cf'].items(): 38 | if cfstats['Key cache hit rate'] != 'NaN': 39 | values["%s_%s" % (kf, cf)] = cfstats['Key cache hit rate'] 40 | else: 41 | values["%s_%s" % (kf, cf)] = "U" 42 | return values 43 | 44 | if __name__ == "__main__": 45 | CassandraKeyCacheRatioPlugin().run() 46 | -------------------------------------------------------------------------------- /plugins/cassandra_latency: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin.cassandra import MuninCassandraPlugin 5 | 6 | class CassandraLatencyPlugin(MuninCassandraPlugin): 7 | title = "read/write latency" 8 | args = "--base 1000 -l 0" 9 | vlabel = "seconds" 10 | 11 | @property 12 | def fields(self): 13 | fs = [] 14 | cfstats = self.cfstats() 15 | for kf, kfstats in cfstats.items(): 16 | if not self.keyspaces or kf not in self.keyspaces: 17 | continue 18 | for cf, cfstats in kfstats['cf'].items(): 19 | name = "%s_%s_read" % (kf, cf) 20 | label = "%s.%s read latency" % (kf, cf) 21 | fs.append((name, dict( 22 | label = label, 23 | info = label, 24 | type = "GAUGE", 25 | min = "0", 26 | ))) 27 | name = "%s_%s_write" % (kf, cf) 28 | label = "%s.%s write latency" % (kf, cf) 29 | fs.append((name, dict( 30 | label = label, 31 | info = label, 32 | type = "GAUGE", 33 | min = "0", 34 | ))) 35 | return fs 36 | 37 | def execute(self): 38 | cfstats = self.cfstats() 39 | values = {} 40 | for kf, kfstats in cfstats.items(): 41 | if not self.keyspaces or kf not in self.keyspaces: 42 | continue 43 | for cf, cfstats in kfstats['cf'].items(): 44 | name = "%s_%s" % (kf, cf) 45 | for k, n in (('read', 'Read Latency'), ('write', 'Write Latency')): 46 | latency = cfstats[n].split(' ')[0] 47 | if latency == 'NaN': 48 | latency = 'U' 49 | else: 50 | latency = float(latency) / 1000 51 | values["%s_%s" % (name, k)] = latency 52 | return values 53 | 54 | if __name__ == "__main__": 55 | CassandraLatencyPlugin().run() 56 | -------------------------------------------------------------------------------- /plugins/cassandra_load: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin.cassandra import MuninCassandraPlugin 5 | 6 | class CassandraLoadPlugin(MuninCassandraPlugin): 7 | title = "load (data stored in node)" 8 | args = "--base 1024 -l 0" 9 | vlabel = "bytes" 10 | fields = [ 11 | ('load', dict( 12 | label = "load", 13 | info = "data stored in node", 14 | type = "GAUGE", 15 | min = "0", 16 | )), 17 | ] 18 | 19 | def execute(self): 20 | info = self.cinfo() 21 | return dict(load = info['Load']) 22 | 23 | if __name__ == "__main__": 24 | CassandraLoadPlugin().run() 25 | -------------------------------------------------------------------------------- /plugins/cassandra_pending: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin.cassandra import MuninCassandraPlugin 5 | 6 | class CassandraPendingPlugin(MuninCassandraPlugin): 7 | title = "thread pool pending tasks" 8 | args = "--base 1000 -l 0" 9 | vlabel = "pending tasks" 10 | scale = False 11 | 12 | @property 13 | def fields(self): 14 | tpstats = self.tpstats() 15 | fs = [] 16 | for name, stats in tpstats.items(): 17 | fs.append((name.lower().replace('-', '_'), dict( 18 | label = name, 19 | info = name, 20 | type = "GAUGE", 21 | min = "0", 22 | ))) 23 | return fs 24 | 25 | def execute(self): 26 | tpstats = self.tpstats() 27 | values = {} 28 | for name, stats in tpstats.items(): 29 | values[name.lower().replace('-', '_')] = stats['pending'] 30 | return values 31 | 32 | if __name__ == "__main__": 33 | CassandraPendingPlugin().run() 34 | -------------------------------------------------------------------------------- /plugins/cpu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | from munin import MuninPlugin 6 | 7 | class CPUPlugin(MuninPlugin): 8 | title = "CPU usage" 9 | args = "--base 1000 -r --lower-limit 0 --upper-limit 100" #" --upper-limit $graphlimit" 10 | vlabel = "%" 11 | category = "system" 12 | period = "second" # TODO: I think this is the default anyway 13 | info = "This graph shows how CPU time is spent." 14 | 15 | @property 16 | def order(self): 17 | return ("system user nice idle " + self.extinfo).strip() 18 | 19 | @property 20 | def extinfo(self): 21 | if hasattr(self, '_extinfo'): 22 | return self._extinfo 23 | 24 | fp = open("/proc/stat", "rb") 25 | stat = fp.read() 26 | fp.close() 27 | if bool(re.match(r"^cpu +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+", stat)): 28 | self._extinfo = "iowait irq softirq" 29 | else: 30 | self._extinfo = "" 31 | 32 | return self._extinfo 33 | 34 | @property 35 | def fields(self): 36 | warning = os.environ.get('load_warn', 10) 37 | critical = os.environ.get('load_crit', 120) 38 | fields = [ 39 | ("system", dict( 40 | label = "system" 41 | draw = "AREA", 42 | max = max, 43 | min = min, 44 | type = "DERIVE", 45 | warning = syswarning, 46 | critical = syscritical, 47 | info = "CPU time spent by the kernel in system activities", 48 | )), 49 | ("user", dict( 50 | label = "user" 51 | draw = "STACK", 52 | max = max, 53 | min = "0", 54 | type = "DERIVE", 55 | warning = usrwarning, 56 | info = "CPU time spent by normal programs and daemons", 57 | )) 58 | ] 59 | return [("load", dict( 60 | label = "load", 61 | info = 'The load average of the machine describes how many processes are in the run-queue (scheduled to run "immediately").', 62 | type = "GAUGE", 63 | min = "0", 64 | warning = str(warning), 65 | critical = str(critical)))] 66 | 67 | def execute(self): 68 | if os.path.exists("/proc/loadavg"): 69 | loadavg = open("/proc/loadavg", "r").read().strip().split(' ') 70 | else: 71 | from subprocess import Popen, PIPE 72 | output = Popen(["uptime"], stdout=PIPE).communicate()[0] 73 | loadavg = output.rsplit(':', 1)[1].strip().split(' ')[:3] 74 | print loadavg 75 | print "load.value %s" % loadavg[1] 76 | 77 | if __name__ == "__main__": 78 | CPUPlugin().run() 79 | 80 | if (`egrep '^cpu +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+' /proc/stat 2>/dev/null >/dev/null`) 81 | then 82 | extinfo="iowait irq softirq" 83 | fi 84 | 85 | if [ "$1" = "config" ]; then 86 | 87 | NCPU=$(egrep '^cpu[0-9]+ ' /proc/stat | wc -l) 88 | PERCENT=$(($NCPU * 100)) 89 | MAX=$(($NCPU * 100)) 90 | if [ "$scaleto100" = "yes" ]; then 91 | graphlimit=100 92 | else 93 | graphlimit=$PERCENT 94 | fi 95 | SYSWARNING=`expr $PERCENT '*' 30 / 100` 96 | SYSCRITICAL=`expr $PERCENT '*' 50 / 100` 97 | USRWARNING=`expr $PERCENT '*' 80 / 100` 98 | echo 'nice.label nice' 99 | echo 'nice.draw STACK' 100 | echo 'nice.min 0' 101 | echo "nice.max $MAX" 102 | echo 'nice.type DERIVE' 103 | echo 'nice.info CPU time spent by nice(1)d programs' 104 | echo 'idle.label idle' 105 | echo 'idle.draw STACK' 106 | echo 'idle.min 0' 107 | echo "idle.max $MAX" 108 | echo 'idle.type DERIVE' 109 | echo 'idle.info Idle CPU time' 110 | if [ "$scaleto100" = "yes" ]; then 111 | echo "system.cdef system,$NCPU,/" 112 | echo "user.cdef user,$NCPU,/" 113 | echo "nice.cdef nice,$NCPU,/" 114 | echo "idle.cdef idle,$NCPU,/" 115 | fi 116 | if [ ! -z "$extinfo" ] 117 | then 118 | echo 'iowait.label iowait' 119 | echo 'iowait.draw STACK' 120 | echo 'iowait.min 0' 121 | echo "iowait.max $MAX" 122 | echo 'iowait.type DERIVE' 123 | echo 'iowait.info CPU time spent waiting for I/O operations to finish' 124 | echo 'irq.label irq' 125 | echo 'irq.draw STACK' 126 | echo 'irq.min 0' 127 | echo "irq.max $MAX" 128 | echo 'irq.type DERIVE' 129 | echo 'irq.info CPU time spent handling interrupts' 130 | echo 'softirq.label softirq' 131 | echo 'softirq.draw STACK' 132 | echo 'softirq.min 0' 133 | echo "softirq.max $MAX" 134 | echo 'softirq.type DERIVE' 135 | echo 'softirq.info CPU time spent handling "batched" interrupts' 136 | if [ "$scaleto100" = "yes" ]; then 137 | echo "iowait.cdef iowait,$NCPU,/" 138 | echo "irq.cdef irq,$NCPU,/" 139 | echo "softirq.cdef softirq,$NCPU,/" 140 | fi 141 | fi 142 | exit 0 143 | fi 144 | 145 | HZ=`getconf CLK_TCK` 146 | 147 | if [ ! -z "$extinfo" ] 148 | then 149 | awk -v HZ=$HZ 'BEGIN { factor=100/HZ } /^cpu / { for (i=2; i<=8; i++) { $i = int($i * factor) }; print "user.value " $2 "\nnice.value " $3 "\nsystem.value " $4 "\nidle.value " $5 "\niowait.value " $6 "\nirq.value " $7 "\nsoftirq.value " $8 }' < /proc/stat 150 | 151 | else 152 | awk -v HZ=$HZ 'BEGIN { factor=100/HZ } /^cpu / { for (i=2; i<=5; i++) { $i = int($i * factor) }; print "user.value " $2 "\nnice.value " $3 "\nsystem.value " $4 "\nidle.value " $5 }' < /proc/stat 153 | fi 154 | -------------------------------------------------------------------------------- /plugins/ddwrt_wl_rate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.ddwrt import DDWrtPlugin 4 | 5 | class DDWrtWirelessRate(DDWrtPlugin): 6 | title = "Wireless rate" 7 | args = "--base 1000 -l 0" 8 | vlabel = "Mbps" 9 | info = "rate" 10 | fields = ( 11 | ('rate', dict( 12 | label = "rate", 13 | info = "rate", 14 | type = "GAUGE", 15 | )), 16 | ) 17 | 18 | def execute(self): 19 | info = self.get_info() 20 | return dict(rate=info['wl_rate'].split(' ')[0]) 21 | 22 | if __name__ == "__main__": 23 | DDWrtWirelessRate().run() 24 | -------------------------------------------------------------------------------- /plugins/ddwrt_wl_signal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.ddwrt import DDWrtPlugin 4 | 5 | class DDWrtWirelessSignalPlugin(DDWrtPlugin): 6 | title = "Wireless signal" 7 | args = "--base 1000 -l 0" 8 | vlabel = "units" 9 | info = "signal quality" 10 | fields = ( 11 | ('signal', dict( 12 | label = "signal", 13 | info = "signal", 14 | type = "GAUGE", 15 | )), 16 | ('noise', dict( 17 | label = "noise", 18 | info = "noise", 19 | type = "GAUGE", 20 | )), 21 | ) 22 | 23 | def execute(self): 24 | info = self.get_info() 25 | active = info['active_wireless'] 26 | signal, noise = active.split(',')[1:3] 27 | return dict( 28 | signal = signal[1:-1], 29 | noise = noise[1:-1], 30 | ) 31 | 32 | if __name__ == "__main__": 33 | DDWrtWirelessSignalPlugin().run() 34 | -------------------------------------------------------------------------------- /plugins/gearman_connections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.gearman import MuninGearmanPlugin 4 | 5 | class MuninGearmanConnectionsPlugin(MuninGearmanPlugin): 6 | title = "Gearman Connections" 7 | args = "--base 1000" 8 | vlabel = "Connections" 9 | fields = ( 10 | ('total', dict( 11 | label = "Total", 12 | type = "GAUGE", 13 | draw = "LINE2", 14 | min = "0", 15 | )), 16 | ('workers', dict( 17 | label = "Workers", 18 | type = "GAUGE", 19 | draw = "LINE2", 20 | min = "0", 21 | )), 22 | ('clients', dict( 23 | label = "Clients", 24 | type = "GAUGE", 25 | draw = "LINE2", 26 | min = "0", 27 | )), 28 | ) 29 | 30 | def execute(self): 31 | workers = self.get_workers() 32 | return dict( 33 | total = len(workers), 34 | workers = sum(1 for x in workers if x['abilities']), 35 | clients = sum(1 for x in workers if not x['abilities']), 36 | ) 37 | 38 | if __name__ == "__main__": 39 | MuninGearmanConnectionsPlugin().run() 40 | -------------------------------------------------------------------------------- /plugins/gearman_queues: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin.gearman import MuninGearmanPlugin 5 | 6 | class MuninGearmanQueuesPlugin(MuninGearmanPlugin): 7 | title = "Gearman Queues" 8 | args = "--base 1000" 9 | vlabel = "tasks" 10 | 11 | _info_keys = ('total', 'running', 'workers') 12 | 13 | def __init__(self): 14 | super(MuninGearmanQueuesPlugin, self).__init__() 15 | self.queues = os.environ['GEARMAN_QUEUES'].split(',') 16 | 17 | @property 18 | def fields(self): 19 | fs = [] 20 | for q in self.queues: 21 | for k in self._info_keys: 22 | label = "%s %s" % (q, k) 23 | fs.append(("%s_%s" % (q.replace('.', '_'), k), dict( 24 | label = label, 25 | info = label, 26 | type = "GAUGE", 27 | ))) 28 | return fs 29 | 30 | def execute(self): 31 | status = self.get_status() 32 | values = {} 33 | for q in self.queues: 34 | if q in status: 35 | for k in self._info_keys: 36 | values["%s_%s" % (q.replace('.', '_'), k)] = status[q][k] 37 | return values 38 | 39 | if __name__ == "__main__": 40 | MuninGearmanQueuesPlugin().run() 41 | -------------------------------------------------------------------------------- /plugins/hookbox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import json 5 | import urllib 6 | import urllib2 7 | 8 | from munin import MuninPlugin 9 | 10 | 11 | 12 | class HookboxPlugin(MuninPlugin): 13 | title = 'hookbox' 14 | args = "--base 1000" 15 | vlabel = "Y" 16 | info = "Subscibed users" 17 | scale = False 18 | 19 | def get_channels(self): 20 | return os.environ.get('HOOKBOX_CHANNELS', '').split(',') 21 | 22 | def get_url(self): 23 | return os.environ.get('HOOKBOX_URL', 'http://localhost:8001/rest') 24 | 25 | def get_secret(self): 26 | return os.environ.get('HOOKBOX_SECRET', '') 27 | 28 | 29 | @property 30 | def fields(self): 31 | return ( 32 | (channel, dict( 33 | label=channel, 34 | info="%s - users" % channel, 35 | type="GAUGE", 36 | )) 37 | for channel in self.get_channels() 38 | ) 39 | 40 | def get_channel_info(self, channel_name): 41 | values = { 42 | 'channel_name': channel_name, 43 | 'secret': self.get_secret(), 44 | } 45 | req = urllib2.Request("%s/get_channel_info?%s" % (self.get_url(), urllib.urlencode(values))) 46 | resp = urllib2.urlopen(req) 47 | return json.loads(resp.read()) 48 | 49 | def get_subscribers(self, channel_name): 50 | try: 51 | return len(self.get_channel_info(channel_name)[1]['subscribers']) 52 | except (urllib2.URLError, KeyError), e: 53 | return 'U' 54 | 55 | def execute(self): 56 | return dict( 57 | (channel_name, self.get_subscribers(channel_name)) 58 | for channel_name in self.get_channels() 59 | ) 60 | 61 | if __name__ == "__main__": 62 | HookboxPlugin().run() 63 | -------------------------------------------------------------------------------- /plugins/indextank_index_size: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from munin import MuninPlugin 5 | from indextank import ApiClient 6 | 7 | class IndexTankIndexSizePlugin(MuninPlugin): 8 | title = "Indextank Index Size" 9 | args = "--base 1000 -l 0" 10 | vlabel = "documents" 11 | category = "indextank" 12 | 13 | @property 14 | def fields(self): 15 | indexes = self.get_indexes() 16 | fi = [] 17 | for idx in indexes: 18 | if idx.status() != "LIVE": 19 | continue 20 | index_name = idx._IndexClient__index_url.rsplit('/', 1)[-1] 21 | fi.append(("%s_index_size" % index_name.lower(), dict( 22 | label = "Documents in index " + index_name, 23 | info = 'The number of documents stored in the index %s.' % index_name, 24 | type = "GAUGE", 25 | min = "0"))) 26 | return fi 27 | 28 | def get_indexes(self): 29 | url = os.environ.get('INDEXTANK_API_URL') 30 | if not url: 31 | raise Exception("INDEXTANK_API_URL undefined") 32 | cli = ApiClient(url) 33 | return cli.list_indexes() 34 | 35 | def execute(self): 36 | indexes = self.get_indexes() 37 | values = {} 38 | for idx in indexes: 39 | if idx.status() != "LIVE": 40 | continue 41 | index_name = idx._IndexClient__index_url.rsplit('/', 1)[-1] 42 | values["%s_index_size" % index_name.lower()] = idx.get_size() 43 | return values 44 | 45 | if __name__ == "__main__": 46 | IndexTankIndexSizePlugin().run() 47 | -------------------------------------------------------------------------------- /plugins/loadavg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Load average plugin for Munin. 5 | 6 | Based on the default "load" plugin in Munin 7 | 8 | First tries reading from /proc/loadavg if it exists. Otherwise, execute 9 | `uptime` and parse out the load average. 10 | """ 11 | 12 | import os 13 | from munin import MuninPlugin 14 | 15 | class LoadAVGPlugin(MuninPlugin): 16 | title = "Load average" 17 | args = "--base 1000 -l 0" 18 | vlabel = "load" 19 | scale = False 20 | category = "system" 21 | 22 | @property 23 | def fields(self): 24 | warning = os.environ.get('load_warn', 10) 25 | critical = os.environ.get('load_crit', 120) 26 | return [("load", dict( 27 | label = "load", 28 | info = 'The load average of the machine describes how many processes are in the run-queue (scheduled to run "immediately").', 29 | type = "GAUGE", 30 | min = "0", 31 | warning = str(warning), 32 | critical = str(critical)))] 33 | 34 | def execute(self): 35 | if os.path.exists("/proc/loadavg"): 36 | loadavg = open("/proc/loadavg", "r").read().strip().split(' ') 37 | else: 38 | from subprocess import Popen, PIPE 39 | output = Popen(["uptime"], stdout=PIPE).communicate()[0] 40 | loadavg = output.rsplit(':', 1)[1].strip().split(' ')[:3] 41 | return dict(load=loadavg[1]) 42 | 43 | if __name__ == "__main__": 44 | LoadAVGPlugin().run() 45 | -------------------------------------------------------------------------------- /plugins/memcached_bytes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.memcached import MuninMemcachedPlugin 4 | 5 | class MuninMemcachedBytesPlugin(MuninMemcachedPlugin): 6 | title = "Memcached bytes read/written stats" 7 | args = "--base 1024" 8 | vlabel = "bytes read (-) / written (+) per ${graph_period}" 9 | info = "bytes read/writter stats" 10 | order = ("bytes_read", "bytes_written") 11 | fields = ( 12 | ('bytes_read', dict( 13 | label = "bytes read", 14 | type = "COUNTER", 15 | graph = "no", 16 | )), 17 | ('bytes_written', dict( 18 | label = "Bps", 19 | info = "Bytes read/written", 20 | type = "COUNTER", 21 | negative = "bytes_read", 22 | )), 23 | ) 24 | 25 | if __name__ == "__main__": 26 | MuninMemcachedBytesPlugin().run() 27 | -------------------------------------------------------------------------------- /plugins/memcached_connections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.memcached import MuninMemcachedPlugin 4 | 5 | class MuninMemcachedConnectionsPlugin(MuninMemcachedPlugin): 6 | title = "Memcached connections stats" 7 | args = "--base 1000" 8 | vlabel = "Connections" 9 | info = "connections stats" 10 | fields = ( 11 | ('curr_connections', dict( 12 | label = "connections", 13 | info = "connections", 14 | type = "GAUGE", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninMemcachedConnectionsPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/memcached_curr_bytes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.memcached import MuninMemcachedPlugin 4 | 5 | class MuninMemcachedCurrBytesPlugin(MuninMemcachedPlugin): 6 | title = "Memcached current bytes stored" 7 | args = "--base 1024" 8 | vlabel = "bytes" 9 | info = "bytes memory in use" 10 | fields = ( 11 | ('bytes', dict( 12 | label = "live byte", 13 | info = "live bytes", 14 | type = "GAUGE", 15 | )), 16 | ('limit_maxbytes', dict( 17 | label = "max live byte", 18 | info = "max live bytes", 19 | type = "GAUGE", 20 | )), 21 | ) 22 | 23 | if __name__ == "__main__": 24 | MuninMemcachedCurrBytesPlugin().run() 25 | -------------------------------------------------------------------------------- /plugins/memcached_curr_items: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.memcached import MuninMemcachedPlugin 4 | 5 | class MuninMemcachedCurrentItemsPlugin(MuninMemcachedPlugin): 6 | title = "Memcached current items stats" 7 | args = "--base 1000" 8 | vlabel = "Current Items" 9 | info = "current items stats" 10 | fields = ( 11 | ('curr_items', dict( 12 | label = "items", 13 | info = "number current items", 14 | type = "GAUGE", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninMemcachedCurrentItemsPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/memcached_items: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.memcached import MuninMemcachedPlugin 4 | 5 | class MuninMemcachedItemsPlugin(MuninMemcachedPlugin): 6 | title = "Memcached new items stats" 7 | args = "--base 1000" 8 | vlabel = "Items" 9 | info = "items stats" 10 | fields = ( 11 | ('total_items', dict( 12 | label = "items", 13 | info = "number of new items", 14 | type = "COUNTER", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninMemcachedItemsPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/memcached_queries: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.memcached import MuninMemcachedPlugin 4 | 5 | class MuninMemcachedQueriesPlugin(MuninMemcachedPlugin): 6 | title = "Memcached query stats" 7 | args = "--base 1000" 8 | vlabel = "queries per ${graph_period}" 9 | info = "get/set stats" 10 | fields = ( 11 | ('cmd_get', dict( 12 | label = "Gets", 13 | info = "Gets", 14 | type = "DERIVE", 15 | min = "0", 16 | )), 17 | ('cmd_set', dict( 18 | label = "Sets", 19 | info = "Sets", 20 | type = "DERIVE", 21 | min = "0", 22 | )), 23 | ('get_hits', dict( 24 | label = "Get hits", 25 | info = "Get hits", 26 | type = "DERIVE", 27 | min = "0", 28 | )), 29 | ('get_misses', dict( 30 | label = "Get misses", 31 | info = "Get misses", 32 | type = "DERIVE", 33 | min = "0", 34 | )), 35 | ('evictions', dict( 36 | label = "Evictions", 37 | info = "Evictions", 38 | type = "DERIVE", 39 | min = "0", 40 | )), 41 | ) 42 | 43 | if __name__ == "__main__": 44 | MuninMemcachedQueriesPlugin().run() 45 | -------------------------------------------------------------------------------- /plugins/mongodb_flush_avg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBFlushAvg(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "seconds" 9 | title = "MongoDB background flush interval" 10 | info = "The average time between background flushes" 11 | fields = ( 12 | ('total_ms', dict( 13 | label = "Flush interval", 14 | info = "The time interval for background flushes", 15 | type = "DERIVE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | def execute(self): 21 | status = self.connection.admin.command('serverStatus') 22 | try: 23 | value = float(status["backgroundFlushing"]["total_ms"])/1000 24 | except KeyError: 25 | value = "U" 26 | return dict(total_ms=value) 27 | 28 | if __name__ == "__main__": 29 | MongoDBFlushAvg().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_heap_usage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBHeapUsagePlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1024" 8 | vlabel = "bytes" 9 | title = "MongoDB heap usage" 10 | info = "Heap usage" 11 | fields = ( 12 | ('heap_usage', dict( 13 | label = "heap usage", 14 | info = "heap usage", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | def execute(self): 21 | status = self.connection.admin.command('serverStatus') 22 | try: 23 | value = status['extra_info']['heap_usage_bytes'] 24 | except KeyError: 25 | value = "U" 26 | return dict(heap_usage=value) 27 | 28 | if __name__ == "__main__": 29 | MongoDBHeapUsagePlugin().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_index_misses: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBIndexMissesPlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "misses" 9 | title = "MongoDB index misses" 10 | info = "Number of index cache misses" 11 | fields = ( 12 | ('misses', dict( 13 | label = "misses", 14 | info = "Index cache misses", 15 | type = "DERIVE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | def execute(self): 21 | status = self.connection.admin.command('serverStatus') 22 | try: 23 | value = status['indexCounters']['misses'] 24 | except KeyError: 25 | value = "U" 26 | return dict(misses=value) 27 | 28 | if __name__ == "__main__": 29 | MongoDBIndexMissesPlugin().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_lock_ratio: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBLockRatio(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "ratio" 9 | title = "MongoDB global lock time ratio" 10 | info = "How long the global lock has been held compared to the global execution time" 11 | fields = ( 12 | ('lockratio', dict( 13 | label = "Global lock time ratio", 14 | info = "How long the global lock has been held compared to the global execution time", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | def execute(self): 21 | status = self.connection.admin.command('serverStatus') 22 | try: 23 | value = float(status["globalLock"]["lockTime"])/float(status["globalLock"]["totalTime"]) 24 | except KeyError: 25 | value = "U" 26 | return dict(lockratio=value) 27 | 28 | if __name__ == "__main__": 29 | MongoDBLockRatio().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_lock_time: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBLockTime(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "time" 9 | title = "MongoDB global lock time" 10 | info = "How long the global lock has been held" 11 | fields = ( 12 | ('locktime', dict( 13 | label = "Global lock time", 14 | info = "How long the global lock has been held", 15 | type = "COUNTER", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | def execute(self): 21 | status = self.connection.admin.command('serverStatus') 22 | try: 23 | value = int(status["globalLock"]["lockTime"]) 24 | except KeyError: 25 | value = "U" 26 | return dict(locktime=value) 27 | 28 | if __name__ == "__main__": 29 | MongoDBLockTime().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_memory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBMemoryPlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1024" 8 | vlabel = "bytes" 9 | title = "MongoDB memory usage" 10 | info = "Memory usage" 11 | fields = ( 12 | ('virtual', dict( 13 | label = "virtual", 14 | info = "Bytes of virtual memory", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ('resident', dict( 19 | label = "resident", 20 | info = "Bytes of resident memory", 21 | type = "GAUGE", 22 | min = "0", 23 | )), 24 | ('mapped', dict( 25 | label = "mapped", 26 | info = "Bytes of mapped memory", 27 | type = "GAUGE", 28 | min = "0", 29 | )), 30 | ) 31 | 32 | def execute(self): 33 | status = self.connection.admin.command('serverStatus') 34 | values = {} 35 | for k in ("virtual", "resident", "mapped"): 36 | try: 37 | value = int(status["mem"][k]) * 1024 * 1024 38 | except KeyError: 39 | value = "U" 40 | values[k] = value 41 | return values 42 | 43 | if __name__ == "__main__": 44 | MongoDBMemoryPlugin().run() 45 | -------------------------------------------------------------------------------- /plugins/mongodb_objects_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBObjectsPlugin(MuninMongoDBPlugin): 7 | args = "--base 1000" 8 | vlabel = "objects" 9 | info = "Number of objects stored" 10 | dbname_in_args = True 11 | fields = ( 12 | ('objects', dict( 13 | label = "objects", 14 | info = "Number of objects stored", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | @property 21 | def title(self): 22 | return "MongoDB objects in database %s" % self.dbname 23 | 24 | def execute(self): 25 | stats = self.db.command("dbstats") 26 | return dict(objects=stats['objects']) 27 | 28 | if __name__ == "__main__": 29 | MongoDBObjectsPlugin().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_ops: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBOpsPlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "ops/sec" 9 | title = "MongoDB operations" 10 | info = "Operations" 11 | ops = ("query", "update", "insert", "delete", "command", "getmore") 12 | 13 | @property 14 | def fields(self): 15 | return [ 16 | (op, dict( 17 | label = "%s operations" % op, 18 | info = "%s operations" % op, 19 | type = "DERIVE", 20 | min = "0", 21 | )) for op in self.ops 22 | ] 23 | 24 | def execute(self): 25 | status = self.connection.admin.command('serverStatus') 26 | return dict( 27 | (op, status["opcounters"].get(op, 0)) 28 | for op in self.ops 29 | ) 30 | 31 | if __name__ == "__main__": 32 | MongoDBOpsPlugin().run() 33 | -------------------------------------------------------------------------------- /plugins/mongodb_page_faults: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBPageFaultsPlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "page faults / sec" 9 | title = "MongoDB page faults" 10 | info = "Page faults" 11 | fields = ( 12 | ('page_faults', dict( 13 | label = "page faults", 14 | info = "Page faults", 15 | type = "DERIVE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | def execute(self): 21 | status = self.connection.admin.command('serverStatus') 22 | try: 23 | value = status['extra_info']['page_faults'] 24 | except KeyError: 25 | value = "U" 26 | return dict(page_faults=value) 27 | 28 | if __name__ == "__main__": 29 | MongoDBPageFaultsPlugin().run() 30 | -------------------------------------------------------------------------------- /plugins/mongodb_queues: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBQueuesPlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "count" 9 | title = "MongoDB queues" 10 | info = "Queues" 11 | queues = ("readers", "writers") 12 | 13 | @property 14 | def fields(self): 15 | return [ 16 | (q, dict( 17 | label = "%s" % q, 18 | info = "%s" % q, 19 | type = "GAUGE", 20 | min = "0", 21 | )) for q in self.queues 22 | ] 23 | 24 | def execute(self): 25 | status = self.connection.admin.command('serverStatus') 26 | return dict( 27 | (q, status["globalLock"]["currentQueue"][q]) 28 | for q in self.queues 29 | ) 30 | 31 | if __name__ == "__main__": 32 | MongoDBQueuesPlugin().run() 33 | -------------------------------------------------------------------------------- /plugins/mongodb_replset_lag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | PRIMARY_STATE = 1 7 | SECONDARY_STATE = 2 8 | 9 | class MongoReplicaSetLag(MuninMongoDBPlugin): 10 | 11 | vlabel = "seconds" 12 | title = "MongoDB Replica Set Lag" 13 | fields = [("optimeLag", {'label': "Oldest secondary lag"}), ("oplogLength", {"label": "Primary oplog length" })] 14 | 15 | def _get_oplog_length(self): 16 | oplog = self.connection['local'].oplog.rs 17 | last_op = oplog.find({}, {'ts': 1}).sort([('$natural', -1)]).limit(1)[0]['ts'].time 18 | first_op = oplog.find({}, {'ts': 1}).sort([('$natural', 1)]).limit(1)[0]['ts'].time 19 | oplog_length = last_op - first_op 20 | return oplog_length 21 | 22 | def _get_max_replication_lag(self): 23 | status = self.connection.admin.command('replSetGetStatus') 24 | members = status['members'] 25 | primary_optime = None 26 | oldest_secondary_optime = None 27 | for member in members: 28 | member_state = member['state'] 29 | optime = member['optime'] 30 | if member_state == PRIMARY_STATE: 31 | primary_optime = optime.time 32 | elif member_state == SECONDARY_STATE: 33 | if not oldest_secondary_optime or optime.time < oldest_secondary_optime: 34 | oldest_secondary_optime = optime.time 35 | 36 | if not primary_optime or not oldest_secondary_optime: 37 | raise Exception("Replica set is not healthy") 38 | 39 | return primary_optime - oldest_secondary_optime 40 | 41 | def execute(self): 42 | oplog_length = self._get_oplog_length() 43 | replication_lag = self._get_max_replication_lag() 44 | 45 | return { 46 | "optimeLag": replication_lag, 47 | "oplogLength": oplog_length 48 | } 49 | 50 | if __name__ == "__main__": 51 | MongoReplicaSetLag().run() 52 | -------------------------------------------------------------------------------- /plugins/mongodb_size_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mongodb import MuninMongoDBPlugin 5 | 6 | class MongoDBSizePlugin(MuninMongoDBPlugin): 7 | args = "-l 0 --base 1024" 8 | vlabel = "bytes" 9 | info = "Size of database" 10 | dbname_in_args = True 11 | fields = ( 12 | ('storagesize', dict( 13 | label = "Storage size (bytes)", 14 | info = "Storage size", 15 | type = "GAUGE", 16 | draw = "AREA", 17 | )), 18 | ('datasize', dict( 19 | label = "Data size (bytes)", 20 | info = "Data size", 21 | type = "GAUGE", 22 | draw = "AREA", 23 | )), 24 | ('indexsize', dict( 25 | label = "Index size (bytes)", 26 | info = "Index size", 27 | type = "GAUGE", 28 | draw = "AREA", 29 | )), 30 | ) 31 | 32 | @property 33 | def title(self): 34 | return "MongoDB size of database %s" % self.dbname 35 | 36 | def execute(self): 37 | stats = self.db.command("dbstats") 38 | return dict( 39 | storagesize = stats["storageSize"], 40 | datasize = stats["dataSize"], 41 | indexsize = stats["indexSize"], 42 | ) 43 | 44 | if __name__ == "__main__": 45 | MongoDBSizePlugin().run() 46 | -------------------------------------------------------------------------------- /plugins/mysql_connections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mysql import MuninMySQLPlugin 5 | 6 | class MuninMySQLConnectionsPlugin(MuninMySQLPlugin): 7 | dbname_in_args = True 8 | title = "Connections" 9 | args = "-l 0 --base 1000" 10 | vlabel = "connections" 11 | info = "Connections" 12 | fields = ( 13 | ('connections', dict( 14 | label = "Connections", 15 | info = "Connections", 16 | type = "DERIVE", 17 | )), 18 | ('aborted_connects', dict( 19 | label = "Aborted connects", 20 | info = "A high aborted connects can show someone trying to guess a password", 21 | type = "DERIVE", 22 | )), 23 | ('max_used_connections', dict( 24 | label = "Max used connections", 25 | info = "Max used connections", 26 | type = "GAUGE", 27 | )), 28 | ('max_connections', dict( 29 | label = "Max connections", 30 | info = "Max connections", 31 | type = "GAUGE", 32 | )), 33 | ) 34 | 35 | def execute(self): 36 | c = self.cursor() 37 | c.execute("SHOW GLOBAL STATUS") 38 | status = c.fetchall() 39 | c.execute("SHOW GLOBAL VARIABLES") 40 | global_vars = c.fetchall() 41 | 42 | field_names = set(x[0] for x in self.fields) 43 | 44 | values = dict() 45 | 46 | for name, value in status + global_vars: 47 | name = name.lower() 48 | if name in field_names: 49 | values[name] = value 50 | 51 | return values 52 | 53 | if __name__ == "__main__": 54 | MuninMySQLConnectionsPlugin().run() 55 | -------------------------------------------------------------------------------- /plugins/mysql_dbrows_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mysql import MuninMySQLPlugin 5 | 6 | class MuninMySQLDBRowsPlugin(MuninMySQLPlugin): 7 | dbname_in_args = True 8 | args = "-l 0 --base 1000" 9 | vlabel = "rows" 10 | info = "Rows in database" 11 | fields = ( 12 | ('rows', dict( 13 | label = "Row count", 14 | info = "Row count", 15 | type = "GAUGE", 16 | )), 17 | ) 18 | 19 | @property 20 | def title(self): 21 | return "MySQL number of rows in database %s" % self.dbname 22 | 23 | def execute(self): 24 | c = self.cursor() 25 | 26 | c.execute("SELECT sum(table_rows) FROM information_schema.TABLES WHERE table_schema = %s", (self.dbname,)) 27 | row = c.fetchone() 28 | return dict( 29 | rows = row[0], 30 | ) 31 | 32 | if __name__ == "__main__": 33 | MuninMySQLDBRowsPlugin().run() 34 | -------------------------------------------------------------------------------- /plugins/mysql_dbsize_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.mysql import MuninMySQLPlugin 5 | 6 | class MuninMySQLDBSizePlugin(MuninMySQLPlugin): 7 | dbname_in_args = True 8 | args = "-l 0 --base 1024" 9 | vlabel = "bytes" 10 | info = "Size of database" 11 | fields = ( 12 | ('datasize', dict( 13 | label = "Data size (bytes)", 14 | info = "Data size", 15 | type = "GAUGE", 16 | draw = "AREA", 17 | )), 18 | ('indexsize', dict( 19 | label = "Index size (bytes)", 20 | info = "Index size", 21 | type = "GAUGE", 22 | draw = "AREA", 23 | )), 24 | ) 25 | 26 | @property 27 | def title(self): 28 | return "MySQL size of database %s" % self.dbname 29 | 30 | def execute(self): 31 | c = self.cursor() 32 | 33 | c.execute("SELECT sum(data_length), sum(index_length) FROM information_schema.TABLES WHERE table_schema = %s", (self.dbname,)) 34 | row = c.fetchone() 35 | return dict( 36 | datasize = row[0], 37 | indexsize = row[1], 38 | ) 39 | 40 | if __name__ == "__main__": 41 | MuninMySQLDBSizePlugin().run() 42 | -------------------------------------------------------------------------------- /plugins/mysql_replication: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Inspired by http://openquery.com/files/check_replication.pl.txt 5 | 6 | import os 7 | import re 8 | 9 | import MySQLdb 10 | 11 | from munin import MuninPlugin 12 | 13 | 14 | class MuninMySQLPlugin(MuninPlugin): 15 | category = "MySQL" 16 | 17 | def __init__(self): 18 | super(MuninMySQLPlugin, self).__init__() 19 | 20 | self._db_master = None 21 | self._db_slave = None 22 | 23 | # Open a connection 24 | def get_connection(self, source): 25 | vars = self.getvars(source) 26 | 27 | db = None 28 | if source == 'master': 29 | if not self._db_master: 30 | self._db_master = MySQLdb.connect(**vars) 31 | db = self._db_master 32 | else: 33 | if not self._db_slave: 34 | self._db_slave = MySQLdb.connect(**vars) 35 | db = self._db_slave 36 | 37 | return db 38 | 39 | # Get variables plugins 40 | def getvars(self, source): 41 | self.con_master = dict( 42 | user="root", 43 | host="localhost", 44 | ) 45 | 46 | # Search environement vars 47 | if source == 'master': 48 | varname = ('m_user', 'm_passwd', 'm_host', 'm_port') 49 | replacestr = 'm_' 50 | else: 51 | varname = ('s_user', 's_passwd', 's_host', 's_port') 52 | replacestr = 's_' 53 | 54 | conninfo = {} 55 | for k in varname: 56 | v = os.environ.get(k) 57 | if v: 58 | conninfo[k.replace(replacestr, '', 1)] = v 59 | 60 | return conninfo 61 | 62 | # get max bin log size 63 | def getmaxbinlogsize(self): 64 | db = self.get_connection('master') 65 | c = db.cursor(MySQLdb.cursors.DictCursor) 66 | c.execute("show variables like 'max_binlog_size'") 67 | raw = c.fetchone() 68 | return int(raw['Value']) 69 | 70 | # Get replication values 71 | def getvalues(self, source): 72 | # get values 73 | db = self.get_connection(source) 74 | c = db.cursor(MySQLdb.cursors.DictCursor) 75 | c.execute("show %(source)s status" % locals()) 76 | 77 | raw = c.fetchone() 78 | return raw 79 | 80 | def autoconf(self): 81 | return bool(self.get_connection('master'))\ 82 | and bool(self.get_connection('slave')) 83 | 84 | 85 | class MuninMySQLReplicationPlugin(MuninMySQLPlugin): 86 | """ 87 | 88 | from master => GRANT replication client on *.* TO munin@ip IDENTIFIED BY "password"; 89 | from slave => GRANT replication client on *.* TO munin@localhost IDENTIFIED BY "password"; 90 | 91 | ex: /etc/munin/plugin-conf.d/mysql_replication 92 | 93 | [mysql_replication] 94 | env.m_host x.x.x.x 95 | env.m_user munin 96 | env.m_passwd mysqlpass 97 | 98 | env.s_host localhost 99 | env.s_user munin 100 | env.s_passwd mysqlpass 101 | 102 | """ 103 | 104 | args = "-l 0 --base 1000" 105 | vlabel = "delta" 106 | info = "Show delta position from master and slave mySQL database" 107 | fields = ( 108 | ('delta', dict( 109 | label="Delta from master", 110 | info="Delta not replicated from master", 111 | type="GAUGE", 112 | draw="AREA", 113 | )), 114 | ) 115 | 116 | @property 117 | def title(self): 118 | return "MySQL delta replication" 119 | 120 | def execute(self): 121 | m = self.getvalues('master') 122 | s = self.getvalues('slave') 123 | 124 | mfilepos = -1 125 | sfilepos = -1 126 | maxlogsize = self.getmaxbinlogsize() 127 | 128 | # Search number binary file 129 | r = re.search(r'\d+$', m['File']) 130 | if r: 131 | mfilepos = int(r.group(0)) 132 | r = re.search(r'\d+$', s['Relay_Master_Log_File']) 133 | if r: 134 | sfilepos = int(r.group(0)) 135 | 136 | mposition = int(m['Position']) 137 | fposition = int(s['Exec_Master_Log_Pos']) 138 | 139 | # Calc delta 140 | deltafile = (mfilepos - sfilepos) * maxlogsize 141 | deltaposition = mposition - fposition 142 | delta = deltafile + deltaposition 143 | 144 | # Close connection 145 | self.get_connection('master').close() 146 | self.get_connection('slave').close() 147 | 148 | return dict( 149 | delta=delta, 150 | ) 151 | 152 | if __name__ == "__main__": 153 | MuninMySQLReplicationPlugin().run() 154 | -------------------------------------------------------------------------------- /plugins/nginx_connections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import urllib 6 | from munin.nginx import MuninNginxPlugin 7 | 8 | class MuninNginxConnectionsPlugin(MuninNginxPlugin): 9 | title = "Nginx Connections" 10 | args = "--base 1000" 11 | vlabel = "Connections" 12 | fields = ( 13 | ('total', dict( 14 | label = "Active connections", 15 | type = "GAUGE", 16 | draw = "LINE2", 17 | min = "0", 18 | )), 19 | ('reading', dict( 20 | label = "Reading", 21 | type = "GAUGE", 22 | draw = "LINE2", 23 | min = "0", 24 | )), 25 | ('writing', dict( 26 | label = "Writing", 27 | type = "GAUGE", 28 | draw = "LINE2", 29 | min = "0", 30 | )), 31 | ('waiting', dict( 32 | label = "Waiting", 33 | type = "GAUGE", 34 | draw = "LINE2", 35 | min = "0", 36 | )), 37 | ) 38 | 39 | def execute(self): 40 | status = self.get_status() 41 | return dict( 42 | total = status['active'], 43 | reading = status['reading'], 44 | writing = status['writing'], 45 | waiting = status['waiting'], 46 | ) 47 | 48 | if __name__ == "__main__": 49 | MuninNginxConnectionsPlugin().run() 50 | -------------------------------------------------------------------------------- /plugins/nginx_requests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import urllib 6 | from munin.nginx import MuninNginxPlugin 7 | 8 | class MuninNginxRequestsPlugin(MuninNginxPlugin): 9 | title = "Nginx Requests" 10 | args = "--base 1000" 11 | vlabel = "Requests per second" 12 | fields = ( 13 | ('request', dict( 14 | label = "Requests", 15 | type = "DERIVE", 16 | min = "0", 17 | draw = "LINE2", 18 | )), 19 | ) 20 | 21 | def execute(self): 22 | return dict( 23 | request = self.get_status()['requests'], 24 | ) 25 | 26 | if __name__ == "__main__": 27 | MuninNginxRequestsPlugin().run() 28 | -------------------------------------------------------------------------------- /plugins/path_size: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import subprocess 5 | from munin import MuninPlugin 6 | 7 | class PathSizePlugin(MuninPlugin): 8 | args = "--base 1024 -l 0" 9 | vlabel = "bytes" 10 | scale = True 11 | category = "other" 12 | fields = ( 13 | ('size', dict( 14 | label = "size", 15 | info = "Size", 16 | type = "GAUGE", 17 | )), 18 | ) 19 | 20 | def __init__(self, *args, **kwargs): 21 | super(PathSizePlugin, self).__init__(*args, **kwargs) 22 | self.path = os.environ["PATHSIZE_PATH"] 23 | 24 | @property 25 | def title(self): 26 | return "Size of %s" % self.path 27 | 28 | def execute(self): 29 | p = subprocess.Popen("du -sk " + self.path, shell=True, stdout=subprocess.PIPE) 30 | du = p.communicate()[0] 31 | size = int(du.split('\t')[0].strip()) * 1024 32 | return dict(size=size) 33 | 34 | if __name__ == "__main__": 35 | PathSizePlugin().run() 36 | -------------------------------------------------------------------------------- /plugins/pgbouncer_pools_cl_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.pgbouncer import MuninPgBouncerPlugin 5 | 6 | class MuninPgBouncerPoolsClientPlugin(MuninPgBouncerPlugin): 7 | command = "SHOW POOLS" 8 | vlabel = "Connections" 9 | info = "Shows number of connections to pgbouncer" 10 | 11 | fields = ( 12 | ('cl_active', dict( 13 | label = "active", 14 | info = "Active connections to pgbouncer", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ('cl_waiting', dict( 19 | label = "waiting", 20 | info = "Waiting connections to pgbouncer", 21 | type = "GAUGE", 22 | min = "0", 23 | )), 24 | ) 25 | 26 | @property 27 | def title(self): 28 | return "PgBouncer client connections on %s" % self.dbwatched 29 | 30 | if __name__ == "__main__": 31 | MuninPgBouncerPoolsClientPlugin().run() 32 | 33 | -------------------------------------------------------------------------------- /plugins/pgbouncer_pools_sv_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.pgbouncer import MuninPgBouncerPlugin 5 | 6 | class MuninPgBouncerPoolsServerPlugin(MuninPgBouncerPlugin): 7 | command = "SHOW POOLS" 8 | vlabel = "Connections" 9 | info = "Shows number of connections to postgresql" 10 | 11 | fields = ( 12 | ('sv_active', dict( 13 | label = "active", 14 | info = "Active connections to Postgresql", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ('sv_idle', dict( 19 | label = "idle", 20 | info = "Idle connections to Postgresql", 21 | type = "GAUGE", 22 | min = "0", 23 | )), 24 | ('sv_used', dict( 25 | label = "used", 26 | info = "Used connections to Postgresql", 27 | type = "GAUGE", 28 | min = "0", 29 | )), 30 | ('sv_tested', dict( 31 | label = "tested", 32 | info = "Tested connections to Postgresql", 33 | type = "GAUGE", 34 | min = "0", 35 | )), 36 | ('sv_login', dict( 37 | label = "login", 38 | info = "Connections logged in to Postgresql", 39 | type = "GAUGE", 40 | min = "0", 41 | )), 42 | ) 43 | 44 | @property 45 | def title(self): 46 | return "PgBouncer server connections on %s" % self.dbwatched 47 | 48 | if __name__ == "__main__": 49 | MuninPgBouncerPoolsServerPlugin().run() 50 | 51 | -------------------------------------------------------------------------------- /plugins/pgbouncer_stats_avg_bytes_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.pgbouncer import MuninPgBouncerPlugin 5 | 6 | class MuninPgBouncerStatsBytesServerPlugin(MuninPgBouncerPlugin): 7 | command = "SHOW STATS" 8 | vlabel = "Bytes" 9 | info = "Shows average bytes per second" 10 | 11 | fields = ( 12 | ('avg_recv', dict( 13 | label = "received", 14 | info = "Average bytes received per second", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ('avg_sent', dict( 19 | label = "sent", 20 | info = "Average bytes sent per second", 21 | type = "GAUGE", 22 | min = "0", 23 | )), 24 | ) 25 | 26 | @property 27 | def title(self): 28 | return "PgBouncer bytes per second on %s" % self.dbwatched 29 | 30 | if __name__ == "__main__": 31 | MuninPgBouncerStatsBytesServerPlugin().run() 32 | 33 | -------------------------------------------------------------------------------- /plugins/pgbouncer_stats_avg_query_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.pgbouncer import MuninPgBouncerPlugin 5 | 6 | class MuninPgBouncerStatsQueryServerPlugin(MuninPgBouncerPlugin): 7 | command = "SHOW STATS" 8 | vlabel = "Microseconds" 9 | info = "Shows average query duration in microseconds" 10 | 11 | fields = ( 12 | ('avg_query', dict( 13 | label = "received", 14 | info = "Average query duration", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | @property 21 | def title(self): 22 | return "PgBouncer average query duration on %s" % self.dbwatched 23 | 24 | if __name__ == "__main__": 25 | MuninPgBouncerStatsQueryServerPlugin().run() 26 | 27 | -------------------------------------------------------------------------------- /plugins/pgbouncer_stats_avg_req_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.pgbouncer import MuninPgBouncerPlugin 5 | 6 | class MuninPgBouncerStatsRequestsServerPlugin(MuninPgBouncerPlugin): 7 | command = "SHOW STATS" 8 | vlabel = "Requests" 9 | info = "Shows average requests per second" 10 | 11 | fields = ( 12 | ('avg_req', dict( 13 | label = "requests per second", 14 | info = "average requests per second", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | @property 21 | def title(self): 22 | return "PgBouncer average requests per second on %s" % self.dbwatched 23 | 24 | if __name__ == "__main__": 25 | MuninPgBouncerStatsRequestsServerPlugin().run() 26 | 27 | -------------------------------------------------------------------------------- /plugins/postgres_block_read_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Based on a plugin by BjØrn Ruberg. 6 | 7 | Plugin to monitor PostgreSQL memory usage; gives number of blocks 8 | read from disk and from memory, showing how much of the database is 9 | served from PostgreSQL's memory buffer. 10 | 11 | PLEASE NOTE: This plugin may not present the whole truth - the truth 12 | may actually be even better than this plugin will show you! That is 13 | because PostgreSQL statistics only considers memory block reads from 14 | its own allocated memory. When PostgreSQL reads from disk, it may 15 | actually still be read from memory, but from the _kernel_'s 16 | memory. Summarily, your database server may run even better than 17 | this plugin will indicate. See 18 | http://www.postgresql.org/docs/7.4/interactive/monitoring-stats.html 19 | for a (short) description. 20 | """ 21 | 22 | from munin.postgres import MuninPostgresPlugin 23 | 24 | class MuninPostgresBlockReadPlugin(MuninPostgresPlugin): 25 | dbname_in_args = True 26 | args = "--base 1000" 27 | vlabel = "Blocks read per ${graph_period}" 28 | info = "Shows number of blocks read from disk and from memory" 29 | fields = ( 30 | ('from_disk', dict( 31 | label = "Read from disk", 32 | info = "Read from disk", 33 | type = "DERIVE", 34 | min = "0", 35 | draw = "AREA", 36 | )), 37 | ('from_memory', dict( 38 | label = "Cached in memory", 39 | info = "Cached in memory", 40 | type = "DERIVE", 41 | min = "0", 42 | draw = "STACK", 43 | )), 44 | ) 45 | 46 | @property 47 | def title(self): 48 | return "Postgres data reads from %s" % self.dbname 49 | 50 | def execute(self): 51 | c = self.cursor() 52 | query = ( 53 | "SELECT (SUM (heap_blks_read) + SUM (idx_blks_read) + " 54 | " SUM (toast_blks_read) + SUM (tidx_blks_read)) AS disk, " 55 | " (SUM (heap_blks_hit) + SUM (idx_blks_hit) + " 56 | " SUM (toast_blks_hit) + SUM (tidx_blks_hit)) AS mem " 57 | "FROM pg_statio_user_tables") 58 | c.execute(query) 59 | values = {} 60 | for row in c.fetchall(): 61 | values['from_disk'] = row[0] 62 | values['from_memory'] = row[1] 63 | return values 64 | 65 | if __name__ == "__main__": 66 | MuninPostgresBlockReadPlugin().run() 67 | -------------------------------------------------------------------------------- /plugins/postgres_commits_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Based on a plugin by BjØrn Ruberg. 6 | 7 | Plugin to monitor PostgreSQL commits/rollbacks. 8 | 9 | "Why should I care?" 10 | - Too many commits can really bog down the database, as it checks all 11 | the tables for consitency after each change. 12 | - Software is often set to 'AutoCommit = 1', meaning a commit is done 13 | after each transaction. This is a good idea with brittle code so that 14 | you can get some work done if not all, but when you're inserting 10,000 15 | rows this can really suck. 16 | - If you see a spike in rollbacks, some db programmer is probably 17 | abusing their session, or a stored proceudre has gone horribly wrong 18 | and isn't leaving a trace. Time for the rolled-up newspaper. 19 | 20 | Find out more at 21 | http://www.postgresql.org/docs/8.2/interactive/monitoring-stats.html 22 | (where "8.2" can be the version of PostgreSQL you have installed) 23 | """ 24 | 25 | from munin.postgres import MuninPostgresPlugin 26 | 27 | class MuninPostgresCommitsPlugin(MuninPostgresPlugin): 28 | dbname_in_args = True 29 | args = "--base 1000" 30 | vlabel = "Sessions per ${graph_period}" 31 | info = "Shows number of commits and rollbacks" 32 | fields = ( 33 | ('commits', dict( 34 | label = "commits", 35 | info = "SQL sessions terminated with a commit command", 36 | type = "DERIVE", 37 | min = "0", 38 | )), 39 | ('rollbacks', dict( 40 | label = "rollbacks", 41 | info = "SQL sessions terminated with a rollback command", 42 | type = "DERIVE", 43 | min = "0", 44 | )), 45 | ) 46 | 47 | @property 48 | def title(self): 49 | return "Postgres commits/rollbacks on %s" % self.dbname 50 | 51 | def execute(self): 52 | c = self.cursor() 53 | c.execute("SELECT xact_commit, xact_rollback FROM pg_stat_database WHERE datname = %s", (self.dbname,)) 54 | values = {} 55 | for row in c.fetchall(): 56 | values["commits"] = row[0] 57 | values["rollbacks"] = row[1] 58 | return values 59 | 60 | if __name__ == "__main__": 61 | MuninPostgresCommitsPlugin().run() 62 | -------------------------------------------------------------------------------- /plugins/postgres_connections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.postgres import MuninPostgresPlugin 4 | 5 | class MuninPostgresConnectionsPlugin(MuninPostgresPlugin): 6 | dbname_in_args = False 7 | title = "Postgres active connections" 8 | args = "-l 0 --base 1000" 9 | vlabel = "Active connections" 10 | info = "Shows active Postgresql connections" 11 | 12 | @property 13 | def fields(self): 14 | c = self.cursor() 15 | c.execute("SHOW max_connections") 16 | row = c.fetchone() 17 | return ( 18 | ('connections', dict( 19 | label = "Active connections", 20 | info = "Active connections", 21 | type = "GAUGE", 22 | warning = int(int(row[0]) * 0.7), 23 | critical = int(int(row[0]) * 0.8), 24 | )), 25 | ) 26 | 27 | def execute(self): 28 | c = self.cursor() 29 | c.execute("SELECT COUNT(1) FROM pg_stat_activity") 30 | row = c.fetchone() 31 | return dict(connections = row[0]) 32 | 33 | if __name__ == "__main__": 34 | MuninPostgresConnectionsPlugin().run() 35 | -------------------------------------------------------------------------------- /plugins/postgres_locks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Based on a Perl plugin by an unknown author. 6 | 7 | Show postgres lock statistics. 8 | """ 9 | 10 | from munin.postgres import MuninPostgresPlugin 11 | 12 | class MuninPostgresLocksPlugin(MuninPostgresPlugin): 13 | dbname_in_args = False 14 | title = "Postgres locks" 15 | args = "--base 1000" 16 | vlabel = "Locks" 17 | info = "Shows Postgresql locks" 18 | fields = ( 19 | ('locks', dict( 20 | label = "Locks", 21 | info = "Locks", 22 | type = "GAUGE", 23 | warning = 10, 24 | critical = 20, 25 | )), 26 | ('exlocks', dict( 27 | label = "Exclusive locks", 28 | info = "Exclusive locks", 29 | type = "GAUGE", 30 | warning = 5, 31 | critical = 10, 32 | )), 33 | ) 34 | 35 | def execute(self): 36 | c = self.cursor() 37 | c.execute("SELECT mode, COUNT(mode) FROM pg_locks GROUP BY mode ORDER BY mode") 38 | locks = 0 39 | exlocks = 0 40 | for row in c.fetchall(): 41 | if 'exclusive' in row[0].lower(): 42 | exlocks += row[1] 43 | locks += row[1] 44 | return dict( 45 | locks = locks, 46 | exlocks = exlocks, 47 | ) 48 | 49 | if __name__ == "__main__": 50 | MuninPostgresLocksPlugin().run() 51 | -------------------------------------------------------------------------------- /plugins/postgres_queries_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Based on a plugin by BjØrn Ruberg. 6 | 7 | Plugin to monitor PostgreSQL query rate; returns the number of 8 | sequential scans initiated, rows returned by sequential reads, 9 | index scans initiated, rows returned by index scans, inserts, 10 | updates, and deletes. 11 | 12 | Find out more at 13 | http://www.postgresql.org/docs/8.2/interactive/monitoring-stats.html 14 | (should work with PostgreSQL 7.x and 8.x) 15 | """ 16 | 17 | from munin.postgres import MuninPostgresPlugin 18 | 19 | class MuninPostgresQueriesPlugin(MuninPostgresPlugin): 20 | dbname_in_args = True 21 | args = "--base 1000" 22 | vlabel = "Queries per ${graph_period}" 23 | info = "Shows number of select, insert, update and delete queries" 24 | 25 | field_types = ( 26 | ('sel_seq', dict( 27 | label = "s_selects", 28 | info = "Sequential selects on all tables", 29 | column = "seq_scan", 30 | )), 31 | ('sel_seq_rows', dict( 32 | label = "s_select rows", 33 | info = "Rows returned from sequential selects", 34 | column = "seq_tup_read", 35 | )), 36 | ('sel_idx', dict( 37 | label = "i_selects", 38 | info = "Sequential selects on all indexes", 39 | column = "idx_scan", 40 | )), 41 | ('sel_idx_rows', dict( 42 | label = "i_select rows", 43 | info = "Rows returned from index selects", 44 | column = "idx_tup_fetch", 45 | )), 46 | ('inserts', dict( 47 | label = "inserts", 48 | info = "Rows inserted on all tables", 49 | column = "n_tup_ins", 50 | )), 51 | ('updates', dict( 52 | label = "updates", 53 | info = "Rows updated on all tables", 54 | column = "n_tup_upd", 55 | )), 56 | ('deletes', dict( 57 | label = "deletes", 58 | info = "Rows deleted on all tables", 59 | column = "n_tup_del", 60 | )), 61 | ) 62 | 63 | @property 64 | def title(self): 65 | return "Postgres queries on %s" % self.dbname 66 | 67 | @property 68 | def fields(self): 69 | return [ 70 | (k, dict( 71 | label = v['label'], 72 | info = v['label'], 73 | type = "DERIVE", 74 | min = "0", 75 | )) for k, v in self.field_types] 76 | 77 | def execute(self): 78 | c = self.cursor() 79 | keys = [(k, v['column']) for k, v in self.field_types] 80 | query = "SELECT %s FROM pg_stat_all_tables" % ",".join('SUM("%s")' % col for key, col in keys) 81 | c.execute(query) 82 | row = c.fetchone() 83 | values = {} 84 | for k, v in zip(keys, row): 85 | values[k[0]] = v 86 | return values 87 | 88 | if __name__ == "__main__": 89 | MuninPostgresQueriesPlugin().run() 90 | -------------------------------------------------------------------------------- /plugins/postgres_space_: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Based on a plugin by BjØrn Ruberg and Moses Moore. 6 | 7 | Plugin to monitor PostgreSQL disk usage. 8 | """ 9 | 10 | from munin.postgres import MuninPostgresPlugin 11 | 12 | class MuninPostgresSpacePlugin(MuninPostgresPlugin): 13 | dbname_in_args = True 14 | args = "-l 0 --base 1024" 15 | vlabel = "bytes" 16 | info = "Size of database" 17 | fields = ( 18 | ('size', dict( 19 | label = "Database size (bytes)", 20 | info = "Database size", 21 | type = "GAUGE", 22 | draw = "AREA", 23 | )), 24 | ('indexsize', dict( 25 | label = "Index size (bytes)", 26 | info = "Index size", 27 | type = "GAUGE", 28 | draw = "AREA", 29 | )), 30 | ('metasize', dict( 31 | label = "Meta size (bytes)", 32 | info = "Meta size", 33 | type = "GAUGE", 34 | draw = "AREA", 35 | )), 36 | ('metaindexsize', dict( 37 | label = "Meta Index size (bytes)", 38 | info = "Meta Index size", 39 | type = "GAUGE", 40 | draw = "AREA", 41 | )), 42 | ) 43 | 44 | @property 45 | def title(self): 46 | return "Postgres size of database %s" % self.dbname 47 | 48 | def execute(self): 49 | c = self.cursor() 50 | 51 | namespaces = {} 52 | c.execute("SELECT oid, nspname FROM pg_namespace") 53 | for row in c.fetchall(): 54 | namespaces[row[0]] = row[1] 55 | 56 | query = ( 57 | "SELECT relname, relnamespace, relkind, relfilenode, relpages" 58 | " FROM pg_class WHERE relkind IN ('r', 'i')") 59 | 60 | database_pages = 0 61 | database_indexes = 0 62 | metadatabase_pages = 0 63 | metadatabase_indexes = 0 64 | 65 | c.execute(query) 66 | for row in c.fetchall(): 67 | relname, relnamespace, relkind, relfilenode, relpages = row 68 | ns = namespaces[relnamespace] 69 | if ns.startswith('pg_toast'): 70 | continue 71 | 72 | meta = ns.startswith('pg_') or ns == "information_schema" 73 | 74 | c2 = self.cursor() 75 | c2.execute("SELECT SUM(relpages) FROM pg_class WHERE relname IN (%s, %s)", 76 | ("pg_toast_%s" % relfilenode, "pg_toast_%s_index" % relfilenode)) 77 | relpages2 = int(c2.fetchone()[0] or '0') 78 | 79 | if relkind == "r": # Regular table 80 | if meta: 81 | metadatabase_pages += int(relpages) + relpages2 82 | else: 83 | database_pages += int(relpages) + relpages2 84 | elif relkind == "i": # Index 85 | if meta: 86 | metadatabase_indexes += int(relpages) + relpages2 87 | else: 88 | database_indexes += int(relpages) + relpages2 89 | 90 | return dict( 91 | size = database_pages * 8192, 92 | indexsize = database_indexes * 8192, 93 | metasize = metadatabase_pages * 8192, 94 | metaindexsize = metadatabase_indexes * 8192, 95 | ) 96 | 97 | if __name__ == "__main__": 98 | MuninPostgresSpacePlugin().run() 99 | -------------------------------------------------------------------------------- /plugins/postgres_table_sizes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ Monitors the total table size (data + indexes) 4 | for all tables in the specified database.""" 5 | 6 | from munin.postgres import MuninPostgresPlugin 7 | 8 | class PostgresTableSizes(MuninPostgresPlugin): 9 | vlabel = "Table Size" 10 | title = "Table Sizes" 11 | 12 | @property 13 | def fields(self): 14 | return [(table, {"label": table}) for table in self.tables()] 15 | 16 | def execute(self): 17 | tables = {} 18 | for table in self.tables(): 19 | cursor = self.cursor() 20 | cursor.execute("SELECT pg_total_relation_size(%s);", (table,)) 21 | tables[table] = cursor.fetchone()[0] 22 | return tables 23 | 24 | if __name__ == "__main__": 25 | PostgresTableSizes().run() 26 | 27 | -------------------------------------------------------------------------------- /plugins/rabbitmq-throughput: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from munin import MuninPlugin 3 | 4 | import mechanize 5 | import simplejson 6 | import sys 7 | import os 8 | 9 | # configuration 10 | baseurl = 'http://localhost:55672' 11 | usr = 'guest' 12 | pwd = 'guest' 13 | 14 | class RabbitMQThroughputPlugin(MuninPlugin): 15 | title = "RabbitMQ Throughput" 16 | args = "--base 1000 -l-50" 17 | vlabel = "throughput" 18 | scaled = False 19 | category = "rabbitmq" 20 | 21 | # what do we want? 22 | message_fields = ['publish_details', 'ack_details', 'deliver_details', 'deliver_get_details', 'redeliver_details'] 23 | queue_fields = ['messages_details', 'messages_ready_details', 'messages_unacknowledged_details'] 24 | 25 | @property 26 | def fields(self): 27 | msg_warning = os.environ.get('msg_throughput_warn', 250) 28 | msg_critical = os.environ.get('msg_throughput_crit', 300) 29 | 30 | return [ (field.replace('details','rate'), dict( 31 | label = field.replace('details','rate'), 32 | info = '%s throughput (per sec)' % (field,), 33 | type = "GAUGE", 34 | min = "0", 35 | warning = str(msg_warning), 36 | critical = str(msg_critical))) for field in (self.message_fields + self.queue_fields)] 37 | 38 | def execute(self): 39 | global baseurl, usr, pwd 40 | 41 | # make request 42 | b = mechanize.Browser() 43 | b.set_handle_robots(False) 44 | b.add_password(baseurl, usr, pwd) 45 | overview_url = baseurl + '/api/overview' 46 | b.open(overview_url) 47 | resp = b.response().read() 48 | 49 | # get actual fields 50 | ret = simplejson.loads(resp) 51 | for f in self.message_fields: 52 | print "%s.value %s" % (f.replace('details','rate'), ret['message_stats'][f]['rate']) 53 | for f in self.queue_fields: 54 | print "%s.value %s" % (f.replace('details','rate'), ret['queue_totals'][f]['rate']) 55 | 56 | if __name__ == "__main__": 57 | RabbitMQThroughputPlugin().run() 58 | -------------------------------------------------------------------------------- /plugins/redis_active_connections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.redis import MuninRedisPlugin 4 | 5 | class MuninRedisActiveConnectionsPlugin(MuninRedisPlugin): 6 | title = "Redis active connections" 7 | args = "--base 1000" 8 | vlabel = "Connections" 9 | info = "active connections" 10 | fields = ( 11 | ('connected_clients', dict( 12 | label = "connections", 13 | info = "connections", 14 | type = "GAUGE", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninRedisActiveConnectionsPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/redis_commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.redis import MuninRedisPlugin 4 | 5 | class MuninRedisCommandsPlugin(MuninRedisPlugin): 6 | title = "Redis commands" 7 | args = "--base 1000" 8 | vlabel = "commands/sec" 9 | info = "total commands" 10 | fields = ( 11 | ('total_commands_processed', dict( 12 | label = "commands", 13 | info = "commands", 14 | type = "COUNTER", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninRedisCommandsPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/redis_connects: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.redis import MuninRedisPlugin 4 | 5 | class MuninRedisTotalConnectionsPlugin(MuninRedisPlugin): 6 | title = "Redis connects" 7 | args = "--base 1000" 8 | vlabel = "connections/sec" 9 | info = "connections per second" 10 | fields = ( 11 | ('total_connections_received', dict( 12 | label = "connections", 13 | info = "connections", 14 | type = "COUNTER", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninRedisTotalConnectionsPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/redis_used_memory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from munin.redis import MuninRedisPlugin 4 | 5 | class MuninRedisUsedMemoryPlugin(MuninRedisPlugin): 6 | title = "Redis used memory" 7 | args = "--base 1024" 8 | vlabel = "Memory" 9 | info = "used memory" 10 | fields = ( 11 | ('used_memory', dict( 12 | label = "used memory", 13 | info = "used memory", 14 | type = "GAUGE", 15 | )), 16 | ) 17 | 18 | if __name__ == "__main__": 19 | MuninRedisUsedMemoryPlugin().run() 20 | -------------------------------------------------------------------------------- /plugins/request_time: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from time import time 5 | from urllib2 import urlopen, Request 6 | 7 | from munin import MuninPlugin 8 | 9 | class MuninRequestTimePlugin(MuninPlugin): 10 | title = "Request Time" 11 | args = "--base 1000" 12 | vlabel = "seconds" 13 | info = "Time for a request to complete" 14 | 15 | def __init__(self): 16 | super(MuninRequestTimePlugin, self).__init__() 17 | self.bound_time = float(os.environ['RT_BOUND_TIME']) if 'RT_BOUND_TIME' in os.environ else None 18 | self.urls = [] 19 | for k, v in os.environ.iteritems(): 20 | if k.startswith('RT_URL'): 21 | name, url = tuple(v.split('=', 1)) 22 | url = url.split('|') 23 | headers = {} 24 | if len(url) > 1: 25 | headers = dict(x.split('=') for x in url[1:]) 26 | url = url[0] 27 | self.urls.append((name, url, headers)) 28 | self.urls.sort() 29 | 30 | @property 31 | def fields(self): 32 | return [ 33 | (name, dict( 34 | label = name, 35 | type = "GAUGE", 36 | min = "0", 37 | )) for name, url, headers in self.urls 38 | ] 39 | 40 | def execute(self): 41 | values = {} 42 | for name, url, headers in self.urls: 43 | t = time() 44 | req = Request(url, headers=headers) 45 | urlopen(req).read() 46 | dt = time() - t 47 | if self.bound_time: 48 | dt = min(dt, self.bound_time) 49 | values[name] = "%.2f" % dt 50 | return values 51 | 52 | if __name__ == "__main__": 53 | MuninRequestTimePlugin().run() 54 | -------------------------------------------------------------------------------- /plugins/riak_ops: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from munin.riak import MuninRiakPlugin 5 | 6 | class RiakOpsPlugin(MuninRiakPlugin): 7 | args = "-l 0 --base 1000" 8 | vlabel = "ops/sec" 9 | title = "Riak operations" 10 | info = "Operations" 11 | fields = ( 12 | ('gets', dict( 13 | label = "gets", 14 | info = "gets", 15 | type = "DERIVE", 16 | min = "0", 17 | )), 18 | ('puts', dict( 19 | label = "puts", 20 | info = "puts", 21 | type = "DERIVE", 22 | min = "0", 23 | )), 24 | ) 25 | 26 | def execute(self): 27 | status = self.get_status() 28 | return dict( 29 | gets = status['node_gets_total'], 30 | puts = status['node_puts_total'], 31 | ) 32 | 33 | if __name__ == "__main__": 34 | RiakOpsPlugin().run() 35 | -------------------------------------------------------------------------------- /plugins/tc_size: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import subprocess 5 | 6 | from munin import MuninPlugin 7 | 8 | class MuninTokyoCabinetSizePlugin(MuninPlugin): 9 | title = "Size of Tokyo Cabinet database" 10 | args = "--base 1024" 11 | vlabel = "bytes" 12 | fields = ( 13 | ("size", dict( 14 | label = "Size", 15 | type = "GAUGE", 16 | min = "0", 17 | )), 18 | ) 19 | 20 | environ = { 21 | 'PATH': "/usr/bin:/usr/local/bin", 22 | } 23 | 24 | def __init__(self): 25 | super(MuninTokyoCabinetSizePlugin, self).__init__() 26 | path = os.environ['TC_PATH'] 27 | if path.startswith('tt://'): 28 | self.path = None 29 | self.port = None 30 | self.host = path[5:] 31 | if ':' in self.host: 32 | self.host, self.port = path[5:].split(':') 33 | else: 34 | self.path = path 35 | self.host = None 36 | self.port = None 37 | 38 | def inform(self): 39 | if self.path: 40 | raise NotImplementedError() 41 | else: 42 | args = ["tcrmgr", "inform"] 43 | if self.port: 44 | args += ["-port", str(self.port)] 45 | args.append(self.host) 46 | p = subprocess.Popen(args, env=self.environ, stdout=subprocess.PIPE) 47 | res = p.communicate()[0] 48 | res = res.split('\n') 49 | return { 50 | 'records': int(res[0].split(':')[-1]), 51 | 'size': int(res[1].split(':')[-1]), 52 | } 53 | 54 | def execute(self): 55 | info = self.inform() 56 | return dict(size=info['size']) 57 | 58 | if __name__ == "__main__": 59 | MuninTokyoCabinetSizePlugin().run() 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | from munin import __version__ as version 6 | 7 | setup( 8 | name = 'munin', 9 | version = version, 10 | description = 'Framework for building Munin plugins', 11 | author = 'Samuel Stauffer', 12 | author_email = 'samuel@descolada.com', 13 | url = 'http://github.com/samuel/python-munin/tree/master', 14 | packages = ['munin'], 15 | classifiers = [ 16 | 'Intended Audience :: Developers', 17 | 'License :: OSI Approved :: MIT License', 18 | 'Operating System :: OS Independent', 19 | 'Programming Language :: Python', 20 | 'Topic :: Software Development :: Libraries :: Python Modules', 21 | ], 22 | ) 23 | --------------------------------------------------------------------------------