[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Run all tests\n\non: [push, pull_request]\n\nenv:\n  PIP: \"env PIP_DISABLE_PIP_VERSION_CHECK=1\n            PYTHONWARNINGS=ignore:DEPRECATION\n            pip --no-cache-dir\"\n\njobs:\n  tests_py2x:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:2.7\n    strategy:\n      fail-fast: false\n      matrix:\n        toxenv: [py27]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run the unit tests\n      run: TOXENV=${{ matrix.toxenv }} tox\n\n  tests_py34:\n    runs-on: ubuntu-22.04\n    container:\n      image: ubuntu:20.04\n      env:\n        LANG: C.UTF-8\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install build dependencies\n      run: |\n        apt-get update\n        apt-get install -y build-essential unzip wget \\\n                           libncurses5-dev libgdbm-dev libnss3-dev \\\n                           libreadline-dev zlib1g-dev\n\n    - name: Build OpenSSL 1.0.2 (required by Python 3.4)\n      run: |\n        cd $RUNNER_TEMP\n        wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_0_2u/openssl-1.0.2u.tar.gz\n        tar -xf openssl-1.0.2u.tar.gz\n        cd openssl-1.0.2u\n        ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib-dynamic\n        make\n        make install\n\n        echo CFLAGS=\"-I/usr/local/ssl/include $CFLAGS\" >> $GITHUB_ENV\n        echo LDFLAGS=\"-L/usr/local/ssl/lib $LDFLAGS\" >> $GITHUB_ENV\n        echo LD_LIBRARY_PATH=\"/usr/local/ssl/lib:$LD_LIBRARY_PATH\" >> $GITHUB_ENV\n\n        ln -s /usr/local/ssl/lib/libssl.so.1.0.0 /usr/lib/libssl.so.1.0.0\n        ln -s /usr/local/ssl/lib/libcrypto.so.1.0.0 /usr/lib/libcrypto.so.1.0.0\n        ldconfig\n\n    - name: Build Python 3.4\n      run: |\n        cd $RUNNER_TEMP\n        wget -O cpython-3.4.10.zip https://github.com/python/cpython/archive/refs/tags/v3.4.10.zip\n        unzip cpython-3.4.10.zip\n        cd cpython-3.4.10\n        ./configure --with-ensurepip=install\n        make\n        make install\n\n        python3.4 --version\n        python3.4 -c 'import ssl'\n        pip3.4 --version\n\n        ln -s /usr/local/bin/python3.4 /usr/local/bin/python\n        ln -s /usr/local/bin/pip3.4 /usr/local/bin/pip\n\n    - name: Install Python dependencies\n      run: |\n        $PIP install virtualenv==20.4.7 tox==3.14.0\n\n    - name: Run the unit tests\n      run: TOXENV=py34 tox\n\n  tests_py35:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:3.5\n    strategy:\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run the unit tests\n      run: TOXENV=py35 tox\n\n  tests_py36:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:3.6\n    strategy:\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run the unit tests\n      run: TOXENV=py36 tox\n\n  tests_py3x:\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [3.7, 3.8, 3.9, \"3.10\", 3.11, 3.12, 3.13, 3.14]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Set variable for TOXENV based on Python version\n      id: toxenv\n      run: python -c 'import sys; print(\"TOXENV=py%d%d\" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT\n\n    - name: Run the unit tests\n      run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox\n\n    - name: Run the end-to-end tests\n      run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox\n\n  docs:\n    runs-on: ubuntu-22.04\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: \"3.8\"\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox>=4.0.0\n\n    - name: Build the docs\n      run: TOXENV=docs tox\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.egg\n*.egg-info\n*.pyc\n*.pyo\n*.swp\n.DS_Store\n.coverage\n.eggs/\n.tox/\nbuild/\ndocs/_build/\ndist/\nenv*/\nhtmlcov/\ntmp/\ncoverage.xml\nnosetests.xml\n\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# We recommend specifying your dependencies to enable reproducible builds:\n# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\n# python:\n#   install:\n#   - requirements: docs/requirements.txt\n\n"
  },
  {
    "path": "CHANGES.rst",
    "content": "2.0.1.dev0 (Next Release)\n-------------------------\n\n2.0.0 (2021-12-26)\n------------------\n\n- Support for Python 2.6 has been dropped.  On Python 2, Superlance\n  now requires Python 2.7.\n\n- Support for Python 3.2 and 3.3 has been dropped.  On Python 3, Superlance\n  now requires Python 3.4 or later.\n\n- Fixed a bug introduced in 0.10 where if the timeout value is shorter\n  than the time to wait between retries, the httpok check never executed.\n  Issue #110.\n\n- Fixed a bug where ``crashmailbatch`` and ``fatalmatchbatch`` did not set\n  the intended default subject.  Patch by Joe Portela.\n\n- Added a new ``--tls`` option to ``crashmailbatch``, ``fatalmailbath``, and\n  ``crashsms`` to use Transport Layer Security (TLS).  Patch by Zhe Li.\n\n1.0.0 (2016-10-02)\n------------------\n\n- Support for Python 2.5 has been dropped.  On Python 2, Superlance\n  now requires Python 2.6 or later.\n\n- Support for Python 3 has been added.  On Python 3, Superlance\n  requires Python 3.2 or later.\n\n- Fixed parsing of ``-n`` and ``--name`` options in ``httpok``.  Patch\n  by DenisBY.\n\n0.14 (2016-09-24)\n-----------------\n\n- Fixed docs build.\n\n0.13 (2016-09-05)\n-----------------\n\n- ``httpok`` now allows multiple expected status codes to be specified.  Patch\n  by valmiRe.\n\n- ``httpok`` now has a ``--name`` option like ``memmon``.\n\n- All commands now return exit status 0 from ``--help``.\n\n0.12 (2016-09-03)\n-----------------\n\n- Fixed ``crashmail`` parsing of ``--optionalheader``.  Patch by Matt Dziuban.\n\n0.11 (2014-08-15)\n-----------------\n\n- Added support for ``memmon`` to check against cumulative RSS of a process\n  and all its child processes.  Patch by Lukas Graf.\n\n- Fixed a bug introduced in 0.9 where the ``-u`` and ``-n`` options in\n  ``memmon`` were parsed incorrectly.  Patch by Harald Friessnegger.\n\n0.10 (2014-07-08)\n-----------------\n\n- Honor timeout in httok checks even on trying the connection.\n  Without it, processes that take make than 60 seconds to accept connections\n  and http_ok with TICK_60 events cause a permanent restart of the process.\n\n- ``httpok`` now sends a ``User-Agent`` header of ``httpok``.\n\n- Removed ``setuptools`` from the ``requires`` list in ``setup.py`` because\n  it caused installation issues on some systems.\n\n0.9 (2013-09-18)\n----------------\n\n- Added license.\n\n- Fixed bug in cmd line option validator for ProcessStateEmailMonitor\n  Bug report by Val Jordan\n\n- Added ``-u`` option to memmon the only send an email in case the restarted\n  process' uptime (in seconds) is below this limit.  This is useful to only\n  get notified if a processes gets restarted too frequently.\n  Patch by Harald Friessnegger.\n\n0.8 (2013-05-26)\n----------------\n\n- Superlance will now refuse to install on an unsupported version of Python.\n\n- Allow SMTP credentials to be supplied to ProcessStateEmailMonitor\n  Patch by Steven Davidson.\n\n- Added ``-n`` option to memmon that adds this name to the email\n  subject to identify which memmon process restarted a process.\n  Useful in case you run multiple supervisors that control\n  different processes with the same name.\n  Patch by Harald Friessnegger.\n\n- ProcessStateEmailMonitor now adds Date and Message-ID headers to emails.\n  Patch by Andrei Vereha.\n\n0.7 (2012-08-22)\n----------------\n\n- The ``crashmailbatch --toEmail`` option now accepts a comma-separated\n  list of email addresses.\n\n0.6 (2011-08-27)\n----------------\n\n- Separated unit tests into their own files\n\n- Created ``fatalmailbatch`` plugin\n\n- Created ``crashmailbatch`` plugin\n\n- Sphinxified documentation.\n\n- Fixed ``test_suite`` to use the correct module name in setup.py.\n\n- Fixed the tests for ``memmon`` to import the correct module.\n\n- Applied patch from Sam Bartlett: processes which are not autostarted\n  have pid \"0\".  This was crashing ``memmon``.\n\n- Add ``smtpHost`` command line flag to ``mailbatch`` processors.\n\n- Added ``crashsms`` from Juan Batiz-Benet\n\n- Converted ``crashmailbatch`` and friends from camel case to pythonic style\n\n- Fixed a bug where ``httpok`` would crash with the ``-b`` (in-body)\n  option.  Patch by Joaquin Cuenca Abela.\n\n- Fixed a bug where ``httpok`` would not handle a URL with a query string\n  correctly.  Patch by Joaquin Cuenca Abela.\n\n- Fixed a bug where ``httpok`` would not handle process names with a\n  group (\"group:process\") properly.  Patch by Joaquin Cuenca Abela.\n\n\n0.5 (2009-05-24)\n----------------\n\n- Added the ``memmon`` plugin, originally bundled with supervisor and\n  now moved to superlance.\n\n\n0.4 (2009-02-11)\n----------------\n\n- Added ``eager`` and ``not-eager`` options to the ``httpok`` plugin.\n\n  If ``not-eager`` is set, and no process being monitored is in the\n  ``RUNNING`` state, skip the URL check / mail message.\n\n\n0.3 (2008-12-10)\n----------------\n\n- Added ``gcore`` and ``coredir`` options to the ``httpok`` plugin.\n\n\n0.2 (2008-11-21)\n----------------\n\n- Added the ``crashmail`` plugin.\n\n\n0.1 (2008-09-18)\n----------------\n\n- Initial release\n"
  },
  {
    "path": "COPYRIGHT.txt",
    "content": "Superlance is Copyright (c) 2008-2013 Agendaless Consulting and Contributors.\n(http://www.agendaless.com), All Rights Reserved\n\n  This software is subject to the provisions of the license at\n  http://www.repoze.org/LICENSE.txt . A copy of this license should\n  accompany this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND\n  ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING,\n  BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE,\n  MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR\n  PURPOSE.\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Superlance is licensed under the following license:\n\n  A copyright notice accompanies this license document that identifies\n  the copyright holders.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are\n  met:\n\n  1.  Redistributions in source code must retain the accompanying\n      copyright notice, this list of conditions, and the following\n      disclaimer.\n\n  2.  Redistributions in binary form must reproduce the accompanying\n      copyright notice, this list of conditions, and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n\n  3.  Names of the copyright holders must not be used to endorse or\n      promote products derived from this software without prior\n      written permission from the copyright holders.\n\n  4.  If any files are modified, you must cause the modified files to\n      carry prominent notices stating that you changed the files and\n      the date of any change.\n\n  Disclaimer\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND\n    ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n    HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\n    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF\n    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n    SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include CHANGES.rst\ninclude COPYRIGHT.txt\ninclude LICENSE.txt\ninclude README.rst\ninclude docs/Makefile\nrecursive-include docs *.py *.rst\nrecursive-exclude docs/_build *\n\n"
  },
  {
    "path": "README.rst",
    "content": "superlance README\n=================\n\nSuperlance is a package of plugin utilities for monitoring and controlling\nprocesses that run under `supervisor <http://supervisord.org>`_.\n\nPlease see ``docs/index.rst`` for complete documentation.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html      to make standalone HTML files\"\n\t@echo \"  dirhtml   to make HTML files named index.html in directories\"\n\t@echo \"  pickle    to make pickle files\"\n\t@echo \"  json      to make JSON files\"\n\t@echo \"  htmlhelp  to make HTML files and a HTML help project\"\n\t@echo \"  qthelp    to make HTML files and a qthelp project\"\n\t@echo \"  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  changes   to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck to check all external links for integrity\"\n\t@echo \"  doctest   to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/superlance.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/superlance.qhc\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make all-pdf' or \\`make all-ps' in that directory to\" \\\n\t      \"run these through (pdf)latex.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# superlance documentation build configuration file, created by\n# sphinx-quickstart on Thu Jun 10 11:55:43 2010.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys, os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.append(os.path.abspath('.'))\n\n# -- General configuration -----------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'superlance'\ncopyright = u'2010, Chris McDonough, Agendaless Consulting, Inc.'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '2.0.1.dev0'\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of documents that shouldn't be included in the build.\n#unused_docs = []\n\n# List of directories, relative to source directory, that shouldn't be searched\n# for source files.\nexclude_trees = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  Major themes that come with\n# Sphinx are currently 'default' and 'sphinxdoc'.\nhtml_theme = 'default'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n#html_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_use_modindex = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# If nonempty, this is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = ''\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'superlancedoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# The paper size ('letter' or 'a4').\n#latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n#latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'superlance.tex', u'superlance Documentation',\n   u'Chris McDonough, Agendaless Consulting, Inc.', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# Additional stuff for the LaTeX preamble.\n#latex_preamble = ''\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_use_modindex = True\n"
  },
  {
    "path": "docs/crashmail.rst",
    "content": ":command:`crashmail` Documentation\n==================================\n\n:command:`crashmail` is a supervisor \"event listener\", intended to be\nsubscribed to ``PROCESS_STATE_EXITED`` events. When :command:`crashmail`\nreceives that event, and the transition is \"unexpected\", :command:`crashmail`\nsends an email notification to a configured address.\n\n:command:`crashmail` is incapable of monitoring the process status of processes\nwhich are not :command:`supervisord` child processes.\n\n:command:`crashmail` is a \"console script\" installed when you install\n:mod:`superlance`.  Although :command:`crashmail` is an executable program, it\nisn't useful as a general-purpose script:  it must be run as a\n:command:`supervisor` event listener to do anything useful.\n\nCommand-Line Syntax\n-------------------\n\n.. code-block:: sh\n\n   $ crashmail [-p processname] [-a] [-o string] [-m mail_address] \\\n               [-s sendmail]\n\n.. program:: crashmail\n\n.. cmdoption:: -p <process_name>, --program=<process_name>\n\n   Send mail when the specified :command:`supervisord` child process\n   transitions unexpectedly to the ``EXITED`` state.\n\n   This option can be provided more than once to have :command:`crashmail`\n   monitor more than one program.\n\n   To monitor a process which is part of a :command:`supervisord` group,\n   specify its name as ``group_name:process_name``.\n\n.. cmdoption:: -a, --any\n\n   Send mail when any :command:`supervisord` child process transitions\n   unexpectedly to the ``EXITED`` state.\n\n   Overrides any ``-p`` parameters passed in the same :command:`crashmail`\n   process invocation.\n\n.. cmdoption:: -o <prefix>, --optionalheader=<prefix>\n\n   Specify a parameter used as a prefix in the mail :mailheader:`Subject`\n   header.\n\n.. cmdoption:: -s <sendmail_command>, --sendmail_program=<sendmail_command>\n\n   Specify the sendmail command to use to send email.\n\n   Must be a command which accepts header and message data on stdin and\n   sends mail.  Default is ``/usr/sbin/sendmail -t -i``.\n\n.. cmdoption:: -m <email_address>, --email=<email_address>\n\n   Specify an email address to which crash notification messages are sent.\n   If no email address is specified, email will not be sent.\n\n\nConfiguring :command:`crashmail` Into the Supervisor Config\n-----------------------------------------------------------\n\nAn ``[eventlistener:x]`` section must be placed in :file:`supervisord.conf`\nin order for :command:`crashmail` to do its work. See the \"Events\" chapter in\nthe Supervisor manual for more information about event listeners.\n\nThe following example assumes that :command:`crashmail` is on your system\n:envvar:`PATH`.\n\n.. code-block:: ini\n\n   [eventlistener:crashmail]\n   command=crashmail -p program1 -p group1:program2 -m dev@example.com\n   events=PROCESS_STATE_EXITED\n"
  },
  {
    "path": "docs/crashmailbatch.rst",
    "content": ":command:`crashmailbatch` Documentation\n=======================================\n\n:command:`crashmailbatch` is a supervisor \"event listener\", intended to be\nsubscribed to ``PROCESS_STATE`` and ``TICK_60`` events.  It monitors\nall processes running under a given supervisord instance.\n\nSimilar to :command:`crashmail`, :command:`crashmailbatch` sends email \nalerts when processes die unexpectedly.  The difference is that all alerts \ngenerated within the configured time interval are batched together to avoid \nsending too many emails.   \n\n:command:`crashmailbatch` is a \"console script\" installed when you install\n:mod:`superlance`.  Although :command:`crashmailbatch` is an executable \nprogram, it isn't useful as a general-purpose script:  it must be run as a\n:command:`supervisor` event listener to do anything useful.\n\nCommand-Line Syntax\n-------------------\n\n.. code-block:: sh\n\n   $ crashmailbatch --toEmail=<email address> --fromEmail=<email address> \\\n           [--interval=<batch interval in minutes>] [--subject=<email subject>] \\\n\t\t   [--tickEvent=<event name>] [--smtpHost=<SMTP server>] \\\n           [--userName=<SMTP username>] [--password=<STMP password>] \\\n           [--tls]\n   \n.. program:: crashmailbatch\n\n.. cmdoption:: -t <destination email>, --toEmail=<destination email>\n   \n   Specify comma separated email addresses to which crash notification messages are sent.\n \n.. cmdoption:: -f <source email>, --fromEmail=<source email>\n   \n   Specify an email address from which crash notification messages are sent.\n\n.. cmdoption:: -i <interval>, --interval=<interval>\n   \n   Specify the time interval in minutes to use for batching notifcations.\n   Defaults to 1.0 minute.\n\n.. cmdoption:: -s <email subject>, --subject=<email subject>\n   \n   Override the email subject line.  Defaults to \"Crash alert from supervisord\"\n\n.. cmdoption:: -e <event name>, --tickEvent=<event name>\n\n   Override the TICK event name.  Defaults to \"TICK_60\"\n   \n.. cmdoption:: -H <STMP server> --smtpHost <SMTP server>\n\n   Specify STMP server for sending email\n\n.. cmdoption:: -u <STMP username> --userName <SMTP username>\n\n   Specify STMP username\n\n.. cmdoption:: -p <STMP password> --password <SMTP password>\n\n   Specify STMP password\n\n.. cmdoption:: --tls\n\n   Use Transport Layer Security (TLS)\n\nConfiguring :command:`crashmailbatch` Into the Supervisor Config\n----------------------------------------------------------------\n\nAn ``[eventlistener:x]`` section must be placed in :file:`supervisord.conf`\nin order for :command:`crashmailbatch` to do its work. See the \"Events\" chapter in\nthe Supervisor manual for more information about event listeners.\n\nThe following example assumes that :command:`crashmailbatch` is on your system\n:envvar:`PATH`.\n\n.. code-block:: ini\n\n   [eventlistener:crashmailbatch]\n   command=crashmailbatch --toEmail=\"alertme@fubar.com\" --fromEmail=\"supervisord@fubar.com\" \n   events=PROCESS_STATE,TICK_60\n"
  },
  {
    "path": "docs/crashsms.rst",
    "content": ":command:`crashsms` Documentation\n==================================\n\n:command:`crashsms` is a supervisor \"event listener\", intended to be\nsubscribed to ``PROCESS_STATE`` events and ``TICK`` events such as ``TICK_60``.  It monitors\nall processes running under a given supervisord instance.\n\nSimilar to :command:`crashmailbatch`, :command:`crashsms` sends SMS alerts\nthrough an email gateway.  Messages are formatted to fit in SMS\n\n:command:`crashsms` is a \"console script\" installed when you install\n:mod:`superlance`.  Although :command:`crashsms` is an executable \nprogram, it isn't useful as a general-purpose script:  it must be run as a\n:command:`supervisor` event listener to do anything useful.\n\nCommand-Line Syntax\n-------------------\n\n.. code-block:: sh\n\n   $ crashsms --toEmail=<email address> --fromEmail=<email address> \\\n           [--interval=<batch interval in minutes>] [--subject=<email subject>] \\\n\t\t   [--tickEvent=<event name>]\n   \n.. program:: crashsms\n\n.. cmdoption:: -t <destination email>, --toEmail=<destination email>\n   \n   Specify comma separated email addresses to which crash notification messages are sent.\n \n.. cmdoption:: -f <source email>, --fromEmail=<source email>\n   \n   Specify an email address from which crash notification messages are sent.\n\n.. cmdoption:: -i <interval>, --interval=<interval>\n   \n   Specify the time interval in minutes to use for batching notifcations.\n   Defaults to 1.0 minute.\n\n.. cmdoption:: -s <email subject>, --subject=<email subject>\n   \n   Set the email subject line.  Default is None\n\n.. cmdoption:: -e <event name>, --tickEvent=<event name>\n\n   Override the TICK event name.  Defaults to \"TICK_60\"\n\nConfiguring :command:`crashsms` Into the Supervisor Config\n-----------------------------------------------------------\n\nAn ``[eventlistener:x]`` section must be placed in :file:`supervisord.conf`\nin order for :command:`crashsms` to do its work. See the \"Events\" chapter in\nthe Supervisor manual for more information about event listeners.\n\nThe following example assumes that :command:`crashsms` is on your system\n:envvar:`PATH`.\n\n.. code-block:: ini\n\n   [eventlistener:crashsms]\n   command=crashsms --toEmail=\"<mobile number>@<sms email gateway>\" --fromEmail=\"supervisord@fubar.com\" \n   events=PROCESS_STATE,TICK_60\n"
  },
  {
    "path": "docs/development.rst",
    "content": "Resources and Development\n=========================\n\nBug Tracker\n-----------\n\nSuperlance has a bug tracker where you may report any bugs or other\nerrors you find.  Please report bugs to the `GitHub issues page\n<https://github.com/supervisor/Superlance/issues>`_.\n\nVersion Control Repository\n--------------------------\n\nYou can also view the `Superlance version control repository\n<https://github.com/Supervisor/superlance>`_.\n\nContributing\n------------\n\n`Pull requests <https://help.github.com/articles/about-pull-requests/>`_\ncan be submitted to the Superlance repository on GitHub.\n\nIn the time since Superlance was created,\nthere are now many `third party plugins for Supervisor <http://supervisord.org/plugins.html>`_.\nMost new plugins should be in their own package rather than added to Superlance.\n\nAuthor Information\n------------------\n\nThe following people are responsible for creating Superlance.\n\nOriginal Author\n~~~~~~~~~~~~~~~\n\n`Chris McDonough <http://plope.com>`_ is the original author of Superlance.\n\nContributors\n~~~~~~~~~~~~\n\nContributors are tracked on the `GitHub contributions page\n<https://github.com/Supervisor/Superlance/graphs/contributors>`_.\n\nThe list below is included for historical reasons.  It records contributors who\nsigned a legal agreement.  The legal agreement was\n`introduced <https://github.com/Supervisor/superlance/commit/90889bef6f45edb6cb7e6dcc5c7826e718c38da7>`_\nin January 2014 but later\n`withdrawn <https://github.com/Supervisor/superlance/commit/e2f198d2652e3177aea8c4075a13d43f7da04b5b>`_\nin April 2014.  This list is being preserved in case it is useful\nlater (e.g. if at some point there was a desire to donate the project\nto a foundation that required such agreements).\n\n- Chris McDonough, 2008-09-18\n\n- Tres Seaver, 2009-02-11\n\n- Roger Hoover, 2010-07-30\n\n- Joaquín Cuenca Abela, 2011-06-23\n\n- Harald Friessnegger, 2012-11-01\n\n- Mikhail Lukyanchenko, 2013-12-23\n\n- Patrick Gerken, 2014-01-27\n"
  },
  {
    "path": "docs/fatalmailbatch.rst",
    "content": ":command:`fatalmailbatch` Documentation\n=======================================\n\n:command:`fatalmailbatch` is a supervisor \"event listener\", intended to be\nsubscribed to ``PROCESS_STATE`` and ``TICK_60`` events.  It monitors\nall processes running under a given supervisord instance.\n\n:command:`fatalmailbatch` sends email alerts when processes fail to start \ntoo many times such that supervisord gives up retrying.  All of the fatal\nstart events generated within the configured time interval are batched \ntogether to avoid sending too many emails.   \n\n:command:`fatalmailbatch` is a \"console script\" installed when you install\n:mod:`superlance`.  Although :command:`fatalmailbatch` is an executable \nprogram, it isn't useful as a general-purpose script:  it must be run as a\n:command:`supervisor` event listener to do anything useful.\n\nCommand-Line Syntax\n-------------------\n\n.. code-block:: sh\n\n   $ fatalmailbatch --toEmail=<email address> --fromEmail=<email address> \\\n           [--interval=<batch interval in minutes>] [--subject=<email subject>]\n   \n.. program:: fatalmailbatch\n\n.. cmdoption:: -t <destination email>, --toEmail=<destination email>\n   \n   Specify comma separated email addresses to which fatal start notification messages are sent.\n \n.. cmdoption:: -f <source email>, --fromEmail=<source email>\n   \n   Specify an email address from which fatal start notification messages \n   are sent.\n\n.. cmdoption:: -i <interval>, --interval=<interval>\n   \n   Specify the time interval in minutes to use for batching notifcations.\n   Defaults to 1 minute.\n\n.. cmdoption:: -s <email subject>, --subject=<email subject>\n   \n   Override the email subject line.  Defaults to \"Fatal start alert from \n   supervisord\"\n\nConfiguring :command:`fatalmailbatch` Into the Supervisor Config\n----------------------------------------------------------------\n\nAn ``[eventlistener:x]`` section must be placed in :file:`supervisord.conf`\nin order for :command:`fatalmailbatch` to do its work. See the \"Events\" chapter in\nthe Supervisor manual for more information about event listeners.\n\nThe following example assumes that :command:`fatalmailbatch` is on your system\n:envvar:`PATH`.\n\n.. code-block:: ini\n\n   [eventlistener:fatalmailbatch]\n   command=fatalmailbatch --toEmail=\"alertme@fubar.com\" --fromEmail=\"supervisord@fubar.com\" \n   events=PROCESS_STATE,TICK_60\n"
  },
  {
    "path": "docs/httpok.rst",
    "content": ":command:`httpok` Documentation\n==================================\n\n:command:`httpok` is a supervisor \"event listener\" which may be subscribed to\na concrete ``TICK_5``, ``TICK_60`` or ``TICK_3600``  event.\nWhen :command:`httpok` receives a ``TICK_x``\nevent (``TICK_60`` is recommended, indicating activity every 60 seconds),\n:command:`httpok` makes an HTTP GET request to a confgured URL. If the request\nfails or times out, :command:`httpok` will restart the \"hung\" child\nprocess(es). :command:`httpok` can be configured to send an email notification\nwhen it restarts a process.\n\n:command:`httpok` can only monitor the process status of processes\nwhich are :command:`supervisord` child processes.\n\n:command:`httpok` is a \"console script\" installed when you install\n:mod:`superlance`.  Although :command:`httpok` is an executable program, it\nisn't useful as a general-purpose script:  it must be run as a\n:command:`supervisor` event listener to do anything useful.\n\nCommand-Line Syntax\n-------------------\n\n.. code-block:: sh\n\n   $ httpok [-p processname] [-a] [-g] [-t timeout] [-c status_code] \\\n            [-b inbody] [-m mail_address] [-s sendmail] URL\n\n.. program:: httpok\n\n.. cmdoption:: -p <process_name>, --program=<process_name>\n\n   Restart the :command:`supervisord` child process named ``process_name``\n   if it is in the ``RUNNING`` state when the URL returns an unexpected\n   result or times out.\n\n   This option can be provided more than once to have :command:`httpok`\n   monitor more than one process.\n\n   To monitor a process which is part of a :command:`supervisord` group,\n   specify its name as ``group_name:process_name``.\n\n.. cmdoption:: -a, --any\n\n   Restart any child of :command:`supervisord` in the ``RUNNING`` state\n   if the URL returns an unexpected result or times out.\n\n   Overrides any ``-p`` parameters passed in the same :command:`httpok`\n   process invocation.\n\n.. cmdoption:: -g <gcore_program>, --gcore=<gcore_program>\n\n   Use the specifed program to ``gcore`` the :command:`supervisord` child\n   process.  The program should accept two arguments on the command line:\n   a filename and a pid.  Defaults to ``/usr/bin/gcore -o``.\n\n.. cmdoption:: -d <core_directory>, --coredir=<core_directory>\n\n   If a core directory is specified, :command:`httpok` will try to use the\n   ``gcore`` program (see ``-g``) to write a core file into this directory\n   for each hung process before restarting it.  It will then append any gcore\n   stdout output to the email message, if mail is configured (see the ``-m``\n   option below).\n\n.. cmdoption:: -t <timeout>, --timeout=<timeout>\n\n   The number of seconds that :command:`httpok` should wait for a response\n   to the HTTP request before timing out.\n\n   If this timeout is exceeded, :command:`httpok` will attempt to restart\n   child processes which are in the ``RUNNING`` state, and specified by\n   ``-p`` or ``-a``.\n\n   Defaults to 10 seconds.\n\n.. cmdoption:: -c <http_status_code>, --code=<http_status_code>\n\n   Specify the expected HTTP status code for the configured URL.\n\n   If this status code is not the status code provided by the response,\n   :command:`httpok` will attempt to restart child processes which are\n   in the ``RUNNING`` state, and specified by ``-p`` or ``-a``.\n\n   Defaults to 200.\n\n.. cmdoption:: -b <body_string>, --body=<body_string>\n\n   Specify a string which should be present in the body resulting\n   from the GET request.\n\n   If this string is not present in the response, :command:`httpok` will\n   attempt to restart child processes which are in the RUNNING state,\n   and specified by ``-p`` or ``-a``.\n\n   The default is to ignore the body.\n\n.. cmdoption:: -s <sendmail_command>, --sendmail_program=<sendmail_command>\n\n   Specify the sendmail command to use to send email.\n\n   Must be a command which accepts header and message data on stdin and\n   sends mail.  Default is ``/usr/sbin/sendmail -t -i``.\n\n.. cmdoption:: -m <email_address>, --email=<email_address>\n\n   Specify an email address to which notification messages are sent.\n   If no email address is specified, email will not be sent.\n\n.. cmdoption:: -e, --eager\n\n   Enable \"eager\" monitoring:  check the URL and emit mail even if no\n   monitored child process is in the ``RUNNING`` state.\n\n   Enabled by default.\n\n.. cmdoption:: -E, --not-eager\n\n   Disable \"eager\" monitoring:  do not check the URL or emit mail if no\n   monitored process is in the RUNNING state.\n\n.. cmdoption:: URL\n\n   The URL to which to issue a GET request.\n\n.. cmdoption:: -n <httpok name>, --name=<httpok name>\n\n    An optional name that identifies this httpok process. If given, the\n    email subject will start with ``httpok [<httpok name>]:`` instead\n    of ``httpok:``\n    In case you run multiple supervisors on a single host that control\n    different processes with the same name (eg `zopeinstance1`) you can\n    use this option to indicate which project the restarted instance\n    belongs to.\n\n\nConfiguring :command:`httpok` Into the Supervisor Config\n-----------------------------------------------------------\n\nAn ``[eventlistener:x]`` section must be placed in :file:`supervisord.conf`\nin order for :command:`httpok` to do its work.\nSee the \"Events\" chapter in the\nSupervisor manual for more information about event listeners.\n\nThe following example assumes that :command:`httpok` is on your system\n:envvar:`PATH`.\n\n.. code-block:: ini\n\n   [eventlistener:httpok]\n   command=httpok -p program1 -p group1:program2 http://localhost:8080/tasty\n   events=TICK_60\n"
  },
  {
    "path": "docs/index.rst",
    "content": "superlance plugins for supervisor\n=================================\n\nSuperlance is a package of plugin utilities for monitoring and\ncontrolling processes that run under `Supervisor\n<http://supervisord.org>`_.  It provides these plugins:\n\n:command:`httpok`\n    This plugin is meant to be used as a supervisor event listener,\n    subscribed to ``TICK_*`` events.  It tests that a given child process\n    which must in the ``RUNNING`` state, is viable via an HTTP ``GET``\n    request to a configured URL.  If the request fails or times out,\n    :command:`httpok` will restart the \"hung\" child process.\n\n:command:`crashmail`\n    This plugin is meant to be used as a supervisor event listener,\n    subscribed to ``PROCESS_STATE_EXITED`` events.  It email a user when\n    a process enters the ``EXITED`` state unexpectedly.\n\n:command:`memmon`\n    This plugin is meant to be used as a supervisor event listener,\n    subscribed to ``TICK_*`` events.  It monitors memory usage for configured\n    child processes, and restarts them when they exceed a configured\n    maximum size.\n\n:command:`crashmailbatch`\n    Similar to :command:`crashmail`, :command:`crashmailbatch` sends email\n    alerts when processes die unexpectedly.  The difference is that all alerts\n    generated within the configured time interval are batched together to avoid\n    sending too many emails.\n\n:command:`fatalmailbatch`\n    This plugin sends email alerts when processes fail to start\n    too many times such that supervisord gives up retrying.  All of the fatal\n    start events generated within the configured time interval are batched\n    together to avoid sending too many emails.\n\n:command:`crashsms`\n    Similar to :command:`crashmailbatch` except it sends SMS alerts\n    through an email gateway.  Messages are formatted to fit in SMS.\n\nContents:\n\n.. toctree::\n   :maxdepth: 2\n\n   httpok\n   crashmail\n   memmon\n   crashmailbatch\n   fatalmailbatch\n   crashsms\n   development\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n"
  },
  {
    "path": "docs/memmon.rst",
    "content": ":command:`memmon` Overview\n==========================\n\n:command:`memmon` is a supervisor \"event listener\" which may be subscribed to\na concrete ``TICK_x`` event. When :command:`memmon` receives a ``TICK_x``\nevent (``TICK_60`` is recommended, indicating activity every 60 seconds),\n:command:`memmon` checks that a configurable list of programs (or all\nprograms running under supervisor) are not exceeding a configurable amount of\nmemory (resident segment size, or RSS).  If one or more of these processes is\nconsuming more than the amount of memory that :command:`memmon` believes it\nshould, :command:`memmon` will restart the process. :command:`memmon` can be\nconfigured to send an email notification when it restarts a process.\n\n:command:`memmon` is known to work on Linux and Mac OS X, but has not been\ntested on other operating systems (it relies on :command:`ps` output and\ncommand-line switches).\n\n:command:`memmon` is incapable of monitoring the process status of processes\nwhich are not :command:`supervisord` child processes. Without the\n`--cumulative` option, only the RSS of immediate children of the\n:command:`supervisord` process will be considered.\n\n:command:`memmon` is a \"console script\" installed when you install\n:mod:`superlance`.  Although :command:`memmon` is an executable program, it\nisn't useful as a general-purpose script:  it must be run as a\n:command:`supervisor` event listener to do anything useful.\n\n:command:`memmon` uses Supervisor's XML-RPC interface.  Your ``supervisord.conf``\nfile must have a valid `[unix_http_server]\n<http://supervisord.org/configuration.html#unix-http-server-section-settings>`_\nor `[inet_http_server]\n<http://supervisord.org/configuration.html#inet-http-server-section-settings>`_\nsection, and must have an `[rpcinterface:supervisor]\n<http://supervisord.org/configuration.html#rpcinterface-x-section-settings>`_\nsection.  If you are able to control your ``supervisord`` instance with\n``supervisorctl``, you have already met these requirements.\n\nCommand-Line Syntax\n-------------------\n\n.. code-block:: sh\n\n   $ memmon [-c] [-p processname=byte_size] [-g groupname=byte_size] \\\n            [-a byte_size] [-s sendmail] [-m email_address] \\\n            [-u email_uptime_limit] [-n memmon_name]\n\n.. program:: memmon\n\n.. cmdoption:: -h, --help\n\n   Show program help.\n\n.. cmdoption:: -c, --cumulative\n\n   Check against cumulative RSS. When calculating a process' RSS, also\n   consider its child processes. With this option `memmon` will sum up\n   the RSS of the process to be monitored and all its children.\n\n.. cmdoption:: -p <name/size pair>, --program=<name/size pair>\n\n   A name/size pair, e.g. \"foo=1MB\". The name represents the supervisor\n   program name that you would like :command:`memmon` to monitor; the size\n   represents the number of bytes (suffix-multiplied using \"KB\", \"MB\" or \"GB\")\n   that should be considered \"too much\".\n\n   This option can be provided more than once to have :command:`memmon`\n   monitor more than one program.\n\n   Programs can be specified using a \"namespec\", to disambiguate same-named\n   programs in different groups, e.g. ``foo:bar`` represents the program\n   ``bar`` in the ``foo`` group.\n\n.. cmdoption:: -g <name/size pair>, --groupname=<name/size pair>\n\n   A groupname/size pair, e.g. \"group=1MB\". The name represents the supervisor\n   group name that you would like :command:`memmon` to monitor; the size\n   represents the number of bytes (suffix-multiplied using \"KB\", \"MB\" or \"GB\")\n   that should be considered \"too much\".\n\n   Multiple ``-g`` options can be provided to have :command:`memmon` monitor\n   more than one group.  If any process in this group exceeds the maximum,\n   it will be restarted.\n\n.. cmdoption:: -a <size>, --any=<size>\n\n   A size (suffix-multiplied using \"KB\", \"MB\" or \"GB\") that should be\n   considered \"too much\". If any program running as a child of supervisor\n   exceeds this maximum, it will be restarted. E.g. 100MB.\n\n.. cmdoption:: -s <command>, --sendmail=<command>\n\n   A command that will send mail if passed the email body (including the\n   headers).  Defaults to ``/usr/sbin/sendmail -t -i``.\n\n.. note::\n\n   Specifying this option doesn't cause memmon to send mail by itself:\n   see the ``-m`` / ``--email`` option.\n\n.. cmdoption:: -m <email address>, --email=<email address>\n\n   An email address to which to send email when a process is restarted.\n   By default, memmon will not send any mail unless an email address is\n   specified.\n\n.. cmdoption:: -u <email uptime limit>, --uptime=<email uptime limit>\n\n   Only send an email in case the restarted process' uptime (in seconds)\n   is below this limit.\n   (Useful to only get notified if a processes gets restarted too frequently)\n\n   Uptime is given in seconds (suffix-multiplied using \"m\" for minutes,\n   \"h\" for hours or \"d\" for days)\n\n.. cmdoption:: -n <memmon name>, --name=<memmon name>\n\n   An optional name that identifies this memmon process. If given, the\n   email subject will start with ``memmon [<memmon name>]:`` instead\n   of ``memmon:``\n   In case you run multiple supervisors on a single host that control\n   different processes with the same name (eg `zopeinstance1`) you can\n   use this option to indicate which project the restarted instance\n   belongs to.\n\n\n\nConfiguring :command:`memmon` Into the Supervisor Config\n--------------------------------------------------------\n\nAn ``[eventlistener:x]`` section must be placed in :file:`supervisord.conf`\nin order for :command:`memmon` to do its work. See the \"Events\" chapter in the\nSupervisor manual for more information about event listeners.\n\nIf the `[unix_http_server]\n<http://supervisord.org/configuration.html#unix-http-server-section-settings>`_\nor `[inet_http_server]\n<http://supervisord.org/configuration.html#inet-http-server-section-settings>`_\nhas been configured to use authentication, add the environment variables\n``SUPERVISOR_USERNAME`` and ``SUPERVISOR_PASSWORD`` in the ``[eventlistener:x]``\nsection as shown in Example Configuration 5.\n\nThe following examples assume that :command:`memmon` is on your system\n:envvar:`PATH`.\n\nExample Configuration 1\n#######################\n\nThis configuration causes :command:`memmon` to restart any process which is\na child of :command:`supervisord` consuming more than 200MB of RSS, and will\nsend mail to ``bob@example.com`` when it restarts a process using the\ndefault :command:`sendmail` command.\n\n.. code-block:: ini\n\n   [eventlistener:memmon]\n   command=memmon -a 200MB -m bob@example.com\n   events=TICK_60\n\n\nExample Configuration 2\n#######################\n\nThis configuration causes :command:`memmon` to restart any process with the\nsupervisor program name ``foo`` consuming more than 200MB of RSS, and\nwill send mail to ``bob@example.com`` when it restarts a process using\nthe default sendmail command.\n\n.. code-block:: ini\n\n   [eventlistener:memmon]\n   command=memmon -p foo=200MB -m bob@example.com\n   events=TICK_60\n\n\nExample Configuration 3\n#######################\n\nThis configuration causes :command:`memmon` to restart any process in the\nprocess group \"bar\" consuming more than 200MB of RSS, and will send mail to\n``bob@example.com`` when it restarts a process using the default\n:command:`sendmail` command.\n\n.. code-block:: ini\n\n   [eventlistener:memmon]\n   command=memmon -g bar=200MB -m bob@example.com\n   events=TICK_60\n\n\nExample Configuration 4\n#######################\n\nThis configuration causes :command:`memmon` to restart any process meeting\nthe same requirements as in `Example Configuration 2`_ with one difference:\n\nThe email will only be sent if the process' uptime is less or equal than\n2 days (172800 seconds)\n\n.. code-block:: ini\n\n   [eventlistener:memmon]\n   command=memmon -p foo=200MB -m bob@example.com -u 2d\n   events=TICK_60\n\n\nExample Configuration 5 (Authentication)\n########################################\n\nThis configuration is the same as the one in `Example Configuration 1`_ with\nthe only difference being that the `[unix_http_server]\n<http://supervisord.org/configuration.html#unix-http-server-section-settings>`_\nor `[inet_http_server]\n<http://supervisord.org/configuration.html#inet-http-server-section-settings>`_\nhas been configured to use authentication.\n\n.. code-block:: ini\n\n   [eventlistener:memmon]\n   command=memmon -a 200MB -m bob@example.com\n   environment=SUPERVISOR_USERNAME=\"<username>\",SUPERVISOR_PASSWORD=\"<password>\"\n   events=TICK_60\n"
  },
  {
    "path": "setup.cfg",
    "content": ";Marking a wheel as universal with \"universal = 1\" was deprecated\n;in Setuptools 75.1.0.  Setting \"python_tag = py2.py3\" should do\n;the equivalent on Setuptools 30.3.0 or later.\n;\n;https://github.com/pypa/setuptools/pull/4617\n;https://github.com/pypa/setuptools/pull/4939\n;\n[bdist_wheel]\npython_tag = py2.py3\n"
  },
  {
    "path": "setup.py",
    "content": "##############################################################################\n#\n# Copyright (c) 2008-2013 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\nimport os\nimport sys\n\npy_version = sys.version_info[:2]\n\nif py_version < (2, 7):\n    raise RuntimeError('On Python 2, superlance requires Python 2.7 or later')\nelif (3, 0) < py_version < (3, 4):\n    raise RuntimeError('On Python 3, superlance requires Python 3.4 or later')\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\ntry:\n    README = open(os.path.join(here, 'README.rst')).read()\nexcept (IOError, OSError):\n    README = ''\ntry:\n    CHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\nexcept (IOError, OSError):\n    CHANGES = ''\n\nsetup(name='superlance',\n      version='2.0.1.dev0',\n      license='BSD-derived (http://www.repoze.org/LICENSE.txt)',\n      description='superlance plugins for supervisord',\n      long_description=README + '\\n\\n' +  CHANGES,\n      classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        'Environment :: No Input/Output (Daemon)',\n        'Intended Audience :: System Administrators',\n        'Natural Language :: English',\n        'Operating System :: POSIX',\n        'Programming Language :: Python :: 2',\n        'Programming Language :: Python :: 2.7',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.4',\n        'Programming Language :: Python :: 3.5',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n        'Programming Language :: Python :: 3.11',\n        'Programming Language :: Python :: 3.12',\n        'Programming Language :: Python :: 3.13',\n        'Programming Language :: Python :: 3.14',\n        'Topic :: System :: Boot',\n        'Topic :: System :: Monitoring',\n        'Topic :: System :: Systems Administration',\n        ],\n      author='Chris McDonough',\n      author_email='chrism@plope.com',\n      url='https://github.com/Supervisor/superlance',\n      keywords = 'supervisor monitoring',\n      packages = find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=['supervisor',],\n      extras_require={'test': ['pytest'],},\n      entry_points = \"\"\"\\\n      [console_scripts]\n      httpok = superlance.httpok:main\n      crashsms = superlance.crashsms:main\n      crashmail = superlance.crashmail:main\n      crashmailbatch = superlance.crashmailbatch:main\n      fatalmailbatch = superlance.fatalmailbatch:main\n      memmon = superlance.memmon:main\n      \"\"\"\n      )\n"
  },
  {
    "path": "superlance/__init__.py",
    "content": "# superlance package\n"
  },
  {
    "path": "superlance/compat.py",
    "content": "try:\n    import http.client as httplib\nexcept ImportError:\n    import httplib\n\ntry:\n    from StringIO import StringIO\nexcept ImportError:\n    from io import StringIO\n\ntry:\n    from sys import maxsize as maxint\nexcept ImportError:\n    from sys import maxint\n\ntry:\n    import urllib.parse as urlparse\n    import urllib.parse as urllib\nexcept ImportError:\n    import urlparse\n    import urllib\n\ntry:\n    import xmlrpc.client as xmlrpclib\nexcept ImportError:\n    import xmlrpclib\n"
  },
  {
    "path": "superlance/crashmail.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\n# A event listener meant to be subscribed to PROCESS_STATE_CHANGE\n# events.  It will send mail when processes that are children of\n# supervisord transition unexpectedly to the EXITED state.\n\n# A supervisor config snippet that tells supervisor to use this script\n# as a listener is below.\n#\n# [eventlistener:crashmail]\n# command =\n#     /usr/bin/crashmail\n#         -o hostname -a -m notify-on-crash@domain.com\n#         -s '/usr/sbin/sendmail -t -i -f crash-notifier@domain.com'\n# events=PROCESS_STATE\n#\n# Sendmail is used explicitly here so that we can specify the 'from' address.\n\ndoc = \"\"\"\\\ncrashmail.py [-p processname] [-a] [-o string] [-m mail_address]\n             [-s sendmail] URL\n\nOptions:\n\n-p -- specify a supervisor process_name.  Send mail when this process\n      transitions to the EXITED state unexpectedly. If this process is\n      part of a group, it can be specified using the\n      'group_name:process_name' syntax.\n\n-a -- Send mail when any child of the supervisord transitions\n      unexpectedly to the EXITED state unexpectedly.  Overrides any -p\n      parameters passed in the same crashmail process invocation.\n\n-o -- Specify a parameter used as a prefix in the mail subject header.\n\n-s -- the sendmail command to use to send email\n      (e.g. \"/usr/sbin/sendmail -t -i\").  Must be a command which accepts\n      header and message data on stdin and sends mail.  Default is\n      \"/usr/sbin/sendmail -t -i\".\n\n-m -- specify an email address.  The script will send mail to this\n      address when crashmail detects a process crash.  If no email\n      address is specified, email will not be sent.\n\nThe -p option may be specified more than once, allowing for\nspecification of multiple processes.  Specifying -a overrides any\nselection of -p.\n\nA sample invocation:\n\ncrashmail.py -p program1 -p group1:program2 -m dev@example.com\n\n\"\"\"\n\nimport getopt\nimport os\nimport sys\n\nfrom supervisor import childutils\n\n\ndef usage(exitstatus=255):\n    print(doc)\n    sys.exit(exitstatus)\n\n\nclass CrashMail:\n\n    def __init__(self, programs, any, email, sendmail, optionalheader):\n\n        self.programs = programs\n        self.any = any\n        self.email = email\n        self.sendmail = sendmail\n        self.optionalheader = optionalheader\n        self.stdin = sys.stdin\n        self.stdout = sys.stdout\n        self.stderr = sys.stderr\n\n    def runforever(self, test=False):\n        while 1:\n            # we explicitly use self.stdin, self.stdout, and self.stderr\n            # instead of sys.* so we can unit test this code\n            headers, payload = childutils.listener.wait(\n                self.stdin, self.stdout)\n\n            if not headers['eventname'] == 'PROCESS_STATE_EXITED':\n                # do nothing with non-TICK events\n                childutils.listener.ok(self.stdout)\n                if test:\n                    self.stderr.write('non-exited event\\n')\n                    self.stderr.flush()\n                    break\n                continue\n\n            pheaders, pdata = childutils.eventdata(payload+'\\n')\n\n            if int(pheaders['expected']):\n                childutils.listener.ok(self.stdout)\n                if test:\n                    self.stderr.write('expected exit\\n')\n                    self.stderr.flush()\n                    break\n                continue\n\n            msg = ('Process %(processname)s in group %(groupname)s exited '\n                   'unexpectedly (pid %(pid)s) from state %(from_state)s' %\n                   pheaders)\n\n            subject = ' %s crashed at %s' % (pheaders['processname'],\n                                             childutils.get_asctime())\n            if self.optionalheader:\n                subject = self.optionalheader + ':' + subject\n\n            self.stderr.write('unexpected exit, mailing\\n')\n            self.stderr.flush()\n\n            self.mail(self.email, subject, msg)\n\n            childutils.listener.ok(self.stdout)\n            if test:\n                break\n\n    def mail(self, email, subject, msg):\n        body = 'To: %s\\n' % self.email\n        body += 'Subject: %s\\n' % subject\n        body += '\\n'\n        body += msg\n        with os.popen(self.sendmail, 'w') as m:\n            m.write(body)\n        self.stderr.write('Mailed:\\n\\n%s' % body)\n        self.mailed = body\n\n\ndef main(argv=sys.argv):\n    short_args = \"hp:ao:s:m:\"\n    long_args = [\n        \"help\",\n        \"program=\",\n        \"any\",\n        \"optionalheader=\",\n        \"sendmail_program=\",\n        \"email=\",\n        ]\n    arguments = argv[1:]\n    try:\n        opts, args = getopt.getopt(arguments, short_args, long_args)\n    except:\n        usage()\n\n    programs = []\n    any = False\n    sendmail = '/usr/sbin/sendmail -t -i'\n    email = None\n    optionalheader = None\n\n    for option, value in opts:\n\n        if option in ('-h', '--help'):\n            usage(exitstatus=0)\n\n        if option in ('-p', '--program'):\n            programs.append(value)\n\n        if option in ('-a', '--any'):\n            any = True\n\n        if option in ('-s', '--sendmail_program'):\n            sendmail = value\n\n        if option in ('-m', '--email'):\n            email = value\n\n        if option in ('-o', '--optionalheader'):\n            optionalheader = value\n\n    if not 'SUPERVISOR_SERVER_URL' in os.environ:\n        sys.stderr.write('crashmail must be run as a supervisor event '\n                         'listener\\n')\n        sys.stderr.flush()\n        return\n\n    prog = CrashMail(programs, any, email, sendmail, optionalheader)\n    prog.runforever()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "superlance/crashmailbatch.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\n# A event listener meant to be subscribed to PROCESS_STATE_CHANGE\n# events.  It will send mail when processes that are children of\n# supervisord transition unexpectedly to the EXITED state.\n\n# A supervisor config snippet that tells supervisor to use this script\n# as a listener is below.\n#\n# [eventlistener:crashmailbatch]\n# command=python crashmailbatch --toEmail=you@bar.com --fromEmail=me@bar.com\n# events=PROCESS_STATE,TICK_60\n\ndoc = \"\"\"\\\ncrashmailbatch.py [--interval=<batch interval in minutes>]\n        [--toEmail=<email address>]\n        [--fromEmail=<email address>]\n        [--subject=<email subject>]\n        [--smtpHost=<hostname or address>]\n\nOptions:\n\n--interval  - batch cycle length (in minutes).  The default is 1.0 minute.\n                  This means that all events in each cycle are batched together\n                  and sent as a single email\n\n--toEmail   - the email address(es) to send alerts to - comma separated\n\n--fromEmail - the email address to send alerts from\n\n--subject   - the email subject line\n\n--smtpHost  - the SMTP server's hostname or address (defaults to 'localhost')\n\nA sample invocation:\n\ncrashmailbatch.py --toEmail=\"you@bar.com\" --fromEmail=\"me@bar.com\"\n\n\"\"\"\n\nfrom supervisor import childutils\nfrom superlance.process_state_email_monitor import ProcessStateEmailMonitor\n\n\nclass CrashMailBatch(ProcessStateEmailMonitor):\n\n    process_state_events = ['PROCESS_STATE_EXITED']\n\n    def __init__(self, **kwargs):\n        if kwargs.get('subject') is None:\n            kwargs['subject'] = 'Crash alert from supervisord'\n        ProcessStateEmailMonitor.__init__(self, **kwargs)\n        self.now = kwargs.get('now', None)\n\n    def get_process_state_change_msg(self, headers, payload):\n        pheaders, pdata = childutils.eventdata(payload+'\\n')\n\n        if int(pheaders['expected']):\n            return None\n\n        txt = 'Process %(groupname)s:%(processname)s (pid %(pid)s) died \\\nunexpectedly' % pheaders\n        return '%s -- %s' % (childutils.get_asctime(self.now), txt)\n\n\ndef main():\n    crash = CrashMailBatch.create_from_cmd_line()\n    crash.run()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "superlance/crashsms.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\n##############################################################################\n# crashsms\n# author: Juan Batiz-Benet (http://github.com/jbenet)\n# based on crashmailbatch.py\n##############################################################################\n\n\n# A event listener meant to be subscribed to PROCESS_STATE_CHANGE\n# events.  It will send mail when processes that are children of\n# supervisord transition unexpectedly to the EXITED state.\n\n# A supervisor config snippet that tells supervisor to use this script\n# as a listener is below.\n#\n# [eventlistener:crashsms]\n# command =\n#     python crashsms\n#         -t <mobile phone>@<mobile provider> -f me@bar.com -e TICK_5\n# events=PROCESS_STATE,TICK_5\n\ndoc = \"\"\"\\\ncrashsms.py [--interval=<batch interval in minutes>]\n        [--toEmail=<email address>]\n        [--fromEmail=<email address>]\n        [--subject=<email subject>]\n\nOptions:\n\n-i,--interval  - batch cycle length (in minutes).  The default is 1 minute.\n                 This means that all events in each cycle are batched together\n                 and sent as a single email\n\n-t,--toEmail   - the comma separated email addresses to send alerts to. Mobile providers\n                 tend to allow sms messages to be sent to their phone numbers\n                 via an email address (e.g.: 1234567890@txt.att.net)\n\n-f,--fromEmail - the email address to send alerts from\n\n-s,--subject   - the email subject line\n\n-e, --tickEvent - specify which TICK event to use (e.g. TICK_5, TICK_60,\n                  TICK_3600)\n\nA sample invocation:\n\ncrashsms.py -t <mobile phone>@<mobile provider> -f me@bar.com -e TICK_5\n\n\"\"\"\n\nfrom supervisor import childutils\nfrom superlance.process_state_email_monitor import ProcessStateEmailMonitor\n\n\nclass CrashSMS(ProcessStateEmailMonitor):\n    process_state_events = ['PROCESS_STATE_EXITED']\n\n    def __init__(self, **kwargs):\n        ProcessStateEmailMonitor.__init__(self, **kwargs)\n        self.now = kwargs.get('now', None)\n\n    def get_process_state_change_msg(self, headers, payload):\n        pheaders, pdata = childutils.eventdata(payload+'\\n')\n\n        if int(pheaders['expected']):\n            return None\n\n        txt = '[%(groupname)s:%(processname)s](%(pid)s) exited unexpectedly' \\\n              % pheaders\n        return '%s %s' % (txt, childutils.get_asctime(self.now))\n\n\ndef main():\n    crash = CrashSMS.create_from_cmd_line()\n    crash.run()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "superlance/fatalmailbatch.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\n# A event listener meant to be subscribed to PROCESS_STATE_CHANGE\n# events.  It will send mail when processes that are children of\n# supervisord transition unexpectedly to the EXITED state.\n\n# A supervisor config snippet that tells supervisor to use this script\n# as a listener is below.\n#\n# [eventlistener:fatalmailbatch]\n# command=python fatalmailbatch\n# events=PROCESS_STATE,TICK_60\n\ndoc = \"\"\"\\\nfatalmailbatch.py [--interval=<batch interval in minutes>]\n        [--toEmail=<email address>]\n        [--fromEmail=<email address>]\n        [--subject=<email subject>]\n        [--smtpHost=<smtp server>]\n        [--userName=<smtp server username>]\n        [--password=<smtp server password]\n\nOptions:\n\n--interval  - batch cycle length (in minutes).  The default is 1 minute.\n                  This means that all events in each cycle are batched together\n                  and sent as a single email\n\n--toEmail   - the email address(es) to send alerts to - comma separated\n\n--fromEmail - the email address to send alerts from\n\n--subject - the email subject line\n\nA sample invocation:\n\nfatalmailbatch.py --toEmail=\"you@bar.com\" --fromEmail=\"me@bar.com\"\n\n\"\"\"\n\nfrom supervisor import childutils\nfrom superlance.process_state_email_monitor import ProcessStateEmailMonitor\n\nclass FatalMailBatch(ProcessStateEmailMonitor):\n\n    process_state_events = ['PROCESS_STATE_FATAL']\n\n    def __init__(self, **kwargs):\n        if kwargs.get('subject') is None:\n            kwargs['subject'] = 'Fatal start alert from supervisord'\n        ProcessStateEmailMonitor.__init__(self, **kwargs)\n        self.now = kwargs.get('now', None)\n\n    def get_process_state_change_msg(self, headers, payload):\n        pheaders, pdata = childutils.eventdata(payload+'\\n')\n\n        txt = 'Process %(groupname)s:%(processname)s failed to start too many \\\ntimes' % pheaders\n        return '%s -- %s' % (childutils.get_asctime(self.now), txt)\n\ndef main():\n    fatal = FatalMailBatch.create_from_cmd_line()\n    fatal.run()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "superlance/httpok.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\n# A event listener meant to be subscribed to TICK_60 (or TICK_5)\n# events, which restarts processes that are children of\n# supervisord based on the response from an HTTP port.\n\n# A supervisor config snippet that tells supervisor to use this script\n# as a listener is below.\n#\n# [eventlistener:httpok]\n# command=python -u /bin/httpok http://localhost:8080/tasty/service\n# events=TICK_60\n\ndoc = \"\"\"\\\nhttpok.py [-p processname] [-a] [-g] [-t timeout] [-c status_code] [-b inbody]\n          [-m mail_address] [-s sendmail] URL\n\nOptions:\n\n-p -- specify a supervisor process_name.  Restart the supervisor\n      process named 'process_name' if it's in the RUNNING state when\n      the URL returns an unexpected result or times out.  If this\n      process is part of a group, it can be specified using the\n      'group_name:process_name' syntax.\n\n-a -- Restart any child of the supervisord under in the RUNNING state\n      if the URL returns an unexpected result or times out.  Overrides\n      any -p parameters passed in the same httpok process\n      invocation.\n\n-g -- The ``gcore`` program.  By default, this is ``/usr/bin/gcore\n      -o``.  The program should accept two arguments on the command\n      line: a filename and a pid.\n\n-d -- Core directory.  If a core directory is specified, httpok will\n      try to use the ``gcore`` program (see ``-g``) to write a core\n      file into this directory against each hung process before we\n      restart it.  Append gcore stdout output to email.\n\n-t -- The number of seconds that httpok should wait for a response\n      before timing out.  If this timeout is exceeded, httpok will\n      attempt to restart processes in the RUNNING state specified by\n      -p or -a.  This defaults to 10 seconds.\n\n-c -- specify an expected HTTP status code from a GET request to the\n      URL.  If this status code is not the status code provided by the\n      response, httpok will attempt to restart processes in the\n      RUNNING state specified by -p or -a.  Default is 200.\n\n-b -- specify a string which should be present in the body resulting\n      from the GET request.  If this string is not present in the\n      response, the processes in the RUNNING state specified by -p\n      or -a will be restarted.  The default is to ignore the\n      body.\n\n-s -- the sendmail command to use to send email\n      (e.g. \"/usr/sbin/sendmail -t -i\").  Must be a command which accepts\n      header and message data on stdin and sends mail.\n      Default is \"/usr/sbin/sendmail -t -i\".\n\n-m -- specify an email address.  The script will send mail to this\n      address when httpok attempts to restart processes.  If no email\n      address is specified, email will not be sent.\n\n-e -- \"eager\":  check URL / emit mail even if no process we are monitoring\n      is in the RUNNING state.  Enabled by default.\n\n-E -- not \"eager\":  do not check URL / emit mail if no process we are\n      monitoring is in the RUNNING state.\n\n-n -- optionally specify the name of the httpok process.  This name will\n      be used in the email subject to identify which httpok process\n      restarted the process.\n\nURL -- The URL to which to issue a GET request.\n\nThe -c option may be specified more than once, allowing for\nspecification of multiple expected HTTP status codes.\n\nThe -p option may be specified more than once, allowing for\nspecification of multiple processes.  Specifying -a overrides any\nselection of -p.\n\nA sample invocation:\n\nhttpok.py -p program1 -p group1:program2 http://localhost:8080/tasty\n\n\"\"\"\n\nimport getopt\nimport os\nimport socket\nimport sys\nimport time\nfrom superlance.compat import urlparse\nfrom superlance.compat import xmlrpclib\n\nfrom supervisor import childutils\nfrom supervisor.states import ProcessStates\nfrom supervisor.options import make_namespec\n\nfrom superlance import timeoutconn\n\ndef usage(exitstatus=255):\n    print(doc)\n    sys.exit(exitstatus)\n\nclass HTTPOk:\n    connclass = None\n    def __init__(self, rpc, programs, any, url, timeout, statuses, inbody,\n                 email, sendmail, coredir, gcore, eager, retry_time, name):\n        self.rpc = rpc\n        self.programs = programs\n        self.any = any\n        self.url = url\n        self.timeout = timeout\n        self.retry_time = retry_time\n        self.statuses = statuses\n        self.inbody = inbody\n        self.email = email\n        self.sendmail = sendmail\n        self.coredir = coredir\n        self.gcore = gcore\n        self.eager = eager\n        self.stdin = sys.stdin\n        self.stdout = sys.stdout\n        self.stderr = sys.stderr\n        self.name = name\n\n    def listProcesses(self, state=None):\n        return [x for x in self.rpc.supervisor.getAllProcessInfo()\n                   if x['name'] in self.programs and\n                      (state is None or x['state'] == state)]\n\n    def runforever(self, test=False):\n        parsed = urlparse.urlsplit(self.url)\n        scheme = parsed.scheme.lower()\n        hostport = parsed.netloc\n        path = parsed.path\n        query = parsed.query\n\n        if query:\n            path += '?' + query\n\n        if self.connclass:\n            ConnClass = self.connclass\n        elif scheme == 'http':\n            ConnClass = timeoutconn.TimeoutHTTPConnection\n        elif scheme == 'https':\n            ConnClass = timeoutconn.TimeoutHTTPSConnection\n        else:\n            raise ValueError('Bad scheme %s' % scheme)\n\n        while 1:\n            # we explicitly use self.stdin, self.stdout, and self.stderr\n            # instead of sys.* so we can unit test this code\n            headers, payload = childutils.listener.wait(self.stdin, self.stdout)\n\n            if not headers['eventname'].startswith('TICK'):\n                # do nothing with non-TICK events\n                childutils.listener.ok(self.stdout)\n                if test:\n                    break\n                continue\n\n            conn = ConnClass(hostport)\n            conn.timeout = self.timeout\n\n            specs = self.listProcesses(ProcessStates.RUNNING)\n            if self.eager or len(specs) > 0:\n\n                try:\n                    # build a loop value that is guaranteed to execute at least\n                    # once and at most until the timeout is reached and that\n                    # has 0 as the last value (to allow raising an exception\n                    # in the last iteration)\n                    for will_retry in range(\n                            (self.timeout - 1) // (self.retry_time or 1),\n                            -1, -1):\n                        try:\n                            headers = {'User-Agent': 'httpok'}\n                            conn.request('GET', path, headers=headers)\n                            break\n                        except socket.error as e:\n                            if e.errno == 111 and will_retry:\n                                time.sleep(self.retry_time)\n                            else:\n                                raise\n\n                    res = conn.getresponse()\n                    body = res.read()\n                    status = res.status\n                    msg = 'status contacting %s: %s %s' % (self.url,\n                                                           res.status,\n                                                           res.reason)\n                except Exception as e:\n                    body = ''\n                    status = None\n                    msg = 'error contacting %s:\\n\\n %s' % (self.url, e)\n\n                if status not in self.statuses:\n                    subject = self.format_subject(\n                        '%s: bad status returned' % self.url\n                        )\n                    self.act(subject, msg)\n                elif self.inbody and self.inbody not in body:\n                    subject = self.format_subject(\n                        '%s: bad body returned' % self.url\n                    )\n                    self.act(subject, msg)\n\n            childutils.listener.ok(self.stdout)\n            if test:\n                break\n\n    def format_subject(self, subject):\n        if self.name is None:\n            return 'httpok: %s' % subject\n        else:\n            return 'httpok [%s]: %s' % (self.name, subject)\n\n    def act(self, subject, msg):\n        messages = [msg]\n\n        def write(msg):\n            self.stderr.write('%s\\n' % msg)\n            self.stderr.flush()\n            messages.append(msg)\n\n        try:\n            specs = self.rpc.supervisor.getAllProcessInfo()\n        except Exception as e:\n            write('Exception retrieving process info %s, not acting' % e)\n            return\n\n        waiting = list(self.programs)\n\n        if self.any:\n            write('Restarting all running processes')\n            for spec in specs:\n                name = spec['name']\n                group = spec['group']\n                self.restart(spec, write)\n                namespec = make_namespec(group, name)\n                if name in waiting:\n                    waiting.remove(name)\n                if namespec in waiting:\n                    waiting.remove(namespec)\n        else:\n            write('Restarting selected processes %s' % self.programs)\n            for spec in specs:\n                name = spec['name']\n                group = spec['group']\n                namespec = make_namespec(group, name)\n                if (name in self.programs) or (namespec in self.programs):\n                    self.restart(spec, write)\n                    if name in waiting:\n                        waiting.remove(name)\n                    if namespec in waiting:\n                        waiting.remove(namespec)\n\n        if waiting:\n            write(\n                'Programs not restarted because they did not exist: %s' %\n                waiting)\n\n        if self.email:\n            message = '\\n'.join(messages)\n            self.mail(self.email, subject, message)\n\n    def mail(self, email, subject, msg):\n        body =  'To: %s\\n' % self.email\n        body += 'Subject: %s\\n' % subject\n        body += '\\n'\n        body += msg\n        with os.popen(self.sendmail, 'w') as m:\n            m.write(body)\n        self.stderr.write('Mailed:\\n\\n%s' % body)\n        self.mailed = body\n\n    def restart(self, spec, write):\n        namespec = make_namespec(spec['group'], spec['name'])\n        if spec['state'] is ProcessStates.RUNNING:\n            if self.coredir and self.gcore:\n                corename = os.path.join(self.coredir, namespec)\n                cmd = self.gcore + ' \"%s\" %s' % (corename, spec['pid'])\n                with os.popen(cmd) as m:\n                    write('gcore output for %s:\\n\\n %s' % (\n                        namespec, m.read()))\n            write('%s is in RUNNING state, restarting' % namespec)\n            try:\n                self.rpc.supervisor.stopProcess(namespec)\n            except xmlrpclib.Fault as e:\n                write('Failed to stop process %s: %s' % (\n                    namespec, e))\n\n            try:\n                self.rpc.supervisor.startProcess(namespec)\n            except xmlrpclib.Fault as e:\n                write('Failed to start process %s: %s' % (\n                    namespec, e))\n            else:\n                write('%s restarted' % namespec)\n\n        else:\n            write('%s not in RUNNING state, NOT restarting' % namespec)\n\n\ndef main(argv=sys.argv):\n    short_args=\"hp:at:c:b:s:m:g:d:eEn:\"\n    long_args=[\n        \"help\",\n        \"program=\",\n        \"any\",\n        \"timeout=\",\n        \"code=\",\n        \"body=\",\n        \"sendmail_program=\",\n        \"email=\",\n        \"gcore=\",\n        \"coredir=\",\n        \"eager\",\n        \"not-eager\",\n        \"name=\",\n        ]\n    arguments = argv[1:]\n    try:\n        opts, args = getopt.getopt(arguments, short_args, long_args)\n    except:\n        usage()\n\n    # check for -h must be done before positional args check\n    for option, value in opts:\n        if option in ('-h', '--help'):\n            usage(exitstatus=0)\n\n    if not args:\n        usage()\n    if len(args) > 1:\n        usage()\n\n    programs = []\n    any = False\n    sendmail = '/usr/sbin/sendmail -t -i'\n    gcore = '/usr/bin/gcore -o'\n    coredir = None\n    eager = True\n    email = None\n    timeout = 10\n    retry_time = 10\n    statuses = []\n    inbody = None\n    name = None\n\n    for option, value in opts:\n\n        if option in ('-p', '--program'):\n            programs.append(value)\n\n        if option in ('-a', '--any'):\n            any = True\n\n        if option in ('-s', '--sendmail_program'):\n            sendmail = value\n\n        if option in ('-m', '--email'):\n            email = value\n\n        if option in ('-t', '--timeout'):\n            timeout = int(value)\n\n        if option in ('-c', '--code'):\n            statuses.append(int(value))\n\n        if option in ('-b', '--body'):\n            inbody = value\n\n        if option in ('-g', '--gcore'):\n            gcore = value\n\n        if option in ('-d', '--coredir'):\n            coredir = value\n\n        if option in ('-e', '--eager'):\n            eager = True\n\n        if option in ('-E', '--not-eager'):\n            eager = False\n\n        if option in ('-n', '--name'):\n            name = value\n\n    if not statuses:\n        statuses = [200]\n\n    url = arguments[-1]\n\n    try:\n        rpc = childutils.getRPCInterface(os.environ)\n    except KeyError as e:\n        if e.args[0] != 'SUPERVISOR_SERVER_URL':\n            raise\n        sys.stderr.write('httpok must be run as a supervisor event '\n                         'listener\\n')\n        sys.stderr.flush()\n        return\n\n    prog = HTTPOk(rpc, programs, any, url, timeout, statuses, inbody, email,\n                  sendmail, coredir, gcore, eager, retry_time, name)\n    prog.runforever()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "superlance/memmon.py",
    "content": "#!/usr/bin/env python\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\n# A event listener meant to be subscribed to TICK_60 (or TICK_5)\n# events, which restarts any processes that are children of\n# supervisord that consume \"too much\" memory.  Performs horrendous\n# screenscrapes of ps output.  Works on Linux and OS X (Tiger/Leopard)\n# as far as I know.\n\n# A supervisor config snippet that tells supervisor to use this script\n# as a listener is below.\n#\n# [eventlistener:memmon]\n# command=python memmon.py [options]\n# events=TICK_60\n\ndoc = \"\"\"\\\nmemmon.py [-c] [-p processname=byte_size] [-g groupname=byte_size]\n          [-a byte_size] [-s sendmail] [-m email_address]\n          [-u uptime] [-n memmon_name]\n\nOptions:\n\n-c -- Check against cumulative RSS. When calculating a process' RSS, also\n      consider its child processes. With this option `memmon` will sum up\n      the RSS of the process to be monitored and all its children.\n\n-p -- specify a process_name=byte_size pair.  Restart the supervisor\n      process named 'process_name' when it uses more than byte_size\n      RSS.  If this process is in a group, it can be specified using\n      the 'group_name:process_name' syntax.\n\n-g -- specify a group_name=byte_size pair.  Restart any process in this group\n      when it uses more than byte_size RSS.\n\n-a -- specify a global byte_size.  Restart any child of the supervisord\n      under which this runs if it uses more than byte_size RSS.\n\n-s -- the sendmail command to use to send email\n      (e.g. \"/usr/sbin/sendmail -t -i\").  Must be a command which accepts\n      header and message data on stdin and sends mail.\n      Default is \"/usr/sbin/sendmail -t -i\".\n\n-m -- specify an email address.  The script will send mail to this\n      address when any process is restarted.  If no email address is\n      specified, email will not be sent.\n\n-u -- optionally specify the minimum uptime in seconds for the process.\n      if the process uptime is longer than this value, no email is sent\n      (useful to only be notified if processes are restarted too often/early)\n\n      seconds can be specified as plain integer values or a suffix-multiplied\n      integer (e.g. 1m). Valid suffixes are m (minute), h (hour) and d (day).\n\n-n -- optionally specify the name of the memmon process.  This name will\n      be used in the email subject to identify which memmon process\n      restarted the process.\n\nThe -p and -g options may be specified more than once, allowing for\nspecification of multiple groups and processes.\n\nAny byte_size can be specified as a plain integer (10000) or a\nsuffix-multiplied integer (e.g. 1GB).  Valid suffixes are 'KB', 'MB'\nand 'GB'.\n\nA sample invocation:\n\nmemmon.py -p program1=200MB -p theprog:thegroup=100MB -g thegroup=100MB -a 1GB -s \"/usr/sbin/sendmail -t -i\" -m chrism@plope.com -n \"Project 1\"\n\"\"\"\n\nimport getopt\nimport os\nimport sys\nimport time\nfrom collections import namedtuple\nfrom superlance.compat import maxint\nfrom superlance.compat import xmlrpclib\n\nfrom supervisor import childutils\nfrom supervisor.datatypes import byte_size, SuffixMultiplier\n\ndef usage(exitstatus=255):\n    print(doc)\n    sys.exit(exitstatus)\n\ndef shell(cmd):\n    with os.popen(cmd) as f:\n        return f.read()\n\nclass Memmon:\n    def __init__(self, cumulative, programs, groups, any, sendmail, email, email_uptime_limit, name, rpc=None):\n        self.cumulative = cumulative\n        self.programs = programs\n        self.groups = groups\n        self.any = any\n        self.sendmail = sendmail\n        self.email = email\n        self.email_uptime_limit = email_uptime_limit\n        self.name = name\n        self.rpc = rpc\n        self.stdin = sys.stdin\n        self.stdout = sys.stdout\n        self.stderr = sys.stderr\n        self.pscommand = 'ps -orss= -p %s'\n        self.pstreecommand = 'ps ax -o \"pid= ppid= rss=\"'\n        self.mailed = False # for unit tests\n\n    def runforever(self, test=False):\n        while 1:\n            # we explicitly use self.stdin, self.stdout, and self.stderr\n            # instead of sys.* so we can unit test this code\n            headers, payload = childutils.listener.wait(self.stdin, self.stdout)\n\n            if not headers['eventname'].startswith('TICK'):\n                # do nothing with non-TICK events\n                childutils.listener.ok(self.stdout)\n                if test:\n                    break\n                continue\n\n            status = []\n            if self.programs:\n                keys = sorted(self.programs.keys())\n                status.append(\n                    'Checking programs %s' % ', '.join(\n                    [ '%s=%s' % (k, self.programs[k]) for k in keys ])\n                    )\n\n            if self.groups:\n                keys = sorted(self.groups.keys())\n                status.append(\n                    'Checking groups %s' % ', '.join(\n                    [ '%s=%s' % (k, self.groups[k]) for k in keys ])\n                    )\n            if self.any is not None:\n                status.append('Checking any=%s' % self.any)\n\n            self.stderr.write('\\n'.join(status) + '\\n')\n\n            infos = self.rpc.supervisor.getAllProcessInfo()\n\n            for info in infos:\n                pid = info['pid']\n                name = info['name']\n                group = info['group']\n                pname = '%s:%s' % (group, name)\n\n                if not pid:\n                    # ps throws an error in this case (for processes\n                    # in standby mode, non-auto-started).\n                    continue\n\n                rss = self.calc_rss(pid)\n                if rss is None:\n                    # no such pid (deal with race conditions) or\n                    # rss couldn't be calculated for other reasons\n                    continue\n\n                for n in name, pname:\n                    if n in self.programs:\n                        self.stderr.write('RSS of %s is %s\\n' % (pname, rss))\n                        if  rss > self.programs[name]:\n                            self.restart(pname, rss)\n                            continue\n\n                if group in self.groups:\n                    self.stderr.write('RSS of %s is %s\\n' % (pname, rss))\n                    if rss > self.groups[group]:\n                        self.restart(pname, rss)\n                        continue\n\n                if self.any is not None:\n                    self.stderr.write('RSS of %s is %s\\n' % (pname, rss))\n                    if rss > self.any:\n                        self.restart(pname, rss)\n                        continue\n\n            self.stderr.flush()\n            childutils.listener.ok(self.stdout)\n            if test:\n                break\n\n    def restart(self, name, rss):\n        info = self.rpc.supervisor.getProcessInfo(name)\n        uptime = info['now'] - info['start'] #uptime in seconds\n        self.stderr.write('Restarting %s\\n' % name)\n        try:\n            self.rpc.supervisor.stopProcess(name)\n        except xmlrpclib.Fault as e:\n            msg = ('Failed to stop process %s (RSS %s), exiting: %s' %\n                   (name, rss, e))\n            self.stderr.write(str(msg))\n            if self.email:\n                subject = self.format_subject(\n                    'failed to stop process %s, exiting' % name\n                    )\n                self.mail(self.email, subject, msg)\n            raise\n\n        try:\n            self.rpc.supervisor.startProcess(name)\n        except xmlrpclib.Fault as e:\n            msg = ('Failed to start process %s after stopping it, '\n                   'exiting: %s' % (name, e))\n            self.stderr.write(str(msg))\n            if self.email:\n                subject = self.format_subject(\n                    'failed to start process %s, exiting' % name\n                )\n                self.mail(self.email, subject, msg)\n            raise\n\n        if self.email and uptime <= self.email_uptime_limit:\n            now = time.asctime()\n            timezone = time.strftime('%Z')\n            msg = (\n                'memmon.py restarted the process named %s at %s %s because '\n                'it was consuming too much memory (%s bytes RSS)' % (\n                name, now, timezone, rss)\n                )\n            subject = self.format_subject(\n                'process %s restarted' % name\n                )\n            self.mail(self.email, subject, msg)\n\n    def format_subject(self, subject):\n        if self.name is None:\n            return 'memmon: %s' % subject\n        else:\n            return 'memmon [%s]: %s' % (self.name, subject)\n\n    def calc_rss(self, pid):\n        ProcInfo = namedtuple('ProcInfo', ['pid', 'ppid', 'rss'])\n\n        def find_children(parent_pid, procs):\n            children = []\n            for proc in procs:\n                pid, ppid, rss = proc\n                if ppid == parent_pid:\n                    children.append(proc)\n                    children.extend(find_children(pid, procs))\n            return children\n\n        def cum_rss(pid, procs):\n            parent_proc = [p for p in procs if p.pid == pid][0]\n            children = find_children(pid, procs)\n            tree = [parent_proc] + children\n            total_rss = sum(map(int, [p.rss for p in tree]))\n            return total_rss\n\n        def get_all_process_infos(data):\n            data = data.strip()\n            procs = []\n            for line in data.splitlines():\n                pid, ppid, rss = map(int, line.split())\n                procs.append(ProcInfo(pid=pid, ppid=ppid, rss=rss))\n            return procs\n\n        if self.cumulative:\n            data = shell(self.pstreecommand)\n            procs = get_all_process_infos(data)\n\n            try:\n                rss = cum_rss(pid, procs)\n            except (ValueError, IndexError):\n                # Could not determine cumulative RSS\n                return None\n\n        else:\n            data = shell(self.pscommand % pid)\n            if not data:\n                # no such pid (deal with race conditions)\n                return None\n\n            try:\n                rss = data.lstrip().rstrip()\n                rss = int(rss)\n            except ValueError:\n                # line doesn't contain any data, or rss cant be intified\n                return None\n\n        rss = rss * 1024  # rss is in KB\n        return rss\n\n    def mail(self, email, subject, msg):\n        body = 'To: %s\\n' % self.email\n        body += 'Subject: %s\\n' % subject\n        body += '\\n'\n        body += msg\n        with os.popen(self.sendmail, 'w') as m:\n            m.write(body)\n        self.mailed = body\n\ndef parse_namesize(option, value):\n    try:\n        name, size = value.split('=')\n    except ValueError:\n        print('Unparseable value %r for %r' % (value, option))\n        usage()\n    size = parse_size(option, size)\n    return name, size\n\ndef parse_size(option, value):\n    try:\n        size = byte_size(value)\n    except:\n        print('Unparseable byte_size in %r for %r' % (value, option))\n        usage()\n\n    return size\n\nseconds_size = SuffixMultiplier({'s': 1,\n                                 'm': 60,\n                                 'h': 60 * 60,\n                                 'd': 60 * 60 * 24\n                                 })\n\ndef parse_seconds(option, value):\n    try:\n        seconds = seconds_size(value)\n    except:\n        print('Unparseable value for time in %r for %s' % (value, option))\n        usage()\n    return seconds\n\nhelp_request = object()  # returned from memmon_from_args to indicate --help\n\ndef memmon_from_args(arguments):\n    short_args = \"hcp:g:a:s:m:n:u:\"\n    long_args = [\n        \"help\",\n        \"cumulative\",\n        \"program=\",\n        \"group=\",\n        \"any=\",\n        \"sendmail_program=\",\n        \"email=\",\n        \"uptime=\",\n        \"name=\",\n        ]\n\n    if not arguments:\n        return None\n    try:\n        opts, args = getopt.getopt(arguments, short_args, long_args)\n    except:\n        return None\n\n    cumulative = False\n    programs = {}\n    groups = {}\n    any = None\n    sendmail = '/usr/sbin/sendmail -t -i'\n    email = None\n    uptime_limit = maxint\n    name = None\n\n    for option, value in opts:\n\n        if option in ('-h', '--help'):\n            return help_request\n\n        if option in ('-c', '--cumulative'):\n            cumulative = True\n\n        if option in ('-p', '--program'):\n            name, size = parse_namesize(option, value)\n            programs[name] = size\n\n        if option in ('-g', '--group'):\n            name, size = parse_namesize(option, value)\n            groups[name] = size\n\n        if option in ('-a', '--any'):\n            size = parse_size(option, value)\n            any = size\n\n        if option in ('-s', '--sendmail_program'):\n            sendmail = value\n\n        if option in ('-m', '--email'):\n            email = value\n\n        if option in ('-u', '--uptime'):\n            uptime_limit = parse_seconds(option, value)\n\n        if option in ('-n', '--name'):\n            name = value\n\n    memmon = Memmon(cumulative=cumulative,\n                    programs=programs,\n                    groups=groups,\n                    any=any,\n                    sendmail=sendmail,\n                    email=email,\n                    email_uptime_limit=uptime_limit,\n                    name=name)\n    return memmon\n\ndef main():\n    memmon = memmon_from_args(sys.argv[1:])\n    if memmon is help_request:  # --help\n        usage(exitstatus=0)\n    elif memmon is None:  # something went wrong\n        usage()\n    memmon.rpc = childutils.getRPCInterface(os.environ)\n    memmon.runforever()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "superlance/process_state_email_monitor.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\nimport copy\nimport optparse\nimport os\nimport smtplib\nimport sys\n\nfrom email.mime.text import MIMEText\nfrom email.utils import formatdate, make_msgid\nfrom superlance.process_state_monitor import ProcessStateMonitor\n\ndoc = \"\"\"\\\nBase class for common functionality when monitoring process state changes\nand sending email notification\n\"\"\"\n\nclass ProcessStateEmailMonitor(ProcessStateMonitor):\n    COMMASPACE = ', '\n\n    @classmethod\n    def _get_opt_parser(cls):\n        parser = optparse.OptionParser()\n        parser.add_option(\"-i\", \"--interval\", dest=\"interval\", type=\"float\", default=1.0,\n                        help=\"batch interval in minutes (defaults to 1 minute)\")\n        parser.add_option(\"-t\", \"--toEmail\", dest=\"to_emails\",\n                        help=\"destination email address(es) - comma separated\")\n        parser.add_option(\"-f\", \"--fromEmail\", dest=\"from_email\",\n                        help=\"source email address\")\n        parser.add_option(\"-s\", \"--subject\", dest=\"subject\",\n                        help=\"email subject\")\n        parser.add_option(\"-H\", \"--smtpHost\", dest=\"smtp_host\", default=\"localhost\",\n                        help=\"SMTP server hostname or address\")\n        parser.add_option(\"-e\", \"--tickEvent\", dest=\"eventname\", default=\"TICK_60\",\n                        help=\"TICK event name (defaults to TICK_60)\")\n        parser.add_option(\"-u\", \"--userName\", dest=\"smtp_user\", default=\"\",\n                        help=\"SMTP server user name (defaults to nothing)\")\n        parser.add_option(\"-p\", \"--password\", dest=\"smtp_password\", default=\"\",\n                        help=\"SMTP server password (defaults to nothing)\")\n        parser.add_option(\"--tls\", dest=\"use_tls\", action=\"store_true\", default=False,\n                        help=\"Use Transport Layer Security (TLS), default to False\")\n        return parser\n\n    @classmethod\n    def parse_cmd_line_options(cls):\n        parser = cls._get_opt_parser()\n        (options, args) = parser.parse_args()\n        return options\n\n    @classmethod\n    def validate_cmd_line_options(cls, options):\n        parser = cls._get_opt_parser()\n        if not options.to_emails:\n            parser.print_help()\n            sys.exit(1)\n        if not options.from_email:\n            parser.print_help()\n            sys.exit(1)\n\n        validated = copy.copy(options)\n        validated.to_emails = [x.strip() for x in options.to_emails.split(\",\")]\n        return validated\n\n    @classmethod\n    def get_cmd_line_options(cls):\n        return cls.validate_cmd_line_options(cls.parse_cmd_line_options())\n\n    @classmethod\n    def create_from_cmd_line(cls):\n        options = cls.get_cmd_line_options()\n\n        if not 'SUPERVISOR_SERVER_URL' in os.environ:\n            sys.stderr.write('Must run as a supervisor event listener\\n')\n            sys.exit(1)\n\n        return cls(**options.__dict__)\n\n    def __init__(self, **kwargs):\n        ProcessStateMonitor.__init__(self, **kwargs)\n\n        self.from_email = kwargs['from_email']\n        self.to_emails = kwargs['to_emails']\n        self.subject = kwargs.get('subject')\n        self.smtp_host = kwargs.get('smtp_host', 'localhost')\n        self.smtp_user = kwargs.get('smtp_user')\n        self.smtp_password = kwargs.get('smtp_password')\n        self.use_tls = kwargs.get('use_tls')\n        self.digest_len = 76\n\n    def send_batch_notification(self):\n        email = self.get_batch_email()\n        if email:\n            self.send_email(email)\n            self.log_email(email)\n\n    def log_email(self, email):\n        email_for_log = copy.copy(email)\n        email_for_log['to'] = self.COMMASPACE.join(email['to'])\n        if len(email_for_log['body']) > self.digest_len:\n            email_for_log['body'] = '%s...' % email_for_log['body'][:self.digest_len]\n        self.write_stderr(\"Sending notification email:\\nTo: %(to)s\\n\\\nFrom: %(from)s\\nSubject: %(subject)s\\nBody:\\n%(body)s\\n\" % email_for_log)\n\n    def get_batch_email(self):\n        if len(self.batchmsgs):\n            return {\n                'to': self.to_emails,\n                'from': self.from_email,\n                'subject': self.subject,\n                'body': '\\n'.join(self.get_batch_msgs()),\n            }\n        return None\n\n    def send_email(self, email):\n        msg = MIMEText(email['body'])\n        if self.subject:\n          msg['Subject'] = email['subject']\n        msg['From'] = email['from']\n        msg['To'] = self.COMMASPACE.join(email['to'])\n        msg['Date'] = formatdate()\n        msg['Message-ID'] = make_msgid()\n\n        try:\n            self.send_smtp(msg, email['to'])\n        except Exception as e:\n            self.write_stderr(\"Error sending email: %s\\n\" % e)\n\n    def send_smtp(self, mime_msg, to_emails):\n        s = smtplib.SMTP(self.smtp_host)\n        try:\n            if self.smtp_user and self.smtp_password:\n                if self.use_tls:\n                    s.starttls()\n                s.login(self.smtp_user, self.smtp_password)\n            s.sendmail(mime_msg['From'], to_emails, mime_msg.as_string())\n        except:\n            s.quit()\n            raise\n        s.quit()\n\n"
  },
  {
    "path": "superlance/process_state_monitor.py",
    "content": "#!/usr/bin/env python -u\n##############################################################################\n#\n# Copyright (c) 2007 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\ndoc = \"\"\"\\\nBase class for common functionality when monitoring process state changes\n\"\"\"\n\nimport sys\n\nfrom supervisor import childutils\n\nclass ProcessStateMonitor:\n\n    # In child class, define a list of events to monitor\n    process_state_events = []\n\n    def __init__(self, **kwargs):\n        self.interval = kwargs.get('interval', 1.0)\n\n        self.debug = kwargs.get('debug', False)\n        self.stdin = kwargs.get('stdin', sys.stdin)\n        self.stdout = kwargs.get('stdout', sys.stdout)\n        self.stderr = kwargs.get('stderr', sys.stderr)\n        self.eventname = kwargs.get('eventname', 'TICK_60')\n        self.tickmins = self._get_tick_mins(self.eventname)\n\n        self.batchmsgs = []\n        self.batchmins = 0.0\n\n    def _get_tick_mins(self, eventname):\n        return float(self._get_tick_secs(eventname))/60.0\n\n    def _get_tick_secs(self, eventname):\n        self._validate_tick_name(eventname)\n        return int(eventname.split('_')[1])\n\n    def _validate_tick_name(self, eventname):\n        if not eventname.startswith('TICK_'):\n            raise ValueError(\"Invalid TICK event name: %s\" % eventname)\n\n    def run(self):\n        while 1:\n            hdrs, payload = childutils.listener.wait(self.stdin, self.stdout)\n            self.handle_event(hdrs, payload)\n            childutils.listener.ok(self.stdout)\n\n    def handle_event(self, headers, payload):\n        if headers['eventname'] in self.process_state_events:\n            self.handle_process_state_change_event(headers, payload)\n        elif headers['eventname'] == self.eventname:\n            self.handle_tick_event(headers, payload)\n\n    def handle_process_state_change_event(self, headers, payload):\n        msg = self.get_process_state_change_msg(headers, payload)\n        if msg:\n            self.write_stderr('%s\\n' % msg)\n            self.batchmsgs.append(msg)\n\n    \"\"\"\n    Override this method in child classes to customize messaging\n    \"\"\"\n    def get_process_state_change_msg(self, headers, payload):\n        return None\n\n    def handle_tick_event(self, headers, payload):\n        self.batchmins += self.tickmins\n        if self.batchmins >= self.interval:\n            self.send_batch_notification()\n            self.clear_batch()\n\n    \"\"\"\n    Override this method in child classes to send notification\n    \"\"\"\n    def send_batch_notification(self):\n        pass\n\n    def get_batch_minutes(self):\n        return self.batchmins\n\n    def get_batch_msgs(self):\n        return self.batchmsgs\n\n    def clear_batch(self):\n        self.batchmins = 0.0\n        self.batchmsgs = []\n\n    def write_stderr(self, msg):\n        self.stderr.write(msg)\n        self.stderr.flush()\n"
  },
  {
    "path": "superlance/tests/__init__.py",
    "content": ""
  },
  {
    "path": "superlance/tests/dummy.py",
    "content": "import time\nfrom supervisor.process import ProcessStates\n\n_NOW = time.time()\n\nclass DummyRPCServer:\n    def __init__(self):\n        self.supervisor = DummySupervisorRPCNamespace()\n        self.system = DummySystemRPCNamespace()\n\nclass DummyResponse:\n    status = 200\n    reason = 'OK'\n    body = 'OK'\n    def read(self):\n        return self.body\n\nclass DummySystemRPCNamespace:\n    pass\n\nclass DummySupervisorRPCNamespace:\n    _restartable = True\n    _restarted = False\n    _shutdown = False\n    _readlog_error = False\n\n\n    all_process_info = [\n        {\n        'name':'foo',\n        'group':'foo',\n        'pid':11,\n        'state':ProcessStates.RUNNING,\n        'statename':'RUNNING',\n        'start':_NOW - 100,\n        'stop':0,\n        'spawnerr':'',\n        'now':_NOW,\n        'description':'foo description',\n        },\n        {\n        'name':'bar',\n        'group':'bar',\n        'pid':12,\n        'state':ProcessStates.FATAL,\n        'statename':'FATAL',\n        'start':_NOW - 100,\n        'stop':_NOW - 50,\n        'spawnerr':'screwed',\n        'now':_NOW,\n        'description':'bar description',\n        },\n        {\n        'name':'baz_01',\n        'group':'baz',\n        'pid':12,\n        'state':ProcessStates.STOPPED,\n        'statename':'STOPPED',\n        'start':_NOW - 100,\n        'stop':_NOW - 25,\n        'spawnerr':'',\n        'now':_NOW,\n        'description':'baz description',\n        },\n        ]\n\n    def getAllProcessInfo(self):\n        return self.all_process_info\n\n    def getProcessInfo(self, name):\n        for info in self.all_process_info:\n            if info['name'] == name or name == '%s:%s' %(info['group'], info['name']):\n                return info\n        return None\n\n    def startProcess(self, name):\n        from supervisor import xmlrpc\n        from superlance.compat import xmlrpclib\n        if name.endswith('SPAWN_ERROR'):\n            raise xmlrpclib.Fault(xmlrpc.Faults.SPAWN_ERROR, 'SPAWN_ERROR')\n        return True\n\n    def stopProcess(self, name):\n        from supervisor import xmlrpc\n        from superlance.compat import xmlrpclib\n        if name == 'BAD_NAME:BAD_NAME':\n            raise xmlrpclib.Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME:BAD_NAME')\n        if name.endswith('FAILED'):\n            raise xmlrpclib.Fault(xmlrpc.Faults.FAILED, 'FAILED')\n        return True\n\n"
  },
  {
    "path": "superlance/tests/test_crashmail.py",
    "content": "import unittest\nfrom superlance.compat import StringIO\n\nclass CrashMailTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from superlance.crashmail import CrashMail\n        return CrashMail\n\n    def _makeOne(self, *opts):\n        return self._getTargetClass()(*opts)\n\n    def setUp(self):\n        import tempfile\n        self.tempdir = tempfile.mkdtemp()\n\n    def tearDown(self):\n        import shutil\n        shutil.rmtree(self.tempdir)\n\n    def _makeOnePopulated(self, programs, any, response=None):\n        import os\n        sendmail = 'cat - > %s' % os.path.join(self.tempdir, 'email.log')\n        email = 'chrism@plope.com'\n        header = '[foo]'\n        prog = self._makeOne(programs, any, email, sendmail, header)\n        prog.stdin = StringIO()\n        prog.stdout = StringIO()\n        prog.stderr = StringIO()\n        return prog\n\n    def test_runforever_not_process_state_exited(self):\n        programs = {'foo':0, 'bar':0, 'baz_01':0 }\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        prog.stdin.write('eventname:PROCESS_STATE len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        self.assertEqual(prog.stderr.getvalue(), 'non-exited event\\n')\n\n    def test_runforever_expected_exit(self):\n        programs = ['foo']\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        payload=('expected:1 processname:foo groupname:bar '\n                 'from_state:RUNNING pid:1')\n        prog.stdin.write(\n            'eventname:PROCESS_STATE_EXITED len:%s\\n' % len(payload))\n        prog.stdin.write(payload)\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        self.assertEqual(prog.stderr.getvalue(), 'expected exit\\n')\n\n    def test_runforever_unexpected_exit(self):\n        programs = ['foo']\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        payload=('expected:0 processname:foo groupname:bar '\n                 'from_state:RUNNING pid:1')\n        prog.stdin.write(\n            'eventname:PROCESS_STATE_EXITED len:%s\\n' % len(payload))\n        prog.stdin.write(payload)\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        output = prog.stderr.getvalue()\n        lines = output.split('\\n')\n        self.assertEqual(lines[0], 'unexpected exit, mailing')\n        self.assertEqual(lines[1], 'Mailed:')\n        self.assertEqual(lines[2], '')\n        self.assertEqual(lines[3], 'To: chrism@plope.com')\n        self.assertTrue('Subject: [foo]: foo crashed at' in lines[4])\n        self.assertEqual(lines[5], '')\n        self.assertTrue(\n            'Process foo in group bar exited unexpectedly' in lines[6])\n        import os\n        f = open(os.path.join(self.tempdir, 'email.log'), 'r')\n        mail = f.read()\n        f.close()\n        self.assertTrue(\n            'Process foo in group bar exited unexpectedly' in mail)\n"
  },
  {
    "path": "superlance/tests/test_crashmailbatch.py",
    "content": "import unittest\ntry: # pragma: no cover\n    from unittest.mock import Mock\nexcept ImportError: # pragma: no cover\n    from mock import Mock\nfrom superlance.compat import StringIO\n\nclass CrashMailBatchTests(unittest.TestCase):\n    from_email = 'testFrom@blah.com'\n    to_emails = ('testTo@blah.com')\n    subject = 'Test Alert'\n    unexpected_err_msg = 'Process bar:foo (pid 58597) died unexpectedly'\n\n    def _get_target_class(self):\n        from superlance.crashmailbatch import CrashMailBatch\n        return CrashMailBatch\n\n    def _make_one_mocked(self, **kwargs):\n        kwargs['stdin'] = StringIO()\n        kwargs['stdout'] = StringIO()\n        kwargs['stderr'] = StringIO()\n        kwargs['from_email'] = kwargs.get('from_email', self.from_email)\n        kwargs['to_emails'] = kwargs.get('to_emails', self.to_emails)\n        kwargs['subject'] = kwargs.get('subject', self.subject)\n\n        obj = self._get_target_class()(**kwargs)\n        obj.send_email = Mock()\n        return obj\n\n    def get_process_exited_event(self, pname, gname, expected):\n        headers = {\n            'ver': '3.0', 'poolserial': '7', 'len': '71',\n            'server': 'supervisor', 'eventname': 'PROCESS_STATE_EXITED',\n            'serial': '7', 'pool': 'checkmailbatch',\n        }\n        payload = 'processname:%s groupname:%s from_state:RUNNING expected:%d \\\npid:58597' % (pname, gname, expected)\n        return (headers, payload)\n\n    def test_get_process_state_change_msg_expected(self):\n        crash = self._make_one_mocked()\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 1)\n        self.assertEqual(None, crash.get_process_state_change_msg(hdrs, payload))\n\n    def test_get_process_state_change_msg_unexpected(self):\n        crash = self._make_one_mocked()\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 0)\n        msg = crash.get_process_state_change_msg(hdrs, payload)\n        self.assertTrue(self.unexpected_err_msg in msg)\n\n    def test_handle_event_exit_expected(self):\n        crash = self._make_one_mocked()\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 1)\n        crash.handle_event(hdrs, payload)\n        self.assertEqual([], crash.get_batch_msgs())\n        self.assertEqual('', crash.stderr.getvalue())\n\n    def test_handle_event_exit_unexpected(self):\n        crash = self._make_one_mocked()\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 0)\n        crash.handle_event(hdrs, payload)\n        msgs = crash.get_batch_msgs()\n        self.assertEqual(1, len(msgs))\n        self.assertTrue(self.unexpected_err_msg in msgs[0])\n        self.assertTrue(self.unexpected_err_msg in crash.stderr.getvalue())\n\n    def test_sets_default_subject_when_None(self):\n        crash = self._make_one_mocked(subject=None) # see issue #109\n        self.assertEqual(crash.subject, \"Crash alert from supervisord\")\n"
  },
  {
    "path": "superlance/tests/test_crashsms.py",
    "content": "import unittest\n\nfrom .test_crashmailbatch import CrashMailBatchTests\n\nclass CrashSMSTests(CrashMailBatchTests):\n    subject = None\n    unexpected_err_msg = '[bar:foo](58597) exited unexpectedly'\n\n    def _get_target_class(self):\n        from superlance.crashsms import CrashSMS\n        return CrashSMS\n\n    def test_sets_default_subject_when_None(self):\n        crash = self._make_one_mocked(subject=None)\n        self.assertEqual(crash.subject, self.subject)\n"
  },
  {
    "path": "superlance/tests/test_fatalmailbatch.py",
    "content": "import unittest\ntry: # pragma: no cover\n    from unittest.mock import Mock\nexcept ImportError: # pragma: no cover\n    from mock import Mock\nfrom superlance.compat import StringIO\n\nclass FatalMailBatchTests(unittest.TestCase):\n    from_email = 'testFrom@blah.com'\n    to_emails = ('testTo@blah.com')\n    subject = 'Test Alert'\n    unexpected_err_msg = 'Process bar:foo failed to start too many times'\n\n    def _get_target_class(self):\n        from superlance.fatalmailbatch import FatalMailBatch\n        return FatalMailBatch\n\n    def _make_one_mocked(self, **kwargs):\n        kwargs['stdin'] = StringIO()\n        kwargs['stdout'] = StringIO()\n        kwargs['stderr'] = StringIO()\n        kwargs['from_email'] = kwargs.get('from_email', self.from_email)\n        kwargs['to_emails'] = kwargs.get('to_emails', self.to_emails)\n        kwargs['subject'] = kwargs.get('subject', self.subject)\n\n        obj = self._get_target_class()(**kwargs)\n        obj.send_email = Mock()\n        return obj\n\n    def get_process_fatal_event(self, pname, gname):\n        headers = {\n            'ver': '3.0', 'poolserial': '7', 'len': '71',\n            'server': 'supervisor', 'eventname': 'PROCESS_STATE_FATAL',\n            'serial': '7', 'pool': 'checkmailbatch',\n        }\n        payload = 'processname:%s groupname:%s from_state:BACKOFF' \\\n                % (pname, gname)\n        return (headers, payload)\n\n    def test_get_process_state_change_msg(self):\n        crash = self._make_one_mocked()\n        hdrs, payload = self.get_process_fatal_event('foo', 'bar')\n        msg = crash.get_process_state_change_msg(hdrs, payload)\n        self.assertTrue(self.unexpected_err_msg in msg)\n\n    def test_sets_default_subject_when_None(self):\n        crash = self._make_one_mocked(subject=None) # see issue #109\n        self.assertEqual(crash.subject, \"Fatal start alert from supervisord\")\n"
  },
  {
    "path": "superlance/tests/test_httpok.py",
    "content": "import socket\nimport time\nimport unittest\nfrom superlance.compat import StringIO\nfrom supervisor.process import ProcessStates\nfrom superlance.tests.dummy import DummyResponse\nfrom superlance.tests.dummy import DummyRPCServer\nfrom superlance.tests.dummy import DummySupervisorRPCNamespace\n\n_NOW = time.time()\n\n_FAIL = [ {\n        'name':'FAILED',\n        'group':'foo',\n        'pid':11,\n        'state':ProcessStates.RUNNING,\n        'statename':'RUNNING',\n        'start':_NOW - 100,\n        'stop':0,\n        'spawnerr':'',\n        'now':_NOW,\n        'description':'foo description',\n        },\n{\n        'name':'SPAWN_ERROR',\n        'group':'foo',\n        'pid':11,\n        'state':ProcessStates.RUNNING,\n        'statename':'RUNNING',\n        'start':_NOW - 100,\n        'stop':0,\n        'spawnerr':'',\n        'now':_NOW,\n        'description':'foo description',\n        },]\n\ndef make_connection(response, exc=None):\n    class TestConnection:\n        def __init__(self, hostport):\n            self.hostport = hostport\n\n        def request(self, method, path, headers):\n            if exc:\n                if exc is True:\n                    raise ValueError('foo')\n                else:\n                    raise exc.pop()\n            self.method = method\n            self.path = path\n            self.headers = headers\n\n        def getresponse(self):\n            return response\n\n    return TestConnection\n\nclass HTTPOkTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from superlance.httpok import HTTPOk\n        return HTTPOk\n\n    def _makeOne(self, *args, **kwargs):\n        return self._getTargetClass()(*args, **kwargs)\n\n    def _makeOnePopulated(self, programs, any=None, statuses=None, inbody=None,\n                          eager=True, gcore=None, coredir=None,\n                          response=None, exc=None, name=None,\n                          timeout=10, retry_time=0):\n        if statuses is None:\n            statuses = [200]\n        if response is None:\n            response = DummyResponse()\n        httpok = self._makeOne(\n            programs=programs,\n            any=any,\n            statuses=statuses,\n            inbody=inbody,\n            eager=eager,\n            coredir=coredir,\n            gcore=gcore,\n            name=name,\n            rpc=DummyRPCServer(),\n            url='http://foo/bar',\n            timeout=timeout,\n            email='chrism@plope.com',\n            sendmail='cat - > /dev/null',\n            retry_time=retry_time,\n            )\n        httpok.stdin = StringIO()\n        httpok.stdout = StringIO()\n        httpok.stderr = StringIO()\n        httpok.connclass = make_connection(response, exc=exc)\n        return httpok\n\n    def test_listProcesses_no_programs(self):\n        programs = []\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        specs = list(prog.listProcesses())\n        self.assertEqual(len(specs), 0)\n\n    def test_listProcesses_w_RUNNING_programs_default_state(self):\n        programs = ['foo']\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        specs = list(prog.listProcesses())\n        self.assertEqual(len(specs), 1)\n        self.assertEqual(specs[0],\n                         DummySupervisorRPCNamespace.all_process_info[0])\n\n    def test_listProcesses_w_nonRUNNING_programs_default_state(self):\n        programs = ['bar']\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        specs = list(prog.listProcesses())\n        self.assertEqual(len(specs), 1)\n        self.assertEqual(specs[0],\n                         DummySupervisorRPCNamespace.all_process_info[1])\n\n    def test_listProcesses_w_nonRUNNING_programs_RUNNING_state(self):\n        programs = ['bar']\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        specs = list(prog.listProcesses(ProcessStates.RUNNING))\n        self.assertEqual(len(specs), 0, (prog.programs, specs))\n\n    def test_runforever_eager_notatick(self):\n        programs = {'foo':0, 'bar':0, 'baz_01':0 }\n        any = None\n        prog = self._makeOnePopulated(programs, any)\n        prog.stdin.write('eventname:NOTATICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        self.assertEqual(prog.stderr.getvalue(), '')\n\n    def test_runforever_doesnt_act_if_status_is_expected(self):\n        statuses = [200, 201]\n        for status in statuses:\n            response = DummyResponse()\n            response.status = status # expected\n            prog = self._makeOnePopulated(\n                programs=['foo'],\n                statuses=statuses,\n                response=response,\n                )\n            prog.stdin.write('eventname:TICK len:0\\n')\n            prog.stdin.seek(0)\n            prog.runforever(test=True)\n            # status is expected so there should be no output\n            self.assertEqual('', prog.stderr.getvalue())\n\n    def test_runforever_acts_if_status_is_unexpected(self):\n        statuses = [200, 201]\n        response = DummyResponse()\n        response.status = 500 # unexpected\n        response.reason = 'Internal Server Error'\n        prog = self._makeOnePopulated(\n            programs=['foo'],\n            statuses=[statuses],\n            response=response,\n            )\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertTrue('Subject: httpok: http://foo/bar: '\n                        'bad status returned' in lines)\n        self.assertTrue('status contacting http://foo/bar: '\n                        '500 Internal Server Error' in lines)\n\n    def test_runforever_doesnt_act_if_inbody_is_present(self):\n        response = DummyResponse()\n        response.body = 'It works'\n        prog = self._makeOnePopulated(\n            programs=['foo'],\n            statuses=[response.status],\n            response=response,\n            inbody='works',\n            )\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        # body is expected so there should be no output\n        self.assertEqual('', prog.stderr.getvalue())\n\n    def test_runforever_acts_if_inbody_isnt_present(self):\n        response = DummyResponse()\n        response.body = 'Some kind of error'\n        prog = self._makeOnePopulated(\n            programs=['foo'],\n            statuses=[response.status],\n            response=response,\n            inbody=\"works\",\n            )\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertTrue('Subject: httpok: http://foo/bar: '\n                        'bad body returned' in lines)\n\n    def test_runforever_eager_error_on_request_some(self):\n        programs = ['foo', 'bar', 'baz_01', 'notexisting']\n        any = None\n        prog = self._makeOnePopulated(programs, any, exc=True)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertEqual(lines[0],\n                         (\"Restarting selected processes ['foo', 'bar', \"\n                          \"'baz_01', 'notexisting']\")\n                         )\n        self.assertEqual(lines[1], 'foo is in RUNNING state, restarting')\n        self.assertEqual(lines[2], 'foo restarted')\n        self.assertEqual(lines[3], 'bar not in RUNNING state, NOT restarting')\n        self.assertEqual(lines[4],\n                         'baz:baz_01 not in RUNNING state, NOT restarting')\n        self.assertEqual(lines[5],\n          \"Programs not restarted because they did not exist: ['notexisting']\")\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 12)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_runforever_eager_error_on_request_any(self):\n        programs = []\n        any = True\n        prog = self._makeOnePopulated(programs, any, exc=True)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'Restarting all running processes')\n        self.assertEqual(lines[1], 'foo is in RUNNING state, restarting')\n        self.assertEqual(lines[2], 'foo restarted')\n        self.assertEqual(lines[3], 'bar not in RUNNING state, NOT restarting')\n        self.assertEqual(lines[4],\n                         'baz:baz_01 not in RUNNING state, NOT restarting')\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 11)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_runforever_eager_error_on_process_stop(self):\n        programs = ['FAILED']\n        any = False\n        prog = self._makeOnePopulated(programs, any, exc=True)\n        prog.rpc.supervisor.all_process_info = _FAIL\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertEqual(lines[0], \"Restarting selected processes ['FAILED']\")\n        self.assertEqual(lines[1], 'foo:FAILED is in RUNNING state, restarting')\n        self.assertEqual(lines[2],\n                    \"Failed to stop process foo:FAILED: <Fault 30: 'FAILED'>\")\n        self.assertEqual(lines[3], 'foo:FAILED restarted')\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 10)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_runforever_eager_error_on_process_start(self):\n        programs = ['SPAWN_ERROR']\n        any = False\n        prog = self._makeOnePopulated(programs, any, exc=True)\n        prog.rpc.supervisor.all_process_info = _FAIL\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertEqual(lines[0],\n                         \"Restarting selected processes ['SPAWN_ERROR']\")\n        self.assertEqual(lines[1],\n                         'foo:SPAWN_ERROR is in RUNNING state, restarting')\n        self.assertEqual(lines[2],\n           \"Failed to start process foo:SPAWN_ERROR: <Fault 50: 'SPAWN_ERROR'>\")\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 9)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_runforever_eager_gcore(self):\n        programs = ['foo', 'bar', 'baz_01', 'notexisting']\n        any = None\n        prog = self._makeOnePopulated(programs, any, exc=True, gcore=\"true\",\n                                      coredir=\"/tmp\")\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = prog.stderr.getvalue().split('\\n')\n        self.assertEqual(lines[0],\n                         (\"Restarting selected processes ['foo', 'bar', \"\n                          \"'baz_01', 'notexisting']\")\n                         )\n        self.assertEqual(lines[1], 'gcore output for foo:')\n        self.assertEqual(lines[2], '')\n        self.assertEqual(lines[3], ' ')\n        self.assertEqual(lines[4], 'foo is in RUNNING state, restarting')\n        self.assertEqual(lines[5], 'foo restarted')\n        self.assertEqual(lines[6], 'bar not in RUNNING state, NOT restarting')\n        self.assertEqual(lines[7],\n                         'baz:baz_01 not in RUNNING state, NOT restarting')\n        self.assertEqual(lines[8],\n          \"Programs not restarted because they did not exist: ['notexisting']\")\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 15)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_runforever_not_eager_none_running(self):\n        programs = ['bar', 'baz_01']\n        any = None\n        prog = self._makeOnePopulated(programs, any, exc=True, gcore=\"true\",\n                                      coredir=\"/tmp\", eager=False)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = [x for x in prog.stderr.getvalue().split('\\n') if x]\n        self.assertEqual(len(lines), 0, lines)\n        self.assertFalse('mailed' in prog.__dict__)\n\n    def test_runforever_not_eager_running(self):\n        programs = ['foo', 'bar']\n        any = None\n        prog = self._makeOnePopulated(programs, any, exc=True, eager=False)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = [x for x in prog.stderr.getvalue().split('\\n') if x]\n        self.assertEqual(lines[0],\n                         (\"Restarting selected processes ['foo', 'bar']\")\n                         )\n        self.assertEqual(lines[1], 'foo is in RUNNING state, restarting')\n        self.assertEqual(lines[2], 'foo restarted')\n        self.assertEqual(lines[3], 'bar not in RUNNING state, NOT restarting')\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 10)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_runforever_honor_timeout_on_connrefused(self):\n        programs = ['foo', 'bar']\n        any = None\n        error = socket.error()\n        error.errno = 111\n        prog = self._makeOnePopulated(programs, any, exc=[error], eager=False)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        self.assertEqual(prog.stderr.getvalue(), '')\n        self.assertEqual(prog.stdout.getvalue(), 'READY\\nRESULT 2\\nOK')\n\n    def test_runforever_connrefused_error(self):\n        programs = ['foo', 'bar']\n        any = None\n        error = socket.error()\n        error.errno = 111\n        prog = self._makeOnePopulated(programs, any,\n            exc=[error for x in range(100)], eager=False)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = [x for x in prog.stderr.getvalue().split('\\n') if x]\n        self.assertEqual(lines[0],\n                         (\"Restarting selected processes ['foo', 'bar']\")\n                         )\n        self.assertEqual(lines[1], 'foo is in RUNNING state, restarting')\n        self.assertEqual(lines[2], 'foo restarted')\n        self.assertEqual(lines[3], 'bar not in RUNNING state, NOT restarting')\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(len(mailed), 10)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                    'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_bug_110(self):\n        error = socket.error()\n        error.errno = 111\n        prog = self._makeOnePopulated(programs=['foo'], any=None,\n            exc=[error for x in range(100)], eager=False,\n                                      timeout=1, retry_time=10)\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        lines = [x for x in prog.stderr.getvalue().split('\\n') if x]\n        self.assertEqual(lines[0],\n                         (\"Restarting selected processes ['foo']\")\n                         )\n        self.assertEqual(lines[1], 'foo is in RUNNING state, restarting')\n        self.assertEqual(lines[2], 'foo restarted')\n\n    def test_subject_no_name(self):\n        \"\"\"set the name to None to check if subject formats to:\n        httpok: %(subject)s\n        \"\"\"\n        prog = self._makeOnePopulated(\n            programs=['foo', 'bar'],\n            any=None,\n            eager=False,\n            exc=[ValueError('this causes status to be None')],\n            name=None,\n            )\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(mailed[1],\n          'Subject: httpok: http://foo/bar: bad status returned')\n\n    def test_subject_with_name(self):\n        \"\"\"set the name to a string to check if subject formats to:\n        httpok [%(name)s]: %(subject)s\n        \"\"\"\n        prog = self._makeOnePopulated(\n            programs=['foo', 'bar'],\n            any=None,\n            eager=False,\n            exc=[ValueError('this causes status to be None')],\n            name='thinko',\n            )\n        prog.stdin.write('eventname:TICK len:0\\n')\n        prog.stdin.seek(0)\n        prog.runforever(test=True)\n        mailed = prog.mailed.split('\\n')\n        self.assertEqual(mailed[1],\n          'Subject: httpok [thinko]: http://foo/bar: bad status returned')\n"
  },
  {
    "path": "superlance/tests/test_memmon.py",
    "content": "# -*- coding: utf-8 -*-\nimport unittest\nfrom superlance.compat import StringIO\nfrom superlance.compat import maxint\nfrom superlance.memmon import (\n    help_request,\n    memmon_from_args,\n    seconds_size\n    )\nfrom superlance.tests.dummy import DummyRPCServer\n\nclass MemmonTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from superlance.memmon import Memmon\n        return Memmon\n\n    def _makeOne(self, *args, **kwargs):\n        return self._getTargetClass()(*args, **kwargs)\n\n    def _makeOnePopulated(self, programs, groups, any, name=None):\n        memmon = self._makeOne(\n            programs=programs,\n            groups=groups,\n            any=any,\n            name=name,\n            rpc=DummyRPCServer(),\n            cumulative=False,\n            sendmail='cat - > /dev/null',\n            email='chrism@plope.com',\n            email_uptime_limit=2000,\n            )\n        memmon.stdin = StringIO()\n        memmon.stdout = StringIO()\n        memmon.stderr = StringIO()\n        memmon.pscommand = 'echo 22%s'\n        return memmon\n\n    def test_runforever_notatick(self):\n        programs = {'foo':0, 'bar':0, 'baz_01':0 }\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:NOTATICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        self.assertEqual(memmon.stderr.getvalue(), '')\n\n    def test_runforever_tick_programs(self):\n        programs = {'foo':0, 'bar':0, 'baz_01':0 }\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 8)\n        self.assertEqual(lines[0], 'Checking programs bar=0, baz_01=0, foo=0')\n        self.assertEqual(lines[1], 'RSS of foo:foo is 2264064')\n        self.assertEqual(lines[2], 'Restarting foo:foo')\n        self.assertEqual(lines[3], 'RSS of bar:bar is 2265088')\n        self.assertEqual(lines[4], 'Restarting bar:bar')\n        self.assertEqual(lines[5], 'RSS of baz:baz_01 is 2265088')\n        self.assertEqual(lines[6], 'Restarting baz:baz_01')\n        self.assertEqual(lines[7], '')\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(len(mailed), 4)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                         'Subject: memmon: process baz:baz_01 restarted')\n        self.assertEqual(mailed[2], '')\n        self.assertTrue(mailed[3].startswith('memmon.py restarted'))\n\n    def test_runforever_tick_groups(self):\n        programs = {}\n        groups = {'foo':0}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 4)\n        self.assertEqual(lines[0], 'Checking groups foo=0')\n        self.assertEqual(lines[1], 'RSS of foo:foo is 2264064')\n        self.assertEqual(lines[2], 'Restarting foo:foo')\n        self.assertEqual(lines[3], '')\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(len(mailed), 4)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n          'Subject: memmon: process foo:foo restarted')\n        self.assertEqual(mailed[2], '')\n        self.assertTrue(mailed[3].startswith('memmon.py restarted'))\n\n    def test_runforever_tick_any(self):\n        programs = {}\n        groups = {}\n        any = 0\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 8)\n        self.assertEqual(lines[0], 'Checking any=0')\n        self.assertEqual(lines[1], 'RSS of foo:foo is 2264064')\n        self.assertEqual(lines[2], 'Restarting foo:foo')\n        self.assertEqual(lines[3], 'RSS of bar:bar is 2265088')\n        self.assertEqual(lines[4], 'Restarting bar:bar')\n        self.assertEqual(lines[5], 'RSS of baz:baz_01 is 2265088')\n        self.assertEqual(lines[6], 'Restarting baz:baz_01')\n        self.assertEqual(lines[7], '')\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(len(mailed), 4)\n\n    def test_runforever_tick_programs_and_groups(self):\n        programs = {'baz_01':0}\n        groups = {'foo':0}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 7)\n        self.assertEqual(lines[0], 'Checking programs baz_01=0')\n        self.assertEqual(lines[1], 'Checking groups foo=0')\n        self.assertEqual(lines[2], 'RSS of foo:foo is 2264064')\n        self.assertEqual(lines[3], 'Restarting foo:foo')\n        self.assertEqual(lines[4], 'RSS of baz:baz_01 is 2265088')\n        self.assertEqual(lines[5], 'Restarting baz:baz_01')\n        self.assertEqual(lines[6], '')\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(len(mailed), 4)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n                         'Subject: memmon: process baz:baz_01 restarted')\n        self.assertEqual(mailed[2], '')\n        self.assertTrue(mailed[3].startswith('memmon.py restarted'))\n\n    def test_runforever_tick_programs_norestart(self):\n        programs = {'foo': maxint}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 3)\n        self.assertEqual(lines[0], 'Checking programs foo=%s' % maxint)\n        self.assertEqual(lines[1], 'RSS of foo:foo is 2264064')\n        self.assertEqual(lines[2], '')\n        self.assertEqual(memmon.mailed, False)\n\n    def test_stopprocess_fault_tick_programs_norestart(self):\n        programs = {'foo': maxint}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 3)\n        self.assertEqual(lines[0], 'Checking programs foo=%s' % maxint)\n        self.assertEqual(lines[1], 'RSS of foo:foo is 2264064')\n        self.assertEqual(lines[2], '')\n        self.assertEqual(memmon.mailed, False)\n\n    def test_stopprocess_fails_to_stop(self):\n        programs = {'BAD_NAME': 0}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        from supervisor.process import ProcessStates\n        memmon.rpc.supervisor.all_process_info = [ {\n            'name':'BAD_NAME',\n            'group':'BAD_NAME',\n            'pid':11,\n            'state':ProcessStates.RUNNING,\n            'statename':'RUNNING',\n            'start':0,\n            'stop':0,\n            'spawnerr':'',\n            'now':0,\n            'description':'BAD_NAME description',\n             } ]\n        from superlance.compat import xmlrpclib\n        self.assertRaises(xmlrpclib.Fault, memmon.runforever, True)\n        lines = memmon.stderr.getvalue().split('\\n')\n        self.assertEqual(len(lines), 4)\n        self.assertEqual(lines[0], 'Checking programs BAD_NAME=%s' % 0)\n        self.assertEqual(lines[1], 'RSS of BAD_NAME:BAD_NAME is 2264064')\n        self.assertEqual(lines[2], 'Restarting BAD_NAME:BAD_NAME')\n        self.assertTrue(lines[3].startswith('Failed'))\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(len(mailed), 4)\n        self.assertEqual(mailed[0], 'To: chrism@plope.com')\n        self.assertEqual(mailed[1],\n          'Subject: memmon: failed to stop process BAD_NAME:BAD_NAME, exiting')\n        self.assertEqual(mailed[2], '')\n        self.assertTrue(mailed[3].startswith('Failed'))\n\n    def test_subject_no_name(self):\n        \"\"\"set the name to None to check if subject formats to:\n        memmon: %(subject)s\n        \"\"\"\n        memmon = self._makeOnePopulated(\n            programs={},\n            groups={},\n            any=0,\n            name=None,\n            )\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(mailed[1],\n          'Subject: memmon: process baz:baz_01 restarted')\n\n    def test_subject_with_name(self):\n        \"\"\"set the name to a string to check if subject formats to:\n        memmon [%(name)s]: %(subject)s\n        \"\"\"\n        memmon = self._makeOnePopulated(\n            programs={},\n            groups={},\n            any=0,\n            name='thinko',\n            )\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n\n        mailed = memmon.mailed.split('\\n')\n        self.assertEqual(mailed[1],\n          'Subject: memmon [thinko]: process baz:baz_01 restarted')\n\n    def test_parse_uptime(self):\n        \"\"\"test parsing of time parameter for uptime\n        \"\"\"\n        self.assertEqual(seconds_size('1'), 1, 'default is seconds')\n        self.assertEqual(seconds_size('1s'), 1, 'seconds suffix is allowed, too')\n        self.assertEqual(seconds_size('2m'), 120)\n        self.assertEqual(seconds_size('3h'), 10800)\n        self.assertEqual(seconds_size('1d'), 86400)\n        self.assertRaises(ValueError, seconds_size, '1y')\n\n    def test_uptime_short_email(self):\n        \"\"\"in case an email is provided and the restarted process' uptime\n        is shorter than our uptime_limit we do send an email\n        \"\"\"\n        programs = {'foo':0}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.email_uptime_limit = 101\n\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        self.assertTrue(memmon.mailed, 'email has been sent')\n\n        #in case uptime == limit, we send an email too\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.email_uptime_limit = 100\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        self.assertTrue(memmon.mailed, 'email has been sent')\n\n\n\n    def test_uptime_long_no_email(self):\n        \"\"\"in case an email is provided and the restarted process' uptime\n        is longer than our uptime_limit we do not send an email\n        \"\"\"\n        programs = {'foo':0}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.email_uptime_limit = 99\n\n        memmon.stdin.write('eventname:TICK len:0\\n')\n        memmon.stdin.seek(0)\n        memmon.runforever(test=True)\n        self.assertFalse(memmon.mailed, 'no email should be sent because uptime is above limit')\n\n    def test_calc_rss_not_cumulative(self):\n        programs = {}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n\n        noop = '_=%s; '\n        pid = 1\n\n        memmon.pscommand = noop + 'echo 16'\n        rss = memmon.calc_rss(pid)\n        self.assertEqual(16 * 1024, rss)\n\n        memmon.pscommand = noop + 'echo not_an_int'\n        rss = memmon.calc_rss(pid)\n        self.assertEqual(\n            None, rss, 'Failure to parse an integer RSS value from the ps '\n            'output should result in calc_rss() returning None.')\n\n    def test_calc_rss_cumulative(self):\n        \"\"\"Let calc_rss() do its work on a fake process tree:\n\n        ├─┬= 99\n        │ └─┬= 1\n        │   └─┬= 2\n        │     ├─── 3\n        │     └─── 4\n\n        (Where the process with PID 1 is the one being monitored)\n        \"\"\"\n        programs = {}\n        groups = {}\n        any = None\n        memmon = self._makeOnePopulated(programs, groups, any)\n        memmon.cumulative = True\n\n        # output of ps ax -o \"pid= ppid= rss=\" representing the process\n        # tree described above, including extraneous whitespace and\n        # unrelated processes.\n        ps_output = \"\"\"\n        11111 22222    333\n        1     99       100\n        2     1        200\n        3     2        300\n        4     2        400\n        11111 22222    333\n        \"\"\"\n\n        memmon.pstreecommand = 'echo \"%s\"' % ps_output\n        rss = memmon.calc_rss(1)\n        self.assertEqual(\n            1000 * 1024, rss,\n            'Cumulative RSS of the test process and its three children '\n            'should add up to 1000 kb.')\n\n    def test_argparser(self):\n        \"\"\"test if arguments are parsed correctly\n        \"\"\"\n        # help\n        arguments = ['-h', ]\n        memmon = memmon_from_args(arguments)\n        self.assertTrue(memmon is help_request,\n            '-h returns help_request to make main() script print usage'\n            )\n\n        #all arguments\n        arguments = ['-c',\n                     '-p', 'foo=50MB',\n                     '-g', 'bar=10kB',\n                     '--any', '250',\n                     '-s', 'mutt',\n                     '-m', 'me@you.com',\n                     '-u', '1d',\n                     '-n', 'myproject']\n        memmon = memmon_from_args(arguments)\n        self.assertEqual(memmon.cumulative, True)\n        self.assertEqual(memmon.programs['foo'], 50 * 1024 * 1024)\n        self.assertEqual(memmon.groups['bar'], 10 * 1024)\n        self.assertEqual(memmon.any, 250)\n        self.assertEqual(memmon.sendmail, 'mutt')\n        self.assertEqual(memmon.email, 'me@you.com')\n        self.assertEqual(memmon.email_uptime_limit, 1 * 24 * 60 * 60)\n        self.assertEqual(memmon.name, 'myproject')\n\n\n        #default arguments\n        arguments = ['-m', 'me@you.com']\n        memmon = memmon_from_args(arguments)\n        self.assertEqual(memmon.cumulative, False)\n        self.assertEqual(memmon.programs, {})\n        self.assertEqual(memmon.groups, {})\n        self.assertEqual(memmon.any, None)\n        self.assertTrue('sendmail' in memmon.sendmail, 'not using sendmail as default')\n        self.assertEqual(memmon.email_uptime_limit, maxint)\n        self.assertEqual(memmon.name, None)\n\n        arguments = ['-p', 'foo=50MB']\n        memmon = memmon_from_args(arguments)\n        self.assertEqual(memmon.email, None)\n"
  },
  {
    "path": "superlance/tests/test_process_state_email_monitor.py",
    "content": "import unittest\ntry: # pragma: no cover\n    from unittest.mock import Mock\nexcept ImportError: # pragma: no cover\n    from mock import Mock\nfrom superlance.compat import StringIO\n\nclass ProcessStateEmailMonitorTestException(Exception):\n    pass\n\nclass ProcessStateEmailMonitorTests(unittest.TestCase):\n    from_email = 'testFrom@blah.com'\n    to_emails = ('testTo@blah.com', 'testTo2@blah.com')\n    to_str = 'testTo@blah.com, testTo2@blah.com'\n    subject = 'Test Alert'\n\n    def _get_target_class(self):\n        from superlance.process_state_email_monitor \\\n        import ProcessStateEmailMonitor\n        return ProcessStateEmailMonitor\n\n    def _make_one(self, **kwargs):\n        kwargs['stdin'] = StringIO()\n        kwargs['stdout'] = StringIO()\n        kwargs['stderr'] = StringIO()\n        kwargs['from_email'] = kwargs.get('from_email', self.from_email)\n        kwargs['to_emails'] = kwargs.get('to_emails', self.to_emails)\n        kwargs['subject'] = kwargs.get('subject', self.subject)\n\n        obj = self._get_target_class()(**kwargs)\n        return obj\n\n    def _make_one_mock_send_email(self, **kwargs):\n        obj = self._make_one(**kwargs)\n        obj.send_email = Mock()\n        return obj\n\n    def _make_one_mock_send_smtp(self, **kwargs):\n        obj = self._make_one(**kwargs)\n        obj.send_smtp = Mock()\n        return obj\n\n    def test_validate_cmd_line_options_single_to_email_ok(self):\n        klass = self._get_target_class()\n\n        options = Mock()\n        options.from_email = 'blah'\n        options.to_emails = 'frog'\n\n        validated = klass.validate_cmd_line_options(options)\n        self.assertEqual(['frog'], validated.to_emails)\n\n    def test_validate_cmd_line_options_multi_to_emails_ok(self):\n        klass = self._get_target_class()\n\n        options = Mock()\n        options.from_email = 'blah'\n        options.to_emails = 'frog, log,dog'\n\n        validated = klass.validate_cmd_line_options(options)\n        self.assertEqual(['frog', 'log', 'dog'], validated.to_emails)\n\n    def test_send_email_ok(self):\n        email = {\n            'body': 'msg1\\nmsg2',\n            'to': self.to_emails,\n            'from': 'testFrom@blah.com',\n            'subject': 'Test Alert',\n        }\n        monitor = self._make_one_mock_send_smtp()\n        monitor.send_email(email)\n\n        # Test that email was sent\n        self.assertEqual(1, monitor.send_smtp.call_count)\n        smtpCallArgs = monitor.send_smtp.call_args[0]\n        mimeMsg = smtpCallArgs[0]\n        self.assertEqual(self.to_str, mimeMsg['To'])\n        self.assertEqual(email['from'], mimeMsg['From'])\n        self.assertEqual(email['subject'], mimeMsg['Subject'])\n        self.assertEqual(email['body'], mimeMsg.get_payload())\n\n    def _raiseSTMPException(self, mime, to_emails):\n        raise ProcessStateEmailMonitorTestException('test')\n\n    def test_send_email_exception(self):\n        email = {\n            'body': 'msg1\\nmsg2',\n            'to': self.to_emails,\n            'from': 'testFrom@blah.com',\n            'subject': 'Test Alert',\n        }\n        monitor = self._make_one_mock_send_smtp()\n        monitor.send_smtp.side_effect = self._raiseSTMPException\n        monitor.send_email(email)\n\n        # Test that error was logged to stderr\n        self.assertEqual(\"Error sending email: test\\n\", monitor.stderr.getvalue())\n\n    def test_send_batch_notification(self):\n        test_msgs = ['msg1', 'msg2']\n        monitor = self._make_one_mock_send_email()\n        monitor.batchmsgs = test_msgs\n        monitor.send_batch_notification()\n\n        # Test that email was sent\n        expected = {\n            'body': 'msg1\\nmsg2',\n            'to': self.to_emails,\n            'from': 'testFrom@blah.com',\n            'subject': 'Test Alert',\n        }\n        self.assertEqual(1, monitor.send_email.call_count)\n        monitor.send_email.assert_called_with(expected)\n\n        # Test that email was logged\n        self.assertEqual(\"\"\"Sending notification email:\nTo: %s\nFrom: testFrom@blah.com\nSubject: Test Alert\nBody:\nmsg1\nmsg2\n\"\"\" % (self.to_str), monitor.stderr.getvalue())\n\n    def test_log_email_with_body_digest(self):\n        bodyLen = 80\n        monitor = self._make_one_mock_send_email()\n        email = {\n            'to': ['you@fubar.com'],\n            'from': 'me@fubar.com',\n            'subject': 'yo yo',\n            'body': 'a' * bodyLen,\n        }\n        monitor.log_email(email)\n        self.assertEqual(\"\"\"Sending notification email:\nTo: you@fubar.com\nFrom: me@fubar.com\nSubject: yo yo\nBody:\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...\n\"\"\", monitor.stderr.getvalue())\n        self.assertEqual('a' * bodyLen, email['body'])\n\n    def test_log_email_without_body_digest(self):\n        monitor = self._make_one_mock_send_email()\n        email = {\n            'to': ['you@fubar.com'],\n            'from': 'me@fubar.com',\n            'subject': 'yo yo',\n            'body': 'a' * 20,\n        }\n        monitor.log_email(email)\n        self.assertEqual(\"\"\"Sending notification email:\nTo: you@fubar.com\nFrom: me@fubar.com\nSubject: yo yo\nBody:\naaaaaaaaaaaaaaaaaaaa\n\"\"\", monitor.stderr.getvalue())\n"
  },
  {
    "path": "superlance/tests/test_process_state_monitor.py",
    "content": "import unittest\ntry: # pragma: no cover\n    from unittest.mock import Mock\nexcept ImportError: # pragma: no cover\n    from mock import Mock\nfrom superlance.compat import StringIO\nfrom superlance.process_state_monitor import ProcessStateMonitor\n\nclass _TestProcessStateMonitor(ProcessStateMonitor):\n\n    process_state_events = ['PROCESS_STATE_EXITED']\n\n    def get_process_state_change_msg(self, headers, payload):\n        return repr(payload)\n\nclass ProcessStateMonitorTests(unittest.TestCase):\n\n    def _get_target_class(self):\n        return _TestProcessStateMonitor\n\n    def _make_one_mocked(self, **kwargs):\n        kwargs['stdin'] = StringIO()\n        kwargs['stdout'] = StringIO()\n        kwargs['stderr'] = StringIO()\n\n        obj = self._get_target_class()(**kwargs)\n        obj.send_batch_notification = Mock()\n        return obj\n\n    def get_process_exited_event(self, pname, gname, expected,\n                                eventname='PROCESS_STATE_EXITED'):\n        headers = {\n            'ver': '3.0', 'poolserial': '7', 'len': '71',\n            'server': 'supervisor', 'eventname': eventname,\n            'serial': '7', 'pool': 'checkmailbatch',\n        }\n        payload = 'processname:%s groupname:%s from_state:RUNNING expected:%d \\\npid:58597' % (pname, gname, expected)\n        return (headers, payload)\n\n    def get_tick60_event(self):\n        headers = {\n            'ver': '3.0', 'poolserial': '5', 'len': '15',\n            'server': 'supervisor', 'eventname': 'TICK_60',\n            'serial': '5', 'pool': 'checkmailbatch',\n        }\n        payload = 'when:1279665240'\n        return (headers, payload)\n\n    def test__get_tick_secs(self):\n        monitor = self._make_one_mocked()\n        self.assertEqual(5, monitor._get_tick_secs('TICK_5'))\n        self.assertEqual(60, monitor._get_tick_secs('TICK_60'))\n        self.assertEqual(3600, monitor._get_tick_secs('TICK_3600'))\n        self.assertRaises(ValueError, monitor._get_tick_secs, 'JUNK_60')\n\n    def test__get_tick_mins(self):\n        monitor = self._make_one_mocked()\n        self.assertEqual(5.0/60.0, monitor._get_tick_mins('TICK_5'))\n\n    def test_handle_event_exit(self):\n        monitor = self._make_one_mocked()\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 0)\n        monitor.handle_event(hdrs, payload)\n        unexpected_err_msg = repr(payload)\n        self.assertEqual([unexpected_err_msg], monitor.get_batch_msgs())\n        self.assertEqual('%s\\n' % unexpected_err_msg, monitor.stderr.getvalue())\n\n    def test_handle_event_non_exit(self):\n        monitor = self._make_one_mocked()\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 0,\n                                            eventname='PROCESS_STATE_FATAL')\n        monitor.handle_event(hdrs, payload)\n        self.assertEqual([], monitor.get_batch_msgs())\n        self.assertEqual('', monitor.stderr.getvalue())\n\n    def test_handle_event_tick_interval_expired(self):\n        monitor = self._make_one_mocked()\n        #Put msgs in batch\n        hdrs, payload = self.get_process_exited_event('foo', 'bar', 0)\n        monitor.handle_event(hdrs, payload)\n        hdrs, payload = self.get_process_exited_event('bark', 'dog', 0)\n        monitor.handle_event(hdrs, payload)\n        self.assertEqual(2, len(monitor.get_batch_msgs()))\n        #Time expired\n        hdrs, payload = self.get_tick60_event()\n        monitor.handle_event(hdrs, payload)\n\n        # Test that batch messages are now gone\n        self.assertEqual([], monitor.get_batch_msgs())\n        # Test that email was sent\n        self.assertEqual(1, monitor.send_batch_notification.call_count)\n\n    def test_handle_event_tick_interval_not_expired(self):\n        monitor = self._make_one_mocked(interval=3)\n        hdrs, payload = self.get_tick60_event()\n        monitor.handle_event(hdrs, payload)\n        self.assertEqual(1.0, monitor.get_batch_minutes())\n        monitor.handle_event(hdrs, payload)\n        self.assertEqual(2.0, monitor.get_batch_minutes())\n"
  },
  {
    "path": "superlance/timeoutconn.py",
    "content": "from superlance.compat import httplib\nimport socket\nimport ssl\n\n\nclass TimeoutHTTPConnection(httplib.HTTPConnection):\n    \"\"\"A customised HTTPConnection allowing a per-connection\n    timeout, specified at construction.\"\"\"\n    timeout = None\n\n    def connect(self):\n        \"\"\"Override HTTPConnection.connect to connect to\n        host/port specified in __init__.\"\"\"\n\n        e = \"getaddrinfo returns an empty list\"\n        for res in socket.getaddrinfo(self.host, self.port,\n                                      0, socket.SOCK_STREAM):\n            af, socktype, proto, canonname, sa = res\n            try:\n                self.sock = socket.socket(af, socktype, proto)\n                if self.timeout:   # this is the new bit\n                    self.sock.settimeout(self.timeout)\n                self.sock.connect(sa)\n            except socket.error:\n                if self.sock:\n                    self.sock.close()\n                self.sock = None\n                continue\n            break\n        if not self.sock:\n            raise socket.error(e)\n\n\nclass TimeoutHTTPSConnection(httplib.HTTPSConnection):\n    timeout = None\n\n    def connect(self):\n        \"Connect to a host on a given (SSL) port.\"\n\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        if self.timeout:\n            sock.settimeout(self.timeout)\n        sock.connect((self.host, self.port))\n        self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist =\n    docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313,py314\n\n[testenv]\ndeps =\n    pytest\ncommands =\n    pytest\n\n[testenv:py27]\ndeps =\n    {[testenv]deps}\n    mock >= 0.5.0\ncommands = {[testenv]commands}\n\n[testenv:docs]\ndeps =\n    Sphinx\n    readme\n    setuptools >= 18.5\nallowlist_externals = make\ncommands =\n    make -C docs html BUILDDIR={envtmpdir} \"SPHINXOPTS=-W -E\"\n    python setup.py check -m -r -s\n"
  }
]