From a289c897b31fbe90d346da0f3ef030b9b7369c61 Mon Sep 17 00:00:00 2001 From: earthgecko Date: Sun, 17 Jul 2016 13:15:41 +0100 Subject: [PATCH] crucible branch - Added a variant of @astanway Crucible into the Skyline - Added a Crucible app so that ad-hoc timeseries can be feed to it and analyzed - Restructured the Skyline layout to be more inline with a Python package and setuptools with a merge of @languitar changes for setuptools and more pythonic structure that was submitted as per: https://github.com/etsy/skyline/pull/93 https://github.com/etsy/skyline/issues/91 Provide a setuptools-based build infrastructure #93 - etsy#91 - Not totally setuptools compliant yet - Added sphinx docs and documentation build pattern - More documentation - Serve sphinx documentation via webapp /static/docs/ - Handle pandas versions changes to - PANDAS_VERSION - Attempted to handle logging without log overwrites, not pretty but works - Performance profiled - pprofile, RunSnakeRun, cProfile, vmprof, snakeviz - deroomba - kill any lingering vacuum processes - ROOMBA_TIMEOUT - Self monitor Analyzer spin_process threads and terminate if any spin_process has run for longer than 180 seconds - MAX_ANALYZER_PROCESS_RUNTIME - Optimizations to Analyzer workflow logic - RUN_OPTIMIZED_WORKFLOW - Some general code optimizations based on profiling results - Addition of algorithm_breakdown metrics - ENABLE_ALGORITHM_RUN_METRICS and SKYLINE_TMP_DIR, tmpfs over multiprocessing Value - Updated current requirements - Patterned and tested in Python virtualenv for ease of python version, package management and dependencies management - More documentation - More documentation - More documentation - More documentation, docstrings, docstrings, docstrings - Panorama modules files and related Webapp UI changes - rebrow (added https://github.com/marians/rebrow) - Upgraded Webapp UI jquery, dygraph, bootstrap - Tested all Skyline components on Python 2.7.11 and 2.7.12 Added: CHANGES.md bin/analyzer_dev.d bin/crucible.d bin/panorama.d bin/skyline.d docs/Makefile docs/_build/html/.buildinfo docs/_build/html/_images/mirage-1.png docs/_build/html/_images/nupic.radar.predicted.14.month.requests.png docs/_build/html/_images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png docs/_build/html/_images/nupic.radar.real.predicted.difference.14.month.requests.png docs/_build/html/_images/panorama.closest.approximation.aggregrated.png docs/_build/html/_images/radar.real.14.month.requests.png docs/_build/html/_modules/algorithm_exceptions.html docs/_build/html/_modules/analyzer/agent.html docs/_build/html/_modules/analyzer/alerters.html docs/_build/html/_modules/analyzer/algorithms.html docs/_build/html/_modules/analyzer/analyzer.html docs/_build/html/_modules/analyzer_dev/agent.html docs/_build/html/_modules/analyzer_dev/alerters.html docs/_build/html/_modules/analyzer_dev/algorithms_dev.html docs/_build/html/_modules/analyzer_dev/analyzer_dev.html docs/_build/html/_modules/boundary/agent.html docs/_build/html/_modules/boundary/boundary.html docs/_build/html/_modules/boundary/boundary_alerters.html docs/_build/html/_modules/boundary/boundary_algorithms.html docs/_build/html/_modules/crucible/agent.html docs/_build/html/_modules/crucible/crucible.html docs/_build/html/_modules/crucible/crucible_algorithms.html docs/_build/html/_modules/horizon/agent.html docs/_build/html/_modules/horizon/listen.html docs/_build/html/_modules/horizon/roomba.html docs/_build/html/_modules/horizon/worker.html docs/_build/html/_modules/index.html docs/_build/html/_modules/logging.html docs/_build/html/_modules/mirage/agent.html docs/_build/html/_modules/mirage/mirage.html docs/_build/html/_modules/mirage/mirage_alerters.html docs/_build/html/_modules/mirage/mirage_algorithms.html docs/_build/html/_modules/mirage/negaters.html docs/_build/html/_modules/panorama/agent.html docs/_build/html/_modules/panorama/panorama.html docs/_build/html/_modules/skyline_functions.html docs/_build/html/_modules/webapp/backend.html docs/_build/html/_modules/webapp/webapp.html docs/_build/html/_sources/alert-testing.txt docs/_build/html/_sources/analyzer-optimizations.txt docs/_build/html/_sources/analyzer.txt docs/_build/html/_sources/boundary.txt docs/_build/html/_sources/building-documentation.txt docs/_build/html/_sources/crucible.txt docs/_build/html/_sources/debian-and-vagrant-installation-tips.txt docs/_build/html/_sources/development/index.txt docs/_build/html/_sources/development/webapp.txt docs/_build/html/_sources/getting-data-into-skyline.txt docs/_build/html/_sources/getting-started.txt docs/_build/html/_sources/horizon.txt docs/_build/html/_sources/index.txt docs/_build/html/_sources/installation.txt docs/_build/html/_sources/logging.txt docs/_build/html/_sources/mirage.txt docs/_build/html/_sources/modules.txt docs/_build/html/_sources/monitoring-skyline.txt docs/_build/html/_sources/overview.txt docs/_build/html/_sources/panorama.txt docs/_build/html/_sources/redis-integration.txt docs/_build/html/_sources/releases.txt docs/_build/html/_sources/releases/1_0_0.txt docs/_build/html/_sources/requirements.txt docs/_build/html/_sources/roadmap.txt docs/_build/html/_sources/running-in-python-virtualenv.txt docs/_build/html/_sources/skyline-and-friends.txt docs/_build/html/_sources/skyline.analyzer.txt docs/_build/html/_sources/skyline.analyzer_dev.txt docs/_build/html/_sources/skyline.boundary.txt docs/_build/html/_sources/skyline.crucible.txt docs/_build/html/_sources/skyline.horizon.txt docs/_build/html/_sources/skyline.mirage.txt docs/_build/html/_sources/skyline.panorama.txt docs/_build/html/_sources/skyline.txt docs/_build/html/_sources/skyline.webapp.txt docs/_build/html/_sources/tuning-tips.txt docs/_build/html/_sources/upgrading.txt docs/_build/html/_sources/webapp.txt docs/_build/html/_sources/whats-new.txt docs/_build/html/_static/ajax-loader.gif docs/_build/html/_static/basic.css docs/_build/html/_static/comment-bright.png docs/_build/html/_static/comment-close.png docs/_build/html/_static/comment.png docs/_build/html/_static/css/badge_only.css docs/_build/html/_static/css/theme.css docs/_build/html/_static/doctools.js docs/_build/html/_static/down-pressed.png docs/_build/html/_static/down.png docs/_build/html/_static/file.png docs/_build/html/_static/fonts/Inconsolata-Bold.ttf docs/_build/html/_static/fonts/Inconsolata-Regular.ttf docs/_build/html/_static/fonts/Lato-Bold.ttf docs/_build/html/_static/fonts/Lato-Regular.ttf docs/_build/html/_static/fonts/RobotoSlab-Bold.ttf docs/_build/html/_static/fonts/RobotoSlab-Regular.ttf docs/_build/html/_static/fonts/fontawesome-webfont.eot docs/_build/html/_static/fonts/fontawesome-webfont.svg docs/_build/html/_static/fonts/fontawesome-webfont.ttf docs/_build/html/_static/fonts/fontawesome-webfont.woff docs/_build/html/_static/jquery-1.11.1.js docs/_build/html/_static/jquery.js docs/_build/html/_static/js/modernizr.min.js docs/_build/html/_static/js/theme.js docs/_build/html/_static/minus.png docs/_build/html/_static/plus.png docs/_build/html/_static/pygments.css docs/_build/html/_static/searchtools.js docs/_build/html/_static/skyline.styles.css docs/_build/html/_static/underscore-1.3.1.js docs/_build/html/_static/underscore.js docs/_build/html/_static/up-pressed.png docs/_build/html/_static/up.png docs/_build/html/_static/websupport.js docs/_build/html/alert-testing.html docs/_build/html/analyzer-optimizations.html docs/_build/html/analyzer.html docs/_build/html/boundary.html docs/_build/html/building-documentation.html docs/_build/html/crucible.html docs/_build/html/debian-and-vagrant-installation-tips.html docs/_build/html/development/index.html docs/_build/html/development/webapp.html docs/_build/html/genindex.html docs/_build/html/getting-data-into-skyline.html docs/_build/html/getting-started.html docs/_build/html/horizon.html docs/_build/html/index.html docs/_build/html/installation.html docs/_build/html/logging.html docs/_build/html/mirage-1.hires.png docs/_build/html/mirage-1.pdf docs/_build/html/mirage-1.png docs/_build/html/mirage-1.py docs/_build/html/mirage.html docs/_build/html/modules.html docs/_build/html/monitoring-skyline.html docs/_build/html/objects.inv docs/_build/html/overview.html docs/_build/html/panorama.html docs/_build/html/py-modindex.html docs/_build/html/redis-integration.html docs/_build/html/releases.html docs/_build/html/releases/1_0_0.html docs/_build/html/requirements.html docs/_build/html/roadmap.html docs/_build/html/running-in-python-virtualenv.html docs/_build/html/search.html docs/_build/html/searchindex.js docs/_build/html/skyline-and-friends.html docs/_build/html/skyline.analyzer.html docs/_build/html/skyline.analyzer_dev.html docs/_build/html/skyline.boundary.html docs/_build/html/skyline.crucible.html docs/_build/html/skyline.horizon.html docs/_build/html/skyline.html docs/_build/html/skyline.mirage.html docs/_build/html/skyline.panorama.html docs/_build/html/skyline.webapp.html docs/_build/html/tuning-tips.html docs/_build/html/upgrading.html docs/_build/html/webapp.html docs/_build/html/whats-new.html docs/_build/plot_directive/mirage-1.hires.png docs/_build/plot_directive/mirage-1.pdf docs/_build/plot_directive/mirage-1.png docs/_static/skyline.styles.css docs/alert-testing.rst docs/analyzer-optimizations.rst docs/analyzer.rst docs/boundary.rst docs/building-documentation.md docs/conf.py docs/crucible.rst docs/debian-and-vagrant-installation-tips.md docs/development/index.rst docs/development/webapp.rst docs/getting-data-into-skyline.rst docs/getting-started.rst docs/horizon.md docs/images/nupic.radar.predicted.14.month.requests.png docs/images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png docs/images/nupic.radar.real.predicted.difference.14.month.requests.png docs/images/panorama.closest.approximation.aggregrated.png docs/images/radar.real.14.month.requests.png docs/index.rst docs/installation.rst docs/logging.md docs/mirage.rst docs/modules.rst docs/monitoring-skyline.md docs/overview.rst docs/panorama.rst docs/redis-integration.md docs/releases.rst docs/releases/1_0_0.rst docs/requirements.rst docs/roadmap.rst docs/running-in-python-virtualenv.rst docs/skyline-and-friends.md docs/skyline.analyzer.rst docs/skyline.analyzer_dev.rst docs/skyline.boundary.rst docs/skyline.crucible.rst docs/skyline.horizon.rst docs/skyline.mirage.rst docs/skyline.panorama.rst docs/skyline.rst docs/skyline.webapp.rst docs/tuning-tips.md docs/upgrading.rst docs/webapp.rst docs/whats-new.md etc/skyline.conf etc/skyline.httpd.conf.d.example examples/data/Real_time_energy_data_October.csv skyline.png skyline/__init__.py skyline/algorithm_exceptions.py skyline/analyzer/__init__.py skyline/analyzer/agent.py skyline/analyzer/alerters.py skyline/analyzer/algorithms.py skyline/analyzer/analyzer.py skyline/analyzer_dev/__init__.py skyline/analyzer_dev/agent.py skyline/analyzer_dev/alerters.py skyline/analyzer_dev/algorithms_dev.py skyline/analyzer_dev/analyzer_dev.py skyline/boundary/LICENSE.md skyline/boundary/__init__.py skyline/boundary/agent.py skyline/boundary/boundary.py skyline/boundary/boundary_alerters.py skyline/boundary/boundary_algorithms.py skyline/crucible/LICENSE.md skyline/crucible/__init__.py skyline/crucible/agent.py skyline/crucible/crucible.py skyline/crucible/crucible_algorithms.py skyline/horizon/__init__.py skyline/horizon/agent.py skyline/horizon/listen.py skyline/horizon/roomba.py skyline/horizon/worker.py skyline/mirage/LICENSE.md skyline/mirage/__init__.py skyline/mirage/agent.py skyline/mirage/mirage.py skyline/mirage/mirage_alerters.py skyline/mirage/mirage_algorithms.py skyline/mirage/negaters.py skyline/panorama/LICENSE.md skyline/panorama/__init__.py skyline/panorama/agent.py skyline/panorama/panorama.py skyline/settings.py skyline/skyline.sql skyline/skyline_functions.py skyline/skyline_version.py skyline/webapp/__init__.py skyline/webapp/backend.py skyline/webapp/gunicorn.py skyline/webapp/rebrow/LICENSE skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.css skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.css.map skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css.map skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.css skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.css.map skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.min.css skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.min.css.map skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.eot skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.svg skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.ttf skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.woff skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.woff2 skyline/webapp/static/bootstrap-3.3.6-dist/js/bootstrap.js skyline/webapp/static/bootstrap-3.3.6-dist/js/bootstrap.min.js skyline/webapp/static/bootstrap-3.3.6-dist/js/npm.js skyline/webapp/static/css/skyline.styles.css skyline/webapp/static/docs skyline/webapp/static/dump/.gitignore skyline/webapp/static/dygraph-1.1.1/dygraph-combined.js skyline/webapp/static/fontawesome-4.6.3/css/font-awesome.css skyline/webapp/static/fontawesome-4.6.3/css/font-awesome.css.map skyline/webapp/static/fontawesome-4.6.3/css/font-awesome.min.css skyline/webapp/static/fontawesome-4.6.3/fonts/FontAwesome.otf skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.eot skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.svg skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.ttf skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.woff skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.woff2 skyline/webapp/static/fonts/montserrat-bold.woff skyline/webapp/static/fonts/montserrat-regular.woff skyline/webapp/static/images/favicon.ico skyline/webapp/static/jquery-2.2.4/dist/jquery.js skyline/webapp/static/jquery-2.2.4/dist/jquery.min.js skyline/webapp/static/jquery-2.2.4/dist/jquery.min.map skyline/webapp/static/js/cubism.v1.min.js skyline/webapp/static/js/mousetrap.min.js skyline/webapp/static/js/panorama.js skyline/webapp/static/js/skyline.js skyline/webapp/static/strftime-0.9.2/strftime-min.js skyline/webapp/templates/docs.html skyline/webapp/templates/layout.html skyline/webapp/templates/now.html skyline/webapp/templates/panorama.html skyline/webapp/templates/rebrow_key.html skyline/webapp/templates/rebrow_keys.html skyline/webapp/templates/rebrow_login.html skyline/webapp/templates/rebrow_server_db.html skyline/webapp/templates/uh_oh.html skyline/webapp/webapp.py tests/test_crucible_algorithms.py tests/test_imports.py Modified: bin/analyzer.d bin/boundary.d bin/horizon.d bin/mirage.d bin/webapp.d readme.md requirements.txt tests/algorithms_test.py utils/continuity.py utils/seed_data.py utils/verify_alerts.py .gitignore --- .gitignore | 3 +- .travis.yml | 3 +- CHANGES.md | 192 + bin/analyzer.d | 390 +- bin/analyzer_dev.d | 382 + bin/boundary.d | 390 +- bin/crucible.d | 382 + bin/horizon.d | 390 +- bin/mirage.d | 390 +- bin/panorama.d | 382 + bin/skyline.d | 249 + bin/webapp.d | 442 +- docs/Makefile | 216 + docs/_build/html/.buildinfo | 4 + docs/_build/html/_images/mirage-1.png | Bin 0 -> 87377 bytes ...upic.radar.predicted.14.month.requests.png | Bin 0 -> 31030 bytes ...difference.14.month.requests.overlayed.png | Bin 0 -> 79941 bytes ...predicted.difference.14.month.requests.png | Bin 0 -> 47554 bytes ...rama.closest.approximation.aggregrated.png | Bin 0 -> 66823 bytes .../_images/radar.real.14.month.requests.png | Bin 0 -> 37600 bytes .../html/_modules/algorithm_exceptions.html | 231 + docs/_build/html/_modules/analyzer/agent.html | 334 + .../html/_modules/analyzer/alerters.html | 440 + .../html/_modules/analyzer/algorithms.html | 807 ++ .../html/_modules/analyzer/analyzer.html | 961 ++ .../html/_modules/analyzer_dev/agent.html | 392 + .../html/_modules/analyzer_dev/alerters.html | 394 + .../_modules/analyzer_dev/algorithms_dev.html | 711 ++ .../_modules/analyzer_dev/analyzer_dev.html | 768 ++ docs/_build/html/_modules/boundary/agent.html | 373 + .../html/_modules/boundary/boundary.html | 1156 ++ .../_modules/boundary/boundary_alerters.html | 445 + .../boundary/boundary_algorithms.html | 593 + docs/_build/html/_modules/crucible/agent.html | 345 + .../html/_modules/crucible/crucible.html | 1156 ++ .../crucible/crucible_algorithms.html | 714 ++ docs/_build/html/_modules/horizon/agent.html | 352 + docs/_build/html/_modules/horizon/listen.html | 525 + docs/_build/html/_modules/horizon/roomba.html | 516 + docs/_build/html/_modules/horizon/worker.html | 420 + docs/_build/html/_modules/index.html | 249 + docs/_build/html/_modules/logging.html | 1957 +++ docs/_build/html/_modules/mirage/agent.html | 307 + docs/_build/html/_modules/mirage/mirage.html | 920 ++ .../html/_modules/mirage/mirage_alerters.html | 397 + .../_modules/mirage/mirage_algorithms.html | 689 ++ .../_build/html/_modules/mirage/negaters.html | 392 + docs/_build/html/_modules/panorama/agent.html | 344 + .../html/_modules/panorama/panorama.html | 1081 ++ .../html/_modules/skyline_functions.html | 937 ++ docs/_build/html/_modules/webapp/backend.html | 610 + docs/_build/html/_modules/webapp/webapp.html | 1030 ++ docs/_build/html/_sources/alert-testing.txt | 23 + .../html/_sources/analyzer-optimizations.txt | 285 + docs/_build/html/_sources/analyzer.txt | 129 + docs/_build/html/_sources/boundary.txt | 148 + .../html/_sources/building-documentation.txt | 113 + docs/_build/html/_sources/crucible.txt | 86 + .../debian-and-vagrant-installation-tips.txt | 104 + .../html/_sources/development/index.txt | 15 + .../html/_sources/development/webapp.txt | 28 + .../_sources/getting-data-into-skyline.txt | 109 + docs/_build/html/_sources/getting-started.txt | 123 + docs/_build/html/_sources/horizon.txt | 28 + docs/_build/html/_sources/index.txt | 47 + docs/_build/html/_sources/installation.txt | 247 + docs/_build/html/_sources/logging.txt | 112 + docs/_build/html/_sources/mirage.txt | 280 + docs/_build/html/_sources/modules.txt | 7 + .../html/_sources/monitoring-skyline.txt | 33 + docs/_build/html/_sources/overview.txt | 110 + docs/_build/html/_sources/panorama.txt | 32 + .../html/_sources/redis-integration.txt | 29 + docs/_build/html/_sources/releases.txt | 8 + docs/_build/html/_sources/releases/1_0_0.txt | 107 + docs/_build/html/_sources/requirements.txt | 135 + docs/_build/html/_sources/roadmap.txt | 167 + .../_sources/running-in-python-virtualenv.txt | 111 + .../html/_sources/skyline-and-friends.txt | 63 + .../_build/html/_sources/skyline.analyzer.txt | 46 + .../html/_sources/skyline.analyzer_dev.txt | 46 + .../_build/html/_sources/skyline.boundary.txt | 46 + .../_build/html/_sources/skyline.crucible.txt | 38 + docs/_build/html/_sources/skyline.horizon.txt | 46 + docs/_build/html/_sources/skyline.mirage.txt | 54 + .../_build/html/_sources/skyline.panorama.txt | 30 + docs/_build/html/_sources/skyline.txt | 60 + docs/_build/html/_sources/skyline.webapp.txt | 38 + docs/_build/html/_sources/tuning-tips.txt | 68 + docs/_build/html/_sources/upgrading.txt | 143 + docs/_build/html/_sources/webapp.txt | 206 + docs/_build/html/_sources/whats-new.txt | 97 + docs/_build/html/_static/ajax-loader.gif | Bin 0 -> 673 bytes docs/_build/html/_static/basic.css | 604 + docs/_build/html/_static/comment-bright.png | Bin 0 -> 3500 bytes docs/_build/html/_static/comment-close.png | Bin 0 -> 3578 bytes docs/_build/html/_static/comment.png | Bin 0 -> 3445 bytes docs/_build/html/_static/css/badge_only.css | 2 + docs/_build/html/_static/css/theme.css | 5 + docs/_build/html/_static/doctools.js | 287 + docs/_build/html/_static/down-pressed.png | Bin 0 -> 347 bytes docs/_build/html/_static/down.png | Bin 0 -> 347 bytes docs/_build/html/_static/file.png | Bin 0 -> 358 bytes .../html/_static/fonts/Inconsolata-Bold.ttf | Bin 0 -> 66352 bytes .../_static/fonts/Inconsolata-Regular.ttf | Bin 0 -> 84548 bytes docs/_build/html/_static/fonts/Lato-Bold.ttf | Bin 0 -> 121788 bytes .../html/_static/fonts/Lato-Regular.ttf | Bin 0 -> 120196 bytes .../html/_static/fonts/RobotoSlab-Bold.ttf | Bin 0 -> 170616 bytes .../html/_static/fonts/RobotoSlab-Regular.ttf | Bin 0 -> 169064 bytes .../_static/fonts/fontawesome-webfont.eot | Bin 0 -> 56006 bytes .../_static/fonts/fontawesome-webfont.svg | 520 + .../_static/fonts/fontawesome-webfont.ttf | Bin 0 -> 112160 bytes .../_static/fonts/fontawesome-webfont.woff | Bin 0 -> 65452 bytes docs/_build/html/_static/jquery-1.11.1.js | 10308 ++++++++++++++++ docs/_build/html/_static/jquery.js | 4 + docs/_build/html/_static/js/modernizr.min.js | 4 + docs/_build/html/_static/js/theme.js | 153 + docs/_build/html/_static/minus.png | Bin 0 -> 173 bytes docs/_build/html/_static/plus.png | Bin 0 -> 173 bytes docs/_build/html/_static/pygments.css | 65 + docs/_build/html/_static/searchtools.js | 651 + docs/_build/html/_static/skyline.styles.css | 28 + docs/_build/html/_static/underscore-1.3.1.js | 999 ++ docs/_build/html/_static/underscore.js | 31 + docs/_build/html/_static/up-pressed.png | Bin 0 -> 345 bytes docs/_build/html/_static/up.png | Bin 0 -> 345 bytes docs/_build/html/_static/websupport.js | 808 ++ docs/_build/html/alert-testing.html | 249 + docs/_build/html/analyzer-optimizations.html | 525 + docs/_build/html/analyzer.html | 351 + docs/_build/html/boundary.html | 374 + docs/_build/html/building-documentation.html | 344 + docs/_build/html/crucible.html | 312 + .../debian-and-vagrant-installation-tips.html | 329 + docs/_build/html/development/index.html | 252 + docs/_build/html/development/webapp.html | 268 + docs/_build/html/genindex.html | 1833 +++ .../html/getting-data-into-skyline.html | 332 + docs/_build/html/getting-started.html | 340 + docs/_build/html/horizon.html | 263 + docs/_build/html/index.html | 273 + docs/_build/html/installation.html | 478 + docs/_build/html/logging.html | 340 + docs/_build/html/mirage-1.hires.png | Bin 0 -> 263626 bytes docs/_build/html/mirage-1.pdf | Bin 0 -> 21874 bytes docs/_build/html/mirage-1.png | Bin 0 -> 87377 bytes docs/_build/html/mirage-1.py | 132 + docs/_build/html/mirage.html | 359 + docs/_build/html/modules.html | 307 + docs/_build/html/monitoring-skyline.html | 270 + docs/_build/html/objects.inv | Bin 0 -> 3424 bytes docs/_build/html/overview.html | 323 + docs/_build/html/panorama.html | 260 + docs/_build/html/py-modindex.html | 478 + docs/_build/html/redis-integration.html | 263 + docs/_build/html/releases.html | 241 + docs/_build/html/releases/1_0_0.html | 339 + docs/_build/html/requirements.html | 366 + docs/_build/html/roadmap.html | 394 + .../html/running-in-python-virtualenv.html | 337 + docs/_build/html/search.html | 235 + docs/_build/html/searchindex.js | 1 + docs/_build/html/skyline-and-friends.html | 305 + docs/_build/html/skyline.analyzer.html | 579 + docs/_build/html/skyline.analyzer_dev.html | 520 + docs/_build/html/skyline.boundary.html | 405 + docs/_build/html/skyline.crucible.html | 447 + docs/_build/html/skyline.horizon.html | 420 + docs/_build/html/skyline.html | 2412 ++++ docs/_build/html/skyline.mirage.html | 550 + docs/_build/html/skyline.panorama.html | 414 + docs/_build/html/skyline.webapp.html | 441 + docs/_build/html/tuning-tips.html | 289 + docs/_build/html/upgrading.html | 373 + docs/_build/html/webapp.html | 436 + docs/_build/html/whats-new.html | 348 + docs/_build/plot_directive/mirage-1.hires.png | Bin 0 -> 263626 bytes docs/_build/plot_directive/mirage-1.pdf | Bin 0 -> 21874 bytes docs/_build/plot_directive/mirage-1.png | Bin 0 -> 87377 bytes docs/_static/skyline.styles.css | 28 + docs/alert-testing.rst | 23 + docs/analyzer-optimizations.rst | 285 + docs/analyzer.rst | 129 + docs/boundary.rst | 148 + docs/building-documentation.md | 113 + docs/conf.py | 316 + docs/crucible.rst | 86 + docs/debian-and-vagrant-installation-tips.md | 104 + docs/development/index.rst | 15 + docs/development/webapp.rst | 28 + docs/getting-data-into-skyline.rst | 109 + docs/getting-started.rst | 123 + docs/horizon.md | 28 + ...upic.radar.predicted.14.month.requests.png | Bin 0 -> 31030 bytes ...difference.14.month.requests.overlayed.png | Bin 0 -> 79941 bytes ...predicted.difference.14.month.requests.png | Bin 0 -> 47554 bytes ...rama.closest.approximation.aggregrated.png | Bin 0 -> 66823 bytes docs/images/radar.real.14.month.requests.png | Bin 0 -> 37600 bytes docs/index.rst | 47 + docs/installation.rst | 247 + docs/logging.md | 112 + docs/mirage.rst | 280 + docs/modules.rst | 7 + docs/monitoring-skyline.md | 33 + docs/overview.rst | 110 + docs/panorama.rst | 32 + docs/redis-integration.md | 29 + docs/releases.rst | 8 + docs/releases/1_0_0.rst | 107 + docs/requirements.rst | 135 + docs/roadmap.rst | 167 + docs/running-in-python-virtualenv.rst | 111 + docs/skyline-and-friends.md | 63 + docs/skyline.analyzer.rst | 46 + docs/skyline.analyzer_dev.rst | 46 + docs/skyline.boundary.rst | 46 + docs/skyline.crucible.rst | 38 + docs/skyline.horizon.rst | 46 + docs/skyline.mirage.rst | 54 + docs/skyline.panorama.rst | 30 + docs/skyline.rst | 60 + docs/skyline.webapp.rst | 38 + docs/tuning-tips.md | 68 + docs/upgrading.rst | 143 + docs/webapp.rst | 206 + docs/whats-new.md | 97 + {bin => etc}/redis.conf | 0 etc/skyline.conf | 9 + etc/skyline.httpd.conf.d.example | 68 + .../data/Real_time_energy_data_October.csv | 32 + readme.md | 305 +- requirements.txt | 93 +- screenshot.png | Bin 169759 -> 0 bytes skyline.png | Bin 0 -> 157019 bytes skyline/__init__.py | 4 + .../algorithm_exceptions.py | 0 skyline/analyzer/__init__.py | 0 skyline/analyzer/agent.py | 113 + skyline/analyzer/alerters.py | 219 + skyline/analyzer/algorithms.py | 586 + skyline/analyzer/analyzer.py | 740 ++ skyline/analyzer_dev/__init__.py | 0 skyline/analyzer_dev/agent.py | 171 + .../analyzer_dev}/alerters.py | 48 +- skyline/analyzer_dev/algorithms_dev.py | 490 + skyline/analyzer_dev/analyzer_dev.py | 547 + skyline/boundary/LICENSE.md | 21 + skyline/boundary/__init__.py | 0 .../boundary/agent.py | 65 +- {src => skyline}/boundary/boundary.py | 407 +- skyline/boundary/boundary_alerters.py | 224 + .../boundary/boundary_algorithms.py | 133 +- skyline/crucible/LICENSE.md | 21 + skyline/crucible/__init__.py | 1 + skyline/crucible/agent.py | 124 + skyline/crucible/crucible.py | 935 ++ skyline/crucible/crucible_algorithms.py | 493 + skyline/horizon/__init__.py | 0 .../horizon/agent.py | 70 +- {src => skyline}/horizon/listen.py | 112 +- skyline/horizon/roomba.py | 295 + skyline/horizon/worker.py | 199 + skyline/mirage/LICENSE.md | 21 + skyline/mirage/__init__.py | 0 .../mirage/agent.py | 44 +- {src => skyline}/mirage/mirage.py | 313 +- skyline/mirage/mirage_alerters.py | 176 + skyline/mirage/mirage_algorithms.py | 468 + {src => skyline}/mirage/negaters.py | 25 +- skyline/panorama/LICENSE.md | 21 + skyline/panorama/__init__.py | 0 skyline/panorama/agent.py | 123 + skyline/panorama/panorama.py | 860 ++ skyline/settings.py | 1209 ++ skyline/skyline.sql | 95 + skyline/skyline_functions.py | 716 ++ skyline/skyline_version.py | 12 + skyline/webapp/__init__.py | 0 skyline/webapp/backend.py | 389 + skyline/webapp/gunicorn.py | 42 + skyline/webapp/rebrow/LICENSE | 21 + .../css/bootstrap-theme.css | 587 + .../css/bootstrap-theme.css.map | 1 + .../css/bootstrap-theme.min.css | 6 + .../css/bootstrap-theme.min.css.map | 1 + .../bootstrap-3.3.6-dist/css/bootstrap.css | 6760 ++++++++++ .../css/bootstrap.css.map | 1 + .../css/bootstrap.min.css | 6 + .../css/bootstrap.min.css.map | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../bootstrap-3.3.6-dist/js/bootstrap.js | 2363 ++++ .../bootstrap-3.3.6-dist/js/bootstrap.min.js | 7 + .../static/bootstrap-3.3.6-dist/js/npm.js | 13 + skyline/webapp/static/css/skyline.styles.css | 745 ++ skyline/webapp/static/docs | 1 + .../webapp/static/dump/.gitignore | 0 .../static/dygraph-1.1.1/dygraph-combined.js | 6 + .../fontawesome-4.6.3/css/font-awesome.css | 2199 ++++ .../css/font-awesome.css.map | 7 + .../css/font-awesome.min.css | 4 + .../fontawesome-4.6.3/fonts/FontAwesome.otf | Bin 0 -> 124988 bytes .../fonts/fontawesome-webfont.eot | Bin 0 -> 76518 bytes .../fonts/fontawesome-webfont.svg | 685 + .../fonts/fontawesome-webfont.ttf | Bin 0 -> 152796 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 90412 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 71896 bytes .../webapp/static/fonts/montserrat-bold.woff | Bin 0 -> 14584 bytes .../static/fonts/montserrat-regular.woff | Bin 0 -> 14412 bytes skyline/webapp/static/images/favicon.ico | Bin 0 -> 1150 bytes .../webapp/static/jquery-2.2.4/dist/jquery.js | 9814 +++++++++++++++ .../static/jquery-2.2.4/dist/jquery.min.js | 4 + .../static/jquery-2.2.4/dist/jquery.min.map | 1 + skyline/webapp/static/js/cubism.v1.min.js | 1 + .../webapp/static/js/mousetrap.min.js | 0 skyline/webapp/static/js/panorama.js | 318 + {src => skyline}/webapp/static/js/skyline.js | 90 +- .../static/strftime-0.9.2/strftime-min.js | 13 + skyline/webapp/templates/docs.html | 25 + skyline/webapp/templates/layout.html | 119 + skyline/webapp/templates/now.html | 35 + skyline/webapp/templates/panorama.html | 165 + skyline/webapp/templates/rebrow_key.html | 125 + skyline/webapp/templates/rebrow_keys.html | 90 + skyline/webapp/templates/rebrow_login.html | 53 + .../webapp/templates/rebrow_server_db.html | 131 + skyline/webapp/templates/uh_oh.html | 39 + skyline/webapp/webapp.py | 809 ++ src/analyzer/algorithms.py | 301 - src/analyzer/analyzer-agent.py | 79 - src/analyzer/analyzer.py | 286 - src/boundary/alerters.py | 190 - src/boundary/algorithm_exceptions.py | 10 - src/horizon/roomba.py | 180 - src/horizon/worker.py | 128 - src/mirage/alerters.py | 137 - src/mirage/algorithm_exceptions.py | 10 - src/mirage/algorithms.py | 283 - src/settings.py.example | 489 - src/webapp/static/css/bootstrap.min.css | 9 - src/webapp/static/css/font-awesome.min.css | 33 - src/webapp/static/css/skyline.css | 278 - src/webapp/static/font/FontAwesome.otf | Bin 48748 -> 0 bytes .../static/font/fontawesome-webfont.eot | Bin 25395 -> 0 bytes .../static/font/fontawesome-webfont.svg | 284 - .../static/font/fontawesome-webfont.ttf | Bin 55096 -> 0 bytes .../static/font/fontawesome-webfont.woff | Bin 29380 -> 0 bytes src/webapp/static/js/dygraph-combined.js | 2 - src/webapp/static/js/jquery.min.js | 2 - src/webapp/templates/index.html | 44 - src/webapp/webapp.py | 98 - tests/algorithms_test.py | 16 +- tests/test_crucible_algorithms.py | 142 + tests/test_imports.py | 146 + utils/continuity.py | 6 +- utils/seed_data.py | 77 +- utils/verify_alerts.py | 80 +- 360 files changed, 101641 insertions(+), 3717 deletions(-) create mode 100644 CHANGES.md create mode 100755 bin/analyzer_dev.d create mode 100755 bin/crucible.d create mode 100755 bin/panorama.d create mode 100755 bin/skyline.d create mode 100644 docs/Makefile create mode 100644 docs/_build/html/.buildinfo create mode 100644 docs/_build/html/_images/mirage-1.png create mode 100644 docs/_build/html/_images/nupic.radar.predicted.14.month.requests.png create mode 100644 docs/_build/html/_images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png create mode 100644 docs/_build/html/_images/nupic.radar.real.predicted.difference.14.month.requests.png create mode 100644 docs/_build/html/_images/panorama.closest.approximation.aggregrated.png create mode 100644 docs/_build/html/_images/radar.real.14.month.requests.png create mode 100644 docs/_build/html/_modules/algorithm_exceptions.html create mode 100644 docs/_build/html/_modules/analyzer/agent.html create mode 100644 docs/_build/html/_modules/analyzer/alerters.html create mode 100644 docs/_build/html/_modules/analyzer/algorithms.html create mode 100644 docs/_build/html/_modules/analyzer/analyzer.html create mode 100644 docs/_build/html/_modules/analyzer_dev/agent.html create mode 100644 docs/_build/html/_modules/analyzer_dev/alerters.html create mode 100644 docs/_build/html/_modules/analyzer_dev/algorithms_dev.html create mode 100644 docs/_build/html/_modules/analyzer_dev/analyzer_dev.html create mode 100644 docs/_build/html/_modules/boundary/agent.html create mode 100644 docs/_build/html/_modules/boundary/boundary.html create mode 100644 docs/_build/html/_modules/boundary/boundary_alerters.html create mode 100644 docs/_build/html/_modules/boundary/boundary_algorithms.html create mode 100644 docs/_build/html/_modules/crucible/agent.html create mode 100644 docs/_build/html/_modules/crucible/crucible.html create mode 100644 docs/_build/html/_modules/crucible/crucible_algorithms.html create mode 100644 docs/_build/html/_modules/horizon/agent.html create mode 100644 docs/_build/html/_modules/horizon/listen.html create mode 100644 docs/_build/html/_modules/horizon/roomba.html create mode 100644 docs/_build/html/_modules/horizon/worker.html create mode 100644 docs/_build/html/_modules/index.html create mode 100644 docs/_build/html/_modules/logging.html create mode 100644 docs/_build/html/_modules/mirage/agent.html create mode 100644 docs/_build/html/_modules/mirage/mirage.html create mode 100644 docs/_build/html/_modules/mirage/mirage_alerters.html create mode 100644 docs/_build/html/_modules/mirage/mirage_algorithms.html create mode 100644 docs/_build/html/_modules/mirage/negaters.html create mode 100644 docs/_build/html/_modules/panorama/agent.html create mode 100644 docs/_build/html/_modules/panorama/panorama.html create mode 100644 docs/_build/html/_modules/skyline_functions.html create mode 100644 docs/_build/html/_modules/webapp/backend.html create mode 100644 docs/_build/html/_modules/webapp/webapp.html create mode 100644 docs/_build/html/_sources/alert-testing.txt create mode 100644 docs/_build/html/_sources/analyzer-optimizations.txt create mode 100644 docs/_build/html/_sources/analyzer.txt create mode 100644 docs/_build/html/_sources/boundary.txt create mode 100644 docs/_build/html/_sources/building-documentation.txt create mode 100644 docs/_build/html/_sources/crucible.txt create mode 100644 docs/_build/html/_sources/debian-and-vagrant-installation-tips.txt create mode 100644 docs/_build/html/_sources/development/index.txt create mode 100644 docs/_build/html/_sources/development/webapp.txt create mode 100644 docs/_build/html/_sources/getting-data-into-skyline.txt create mode 100644 docs/_build/html/_sources/getting-started.txt create mode 100644 docs/_build/html/_sources/horizon.txt create mode 100644 docs/_build/html/_sources/index.txt create mode 100644 docs/_build/html/_sources/installation.txt create mode 100644 docs/_build/html/_sources/logging.txt create mode 100644 docs/_build/html/_sources/mirage.txt create mode 100644 docs/_build/html/_sources/modules.txt create mode 100644 docs/_build/html/_sources/monitoring-skyline.txt create mode 100644 docs/_build/html/_sources/overview.txt create mode 100644 docs/_build/html/_sources/panorama.txt create mode 100644 docs/_build/html/_sources/redis-integration.txt create mode 100644 docs/_build/html/_sources/releases.txt create mode 100644 docs/_build/html/_sources/releases/1_0_0.txt create mode 100644 docs/_build/html/_sources/requirements.txt create mode 100644 docs/_build/html/_sources/roadmap.txt create mode 100644 docs/_build/html/_sources/running-in-python-virtualenv.txt create mode 100644 docs/_build/html/_sources/skyline-and-friends.txt create mode 100644 docs/_build/html/_sources/skyline.analyzer.txt create mode 100644 docs/_build/html/_sources/skyline.analyzer_dev.txt create mode 100644 docs/_build/html/_sources/skyline.boundary.txt create mode 100644 docs/_build/html/_sources/skyline.crucible.txt create mode 100644 docs/_build/html/_sources/skyline.horizon.txt create mode 100644 docs/_build/html/_sources/skyline.mirage.txt create mode 100644 docs/_build/html/_sources/skyline.panorama.txt create mode 100644 docs/_build/html/_sources/skyline.txt create mode 100644 docs/_build/html/_sources/skyline.webapp.txt create mode 100644 docs/_build/html/_sources/tuning-tips.txt create mode 100644 docs/_build/html/_sources/upgrading.txt create mode 100644 docs/_build/html/_sources/webapp.txt create mode 100644 docs/_build/html/_sources/whats-new.txt create mode 100644 docs/_build/html/_static/ajax-loader.gif create mode 100644 docs/_build/html/_static/basic.css create mode 100644 docs/_build/html/_static/comment-bright.png create mode 100644 docs/_build/html/_static/comment-close.png create mode 100644 docs/_build/html/_static/comment.png create mode 100644 docs/_build/html/_static/css/badge_only.css create mode 100644 docs/_build/html/_static/css/theme.css create mode 100644 docs/_build/html/_static/doctools.js create mode 100644 docs/_build/html/_static/down-pressed.png create mode 100644 docs/_build/html/_static/down.png create mode 100644 docs/_build/html/_static/file.png create mode 100644 docs/_build/html/_static/fonts/Inconsolata-Bold.ttf create mode 100644 docs/_build/html/_static/fonts/Inconsolata-Regular.ttf create mode 100644 docs/_build/html/_static/fonts/Lato-Bold.ttf create mode 100644 docs/_build/html/_static/fonts/Lato-Regular.ttf create mode 100644 docs/_build/html/_static/fonts/RobotoSlab-Bold.ttf create mode 100644 docs/_build/html/_static/fonts/RobotoSlab-Regular.ttf create mode 100644 docs/_build/html/_static/fonts/fontawesome-webfont.eot create mode 100644 docs/_build/html/_static/fonts/fontawesome-webfont.svg create mode 100644 docs/_build/html/_static/fonts/fontawesome-webfont.ttf create mode 100644 docs/_build/html/_static/fonts/fontawesome-webfont.woff create mode 100644 docs/_build/html/_static/jquery-1.11.1.js create mode 100644 docs/_build/html/_static/jquery.js create mode 100644 docs/_build/html/_static/js/modernizr.min.js create mode 100644 docs/_build/html/_static/js/theme.js create mode 100644 docs/_build/html/_static/minus.png create mode 100644 docs/_build/html/_static/plus.png create mode 100644 docs/_build/html/_static/pygments.css create mode 100644 docs/_build/html/_static/searchtools.js create mode 100644 docs/_build/html/_static/skyline.styles.css create mode 100644 docs/_build/html/_static/underscore-1.3.1.js create mode 100644 docs/_build/html/_static/underscore.js create mode 100644 docs/_build/html/_static/up-pressed.png create mode 100644 docs/_build/html/_static/up.png create mode 100644 docs/_build/html/_static/websupport.js create mode 100644 docs/_build/html/alert-testing.html create mode 100644 docs/_build/html/analyzer-optimizations.html create mode 100644 docs/_build/html/analyzer.html create mode 100644 docs/_build/html/boundary.html create mode 100644 docs/_build/html/building-documentation.html create mode 100644 docs/_build/html/crucible.html create mode 100644 docs/_build/html/debian-and-vagrant-installation-tips.html create mode 100644 docs/_build/html/development/index.html create mode 100644 docs/_build/html/development/webapp.html create mode 100644 docs/_build/html/genindex.html create mode 100644 docs/_build/html/getting-data-into-skyline.html create mode 100644 docs/_build/html/getting-started.html create mode 100644 docs/_build/html/horizon.html create mode 100644 docs/_build/html/index.html create mode 100644 docs/_build/html/installation.html create mode 100644 docs/_build/html/logging.html create mode 100644 docs/_build/html/mirage-1.hires.png create mode 100644 docs/_build/html/mirage-1.pdf create mode 100644 docs/_build/html/mirage-1.png create mode 100644 docs/_build/html/mirage-1.py create mode 100644 docs/_build/html/mirage.html create mode 100644 docs/_build/html/modules.html create mode 100644 docs/_build/html/monitoring-skyline.html create mode 100644 docs/_build/html/objects.inv create mode 100644 docs/_build/html/overview.html create mode 100644 docs/_build/html/panorama.html create mode 100644 docs/_build/html/py-modindex.html create mode 100644 docs/_build/html/redis-integration.html create mode 100644 docs/_build/html/releases.html create mode 100644 docs/_build/html/releases/1_0_0.html create mode 100644 docs/_build/html/requirements.html create mode 100644 docs/_build/html/roadmap.html create mode 100644 docs/_build/html/running-in-python-virtualenv.html create mode 100644 docs/_build/html/search.html create mode 100644 docs/_build/html/searchindex.js create mode 100644 docs/_build/html/skyline-and-friends.html create mode 100644 docs/_build/html/skyline.analyzer.html create mode 100644 docs/_build/html/skyline.analyzer_dev.html create mode 100644 docs/_build/html/skyline.boundary.html create mode 100644 docs/_build/html/skyline.crucible.html create mode 100644 docs/_build/html/skyline.horizon.html create mode 100644 docs/_build/html/skyline.html create mode 100644 docs/_build/html/skyline.mirage.html create mode 100644 docs/_build/html/skyline.panorama.html create mode 100644 docs/_build/html/skyline.webapp.html create mode 100644 docs/_build/html/tuning-tips.html create mode 100644 docs/_build/html/upgrading.html create mode 100644 docs/_build/html/webapp.html create mode 100644 docs/_build/html/whats-new.html create mode 100644 docs/_build/plot_directive/mirage-1.hires.png create mode 100644 docs/_build/plot_directive/mirage-1.pdf create mode 100644 docs/_build/plot_directive/mirage-1.png create mode 100644 docs/_static/skyline.styles.css create mode 100644 docs/alert-testing.rst create mode 100644 docs/analyzer-optimizations.rst create mode 100644 docs/analyzer.rst create mode 100644 docs/boundary.rst create mode 100644 docs/building-documentation.md create mode 100644 docs/conf.py create mode 100644 docs/crucible.rst create mode 100644 docs/debian-and-vagrant-installation-tips.md create mode 100644 docs/development/index.rst create mode 100644 docs/development/webapp.rst create mode 100644 docs/getting-data-into-skyline.rst create mode 100644 docs/getting-started.rst create mode 100644 docs/horizon.md create mode 100644 docs/images/nupic.radar.predicted.14.month.requests.png create mode 100644 docs/images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png create mode 100644 docs/images/nupic.radar.real.predicted.difference.14.month.requests.png create mode 100644 docs/images/panorama.closest.approximation.aggregrated.png create mode 100644 docs/images/radar.real.14.month.requests.png create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/logging.md create mode 100644 docs/mirage.rst create mode 100644 docs/modules.rst create mode 100644 docs/monitoring-skyline.md create mode 100644 docs/overview.rst create mode 100644 docs/panorama.rst create mode 100644 docs/redis-integration.md create mode 100644 docs/releases.rst create mode 100644 docs/releases/1_0_0.rst create mode 100644 docs/requirements.rst create mode 100644 docs/roadmap.rst create mode 100644 docs/running-in-python-virtualenv.rst create mode 100644 docs/skyline-and-friends.md create mode 100644 docs/skyline.analyzer.rst create mode 100644 docs/skyline.analyzer_dev.rst create mode 100644 docs/skyline.boundary.rst create mode 100644 docs/skyline.crucible.rst create mode 100644 docs/skyline.horizon.rst create mode 100644 docs/skyline.mirage.rst create mode 100644 docs/skyline.panorama.rst create mode 100644 docs/skyline.rst create mode 100644 docs/skyline.webapp.rst create mode 100644 docs/tuning-tips.md create mode 100644 docs/upgrading.rst create mode 100644 docs/webapp.rst create mode 100644 docs/whats-new.md rename {bin => etc}/redis.conf (100%) create mode 100644 etc/skyline.conf create mode 100644 etc/skyline.httpd.conf.d.example create mode 100644 examples/data/Real_time_energy_data_October.csv delete mode 100644 screenshot.png create mode 100644 skyline.png create mode 100644 skyline/__init__.py rename {src/analyzer => skyline}/algorithm_exceptions.py (100%) create mode 100644 skyline/analyzer/__init__.py create mode 100644 skyline/analyzer/agent.py create mode 100644 skyline/analyzer/alerters.py create mode 100644 skyline/analyzer/algorithms.py create mode 100644 skyline/analyzer/analyzer.py create mode 100644 skyline/analyzer_dev/__init__.py create mode 100644 skyline/analyzer_dev/agent.py rename {src/analyzer => skyline/analyzer_dev}/alerters.py (79%) create mode 100644 skyline/analyzer_dev/algorithms_dev.py create mode 100644 skyline/analyzer_dev/analyzer_dev.py create mode 100644 skyline/boundary/LICENSE.md create mode 100644 skyline/boundary/__init__.py rename src/boundary/boundary-agent.py => skyline/boundary/agent.py (65%) rename {src => skyline}/boundary/boundary.py (61%) create mode 100644 skyline/boundary/boundary_alerters.py rename src/boundary/algorithms.py => skyline/boundary/boundary_algorithms.py (72%) create mode 100644 skyline/crucible/LICENSE.md create mode 100644 skyline/crucible/__init__.py create mode 100644 skyline/crucible/agent.py create mode 100644 skyline/crucible/crucible.py create mode 100644 skyline/crucible/crucible_algorithms.py create mode 100644 skyline/horizon/__init__.py rename src/horizon/horizon-agent.py => skyline/horizon/agent.py (51%) rename {src => skyline}/horizon/listen.py (59%) create mode 100644 skyline/horizon/roomba.py create mode 100644 skyline/horizon/worker.py create mode 100644 skyline/mirage/LICENSE.md create mode 100644 skyline/mirage/__init__.py rename src/mirage/mirage-agent.py => skyline/mirage/agent.py (61%) rename {src => skyline}/mirage/mirage.py (52%) create mode 100644 skyline/mirage/mirage_alerters.py create mode 100644 skyline/mirage/mirage_algorithms.py rename {src => skyline}/mirage/negaters.py (91%) create mode 100644 skyline/panorama/LICENSE.md create mode 100644 skyline/panorama/__init__.py create mode 100644 skyline/panorama/agent.py create mode 100644 skyline/panorama/panorama.py create mode 100644 skyline/settings.py create mode 100644 skyline/skyline.sql create mode 100644 skyline/skyline_functions.py create mode 100644 skyline/skyline_version.py create mode 100644 skyline/webapp/__init__.py create mode 100644 skyline/webapp/backend.py create mode 100644 skyline/webapp/gunicorn.py create mode 100644 skyline/webapp/rebrow/LICENSE create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.css create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.css.map create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css.map create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.css create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.css.map create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.min.css create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/css/bootstrap.min.css.map create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.eot create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.svg create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.ttf create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.woff create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.woff2 create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/js/bootstrap.js create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/js/bootstrap.min.js create mode 100644 skyline/webapp/static/bootstrap-3.3.6-dist/js/npm.js create mode 100644 skyline/webapp/static/css/skyline.styles.css create mode 120000 skyline/webapp/static/docs rename {src => skyline}/webapp/static/dump/.gitignore (100%) create mode 100644 skyline/webapp/static/dygraph-1.1.1/dygraph-combined.js create mode 100644 skyline/webapp/static/fontawesome-4.6.3/css/font-awesome.css create mode 100644 skyline/webapp/static/fontawesome-4.6.3/css/font-awesome.css.map create mode 100644 skyline/webapp/static/fontawesome-4.6.3/css/font-awesome.min.css create mode 100644 skyline/webapp/static/fontawesome-4.6.3/fonts/FontAwesome.otf create mode 100644 skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.eot create mode 100644 skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.svg create mode 100644 skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.ttf create mode 100644 skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.woff create mode 100644 skyline/webapp/static/fontawesome-4.6.3/fonts/fontawesome-webfont.woff2 create mode 100644 skyline/webapp/static/fonts/montserrat-bold.woff create mode 100644 skyline/webapp/static/fonts/montserrat-regular.woff create mode 100644 skyline/webapp/static/images/favicon.ico create mode 100644 skyline/webapp/static/jquery-2.2.4/dist/jquery.js create mode 100644 skyline/webapp/static/jquery-2.2.4/dist/jquery.min.js create mode 100644 skyline/webapp/static/jquery-2.2.4/dist/jquery.min.map create mode 100644 skyline/webapp/static/js/cubism.v1.min.js rename {src => skyline}/webapp/static/js/mousetrap.min.js (100%) create mode 100644 skyline/webapp/static/js/panorama.js rename {src => skyline}/webapp/static/js/skyline.js (65%) create mode 100644 skyline/webapp/static/strftime-0.9.2/strftime-min.js create mode 100644 skyline/webapp/templates/docs.html create mode 100644 skyline/webapp/templates/layout.html create mode 100644 skyline/webapp/templates/now.html create mode 100644 skyline/webapp/templates/panorama.html create mode 100644 skyline/webapp/templates/rebrow_key.html create mode 100644 skyline/webapp/templates/rebrow_keys.html create mode 100644 skyline/webapp/templates/rebrow_login.html create mode 100644 skyline/webapp/templates/rebrow_server_db.html create mode 100644 skyline/webapp/templates/uh_oh.html create mode 100644 skyline/webapp/webapp.py delete mode 100644 src/analyzer/algorithms.py delete mode 100644 src/analyzer/analyzer-agent.py delete mode 100644 src/analyzer/analyzer.py delete mode 100644 src/boundary/alerters.py delete mode 100644 src/boundary/algorithm_exceptions.py delete mode 100644 src/horizon/roomba.py delete mode 100644 src/horizon/worker.py delete mode 100644 src/mirage/alerters.py delete mode 100644 src/mirage/algorithm_exceptions.py delete mode 100644 src/mirage/algorithms.py delete mode 100644 src/settings.py.example delete mode 100644 src/webapp/static/css/bootstrap.min.css delete mode 100644 src/webapp/static/css/font-awesome.min.css delete mode 100644 src/webapp/static/css/skyline.css delete mode 100644 src/webapp/static/font/FontAwesome.otf delete mode 100755 src/webapp/static/font/fontawesome-webfont.eot delete mode 100755 src/webapp/static/font/fontawesome-webfont.svg delete mode 100755 src/webapp/static/font/fontawesome-webfont.ttf delete mode 100755 src/webapp/static/font/fontawesome-webfont.woff delete mode 100644 src/webapp/static/js/dygraph-combined.js delete mode 100644 src/webapp/static/js/jquery.min.js delete mode 100644 src/webapp/templates/index.html delete mode 100644 src/webapp/webapp.py create mode 100644 tests/test_crucible_algorithms.py create mode 100644 tests/test_imports.py diff --git a/.gitignore b/.gitignore index 23678671..0240767b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc *.swp *.log -settings.py dump.rdb nohup.out +docs/_build/doctrees +__pycache__/ diff --git a/.travis.yml b/.travis.yml index 572d3697..017296af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,9 @@ install: - PYTHONPATH= PATH=/home/travis/anaconda/bin:$PATH pip install -r requirements.txt --use-mirrors - PYTHONPATH= PATH=/home/travis/anaconda/bin:$PATH pip install patsy --use-mirrors - PYTHONPATH= PATH=/home/travis/anaconda/bin:$PATH pip install msgpack_python --use-mirrors - - cp src/settings.py.example src/settings.py - pip install pep8 --use-mirrors script: - PYTHONPATH= PATH=/home/travis/anaconda/bin:$PATH nosetests -v --nocapture - - pep8 --exclude=migrations --ignore=E501,E251,E265,E402 ./ + - pep8 --exclude=migrations --ignore=E501,E251,E265,E402 ./ notifications: email: false diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 00000000..e22c1f73 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,192 @@ +# CHANGES.md + +## Panorama + +- mysql-connector-python added +- rebrow added +- Webapp UI changes +- Added Panorama module files and related Webapp UI changes +- Upgraded Webapp UI jquery, dygraph, bootstrap +- Tested all Skyline components on Python 2.7.11 and 2.7.12 + +## Crucible + +- Added a variant of @astanway crucible into the skyline src +- Added a crucible app so that ad-hoc timeseries can be feed to it and analyzed +- Restructured the skyline layout to be more inline with a Python package and + setuptools with a merge of @languitar changes for setuptools and more pythonic + structure that was submitted as per: + https://github.com/etsy/skyline/pull/93 + https://github.com/etsy/skyline/issues/91 + Provide a setuptools-based build infrastructure #93 - etsy#91 +- Not totally setuptools compliant yet +- Added sphinx docs and documentation build pattern +- More documentation +- Serve sphinx documentation via webapp /static/docs/ +- Handle pandas versions changes to - PANDAS_VERSION +- Attempted to handle logging without log overwrites, not pretty but works +- Performance profiled - pprofile, RunSnakeRun, cProfile, vmprof, snakeviz +- deroomba - kill any lingering vacuum processes - ROOMBA_TIMEOUT +- Self monitor Analyzer spin_process threads and terminate if any spin_process + has run for longer than 180 seconds - MAX_ANALYZER_PROCESS_RUNTIME +- Optimizations to Analyzer workflow logic - RUN_OPTIMIZED_WORKFLOW +- Some general code optimizations based on profiling results +- Addition of algorithm_breakdown metrics - ENABLE_ALGORITHM_RUN_METRICS and + SKYLINE_TMP_DIR, tmpfs over multiprocessing Value +- Updated current requirements +- Patterned and tested in Python virtualenv for ease of python version, package + management and dependencies management + +## boundary - Nov 24, 2015 + +- boundary - hipchat - wildcard metric namespaces +- Merged the boundary branch and functionality to master, this supercedes the + detect_drop_off_cliff-algorithm branch which was not really fit for purpose + in so much as it was too much modification of analyzer. boundary is more fit + for purposes and adds a lot of functionality and another dimension to skyline. + This commit reverts skyline analyzer back to the mirage branch version and + extends through boundary. +- Refactored other skyline code to pep8 (mostly bar E501 and E402) + +## detect_drop_off_cliff - Nov 12, 2015 + +- Update fork info From etsy/skyline to earthgecko/skylineMerge Modified: readme.md +- Merge detect_drop_off_cliff-algorithm branch and functionality to master + +## mirage - Nov 12, 2015 + +- Merge mirage branch and functionality to master +- Handle connection error to Graphite +- Break the loop when connection closes. +- Add ability to embed graphite graphs in emails - @mikedougherty As per https://github.com/etsy/skyline/pull/76 +- multiple_skylines_graphite_namespace - merge +- wildcard_alert_patterns - merge +- Fixing #92 - Satisfying missing module dependencies for SafeUnpickler class +- Fixing #77 - Patched in the SafeUnpickler from Graphite Carbon + +## skyline.analyzer.metrics - Jun 11, 2014 + +- Added functionality to analyzer.py for skyline to feed all of its own metrics + back to graphite. This results in skyline analyzing its own metrics for free. + +## alert_syslog - Jun 10, 2014 +- Added new alerter syslog alert_syslog to write anomalous metrics to the + syslog at LOG_LOCAL4, with Anomalous metric: %s (value: %s) so LOG_WARN + priority this creates in local log and ships to any remote syslog as well, so + that it can be used further down a data pipeline + +## etsy - original +- Fixing #83 - correcting algorithm docs +- Doh. +- pep8 rule tweak +- Use SVG for the Travis badge in the README +- added restart option +- whitespace cleanup for pep8 +- update language of GRAPHITE_HOST a little, could still use some love +- Add GRAPH_URL config option so that GRAPHITE_HOST is just the Graphite Host or ip. +- Added python-simple-hipchat to requirements and added a comment to README file +- Support for multiple alert recipients +- Process series under FULL_DURATION anyway. Closes #63 +- Rename GRAPHITE_PORT to CARBON_PORT +- Update settings.py +- Expand the Contributions section of the README +- missed settings file in my initial pep8 cleanup +- Update travis config to search all code instead of just src +- pep8 cleanup +- Indent to PEP8 standards +- Move dicts out of class variables +- Clean up debugging statements +- Remove shared dictionaries +- UX on verify_alerts.py +- Add script to test/verify alert configuration +- PEP8 spacing on seed_data.py +- update seed_data to respect namespace settings +- Expand the Contributions section of the README +- Missed one +- pep8 cleanup to pass travis tests +- missed settings file in my initial pep8 cleanup +- Update travis config to search all code instead of just src +- pep8 cleanup +- Indent to PEP8 standards +- Move dicts out of class variables +- Clean up debugging statements +- Remove shared dictionaries +- UX on verify_alerts.py +- Add script to test/verify alert configuration +- PEP8 spacing on seed_data.py +- Removing dependency on Oculus +- update seed_data to respect namespace settings +- Removing dependency on Oculus +- Removing dependency on Oculus +- seed_data: Delay Redis connection until we need it +- seed_data: Don't catch all Exceptions +- seed_data: Simplify settings import. +- seed_data: Reorganize imports per PEP8. +- seed_data: Move into function. +- seed_data: Allow running from arbitrary directories. +- seed_data: Make executable. +- Rename simple_stddev; better UX around broken algorithms +- Log only on canary: https://github.com/etsy/skyline/pull/56 +- Graphite canary; pids on all logs. Fixes #55 +- That time we actually wrote this ourselves +- Add BOREDOM_SET_SIZE setting +- Raise boredom level to 2 by default; ignore binary series +- Fix tests +- Dump RDB to /var/dump by default +- Turn second order anomalies +- Fixes #53 +- Second order anomalies +- Trailing comma +- Update .travis.yml for working Anaconda URL +- Update readme.md +- Abstracting alerters and adding HipChat and PagerDuty +- Markdown +- Update readme for alerts +- [API BREAKING]: Email alerts +- Get rid of Bootstrap folder +- feed enough data into ks and adf tests +- Update defaults for Kolmogorov-Smirnov +- fix non-ascii in description +- add 2 sample Kolmogorov-Smirnov test +- Add some messaging for start scripts' run command +- Add usage for run in analyzer.d +- Add run to usage help in webapp.d +- Add support for running in non-daemon mode +- Compare median of deviations instead of true median +- Median absolute deviation algorithm +- Add space in canary log metric +- Append namespace to canary metric +- Update readme.md +- Add example statsd exclusion namespaces +- Refactor Graphite sending, add GRAPHITE_PORT setting +- Allow start/stop scripts to be run from anywhere Add -f to rm in start/stop scripts to suppress no such file warnings +- Actual link +- Link to mailing list +- Add HORIZON_IP to sample settings. Change Listen to use said variable, defaulting to 127.0.0.1 if it does not exist +- Link travis build status image to build details +- Disable email notifications +- Add Travis status +- Fixup _addSkip() - https://github.com/etsy/skyline/pull/27#issuecomment-19858932 +- unittest2 and mock in requirements.txt +- Unit tests (single commit). See https://github.com/etsy/skyline/pull/11 +- removed extra lines +- added ROOMBA_GRACE_TIME setting for cleanup process +- Bugfix: TypeError: unhashable type: 'list'. Resolution: Include only the data points from timeseries in the set. +- Update example settings +- A data set is boring if "the last MAX_TOLERABLE_BOREDOM values are the same number, not that they sum to zero." +- Readme +- Ignore settings.py for public repo. You should Chef out your local settings.py if you're using this in production. +- Removing unused imports from src/horizon/* +- Call super() in Thread/Process subclasses in src/horizon/ +- Bugfix: missed a trailing : +- Minor idiomatic cleanup: - Call super().__init__() in Thread subclass - Use context manager to close out file when dumping anomalous metrics - Use identity comparison for None - Simplified unpacker -> timeseries fixup +- Minor refactoring of return values to avoid redundant if blocks +- Removing unused imports +- Removing unused imports +- Cleaning up continuity.py a bit: - "tuple" is reserved - Calculate total_sum as the sum of the last 50 items via slice notation - None is singleton. Compare via identity, not equality +- Various doc comments +- Update readme.md +- PID location for the webapp +- License +- Readme +- First diff --git a/bin/analyzer.d b/bin/analyzer.d index 1730dbac..5ad38188 100755 --- a/bin/analyzer.d +++ b/bin/analyzer.d @@ -1,62 +1,359 @@ #!/bin/bash -# This is used to start/stop the service +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") -BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." RETVAL=0 SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + start () { - rm -f "$BASEDIR/src/${SERVICE_NAME}/*.pyc" - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then - mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" start - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "started ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - else - echo "failed to start ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - fi - return $RETVAL + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL } stop () { - # TODO: write a real kill script - ps aux | grep "${SERVICE_NAME}-agent.py start" | grep -v grep | awk '{print $2 }' | xargs sudo kill -9 - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then -# Preserve logs - the mv operation does not change the file handle -# mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - cat "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" stop - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "stopped ${SERVICE_NAME}-agent" - else - echo "failed to stop ${SERVICE_NAME}-agent" - fi - if [ -f "/var/log/skyline/${SERVICE_NAME}.log.last" ]; then - cat "/var/log/skyline/${SERVICE_NAME}.log.last" > "/var/log/skyline/${SERVICE_NAME}.log" - LOG_DATE_STRING=$(date "+%Y-%m-%d %H:%M:%S") - echo "$LOG_DATE_STRING :: $$ :: ${SERVICE_NAME} stopped" >> "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid fi - return $RETVAL + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL } run () { echo "running ${SERVICE_NAME}" - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" run + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi } # See how we were called. @@ -68,15 +365,18 @@ case "$1" in stop ;; restart) + RESTART=1 stop start ;; run) run + ;; + status) + status ;; - *) - echo $"Usage: $0 {start|stop|run}" + echo $"Usage: $0 {start|stop|run|status}" exit 2 ;; esac diff --git a/bin/analyzer_dev.d b/bin/analyzer_dev.d new file mode 100755 index 00000000..5ad38188 --- /dev/null +++ b/bin/analyzer_dev.d @@ -0,0 +1,382 @@ +#!/bin/bash + +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") + +RETVAL=0 + +SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) + +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + +start () { + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL +} + +stop () { + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid + fi + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL +} + +run () { + echo "running ${SERVICE_NAME}" + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + RESTART=1 + stop + start + ;; + run) + run + ;; + status) + status + ;; + *) + echo $"Usage: $0 {start|stop|run|status}" + exit 2 + ;; +esac diff --git a/bin/boundary.d b/bin/boundary.d index 1730dbac..5ad38188 100755 --- a/bin/boundary.d +++ b/bin/boundary.d @@ -1,62 +1,359 @@ #!/bin/bash -# This is used to start/stop the service +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") -BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." RETVAL=0 SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + start () { - rm -f "$BASEDIR/src/${SERVICE_NAME}/*.pyc" - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then - mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" start - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "started ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - else - echo "failed to start ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - fi - return $RETVAL + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL } stop () { - # TODO: write a real kill script - ps aux | grep "${SERVICE_NAME}-agent.py start" | grep -v grep | awk '{print $2 }' | xargs sudo kill -9 - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then -# Preserve logs - the mv operation does not change the file handle -# mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - cat "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" stop - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "stopped ${SERVICE_NAME}-agent" - else - echo "failed to stop ${SERVICE_NAME}-agent" - fi - if [ -f "/var/log/skyline/${SERVICE_NAME}.log.last" ]; then - cat "/var/log/skyline/${SERVICE_NAME}.log.last" > "/var/log/skyline/${SERVICE_NAME}.log" - LOG_DATE_STRING=$(date "+%Y-%m-%d %H:%M:%S") - echo "$LOG_DATE_STRING :: $$ :: ${SERVICE_NAME} stopped" >> "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid fi - return $RETVAL + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL } run () { echo "running ${SERVICE_NAME}" - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" run + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi } # See how we were called. @@ -68,15 +365,18 @@ case "$1" in stop ;; restart) + RESTART=1 stop start ;; run) run + ;; + status) + status ;; - *) - echo $"Usage: $0 {start|stop|run}" + echo $"Usage: $0 {start|stop|run|status}" exit 2 ;; esac diff --git a/bin/crucible.d b/bin/crucible.d new file mode 100755 index 00000000..5ad38188 --- /dev/null +++ b/bin/crucible.d @@ -0,0 +1,382 @@ +#!/bin/bash + +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") + +RETVAL=0 + +SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) + +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + +start () { + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL +} + +stop () { + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid + fi + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL +} + +run () { + echo "running ${SERVICE_NAME}" + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + RESTART=1 + stop + start + ;; + run) + run + ;; + status) + status + ;; + *) + echo $"Usage: $0 {start|stop|run|status}" + exit 2 + ;; +esac diff --git a/bin/horizon.d b/bin/horizon.d index 1730dbac..5ad38188 100755 --- a/bin/horizon.d +++ b/bin/horizon.d @@ -1,62 +1,359 @@ #!/bin/bash -# This is used to start/stop the service +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") -BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." RETVAL=0 SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + start () { - rm -f "$BASEDIR/src/${SERVICE_NAME}/*.pyc" - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then - mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" start - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "started ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - else - echo "failed to start ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - fi - return $RETVAL + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL } stop () { - # TODO: write a real kill script - ps aux | grep "${SERVICE_NAME}-agent.py start" | grep -v grep | awk '{print $2 }' | xargs sudo kill -9 - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then -# Preserve logs - the mv operation does not change the file handle -# mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - cat "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" stop - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "stopped ${SERVICE_NAME}-agent" - else - echo "failed to stop ${SERVICE_NAME}-agent" - fi - if [ -f "/var/log/skyline/${SERVICE_NAME}.log.last" ]; then - cat "/var/log/skyline/${SERVICE_NAME}.log.last" > "/var/log/skyline/${SERVICE_NAME}.log" - LOG_DATE_STRING=$(date "+%Y-%m-%d %H:%M:%S") - echo "$LOG_DATE_STRING :: $$ :: ${SERVICE_NAME} stopped" >> "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid fi - return $RETVAL + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL } run () { echo "running ${SERVICE_NAME}" - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" run + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi } # See how we were called. @@ -68,15 +365,18 @@ case "$1" in stop ;; restart) + RESTART=1 stop start ;; run) run + ;; + status) + status ;; - *) - echo $"Usage: $0 {start|stop|run}" + echo $"Usage: $0 {start|stop|run|status}" exit 2 ;; esac diff --git a/bin/mirage.d b/bin/mirage.d index 1730dbac..5ad38188 100755 --- a/bin/mirage.d +++ b/bin/mirage.d @@ -1,62 +1,359 @@ #!/bin/bash -# This is used to start/stop the service +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") -BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." RETVAL=0 SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + start () { - rm -f "$BASEDIR/src/${SERVICE_NAME}/*.pyc" - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then - mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" start - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "started ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - else - echo "failed to start ${SERVICE_NAME}-agent" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - fi - return $RETVAL + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL } stop () { - # TODO: write a real kill script - ps aux | grep "${SERVICE_NAME}-agent.py start" | grep -v grep | awk '{print $2 }' | xargs sudo kill -9 - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then -# Preserve logs - the mv operation does not change the file handle -# mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - cat "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" stop - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "stopped ${SERVICE_NAME}-agent" - else - echo "failed to stop ${SERVICE_NAME}-agent" - fi - if [ -f "/var/log/skyline/${SERVICE_NAME}.log.last" ]; then - cat "/var/log/skyline/${SERVICE_NAME}.log.last" > "/var/log/skyline/${SERVICE_NAME}.log" - LOG_DATE_STRING=$(date "+%Y-%m-%d %H:%M:%S") - echo "$LOG_DATE_STRING :: $$ :: ${SERVICE_NAME} stopped" >> "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid fi - return $RETVAL + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL } run () { echo "running ${SERVICE_NAME}" - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}-agent.py" run + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi } # See how we were called. @@ -68,15 +365,18 @@ case "$1" in stop ;; restart) + RESTART=1 stop start ;; run) run + ;; + status) + status ;; - *) - echo $"Usage: $0 {start|stop|run}" + echo $"Usage: $0 {start|stop|run|status}" exit 2 ;; esac diff --git a/bin/panorama.d b/bin/panorama.d new file mode 100755 index 00000000..5ad38188 --- /dev/null +++ b/bin/panorama.d @@ -0,0 +1,382 @@ +#!/bin/bash + +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") + +RETVAL=0 + +SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) + +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + +start () { + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" start + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi + + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" + else + sleep .2 + NOW=$(date +%s) + fi + done + + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + fi + + return $RETVAL +} + +stop () { + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid + fi + done + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL +} + +run () { + echo "running ${SERVICE_NAME}" + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/agent.py" run + fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + RESTART=1 + stop + start + ;; + run) + run + ;; + status) + status + ;; + *) + echo $"Usage: $0 {start|stop|run|status}" + exit 2 + ;; +esac diff --git a/bin/skyline.d b/bin/skyline.d new file mode 100755 index 00000000..39bad734 --- /dev/null +++ b/bin/skyline.d @@ -0,0 +1,249 @@ +#!/bin/bash + +# This is used to stop and start any running skyline apps + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") + +RETVAL=0 + +SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) + +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +restart () { + + FAILURES=0 + STOP_APPS="" + STOPPED_APPS="" + find "$CURRENT_DIR/" -type f | grep -v "$SERVICE_NAME\.d" | grep "bin/*.*\.d$" > /tmp/skyline.d.apps.tmp + # Validate file has contents as a while loop will never complete on a blank + # input file + HAS_APPS=$(cat /tmp/skyline.d.apps.tmp | wc -l) + if [ $HAS_APPS -gt 0 ]; then + while read binfile + do + CHECK_APP=$(basename "$binfile" | cut -d'.' -f1) + $binfile status + if [ $? -eq 0 ]; then + _STOP_APPS="${STOP_APPS}${CHECK_APP} " + STOP_APPS="$_STOP_APPS" + fi + done < /tmp/skyline.d.apps.tmp + + for i_app in $STOP_APPS + do + CHECK_APP="$i_app" + if [ -f "/etc/init.d/$CHECK_APP" ]; then + /etc/init.d/$CHECK_APP stop + else + $CURRENT_DIR/$CHECK_APP.d stop + fi + STOP_EXITCODE=$? + if [ $STOP_EXITCODE -eq 0 ]; then + _STOPPED_APPS="${STOPPED_APPS}${CHECK_APP} " + STOPPED_APPS="$_STOPPED_APPS" + else + FAILURES=$((FAILURES+1)) + fi + done + fi + + if [ $FAILURES -eq 0 ]; then + echo "skyline apps stopped, removing .pyc files" + # Remove .pyc files + find "${BASEDIR}/skyline" -type f -name "*.pyc" | while read pyc_file + do + if [ -f "$pyc_file" ]; then + rm -f "$pyc_file" + fi + done + else + echo "failures were encountered, not removing .pyc files" + fi + + for i_app in $STOPPED_APPS + do + CHECK_APP="$i_app" + binfile=$(cat /tmp/skyline.d.apps.tmp | grep "${i_app}.d") + if [ -f "/etc/init.d/$CHECK_APP" ]; then + /etc/init.d/$CHECK_APP start + else + $binfile start + fi + START_EXITCODE=$? + if [ $START_EXITCODE -ne 0 ]; then + FAILURES=$((FAILURES+1)) + fi + done + + if [ $FAILURES -eq 0 ]; then + echo "skyline apps stopped and restarted" + RETVAL=0 + else + echo "skyline apps stopped and started with failures" + RETVAL=1 + fi + # cleanup + if [ -f /tmp/skyline.d.apps.tmp ]; then + rm -f /tmp/skyline.d.apps.tmp + fi + return $RETVAL + +} + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + find "$CURRENT_DIR/" -type f | grep -v "$SERVICE_NAME\.d" | grep "bin/*.*\.d$" > /tmp/skyline.d.apps.tmp + # Validate file has contents as a while loop will never complete on a blank + # input file + HAS_APPS=$(cat /tmp/skyline.d.apps.tmp | wc -l) + if [ $HAS_APPS -gt 0 ]; then + while read binfile + do + CHECK_APP=$(basename "$binfile" | cut -d'.' -f1) + $binfile status + done < /tmp/skyline.d.apps.tmp + fi + # cleanup + if [ -f /tmp/skyline.d.apps.tmp ]; then + rm -f /tmp/skyline.d.apps.tmp + fi + return $RETVAL + +} + +stop () { + + FAILURES=0 + STOPPED_APPS="" + find "$CURRENT_DIR/" -type f | grep -v "$SERVICE_NAME\.d" | grep "bin/*.*\.d$" > /tmp/skyline.d.apps.tmp + # Validate file has contents as a while loop will never complete on a blank + # input file + HAS_APPS=$(cat /tmp/skyline.d.apps.tmp | wc -l) + if [ $HAS_APPS -gt 0 ]; then + while read binfile + do + CHECK_APP=$(basename "$binfile" | cut -d'.' -f1) + $binfile status + if [ $? -eq 0 ]; then + if [ -f "/etc/init.d/$CHECK_APP" ]; then + /etc/init.d/$CHECK_APP stop + else + $binfile stop + fi + STOP_EXITCODE=$? + if [ $STOP_EXITCODE -eq 0 ]; then + _STOPPED_APPS="${STOPPED_APPS}${CHECK_APP} " + STOPPED_APPS="$_STOPPED_APPS" + else + FAILURES=$((FAILURES+1)) + fi + fi + done < /tmp/skyline.d.apps.tmp + fi + + if [ $FAILURES -eq 0 ]; then + echo "skyline apps stopped, removing .pyc files" + # Remove .pyc files + find "${BASEDIR}/skyline" -type f -name "*.pyc" | while read pyc_file + do + if [ -f "$pyc_file" ]; then + rm -f "$pyc_file" + fi + done + else + echo "failures were encountered, not removing .pyc files" + fi + + if [ $FAILURES -eq 0 ]; then + echo "skyline apps stopped" + RETVAL=0 + else + echo "skyline apps stopped with failures" + RETVAL=1 + fi + # cleanup + if [ -f /tmp/skyline.d.apps.tmp ]; then + rm -f /tmp/skyline.d.apps.tmp + fi + return $RETVAL + +} + +# See how we were called. +case "$1" in + stop) + stop + ;; + restart) + restart + ;; + status) + status + ;; + *) + echo $"Usage: $0 {restart|stop|status}" + exit 2 + ;; +esac diff --git a/bin/webapp.d b/bin/webapp.d index 3267b97e..6b1ac05a 100755 --- a/bin/webapp.d +++ b/bin/webapp.d @@ -1,81 +1,393 @@ #!/bin/bash -# -# This is used to start/stop webapp -BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +# This is used to start the python daemon_runner and issue kill to the parent +# process. Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. + +CURRENT_DIR=$(dirname "${BASH_SOURCE[0]}") +#BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." +BASEDIR=$(dirname "$CURRENT_DIR") + RETVAL=0 SERVICE_NAME=$(basename "$0" | cut -d'.' -f1) +PID=$$ + +# Python virtualenv support +# A skyline.conf can be used for passing any additional variables to this script +# This was specifically added to allow for the operator to run in a virtualenv +# environment with whichever version of python they choose to run. Some simple +# sanity checks are made if a virtualenv is used +if [ -f /etc/skyline/skyline.conf ]; then + # Test the config file has sensible variables + bash -n /etc/skyline/skyline.conf + if [ $? -eq 0 ]; then + . /etc/skyline/skyline.conf + else + echo "error: There is a syntax error in /etc/skyline/skyline.conf, try bash -n /etc/skyline/skyline.conf" + exit 1 + fi +fi +USE_VIRTUALENV=0 +if [ "$PYTHON_VIRTUALENV" == "true" ]; then + if [ ! -f "$USE_PYTHON" ]; then + echo "error: The python binary specified does not exists as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +# Test the python binary + $USE_PYTHON --version > /dev/null 2>&1 + if [ $? -eq 0 ]; then + USE_VIRTUALENV=1 + else + echo "error: The python binary specified does not execute as expected as specified as USE_PYTHON in /etc/skyline/skyline.conf" + exit 1 + fi +fi + +# Determine LOG and PID PATHs from the settings.py +# Determine LOG and PID PATHs from the settings.py +if [ ! -f "$BASEDIR/skyline/settings.py" ]; then + echo "error: The Skyline settings.py was not found at $BASEDIR/skyline/settings.py" + exit 1 +fi +LOG_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^LOG_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$LOG_PATH" ]; then + echo "error: The LOG_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +PID_PATH=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^PID_PATH = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$PID_PATH" ]; then + echo "error: The PID_PATH directory in $BASEDIR/skyline/settings.py does not exist" + exit 1 +fi +SKYLINE_TMP_DIR=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^SKYLINE_TMP_DIR = " | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ ! -d "$SKYLINE_TMP_DIR" ]; then + echo "notice: The SKYLINE_TMP_DIR directory in $BASEDIR/skyline/settings.py does not exist, creating" + mkdir -p "$SKYLINE_TMP_DIR" +fi + +WEBAPP_SERVER=$(cat "$BASEDIR/skyline/settings.py" | grep -v "^#" | grep "^WEBAPP_SERVER = '" | sed -e "s/.*= //;s/'//g" | sed -e 's/"//g') +if [ "$WEBAPP_SERVER" == "" ]; then + echo "notice: The WEBAPP_SERVER could not be determined from $BASEDIR/skyline/settings.py, please specify gunicorn or flask" + exit 1 +fi +if [ "$WEBAPP_SERVER" == "gunicorn" ]; then + WEBAPP_SERVICE_STRING="gunicorn.py webapp" +fi +if [ "$WEBAPP_SERVER" == "flask" ]; then + WEBAPP_SERVICE_STRING="webapp.py start" +fi + +# Check if it is running and if so its state +RESTART=0 +RUNNING=0 +VALID_PIDFILE=0 +VALID_PID=0 +PROCESS_STALLED=0 +if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING=1 + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + # Is the RUNNING_PID a valid number? + # shellcheck disable=SC2065 + test "$RUNNING_PID" -gt 1 > /dev/null 2>&1 + if [ $? -eq 0 ]; then + VALID_PIDFILE=1 + fi + if [ $VALID_PIDFILE -eq 1 ]; then + if [ -f "/proc/$RUNNING_PID/status" ]; then + RUNNING=1 + VALID_PID=1 + else + PROCESS_STALLED=1 + fi + fi +fi + +status () { + +# As per http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html +# 0 program is running or service is OK +# 1 program is dead and /var/run pid file exists +# 2 program is dead and /var/lock lock file exists +# 3 program is not running +# 4 program or service status is unknown + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 1 ]]; then + echo "${SERVICE_NAME} is running with pid $RUNNING_PID" + return 0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead and pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 3 + fi + +} + start () { - rm -f "$BASEDIR/src/${SERVICE_NAME}/*.pyc" - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then - mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}.py" start - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "started webapp" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - else - echo "failed to start webapp" - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" + + if [ $RESTART -eq 1 ]; then + # These a reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + fi + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [[ $RUNNING -eq 1 && $VALID_PID -eq 1 ]]; then + echo "${SERVICE_NAME} is already running with pid $RUNNING_PID" + return 0 + fi + + rm -f "$BASEDIR/skyline/${SERVICE_NAME}/*.pyc" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.last" + fi + + touch "$LOG_PATH/${SERVICE_NAME}.log.lock" + touch "$LOG_PATH/${SERVICE_NAME}.log.wait" + + if [ -f "$BASEDIR/skyline/settings.pyc" ]; then + rm -f "$BASEDIR/skyline/settings.pyc" + fi + + if [ $USE_VIRTUALENV -eq 0 ]; then + if [ "$WEBAPP_SERVER" == "flask" ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/webapp.py" start + fi + if [ "$WEBAPP_SERVER" == "gunicorn" ]; then + cd "$BASEDIR/skyline/${SERVICE_NAME}" + gunicorn --config "$BASEDIR/skyline/${SERVICE_NAME}/gunicorn.py" webapp:app & + fi + else + if [ "$WEBAPP_SERVER" == "flask" ]; then + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/webapp.py" start + fi + if [ "$WEBAPP_SERVER" == "gunicorn" ]; then + cd "$BASEDIR/skyline/${SERVICE_NAME}" + PYTHON_VIRTUALENV_BIN_DIR=$(dirname $USE_PYTHON) + source "$PYTHON_VIRTUALENV_BIN_DIR/activate" + gunicorn --config "$BASEDIR/skyline/${SERVICE_NAME}/gunicorn.py" webapp:app & + fi + fi + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + if [ "$WEBAPP_SERVER" == "flask" ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" fi - return $RETVAL -} + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + fi + echo "error - failed to start ${SERVICE_NAME}" + return $RETVAL + fi -stop () { - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then -# @modified 20151014 - Feature #20451: skyline mirage -# Preserve logs - the mv operation does not change the file handle -# mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - cat "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.last" - fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}.py stop" - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "stopped webapp" + if [ "$WEBAPP_SERVER" == "flask" ]; then + PROCESS_WAITING=0 + NOW=$(date +%s) + WAIT_FOR=$((NOW+5)) + while [ $NOW -lt $WAIT_FOR ]; + do + if [ ! -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + NOW=$((WAIT_FOR+1)) + PROCESS_WAITING=1 + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: process removed log.wait file, starting log management" >> "$LOG_PATH/${SERVICE_NAME}.log" else - echo "failed to stop webapp" + sleep .2 + NOW=$(date +%s) + fi + done + fi + + sleep .5 + if [ -f "$PID_PATH/${SERVICE_NAME}.pid" ]; then + RUNNING_PID=$(cat "$PID_PATH/${SERVICE_NAME}.pid" | head -n 1) + else + RUNNING_PID="unknown" + fi + + if [ "$WEBAPP_SERVER" == "flask" ]; then + if [ $PROCESS_WAITING -eq 0 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.wait" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.wait" + fi + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" + fi + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - log management failed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "${SERVICE_NAME} started with pid $RUNNING_PID, but log management failed" + fi + + if [ $PROCESS_WAITING -eq 1 ]; then + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.last" ]; then + cat "$LOG_PATH/${SERVICE_NAME}.log.last" "$LOG_PATH/${SERVICE_NAME}.log" > "$LOG_PATH/${SERVICE_NAME}.log.new" + cat "$LOG_PATH/${SERVICE_NAME}.log.new" > "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.last" + rm -f "$LOG_PATH/${SERVICE_NAME}.log.new" fi - if [ -f "/var/log/skyline/${SERVICE_NAME}.log.last" ]; then - cat "/var/log/skyline/${SERVICE_NAME}.log.last" > "/var/log/skyline/${SERVICE_NAME}.log" - LOG_DATE_STRING=$(date "+%Y-%m-%d %H:%M:%S") - echo "$LOG_DATE_STRING :: $$ :: ${SERVICE_NAME} stopped" >> "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: log management done" >> "$LOG_PATH/${SERVICE_NAME}.log" + if [ -f "$LOG_PATH/${SERVICE_NAME}.log.lock" ]; then + rm -f "$LOG_PATH/${SERVICE_NAME}.log.lock" fi - return $RETVAL + fi + fi + echo "${SERVICE_NAME} started with pid $RUNNING_PID" + + return $RETVAL } -restart () { - rm -f $BASEDIR/src/webapp/*.pyc - if [ -f "/var/log/skyline/${SERVICE_NAME}.log" ]; then -# mv "/var/log/skyline/${SERVICE_NAME}.log" "/var/log/skyline/${SERVICE_NAME}.log.last" - cat "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.last" +stop () { + + if [ $PROCESS_STALLED -eq 1 ]; then + echo "${SERVICE_NAME} is dead but pid file exists - $PID_PATH/${SERVICE_NAME}.pid" + return 1 fi - /usr/bin/env python "$BASEDIR/src/${SERVICE_NAME}/${SERVICE_NAME}.py" restart - RETVAL=$? - if [[ $RETVAL -eq 0 ]]; then - echo "restarted webapp" - else - echo "failed to restart webapp" + + if [[ $RUNNING -eq 1 && $VALID_PIDFILE -eq 0 ]]; then + echo "error: A pid file exists for ${SERVICE_NAME} with no valid pid $PID_PATH/${SERVICE_NAME}.pid" + return 1 + fi + + if [ $RUNNING -eq 0 ]; then + echo "${SERVICE_NAME} is not running" + return 0 + fi + +# Originally a stop call was made to the python daemon_runner which +# initiated a new instance of the agent and the agent's main module, this often +# resulted in the daemon_runner overwritting to the Skyline app log file due to +# complexities in the python logging context relating to log handlers and the +# use of multiprocessing. These stop calls are no longer made directly to the +# agent and they are made directly to the parent pid. In terms of the +# python-daemon this is exactly what initiating a new agent does, the +# python-daemon simply issues a os.kill pid, however initiating a new instance +# of the application results in a new daemon_runner that cannot preserve the +# log file object of the running daemon_runner in terms of: +# daemon_context.files_preserve = [handler.stream] +# which results in the log file being overwritten. +# if [ $USE_VIRTUALENV -eq 0 ]; then +# /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/webapp.py" stop +# else +# $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/webapp.py" stop +# fi +# RETVAL=$? +# if [[ $RETVAL -eq 0 ]]; then +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopped ${SERVICE_NAME}-agent" +# else +# echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop ${SERVICE_NAME}-agent" +# fi + + SERVICE_PID=$RUNNING_PID + SERVICE_RELATED_PID=$(ps aux | grep "$WEBAPP_SERVICE_STRING" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$SERVICE_PID") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: stopping process $SERVICE_PID" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $SERVICE_PID + fi + + PROCESS_COUNT=$(ps aux | grep "$WEBAPP_SERVICE_STRING" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + sleep 1 + fi + + # TODO: write a real kill script + PROCESS_COUNT=$(ps aux | grep "$WEBAPP_SERVICE_STRING" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -15 + ps aux | grep "webapp.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "$WEBAPP_SERVICE_STRING" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: cleaning up process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill $i_pid fi - cat "/var/log/skyline/${SERVICE_NAME}.log.last" "/var/log/skyline/${SERVICE_NAME}.log" > "/var/log/skyline/${SERVICE_NAME}.log.new" - cat "/var/log/skyline/${SERVICE_NAME}.log.new" > "/var/log/skyline/${SERVICE_NAME}.log" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.last" - rm -f "/var/log/skyline/${SERVICE_NAME}.log.new" - return $RETVAL + done + + PROCESS_COUNT=$(ps aux | grep "$WEBAPP_SERVICE_STRING" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [ $PROCESS_COUNT -gt 0 ]; then + # kill -9 + ps aux | grep "webapp.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | while read i_pid + do + SERVICE_RELATED_PID=$(ps aux | grep "$WEBAPP_SERVICE_STRING" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | grep -c "$i_pid") + if [ $SERVICE_RELATED_PID -eq 1 ]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: kill -9 process $i_pid" >> "$LOG_PATH/${SERVICE_NAME}.log" + sudo kill -9 $i_pid + fi + done + fi + fi + + PROCESS_COUNT=$(ps aux | grep "agent.py start" | grep "$SERVICE_NAME" | grep -v grep | awk '{print $2 }' | wc -l) + if [[ ! -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: all ${SERVICE_NAME} processes have been stopped - OK" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=0 + return $RETVAL + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -eq 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - stopped all ${SERVICE_NAME} processes, but a pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + rm -f "$PID_PATH/${SERVICE_NAME}.pid" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: pid file removed" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME has been stopped" + RETVAL=1 + fi + if [[ -f "$PID_PATH/${SERVICE_NAME}.pid" && $PROCESS_COUNT -gt 0 ]]; then + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - failed to stop all ${SERVICE_NAME} processes and pid file remains" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$(date +"%Y-%m-%d %H:%M:%S") :: $PID :: ${SERVICE_NAME}.d :: error - there maybe zombies or multiple instances running" >> "$LOG_PATH/${SERVICE_NAME}.log" + echo "$SERVICE_NAME.d falied to stop all $SERVICE_NAME processes, there maybe zombies or multiple instances running" + RETVAL=1 + fi + + # These are reset for the restart context + RUNNING=0 + VALID_PIDFILE=0 + VALID_PID=0 + PROCESS_STALLED=0 + + return $RETVAL } run () { - echo "running webapp" - /usr/bin/env python $BASEDIR/src/webapp/webapp.py run + echo "running ${SERVICE_NAME}" + if [ $USE_VIRTUALENV -eq 0 ]; then + /usr/bin/env python "$BASEDIR/skyline/${SERVICE_NAME}/webapp.py" run + else + $USE_PYTHON "$BASEDIR/skyline/${SERVICE_NAME}/webapp.py" run + fi } # See how we were called. @@ -87,14 +399,18 @@ case "$1" in stop ;; restart) - restart - ;; + RESTART=1 + stop + start + ;; run) run + ;; + status) + status ;; - *) - echo $"Usage: $0 {start|stop|restart|run}" + echo $"Usage: $0 {start|stop|run|status}" exit 2 ;; esac diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..6c91dbc3 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/skyline.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/skyline.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/skyline" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/skyline" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo new file mode 100644 index 00000000..8a930d92 --- /dev/null +++ b/docs/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 9efc9a25c653c578757afe76df6dd87f +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_images/mirage-1.png b/docs/_build/html/_images/mirage-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d176cccd9fb9797f59752ea50ef7224156246b7f GIT binary patch literal 87377 zcmZ_02Rzqp`#(%c3fUtnvow(s$qFGOgiuC^Xi3OUWv`5~DrA){QAW0kA}g!xJ+rs) zyg&DE{GZn|uGe+H?kn;Ae!k~yVc9W2hP+wG(yG%ktcAbQTG?;=M zU+GI^X2JiF*~nbfqQE~c6gPbE=Uw*|^=wE;s8osnkv7$3Tp}UiB)KSeUd!S2WS67< z&))-5Gi~bzdH3>E)0CXUDH>^y9Q}33xs}#aPWFq>`M2DrUO{_rm=7{s4siWh^38%v z*?gt_<&kSwC&LO0qkA|WEpPnoDsK51J|xZ&wB8%z+&$u2=O~c3TyWEvf{Tptg2w;; z+w5F<<{|lEzxer;O~JcfrlgT5<{sA`j1uf4o_L;NQRb`f2el*Y%}1 z{7+p|(>*klK1RxoU&PSX_Vl!_j@wyL(dN$__V)Gx`_C;I+$=p$&%lsNwkUexU4U9CTU>^^z&Was+msDEKg;)Vw!IrOtRkn6Xl@y~Oq0OX1sIetx?<3v70-FHd{prTYg4 zLPA5gX=}H9t258QBY8$fvdYQJKQ)u;emK+h*?wSUc7TG0#_gu15-)uKoA7Rae*Tre zc|Dx86mL>e^lrO&8Q));)QfVutfS+0)6prmHOq*`SMKSXH=KqJv68OAF)^Fx=jZyr zQi_U+$>{0rrK6*>Xh~%{AoVDqCPKh;;9H=NukRCS&XqTgp+A29yny8_w4dIitXw1i zy|N?qkx{53Z}s=@=T%gw#KpyDmV4Y}Wn~l2j}_P0@QRAEZYQU5;Iq_du1fqgt!Zj% zdgI28H>$7B1V)}idFT;Uk*pS4Tr4ro(iOs^U{W2-#VYO~$j#0D-eq-8zBbxrXlRIb zwx81yX)AN(Ht?j>L_S9~Vn3w|#3k#B?l6m*K{65=H zbDulMez)V(P{qzY``Sk(K2UI7XML-&WT36R!&h$Z5?gF)PsHojudm;^LnR;}keHNI zUQ?5H-V#s!<;xecLx&Dc^Pbq9oSeLp%)VO6`_VNvH95Jgu`&DP2RRK!?RWJP0$2Uw zODwkU-1Frz>Gt>LKiOVHMA-BR7?pfU*UPzn_b#=zwl?6FVtr zGpxeO&W=^x-q_gK*+B9{nk3w;z?i)0?xU>@``VYnl=AZO4NXlIK6E_lSFif9i`*-# zsiA!N@}_inby*WHHu#vD8=XBj{nc?zUGyzglQbk2YX`-nx?zWyAyvWMNX7Vjyuh44v zo*h5Fe^1HlzSY#+OvXiKk{wC;+`feLgY58NG|ybRegXZJD_79SWZN%Try} zgVl7lqYb3vr7_ds+-p(mrx7s=&PPw3Vz_ka64Bc~ zeiVMRv8coUjJxNZus7!kqmq3Y*Ydp756k74|DU zV!|K&lIPelO0>veMc$dYxr)V!wv%p~F1dF*nD_48i$+(D1Nr{Oclz$7UvE|2y}fsy zvFZ-Mf#~}@T~b-;O=G<Sp?*xQ zl>KAckD)WOysXeu;+lQuw-E9A*g2H&@|!Zj0GZ-J&)F@c@&<>X=^iK`kkkE z5*{AjSwt(-@J@|GzsP}(lKJ@K=;-}R(>+0!zuxv?afBqa-m1RxDsf#mnP|<(w;muj zuY0{~`}Xa}b>F{feQ;Jx>?!TjzDjS?y4Pp#KYWNfdBKi3wdIKcd*{v_jLP7sDBs{< z+81GA#{X7ki}JsJ|Nj2t$FHf{X`IadvuMKHoScuBd*|_ACci(JQS91P{&WXrnIM&r zkkI{+y8SC_YbGtJ+L7XpLjC>y4Jn#@B+2%p(mze3oI^k^2!&3jetCr_ zC9xKslq_@oF{0Mn+_pAEOiWF2JgLu}JBPMQl*&qNAK|$WALOkVoEzOLR zPxM0A^XKkoZHCJ*rO+G~?;Xo>S&YuY=dNr{(LDKZQkQDK7-ODQ&$su+l`|OYo=sPa z98SKDjt+^8^r@(jQHhn@Z)|Lg^6T%X+9yg&blxDTO$v5)_R03_?f9#rh0TBSYGh<& z*TFx1EU0>RvwS|!{ZDe9&I>Pd^nCG3Rz!8-R-Irynzk! zpp`jur(Ie%{Gi+BDxZMiojb=hw6zajxNre~188@F)r$*ft;hibocDN5C>qO;E+ZU+ zl=O7ofvO-rfQ`Wl8yg$E-j~Kk4f9vY$%pYiA&R_3_s7l{8X6Ye2SCPGe&<>;uq>_2 zn#QN6tKegoc`i==e%}83cL-*&;pWx6d#c#PGS404v18BcqR&0wLCGA9iPLti1m`UD+5U5L6??t7 z(3yLqZSWEJ@W?x94$#U_V2`+f|9QE&<69S!hQuEI%dfpTSI08;@$$yL@Q9JbMBezc zv>M91++1EZHnzn4{79C~dZ{g1fRGQ?1M>WnKf;cA7_)w`8L7LvEL%M>aAaCvSXlTV z3%`Ev*9^y!ivBQWiA=cZ=eJ~ zv%HC_CtlHY{h?veqm4B>><|V{UUW>1;a{eOJ?-u7v}$TeoW zYiVgY^w5FF$X&eXYfp1m(FvbyaJqDB_^WpAJse`6z(DFcjD61OB10vF{yju08LXoWG!Eebo;Wr91Lf`O>9B zRF%OS>&rIAe*YQF-HiL6%4sy;^!&HgN8V&qvWG*GijtDj_V1st<%!&P@A$r$oJ4zd z=%CpvBsVNkDOhAgB4AkLv$?TeiROnoqNN}Mx+=pEH=6?*DJ74H8`h`$^y!mOpO~1K z;OWzYg#{525d#ZjzX(!6vMDfo;J^VEH0wY9VwKA0(Y7CYyaukLt! z>Si5UnU=X>ghqmV-23;mTkAbr-G>dpqNX~}-Y_$>&O#B}$X+0N;^sfc%+AXOg|?0Z z-}QqINbFWnQ0NvvBCLJoipgyM*Y`O&?p|KxWZOuOpEyx27|=QYM)GFe>nDIpV2R%- zIfQR99zA;WhF2KI$6R;b?Y2vCrXp6|;spQOwQCovgwts}qsQv}NP@xVhllz3hx>;H z2d_6LYXCTVOLIP!=9Ip1BeC;?qod=j(_x^AJs!0DQdQ6PZ|9PGJ5u}kin{vah6Z(A zJ-zc6F6`L$gapXq+pl+O7(mtj9UpJ&M6GJ$g!caZ8`zSjV`H4AljW72oh{RL5_}XW z(*D`h@0^{$&QF4ZXzq39&)(au_p{h}`Tp{h0gCv|dG|Nz>AE{_I*x?s9v>b*25^jR z5n@ws@#q}3*+$qA{e>?JKaf+^g@u4ePk%^-P zUJ_c)1MHviQek6dEo*29KF7!YGA1mHX>xMX!Pu{rx>Assy2C$TXLm|o;9%D^9ui5D4EA_Ym^{>|>3>M1q4 zxyi^iy{=+lupjTMuZz}9`dB29ngj!e7zdQ>k}jfXOEbOY?&L!}fSo4UtUU4o!57tV z8s27(f6ju|QGDbNi7d)@2>s?dEBUu?-$)DvKEHbPszyRi$p}S3S}EwTFiK~&{&L=w zf@~X@-F-Ko?ou}n)F6nZw7&j69HHQ_Fwbn$svptrmDSbBSHE*^@|-!tjG-;7q_n#(Rw}5rR)tokJ9!U>gcHGT zqITW{yP=i2F|MYg6R~WFhL3-C|E&RALN6zH4CXuF4FP!mE&Ng=d5rN3Ali#fSs8m5 zdw;lgFHVT@RA<41Wi9~$l6~>wO!zg_HO17JC7w zxxrl&Tpzog*{=$Kz>7N0@dgA2p0w_li;RjQgr|0#sp!>o|Dx#=V}d+K#>m=*vwjth z=fwreHWeo8=`>AT2ysp?62DAnKGEADe$@X!KjUKDU68%xF9t zLeO>1mVYwVZPU_X=`5ba;or*1qsNXpjSe1n z+P7I*G`)-4pWBvjJKp+yWl)&EMcn-C*|XI@epEo_>g(^X@H8s4O*y|lx47t8P;mB2 zmLWky6A}{#Gn}R-hXttNfp8{rS!aMP93xUOgKQwHWd9s6?wr z)MJi%(Xp`{V3g{HhR<*`OowZuMqAQYKqmM^{sKUO`k?o4-) z|H5Fj0CftxgoFguULi7~#es)jdM(OzDmzzvvCH4q))u6t(^b-CWhQWn9jyrs^qe$B zIr?t){XQ82Pyhb?+v3;Tz>4KY`#um?u*V{K*0I&@266+uF0ITA*1CK7>4MX0Lt2vt zbZv7$b&J+G5FevI^D4}(lY#x)U=EWTHm z8p9js`J)*%G@6W>3`<;A<%eD<@Y2VDJ`(Z`o@yxkV;nF9Q(Zz@+A-o|pVBhNVX^p= zdt{oX+uqkq&vvSFF96Pn=S_2SZV{0P7IS0cCqVt(?iE-u$I6<=Z3gxA(dTqVvX;%v z%!FtxXJ==HXm0tn#^EJ->35;}zESq%Tl0t7j^zVv* zk)z{TE0b2QnVA`qNDXZ zMHLk}G`ynXV&i-Fy5F2mJ$CGvEZT|H2TRQS&O$r?JinhfXKdms-V<2^ypLFU;+#mD-JIo#_Vs(Ik&{B2K7zFV%0jwvt zF&t8ne)LF^nP1-r{P*a;2A;nUOKit3FO0WP0{T6OIC3}HtR}p?rG*LPfK}w)K6EP$ zef081Wg-&-ufQt!vlmSy_FUj$J9MucyKH5lk)g`E%=kE7rWc zyhSY>{SZoox*Eb>yvT2P07bkH-~la+hnJTO6WPbluiuG65jqtm;4&))DX6&b&H5PQ z@1X}ff5=^i=+q8&LVODQDg9hP7Vw|;&-Man4Ac-HiDy?1KmG*3w)W>w>)tZbNlff} z`!|<5tlA6hj)4j1AN+EAZg%zw3ii;Y*951%$CT*dK?V(Pu5h;0a{4Vb`x#UoDWM`j zM|t!1trsZObr5%TZS4yd7Kb0V(S$d(=s{(k`L}9w{Mx7V@?xq)h$nOh4m`t(^`Y2D z+p;(S2ng6mh?=rqROi7q;?mOtfU{&26m|ju5<4_iOuqYZHlZEXseI>04Rumg{1zIAC>V9a1oLFbRh4~W{0GlEoSHY_Gq7hCjIc+M;? zUYPDKCbTOS0YlECM|VSfEjr-jN9Y7#^C$7rK%a4miDbvFq(2T1XKtfNA9ZDPn0F3) z0c?yNP}kD>kKPNtP1L%d46aOpsNbX5@zK%i=lY6&SziXH z?RfJMf+Bys&9ZCvZf+0-Ib~&kI46GMP76K}-GFl!AsG0+e92~s?OlcBqiAcJTYHoT zq!CpX^$#ar$Z_sQQ?8|Ed}8944BN{cXRx(kT^G&6cA~KfIW1Tiwgb@kLSV8)bEezv z`K7itU}3r^cAun6q(4E3QQc;9+BQt#08v6FUnoE`m370zKF?jS!)Eac3I?;XveL$> zD~j2UN)sK(@HIic!_AlscciRkXKq3_9{kz`ts?v5$AdC)!4NUSC%cPZL8Y!qXSRju z(?2?@2od!nI%73@$VGMa;M!Y)IBz(~9+8oewCH0d_wHSK`t+%f?(st&MTQzY(78q@ zCK#Z75K9Rt$D{eln}#D4YCZ$RVys*qhvTtMoQ&Z2?**IE^-jg7rYc?icSF74a5k6@`-ATGEnH&A4Q=#54B@6=)sp*0@j4)M{|)eRIRwhX2fja;p(yL~Z{qDCI6nB?b=UOX z^25l(zT!nfAxga5C*h=ZXcOwCBJ@umMPBaxVz!(LK?lOl8V&8&bZo2hV>;%8qphK- z$wNFm8rwmrqKlbNd8Z9|BuZ1;bgZpU63+pD0rsRf898|4ik2Y6tzCZ5o%fNT$VYuXOJetGO z4r%-JAAWsHHn!<@p&2A)TC0>16 zHbWPxMAc1D4vO8opJKBy{t8-NmDs`!Ss98jngLsZjGb`7pLu1m%?KU2vt94DtzYv` z0QBC`Q3`M=LN@R2?hcF`P@~+q^!D($jhr{<|1rqH9&g(2z4d=H$di`?&Hy@GaJ>P$ z22P>*&zCLjdaH?pLqku@^j=H4uDMU;!GePW<_>_5-{6c!`~Q}01T1(Dh8T~C7lGA_Tx4QGGN2mUCb#M1i8wF6#8v*tfx zGp&0J3G?+?8**@PStlnkFB%Shk?sPUkzUv)IClhU5PdL6ff2j=+O=zFZ|lWFS?1CB z4|Wuko7H-n+U3jis2R*VZxmbc^2SLj*^y8OWgI|g+@ZUA3T*h$S5tbhhMW=-8?t88 z#m>KMM(2iVe4ah~hF9s4%^YXmw=V!1i|fn=sF{Y{f29*RLhz+N7%+q!4>e%>zOxS! zvvU?MS_Y7(VZAE5yAKc>0_aFZO+EDOTET;ZU=#P@5fP+8!fBxlp61L%R_UV#Gt0k! z{~DW^%#| z-o1ObQ$e9h>=*PJSR*ax>L;o^Xqzko-OyEGw_3e$)g%%H|MTQ%I_-b{n7~C9Jab0g z#3WwjHI^DD1tNR}>^`8g_u1LySOum(3g}rtQWTn+njmBZ+5sr+ljQR#>+0Hn=FFMH zM~?JWB8*!LOUqd=_hu~{ip#Qf?>am637H=#3(R;F)Zg7{n)lG5_>0dEMT%N; z@7uqBD5j^0OBSvqp)cj;=3Yl1bDSTZJ+NR@@ zJF_2*Fwb7I2nY(&f(+$5%yL80ip$J=2FLn3PK%HwKp+$0dIG3S{{5Q`LL@ekdhhG>Yy5Q5B-D4d0megF_)d3Ztcy8nw-4ddK@E4 z7W}rgwN*FQf=;Idsy?>#Ix-ZUXTZVn12|N@NoIHM1fWT5BLQLM=HY3?0ZL9uDK9G{ z!Cy<`LO&dB;6fIp7c+w30-!Pg|nFi{(i+uZWk*VoYpVwS5`OSd*D z-Q3&=1q%!NHJD45mY$y5zQhM60ui_H2@0wL+(zQ&+2h9~#QR~@p%}5?2?!ckFvJQ) zn6Pj`a|xRc)EtiDgXp1+jpd$`aAOId`rN}wX%`pq&CPWy@XlGbh(X_g06BQSE-o$v z&-C%3NWYf12d$WNW{s2=k}Uj0cVqyBPMyLj=^GvO+1i+PqoAh#iq6Kgxwu6*q5l4q zpa4WtDDve?lcvOrAg`g8|ARbkl zvv{4(|0}`L$oK4{cq09n@YGJ7(i}d2?PV-J;TyY&*7jm&5fG#IaKXEaM4{(Po0%Pi zopgVu_k6lxF&ji`h_ZP1RAG+ihg9*ZyYY`hAC8DTs3A7j!G{tgI{*35ox<%dcPOT$<{l z+_`fnj(@1myQ&}#B5?&M>j`K_7?ntwR0gt(-1_M*4u8F0xD0||Wo<20W@e_4^=`y)tU(;!v#K-|aYqiijh$R@lJNj#S7O0Rr9dnub|b4i&f9X))B93TFm_==+C% zuX=cU4Jre9B(6G z7BCDjdbGL&q^%OUf%h;xiA+PF&;l^cc!C1vesWL+Y-L5bog()-IX!5hOJ0I2NKBTm z{(gSkK;38;h7N!P-#vCQjCU#^K}O(esE0)}0LCWQuS?@uSw7uv;|J8$L!JWW)ghej zLCBOTr#9eaet>jT&t^Y~oIcG9{bZm5u{SlmeOz2z z(P_lce1T{RT-VRQ(nS9!EG$*kgD@m7UzS1s=Q~iRW5>|&Fg<3{T#Z0!^VMf44~97P z!#Q^!JP?A@2vs7M(#G1F3nsi>DnIyY3bpc_8aUFtCV6g3*s-mc4^4qFqG{{yld1(p#1+`&RZ5%zgt z>}ROlG$$t~*Zgw1Q0vN-P^6-;Sl{8lS0G=ptL8)J>Lm&;tdg9XT9Cc!*>mR@(BTbV zLL`ERNzcfrHByom>yJIfA0I$Vk*S`T=MfSLrJY+_3*R)lIy`R2A82ZBE^k=uq&*^e z``j})Eu0v}iA6=PDQoKM`9PKIH-rZd=={RS@Wn(Zz+7Xwa`mbb!bHAdVcHoT;IV|W z-H)}YsH`lwZz<7St9-WunoA5K4)5#o?Wa1qP;e_N4*ZjFy1tZ^Jw_xWkP?O1Kja9I z!&3NaT(D))eowzb9Q{mFXND%9b%p^p_0zFsdttY~Ox zx&=PK_=FJ!rB6Q+79*NP-L;Q99X4)HVG=nQBfR7-$9>-1{B7rlx9{E!p!u_&G$G4M z2SIYr$T)#)jOJ|#Gjns9SW*s-W)>;->S`aR$i4h|+E20(D7ik>yY#xR!_zt5Tc>-5I-SLGGI5VE`>2?YV-2TK#DtGwl zEqYRyaT;`yiUCc06BkE{KqaIjK`I_i{t(44>}+sZp2DDiu!wfz3A?w3N^h(wi4vfR z=||8DgwOD9UKr{`8VNCw3)0fk?oBgGOX26p)ItU#LqbT6jEqnm4~!AaB$8|b8D56cFZfwBhxY;|AB)IWz^@+B-J33)xO!?DGglq4C(oIJX$!l?n3-L|bRr!^6WV zFJeh7^-0xWKt+~=qU_6$&OI|*SGARG$^nYZcJFT(Hg9Dj`Ntt`h5?;>7u8@4$A z;a@f)o&!EYXrc~l3rC1QB4IN)I9PoLHbjkc7U4w>#z6cfLN;x>+^*ChKO{4wjXZ|` zfISo7S-~9@N?U&s9**O=9TxW&HK`2`c%TstW9J&nrUljlmn@p&}LFe7CvYMBw5}Kgs3k|%a&R)p8~5N(>OS#2KY&k zO7mtkBO_xkk{D1paZ&{GZHfRX^6jS&SXH~nuR`i8$K)(<^oCgC4eSfBU(!9niDWC< zx~T0aEeRn!{D-S^(ZK2R)dVTgh~*ihwvW77lDA@%KSoJ7pCKYe_c}%4WcL1B zov*H~C4+abFPs(qa#0IXJ?iVb{<+7Ki-|s1OH*JP7_pDRIIrefG`{&e)bb?0>ZFvE z(G*#ma7|-_v%cNx>gw*6mL%nf-iZlHR2GB)=BTJBIC4+(^73RDj`#fkc+f|4U8SW- ztS%3tw80(wVZ5f=y@I?xI6VBL8EGy9hmkZywF!y~7gxW~b{BxfeLFkP&!5j-iV@${ zYqwox(b0amF4`YlbMnteB5B$1>sKWrdH)e_4xeR{WZ=Y2fyKIu&IMn-eKUcEte>A- zRgFLd?f(56t?LkKWq~S)Om6{$@~2xZe(PHpmvoGb9!O93wPsvfnfg3E^Gi+2Iu?nn zUaUV6=ulBrEtpyUxL^tO8Z_~zGYtvW|2ex6I!f6@;q$rf5=7hDFr$RHVs?n~ zN}_cw%mOTqF62!DvrM#}h;o#C+(s>o+z<#18x<9mL#*)N)oqMM+^)dAk;1o`p{CY( zUUWVy`~rF^*|sOOE+YS_3!rRcWF7EeIu1?Km@D8>IZxwfTxp z%iqod29QYt)C&|PCd$QK{6w=~%mYd};iLEaf9-e5+wmBQxq(+!CFijiZ&jk6zIegt zvbC{ncP{}7#O>R+asNW~Ua_LH+)1kK2$3S4yA5p^vhH>wI6XD>3=YP?!8ZwhOXVZzK z-_XCTt*wdM79}N*qOB_{DuQEUgHRTxQ0XF(e**W(-#LU?OtRJXe1}7EQDza>K`VdS zry$4`kWVSs2ZE&DBqn+Qg;jUGh2q%I-24T9IX?MPKn<0RK!qRPeEz`cPj`NA^OdWp z@s0=Y7~Cw}MCz7?B>(%_Bm{Zjj(VZ95CRJHDbaK1<|Vg49P*qih!@+?_9V90&tOnZWL?^L%_`DtvbVO=Do&j85ft3e|)4p^X0eq;IcxI&I z&A@op*47A6vFCue56JZA?hk|_YWbg=Kw3P?=(&)TXMT$GR3Bg5_4t+dL1AI-DL)=+ z!nGqd`s>#nVDN+~hv2m}605HzoSE_7WHU}J&j7^r5P(6#^$Q;C0IDDa zC-?^t+)=1cU?G^oD-@y$n*IU)MDQX&vl}qf4;>;W?mz?v26nFCdW9|o4g^I5umpr{ z356@4kX)k0b^zB$IhrLB3KsI+Xmm2X^yO`BEEv^9kV4pdkdKtOm9>cIISSR}&M-f#}~>=Pw{z%w-u35?)*1eqqMX5+Q@@h&p@ z+#?4DRcJf*6vxbTZty!Hw}HbE(U(4a3BBfH=U*HEkJ8OGN{CGT60entT~_IdcovBK zP+j@uS2%>m{a<{cjLHLN+=upa+^{IJjXjN@p!iT302AF|AI?ZBBL{*$h|~$|Y0D-L zk@{dHZ6IDSm4vgUVrmhK1AtCw{*dACD^r&GLLvq!N^slSL~!f8u`#jpH`x9$Y8P{j z7^p9Z)O@g88*M~*v6 zdC+CBsl4o)e9}!WvP`#f#f`aOCEeqf*kfp*ft*aSnlR8di(Xlxv(a+aQ+K3~vRP6oYF z9=BD#N1eLqFm~@xt}pyRm_$c&)Ev_|fk?lnKTRB6wMCnv-?vYN82e(gb93B?_9kjkjs&=a2JWlyCUgv5V8316h6nnX#ct9qLQJOf+FX~FSx5hXl0<($csIk zR7B&$nB#)RcLOJ#cwH8dhs&>kvqFF-XbGq8)-5H3I}R^*u0sI42<%6=(y&AMhzv6_ zc|vV)e5g($Zj9Jv(QRu;HHmn4A}gH>}u@BQ^BwM*pW_Gclrxkye@J822J%+ODuBx3-d|aDHAP5B2wb)P zyLr(6h&xWWo#pB6y%SM^9*YN(P>q&}TW9#muW0DVAau3=ULA#bxevIg3}0e9cPy6C zS8f|HHKdK%D{}FY`vF2N8sm2mU9@@|y6TDiM1Re({0~FRKzQ#sopYIpN?HgbR{sMsQ&x-*${1p4by` z(r8R-rDf5{GLWw_B20@WizY@)0NgBtS9!3Fgn@;{8<$(+&?-qD12*`X( zsZ(Hh!23U3^hJcYt}A@z56QeQj|V4A25hB}@?+eXJpl1@SoOd%G=~9Plh{7vR8WbO z_i=;57wet4(uOqyNl7HG@47OgG5&hr$`VI67CL>k`O6pLfEl}U({pt7VA!IA^shwlyxq1 zsesDypI8oL1UQv=FNc|4QgjJNo1Z`k|LviP5FI{oCMJul2fO0meuM1 z_feGSKmfxW$rB~1b4iC8&cNpZgN|Zp{dD)*m#V5t$R9+FLNS6y@$Kh!8)&GuV@*3q z-hlr)J3GG?cMN^?g1B~c;od!dmUm*&Qt7nk7aV|?&I??wxK?Jh9wZ+2iC|+N9Lf(glCQw2x#~oMK|Wdi7{*Cr2jI?p7}8 zUzx~KkIGmi->&h}gs}V!0LCf#{Ni3bYwSFKioh(LTdv31e z6nSLc>YjI!5(^G)dZV_qvHU39sFX)1@v{(OIfx(}qF2{%-0;Bq?!R~Mp8o5yGHFnF zf{_!S6xRt5#Ok6r`|iIh3rD-21xK%t9&r7>Q%fXwXHr0Y}dJm6TjQ_-biw;-ES3DQu2co zp$6;*5BzP_zZ#jKKv|i7A_ld6*o|KoBEzQnR&}eqXmG?bph~h3Q@3V7cij(q1Ir*p7iAsyY#f%CdbY*s9 zNzMPh*)J(bP3=l`U|%`)1*gUFgO@b}@sKAS?#r3p3_@F1M8FYxXTI|?zrmTv_`@+k z*@Tw@X%#i_78rlA@dzkl|JU0motJb+Cw>_{J>s|Ka?zW+pfJ4R*Zh(FUUC{nw8$X+ zhc7OA#!(A4E8Lvz|F}}`_L4-`KS_B^pd3x9WYg+!6QkII&wq80Qwlog<0Zv%ZU%h~ z*sF%uj#yW|f6IyOMQs4&F@!AQ4tp{&4PEb-V-DizaU3}P@bB85(trJ{du$f1k#PC; zEwIP**INtlU>n(xRFM+J0p3IM_hCUlN1j`ava-@~ep%hfL)<*qW-68^eFvKyHUzd7YXl6F zQ$vYQEMgTiOx)MJ(z6Xl+w<25;?YO_2Lv|KJo_2WkjE+71AFO?#X;Qw2eYgq-3 zU;9<_MrnAs0f?eAS4feJp&>IQSOf-H(T%>Ojh)wLxjp&(0rV3TcSbo$u=gQrinyUBfiSj@#7(che+y)wpk z)5Dv?Jy12Tt-uWQ;ui4~2898sS4s+htUS`X;)ks0wi72R^ORsLWcJRQlGjA2eQ8c9 zb$eL(z-f`qUnlFzwrZR^k)rz4`ke#gfA%4r-19+4rQJP?{z$Vd|uXJ_VTY^L@_y8$_ZS1I<+Ti#-mQBFG&5OAk4p^D69 z{jcS}b*n>lF{&$lh)TZUr<>1x6`rXMSQJST=NaE0SYD8+w8WM_Be}@&+fK2~l=hZ-^`dF6!(yc`W zhx|Py8?Cext%35EV+>RCI6tN%bz4>4j@}onU8z^Pk|-243c0(UF$uS4A3Jk;sr}@m zc#}3VRV^j0)8XwkY8squOv0bqdFTR7sO1w5WwYFomh<7fTPjtZnNF#s`LM5~{#4LZ zvHjHRsQpW|ubuq_;^JaY89&|i`Qhp?>(hmaEcVs4i5~&`(-f{>m%x?wRNL3{BY8ru zg%Sf*>`-=YCMF%!d|NBkS=tjP=k$n`?dAOMo0~36zwa;&gnE0k{Ou@x&;(n{aTvyaNG^^il~~I39GMp(FLy$DsZuauwG7iAyE47gAaf0XCweC8yja6_N5&r zu4*3*OT7c@w6i37$a7+3#9QlSj>rtqXRuNd+0N3&5J29_)}^lQcNted#sjQ*hIW;g zpXbVgERVZ|r|)$}!d9^Xf_B+|3dlJ{D^+wPZzMt~33-900-Kw_p22A<=$9a#oMB1@ zk2VS~_khejC~CDbS|-98>M57u_GoEx66ou^JGrr`sce*x8B$2b_^lsAqTs0MPHHv} zt)pKnD`k_ckgQTnx;)h*;E?=DdY+D)^+_*}$qn7`sr_Pe8=3n_zQ7L68mVv0`phR3 zHCMCM5yRW)raz>=V70G{t!_zJ&x^TO$gKVS{atO3&VGL_aC4^RU%@^<(aiRnowZ|~ zC2m302F?@)h1*Im^c?-kzEv+}%i0i;`S;Jff*kLw@Z%%iT~Z$`{-)B{?UEE@X1 z=|-fI(dUQHah*mi=zxKC?%3$)uY&oueWEkemX=2xCF?fUSzu_=Yrc~gvS<)Pobg;; zbVvNCQO>Yt?#IQIe*a%8G5eq;ABP;O{SuA8MfaM{&EZ;$KObMM7)`udN4$*tcabCG z-wrji*7Re|nO$MpX@&FkQZ%ISYXqLU@BEecU7hVT>rc!;To@A~CzsE)+Il+tQ(_m5 zk-AA`NcW+4E`r;hykQJE;=T~jAI2q-pngCUdx?O6>GkVnKu6j!cO}W_eFyh<=Z(X? z>Vw;YU+r<^6ZC?^0f$#5-W3H1_}%6WQ6zq;1|)(ZTt>$giMoG2 zL${?>kiMS?o~`1^iEjt#uCacGs2y=b9l(=|i%ZEBK_2zOZ&1EvxkKJNE#69+Anxbr z4@p41q-HzayS6w%WD(&oMj?u2J@kVKqMEOdPxb6;XnVxnBki@*e+`)!B`QdGXkBiQ zFOSQPKKo1JQhMxl-Q7RNq259-a9XG-Bh(X=v{SV+smi{DBv>@Pr&|iUGuHKyeY^9L zNtN0X#r_IOlxz%)O^QFD%p7 zB`2dl)Ea%gRm>eaasT?w`4yHy?ygTpSoqJ38O7>PDdzEicX;nj9xmL2eyC0JQtLS}d zoy^IRp&Ahy+~NxyYcM`KJp3&3<)4&&JlrN{so563c6G5IOFOzSI!n!TtQ8ty3(0Ho zf<1ii4i!DRTRrwG%=*5CZ+7-sX6AmZU*s2-k9-|G;?=ltTm5n! ze!Ih99TEI6G2w<_!E@-47p`G2<|%%aQTnmYA}6^z$PlKgpfJLts3VFDyk&DT9q_vC zWP1qFU>3)<$6GUMVAJ`2A09G@T*bwWTKs7jB_)rfWDF$w5mCw0r(a)McI>CtDG zkd8n0al>}<@Cm1fm4z{s^4j}%buVqFF*!hbe`HgF_=OM|1_~!jjH8{D{KkF+PE4MC zplGaeGIhM#=g9N?t)6`avu@jW+N@fTMFh#yx(9 zH7SJz`B-S!8cj{1Vc+B~cpi03JAF@F?%!;0gLC8wRik=m|a?9qy zt$5M8=X`D^rkbyso87GK-BgyR29k|R&t1K$NAu>u0mJs+(fo9Mik>GWt8FSR)`Slq z8R4R(c)*7{1mi7fr{G;4)k=QTA3|bPaeFX^8pIx^MGlnNBx}NELnC^p6m}Gs8G3tr z%ZMlfyu-{YHC5HG_-F`E4jz65-iTk@blIbHFn0$T+2N%Eump(4Uz(cealLN5Evp{- zwT*ZzLSRiLuFi-Y?mcC$@<a2m>%5=&4GtiM7WA_J{Oz? zQB#vv+QOiGQ<~e}-rvuAf;Qp>ll3JYJ)J0h?KJXeNLA(j2YdB9X4F5nUB7eVOHHC& z*zEW3yGF;^7_Vu+<5RnL-F%qjag1Sjo`%v7<)oANyT@d%8zS9`fBrZ{zB)(jmT~17 z+*UhlYf%!vRTrHj?!3gpF0#jb!D7FBP^$ z?YqhminJXPVwEuHt|rd^u=#?!y(tTjhq#`9CxV~|?|`@-S4c2Ae-?L|k&kZ14bG}f z+#xYBH~)rv3EFE=MP%`Po-6^Ap7QEHosjTy;O4=y) z_w#psS3mEm+w-imVCxX+%YUxsi=_Qe>p}i0{_jO?=0D$)ckOLX>WQ)j{aj+)xSvA@=r|#F$oMUPaWYqd*<}%cba?xdk#3XGM0I8u;3eNFWIQ>UIk+V~*ujGV9<n4YogdR`nP-miWzsKa@YpaT_Ba z3z<0VO2$L@F5#GKy?jDMCuwDcLjL^X~io{@&kv9PdAM z9G-PQ_w)H&pX)lW^E|JgN1vK+CieEeR#ch3__fNo)J}XTwM5EECMYaS0-i@8>W@0u2mqbX2nMj=%CesEgR``pGu7TY| zo-7sFR$Jlx`^O7M?kf|zN4d2gk4}83{#?1nZ(*^KVcqh0$G2|%t^4*d&y05(!yAQV zaeD8!rdUQk@k>k0o!uh)FZ#lVgew(38^~Nv19&I39e5=Q=3kjNClF%e2{%LnuRq@c zbo&umTs%DL;9i2xJWes<*phO}o1~BiDGMo(4lnH`JQ+z_0Je2OST)YUbpKUeI7Xnx zei^ceCGY{o7HD3yW_%DNz@#kzh-c~p&XK6EDWh$WEZ3mcdUa(e(1LQ;uGYDY$q)_0 zXHnBrL^6Kp&>`qSP9Yh8j;y$k2+B!wL(``&rce23bV`@{Z!FidlF?-C-O*!}RuCCVW=Y3OnsP)0++LAKg zN8Pa1sS<;!uhZ>&PlSPptb!EyL;1qdzL!Sf4DAtVX>6Lwdx(s9`}PsF)2H9oDIM9= ztn^Ns8jucOTN1u#Gxmtn+{xAEq0bTtPSGpt%VJl5uWxa5JkMw3^p@T{&+wu*ovN=LwT)e#ZZtl1_^XJb#Wu}0W8CTD}x)Oj4X$ECB|Gt=J zD=`>OHbaeoRB3-(TbueC>UcgsA-&u%-Pt}g_(klxIIC7C`9U+2FLND%;{lvLDx{iz z!LM-~U2c%N-$3?&FiT{B!97JOU~}U=LsC;y)nj^40zwp=lufF}9u3=)+nGbj=jPjg8t2h>w{P%BkQ^RIvw97F+oqr`6DM8tcSD-?DWph}k0` z{z4CtdgSsa!V@pY1r;J-dxYh}S7X?81>~m=idraUwm;O(QI{S~Yic9fYHn^V&rd)` z3r1HlxDpC296StpU(KJM{KJeDY+RbhMAZs&is8OoC#Jy)$}eKl)unw##aut$Ca19d z*2LXNll3}S!hJ^X^K?tmj}_Oc<#UmCCRH*goIK~gU70*b3#&qIA0MUU)6XZF$k*7~ z$6RFAztBZC-{)_b%y3ro6Rf?S$t-zfSLY`Ke@|$KIBIV>wt7SyxjburtHj=5NoM+C zcD71;+T8eOH?yafDsr@xuKmkishXZGG5m{VcjDHJ~@Ohw+!SQc%8dI=b) zRLIp8i<1y(ZftCagEPhV=JnTpU6|K_MiY!)e z-odmNx9{drN`}Hqo%fP7A>PQ*6%Y`J>qF0s?(VfO0|V$$U4(8C4K{d?p;KKCVj$@i z7o9yV_f$CvlE^@nA0s#SLxog5T(>mjS0F*PiSBJhk$E>fJO^F!vNn)~0>FF#QmN46B~W)jEID!QpY%`yQi8qdGlZUC;e%>)D5yB9ueAnzt+)RAr~0a z^kJ;s^?VWiaM-%_E4V#|JQAK2WM>Zqp?Kf^-0};5;;!KN?uCMR8@DLdODV0F(ee zKSDEPZk`Se_}DX>88o`zLFUG5`a%Guxqc~1uO#GGfULOU{g74Hai{xcSuA@evz@y2 z4H*^6WwkTyd=dol1&#WhyLRc9e4r5$5OT!&R5Z(um#U(q~GsCJN^ zf!smZE>^pxp&1AIMf~i(o$A6hTa`0hz`QNFGDNSDT7I(Wr_y;@bCRwPdS?$EY<+q8 zUT3}s<0oN(vk~#@F^P$4Z(@`NDtva(SXv${b*S5S;(eHCmDe0?%RMjFXw^8wENDcW zoNg{7tk?0Remlj{o|$!*Hh3wdm_BOM^X>5dWAT;eJ(tM4?>23e9Q9fhFb_{}e8c>g zwDS9YwO^m%QvhlqJfZJ_&GKkI5yPuvAO zr$79AVsqUMgd=dJ4EY3iBe5|=N!b?0Ag-#VHHri>qzZK}fpUUA0*C{A-7Tgq<0=3=$*v00Q+GcvZIeu{3gLU;cPen%pgCH{zAk*}Q@XfXx!gV1Xsk!Vd2 zy@@KDsEJA=L-< zTxVn5I#IX_n!f0Gea$&Z(y82JSyohl_cy@_+dF@Pt0H@}2+Z6@-|OvB0f|f!;?Nq9pWvxNn2bU6QwRE$ zsJM7G$_PH@y(@nCZ7s{-dgx{=?}o_p9T3uJ^&A4ILUJbV&?^#;kUK-@26Fsrf_8$J zci;xlvEieaAd(JaN%v9pxe5=xR$LBV2ghhV$leJd33v~XhNxRE%+C`J8$dS*G6#wy zofi(qa7iFU?fQ?D)Hq|93N`UF68-^*>p4gl2oAKca3NE;FbQNig!3FqqDKeFzIyy} zJ646g0U@Ooaayd5+{cOp*-s0`3B4~Ili4Tc*CMFla{=>-AX4QUin$X?V`MDh2*XhO zK>-L&z~oYm>A+o2SYv->ad0?k%D9Kc?9G`IWLZz1sF2Ow@+>VnW%_!4ef*Nu$!;ab zU78+I!*dt4SUr*LG@fok(xudfwWUB4oltyb($ z?(RO1E2C~}TX`2xFE#n6Dr5^5r#`P)-yh6pG|AC!aL_u8aBFtUS3trn~3| zJ*upYxmF)rRRK}9Qsh&!~WJj=T8bWiLWt;8Tqb^C=*61%%H zD-ZiS)98~=q8QI#dT-KJV6-WAsnycguX1*HH^-T_0<9236_ve9_bd;{{*jVn5eeE^ zQZ>3+m7qIdv2)wOa2dA3!v|PHOborW;!oX{`1(QkVY6Yg7tEeA^pgd~M0idJ$Rt^1P>tlcshne6sB5k%ex*c3 zE_V#861f=N%L7~F^G^n`?(B}48d8*(fBV?X5xn`TXKyIC9n@I-llb7l*_v8JtkFBi2(N!Ca&WzOjR;M@kB)Gj688g}xR zm$~bg^?L>QTis3MqQ{O|6Hz+Km}f7^(0QHND8`SH4$F_;9qf^jsd!d${QcjxL+ZmT zJBfE-^_0TJba)QO#|8$f57s(3_%i8jHQdL@veYISa8fUdQLFqKi&5U>$t3-q`4YNO zRH7@Dq89H`_FXY4ITV! zl22yvlGmzDWU#VX%=*;!m7CnHQYqft9k-}`tSAHbWLsd@t-L@^TnL-`jr#iq{*317 z8*e^sZWuT*NIp->)9EstgiSH&?}L7c!WyU|&gMt+D$2@yt3YU<+Ol9TLZiUjYR$jDINfYyhvsK*&Yg9`w3p}#sji$kbt&Uahj02Q;ymT<+i zr4h$7_ooI$xT2>Df?dxf4Ab7lU5Yd5{z#*^@m(-_+lSZJIyVo;Uv1y1$yWF^wtTRG z<=_hYP1ewE{X3DuW(?ndlnnFa(TBkk8fnh*+8Y1!+qZ66I5=p)IYW01{)nEC6Uf&I z8Y0^yl#uoH%K4SRMne4WD#jG93`)sWyNyb!fNo0Yl*JOmaL&1+nFTi&r1`s%1%V)V zyuHEq_4Kb578%bsC~0Tr=ZC>)4EY(GiZ<$%xlv8f5ebu1w7)yY{u%nleV+s5Gx6oM z6tV|G;}LQdgEyE)NC`s-7%)7*kPJe2tzT%CiUtQV2_=Z9AQ>GhX_rIYiu}>xbForR z;j^y+8SWf17bFsJJN1xZpi&G48#*m5Z5`a=kd2hbv#20mcM<;Ya2VD`Y5?19F96vM za^W(uUw-C1@G7qeof4h>Y()89^ZlQNiAaI!0%r_MXqlFZI!~rSSTj z*1aINpM19EVY+`aD368R_hMpdz$Ih5!OJ> zq}z(@J{*QfGZ8`nv=+G7*yKBHY;E)0{lM_0yb&84tNZ%q%~KI>4|8+7kh`M$SaxM- z3z2yfXfZGXbbY=J4ZVeKr3@XU<|?rL8@_(sOf^&6BJU3d!tPwxk!H}AW3bE#r3kpN zF?9zBMLwcm5ZrG6z;m0+s2rvNV&G8W=cpri>=0uDSX=9T7;RH*bevB!Cx66DxTI>g zVGv8xsTc$wYY$uN`Gt8U<^%HiisxH&D7UH&@e0xf#>7~tY5t|Zw$xyMjfI}BXvQTh zaYNA3Mu2vxnCk$rJ`nPWUKs=(1zCR^Sgv#8AVW=%U-1~oaSHN#ez}%YCPYm~C>}6v z0}`1VCpMOEw#%*U+OcB?7F-^R4?-c25d?h2zDr-OVGayI@y6f4rrrf$5>D_u;jdv_ zhh9Ph%o)MjCB(&$28YZR+wKCZgfMPEIgbj;@PV7r`Ne_^Z8+D2C*iCZr$3SD6<9KH(L?( zZV0R&3J$&3R~-qX2e+|3Xe#N2@ftm4!p{(*$>%p0UwF1C5RzI#2cCM&1HJ}_#l&>$ zPAi{pjO;nB#-}N`!9lF%*t2k}A`B>w%VYOlhMK_{E$O~}3p>0qD2JJS${qRZz?Dw>D zH5gD9KN8svA?giyJCyjYxPp+NfEo~vFFF*bNC~HU$Z~R#cM#nPsP_n$Jg!2D8=(Go z!cfhu!t*UQF5yszfD4Pn2s|({;VUjC7AL8>6o=pOAs_+u?WI|)qN34oX>V=Sr>6ux zSrchBR$4Yut>)|9j*5z6KfZ`AMCYeZhQ<%>S;D>x5692;HUft{GY-Z-h`!*$(s!9pW^8U^h+BLDBvq2A@><8ryw*+u2Et)zb$3qmFeu z|MZ-9la?BVPE14N+L=R7EgYJdPY5$xkxhn?7f}t<@7*grHKpaY>3@5l-`B>i&V?Dn|KJobG$b*D*@!O)jPKOK1@Q?numGJ}ykgfSv|w#`u5&K*%fU^9Mh zPluhJjr&0YAHTa$Z~-~VC1)e<71}(80?a@*A>$&%4A`SNu;hi}s00H&a8ong!-#R2NdaJx7vEsa8D*^I$ji~;cdDTJq@=k_1@%*XO0po5)-CR?GMLrVP% zY`F;`m>2_MvJnIQzI_V86m*32faLGm5;u6Z4OVSXpw-@njSZyH$v+R^CNabC&obRN zzz26;i9PZp>n@RlZG`+eI+}2Hgr+YNb1`^Up_*+$2`vq|DRf295@=rh5)N$-{8O6F zZoL4J%&>fR{&2(DY~G6V)V#;P%Nu6=&byo!b<$7tKS>VPMpd+ri^sIwi+5=SW%&#R zqnbZ@Uj%C~(A72u9P)}^S05JU&&2G43Y$}|8 z&Ye34>vWjc;o^7TxC7ElQf&5OM9rk+2aJAREGGAHph<``n6KV?$-)lp7eqea!Z09J z_$F6|YRKwdU{(93K0L-A+ccXhv+M}OB1c3W?hqx{H~oqIVUghtEjen?9E9DF7swva zT@C4`%oXV_G*3slEN0V2*kgy7GRf~edEkOX3QA%^DS>xV!V;JmC=+wSVoF@ifHab{+ilPu>cI78xQDP&uWxdTy+jg>-o*gJZ!nuuN!`aKMHB z3EP%&&gqyi*D)pDGw3J?#eIrtR;s%#ba6wpN%Ow7;C=@KfrG`Rk0UUO~2^%{F z?$S+58`53|kPU}}nivE3Wd1%3OnV}s>~z$#KlwP;cfQCeqQ#(|gIOMM{OZ^{tKbMH zUqFk84=Xo`Y^9>1*?I#;AJHr?ML=^1LkhqbfBpqVGDu3^15!qJ>a^1sWc{kxVx+bp z6yeY!Kr|dM;*cBH9rbeU$GYMDCA_)8!OkAn#YGzgRvPYT4aCk!!}KSvLr?vR7!UxQ7jUT zi22&9Fh?gWRxu~;c%(Rz0Q{7p`d8)mchkW*zwtz{Rc*Jt$e)=;hP-rsv^_!kC-EZT#{?!w(0%PL;2h{inzI zw(X~FPdi!SFF7#{mdHNUfAAYTQp{10$4oe*=Hn$kOHPpO(xva-wTlQ;$Vv?lP=p+W zW)0fR&#=ZrtWp1m53@(-8MGzfZbH*@N?Cb3&VRjs&@e&*)LcX=_o%cTIb5E0<`#rW zpRobFb{=y@Fl<8vj;8=c+nuZb)&o%lPNlPx;k2e^4%QoxGul29V4Ez26)>D!@v=C6 zBdt#E*e1f}7WJ_DSPBhbDafmyA;IzuwxuKcOkaU9MNK$CPfX5`DTNYQ05&ALlHjxP zL)}V)CeE|sVsybS5oYwns3CkSXwZpog>6IpG30LpsBmx>f<&dRvojit@5k@oSPC8? zGmq7!oDE6$T4p%EL1ER|L&G140?~~}ZF^7+tCS*=Y9r8b+1j_~Pi3kmX_-}uBkK)n z@VG)2DnQC{ll*HBK6!gs;q(_>?3ObKgnFke7xya0?3 zGB_EP+BzH(@x$9dn1n zG0n?RX+47%6SCUifB>+9qG|Bcfg<_=(TT{{>CJuR5yk+HUc`U!tV}}628zylo7W@5 zC(Q(lZ};xMZC!ps*aQL7(i-3J4F`Na&jROY{0{(k{?W7U?wt7f;lvqc!7{HFa`pYeGdGL@heJ2?4JKtyXd51=#kvkWyng*-H7Q83NXf~pMhctKN)6kvK4C`Vk~X}Gte24WqOXV& zNa;=oE(%UBx0;vH&m;~+G*p&Hlbt(Y?}06k7Q<~cHy?$yPaJ=2P_Q3Khz+cCh$0D= z1ObqAkJ;rbfeSz<avU(4`YqvxtW#VOS- z7^x*c-`jm7UckgOnm5}*;!`%4l=IfPb6+*!b9H@x$*i=Ha)re(WH%-G^66%=pA_NuUkSxL&>DiRaY;EldH}?B|z{ry}_mC zyaZncouEA}V*a+}qz_&}e>{d@h@yy?)rkR3<9*Nh(LfM(>XhRVOVrRAJjTtr@2_0%ah zclT!3sk74V9!J{W7gSF*b& zoYcOe9DYy#(-8%!AhZ1+-j_>dU#0c1tcNoSi9F}fj?{x&AjwyrLGT3+T9 zv1xm^^czGc2%$U7^?iN#E9$nJqT)W+?^ydc*r?z-WMwM@L-@4a z0KKyED9h=u*y(&S$q=%wne@}qJ6I_pw?AjbKd{TLn}5Pv1JSYKLOd&x0$2I&hekdj zVcUteZD+++>8buO1rF^hktqJ9iN>(TDWc74T>x=ZT(EsbC6NPj-{Rzhl&k1uIKA(jOi{u2qXPalZfgE<_n? zt*;T?@cw$97ulA#Yt#mb+mtlzBqn#t9_E>_4RKL$@?8c8dd@M3hZm=6iLH)wVBoCN zHY6(zZ!!c53=JB4`~&~=--Yp`nX z9j?C}u(i7RCAC6CE~86vMZDR!pUEdUJmr^Y;v5wBU>y=rb+Gbq6`T+PV^r?VuqH%v zj3Lke#Tz9m@AM9>y24kUMO&#Ji+bsxQ-!!y-_yhYbx_?^m^*;=II%VeGWV$%Bqfb^+v#I`DyW|v_cDrJmwCptE`0-LNd`Q0G zlO*Ng$|fzHAZj|!yM-CU?7JW_A+AkgyT|(@dIxDn@GWD)MVM$ci-!X_XXxV2{kx@y zEq?q+IQT54clp)#{YtD>VQCCq!)4_;>H!PG&4sNB%f@-`O5Eci<~F5o_xqI z%gmQtn68X@P1{?Sl}8;Zu3EArS&wB^m06tN@pwK1jS%feS(!XF&Vz?}coIGqI6tZs z&B~LFRaCPNU?MJEPK>SpCHoVry2&U_fs`Zr(LngVf8ypo`s;h0ohM9__eV$nBsp7} zcf@C@P4}}i5DbTbqlz=1`%RmX+`7rs_gl_2!-tb@c6oVR9GbG9pB6@@tIs?NYq8t# zTU%dQ?B5^KE`I4KPn%24Hp|gAM@9Gf-%OEyqZ;IcgSx;$wDZ68^z3(be!}$B_+H9b zo8wS-{_fU zkkjj0pVOLy{qBdXI!4)17(3|mDe=VVQ-dFkD1Lv$p^nTL3Ua3uosuP@`ROcejFX3@2A*rzNU-@QxsZS5wvgWJ$-T zf1%}GWMKng!}at$eq!k24lu;mVQd$9j;?uejSDEDt(MOvm1&>bbln!Y1j2*PEfZ}% z%&_3%;W=P#UTCp7MEQkUtG0ZByq}@1RZTC%;9k~YS=mF5zf$}SIhR+JFWTD|+IIFB zMaDB5^$hMh@GJ(T*o~DjQk!t<)?3Zq$mN7 zN_CKFY2oK}<7XMteSPJ-4wX~$7d(RP@XbTVb1}}FJ3;anEglG&nV2Y)_Vpx{^=@t2 zB;#jC+m@7}ynH($TJV;WmF4hWYHL~Iq9Ug_eO30V_w<=JpZ5L8iJ_Oh7fl+;Qv2=g zWM%`n)$ftTfLZwil{b-B1J;3)J%w_(_vW<4RX=uM!AQJ8x*(v`hS#zZvph4gn40$iHjT)e_EwOsLCI z1*xK5EyGw?y6GgoRW{9RXIB}f_8*u=w}GcJYQm#`_{cu;&A@?y|EdW6uIkK`PY{p%BHfOI-lg2%d-SB4f8ui!%JoOIgJ$jfTgl{0 zC+2ubBElIF1cDKEOkR9aU&g-qAm_`KJRr8{%+PDsCWN;RBk zI9OGt1wyTAj>!QAW1hZZM%`g1Ihk~NVP-Lx@1l>4oKLOH#-m_4M01#FXy(V|_4T=H z>Oa1GQE>xJb{|dp*h+=3pQE(5J9W$bYi}DGNKTxfcN)~MT7qr?K3Nj)Ac-?Bc|^ld zX4ts|-5cV|x$@G`GB-w1rxZC+(d*A_+BNnI+CMpPKPKiKLSCXoz6_zDbvkdJQz0}l zksov`;=;P;sY|U_5R6!6efO~3>`Dq%*7o)^jr@z2PRxCEyADe%nmEDBYKdZ4k(hZ3 z56uh=Q-FslG0Yf@F2W#eb3OLD>?IgVU;qU8vBVsYw6r$oYTlnk=q?|q44w?Qop1BEC!+H~e>GV&Qbf`rg{`JUgrEL(jky<|Oxxta z=VbD7k?qKlL>#?zo)I{Dv{FqX6|;iFH63%R^72F;8C<>@983cl<49{_ltPowMAx2y zf$e%P9K3R_T3YfSl$>f0rfgn1nR@I?{@J4a7^Lew0!@e)c+?iq(mpbD#IWZyj9V2Z z5f$w@`|_!sw`B_tv}?4LUF`4Q}nU za&kKojva}4^~wi_vwr91hcf}|GYuE(Bfen3z0cjSbJ_c}3{v9iAG`{Tyjgx=DTw*Q zE|?Y)Ou^owz@J6%Z6(acAPstcng3@II!ic(ZUV4H2ZM|A!4+=@qJC$VNthjpUr4}M zOrqFdyE@_nUHI?rM+W>~nMEMC%!ctAkoyn#1G={>OJ4d>y~<0LmO`mL4tZJL=?`#+JWf04C&F88{owF_=tnj#S#V)%)#;HK-Xg)Ju_~gfu z>mIhrcr+o6`TUh<&wW%0s9DGy_Dez!cMtdVfxT*l9(qK+P^x4pd~(4edgF5PDF zJ#b?1PLbt8Z^_u@`moR8@dgGtqBiZfI!h<6kR(Yk)YpaftDW{q{owBP{c}k&L?rD+ z?V#qjr+?MATbf_L70cZ-+kPT));Fc^`Rk6yiS&ks1`}Ve6Q^T}>}!U`*&4f#_5zwo zyWS2A1RV1y?}Fk$EA8Av36-%nbF}hI1q4#Svy8ZQMoSArw8A$28hx7@*mnH&>Ml@X zS*|k;Ly|&z)WzZm?Dut_m|R92skKRe!Y(Vz)jswolQ&$=g^Xo~hv@H}vX|ZsHz^C8 z%Nz7=h0X*eJkaRmX+J+-UlB1e7%WGEHk^b`uHN%UI8iAnEtmfY$bxJvLMW(QS%z-| z^wO>LZu5Q&^2I(`j{;MRCICN@AWJ8j|r{Pv|by*EOpimDkXS zXmEFPypgKOb#>}%JDlrR{CoCzyU%7kmN(9Exm>HTJoAlIi1}Cu1YIOXPUmdeQEC|Y z#c~RBaKwhS?#l7|S=QbBVP$Q3>Y=Y!zf8En4lA+5t`I}Bi9QcWM4%x^v>gVg-Y@)Y z$UFZ!G;8zI6LHSNau3ZaA5RZ_F59WBYzXC3u1>bU(&5cD9G8MxTAG-jO0s>o$=b#t zk{!2|LpD$6m_Q0V3z;s7jJJDmD1GhSJ7=FBh)qlkNKEv5!2pBaPc0rBW9`mtvDP@^ zb8QRGb_R=Che7gxrup8Hk&gwQT+)1o?(!-sC(g`X-!+UC5|~vF2G?EAS7*b06e6uB zio$kz&T^i4P0LUA(Go0pWVUiJGoW#Hb|zfv?h853_Y(Ytjdf{RZ!5ns3~_(k+#G_% z^P%u-!v{-{=yNb7h2RGSQ&fg3hYybLMUoCQbY-p`kp~bo{GN@g*29@0^msG45zZW| z%Gq6T|MkD~3*^gJWyslZ%)zp327ozXc<1X|5yON(jsGl&WTx>ws%XYQN(7mNl>}w# zvX2ab#;^YTsAdiRHK0|~azF0L_YB+ECaa*SUgZ+ybnSc#B@*{R^$l+MS@Rm*Wq+Ha z0bd0#jij(nesa%z96&0oyo*avRS!FSh|bbcXRP0K&O zD=j7EWSJ}7oXEA#Iaq-r+2K{N!h?51Uq2KRl9nd*^BXbH=HQ^jPJj07iUss)0_Xck zuPt>_2q)y_q3=#}KuTz>`m%R~-2GEB8!>vB4R$u)Ms&} zI|L(Y>39BQ5d_m=;W&DL)Ds@E($a(>Ed~+1LHa^xCIUqc_O|wmDMs1Du}v5*BKVB7 z{`2R=pFM7$EYU&Ea2-im=*6Tb!kyLJ7cI~5YZIX`nucmNmsV(7Mt1h&h7k6lryZ6# zG-cTB?t~bluDQXzj=xPtgn=P{O6J-9sOfwE)y@CuA4*@bU+3I&XfyG5?T#_Kw36!c zS&no2cTIl0-FL*^!p^Df@3mKh_4N%dV;lThsmEJp!+-J?IG^m_FG% zD5bfH)a9q8&R>#*&aJHpGyR%E_M_eR$)6>#GMT^8iW5@_o`ue`+`DJsxh!ImuX#8Y z1C6ZXJ5nQ+6yLdm7=C(T(lJ1Qfkk%py!F7%9rvG=ww|w_{MdiNW~{l$^7M_z+luLh z9Bg`@QC(`Qc-(tfOQw0@r;69gtX;schsUq?pGz*nJEua|N1=vB=v=RQ?JemORL74? zWh#e*A9DQonY|chQl)kB&aIQFuzT0;_aY)97*8$b`tIGjyuJOPR zqQ3iQvEAC*X`L~nNYY&sUn2F!LJcEH-5F*Xz+}@K%c9+$6{ww~PM4K!u+R&lkb%Or zjr7X3J(njPejMG<7J(9Cf!}- zDu;N4u2deK3_8YlZ)D54JAXFhcPd|LQUBfUF{10L)#jzeR6Q)$-YAGZAEe+Nj|{vc zKooUdb^pY49AeJX{k@pfZvXL7T_D+n^u3gnR**k{N{@l-GTDGm2r-03RMh#tB_b3z z1{wChJmMyB>{!y9H*e_oUp**cdlFip#A6E!u2bVW@0l>DW#b|9>hRQ5E8Nn%>mG)w zOmr2dTY{bpjV|@Uq(n=|*r24l3mu0JIy5-8ayuhHwhG-by=<&rVJ>DA+Nh>`gW8Kv zDwN$zdT(nTzo|A4?fmamHoBJMvpsd8^9L6h`~xoY7-wyhJuyCO(%`~#xnbx>!%?#gzs3-`cpao_KsUOTVeuS9*fkFDxUnWwHiJr^wZMUL~Z(7l9L zXBQOj)b1#!_FCXO#T_;>UsnB!mZ51C)3?1lw;bNXQpeI_6tjJvVW@4M%&qMH`!CfR z!cJ)I{f5zI{pkBaMHO>azDWUS4%=#PFI{oz6ws)vSH?WMhZ4*TASI+)_y2rsF+beI zv2UNnD>v?t>diePJ?1LK9zS=06q$BBJ(G{^YW zGr_U;>7lk%#+2hPL#L}QM}}(}m^`2L9J>74J@GAyNGNANPJc`kHwA6f#==4f`Wv2@ z*lE3&PScxxA_0H?>?ST?e4G-ZUV4DHtenH2i{^wkd`KqTrn?JIz&=xjn!zjKa*x(C z`;SzQ41e9o$=MpUv1C0r%6snBv-{UKc8=joW)wOvPfDrT(j{KzUXo3=jORb9)76~x zCj5HGSIeXIw{GnkAKV&w=2q40QRgH}60Rpt?7lph@OdwKqvL(Q4iBxstf|(Mt523K z*FN2+wJ;k{h8P58AfY*jgDAo4AQ-R6);p`KFGVORD%#QtX{9!|77@+4&x^+j4#4sT zw!H^nWq|l;J=_>gNOjO$*Dkg7j#$8KNa#)Epg95;wiYu90G%9!AA;}3N{gEV26o^W zCLF{i9aP(kK)EB-9}C2gLjA{QqdyjW6>7kUH&qa$AY&KAppGGQ%V5zTiZ-F%b9)0B z8M72i;8z#WCN5ksbNo^s4`O5@m{y|D;t`CM>A?wUNth-hSA^5r2WX79T>ra3kGZZL z30p@Et{a8Y^48oS-AYX4fVOxPKnog@pL zPSyMv&A)vlwoPp~KP*dCIY+v1)TYVKMtFNyrZwY>-=Bl~5A0>;=GH4z)-;i{Z|&Sn zwfbG0^X^@pi^MvoUA|pdqNMTPzQzJ0&*gD8wWaA;jKL~1rZ!m^j|h-wo7BIiY;92! z@vU;e@6z_aIUD@F#cOAn<-8St*&i1bwy*uj7(Eac@#*Mdrlksg$hgOeW#s|Q-EnM{ zt0Wyo2A8h}Pt{&O&q@)zWiCr8U#Bc&^#Db1V4p$3+y`OO)>S9An?8#}KIzUSUOUWu zjhdSu$z-ccTi0%f^^%_s-vW>6i#^= zhFk7sdKI{R_x6fE10>vMw!6$)QLV0S&(k;T?U#autl!b268h7iO18anVcSNgd10tV z5~CKknfBXX2hX~GB4eqUt&Cc|Ec`@q%TLk#Kz;t zTs%_oJO13ci|KA~@S*hURivj*>9P9xC+K8feRi?-K0HR4DCoqp^z$c6ou^l7+-%^w z5d7G5?CtluRJ4yW)#;2@=Zzl?4i^2e+P81RdZJ6{0AuopC;d{BUbc#ZO~2UWS1uma z+J2s4Pkgi`tXOw3tdSMm^snvfin;pUQ%3(BVE?&3*z7ZXKrY-NZB~gl(jnw+#Fyq@z}gN4WqRz zz$*dvBXUHuy$BB%TQ=;eTAw|Q68a8W>-$4 zv?L5IU|L{KAWEQnfAyW)O-QyrAdi!chYQO5m=-oY*{;u04vX^0&`=*M| zpQ9_H3-;X)LiCj8OIE1ns#1^oB~eD-pXmRUz2rRrgAJ#3pI8oVEvoBl_moKwuecl> zblY9@#44mJ!2JdPxQy)S4>>J8g#xxazm)p_eK|FFK2Fl{_h@pBs#9n1(T&)BSh27#qAp((nhmVwRgm)k+NTZ+!1T!KGSzS?PXM1?8BlfD|-db zZE$!z>bcO@J0szezd6{x(Egq$x9xAD><(d(%L8jIqN?qU>7O3g+caG}OZW1^^J!f< z+kP|IrV6SuwZpa_cbCtms#GL!hBea9y87Q6$kpeY7|a)cc$KW=Lf@{cy?-7~s1!== zXiZ*ST&vT+{3WdK<0J7`Hq%Sj26C!=mnSZmNI5ls?4Z$LmX$Q$Zw^tfn{v3WwU?)7 z0yVV@Phqmrn~D{R-C{bAem5T2_aHP_@_gl0M8O$mspx`CZrgK(%&FTKtvhE~%>UL% z4b#86WxlZ-UEX%&>*Wa2?&+xz^KS?5wI#i8O7$7Msa*LIVtIp>2yCWw%vtH2h&2RV|x~rk4HUl@ZLGhD4RVfsinr88q zH@w*w*UyGsk7kr|w$RIZM;R)#a*QJJB3_)im;B+BwR=t!*Som~Bziae=A%ay-9gA}K!FAtkHV=_ zL~{h3>;H;Ip*)VnsKj!T61cQiEq|vhcUNQCz&vnblmTegxj=@>{ACr$F?dd4*rr_U zwu)A}S~h>M09)Gq>h0wrFBdV#5c9z0L8Aso?>p4yAg$8FRcS#E5*~I;Z@n=*Y=xMd zBUDd9d`XNtS&$>%u+cmw=DTtAhG|TUmDzT-*}&GJ8Gldja#&HHeN`nctYD-@ntS6inEmjP{tj7%et}^)QWpq1epEQ!^CU~J z3us+?yB+`iUoQu(Fk3VE^|9wus1GX<^oBy4Ge%RTJAp%Z{m9uopmh+&e})^!+g zF0lwgH_<1!0j6)ZJ|7yW_Dm*>eqGj2W)$ue(|Z|`!yz=MNhT- zwNQcF5dr;{!VotuI-*%P*0kYkRdDm(X{wqqb;} zjzW2X#s)Fi05|LMSrsoHJn($syj1(~qvk!o9=5VCvEnZ&iB+Pg6(xOv;u&a+AF@^UVmcGy zNP<~w#8_wyF{Ys7dxtl}*xgn}Ju(cO%bi5*s z+BVrKn2lZ3hD+y#zpVJ-Q8mhiKu|50_^PX!kRBOiYm^D9jWYs$A+ zcCwWkfTaGb|Ml21{ph-3SmW-MCsY{PTaQzHX1?`vGR1$tjxxC*_m>w+TH;5Zzp1rM ztiSJio=0okzJ0Hr9%bLlYnE$QPdpKNwv$b9I}5^AimmFezU!mo|L=Q(cKa4f@Y%D* z|LZ-6@7nOMm!JLBuG!^iI(%!)M}8a^c;Cl=Tp>V{oK5Tj{Z3s32;&I_wW8fwIwGTRr-DQF zf?}HIi<-jFkN#{yJdN&UOYJ4@7oTDb=I`jiiLz6JHeWc&4n%XO{c_?tzZc^`Xcfh1 zPm^bcySoS_Rj^K%IK{B3{)=eN^0zE-%XGX8-A_!UrqvGnebVgOs!RmC?}Clm0&Jx> zcae2+R(^21UC~~yMj@gwoVDEl@_>}z&Y73aBOZD|RMP>ir0nj|R*4;wVzi3n(WVbb zI246ujhPxNSRDQsGbQwBQY%#K5ZiI5Ln+2up}#SZO5x4`yW1P`=ujDz`7K*E*9^#Y zI`CCzhVKeDHK`*eTJwZ-?ELrS*>>W|vKN9HmG;+#h+k{IA`n8?4jD`|hr6R{M1DS5 zFh%0C>(|vL$T-|@StTMg&0NJ5;QA{&>)52RSW4~Oj0|h2|9xL!F0upoZqwmH`#Ib_ zxR}oW{KXkW#ctEcr>}5dl@wq6olkZ_9r^hOp_Qq`w!B_U5#PZjwzCj#%r=-%P{2qL zoUXNSbss)wBd1Vqgu6g{XDaKzcU7n;3LDhI^AH~l@`8k-|96vBN8b=1|1LRZOBhud z=l?f4<7)oz#~VG$k9}Bq*|H^Z>E>>^(5-Xjyu>F*3q4)N%vOav*EsOY{&e@qo!j}l z^1#|>P29s~LKYO{sZ#v?K9xfM_@t&Tn`2QRhm{WJEIjyM8{yZOgh zpw=$9!?4^nc3#Q75>LM^a7s{d4@|yJW&X+bXn8xZ#$9NNJFigjdj4W*fLMy5EmRJk zzP{^!=G{N6G6sL{To}ptKzYXhnCXpuWv9H~+&Pzh`OB)6KM6jePx;md3dh5;tnSHA z=ey{=P7@EuQrmxf_~D}m9#^mdZ$_^`>#L?#ue*}_KQ6$` z`bGm2vrL@zL+qG4*RWEef25^$s2`T1QSh-AsI0QQy}q_z!PMdF?}oI>Q6{;vV};4= z_yAf@z+z>Sp9e+Chu_wkY9u@lTJCq`A_>5hB*_1jP5Yqi=%Cija&2mB)qSk`sQ4@G_htBbaiDI zrB-8NW}_5>HI~2K96Dy?kn(Mi(y#J4qJ2CzCVNApZnwgn6i#A?UA+F=^0Lw#e;&Z*ILf99L$J54v-W?Nk)SGkD3^u2*1` zGgJhM5GC-%tN-=I%=e<=;^xHzgy1(+xGIAX{b9f|^m}h00mugdzU_$Fe^+x*2OC0k zD3a_8nC=M$Lj4@XO*MUeN$4*WV=U;x9LCat_Y(rXc3wKz`Ao>RK_nV6d5x9*pLcQO zCOaS)4Op4*X2F52%_qKj*5-YZlD0N6{Wb%Xcw*uU(x)v~Ft8DXijw(=qJJtRNuz$@ zf7=TG*ord*J|M%R6Bt4Zpa?2jTZcJ_j5ezv7$=ehWJ6t;6EonH`=5_yPrr+yk`3H` zbgYSY4DY0HktJ*qf+hia5?7e{z$^u_5dVn{s4_<(!`YGnA5ETkb!4`14+-&3hi-UmGyPKaiRNqJo=vUdVCZ?V!{A&ipI+d)c|%&rtt;kt?GYnYcyf}X+~)C~}+Ei4iq znh?1|F=*x)hT9{0eZzOOJ-zTq9X+fq;?%hSC^r7g9i~KMQR)97>n+2o+M+g4Ob}5} zkdhD(K~lOwQM$W9MY^OLR8lDc=@JnT>F$nAH-e;egLK0k>zwm_-@VWMaUMJeyGWbYyOt&}mDPQDRT@JUtYIHld|41w$XDSztLq2p5=RpU2ja9+-!(nlPvgyMA^= z(6fd>#Firo&rC>VWmvWrHR^$p?OF%3)E)dt_h3ftGMrS!wl9xkc^n`>>^e4?bSR|= z#N@r{jLzaI0RyL>${8O3R)L)Qh@-<7Ia~jp70`abvHpS+Y?IQ@H^5{otI9IiNv!`$ z8rsQPVGm3tZo*)J3OY=HZ_%LSg&BZ30H~nMg-g>5$zim#{-9MwgpYzqDk6f@fUX1@ zpJ2$t>;=tlg4QJyAgLj$UHFF(Di}Z9<(vzW>E2<_LeCW_<#UUZpqXA^&}v|=nNq5@ zG5CN@IyZ1b|FM-?aEtx@D>x49@1`yYQ7Cd|5DAL%yE%pUVQ(7n{Hnc3q~RWkQcR66 zG>2-H5cHZxyGsK69`W8udU}C@olE1+#Atne1nMPgRJ@Lf&~EGeuDt;7556b*qE{b1 zTClQP7sgP)A>}FfRA8_Q?nqBj!D0G5G5Q}r9)Oos7LUXEsWaeg)(+jV`iMjankGb? z0(CAhPXZui2;@aq{s2Jbw%aF$1aHHzW*fvV10d?5q@;Yp6-9!}(*c|*ml}*vsqg60 zm-dl>S9r+a`XNfEH*H~HN&u6J3$(AfxjpboVA_=onjDC9424}M0hBU7Ly#av$dd-A zJ;ESjj)u<8K;(%TqMrW_ki4Dvgr!44QP3ji=31(c%HP*L><2X0YTWH{zk)|GjxrQ;Bxpg{c#aN7VnDZ(*?~loikupKpIu<4>zo}JRwbbV?s7?$wk{$SIjmukX zB1JT&h_%*tbFp28+k=gX7v{bB^CB|8_j!=soWvTh|xzL$evuG`&_byozXMW@x~n+6EOg` zg>`B!8c-#a z_NZnTSq<9JmM4aQcOW4y{s%-lBs@0WAel4b=>{OX+NH=xC_YGYPvxjc<)THXOTGVU*N2n0g5?iK#S;O)@*v z3E$ce&(+K8qV%{<>rcsRhq)6EPRp^#=UU3$(Tz20pG7)+=a;E(OXk;7J8M{;I9cj1 z5zz6&Lds+`VJ<5wI2?pLblYk#_!_@0>lvL)7F+gz=?$mhc3h@JLl5z_IclFOGL07$ ztE;u1e)y;b>K#I>O;abA$XE02&%k^^YnEH9R2KECgm~zueWy+E>ag5pV>}Bw6;lzY zkn;2IplMgT;L*@@eN0V=*KB|CkiuIQrc!ln8Pt)H`^db2N~~aEWO*PC{5k6f*aBq} z(zeE|^gn)*?8%4qn3bIXc_j4VG)ZZqx7zZf=0|?2csU))vmx|pS%8I>Mp(>(6Zm5i z^}AvX7SzEd3~nq9C8Yog6#uaX@4bPd4Zk+jjCJ8X$Vof?yVq29y5OV$sNeN|*QRYdy^9u5s+8e@%0v43S^^yz(mov2(_w=51hz1d@ z3I^I3wD%WzTuBQe?Fq@`*!+BJeJsf`hcusF=Q`Afg+<`EOqi>nC!eiou+}lk;=HSF zo+b5T)7Y@8rAk~Oi-AEQCf3lEQD@BYPk@}SVzvq!)|$D=#Hv1i&#CX|&_Jc$lvLUE z@AWu{yC+rEV{Qx zHZH+LgoFdhUIZm2LvR$P>x&_`OI^D28mDDvXFkZu3m|?2pRrvK1qH`r%_3SCmlz&H z6O)X|$gR~Kt~zjB85;9xHDSeuM(+8-yF0w=aKwQdTpvil(O~NSuDF;3vem!BFn#3= zZpP28io4X3#%{E+e2z?+s@HJtSbGf)zJbK!rUbr=H?Fc#n#GUxwLVRin7@+m(akzP znel@Y!W9QKV(@7#nK$Jr9Ymwl1?(Wt$-7oY? zz7<2Op`tFsD=-TKy2G(|*V*w|YiA4x%)&7v+QTB~(fdm`S+Q=ZSlL-Z8-+vU?A_GF zZ8|94!-DPy1G`_|epX*CCpt+Ai-$UWC`BZV@iJgw%k=8iCGh+4`hxe*rA-nPVl`8} z1#@0734a6OpA1?hSP_yo(sL3EmD+;{gjsZE-l39iR!K0Op#&eP859^+mrwb?0-!U&E6B1f~$DIij=Qy86Cax zj?eKNxVk>k(E$v);}Y%yCMI&_Zam(Nerhwbwxkj5pU|;4!eWn=CvJl6`)$ZsG(%IS z%Ven50hFU)_Y=SZ7P!PKJwPEK0KWOt zwZ5r2buqfQc+V46)ZRZv&BP?T=<9Qn!n*{z>0S+J7$7O)HWaeZ(xiF_LXrt!g?Dgq zQ_f%RvI5kFM63$BA6p->M#ny~KH1-ksxf?ekSLwVc7N(#2jpp{sf4GkSoGzyxSjr{ z>5Ts)0Tek7-9_zkOxN3FU$tWjtelE_ijO>LOoqyCoCzKizWaV6p&jx`(Q0^L;CY6! zsr0Al1RySck?aZ$luQj9ybv6W%FBA6m?&s%eFv&X>~|w09q&H8Zf^gor~$~TM$qud z3HCSXn9(9jf$Q&B4NQ~eY0Kc6mADN)DG=bK{<#HWK$B7ep^_$ zE&w{ut|!Fk*Voqvk}>iC@G|-W**VOhdW6>lnTQN1Ifg=@TLVIx2t+rY0d?l>+dojc zfp^8{va(tX48q;^d1fh1t$_*&rJDG&TW5kR!NSS%R;z+`7~K(>z3F3qz!A8MOOTnU z{PyrE7iF%l!IF1=yx;*r6eX2LtuwA3v*zD#N(NI41r8*F9)VtB&yQATu3dL$p_7V3 z*hUAv&t@!}Z7=H2%tHpil@|Z(luFCttZm3Mr*I+Uf$CXFBKGKa#{o_T?W+V)@6AWq zef<5s#>=c@^*l*shklaMY7Sl(5O6m)r!(Wn!jhKHV8}0VxLViL6z{Tkoac~@u9iy% zwjVD!En=k$4I76xwZb1gFLzic4M$Q@NHCm@)by^00R&+H{T!H z<*X1;tFEazvlN|(jI<%f2_4XPgzX7g~FkgcQ1*)aEb+e?iuge=7-M|M1A~2PS z2^}O=fzWpC=0VSsJXbk~jhkr~z-eQJOOE9O7;yH=%3^yYUGtP!uu^-A@8>BlR*y8U zph|nu3}wZ>`p`RGY&6mx8`?d#n?j%U)6d0Fm)sR%qFG;!V7#_6@$HSzO+$V_P^u>7 zLX3QtOK5fOgDzDq&;;=K(>x^X%t=x$%%geg!`}1$LJ-IPof zgtV;w6jTF231j8{!A7+=+`IY@*RuNt=wEnUzp^}HUEt#S@vf}fz6Q>-XR{*(&2eGW znNLgy9i*i@^^O37c=7BWam6RoAzl+{#l-sG437)fT?8;pbsP!8`*pl3&9z0`DRvpxps8 zJGi`jE*fW3tL2m2ADW#>f9-T}*CEE+6z7m&^c0#{ByvOjQGIXAkzWnTW3s1Nz;(rm##)byKnRZ0yPSopZ5PZx4iTFv1YGCe^4fN)^&ZlNv=g$DxsLj>6R_A(7vbP1*T)u5aSJ|=>M{vY+eIxo0x-X6S zp#g?*4Th|*zX)z@xRh1W7xLpXnM;|jD2-1hhJ;&U}ya|K4(C{$}yK+D9@F7M1lVZlhhcDPrpy@}f_gY$7K<fJxzL+EX8u!URorIVeScl6p>&G#_3qLH!kH>9m8YO8 zu}laKkP_P6O*~^>U!Q!wb*!y3Ay0d1Jf&N)zH<3!=NBiYW-+l$r9%u)TCP=HhSGiK zzKf(V{binojhY)*yeb0kqtmJE^DL~dcL9Mp8bmYTyf_Ev8e(@1XbA|yEQvWgJ6XWA z1LJQ18Tv~m zRx#B;$>V&FKyX#)Z)z?$aSj}oLyVa^zT9Tm(X)^HMJD}$`qR@Ix{I|QjL%&Trh!GA zrCq9eR$nYnp|4D*w0r9dzw zMc{U-SG&+Er6;NgZLXbOf-{5X?H{pNF7kaBt1u^K0M?f#bq*q}r6oUs#7GD(*AtGR z1g4IPUYyDDrD-IA82x5_$2Ce-K)UdY{G>da^OI^ z)QO$rgGtnv+^A+g#tEkxS@oW?kCP57o|yl*O0NwAEiDGnPQdl*i@_LRn>?Q9Ric+# zfMSwcbNmUE4%bU zl_1Z_Lwi5awsy9WQTeedTTR5vW{M*;9zG)R15CE+J-Zq{SpJTV-Anm;r z(Tu-;pMc~=kE3KUoq=AQ2cL*Y;%NK#qI&bEd!VPKq7-IB`C<_v@MQpjRPa1!3_466 zkqLy&7f!!X@X+J2U%>-*10`58$|eZP!{=~p9T0cT%%3a%sj@hj^D@}-_p>BR@&fud zLfA=_e5EU;_E^VdQOApyCS3DubpG&_9?1YRdLUy!*@H5gND|L}<^5qv-0e-URDh8db9aY=NJ*5WpYt*tF!GDFz7xVW(3X8TgS85~>5X=orU?aTT^-uPRJ z8CHkXezoE!N*QS4;?jd7i@L&>F@1!qN$c|7zG{znELAKSp;2B(kg8a}MqRVA{)$HK z?eeDhu`5=(yvBXZh~ih3 zjeN7=s#0E3Z3g&jrzTU@Ex(4-krnc`Z0t z!xs2^9>7Rpu}Xv`)gF!hVmOlG*FxNPAk4sMj7M@}_W|ZSMK? zwVm%Wcht@eQ8h22<-JyyoJ9&S;NRMA(>2GLPLog3F|QP|oJvsO24l3=+oOU1CzmW` z19pT7ZN9grYI(=jE(d}uyu&Zyke~cn&0*%znj-d*L@<8uO8v&fOM{S1WT(^G(XK3u!`Lp`5T<>#9u zeFz6YG}Bt80LFFq|L+Hy5hy753BU^@0yl<+`a%9=L!W*okH-SKDAuvE{?wS`rp-!j zA#n(w-e zv)kZpoS}n9OZueMa@KDr=ID+mxgTGMTuA*^OeI?8^v+MJb~}<%!d1Gyx8$?=X+E0Z zaeE3X#ALWiB=W4H$gJluX7UA}pT1VcRw1LCLI(tU|M6N@*WiKV21j5RKh>Lhw>=*R zY*z}&sGO~SPKRaIaB3;u+9B;rFmp}_ESJZ_6w224bYgHJcAK;!@z(u^sL6+@L{A~; ztb63Z zx$lT7tH{(--mx1Lm^r`~a#;JD;_k|o5bvg8Bh7|VRD3{(nI?PfiWk56SDcxkW)y06 zcGfRFUF-eY8PyaDQ*ikbGM>lSx=Uo63}q?p2&=E|jZn3@k|LaIP4y|sNc{%;yNZv^ zieF5)K;t8c&+yM$ryio<$Ch~E$6s7h7%qPVoJ*t1_21N7$fW#K9BNw%?;HRUaIU?W zZt%%xHj5hWLJHT(Pf#7h<3#R6La&1{G0kZh>Lf+m^t`!IhO{rItF49hoG4 zw~wj^Sm1qBB}zr@&u%dB>@)ddlgn<+Gy@fS7RFczmWoByXYwqsrhv_5)1QMZ?obbEl&f+snwg@(oA|pjL zHR&W%buHQDppUxUn?@247zlnlzAk&LwpmVFQH$!PrgSbYNFR`V6Y9@iQv^x`Q|pza zv$HcO<`K>pOhA;u5)0$z%_|SU5)x4V6vYf$)ht4=mA(NfEAq91m6lCV)xzwyCht}p zr)TqNQio!N<-G+at?hQs(xu6uEw(h4rGU_A-cjTt|FW9=K3Q|o`U*xnzss$dRkS#p z%iJ_9r^*|FI2rR~;J(>nJ0ahfREy1!k7e7Ak+VHIC0F5l(P|EJQb`)bW1?~ifzKN& zqQ8`;Xl5E_jTNzUtj=0ZecP$?Kz`0PI9o1|=ATZ@N+u(mk6q-cLclxNyF`U_$ zRHB4z?k@JSiC8Yb#~7dw6Lvbh{zwKeFetuk9`Su&W?)bJ?WYY)k9>mew!%%mBf0<{0 z&BzaiYRSrUU?0=~uF_EZ$|eBz4@p1B5EW#Dz)h#2h39|(o;k<}D{Vrd77=n3&r`rR zGdUnp9MVhP&bHh_yG_RL54uA1+azdzAaMo~J{7^`84Wy;#gvut;M-ul!icc%Va!x2 zco9lPQ?%F$=_1=JI}@(yf4b5pnUJQZ+&_< zJgh*w5#2%?rhO~~OdF~~j$1Q=VPTdEl@13CC#7awi5nA(uPk*x@z}nOW?vq;Ih6$J z$KK-AVd*%B+w}Bep#SR_EhcmOTaT3{!{?WoNdV93Tzf=Hu3N4T4y6#iUJWpz$NzLE z?2-idbNd6;D~dUNab$0m21>wG>6l%Tot;&1?otRO_k4GUss!+TjMZVOV1ZQ~+qLsE zE#<6e4pd7Bc~6z|4z0>hJaFeH;oF}fi37(AV8n9(sRKgeLlK53_@INAA-CfO2@q4U zNVq?zh*6{LfQW2Kf2;k;zLx%Cxeq3>nn| z*I}gEl?#{x-=S4vHy^>#)6)Zw4RL8{x6GZ*sSAOw*xTvf8qt;M^xh{eGJ(>aK+Zfm zS@If9m8kpslxhLpl$yF!L&nH!hnc1Hc$kKsJTb?`tACVh_+iY`;Mi=F7DOa>g3p+t zUH>uSX3gv<=$bIPt1@F^V?RxuiYVJ;)%lU~vb3)&;pcW@9Wo1uDpgjnIiz+xDVjpl zWH)cx94j#`%61RHcN;Agx<^8yz9bM%Nym2CLn~^Y7?XfWx_Ns7gOB*D2NMIsyI77& zP=~}DcwV`p4+=|px;+5(^muoFxSvQU{~RQil7e_*aJ${-NZ^%8Rn5i*FNYLR<_u;I zM}a!DcDovX6eNX-d#kn7k0mxpFxOw2nmz+~Y2`!5db2J$wSB_f(@L8No*Z9M3aRzY&&7v1>Fr|Xi`+< zw%nTn((FEfmY|SU$WTU70+7s7DC~2zA z+t31ol^>o=n%^;?q}4sDaN#f&IIxvr5*+p=R{8nUGwE}*xU@D269=1~k!}&K?Csmy zfr0WuYLhu6hIB)=sIKviTBgqxq@%AjvhV)HoF8#rdT=ow*w1aHvlzQFMIRw)s+QZ` zTwCbr`BQDJV|m~{h_LIzX+l7>zGGROgy!zE8D zUajc094kG^cXd>7So_T9C+zx#dCJ@0|H{LMO<~c5QacNcfWUGC%^m!vFOiS}Zue;6 zuCK4*7uwJmeQ;F*SrY}5tPMkBsKCH~>Z3Su?C8tpzyyQvG^C3T3c3YJXc~{!Dj-e( zXkSgRW`QgZQi0HUZb3mokWwJ&P*F@eK;ml4mTJ8ROKlon6ejQQfy(tW>15iEybK}_ zK%0eD*VgtCS`;SjvacTNutFaz|G)vM1sW6~cS%XJCp{p&A}VogbQJg>tf+TW)s7+l znQD5!7gDBaI6MR$=t$BpMn99XMPPzqgs@zqypZEbHz|{-sA5~6!rAydy=VB}&bL2P zrY{=XlHHCsFC4?@?!^78dsV!B zum73pKWHX6yDb@E^N5KFtbtsE#6l^lZRgx5FIYK>!MEi`bnMEngy%Y>E~)gx>q{fv zo)-dgaAcE^!sonsfXaRK|Vqy>0$WSsQALmxu0{aJ2>=0HESck>~f3Rj( zRug2&-X!YnKCMCVA!SYJv#77R7b~fcdUUJmxM~8*P7kII8P0=&)9`llVw8yM)fi2e-1o z{(y-|M3i+qQ{n}x*^sBF+x_HlISLKFx7|FLTNd{=q=wuuA@Irb>WVXef9|@Yoys42 zo^|xOXG8r3{E#RO5hGG?gi)}tM1f3(y}n)wj9AWGxsz~?l5%cW_eZQU=~gj_1dQ6i zn0@bU3N%h{*VG@tX03dH$)L~x>hV#K0k*&){n4MhsluL&-F(S6`4u8==bZ1Q5X#XN z=mvqt(b>VrcYqZvjO=96U2<}E&WG3nd?xKy5Zr^w6uVR;!>cnFsM(8RvRqPK-(V@mxgo@=cOo{; z@PMKHg0?Qce}!prlvnD#H4+Jj|KunQGm_?b%5O4 z4T!$etDXQ8nATQtNSZ(%YM@}9Nkx6N9C?e=ccJ@wtVV%^I|s#m@T#c^Rb5d1{A7P| zJT+kO)bbKA)^`Fn4@RPjU6;*TJO$$`ZyXTv8{&PGI4}V<*?kY)421_j6;kQ!;}cAd zUlG7W-vDNJ*WKcE->#0SY3UQlz=O^Wlf$y+F#zHhcei_{L33nDa#4-lcRkJtT zPOS}t%PU;&5D>J%&DP!jZ@)xX=IR+nt~srCm2;)(4F9!dh*{sQ{OfXmB3c>4}oC-C$Brc8%W)xc*1 zk+D|yfICh!ybPqjJ*1)02M1!H)D2!ELWA8D$p%H-+O1FF(ftIXY$)fRu(5R%uhM~X zzJBb!xy7@Kn0`Y^8#;x>@MzwRJJ)^m1U9Nf4lLQNtzR8G^^llbbW3@}`yOaNH>Z!; z?po6X@#bSj&8l<$YtX`OqqngjY_9h|Xrp=WVPS1oL+poFRRo9~2bS3_!TP5bT>XGyR*|6BYIWgM)+S2OopFMh(>bx%U3{ zkqS4S0#)z3$Jx2_1PO(AFJ=YnfsDrko<}b>T9=16F|9;L{JqMqhBSBPj{bH}& z0@#(nzjd9iIg)usXjJZaw&^_KePHNx!S;VZMbk;x)M5>cRfMZD0uvulW`abl09?c# zxDR^KjsT&p?iL8=fq4V5_0Ql2e#_5>nK_a;38cjzH`(ABY=)K#4T5-J*9Uc>YR@HG zFx$z|t^PSl!v}jeU7r5!)CwkVV4HWSTj~rq{UaB93c84fZ|YmXUWM7(CnGB-nuibb zfLqCL8R|r!*v(ugc(TR@sUQuI)F2FfH?#*$O--L;V=qR5c^wh@8=|)_BW*o%GCR2A+4zk%*mmL<1$zn-L8KL< zrw?8T44Os$;9ZFLlR>8CQ)mcvOs_qZV(n(V&@OYkQJS7OXS!P~?MZ&$)raEwco{gu zUy4F77Pw{g0sa{wyD<(BNr6wnngs%JVC=@|rTpuW6b?R=@W_h+HJse@G(VwRoVzj}bynG3 zg$54@rfREyV%;(N#R7H)f`iP_NYrt&C_zRUh~Ru6B;`$I58q~0BmAeI`T7Q5|LWet zk@H~P%UiKc`?^LzKM;`_dofUQ0;3;*Df zG=5}v3C~{1|G3#D?HFIZnqO=dCtvU#8>oH@qs6;>BY?hU0F43k>B-RFtW90yk)1%& z1Ekzu2JIa+#N)CsyS!v4M&=A)+4>dWXfUhr4Ofj2gT=rG_gt9!iHHJ59A|j@rAzTV zp1~foDEW;xRxIbj5a%Qrz7n=Sdy_9LM?b{HT~Fx~FZHleTwhM-DX^gN)C*Ziy>uoa zjVYSa_vUE#iGJzoNTuV^+L+bhRQhTe>HUt5D<^l<({DGP?4th(QfOrPPRw0pfPOJv z(VW-zYwz3f)P>>pO6E;q(czg5?@zDhqF9~&a-qSL_bSx1$!fXxzA))TgwvGbkWI!A zvB@eGyt#%313PXzV@5A@tEZWJPJd(B;8^0jg6<)b|JuMmP`k0gr@k}MJy_Vs&1NqV z=u3H|T0{C)K|~U5cja(cUn@s?W`13Py1=kMtxNckG%aWw+<{=iFy$7m3NA)3UKDVVeG5kfe?5O*Ue_vqrCfW-t#aPa?&Qm3mDp=+MoN3j=@}&Q z9}`Xvtmj8L1a$~yyG(~nrheWkd{hBG7 zo=Z4r={;x7=X#VMF|=N+8=3azh)A_ylbH^|c#GY~bVw7T>u!w}-|r~5#ZJNk4^uxi zJ)IX_vCj^VuJu;zy`@w02!@q1UKLXRbuoxb5GZvn07-^_!6OwqClpN*OoGG)TgeEQ zSj!1RrLwmyvhgG4Dly1Yj1AY74JG$xn&x+pf|~rE`pLc$%tfwom=1zXNB*))8Il(S z{oEZqyca-I1(Sfq^2Nm2ogVUE`YzRpa)L+RP<$&C8p_!5k^+Ow?RYsITx#Rtw2lRq zu~L>g3#V123v653A9vr~X?QO}b{-USbHR3HO=AOvdL=HC`nr63c|Sv~P_-iIqQdF@ zPjDt0f1UC7D?Vckw-Nq^=Bm(ks+;=&=ZKo2gnIX?ZcIEFj0PP=8ujdVsqy`2-EjcR{QE5nIqW zc4L8$np#6u8&rOLg(?ESvR+YHqFQV=&)p^Qm@S8H5hS2p z(RF2X7f2gk?W)#8OLUs~XeXps_Vzsp17bO4^g^g4Fa}1E%^{|crVFAuJ=st zE(3*Tj)t|Q!fkh{)l03fBzf$pRGd}aBg%YWws>tisZ@%$46JaQ^TlUhPxs zE_0mUEsYxAZ?sEx7nh&Btc&`>sKqv+01y$!MQwVzW5v|6-Hms?TeAmnPmklaft}#4n3tpxf^^T-!i3-{EzpYbF~n(+LXh-YQ#(pts%VPqmY~K@~!h_9Zv>Xi>4I z=G68i@+cZN{i0Ll_fNGPVQn}5MJ8Q#A%WBNkO zS*5GZ#koD5s`+1a0~i;);xo-e0o_gp9zP^PT&F@^FJ$P4!WD4M&(W)u^}4PYasv*% zaGsamZ{_cGM4=iRy;9An%Ix+q=>@eq(n!Z_`I&x*(9@}8i6`Y#@!0QUjqR@)wuq6l zI{bOLBgh!buHUz!63^@Ed*(+OQ|R<6r+r^K;%8Ow_s3Tz$vQp?$>p!sdEjTYdzgC= zQ;$tKtZ~P5#iidRDvt!clg-igof4~ycZEg=2F^fN6#?T*?$uwn<`1T7sU9t`5-^d> zoNT58?7OU;0Ln~^F$H}A#H1t*ypUipQ4YD5V!LBFUW|T<7Iax>M*9iN**2OZ+jgvT$nT3j}^ZjmP3#J(zSy92nrVS zPlsQ=&%Ggb+;}==owBrJDmO{S+fXGpQWS&7IP-jffRFXnK9*r{QuONfnd27gc<~0i zb3WCqk&Mx=t7fVptu>X7tG}q6j^>@$#(6@B*h~xDoVjwed89wyoi7x~5eqpgGUxen z>;9x6WUwC2g(m{VG>B5ftx!M~h8pL{CxoycBm@Rv*B=^%kD%g3L?~aFliUS3K{Mrh zo3wq*srM@mPAUB#4E?! ztD9o$I*4YLa+pcCywmJ#^0nSv&py*9`1heSVX)OTFYmc5Fkqy3#1WKL=-)r{#TLyS z9;;MGUQY$<`juB5QHrHQJL8Ae=8Clzwv0-0Q+q`p=q>w}p6bCUo5o&#A#4Yx@J z8iJOLZ!g=iU*mrl!AojO!1(7hHDI495qYE8dLg9qyGJ{xSp!>_O}KLC?Rg_TOClRl zA-^k~;Ele>LG}a!&Oa9}v*hw0p9Hm3W#8x>{_*5ZCoP^xzTPxk9)FV4DNo%;c@Crt z#>ER39PcDDGO!3)zI^$DcXAJwi+3yaKW}AxVnU2b0qqo;;>Qn=}#9$b`0>9kg5 z#6O?sV_e*QdIDwxAJN8F0qdKzm>I(a z!Kqa4ycfAfYhN7wt8Gz<&A9M>3sh1}IUWTOH=;2!OP}fRzgOWUEfk9FDGy-%t_p9( z_Vd+19J;@gUrbKtb-a5RN~*8253R;XlY+m`Yv-dveb!9F+F|q^8IG$px=0px#fO2PcWWdle3BS6 zy3I=ipPMR`zg@wmtkE|5_fz}|Lwdm;v&r~U0shW0y31_0uKNZOy_Zcd{O@wI zTa5T$x)=_42KE``=Jc2h!^_c6EHJq&CyR`>{<+{1a8n|nxcBxfduPQ#ysv3aehsh2 zJq|mPp+KzK@#UWXgN(QAYdU9f@Y< zMYg>xHtHl3f_tfN9<4T9`|oq8Dv*;$dKN|BAR`;k8>ykC_7OPQ&o@DF{&T;!>3}?r zJYcxE;XB<88UMT^qCP7$!Fy*nilbZe?mUrKAY?W4mVQGERaCOQCBX}n!?VEMzwB1@ zx7@ybGa5N$84n3SK66K?Z$2$Xa`GK-pNI$S?39ZMK1$)pg@RnDil=me(h1 zgUaW}v)F!$0o0up2xL**BDq=VVM4~*7qdxd&sf3_pbiw zp(}hTbX0gD_|Iz&<^hlVz$3g~S2y~&67NdG9&bx6-^Xaa{Cx&^B-9xxD15DIV#I_$ zuqzLKFVcG~7`r3bC{SH?@iOkZdh+nrZ!SISzomL>8vN73$nTlNSwX_lPLFT_5&Xa3 z!(rB6sm-A1U5r0gaiu5y`;+O3HV3*0sJXgJEbgq0s(e@!O%oGU z*{n|*U%vd)I$cnaly}3GJusEa8z)B!cBUW7$ldT|eDlGNrj)USxo;KeY4*?EdMyoL z-J993jf8yt>T0+2;MTq3K4bPQ^}OgFc4Ci$5y9(!Yup3MZ#J%}uv3r)?E2y~*u%~G z5geH5{=@7ox@OJa%d`J1M6^vJETD>hn46o+!Up;b`k&cZu=B!&QT*!XuN_r-BA311=x5LW<}CGS2MnEEejh4Ibw%tph)st)d~l(Q=cm1wBMTqe z{*AVUi(CXfD*mw&vQGQGEHq!vd!_psbte_VlhDdh%qczOGHFWvJeSPI(a~rq%%F4F zl*c6By`rym{pO;^d4}5~@3;8dUzN*vVbu?XS{H{9|4J94zD&n5ygq&VhF$;vZ~@eW zA3S-XK*;ZM6V-rG{kvB2Z6fqB^5`wtzzS5|uldH=iv!8*l+;2Q&JZ|N5t9Q>eF!Z~7k^Fpo#yG9lo_K?%= z$~-P|W-OzENCKbUl|PrMPn3y1+YTO*mgAYuACWJ*r-Xl7v{ zaziLvwF7|d{&gfq6kMMkD}Kj7PI)-=uQ_8j)OR`=-Bp7@kImqEz*94?_ghMjerFa*h>IN?(B*iJW zdSCMyo(?x{5~UzRLha{|WMt}TGx)1}=A$(cI_Udj+2+WJ%dyat*l{OU*v%k+UFGXX zS(JB1II>r~A$i5BE=KEx(@^s8(rgnnGtH1|XAG8jktd)<_y9A(xC`6D=r8lRN7dT9?Ime478H$eG6bwbvLbX;4ghQ6oo}F zV_m)dS}hhF5}mNIcSlP3Pd|NE+IkARZvH)O|vZD%^z3ROWq zA|h@9C60QaC>0pV$~sDeEWZrWCImjwV?K4r6jP^DOT*ffOQ@Is=bSAoJtBwIY{LKlk?P7N-fI&w(+S6z&zp|QJJ+2U*E^B)9 zQ_o(znjPsLEtr}l(rkb&y*b_vMdzOEB^nP%9z8=N!_ zAG+}beb5}hH9mZK@=#=Ywf4fW9@XLOdI*(`0!HYw(eI6Se#W_td~y1KvC!kGo?dJ< zl?Yy|SD5mDfV&!L$e2@cPB5Vrs^xiaC8WZuGN z4Z0}AIS+VG&2Fb4uOt$1VTa)x5&;ZXz7I6a@R*sQ3hxFR9yj5bTAT)}l!g$X!*(*~w5Il+x@yXr~U?#XMP zCYhSPt|Lv+bS16LZ%8!zBTX?l_sE*V5QyEl_Z?*Sz#?^mfg$*K%>k3Ttg^BY{5ufN z0IbUnnA9FZvjzd|oa9pw{0e>so!}Pgv{Al@u(Dtp5JB1_&P}=5Fv)OYd}q_i>2FB$ zzRzQqlC{ycV0YKfu^dso*&)Gdjgz6MYkKmq?kR6lq_ljaKYMtSp+o?EmxT_CjP0(7 z0-8LYkNy3cijwc}8)P@%EhJf!x&4m26Jf;U8L6B9gE`DbU!SB&K1U#_d^+02hl*}2S9 zKawWwn4I#MKhE|na1)@w{G_?~{7!>P8xClzRq$(sLX2WL47D)r-100i{{B95Rdy*6 ztl=e%+jEh?_0H z5PlB42BtR}uPF++99OW;>HaOnE-QtHA3e)8O0BQMed?SYxq2b4^Jq=ZHx*-A6OL_V zscd~y{f#EpSHBcrZ!|3&kG!v*{Hi@<+G*!{IHYik5FO9uuPxCZQS%KwEsU+he_mfL z%UA)al(&c$IO5QX3J%Mw6ex}tf<7?D<1|QTA1pWQAr)7oCTh_4f6x1#y?iL>n<~Zc z)byDaj;AxcWn&74n~I`M#i3+|h-xwUUIC3rb%ViQJImH&57F@UwkSk0sVfc8+gI(i zFOYt9BtUa>z-JZeTKVkHpFMyfC1aaFlbbV5%+MUt!;`O zQO8A4w4;fNwCx1t<4|r&cicTW6j!t7P|)g0uM!k5bG#7rY6<*q+e6kVDQozG^CXgs zxZGoJTR%bxBh=BfT8%N|L!}cE`q57Pt}t$(Qiau)JX2!ex@vO8Wn#1N`%cwEq@->F|nMQ zM|>D2#kM3|G?>(Q`=-fMFzlR{Jwhn0t$lTZXSD09s7hU3lET*)1<;t3*|SY`AdXq? z0!h-?cTXjcHQfU@aj8uvf80^{)L>Zmx$%7d{ZbrQd%eYCCI95o+Dku71FhtX;160Q z3xmm17{pKf7sn@vQip6HC!ymqm(>*AkC_{=Gj|0*H>U}8cg>j{>6F{CUSyQ#r>D8X zB&~)Q>F<^*9p&*-zkZ^95ota_8|5858*Mj zIhIu=Jm)jI(2SFl#7#?UDxoW=l0%{=;?{Do`U_VfpK`AnGkzWJ2_hFMdC<2PS0 ze)Kv`E15HhTh#k=`2P8B(&6oisw$_h#&ENz&bOLq#B~eI1mr!JfG_pkdEQ-E3K+_mdl(s%&M4FDps(in(E6aC-5J=iyk@0cH{&+ zKSHiI+(y`>^;Bg79ODy5^p7=M>dLs)j6YXGQIn1P#&jLteE_`spKamTW7f8|`Y$)R zcuqR*#5$f}JGqQjic^wjl!tCEeJAke&Ddf);@*idNL>q;+n$QZ{JlMNUGfn9E1|7s zo+dZjf3Hgau+_U&99GQ`t4Dt2wvzV;#rENlA*sl%MLDr-LD9r20+TsDCXR#4 z3Tzx6kH{Sb3)hN_M+2!`Z})v`!;7me!6}(*m%vmL|9m4f6nT3Fr>`C7(=mc63%`F@ zZd+gIk|N=peu*7?`a+BNNSGTRUd4hVDF&yTvJl-T?E71pZTS(TE5x{&bBb55@Fr6E zi9q=^EAvOcD+zx5cn&%U-t~cB zq}4Nq`ku6$Wu*hDENowio;7joHVTWl7GjI7)nVJ=Y}AS78mdOTRutZ=H*YM9HTs5< z2oOsf8^6fGrRyI3p{740PKH^@BvI$@Hp`$F*{vw67OJBN!D=D5Nsseg_V83VZA8vn zWvF=5-4im@!WhHRyT3TK|IBiB7K&f45~&YAK_3e{U^9a3b@Qp3hVdso7Q)RM_xHME zxm`&9`;>F{jgAV6?0M!tF3J(7B{GJd|MZxUFaoo!L?uT;*{CC_99xfsci+z2IF2bQVfNF`1K&ypQ4 zID<-v(@M{_6oYSmx5GtU-3nol9{`V0vo-r~=oahVPhi{%dhjIAC9yp&7W3qw@%@!) zaEiVx!UH3oo>$T;{vmw>Kg%y=S3V-|eqaTs66<;+$JJjMfsWIt5V@9NDH)%$&<#BL z`HykGj(7GCa)ZB-q^jj+lv~a{df1d_qQrfEJ_No`<7VB&ahyJdX2XrE;&VEq9QOWj zQRkg><_z`3qK3-n=0~@8chd~J7UO3Gw%|{%n^(-o@IyB$A28>G6JfVpux7~n^;K3W zjUjzeswPz9NdAz7mXUXtK#iYl&sK_*#n=}krS4C$jK6qKE3GsBer%*j&*G{I2(YoQwD5EzRQ%1|3a;$r989nl!TP~ZDMOHos$c`L#tVbDpCbBe+t#EChpu{ zza#6}rP+bMxNN?u(o)gMy8IffUn420s31u7>ZjMrI?uU<$nx&({-rV^-}u6EJxseb zrVyoFb{i(_S%xdy`?Dc&Fs`Cm(;yv?F%_>J)={_8fW-_ z`1%icF8jX?9Hylu4Jo^U?46L&BD1nXi4a0UW=30N@2wJ&Y>|~wL{|35%3fvf^&IDQ z-S_j{_w)bV|KIC%{kksse$Vs#oS)Bo9`EB&j_~fV`Gu-M2^a0JcU22n6s>8(r7={B z0i)e-n0;+p|8&aGvj6rj*)JOM<-9&};mGeV8 zF5PpRq#Q_NG-BW$ocQ`+Z9U0)sM;z;hg`jEKTo3#{r>%z&z+OqvSlOAdbs2Y^;uH~ zN5?})+sd7y)RQ}k6H_%!+xs%Z+D(P;itiX5&p?sx!N}p#7s-!JE^12832sJ&~GgopvDYJpHZ~s&7TYY>W9}CD#RsLcQNXw=6MQ9t53hLET87A z4ieU{Zaio@TBLy(x%REpD-2>BLLikEu2oJK{^d2;n=VXt-;|L_yXTaX$*gO}mUdZ8{CgnOp0N zrh554QQ#DApySC^+#%9ma5Q*$q`yBT#qzJhu%Gs>U3`!mI8Q8qY3l(%$YhN?;oU2wKv-N(kqgjWWXbK3Ls zTB5Q!2tsF?cCuASYuyVFZ())BsCZA)>Yc$gmr8r@yC%LC9h9}*g>i}MTtO@IPq9h! z?+=nlrit#_H4M^*WNvBDr7_Tn&ar*>KKy&?cL$wlmIpcQT@kg^?b`EMzTM}t2~(u2YcqQ~ zgfIQnWR5kzb1EQVJSqwM%76WepgGMhM{)ENLPfpPriXY_7MNoe21BfQjv6TzE3Dop z*JBKk&kwuX-gYN7qTMSgQE94lPuJAcOE7c?9Xj=)ce2Pwr!f9b$2H#5fr_$+K`uPj zWTJ>e(UjQuV{JCq`g>b`j%swk&h1??nL?zx?5(4XeiL2Vp#nz1|D1put z-|0G-ogk%d=zm-w+sN^%Vyn)DQtpMn;+{oCj68*c-sH4vXzGjb6gHK!P1Xs}diReh z^l(I-&EbzpRZmhY+iFSFRrF3*ZBqb~d~i@flG=xsg(sFR+Z676bu&BO?_~-*{ECYF zcU!uaz5S*7*zcE68HgHYP7wIf_OsgjWL1VnEf5Fv@s@W)Tw$H=Md!SOA99+lAp`#) z(AlCxRz*`6&QLP@V?s*n)7WFT%c|L47L8-P^k?%H#(1NnyD`${%XvG z!P>q1L2Y-wx456V40K9{T48X+HqT`XNttT^H+VKR$v8Q`YIuE~FZ@g^pUs#q;nd05 zfd`A&=g#_aHMRPMr9lg?5VMZU8efCcCgsuVT~3orYqR7A6`o#(1q&xm#Dn7M4WyIVtQQFUniDOP<(hs?Ved?`saX&T|lW zvDibAAQv=7$vz;aKWr{Sof_wPCIyb#dLq;{ubVsDjP8z8a;H#+0(mfzACAfD?+LCv z**Nef>MX?@lSb|#>1Lw-)%7rZY3a_=>SXLCcYf@o-4s5oot|?Mq!)}LT77Ji z1My1X?O*PWi0Jjn4Tn$XAI~2zR;&H>Kb9tBnJ|ez_0U=_Kf;ly|2z6wa<|8q&7ZtyP9Rgy-x`vZD z%5o4QDWb$rM@A1So;$bO;)}?uv1Zy~ld>b1<-`VaJiU}nQ^=~E6~#Z__#^(qtovBw z>GzF)z&0~$%&t4P{W}NYV zly0DSfhkFc!$G!sU}&R?s&JP=Wk~Gh2$j@sO&yoqqcAxvkcP(VZ1_A~R2_c$2{ej} z<$1g?96uyz_Sq*Pn*D2ESzPr+1I~w)bYk|LUA#1(<{zX5WtmR0a;PiBLF;$uo2Nie zjP}0oe9U7_{;xCFxB(2SMLtR_X!Xkc0odf|-rnH`XHu=qZLyhEDe~q;E?vTof*wb1 zO$@7w>mFMd<9%$yJ}g45zbra`SE6S1x9Mm5L5+n!9Oh~kd$ltZ)D46r<;pyo1E7yp zccI!>De zcC{b;<{bxt@hT@{{HWJoE;_^{b%ng9lf}%eK0>!6>%NfLRD#}D8QZ^W&op#K!O+|H zl!1>NI>kW8(#%YuF<0cIa=OTRP;c+ z$DBj%!|(l%AD4j-_5R~Cw>jNfBZbo`qPV@`oa=LEb!P+qKDn!^CA5&DWA0tFDY3E` zqo1Ck!XzOf<*;Hxe>30vwafZzg`U*TeKBdatm;?XPiaJ+`D%5$JJX0lvSCR<`}~L? z!W|c1-}^ji3^%rySh>7;^l0h9><*j1V$a~n->W7HiFP?4!ueEXBwTczyDlo*UyG@- z(lS!gHL^B}3`l+b+DOfN(bkpJsSb_vQIT}tV_c=fQ>TkIy@!7Ubqq~PHHs;lw4Se2 z(El?sf892q^83H-xS1sSZ>Z8}AAeUx6as8B5mJEC)zAsLx@T{2jwkBn10-!V%oTy#-byhRvnydp6~x?D#z*vqs^)~Q*DPD<>C?Zb{N=IXG_AD!A0PbpeL8TaOxY5F`R zb9J3^Xvn?vi-|W)R92QOj?6QF$P%>5w|OS|pH>_@Z8NrC$05!-B|2W17Amtkj)`8m z4OjC?Wgb#=cE!HG_J;Axm+mVej~=_7NmKd$r6l!xZHDElKvR-~N61+R{>d&bhZa(a-<#Xj@{@O&Z?ducn+aeF=)X+TpOZ zwUNtQRm)ub5wC^x51!1_<3tXozq)Yxl2kLF#hGM51GzkdHSO%{pO`XR)95@5MN%(c znbkH>I5uc|__&dtW1XlS!;DOSV`Jkl<^T#7>OcChsD4KeQ}qy@rCiRD%0*EMke%J9H+59*xy)3-1BB?q#4<%lgXJ8S=^Y zzKaq2WIf#SV4TX}O9=nGZ~DZp+cYa?;qMo|GoIA^QV{7-LGkWt@U|W5XBMw_=UN{> zbv3%mN?7`iJ56IYQG^k8PvUa>Vh}m|HKWxA*;{qt9jh_&089T{8b|$4Fx2WBv}>c` z!#?Ep)BV|=nMUIC!#u;h)I!W?ra3>{=IDHIb#Zfh78C0TgSexDLQY-0 zqCD?We?TI6-o~CqKTI%$JF0K2c}Vr+QQ6H6>$bwxJ$qj;iHUiTh}nxjda<;6xgoWO zPCYfcv^lB7XU*%$OwF0uwogghxU{AFM>PjZ971Zc#Nuba+&})qsn!DkP+Iejxq;pi z#Rnt39rEf)?xUHy)Q#2IyKJn>9XU@{Ig-D=n7ZFPdgRjAJZ%xjx7+9MZpi3Hu~Tvn z)V$y5(wwW5_LL@nF8<@$cT|(a(8pa5$I4jn9u#XL zfBxKATH48WbLH&4F2{g37h|Uvxc&9i-Y{d_2a-NoU0G(D^<=B7Te+wuHyCDzuOfnc zfe#UdmA6v2_ES?!sjBXw5%M(X$`hgbB^u_F?gj)>+}U9u{0ElqSZ-J7 zM}wfybOX5tPwM?%Im7mD3dNzs+i{pZSPXt|w**tOaDS@99Dkj7jpUg~Np0GSiddyy zxm{y%rXHGM1LcZL4dE>XzRmI39T$8ZYdFo@=zo~mxb6O_%RW0I4hhCHy-F+@8y*hPyT$YRjYy?l zE>~ue%Fki*_RsrvjV>NLaQHb}*loFr(1X$@gBDi`9Uqc%DJexA6%|a5&UT`BcAuqz z!Pf&l;J zvU>^~F{Q7ezue9@`%-j#G+Ff3lYi;`Wfm~15Ap4A%V_pUGp*B_UHmM5h&TS3Y`G54 zXzi&011WX)7oOr6jyNOjHD>bnyf2?#zYPB*b>IR?Nz>B=ja0T#|7$5Nv=`p{WPOiB z|0J>vl7pWe=h=axRfrZ7xw)lWm-iar>mX7mWrtzVEXCqWtIG1O#<4((Zuw2dGn#VDxR%uZPekB?#To$1Nxk4xE49?qigwM$GU?ucR$310&AbPB=8y;L zL(7^3j3~DSX#~24A#1f={R-NI1WITB1#%SP(!x#hUPEf&Y{)mh(fQYn=PztA{zQ?J zK;tQBp@5K z;OYlWZqU6L&%)aZef|BUaOM{zf;v7uM*;faKuyEd*0%heZ%OEd)0(=a_HS3fgZmt^ zFiQSDBY!=#P~JB)PX3>}v6u%DcmfnIz*$qVRkrx^dx(zA)?a4y3k@}nCcA@Fh?t)z z0pOjGH3L^r&1^9-w}lWYVzCk&r&eo_7NF$PqV$p$zgqlXOI6xUDRo>zBHC*3M|(c& z2_FK!^tZdP3M?fe;JEO3dmq~gfEAFM#vx-Dn{QH?8*i;E*gFpzlJ+mo`O8aCm*h= zNpSGvFd_mZ$Mf?~chtXEIteWMS7864j1JKW5DMR)9a1DFZUNVV9H|qMXIoL)|9<7A zHYSu|CB!V#zgytMt;zrtYy5(^RF)2((GS6McdZ z0e#E*Ik(afs5|2C)Q`C5tjJV1Oa1e)<2PPalg3&>)KqV(w*+)8g#4doS4Ej@!GHb6 zykBV2t8d!);tQWyWMe-Bmgx3&+4aQT%(vZ3BlvP;#8}69UhUx#`GG_3Ie9Ywd`trS z3-^PM?QCTc*HvX_W7~zfDoh$Vtp3R%z5eO$VX(^tvZ=hl>FLOQRvP>tZ!O~ElXthl zUUwVu6%#YFA0&SB^Ung>0mv62z4~4$mQ^DqvhWp~*`@!q2sXf39DeEHLC~r~2WGd- zc*o9bg^o7e_Wk78uA}Xlr}FJ433-)1An^_HcGsr9QpmW}F8})&Sl0jX^zVIa%h9)* zxVh6$65w=3=eYyxEA#yzuK@yypxFdOh?mC8_>&guW5yVi#|p(^Pum0OQY}z-A9J0x zF_LwrsfCzW;hzpUh;O+?(Rw-l zghF7Hh^Y>^k=fNh8cVQ%V+lOIAXooEh{e))Weh|G{O9jnZpupo{z$-O&7PR60O>4( zx;wag1QxN)cLo0BjUXZjFF|$SM8of&cV2=OLKJ-B%~MX@C+Bs)#SJvz3yy zP7^lo8Q|$K3>Y4rnyr)U9~_jzOnHNiHfUUjq4l=5GMx)=~kogH{4xTx4#`?uN z7@!CN2g|-od3KXm3=Ej+#AZFaYGn-+|HIz6U;wG7$Qzh%^hFl=Uh5OPO1#2xVd1v! z1m=um|C}%47+n6>%KO>xInEEIy2u-Hasa4zzJ`e$GLOCN6AuiR*{^Ib4Ahh0%Sm_& z=MK(~HS6QiJn2OIVdpY$m+k(COW()mYDvtqwD5mUA#sEm-E;pvXjL_6(Nf>1ZVt59#5*BZB}9|*!TBu^gYddV*FU|>kfh(;MuN2 z6E<=uX#X`LIJC}8L;qzc_LtuExW5|*Lj~39KsX`}3}Gtm*hlvsAUjPZN-4~zXDCC( zco1Xma(2}?k%3SN8ifDyc+2Xcld1uF=p-Bo!d?uFiXD#XJt znRFNA&Oi!=O*HDt^4e@&5Wb89NIo@6DJ2A2fdJvMPJUTUImpOpA+fO7 zdmfyC7Cj&pT(GPMrv{ZFRwl8!Ou zV9ANu(%;lx(*N2SVyf6-cBmd=m~)Qe(b3U}7cwAcgo))c@DgGbMKCaZF{!yDf8+!O zQ+LW`B}zdW#rUEC`}|-a*dK7*GGLmv6>oR`f`CNmpU2sA{39IxsaV$CMzxV|a4In2 z_!J8umT^^C`5^GRlpN~S`Axluva^hw;z%a=Q6CBQEg%wtP7w+c+cTd6%8voV`LVSh1B z7T(W?tJ(}y92jd&t%EQm5dk{bHPEG@IG261in+V?4@6=F zK}`lwpx^7)#}rQHWsYw@$!DiyRTs}~RmbwIf(Iq)tx`&8Y1EB&<_Zw>DxjGm!M^@D z>YIf{*UC1lL;z>pm3JB!EYsy3uuGR>px(Qq`{AVclc!Hjpofa(jKficjk3hD(tY2M zD~NhkqI-;q=^3J(Ll_@5GvgF@oJ(7=ch8vx#t}3(_$aZBVEO4m)73Azxl93J6!p_T zvEHNbJac2suW%|eq1J&`cm?cZ7@z(Q(1np!WJrXBo;{Llq=J7iGjPW4;gNG*2M-;B zG}&H^HNvfmDufHVr={@{6h?T9wqnMJJ|s8S1OQJp#HiVW$?hm1oN@AZPwl;Q4+=6d zNgAmcm<>iyvE>{DU6wXhC+kNav@>u_+e$3{?dr#AJS8j~N)S#xs);z$|L2caQIRC# ztD2eX?m5<&yFjS+90v^%x}JA3r09w^R+4fb2dnw)#6$Cy&B8ZP^5{f@P)4P|-ZK2O zDG?Iwq2es=>4^dK_zsPod+w~?Lm?adbNrG^z%~U^zN33T=O+|)!8HAD*0@E;>%t?0 znPnFgfkrW)i0xBt>12YbU&31BRK()pw>!|3$^}m^%+DJlJXl&?jc_djj|zr#VWoTF z;xG{!9`?oi+b@yphxc!L<(LZL0U;0XEt)e`LacdGhX{T6aJRB!*>|V5I8nR4O z?l!%jt{9i8j(YW$m6A>QS*y-NF9=ywiATIrq1+B4HAFt=U`(oi4HCyjo`08x#My@n zoCp$x;C=}WW6&Z=@-NJSduK1LkSC!akM@TdhjrAI?{E|iic6>HE?=4UhN#lg)Nklr zcw;%H@x=ErybCb*c>s?Y9mL&p^sA0eg7ZKLF9OSg^D}_AQ!rglyZ0#XBVge(L4r-h zVaRt+Oxs?d?iYtmh1%Xqg$qF}2e2f7>y`apA!7 z&~S{aI%2Ir5M{6dGp&qa<6VQFT6I)lVc{$&qpl>ddb}N(cQk~sgdC#JAoyK&IHny> zOYRL)IRvN_fC2*ehd_%j%hE9H9{&FRa9eM)%zvpv9Lf#%tCb|!*!A8=7_dXsg5dDU zlP4I|NL;pqSG-YRUmsm|BsWl{;JCT4vJZne=GfmZv&kch5_Xtj5{=^iUfK1mzXDGh zbP%xa3-}dQ%(P71zfg;Mk>#I`(|dP3{r1a!U~akP?x&&OY&)J31YH?`tsh$r@_p9- zgL4Q)C76|6k9;InUcwE{0upWoSr*hhcAbNMdv(m}g!-j}-u;ks1N;owBPs}e?ST#Z zPADY)(f5e!%oLF{?IZv9L1$|Gd=!GxNB68eKce#*Syd#u1egHXMiN`U^Bm^wVakTl zq$Ug@NQ)puXc6<|l~_7Ucd)?k{M=lGZdVZsBD5{9s!HnJ!!^U1WWR|Kks*8S?no0s z8oYhTGE3UyrPf%#+&SzUQJ0gN%7fW&46OJ5UM#rJ-R@kjSQ#v8Gd9(tZ4oxSB#MoR z%0wJ^d=}WUDgvP6z>6^m!m(i1cj-?~PunNcBaVd9sv8g`j>W=F~Q43Bt(#&fjf<`sKrCp%#XLW=U6ZjlA74I3+!Ji;yS;bMHB*p zbF1EBQuqZz)(pPplm4jGJxy8**cyxmVPatr>C9W48GKnIicM#Obq3V$N%ZV^O6nzj zQCIgE!P0vS@u<=jzFUQkr*YXKcRLWA)WGKw84)(-Td-`{au!x_K}ZXC`%F5PkliDu zUI@N0anD-`(rAsNbdFA#2_Q=GbP`UXuvi_511ET_4o$+! z2;8YYs^c(t7`~t!LkO8SJ+y?QJHP$1vUvwXx0S*zX4kM^MCkauBifn87_oN1$N}!) z6dXZ@N@ZC;@BHnDlZA_)v0y~Pz|FnSen~`##>=X1=)^~NxU5Ys;{8PUZ&P3}?=A=< zB0pF78X?~=Ne~ zDFzg9YVbswn0_aXdR#${LtwHCfCg2$V9rWB*D^9F5vL_y66hczQzsG-SZZvs$L;Lh z2>Vr&wStgb{Sc4XUwS7C%ND9Pc|kZ96$glc?LsDl7+n;eC>FJjAzJg;`j>dI2qTFg zPr#`5XW>IT65=@Yz2YIX)rr{}Xs?Ey`g6$pd&~5`ojValrLDcpfr4z|mCFOg!P7$Y zC%<~rMI=l1Gs^|OhAQ5ID81j)$Ubw~*!tJ?#>Oolso?;LfjEMS4)>;!L~L z=k$YARPlnQEeb?z6o?d_O0`~!;eX}q@;d#=tP~+fqQxI(zpXc>#PTw8a&q=man4-n z<02*=U)Jsh^#5E3e=G`}N4=yMCfYB;1rXY~aOdY^>&5tMd*)`fEegx*dYmsK6M(1! zEe@s;0v*h~y)CYj8c_IOspSlfg}lexpP5EiiI^8TFXr&P9c13z=R}q7S!4R;mhYO* zg!j`uF4n2nTDAcFKKY#;5-{#T~q-)bL` zjZpK72nZBP4qq3ea?inZe+3;cHLJ%=#nJkK0RxaF75g*G6PlQCd?%0fZvHwunfAf^ z&!lVJc~LiZ<#(@k3vs^G{YJyg9^9s)>e+U>%ouBXNWSUDY5#BGNjJQ<$ND{F4t`nq z$m8`K%k}-~v`T_Idlo6WGhNMU-?S-)*3C|hL>De*oi@F*5H6B=Ll=?|(DiZMT&}TZpWODAL_GGjo4-(z?|6LR=K6mARGoso4qKV|EjmB*p?Wya?s5G6i3{CZ z9``bqmkf$GzQW=A`t#?0@N5w-VX9|y;|Sijt76;%)ffNgrQGjs9(o7THT;kG=A#l$!ZIf$ zBp5*5_ZTXus8;($MR_5P##t(9X`#ZmH|X7BQUZPwx(eWSBSl734CD6mvt@tlO~J=h z&kB-`TQIG&UU4#$d}mKrk?UsAp!Ss6zsvK_cT*gn9ee)IBWWMozji3G<*aT- zM#g?h%4d*S{#sQf-;n|FO`=cjQ|Cx? z(h=x9fSrCZ?-kQ~65RQ}-=4p6+(4&r|4fR`PKQ0)p189AXpMT@T8S5^cjpc`-nB^{ zd3*1B@ZbRn2{PD`rbIAO2l#=+7h%!~D0cu1dmd59&jJT)l47_8T?2!;jvvR~i~iU8 z5LdZaQ9x`hT!m`qwoO$^rsX_=={y-ZxhLRfLpb{w-9nk)Qc_Ys>+8?oxx+$ejDn@- zYflg3Id>_EQ>Vz{lQEn&m9wV*ZdLbBc3lyTTqy} zXl%^7d-raVb7Lc*)BgSUuXheg0XBD(rnVgTXW9P4t(;O$rue=65sK9PaN5^D-#-ra z$H!n&iGBT=1gy*7Q0wE@5CaeY@uA4__ zh(NUd%|*8(G)8Jf*#f%6pu_e-C_ECBa4Q^}4g!B~KYw2OG<0#wN)tgJ_Tp+@=5hP; z5J)CRNzNtFB+IP*AW9{Ui6*LNpuL@jm?(tG6|o&y*m2}G8XD{m7fd)hMqSs|KFP^B z5tNV*8bMedGGv~`z-jYB!WP+$)Y3Mw5dMya7McF7dhl7rKYm}^qD-+iI{%vy14<$ zvk8^BWjmt$IMR~*3%}s-YYka!I>zHkieYhiAB9Hi|%Sh06TyZ7wjb6$BMEFw};TucIL;$oEI;#R?X z8&H@O8~YS-<*mFTNO-ap%vqz`p*7nBZB%?7q0oi?Zd9x5k?oT_N=;3r3;}gM60Rpd zfBsBJO_O{7VdHLN8M;*ZXmIj~UEQJ>9UtRN?K}X1E?%=X-nRC3;+n9S*VA-KJv=-< z(0$M3&L}Xk_6d28Q=-G>yfRmU1S8FP)fUY9w^6{oZ)H^qONG zS7i?>Ux(=$KRqxu>Yx^NA5;5|b0f!ZUY3?FZEQRQd52V{^|4Fsc`gC=Sj6$#3(tKy zHBQ0qxIw6~xS?U@@bFkOUfN?~A7#0`dAs7Qi8kypUf!vo_~j1{0XARqX2s0Ez@iZ5 z4%U2Xr2ZuOXD5ywBY{jIUiLHS7eUG)n!~j<^%jIDpH%;x=`Gp1mr;Tax7lPb0VfID z@);CKxX26Sj3tm8M!_R{P61?$@bRy}q)j|1!kzbONx_`ac#kJ|cqkAwUc7gY8x;uV zhqvfcHd^6;QG;u)s6&1H^)=QX$^8v^5HI7k8B@KHZ%dAHIl%&o0~@PxcYg{LTDOC~ zr8UY+Lrzcc_jqk72eiV;Kw3>S$gxA*=jI+%N1UNRqE?J*qmJD|boiNt2)8XI9DP8h ztFlsxJzOx$ts58|Oouj5)~8Q#a&fmI98Z1Z$f>Tn+S>RFZ+fA`I0C5s#fukrY}+=4 zmBm>T=<4h=29r42hYD1w_$34gBa@R?5z9C}d?+9xp_3i$v9ro{sLl za$+0g)uSa{zm(2AfBw9_y*-SDg$3%FtfxQ_3qA;$Mc0jGBO%f!l$75nB$giGK*YwyF`Jv29i^f9K~#;9;NlnG zz&qDPoz=MKLeNyiDOaKqB7`|}_8LKj;hL?i-yzuU07)5k%H0sX zocst8O>%Pbb_|+#=i6PCv%qEMrlwv$eoSg^ZZ3i|yW7xqeoT#qmUb3lopKvuyd@L| zr?I@)^UBV*3hezLvjisOkZ6#%aulsEXk$r~Z{3Q4>N|@pgM`!dyViHn9moaY@&=kK z53H>GOPdGR5gN(_XNd>Nm7xup?7+>++UtA#qOqBo!jU6Kuv|ge+1YF8qrEe_5-nDV zt*3!d&On#GbNlf`X$A4BTD6P!2b3dS8NN z2iz?HuA~>%LfH7{G2+#tlJ^E;9oS(+V0<@F984eBAF=oXWM8$Y((z$$aC38KWn~SZ zt!v8&EOOepj_Pt z`dkn!t0t<_;RIWB7qE3A*&4s8(^6fT_RM*KJ_P%C_k&>R~D{M$W^m;r(0 zDU&SKkt4I{TqeLQ;wmGcRzmb&)op>|JNEk}f6tdMeLK7%Y)$mjGTt$w+mt*}h|0h< zykHdAm@0`97LMG?$_h419|Cb1e{6<~6?%a?du21y(tL@9EG!gPV?TV>X6zd(Oqw?f z*B5@FG{=VnVr*hk39CJW4NHgI4Znr04^3o>su{Iw*Ulk6gnX;I=65uymT{w*;Cmq+ z_pI$l!58nVuH`WjR1$dLcP>QLC_~JX{%mkcN{S#q|7|?-yM=fb4vts2>o}YeRGvx@ zhGSGluK$vj77ZRl0r2whu&{KS@s@%Iqy7`?4WhS{9EqATE2Z+f-Jgyi^h$$1J%pk1 zHvnnE)-79%4GpD0>5SYtAcW^u@Sr$5y9Z7au`!ZAeuQkqIZbh{TVEn^qT^xYA>#vW zG9S6cW}8AZA4Nq(l;V`9U-5kDMO{l6T3cGzum1$AJZLgA1Y=`kM`7`BwLD-=x(ggQ zxw%=ET^(~Q`=voh8Z9j?JvTk=H+K(4pGH*2QLLpL-+h#ll8JWD-IkAR^W;zg>UCpu zsUS{6TU#j1c7|1O>yQTUR~W4Pi63EgA_a`Rr8|&h8X(0F4G;f->Esb(SsuA6jV?03 zphY*PTo8)dBA}uYvKo-74duHS>(lT81s&w0apPE$dYm+TC);Awhi}^9sOzX)zI-`$ zJuV@kv8+rg{7hhr`DvUDC^a(4)~p7HApn1B;)MZ$FFkq;b0?I zV=`^|C<}2G_(W}%svqU|GTzl8$fN}U_h@f#G2u>x)Z05ctPojDAZx?V;}%cdDN)w*ol|{dD%O-tNZuUIPYcLc6N%E$PgnMTBz1)2xa>kfT{5C?(I1i zJ>1XZ;^X;wd3|BLaR!}+qwL-}sE#^eSW#{=@Jy;o$R`-a!`{|vCS)jC^`mfq1%ceY zqrB%#MK)K)d0bExQP!aW3kQ}jfRox4$Ac+oJUOTP_LVF9d>~aUE-v0~Mq17eIhu9x8=#io2 z!3{@LhrR(H8u~?>8)xU|=W|V4x!H7{UpNmsbp)MJsEh)8CfU{16<4XGxwRUeaujl) z{oj3H)B<3|UPBgP5>4yVJ=E0HP0h`N2$4gBgUg9#0)OXw&3s!f9N9435!j!9LMIMU z!ZF#Q;o(e#0(9V+PQ6vctylnP)mI!jK1kvr{BTK@xgmFw6k)tf={i>VS_PiyQ3Cixw*rlzJQuerFn<8j|tBZW(l zg@#8&$OrsF1+Af@BODdEX~Zz_uSm--C!&N_f$1Y-wp(8%s)+@rMjuJOXL# z{!cBV@IoU41K&{UoW>iU`1xil!i6}BRQRg;h6aD^3_Svn0lSWObPqTTrd(L0mXw>? zRKw^88*X(m`LQaDP(h2F7qWkGpFVwR%5Em`K@;Ghs`P)H3hPS*Rw~NM;C)i>Xm6Jd zAj|lsYjWpKIczK+!YFvXtjtXP^XEw`{YfnV9%`7q8ZGuFC@5%7>BxZt=b_2-!p%+K z?AZuH-^|a?&){bltU!<)Ga|$)SRp`srqKx{_DW54H4(oMuMah&ruuq4Ev;}>TSN+I zoXy~l5omJWym_-7@gHKG5cFa8Qcxri=07xmIiLeRML zk2XT@0q|wRP1-O1y-9WWFpw!;f^g^$E|^bnaC6^5#e?aol^Qv(x3@RG)CgFBhhPB% z>PW6GWPK;Km_0A8pfR*~u0m@31;Ndgs#ExG6;**Dq&dU^fDg;WX^T8<8vMW&_FC}F z86&_pv4z`#U-+QPUP(fbsM*=sNngG^i}HmS6o*cpIPq=~tyFmgy(o=_Aa!_)_lx&m zgY8AUR-BFFnlmN006@_xuvc=lLmr60=9uQ~qn{9optESdJYyJ*+B2{($Cq98t56RP zMRYnpkDHB0IDz+3>FK!_ky;p3Wc=%G$C?raPoEwHj5Q2L89A8Bn`>!6pWHyUF2K)E z1SNUUbL}UW{oC>B6+CE2y z{>iYgu*$3~!Fz3K=Y=YHl4pbB>{b`dwKO#?5r|b+RrMos3yX=VL$QmOmGvbOsbQE4 z+2ErZL{|h~T@FPyv>F#kA0RBoihDCkM#J7HC@2h|R%|eDU|~^*&Id0x820T0OG`hN zWkSI#Jw5$}gxy$^JJHf5Rsf(hcpsTpyBr)GveMFek?7VDlJUJIPajxY6U`Ar{uG4g zC==G;j*-)(!R|20@3lft#1I*~f%PK~0vnBVnuzGj%gSW;DBRszjVH(jp%ihWFtCWs z6Oh6fjIgHGq-uTgAUw3Ktq?YavA#ZGFe@9P(Rx31C9(q6Lg(}S^Hf*$^xjA_Dz_A0 z({z>b2fQs{%x6vVYf|=7mA{|gn>Q+G!Q6qX5H_l%6BQQzi9mODxGrRL5Ro*Ftu!qy zZA+U^NG_~o z0s_bexE$O-<;Rbl+<0e~9(5?H**VbWi~@f{r*sc>0e;p67^M%&~DY@Mx}Wjme7!c3B5m55rR^PMFTk5pHtHz0<&t03JOb0)B5G#zM+P6jej4U zlUm2~pKP=*LW^vNV|yAzmg*=a|_WJ$|F8ur70GWY+);YV%%axB8g_I8wPpIzBT#7n zpRRn3yL;~P3kW3QM%~>}Mav3!%E_lZIyT17&+l(gN%nAbMd{Gt!_2>w)YP_C16sn> zRfHBWQoDCqa;}}7P@utn5qmJ=JLiOO3Xv=iM8*6V5izrw|D2wg@sG2E_~qE>sBFLo zAno{IJxy;g5#@kH*V@X;H#l5qV!fGfhrhK30;2&&DVe#D$o)qK2jkK+E(a?Z9Nzuh ztZWAFR1n8mu@`28c%0-d*kVB_u4PDVN7bPH%10DVVM}maWb|;tiESdQe)BsbaD#v~ z?8L`#y$v=dTdUz44W0MNxUvG!>pEL zCp#zSJ5(4A4Ay6=ctq@{bTvgcHx`TH_*A+28_v=|w@wBIys}LK!$Vf&zdLyGvc0z7 zE#%a8i-W>kkmqzqM}ZKa`!$FGs!D;;e7A8yTkJ5k?uQ0O)QTF=*!CwnL8CbCe?BKy zlL%N3DYP%03$8XnFNu2p{$jWQYvkHl&@01Z#~GbCabl@wb0Y+9i->L9+{loEX2n}> zZaCwBn^y0E3+7193o&}MjCzgXpE6V%kc$(=rj!(JoK(XMW?5L_#M)*8;gj8%C1^PR z_QQ^&%K6-P1&RY(VjJ=Uo2#S6wfXtg<0QF!aEmn9?n8Flw{J%bt*@Zq8Cc{|{ug0E z6_u9k3<}9ez%%#}j$T~cROn(1h?3y1joE63ShbKH0XDOd{?4GoP2aU~l3 zfi0#oUWl^z4VD<>IPLplKAfn1bf7>P;`YyArGNn1g&g;Kk&8Hupz$NjC6O=}<*ecL z)xSQ!_4@Ve#hsmWcoOjGXK+SCnNxN%Cq4Z!3K}0jel*Yn?9I2ZkuXb}a1`%v6-j_e zIDPsskx{2*w894yP#iisI#_*PlqJ2af(CJ) z-%wvCu&wwC0zh(&l``my1{LCSVNp@JR(#VrC8YyE7BN3>PQAs5+D3*b+s!(Z_0`qY zp_2H9@{Yd#R+1pcGFEz@=ia@~uy=3*pCcjxb=Kt>_SY`i zpFbakvewblcPSaf>>nZY>4jb7=g}TBLY_t-zV!ju5C(b@zUK+fXz;in&b#(!#CYLn zEV!Q|*)4@@$-5#i|1>8jXTok66kGl`V3{#ytpV$K_?&Eh0eq-6NUGzS4-o2V1>qQMc?Vms<{$?JO3(mP~+7+^|&J8Chb z$ouSpw%P;a4?K2RD4)K8vnp~CPH`l0pAj0jV$HuJJR!Qr1QhJM4`VB9tk%^(fBw`1 z8qt)bLBZ%WMT)TW5rDLEbvw3i2LXYXayQC(&k5ux$rQW@LLM@Zpnk^|N`v3nJG_rr@TICbhWOAdOV7g24=%T7D(mg|XiuEdBK6l&j@0{Mad z&g<8&&zW}1w~8E_BqPy9i3(g}(ukbjC?^s_K(hBtOiZ*IIco6Ar%T8fU&9++&wIcI zbuIQTjwD3ei-K^MWW=d0FC!MVybL{ma6+e)3jgqyWirgWV`0H__UpTk3MiwJVvozl zBiDjDb-?Hnp20UN>ZVCvUEi}~=t>~J4ybIas@elzoH0zrd7aQpM=RwZd(du_(NeN% zedt74wD3B3`0y)ubO!Kt?a0dLN zU;}RYDSTTuazwTXboZ}A4ix_86bb9%&->=Xl3t9G46w9c;naTr{{0vWelw3qM$prF zi{vcB@s)>%|JX3-dxva_Xevs7t9nW83q5<|W%B}$Y~Rq( zonR#ZZYB1|q^eNrR(6JA#I;FH`+odL<3y0k%FfQ7LxD+6It(nlgcky5nIW1;;SVf& ziyss4dlZjJ0;|PQsJ=cZdJVnfCmP-;Q)1eS4^?Q49F^49AJ|-JEg~Vo>VG4~Z18gk zTGHY9pTYo5T-17Vt>VULP@%e8kd5<==C4CDv$JR@-N#`D82-wNyrNOk-R9O^KeN@ z%H*i2&JILkrHFFQAH=qi!fa!;{WY4n41-WsalfXP4gB)&@o^#wvl4iNyXqVr9&+eDu2`HQJo4 zeFX1?V93U#-z&5WiD-vJ&L9_9Wld^3z(J5^e(ma_C&<%re=jgcx|Nl$dT?NXfbX<@ zwmb=7EeCZB7Z(??a&WXGBO|&9OAj-!zh~G3Xt7}VlLIHEW$MJuB^@yFi>}S}aAOU% z?>g_Q7bbgru)9wR3m?U%Dn|Yf70^_^7Ii|L(F65<0wn~H6eGf+`zv!MY#XSt2hDx> zf!KlgZ=$~R(%YNBImS5N_-IC#q!2%r)*vc@3?R%K%6$ad=4#O2MrC#BxKwmEUttnv zA53DvkN{w8hg%x4rGKKNLWpjoq)*Vlvs{T}eQ1Q=9qG)SRdKctif3QfYj^pBHysN4 zvj;XdZ<3N;p{WWf=Yz0FG>k#Sy<lr@~Nq*sbXx|TNO;5STkfS$SD0wiHURMwU~2|uv>$Xurl4VRZilXTM8 zAG?Xz{v1i*=V7kGtsuu)!E5)N0AsZKp^R(>m_IoEJ|D@AV(bX!ug`_B|MZCAG*Z@8 zB~lWE>pLkZE+Z}^+)vo=B(t07P8`ALkv&>>l!W58rW2CafKPRZEBe90pJ4?E{(f|f z2>(U7`#IBD;1J=V34}R~sF|s$_C3v8R60636+eFXv$Vx+{z$nV;wBVDbnqei`4hx= zY}FkdS}aM3at#a&jK67U`5;&K0882VG`ih_rlTDA&n#J@Ru}RM( z+9J$k`<0!qF9pXn=3Dl^D6b{a*ViW|9bwqY@Sv>8axr2K2hozq=psAk{;&fRIK8;> zipt6o__;WHoEaY^?WhPT^`BJG&^UtE{T*erbf7ngsmY4Ex)7GXapRv6;}RIQd-qaa zbVh&wS_N%P?q4PvYV^c)%{ql`#whQ$rX)9uJ0M6j)ZdR-=WT3kJ;Fv$25}mwYCi^S z4=J zd%q?Iox@m4nJZUbBqb$%jQX6Hr!L|>Q!Z})-fLH!;aj1JMAQSMkqi)hA`Dd>MJSI4 z^@}5`_~nGUF}x$l(oPT{C$t+%kh>b1NTx2Pe3)}HY$Wy_20q_WqyRFt6OBh%kP%IdNqWrG?1AH4H?A!|Fms=g!&tCVD)o`(Ly$0 zbh}G`1NqRGjTKXpVib|<(OTKP>Wk#(4LSi0eeYspPr|HNV)DgHRE3s;z42ATZ*AmOfSz@TW?bze~)I8G+hwa{=jH!(sRfLC-P8D6JuYseeWS5ArDwQ z8t24t>NL?N|3-c2&{OzSnerF7-f?#J4>w}Hvz>y@q;jlxDb))vp6GyjfxZ1}{Ow`y zLx2DNU7E=JXHHnqyhC*;*cnj@%0zpBWqE-OZ5nk}?FE7Ep(ao=SmM&sQXrx{QNHH9 znMa96yfm&%NULH6}cqBsj*{VI**Y{YLot4!ScR#xDr8wNo>s#g2 zyWtxQCF_h)01f8Ugr<-Ia0^73*~CsXJOH8UMRs|VSNBoYjB0iYexzq+<_Vw(WPlD6 z=B~gY)9lGq<*68=*-v285$o~YZDK{ZQsF6uX#YG~r$ls!ZC|BsfbN(#zy*D1h@V47 zN9Yw z*a9X9H&n6bZXg9cKuzsVl>cO8WlIo@XO-t*L=YMFNd$M03cYV_jV>I$V030y);BUn zr$=xo#MC?y4q%Rj$O(})z6MZ+$R}|=iOZvME482^NpcjL7s1>&J*KAagY4$^x0eSp zVq(_O_9g-~BDnSUKk+A?MDW)YEiD=po4nA=>_xE%t0RCCBv1Q?PQ0gd+z=pGZ_syD zUP4}#4p<((?u3BAAq2F~fyh7`I?GP09mVs!28 z?I^Qv!-{^;6SJrJk@9z|S|cCKk>^pqEy(o<{{s}c_TQ9hWM6%_%uz`fF}N70VC(?+ zQF@FF&84SEpC`KVk09SXim9)r%`ME@p9>2Mp@z#yfWg7Ua9&2{C8rlL`N-hidyDV=G%*X#~IAH}jsMR3!iZY3j_wsZQHCZqS%Yr)92E zXDG$AjW%z_AS#kd<-9_XXkpB;wMm(%rbW|YLZeN+Qk^W5(VRE3*QiXqu1I6eC^<2w zn1~XI_w&qO^FNoL-|uDZUlB93Ee=v%q({L5 zFAY@wIe_cGkt$AgenXTQ?0hbbyA|(8!T9pmCR$ss``UQw32tNFfOH85Jp#_5L$C~8 zscPplu+5$>-uiER86?5 zP&D#_=|!TtRsuu>Ii*e~Y>5sMnUs`tZ8`YA2I~&twYe2-G^ygXfriAT=aAsymJy!_ zruLo`)rzt+66KVABGTCxH)x;|TN6JJS*_wiR8yZGm%VF{>~24JAlVoCp}L4}jLCT) zhU?Ap`mx>x(qS|$;v$}RWr9r1EaoPJ}0_zayKr#5O2jPSAN=Ic**3nGR?!?6VsldznD*wYC% z5myrGhZ$G`bu_W^j%OotSvPh@xBI<+p(Y8A0D?hF)}Qxtmn8ORQFe=52PTT@KS-wB zw3}itI4+olLvYUG8Y4ERrc8O@Pc}+chUB@p7*{C9LQG_Xs#xw)!Hj|kaDU~g`S+7_-0${Fj}+R*!3WU^@E6Et_IvBi>9 zMwDM{oo!=A&t28Nn0Kg^){r2!h9*uXbv}*;DsbK^;2e3`aXH-*PQUIoZxfjRUY5gz9qF_s- zCTQOQ@R{jfc~tlrv%RA4$7+50nVJ7x8p?m6&7(L-)hr>8ZJ*a;6rgbY2wYeCk(+eV z(b18P9V@h2)6-r4e^0!b{<(RBqL!nD+}tw$a$QZ$Y*Y}-^ zIjS?R=8|VK+ahy_&Y_ZEnNN=lc&-diK0takICv$tw1V!2-KM9Xi&Ms(;EZ(KxH*P~ z`yef1490qTdX^Gcd{|$99$ks4K-1s^rWus$jTy68IHk^=t0*koPq03^{?=};$zgLi zdsE`n4GEJ88Pn)wQCI?1GVf9p^tVP z?N?cXss~}X%y{fYrCbUV z$qO#8t#xm%B?WLsxRY>I2B6x)&4{(Byrb`!n;2Ym?X2poy|v^A=77M=I?ulxTm}bP zS>BU+r5mmJI#voEHcn!Z$JMz!EWE5;$_k2od0ClL?5*AfZ54-aV=!GhGcheKtp~sC z>9tP&J;}qDr5&{iI5q_l7ZXw9kt2iA7}b2lu-fVIkvaMb-*BKcr6G=P#dVwAI+@Hc zJ24b5BOV|*48V3f>-D!aa1S7#Z^3L~ns1bGvj@cX=+|Gr<9CEpMbBi7p@C`krr#vP zoQ_k(q;I_4W|h1+nAtCD8%)GpA#HMYFR?m^xb_B3cSiM3D@o;uO@$K&&x2fl6cWj1 z3^l@7N0)Mb_6AKn!)YT=P+D&FG{+C=T;gAnpPc-?SX1w}n$`90rL;6vY}d?~V$6Yz zizi;|W8G+pUHbA_|7jlK6_m5aO{XMtAoX|@!tCg`NuxX38|{RK#tio)fFoU=Flopo zuC+BQPIbv~EGe?HEpTAQn*~e`d-%w0vKb1%>H10RlI@@nsaNijwx*T7qG_wRNn_T* zM7J1&*E!V1ZEs@2-`XfA&GarPWu63^Mi@ke;)?D&cU}$Zijv-N?w4O)<@1BvXBtEU zJj4xJ*pIzWJyCY%0I1bou0`2?avfvw;;pXdAj%8V3hvaXy(rXs9LPt2xWK?`Ra(B@=D1<(-P}&K zUbGA1Fwvrqs(e>_aDf$$7CnrYq|on9AE?rwTKhWSn{U1c?iYt{$v?v2he7CxSz(=5 z1AT9q!t!e4e-izzGQlr@FfVc>fZWc`3#SU_=QJQM zd|BxJQ)GtvqM$uu!iuwq_wv5q-ke=)`CWXGGIO%(;KtKR8eyaP|q$`X8vJm zVxo%=k2uV2w)2QlqxNI)Yc^B0zY-%qW<#qanHKc@k%bum0=M6#=rnl3(AZd;x42nl zoW+VjBq@6NpvX}%?}A?62WBr-^*(CTE5--(!PBoauUJ5Ko4P@JhXa|)32h_&Y@&@3 z-4Hy0;{E2{ETdhWBdpeIS85`~^s=>@UoB_%H0&LS03M0-B4b&=Uom^U z`*$|dZWTVecMPzo!*L9b-9&^V-D>T?uJQBt=(62_WnEEEKI*)Yyt7+mB(dYgeyNLkjE~S213DIG*(ToR2r=_)8lvrb5B8K(OWGJ28ELw+H9gjRj*~xgrG7v= zQnj?)xpPPBNqA>eNc6Y%O=1ocx#nSqY$sYxa7dIH!iy<{huXDcaGMI^#U`_MAPo-^dS$^^Tzef a4>JlVO|=}%RizplOYK)K`q_5<{{I8xh!!CL literal 0 HcmV?d00001 diff --git a/docs/_build/html/_images/nupic.radar.predicted.14.month.requests.png b/docs/_build/html/_images/nupic.radar.predicted.14.month.requests.png new file mode 100644 index 0000000000000000000000000000000000000000..ccff254fdb26e48ac0c001063f21c36f402ec9be GIT binary patch literal 31030 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

<&PaE4i2%D65-Yl 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 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png b/docs/_build/html/_images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png new file mode 100644 index 0000000000000000000000000000000000000000..02f3a80e95508d07df292c3ce9ee9aa6d73509e2 GIT binary patch literal 79941 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%0D|l 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

~8WNu9lkV-BM@k)_kKmbLGkk9qF2;!hWI+6V! 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 U^Onrs7=2YqP8CrqV;cN_09I)9umAu6 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_images/radar.real.14.month.requests.png b/docs/_build/html/_images/radar.real.14.month.requests.png new file mode 100644 index 0000000000000000000000000000000000000000..33401d6e2b39f1301016bd89c7e4f780addf37d9 GIT binary patch literal 37600 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 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_modules/algorithm_exceptions.html b/docs/_build/html/_modules/algorithm_exceptions.html new file mode 100644 index 00000000..3dea56ea --- /dev/null +++ b/docs/_build/html/_modules/algorithm_exceptions.html @@ -0,0 +1,231 @@ + + + + + + + + + + + algorithm_exceptions — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for algorithm_exceptions

+
[docs]class TooShort(Exception): + pass
+ + +
[docs]class Stale(Exception): + pass
+ + +
[docs]class Boring(Exception): + pass
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer/agent.html b/docs/_build/html/_modules/analyzer/agent.html new file mode 100644 index 00000000..ea455182 --- /dev/null +++ b/docs/_build/html/_modules/analyzer/agent.html @@ -0,0 +1,334 @@ + + + + + + + + + + + analyzer.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer.agent

+import logging
+import sys
+import traceback
+from os import getpid
+from os.path import dirname, abspath, isdir
+from daemon import runner
+from time import sleep, time
+
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+import settings
+
+from analyzer import Analyzer
+
+skyline_app = 'analyzer'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+
+
[docs]class AnalyzerAgent(): + """ + The AnalyzerAgent class does the follow: + + ensures that the required OS resources as defined by the various settings + are available for the app. + """ + + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + logger.info('agent starting skyline %s' % skyline_app) + Analyzer(getpid()).start() + + while 1: + sleep(100)
+ + +
[docs]def run(): + """ + Check that all the `ALGORITHMS` can be run. + + Start the AnalyzerAgent. + + Start the logger. + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(256, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + # Make sure we can run all the algorithms + try: + # from analyzer import algorithms + import algorithms + logger.info('Testing algorithms') + timeseries = map(list, zip(map(float, range(int(time()) - 86400, int(time()) + 1)), [1] * 86401)) + # ensemble = [globals()[algorithm](timeseries) for algorithm in settings.ALGORITHMS] + ensemble = [getattr(algorithms, algorithm)(timeseries) for algorithm in settings.ALGORITHMS] + except KeyError as e: + print ('Algorithm %s deprecated or not defined; check settings.ALGORITHMS' % e) + sys.exit(1) + except Exception as e: + print ('Algorithm test run failed.') + traceback.print_exc() + sys.exit(1) + + logger.info('Tested algorithms') + + analyzer = AnalyzerAgent() + + logger.info('starting analyzer.run') + + memory_handler.flush + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + analyzer.run() + else: + daemon_runner = runner.DaemonRunner(analyzer) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action() + + logger.info('stopping analyzer') + memory_handler.flush
+ +if __name__ == '__main__': + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer/alerters.html b/docs/_build/html/_modules/analyzer/alerters.html new file mode 100644 index 00000000..2376253e --- /dev/null +++ b/docs/_build/html/_modules/analyzer/alerters.html @@ -0,0 +1,440 @@ + + + + + + + + + + + analyzer.alerters — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer.alerters

+from smtplib import SMTP
+import alerters
+try:
+    import urllib2
+except ImportError:
+    import urllib.request
+    import urllib.error
+from requests.utils import quote
+
+import os.path
+import sys
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+python_version = int(sys.version_info[0])
+if python_version == 2:
+    from email.MIMEMultipart import MIMEMultipart
+    from email.MIMEText import MIMEText
+    from email.MIMEImage import MIMEImage
+if python_version == 3:
+    from email.mime.multipart import MIMEMultipart
+    from email.mime.text import MIMEText
+    from email.mime.image import MIMEImage
+
+import settings
+
+"""
+Create any alerter you want here. The function will be invoked from trigger_alert.
+Two arguments will be passed, both of them tuples: alert and metric.
+
+alert: the tuple specified in your settings:
+    alert[0]: The matched substring of the anomalous metric
+    alert[1]: the name of the strategy being used to alert
+    alert[2]: The timeout of the alert that was triggered
+metric: information about the anomaly itself
+    metric[0]: the anomalous value
+    metric[1]: The full name of the anomalous metric
+"""
+
+# FULL_DURATION to hours so that analyzer surfaces the relevant timeseries data
+# in the graph
+try:
+    full_duration_seconds = int(settings.FULL_DURATION)
+except:
+    full_duration_seconds = 86400
+full_duration_in_hours = full_duration_seconds / 60 / 60
+
+
+
[docs]def alert_smtp(alert, metric): + """ + Called by :func:`~trigger_alert` and sends an alert via smtp to the + recipients that are configured for the metric. + + """ + + # FULL_DURATION to hours so that analyzer surfaces the relevant timeseries data + # in the graph + full_duration_in_hours = int(settings.FULL_DURATION) / 3600 + + # For backwards compatibility + if '@' in alert[1]: + sender = settings.ALERT_SENDER + recipient = alert[1] + else: + sender = settings.SMTP_OPTS['sender'] + recipients = settings.SMTP_OPTS['recipients'][alert[0]] + + # Backwards compatibility + if type(recipients) is str: + recipients = [recipients] + + unencoded_graph_title = 'Skyline Analyzer - ALERT at %s hours %s - %s' % (full_duration_in_hours, metric[1], metric[0]) + graph_title_string = quote(unencoded_graph_title, safe='') + graph_title = '&title=%s' % graph_title_string + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shours&target=cactiStyle(%s)%s%s&colorList=orange' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, + settings.GRAPHITE_PORT, full_duration_in_hours, metric[1], + settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + else: + link = '%s://%s/render/?from=-%shours&target=cactiStyle(%s)%s%s&colorList=orange' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, + full_duration_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, + graph_title) + + content_id = metric[1] + image_data = None + if settings.SMTP_OPTS.get('embed-images'): + try: + image_data = urllib2.urlopen(link).read() + except urllib2.URLError: + image_data = None + + # If we failed to get the image or if it was explicitly disabled, + # use the image URL instead of the content. + if image_data is None: + img_tag = '<img src="%s"/>' % link + else: + img_tag = '<img src="cid:%s"/>' % content_id + + body = 'Skyline Analyzer alert <br> Anomalous value: %s <br> Next alert in: %s seconds <br> <a href="%s">%s</a>' % (metric[0], alert[2], link, img_tag) + + for recipient in recipients: + msg = MIMEMultipart('alternative') + msg['Subject'] = '[Skyline alert] - Analyzer ALERT - ' + metric[1] + msg['From'] = sender + msg['To'] = recipient + + msg.attach(MIMEText(body, 'html')) + if image_data is not None: + msg_attachment = MIMEImage(image_data) + msg_attachment.add_header('Content-ID', '<%s>' % content_id) + msg.attach(msg_attachment) + + s = SMTP('127.0.0.1') + s.sendmail(sender, recipient, msg.as_string()) + s.quit()
+ + +
[docs]def alert_pagerduty(alert, metric): + """ + Called by :func:`~trigger_alert` and sends an alert via PagerDuty + """ + if settings.PAGERDUTY_ENABLED: + import pygerduty + pager = pygerduty.PagerDuty(settings.PAGERDUTY_OPTS['subdomain'], settings.PAGERDUTY_OPTS['auth_token']) + pager.trigger_incident(settings.PAGERDUTY_OPTS['key'], "Anomalous metric: %s (value: %s)" % (metric[1], metric[0])) + else: + pagerduty_not_enabled = True
+ + +
[docs]def alert_hipchat(alert, metric): + """ + Called by :func:`~trigger_alert` and sends an alert the hipchat room that is + configured in settings.py. + """ + + if settings.HIPCHAT_ENABLED: + sender = settings.HIPCHAT_OPTS['sender'] + import hipchat + hipster = hipchat.HipChat(token=settings.HIPCHAT_OPTS['auth_token']) + rooms = settings.HIPCHAT_OPTS['rooms'][alert[0]] + + unencoded_graph_title = 'Skyline Analyzer - ALERT at %s hours %s - %s' % (full_duration_in_hours, metric[1], metric[0]) + graph_title_string = quote(unencoded_graph_title, safe='') + graph_title = '&title=%s' % graph_title_string + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shours&target=cactiStyle(%s)%s%s&colorList=orange' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, + settings.GRAPHITE_PORT, full_duration_in_hours, metric[1], + settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + else: + link = '%s://%s/render/?from=-%shours&target=cactiStyle(%s)%s%s&colorList=orange' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, + full_duration_in_hours, metric[1], + settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + embed_graph = "<a href='" + link + "'><img height='308' src='" + link + "'>" + metric[1] + "</a>" + + for room in rooms: + message = '%s - Analyzer - anomalous metric: %s (value: %s) at %s hours %s' % ( + sender, metric[1], metric[0], full_duration_in_hours, embed_graph) + hipchat_color = settings.HIPCHAT_OPTS['color'] + hipster.method( + 'rooms/message', method='POST', + parameters={'room_id': room, 'from': 'Skyline', 'color': hipchat_color, 'message': message}) + else: + hipchat_not_enabled = True
+ + +
[docs]def alert_syslog(alert, metric): + """ + Called by :func:`~trigger_alert` and log anomalies to syslog. + + """ + if settings.SYSLOG_ENABLED: + import sys + import syslog + syslog_ident = settings.SYSLOG_OPTS['ident'] + message = str('Analyzer - Anomalous metric: %s (value: %s)' % (metric[1], metric[0])) + if sys.version_info[:2] == (2, 6): + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + elif sys.version_info[:2] == (2, 7): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + elif sys.version_info[:1] == (3): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + else: + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + syslog.syslog(4, message) + else: + syslog_not_enabled = True
+ + +
[docs]def trigger_alert(alert, metric): + """ + Called by :class:`~skyline.skyline.Analyzer.run` to trigger an alert, + Analyzer passes two arguments, both of them tuples. The alerting strategy + is determined and the approriate alert def is then called and passed the + tuples. + + :param alert: + The alert tuple specified in settings.py.\n + alert[0]: The matched substring of the anomalous metric\n + alert[1]: the name of the strategy being used to alert\n + alert[2]: The timeout of the alert that was triggered\n + :param meric: + The metric tuple.\n + metric[0]: the anomalous value + metric[1]: The full name of the anomalous metric + + """ + + if '@' in alert[1]: + strategy = 'alert_smtp' + else: + strategy = 'alert_%s' % alert[1] + + getattr(alerters, strategy)(alert, metric)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer/algorithms.html b/docs/_build/html/_modules/analyzer/algorithms.html new file mode 100644 index 00000000..a0680f78 --- /dev/null +++ b/docs/_build/html/_modules/analyzer/algorithms.html @@ -0,0 +1,807 @@ + + + + + + + + + + + analyzer.algorithms — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer.algorithms

+import pandas
+import numpy as np
+import scipy
+import statsmodels.api as sm
+import traceback
+import logging
+from time import time
+import os.path
+import sys
+from os import getpid
+
+from timeit import default_timer as timer
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+from settings import (
+    ALGORITHMS,
+    CONSENSUS,
+    FULL_DURATION,
+    MAX_TOLERABLE_BOREDOM,
+    MIN_TOLERABLE_LENGTH,
+    STALE_PERIOD,
+    REDIS_SOCKET_PATH,
+    ENABLE_SECOND_ORDER,
+    BOREDOM_SET_SIZE,
+    PANDAS_VERSION,
+    RUN_OPTIMIZED_WORKFLOW,
+    SKYLINE_TMP_DIR,
+    ENABLE_ALGORITHM_RUN_METRICS,
+    ENABLE_ALL_ALGORITHMS_RUN_METRICS,
+)
+
+from algorithm_exceptions import *
+
+skyline_app = 'analyzer'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+
+if ENABLE_SECOND_ORDER:
+    from redis import StrictRedis
+    from msgpack import unpackb, packb
+    redis_conn = StrictRedis(unix_socket_path=REDIS_SOCKET_PATH)
+
+try:
+    send_algorithm_run_metrics = ENABLE_ALGORITHM_RUN_METRICS
+except:
+    send_algorithm_run_metrics = False
+
+
+"""
+This is no man's land. Do anything you want in here,
+as long as you return a boolean that determines whether the input timeseries is
+anomalous or not.
+
+The key here is to return a True or False boolean.
+
+You should use the pythonic except mechanism to ensure any excpetions do not
+cause things to halt and the record_algorithm_error utility can be used to
+sample any algorithm errors to log.
+
+To add an algorithm, define it here, and add its name to settings.ALGORITHMS.
+"""
+
+
+
[docs]def tail_avg(timeseries): + """ + This is a utility function used to calculate the average of the last three + datapoints in the series as a measure, instead of just the last datapoint. + It reduces noise, but it also reduces sensitivity and increases the delay + to detection. + """ + try: + t = (timeseries[-1][1] + timeseries[-2][1] + timeseries[-3][1]) / 3 + return t + except IndexError: + return timeseries[-1][1]
+ + +
[docs]def median_absolute_deviation(timeseries): + """ + A timeseries is anomalous if the deviation of its latest datapoint with + respect to the median is X times larger than the median of deviations. + """ + # logger.info('Running ' + str(get_function_name())) + try: + series = pandas.Series([x[1] for x in timeseries]) + median = series.median() + demedianed = np.abs(series - median) + median_deviation = demedianed.median() + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + + # The test statistic is infinite when the median is zero, + # so it becomes super sensitive. We play it safe and skip when this happens. + if median_deviation == 0: + return False + + if PANDAS_VERSION < '0.17.0': + try: + test_statistic = demedianed.iget(-1) / median_deviation + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + else: + try: + test_statistic = demedianed.iat[-1] / median_deviation + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + + # Completely arbitary...triggers if the median deviation is + # 6 times bigger than the median + if test_statistic > 6: + return True + + # As per https://github.com/etsy/skyline/pull/104 by @rugger74 + # Although never seen this should return False if not > arbitary_value + # 20160523 @earthgecko + return False
+ + +
[docs]def grubbs(timeseries): + """ + A timeseries is anomalous if the Z score is greater than the Grubb's score. + """ + + try: + series = scipy.array([x[1] for x in timeseries]) + stdDev = scipy.std(series) + mean = np.mean(series) + tail_average = tail_avg(timeseries) + z_score = (tail_average - mean) / stdDev + len_series = len(series) + threshold = scipy.stats.t.isf(.05 / (2 * len_series), len_series - 2) + threshold_squared = threshold * threshold + grubbs_score = ((len_series - 1) / np.sqrt(len_series)) * np.sqrt(threshold_squared / (len_series - 2 + threshold_squared)) + + return z_score > grubbs_score + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def first_hour_average(timeseries): + """ + Calcuate the simple average over one hour, FULL_DURATION seconds ago. + A timeseries is anomalous if the average of the last three datapoints + are outside of three standard deviations of this value. + """ + + try: + last_hour_threshold = time() - (FULL_DURATION - 3600) + series = pandas.Series([x[1] for x in timeseries if x[0] < last_hour_threshold]) + mean = (series).mean() + stdDev = (series).std() + t = tail_avg(timeseries) + + return abs(t - mean) > 3 * stdDev + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def stddev_from_average(timeseries): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the average. This does not exponentially weight the MA and so + is better for detecting anomalies with respect to the entire series. + """ + + try: + series = pandas.Series([x[1] for x in timeseries]) + mean = series.mean() + stdDev = series.std() + t = tail_avg(timeseries) + + return abs(t - mean) > 3 * stdDev + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def stddev_from_moving_average(timeseries): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the moving average. This is better for finding anomalies with + respect to the short term trends. + """ + try: + series = pandas.Series([x[1] for x in timeseries]) + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=50) + stdDev = pandas.stats.moments.ewmstd(series, com=50) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).mean() + stdDev = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).std(bias=False) + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1) - expAverage.iget(-1)) > 3 * stdDev.iget(-1) + else: + return abs(series.iat[-1] - expAverage.iat[-1]) > 3 * stdDev.iat[-1] +# http://stackoverflow.com/questions/28757389/loc-vs-iloc-vs-ix-vs-at-vs-iat + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def mean_subtraction_cumulation(timeseries): + """ + A timeseries is anomalous if the value of the next datapoint in the + series is farther than three standard deviations out in cumulative terms + after subtracting the mean from each data point. + """ + + try: + series = pandas.Series([x[1] if x[1] else 0 for x in timeseries]) + series = series - series[0:len(series) - 1].mean() + stdDev = series[0:len(series) - 1].std() + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=15) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=15).mean() + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1)) > 3 * stdDev + else: + return abs(series.iat[-1]) > 3 * stdDev + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def least_squares(timeseries): + """ + A timeseries is anomalous if the average of the last three datapoints + on a projected least squares model is greater than three sigma. + """ + + try: + x = np.array([t[0] for t in timeseries]) + y = np.array([t[1] for t in timeseries]) + A = np.vstack([x, np.ones(len(x))]).T + results = np.linalg.lstsq(A, y) + residual = results[1] + m, c = np.linalg.lstsq(A, y)[0] + errors = [] + # Evaluate append once, not every time in the loop - this gains ~0.020 s on + # every timeseries potentially @earthgecko #1310 + append_error = errors.append + + # Further a question exists related to performance and accruracy with + # regards to how many datapoints are in the sample, currently all datapoints + # are used but this may not be the ideal or most efficient computation or + # fit for a timeseries... @earthgecko is checking graphite... + for i, value in enumerate(y): + projected = m * x[i] + c + error = value - projected + # errors.append(error) # @earthgecko #1310 + append_error(error) + + if len(errors) < 3: + return False + + std_dev = scipy.std(errors) + t = (errors[-1] + errors[-2] + errors[-3]) / 3 + + return abs(t) > std_dev * 3 and round(std_dev) != 0 and round(t) != 0 + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def histogram_bins(timeseries): + """ + A timeseries is anomalous if the average of the last three datapoints falls + into a histogram bin with less than 20 other datapoints (you'll need to tweak + that number depending on your data) + + Returns: the size of the bin which contains the tail_avg. Smaller bin size + means more anomalous. + """ + + try: + series = scipy.array([x[1] for x in timeseries]) + t = tail_avg(timeseries) + h = np.histogram(series, bins=15) + bins = h[1] + for index, bin_size in enumerate(h[0]): + if bin_size <= 20: + # Is it in the first bin? + if index == 0: + if t <= bins[0]: + return True + # Is it in the current bin? + elif t >= bins[index] and t < bins[index + 1]: + return True + + return False + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def ks_test(timeseries): + """ + A timeseries is anomalous if 2 sample Kolmogorov-Smirnov test indicates + that data distribution for last 10 minutes is different from last hour. + It produces false positives on non-stationary series so Augmented + Dickey-Fuller test applied to check for stationarity. + """ + + try: + hour_ago = time() - 3600 + ten_minutes_ago = time() - 600 + reference = scipy.array([x[1] for x in timeseries if x[0] >= hour_ago and x[0] < ten_minutes_ago]) + probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_minutes_ago]) + + if reference.size < 20 or probe.size < 20: + return False + + ks_d, ks_p_value = scipy.stats.ks_2samp(reference, probe) + + if ks_p_value < 0.05 and ks_d > 0.5: + adf = sm.tsa.stattools.adfuller(reference, 10) + if adf[1] < 0.05: + return True + + return False + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + + return False
+ +""" +THE END of NO MAN'S LAND + + +THE START of UTILITY FUNCTIONS + +""" + + +
[docs]def get_function_name(): + """ + This is a utility function is used to determine what algorithm is reporting + an algorithm error when the record_algorithm_error is used. + """ + return traceback.extract_stack(None, 2)[0][2]
+ + +
[docs]def record_algorithm_error(algorithm_name, traceback_format_exc_string): + """ + This utility function is used to facilitate the traceback from any algorithm + errors. The algorithm functions themselves we want to run super fast and + without fail in terms of stopping the function returning and not reporting + anything to the log, so the pythonic except is used to "sample" any + algorithm errors to a tmp file and report once per run rather than spewing + tons of errors into the log. + + .. note:: + algorithm errors tmp file clean up + the algorithm error tmp files are handled and cleaned up in + :class:`Analyzer` after all the spawned processes are completed. + + :param algorithm_name: the algoritm function name + :type algorithm_name: str + :param traceback_format_exc_string: the traceback_format_exc string + :type traceback_format_exc_string: str + :return: + - ``True`` the error string was written to the algorithm_error_file + - ``False`` the error string was not written to the algorithm_error_file + + :rtype: + - boolean + + """ + + current_process_pid = getpid() + algorithm_error_file = '%s/%s.%s.%s.algorithm.error' % ( + SKYLINE_TMP_DIR, skyline_app, str(current_process_pid), algorithm_name) + try: + with open(algorithm_error_file, 'w') as f: + f.write(str(traceback_format_exc_string)) + return True + except: + return False
+ + +
[docs]def determine_median(array): + """ + Determine the median in an array of values + """ + + # logger.info('Running ' + str(get_function_name())) + try: + np_array = np.array(array) + except: + return False + try: + array_median = np.median(np_array) + return array_median + except: + return False + + return False
+ + +
[docs]def is_anomalously_anomalous(metric_name, ensemble, datapoint): + """ + This method runs a meta-analysis on the metric to determine whether the + metric has a past history of triggering. TODO: weight intervals based on datapoint + """ + # We want the datapoint to avoid triggering twice on the same data + new_trigger = [time(), datapoint] + + # Get the old history + raw_trigger_history = redis_conn.get('trigger_history.' + metric_name) + if not raw_trigger_history: + redis_conn.set('trigger_history.' + metric_name, packb([(time(), datapoint)])) + return True + + trigger_history = unpackb(raw_trigger_history) + + # Are we (probably) triggering on the same data? + if (new_trigger[1] == trigger_history[-1][1] and + new_trigger[0] - trigger_history[-1][0] <= 300): + return False + + # Update the history + trigger_history.append(new_trigger) + redis_conn.set('trigger_history.' + metric_name, packb(trigger_history)) + + # Should we surface the anomaly? + trigger_times = [x[0] for x in trigger_history] + intervals = [ + trigger_times[i + 1] - trigger_times[i] + for i, v in enumerate(trigger_times) + if (i + 1) < len(trigger_times) + ] + + series = pandas.Series(intervals) + mean = series.mean() + stdDev = series.std() + + return abs(intervals[-1] - mean) > 3 * stdDev
+ + +
[docs]def run_selected_algorithm(timeseries, metric_name): + """ + Filter timeseries and run selected algorithm. + """ + # Get rid of short series + if len(timeseries) < MIN_TOLERABLE_LENGTH: + raise TooShort() + + # Get rid of stale series + if time() - timeseries[-1][0] > STALE_PERIOD: + raise Stale() + + # Get rid of boring series + if len(set(item[1] for item in timeseries[-MAX_TOLERABLE_BOREDOM:])) == BOREDOM_SET_SIZE: + raise Boring() + + # RUN_OPTIMIZED_WORKFLOW - replaces the original ensemble method: + # ensemble = [globals()[algorithm](timeseries) for algorithm in ALGORITHMS] + # which runs all timeseries through all ALGORITHMS + final_ensemble = [] + number_of_algorithms_triggered = 0 + number_of_algorithms_run = 0 + number_of_algorithms = len(ALGORITHMS) + maximum_false_count = number_of_algorithms - CONSENSUS + 1 + # logger.info('the maximum_false_count is %s, above which CONSENSUS cannot be achieved' % (str(maximum_false_count))) + consensus_possible = True + # DEVELOPMENT: this is for a development version of analyzer only + if skyline_app == 'analyzer_dev': + time_all_algorithms = True + else: + time_all_algorithms = False + + algorithm_tmp_file_prefix = '%s/%s.' % (SKYLINE_TMP_DIR, skyline_app) + + for algorithm in ALGORITHMS: + if consensus_possible: + + if send_algorithm_run_metrics: + algorithm_count_file = '%s%s.count' % (algorithm_tmp_file_prefix, algorithm) + algorithm_timings_file = '%s%s.timings' % (algorithm_tmp_file_prefix, algorithm) + + run_algorithm = [] + run_algorithm.append(algorithm) + number_of_algorithms_run += 1 + if send_algorithm_run_metrics: + start = timer() + try: + algorithm_result = [globals()[test_algorithm](timeseries) for test_algorithm in run_algorithm] + except: + # logger.error('%s failed' % (algorithm)) + algorithm_result = [None] + + if send_algorithm_run_metrics: + end = timer() + with open(algorithm_count_file, 'a') as f: + f.write('1\n') + with open(algorithm_timings_file, 'a') as f: + f.write('%.6f\n' % (end - start)) + else: + algorithm_result = [False] + # logger.info('CONSENSUS NOT ACHIEVABLE - skipping %s' % (str(algorithm))) + + if algorithm_result.count(True) == 1: + result = True + number_of_algorithms_triggered += 1 + # logger.info('algorithm %s triggerred' % (str(algorithm))) + elif algorithm_result.count(False) == 1: + result = False + elif algorithm_result.count(None) == 1: + algorithm_result = None + else: + result = False + + final_ensemble.append(result) + + if not RUN_OPTIMIZED_WORKFLOW: + continue + + if time_all_algorithms: + continue + + if ENABLE_ALL_ALGORITHMS_RUN_METRICS: + continue + + false_count = final_ensemble.count(False) + true_count = final_ensemble.count(True) + + # logger.info('current false_count %s' % (str(false_count))) + + if final_ensemble.count(False) >= maximum_false_count: + consensus_possible = False + # logger.info('CONSENSUS cannot be reached as %s algorithms have already not been triggered' % (str(false_count))) + skip_algorithms_count = number_of_algorithms - number_of_algorithms_run + # logger.info('skipping %s algorithms' % (str(skip_algorithms_count))) + + # logger.info('final_ensemble: %s' % (str(final_ensemble))) + + try: + # ensemble = [globals()[algorithm](timeseries) for algorithm in ALGORITHMS] + ensemble = final_ensemble + + threshold = len(ensemble) - CONSENSUS + if ensemble.count(False) <= threshold: + if ENABLE_SECOND_ORDER: + if is_anomalously_anomalous(metric_name, ensemble, timeseries[-1][1]): + return True, ensemble, timeseries[-1][1] + else: + return True, ensemble, timeseries[-1][1] + + return False, ensemble, timeseries[-1][1] + except: + logger.error('Algorithm error: %s' % traceback.format_exc()) + return False, [], 1
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer/analyzer.html b/docs/_build/html/_modules/analyzer/analyzer.html new file mode 100644 index 00000000..cf36d7da --- /dev/null +++ b/docs/_build/html/_modules/analyzer/analyzer.html @@ -0,0 +1,961 @@ + + + + + + + + + + + analyzer.analyzer — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer.analyzer

+import logging
+try:
+    from Queue import Empty
+except:
+    from queue import Empty
+from redis import StrictRedis
+from time import time, sleep
+from threading import Thread
+from collections import defaultdict
+from multiprocessing import Process, Manager, Queue
+from msgpack import Unpacker, unpackb, packb
+import os
+from os import path, kill, getpid, system
+from math import ceil
+import traceback
+import operator
+import socket
+import re
+from sys import version_info
+
+import os.path
+import sys
+import settings
+from skyline_functions import send_graphite_metric, write_data_to_file
+
+from alerters import trigger_alert
+from algorithms import run_selected_algorithm
+from algorithm_exceptions import *
+
+try:
+    send_algorithm_run_metrics = settings.ENABLE_ALGORITHM_RUN_METRICS
+except:
+    send_algorithm_run_metrics = False
+if send_algorithm_run_metrics:
+    from algorithms import determine_median
+
+# TODO if settings.ENABLE_CRUCIBLE: and ENABLE_PANORAMA
+#    from spectrum import push_to_crucible
+
+skyline_app = 'analyzer'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+python_version = int(version_info[0])
+
+this_host = str(os.uname()[1])
+
+try:
+    SERVER_METRIC_PATH = '.%s' % settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.%s%s' % (skyline_app, SERVER_METRIC_PATH)
+
+
+
[docs]class Analyzer(Thread): + """ + The Analyzer class which controls the analyzer thread and spawned processes. + """ + + def __init__(self, parent_pid): + """ + Initialize the Analyzer + + Create the :obj:`self.anomalous_metrics` list + + Create the :obj:`self.exceptions_q` queue + + Create the :obj:`self.anomaly_breakdown_q` queue + + """ + super(Analyzer, self).__init__() + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.anomalous_metrics = Manager().list() + self.exceptions_q = Queue() + self.anomaly_breakdown_q = Queue() + self.mirage_metrics = Manager().list() + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def spin_process(self, i, unique_metrics): + """ + Assign a bunch of metrics for a process to analyze. + + Multiple get the assigned_metrics to the process from Redis. + + For each metric: + + - unpack the `raw_timeseries` for the metric. + - Analyse each timeseries against `ALGORITHMS` to determine if it is + anomalous. + - If anomalous add it to the :obj:`self.anomalous_metrics` list + - Add what algorithms triggered to the :obj:`self.anomaly_breakdown_q` + queue + - If :mod:`settings.ENABLE_CRUCIBLE` is ``True``: + + - Add a crucible data file with the details about the timeseries and + anomaly. + - Write the timeseries to a json file for crucible. + + Add keys and values to the queue so the parent process can collate for:\n + * :py:obj:`self.anomaly_breakdown_q` + * :py:obj:`self.exceptions_q` + """ + + spin_start = time() + logger.info('spin_process started') + + # Discover assigned metrics + keys_per_processor = int(ceil(float(len(unique_metrics)) / float(settings.ANALYZER_PROCESSES))) + if i == settings.ANALYZER_PROCESSES: + assigned_max = len(unique_metrics) + else: + assigned_max = min(len(unique_metrics), i * keys_per_processor) + # Fix analyzer worker metric assignment #94 + # https://github.com/etsy/skyline/pull/94 @languitar:worker-fix + assigned_min = (i - 1) * keys_per_processor + assigned_keys = range(assigned_min, assigned_max) + + # Compile assigned metrics + assigned_metrics = [unique_metrics[index] for index in assigned_keys] + + # Check if this process is unnecessary + if len(assigned_metrics) == 0: + return + + # Multi get series + raw_assigned = self.redis_conn.mget(assigned_metrics) + + # Make process-specific dicts + exceptions = defaultdict(int) + anomaly_breakdown = defaultdict(int) + + # Distill timeseries strings into lists + for i, metric_name in enumerate(assigned_metrics): + self.check_if_parent_is_alive() + + try: + raw_series = raw_assigned[i] + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + + anomalous, ensemble, datapoint = run_selected_algorithm(timeseries, metric_name) + + # If it's anomalous, add it to list + if anomalous: + base_name = metric_name.replace(settings.FULL_NAMESPACE, '', 1) + metric = [datapoint, base_name] + self.anomalous_metrics.append(metric) + + # Get the anomaly breakdown - who returned True? + triggered_algorithms = [] + for index, value in enumerate(ensemble): + if value: + algorithm = settings.ALGORITHMS[index] + anomaly_breakdown[algorithm] += 1 + triggered_algorithms.append(algorithm) + + # If Crucible or Panorama are enabled determine details + determine_anomaly_details = False + if settings.ENABLE_CRUCIBLE and settings.ANALYZER_CRUCIBLE_ENABLED: + determine_anomaly_details = True + if settings.PANORAMA_ENABLED: + determine_anomaly_details = True + + if determine_anomaly_details: + metric_timestamp = str(int(timeseries[-1][0])) + from_timestamp = str(int(timeseries[1][0])) + timeseries_dir = base_name.replace('.', '/') + + # If Panorama is enabled - create a Panorama check + if settings.PANORAMA_ENABLED: + if not os.path.exists(settings.PANORAMA_CHECK_PATH): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.PANORAMA_CHECK_PATH, mode_arg) + + # Note: + # The values are enclosed is single quoted intentionally + # as the imp.load_source used results in a shift in the + # decimal position when double quoted, e.g. + # value = "5622.0" gets imported as + # 2016-03-02 12:53:26 :: 28569 :: metric variable - value - 562.2 + # single quoting results in the desired, + # 2016-03-02 13:16:17 :: 1515 :: metric variable - value - 5622.0 + added_at = str(int(time())) + source = 'graphite' + panaroma_anomaly_data = 'metric = \'%s\'\n' \ + 'value = \'%s\'\n' \ + 'from_timestamp = \'%s\'\n' \ + 'metric_timestamp = \'%s\'\n' \ + 'algorithms = %s\n' \ + 'triggered_algorithms = %s\n' \ + 'app = \'%s\'\n' \ + 'source = \'%s\'\n' \ + 'added_by = \'%s\'\n' \ + 'added_at = \'%s\'\n' \ + % (base_name, str(datapoint), from_timestamp, + metric_timestamp, str(settings.ALGORITHMS), + triggered_algorithms, skyline_app, source, + this_host, added_at) + + # Create an anomaly file with details about the anomaly + panaroma_anomaly_file = '%s/%s.%s.txt' % ( + settings.PANORAMA_CHECK_PATH, added_at, + base_name) + try: + write_data_to_file( + skyline_app, panaroma_anomaly_file, 'w', + panaroma_anomaly_data) + logger.info('added panorama anomaly file :: %s' % (panaroma_anomaly_file)) + except: + logger.error('error :: failed to add panorama anomaly file :: %s' % (panaroma_anomaly_file)) + logger.info(traceback.format_exc()) + + # If Crucible is enabled - save timeseries and create a + # Crucible check + if settings.ENABLE_CRUCIBLE and settings.ANALYZER_CRUCIBLE_ENABLED: + crucible_anomaly_dir = settings.CRUCIBLE_DATA_FOLDER + '/' + timeseries_dir + '/' + metric_timestamp + if not os.path.exists(crucible_anomaly_dir): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(crucible_anomaly_dir, mode_arg) + + # Note: + # The values are enclosed is single quoted intentionally + # as the imp.load_source used in crucible results in a + # shift in the decimal position when double quoted, e.g. + # value = "5622.0" gets imported as + # 2016-03-02 12:53:26 :: 28569 :: metric variable - value - 562.2 + # single quoting results in the desired, + # 2016-03-02 13:16:17 :: 1515 :: metric variable - value - 5622.0 + + crucible_anomaly_data = 'metric = \'%s\'\n' \ + 'value = \'%s\'\n' \ + 'from_timestamp = \'%s\'\n' \ + 'metric_timestamp = \'%s\'\n' \ + 'algorithms = %s\n' \ + 'triggered_algorithms = %s\n' \ + 'anomaly_dir = \'%s\'\n' \ + 'graphite_metric = True\n' \ + 'run_crucible_tests = False\n' \ + 'added_by = \'%s\'\n' \ + 'added_at = \'%s\'\n' \ + % (base_name, str(datapoint), from_timestamp, + metric_timestamp, str(settings.ALGORITHMS), + triggered_algorithms, crucible_anomaly_dir, + skyline_app, metric_timestamp) + + # Create an anomaly file with details about the anomaly + crucible_anomaly_file = '%s/%s.txt' % (crucible_anomaly_dir, base_name) + try: + write_data_to_file( + skyline_app, crucible_anomaly_file, 'w', + crucible_anomaly_data) + logger.info('added crucible anomaly file :: %s' % (crucible_anomaly_file)) + except: + logger.error('error :: failed to add crucible anomaly file :: %s' % (crucible_anomaly_file)) + logger.info(traceback.format_exc()) + + # Create timeseries json file with the timeseries + json_file = '%s/%s.json' % (crucible_anomaly_dir, base_name) + timeseries_json = str(timeseries).replace('[', '(').replace(']', ')') + try: + write_data_to_file(skyline_app, json_file, 'w', timeseries_json) + logger.info('added crucible timeseries file :: %s' % (json_file)) + except: + logger.error('error :: failed to add crucible timeseries file :: %s' % (json_file)) + logger.info(traceback.format_exc()) + + # Create a crucible check file + crucible_check_file = '%s/%s.%s.txt' % (settings.CRUCIBLE_CHECK_PATH, metric_timestamp, base_name) + try: + write_data_to_file( + skyline_app, crucible_check_file, 'w', + crucible_anomaly_data) + logger.info('added crucible check :: %s,%s' % (base_name, metric_timestamp)) + except: + logger.error('error :: failed to add crucible check file :: %s' % (crucible_check_file)) + logger.info(traceback.format_exc()) + + # It could have been deleted by the Roomba + except TypeError: + exceptions['DeletedByRoomba'] += 1 + except TooShort: + exceptions['TooShort'] += 1 + except Stale: + exceptions['Stale'] += 1 + except Boring: + exceptions['Boring'] += 1 + except: + exceptions['Other'] += 1 + logger.info(traceback.format_exc()) + + # Add values to the queue so the parent process can collate + for key, value in anomaly_breakdown.items(): + self.anomaly_breakdown_q.put((key, value)) + + for key, value in exceptions.items(): + self.exceptions_q.put((key, value)) + + spin_end = time() - spin_start + logger.info('spin_process took %.2f seconds' % spin_end)
+ +
[docs] def run(self): + """ + - Called when the process intializes. + + - Determine if Redis is up and discover the number of `unique metrics`. + + - Divide the `unique_metrics` between the number of `ANALYZER_PROCESSES` + and assign each process a set of metrics to analyse for anomalies. + + - Wait for the processes to finish. + + - Determine whether if any anomalous metrics require: + + - Alerting on (and set `EXPIRATION_TIME` key in Redis for alert). + - Feed to another module e.g. mirage. + - Alert to syslog. + + - Populate the webapp json with the anomalous_metrics details. + + - Log the details about the run to the skyline analyzer log. + + - Send skyline.analyzer metrics to `GRAPHITE_HOST` + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os.remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os.remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + if not os.path.exists(settings.SKYLINE_TMP_DIR): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.SKYLINE_TMP_DIR, mode_arg) + + # Initiate the algorithm timings if Analyzer is configured to send the + # algorithm_breakdown metrics with ENABLE_ALGORITHM_RUN_METRICS + algorithm_tmp_file_prefix = settings.SKYLINE_TMP_DIR + '/' + skyline_app + '.' + algorithms_to_time = [] + if send_algorithm_run_metrics: + algorithms_to_time = settings.ALGORITHMS + + while 1: + now = time() + + # Make sure Redis is up + try: + self.redis_conn.ping() + except: + logger.error('skyline can\'t connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + sleep(10) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + # Report app up + self.redis_conn.setex(skyline_app, 120, now) + + # Discover unique metrics + unique_metrics = list(self.redis_conn.smembers(settings.FULL_NAMESPACE + 'unique_metrics')) + + if len(unique_metrics) == 0: + logger.info('no metrics in redis. try adding some - see README') + sleep(10) + continue + + # Using count files rather that multiprocessing.Value to enable metrics for + # metrics for algorithm run times, etc + for algorithm in algorithms_to_time: + algorithm_count_file = algorithm_tmp_file_prefix + algorithm + '.count' + algorithm_timings_file = algorithm_tmp_file_prefix + algorithm + '.timings' + # with open(algorithm_count_file, 'a') as f: + with open(algorithm_count_file, 'w') as f: + pass + with open(algorithm_timings_file, 'w') as f: + pass + + # Remove any existing algorithm.error files from any previous runs + # that did not cleanup for any reason + pattern = '%s.*.algorithm.error' % skyline_app + try: + for f in os.listdir(settings.SKYLINE_TMP_DIR): + if re.search(pattern, f): + try: + os.remove(os.path.join(settings.SKYLINE_TMP_DIR, f)) + logger.info('cleaning up old error file - %s' % (str(f))) + except OSError: + pass + except: + logger.error('failed to cleanup algorithm.error files ' + traceback.format_exc()) + + # Spawn processes + pids = [] + spawned_pids = [] + pid_count = 0 + for i in range(1, settings.ANALYZER_PROCESSES + 1): + if i > len(unique_metrics): + logger.info('WARNING: skyline is set for more cores than needed.') + break + + p = Process(target=self.spin_process, args=(i, unique_metrics)) + pids.append(p) + pid_count += 1 + logger.info('starting %s of %s spin_process/es' % (str(pid_count), str(settings.ANALYZER_PROCESSES))) + p.start() + spawned_pids.append(p.pid) + + # Send wait signal to zombie processes + # for p in pids: + # p.join() + # Self monitor processes and terminate if any spin_process has run + # for longer than 180 seconds - 20160512 @earthgecko + p_starts = time() + while time() - p_starts <= settings.MAX_ANALYZER_PROCESS_RUNTIME: + if any(p.is_alive() for p in pids): + # Just to avoid hogging the CPU + sleep(.1) + else: + # All the processes are done, break now. + time_to_run = time() - p_starts + logger.info('%s :: %s spin_process/es completed in %.2f seconds' % (skyline_app, str(settings.ANALYZER_PROCESSES), time_to_run)) + break + else: + # We only enter this if we didn't 'break' above. + logger.info('%s :: timed out, killing all spin_process processes' % (skyline_app)) + for p in pids: + p.terminate() + p.join() + + # Log the last reported error by any algorithms that errored in the + # spawned processes from algorithms.py + for completed_pid in spawned_pids: + logger.info('spin_process with pid %s completed' % (str(completed_pid))) + for algorithm in settings.ALGORITHMS: + algorithm_error_file = '%s/%s.%s.%s.algorithm.error' % ( + settings.SKYLINE_TMP_DIR, skyline_app, + str(completed_pid), algorithm) + if os.path.isfile(algorithm_error_file): + logger.info( + 'error - spin_process with pid %s has reported an error with the %s algorithm' % ( + str(completed_pid), algorithm)) + try: + with open(algorithm_error_file, 'r') as f: + error_string = f.read() + logger.error('%s' % str(error_string)) + except: + logger.error('failed to read %s error file' % algorithm) + try: + os.remove(algorithm_error_file) + except OSError: + pass + + # Grab data from the queue and populate dictionaries + exceptions = dict() + anomaly_breakdown = dict() + while 1: + try: + key, value = self.anomaly_breakdown_q.get_nowait() + if key not in anomaly_breakdown.keys(): + anomaly_breakdown[key] = value + else: + anomaly_breakdown[key] += value + except Empty: + break + + while 1: + try: + key, value = self.exceptions_q.get_nowait() + if key not in exceptions.keys(): + exceptions[key] = value + else: + exceptions[key] += value + except Empty: + break + + # Send alerts + if settings.ENABLE_ALERTS: + for alert in settings.ALERTS: + for metric in self.anomalous_metrics: + ALERT_MATCH_PATTERN = alert[0] + METRIC_PATTERN = metric[1] + alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) + pattern_match = alert_match_pattern.match(METRIC_PATTERN) + if pattern_match: + cache_key = 'last_alert.%s.%s' % (alert[1], metric[1]) + try: + last_alert = self.redis_conn.get(cache_key) + if not last_alert: + try: + SECOND_ORDER_RESOLUTION_FULL_DURATION = alert[3] + logger.info('mirage check :: %s' % (metric[1])) + # Write anomalous metric to test at second + # order resolution by crucible to the check + # file + metric_timestamp = int(time()) + anomaly_check_file = '%s/%s.%s.txt' % (settings.MIRAGE_CHECK_PATH, metric_timestamp, metric[1]) + with open(anomaly_check_file, 'w') as fh: + # metric_name, anomalous datapoint, hours to resolve, timestamp + fh.write('metric = "%s"\nvalue = "%s"\nhours_to_resolve = "%s"\nmetric_timestamp = "%s"\n' % (metric[1], metric[0], alert[3], metric_timestamp)) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(anomaly_check_file, mode_arg) + + logger.info('added mirage check :: %s,%s,%s' % (metric[1], metric[0], alert[3])) + # Add to the mirage_metrics list + base_name = METRIC_PATTERN.replace(settings.FULL_NAMESPACE, '', 1) + metric = [metric[0], base_name] + self.mirage_metrics.append(metric) + # Alert for analyzer if enabled + if settings.ENABLE_FULL_DURATION_ALERTS: + self.redis_conn.setex(cache_key, alert[2], packb(metric[0])) + trigger_alert(alert, metric) + except: + self.redis_conn.setex(cache_key, alert[2], packb(metric[0])) + trigger_alert(alert, metric) + except Exception as e: + logger.error('error :: could not send alert: %s' % e) + + # Push to crucible +# if len(self.crucible_anomalous_metrics) > 0: +# logger.info('to do - push to crucible') + + # Write anomalous_metrics to static webapp directory + if len(self.anomalous_metrics) > 0: + filename = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + with open(filename, 'w') as fh: + # Make it JSONP with a handle_data() function + anomalous_metrics = list(self.anomalous_metrics) + anomalous_metrics.sort(key=operator.itemgetter(1)) + fh.write('handle_data(%s)' % anomalous_metrics) + + # Using count files rather that multiprocessing.Value to enable metrics for + # metrics for algorithm run times, etc + for algorithm in algorithms_to_time: + algorithm_count_file = algorithm_tmp_file_prefix + algorithm + '.count' + algorithm_timings_file = algorithm_tmp_file_prefix + algorithm + '.timings' + + try: + algorithm_count_array = [] + with open(algorithm_count_file, 'r') as f: + for line in f: + value_string = line.replace('\n', '') + unquoted_value_string = value_string.replace("'", '') + float_value = float(unquoted_value_string) + algorithm_count_array.append(float_value) + except: + algorithm_count_array = False + + if not algorithm_count_array: + continue + + number_of_times_algorithm_run = len(algorithm_count_array) + logger.info( + 'algorithm run count - %s run %s times' % ( + algorithm, str(number_of_times_algorithm_run))) + if number_of_times_algorithm_run == 0: + continue + + try: + algorithm_timings_array = [] + with open(algorithm_timings_file, 'r') as f: + for line in f: + value_string = line.replace('\n', '') + unquoted_value_string = value_string.replace("'", '') + float_value = float(unquoted_value_string) + algorithm_timings_array.append(float_value) + except: + algorithm_timings_array = False + + if not algorithm_timings_array: + continue + + number_of_algorithm_timings = len(algorithm_timings_array) + logger.info( + 'algorithm timings count - %s has %s timings' % ( + algorithm, str(number_of_algorithm_timings))) + + if number_of_algorithm_timings == 0: + continue + + try: + _sum_of_algorithm_timings = sum(algorithm_timings_array) + except: + logger.error("sum error: " + traceback.format_exc()) + _sum_of_algorithm_timings = round(0.0, 6) + logger.error('error - sum_of_algorithm_timings - %s' % (algorithm)) + continue + + sum_of_algorithm_timings = round(_sum_of_algorithm_timings, 6) + # logger.info('sum_of_algorithm_timings - %s - %.16f seconds' % (algorithm, sum_of_algorithm_timings)) + + try: + _median_algorithm_timing = determine_median(algorithm_timings_array) + except: + _median_algorithm_timing = round(0.0, 6) + logger.error('error - _median_algorithm_timing - %s' % (algorithm)) + continue + median_algorithm_timing = round(_median_algorithm_timing, 6) + # logger.info('median_algorithm_timing - %s - %.16f seconds' % (algorithm, median_algorithm_timing)) + + logger.info( + 'algorithm timing - %s - total: %.6f - median: %.6f' % ( + algorithm, sum_of_algorithm_timings, + median_algorithm_timing)) + use_namespace = skyline_app_graphite_namespace + '.algorithm_breakdown.' + algorithm + send_metric_name = use_namespace + '.timing.times_run' + send_graphite_metric(skyline_app, send_metric_name, str(number_of_algorithm_timings)) + send_metric_name = use_namespace + '.timing.total_time' + send_graphite_metric(skyline_app, send_metric_name, str(sum_of_algorithm_timings)) + send_metric_name = use_namespace + '.timing.median_time' + send_graphite_metric(skyline_app, send_metric_name, str(median_algorithm_timing)) + + run_time = time() - now + total_metrics = str(len(unique_metrics)) + total_analyzed = str(len(unique_metrics) - sum(exceptions.values())) + total_anomalies = str(len(self.anomalous_metrics)) + + # Log progress + logger.info('seconds to run :: %.2f' % run_time) + logger.info('total metrics :: %s' % total_metrics) + logger.info('total analyzed :: %s' % total_analyzed) + logger.info('total anomalies :: %s' % total_anomalies) + logger.info('exception stats :: %s' % exceptions) + logger.info('anomaly breakdown :: %s' % anomaly_breakdown) + + # Log to Graphite + graphite_run_time = '%.2f' % run_time + send_metric_name = skyline_app_graphite_namespace + '.run_time' + send_graphite_metric(skyline_app, send_metric_name, graphite_run_time) + + send_metric_name = skyline_app_graphite_namespace + '.total_analyzed' + send_graphite_metric(skyline_app, send_metric_name, total_analyzed) + + send_metric_name = skyline_app_graphite_namespace + '.total_anomalies' + send_graphite_metric(skyline_app, send_metric_name, total_anomalies) + + send_metric_name = skyline_app_graphite_namespace + '.total_metrics' + send_graphite_metric(skyline_app, send_metric_name, total_metrics) + for key, value in exceptions.items(): + send_metric_name = '%s.exceptions.%s' % (skyline_app_graphite_namespace, key) + send_graphite_metric(skyline_app, send_metric_name, str(value)) + for key, value in anomaly_breakdown.items(): + send_metric_name = '%s.anomaly_breakdown.%s' % (skyline_app_graphite_namespace, key) + send_graphite_metric(skyline_app, send_metric_name, str(value)) + + # Check canary metric + raw_series = self.redis_conn.get(settings.FULL_NAMESPACE + settings.CANARY_METRIC) + if raw_series is not None: + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + time_human = (timeseries[-1][0] - timeseries[0][0]) / 3600 + projected = 24 * (time() - now) / time_human + + logger.info('canary duration :: %.2f' % time_human) + send_metric_name = skyline_app_graphite_namespace + '.duration' + send_graphite_metric(skyline_app, send_metric_name, str(time_human)) + + send_metric_name = skyline_app_graphite_namespace + '.projected' + send_graphite_metric(skyline_app, send_metric_name, str(projected)) + + # Reset counters + self.anomalous_metrics[:] = [] + + # Sleep if it went too fast + # if time() - now < 5: + # logger.info('sleeping due to low run time...') + # sleep(10) + # @modified 20160504 - @earthgecko - development internal ref #1338, #1340) + # Etsy's original for this was a value of 5 seconds which does + # not make skyline Analyzer very efficient in terms of installations + # where 100s of 1000s of metrics are being analyzed. This lead to + # Analyzer running over several metrics multiple time in a minute + # and always working. Therefore this was changed from if you took + # less than 5 seconds to run only then sleep. This behaviour + # resulted in Analyzer analysing a few 1000 metrics in 9 seconds and + # then doing it again and again in a single minute. Therefore the + # ANALYZER_OPTIMUM_RUN_DURATION setting was added to allow this to + # self optimise in cases where skyline is NOT deployed to analyze + # 100s of 1000s of metrics. This relates to optimising performance + # for any deployments in the few 1000s and 60 second resolution + # area, e.g. smaller and local deployments. + process_runtime = time() - now + analyzer_optimum_run_duration = settings.ANALYZER_OPTIMUM_RUN_DURATION + if process_runtime < analyzer_optimum_run_duration: + sleep_for = (analyzer_optimum_run_duration - process_runtime) + logger.info('sleeping for %.2f seconds due to low run time...' % sleep_for) + sleep(sleep_for)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer_dev/agent.html b/docs/_build/html/_modules/analyzer_dev/agent.html new file mode 100644 index 00000000..2ee29210 --- /dev/null +++ b/docs/_build/html/_modules/analyzer_dev/agent.html @@ -0,0 +1,392 @@ + + + + + + + + + + + analyzer_dev.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer_dev.agent

+import logging
+import sys
+import traceback
+from os import getpid, kill
+import signal
+from os.path import dirname, abspath, isdir
+from daemon import runner
+from time import sleep, time
+
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+import settings
+
+from analyzer_dev import Analyzer
+
+skyline_app = 'analyzer_dev'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+
+
[docs]class AnalyzerAgent(): + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + if len(sys.argv) > 1 and sys.argv[1] == 'stop': + do_not_overwrite_log = True + # This should hopefully take care of a TODO from the bin files, + # TODO: write a real kill script + # This is basically from the python-daemon function: + # def _terminate_daemon_process from: +# https://github.com/elephantum/python-daemon/blob/a38aefd37d319586a9e7ab034435928b1c243e49/daemon/runner.py#L133 + # logging with multiprocessing and log rotation is difficult. I am + # certain that many a people have been in a helpless and hopeless + # state when trying to debug Skyline, those python truncating log + # handlers, it is not easy. Many, many combinations of things have + # been attempted in this area to attempt to be able to have the + # agents just append the log. The TimedRotatingFileHandler does not + # help matters either as it has no mode='a'. It could be handled by + # normal log rotation but multiprocessing does not make that easy + # either. It is a difficult problem and adding a multiprocessing + # log Queue with the agent listening and writing has been consider + # too. It may work, but adds a lot more complexity, for me anyway. + # The ideal is to have to agent.py creating/appending to log + # and not overwriting the damn thing and TimedRotatingFileHandler, + # everything is possible :) And the new bin bash files do a pretty + # good job anyway and have for 2 years now, maybe havign lost 1 or 2 + # in the 2 years that they have been managing the logs :) + # @earthgecko 20160520 + pid = int(open(pidfile_path).read()) + try: + kill(pid, signal.SIGTERM) + print '%s pid %s stopped' % (skyline_app, str(pid)) + sys.exit(0) + except OSError, exc: + print 'Failed to kill pid %s' % str(pid) + sys.exit(1) + else: + logger.info('starting skyline ' + skyline_app) + Analyzer(getpid()).start() + + while 1: + sleep(10)
+ + +
[docs]def run(): + """ + Check that all the `ALGORITHMS` can be run. + + Start the AnalyzerAgent. + + Start the logger. + """ + if not isdir(settings.PID_PATH): + print 'pid directory does not exist at %s' % settings.PID_PATH + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print 'log directory does not exist at %s' % settings.LOG_PATH + sys.exit(1) + + if len(sys.argv) > 1 and sys.argv[1] == 'stop': + do_not_overwrite_log = True + # This should hopefully take care of a TODO from the bin files, + # TODO: write a real kill script + # as above @earthgecko 20160520 + pidfile_path = settings.PID_PATH + '/' + skyline_app + '.pid' + pid = int(open(pidfile_path).read()) + try: + kill(pid, signal.SIGTERM) + print '%s pid %s stopped' % (skyline_app, str(pid)) + sys.exit(0) + except OSError, exc: + print 'Failed to kill pid %s' % str(pid) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + + if len(sys.argv) > 1 and sys.argv[1] == 'stop': + handler = logging.FileHandler( + settings.LOG_PATH + '/' + skyline_app + '.stop.log', + mode='a', delay=False) + else: + + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(256, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + if len(sys.argv) > 1 and sys.argv[1] == 'stop': + do_not_overwrite_log = True + else: + # Make sure we can run all the algorithms + try: + # from analyzer import algorithms + import algorithms_dev + logger.info('Testing algorithms') + timeseries = map(list, zip(map(float, range(int(time()) - 86400, int(time()) + 1)), [1] * 86401)) + # ensemble = [globals()[algorithm](timeseries) for algorithm in settings.ALGORITHMS] + ensemble = [getattr(algorithms_dev, algorithm)(timeseries) for algorithm in settings.ALGORITHMS] + logger.info('Tested algorithms OK') + logger.info('ensemble: %s' % str(ensemble)) + except KeyError as e: + print "Algorithm %s deprecated or not defined; check settings.ALGORITHMS" % e + sys.exit(1) + except Exception as e: + print "Algorithm test run failed." + traceback.print_exc() + sys.exit(1) + + logger.info('Tested algorithms') + + analyzer = AnalyzerAgent() + + if len(sys.argv) > 1 and sys.argv[1] == 'stop': + do_not_overwrite_log = True + else: + logger.info('starting analyzer_dev.run') + memory_handler.flush + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + analyzer.run() + else: + daemon_runner = runner.DaemonRunner(analyzer) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action() + + if len(sys.argv) > 1 and sys.argv[1] == 'stop': + do_not_overwrite_log = True + else: + logger.info('stopped analyzer_dev')
+ +if __name__ == '__main__': + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer_dev/alerters.html b/docs/_build/html/_modules/analyzer_dev/alerters.html new file mode 100644 index 00000000..7daa62e0 --- /dev/null +++ b/docs/_build/html/_modules/analyzer_dev/alerters.html @@ -0,0 +1,394 @@ + + + + + + + + + + + analyzer_dev.alerters — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer_dev.alerters

+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from email.MIMEImage import MIMEImage
+from smtplib import SMTP
+import alerters
+import urllib2
+
+import os.path
+import sys
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+"""
+Create any alerter you want here. The function will be invoked from trigger_alert.
+Two arguments will be passed, both of them tuples: alert and metric.
+
+alert: the tuple specified in your settings:
+    alert[0]: The matched substring of the anomalous metric
+    alert[1]: the name of the strategy being used to alert
+    alert[2]: The timeout of the alert that was triggered
+metric: information about the anomaly itself
+    metric[0]: the anomalous value
+    metric[1]: The full name of the anomalous metric
+"""
+
+# FULL_DURATION to hours so that analyzer surfaces the relevant timeseries data
+# in the graph
+try:
+    full_duration_seconds = int(settings.FULL_DURATION)
+except:
+    full_duration_seconds = 86400
+full_duration_in_hours = full_duration_seconds / 60 / 60
+
+
+
[docs]def alert_smtp(alert, metric): + """ + Called by :func:`~trigger_alert` and sends an alert via smtp to the + recipients that are configured for the metric. + + """ + + # FULL_DURATION to hours so that analyzer surfaces the relevant timeseries data + # in the graph + full_duration_in_hours = int(settings.FULL_DURATION) / 3600 + + # For backwards compatibility + if '@' in alert[1]: + sender = settings.ALERT_SENDER + recipient = alert[1] + else: + sender = settings.SMTP_OPTS['sender'] + recipients = settings.SMTP_OPTS['recipients'][alert[0]] + + # Backwards compatibility + if type(recipients) is str: + recipients = [recipients] + + graph_title = '&title=skyline%%20analyzer%%20ALERT%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % (full_duration_in_hours, metric[1], metric[0]) + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, full_duration_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, full_duration_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + + content_id = metric[1] + image_data = None + if settings.SMTP_OPTS.get('embed-images'): + try: + image_data = urllib2.urlopen(link).read() + except urllib2.URLError: + image_data = None + + # If we failed to get the image or if it was explicitly disabled, + # use the image URL instead of the content. + if image_data is None: + img_tag = '<img src="%s"/>' % link + else: + img_tag = '<img src="cid:%s"/>' % content_id + + body = 'skyline analyzer alert <br> Anomalous value: %s <br> Next alert in: %s seconds <br> <a href="%s">%s</a>' % (metric[0], alert[2], link, img_tag) + + for recipient in recipients: + msg = MIMEMultipart('alternative') + msg['Subject'] = '[skyline alert] ' + metric[1] + msg['From'] = sender + msg['To'] = recipient + + msg.attach(MIMEText(body, 'html')) + if image_data is not None: + msg_attachment = MIMEImage(image_data) + msg_attachment.add_header('Content-ID', '<%s>' % content_id) + msg.attach(msg_attachment) + + s = SMTP('127.0.0.1') + s.sendmail(sender, recipient, msg.as_string()) + s.quit()
+ + +
[docs]def alert_pagerduty(alert, metric): + """ + Called by :func:`~trigger_alert` and sends an alert via PagerDuty + """ + import pygerduty + pager = pygerduty.PagerDuty(settings.PAGERDUTY_OPTS['subdomain'], settings.PAGERDUTY_OPTS['auth_token']) + pager.trigger_incident(settings.PAGERDUTY_OPTS['key'], "Anomalous metric: %s (value: %s)" % (metric[1], metric[0]))
+ + +
[docs]def alert_hipchat(alert, metric): + """ + Called by :func:`~trigger_alert` and sends an alert the hipchat room that is + configured in settings.py. + """ + sender = settings.HIPCHAT_OPTS['sender'] + import hipchat + hipster = hipchat.HipChat(token=settings.HIPCHAT_OPTS['auth_token']) + rooms = settings.HIPCHAT_OPTS['rooms'][alert[0]] + + graph_title = '&title=skyline%%20analyzer%%20ALERT%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % (full_duration_in_hours, metric[1], metric[0]) + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, full_duration_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, full_duration_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + embed_graph = "<a href='" + link + "'><img height='308' src='" + link + "'>" + metric[1] + "</a>" + + for room in rooms: + hipster.method('rooms/message', method='POST', parameters={'room_id': room, 'from': 'skyline', 'color': settings.HIPCHAT_OPTS['color'], 'message': '%s - analyzer - Anomalous metric: %s (value: %s) at %s hours %s' % (sender, metric[1], metric[0], full_duration_in_hours, embed_graph)})
+ + +
[docs]def alert_syslog(alert, metric): + """ + Called by :func:`~trigger_alert` and log anomalies to syslog. + + """ + import sys + import syslog + syslog_ident = settings.SYSLOG_OPTS['ident'] + message = str("Anomalous metric: %s (value: %s)" % (metric[1], metric[0])) + if sys.version_info[:2] == (2, 6): + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + elif sys.version_info[:2] == (2, 7): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + elif sys.version_info[:1] == (3): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + else: + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + syslog.syslog(4, message)
+ + +
[docs]def trigger_alert(alert, metric): + """ + Called by :class:`~skyline.skyline.Analyzer.run` to trigger an alert, analyzer passes + two arguments, both of them tuples. The alerting strategy is determined and + the approriate alert def is then called and passed the tuples. + + :param alert: + The alert tuple specified in settings.py.\n + alert[0]: The matched substring of the anomalous metric\n + alert[1]: the name of the strategy being used to alert\n + alert[2]: The timeout of the alert that was triggered\n + :param meric: + The metric tuple.\n + metric[0]: the anomalous value + metric[1]: The full name of the anomalous metric + + """ + + if '@' in alert[1]: + strategy = 'alert_smtp' + else: + strategy = 'alert_%s' % alert[1] + + getattr(alerters, strategy)(alert, metric)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer_dev/algorithms_dev.html b/docs/_build/html/_modules/analyzer_dev/algorithms_dev.html new file mode 100644 index 00000000..b5c20cd6 --- /dev/null +++ b/docs/_build/html/_modules/analyzer_dev/algorithms_dev.html @@ -0,0 +1,711 @@ + + + + + + + + + + + analyzer_dev.algorithms_dev — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer_dev.algorithms_dev

+import pandas
+import numpy as np
+import scipy
+import statsmodels.api as sm
+import traceback
+import logging
+from time import time
+import os.path
+import sys
+from timeit import default_timer as timer
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+try:
+    from settings import (
+        ALGORITHMS,
+        CONSENSUS,
+        FULL_DURATION,
+        MAX_TOLERABLE_BOREDOM,
+        MIN_TOLERABLE_LENGTH,
+        STALE_PERIOD,
+        REDIS_SOCKET_PATH,
+        ENABLE_SECOND_ORDER,
+        BOREDOM_SET_SIZE,
+        PANDAS_VERSION,
+        RUN_OPTIMIZED_WORKFLOW,
+        SKYLINE_TMP_DIR,
+        ENABLE_ALGORITHM_RUN_METRICS,
+        ENABLE_ALL_ALGORITHMS_RUN_METRICS,
+    )
+except:
+    print('failed to import expected settings' + traceback.format_exc())
+    print(traceback.format_exc())
+
+from algorithm_exceptions import *
+
+skyline_app = 'analyzer_dev'
+skyline_app_logger = skyline_app + 'Log'
+logger = logging.getLogger(skyline_app_logger)
+
+if ENABLE_SECOND_ORDER:
+    from redis import StrictRedis
+    from msgpack import unpackb, packb
+    redis_conn = StrictRedis(unix_socket_path=REDIS_SOCKET_PATH)
+
+try:
+    send_algorithm_run_metrics = ENABLE_ALGORITHM_RUN_METRICS
+except:
+    send_algorithm_run_metrics = False
+
+
+
[docs]def get_function_name(): + return traceback.extract_stack(None, 2)[0][2]
+ +""" +This is no man's land. Do anything you want in here, +as long as you return a boolean that determines whether the input +timeseries is anomalous or not. + +To add an algorithm, define it here, and add its name to settings.ALGORITHMS. +""" + + +
[docs]def tail_avg(timeseries): + """ + This is a utility function used to calculate the average of the last three + datapoints in the series as a measure, instead of just the last datapoint. + It reduces noise, but it also reduces sensitivity and increases the delay + to detection. + """ + try: + t = (timeseries[-1][1] + timeseries[-2][1] + timeseries[-3][1]) / 3 + return t + except IndexError: + return timeseries[-1][1]
+ + +
[docs]def median_absolute_deviation(timeseries): + """ + A timeseries is anomalous if the deviation of its latest datapoint with + respect to the median is X times larger than the median of deviations. + """ + # logger.info('Running ' + str(get_function_name())) + try: + series = pandas.Series([x[1] for x in timeseries]) + median = series.median() + demedianed = np.abs(series - median) + median_deviation = demedianed.median() + except: + return None + + # The test statistic is infinite when the median is zero, + # so it becomes super sensitive. We play it safe and skip when this happens. + if median_deviation == 0: + return False + + if PANDAS_VERSION < '0.17.0': + try: + test_statistic = demedianed.iget(-1) / median_deviation + except: + return None + else: + try: + test_statistic = demedianed.iat[-1] / median_deviation + except: + return None + + # Completely arbitary...triggers if the median deviation is + # 6 times bigger than the median + if test_statistic > 6: + return True + + # As per https://github.com/etsy/skyline/pull/104 by @rugger74 + # Although never seen this should return False if not > arbitary_value + # 20160523 @earthgecko + return False
+ + +
[docs]def grubbs(timeseries): + """ + A timeseries is anomalous if the Z score is greater than the Grubb's score. + """ + # logger.info('Running ' + str(get_function_name())) + + try: + series = scipy.array([x[1] for x in timeseries]) + stdDev = scipy.std(series) + mean = np.mean(series) + tail_average = tail_avg(timeseries) + z_score = (tail_average - mean) / stdDev + len_series = len(series) + threshold = scipy.stats.t.isf(.05 / (2 * len_series), len_series - 2) + threshold_squared = threshold * threshold + grubbs_score = ((len_series - 1) / np.sqrt(len_series)) * np.sqrt(threshold_squared / (len_series - 2 + threshold_squared)) + + return z_score > grubbs_score + except: + return None
+ + +
[docs]def first_hour_average(timeseries): + """ + Calcuate the simple average over one hour, FULL_DURATION seconds ago. + A timeseries is anomalous if the average of the last three datapoints + are outside of three standard deviations of this value. + """ + # logger.info('Running ' + str(get_function_name())) + + try: + last_hour_threshold = time() - (FULL_DURATION - 3600) + series = pandas.Series([x[1] for x in timeseries if x[0] < last_hour_threshold]) + mean = (series).mean() + stdDev = (series).std() + t = tail_avg(timeseries) + + return abs(t - mean) > 3 * stdDev + except: + return None
+ + +
[docs]def stddev_from_average(timeseries): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the average. This does not exponentially weight the MA and so + is better for detecting anomalies with respect to the entire series. + """ + # logger.info('Running ' + str(get_function_name())) + + try: + series = pandas.Series([x[1] for x in timeseries]) + mean = series.mean() + stdDev = series.std() + t = tail_avg(timeseries) + + return abs(t - mean) > 3 * stdDev + except: + return None
+ + +
[docs]def stddev_from_moving_average(timeseries): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the moving average. This is better for finding anomalies with + respect to the short term trends. + """ + # logger.info('Running ' + str(get_function_name())) + try: + series = pandas.Series([x[1] for x in timeseries]) + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=50) + stdDev = pandas.stats.moments.ewmstd(series, com=50) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).mean() + stdDev = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).std(bias=False) + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1) - expAverage.iget(-1)) > 3 * stdDev.iget(-1) + else: + return abs(series.iat[-1] - expAverage.iat[-1]) > 3 * stdDev.iat[-1] +# http://stackoverflow.com/questions/28757389/loc-vs-iloc-vs-ix-vs-at-vs-iat + except: + return None
+ + +
[docs]def mean_subtraction_cumulation(timeseries): + """ + A timeseries is anomalous if the value of the next datapoint in the + series is farther than three standard deviations out in cumulative terms + after subtracting the mean from each data point. + """ + # logger.info('Running ' + str(get_function_name())) + + try: + series = pandas.Series([x[1] if x[1] else 0 for x in timeseries]) + series = series - series[0:len(series) - 1].mean() + stdDev = series[0:len(series) - 1].std() + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=15) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=15).mean() + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1)) > 3 * stdDev + else: + return abs(series.iat[-1]) > 3 * stdDev + except: + return None
+ + +
[docs]def least_squares(timeseries): + """ + A timeseries is anomalous if the average of the last three datapoints + on a projected least squares model is greater than three sigma. + """ + # logger.info('Running ' + str(get_function_name())) + + try: + x = np.array([t[0] for t in timeseries]) + y = np.array([t[1] for t in timeseries]) + A = np.vstack([x, np.ones(len(x))]).T + results = np.linalg.lstsq(A, y) + residual = results[1] + m, c = np.linalg.lstsq(A, y)[0] + errors = [] + # Evaluate append once, not every time in the loop - this gains ~0.020 s on + # every timeseries potentially @earthgecko #1310 + append_error = errors.append + + # Further a question exists related to performance and accruracy with + # regards to how many datapoints are in the sample, currently all datapoints + # are used but this may not be the ideal or most efficient computation or + # fit for a timeseries... @earthgecko is checking graphite... + for i, value in enumerate(y): + projected = m * x[i] + c + error = value - projected + # errors.append(error) # @earthgecko #1310 + append_error(error) + + if len(errors) < 3: + return False + + std_dev = scipy.std(errors) + t = (errors[-1] + errors[-2] + errors[-3]) / 3 + + return abs(t) > std_dev * 3 and round(std_dev) != 0 and round(t) != 0 + except: + return None
+ + +
[docs]def histogram_bins(timeseries): + """ + A timeseries is anomalous if the average of the last three datapoints falls + into a histogram bin with less than 20 other datapoints (you'll need to tweak + that number depending on your data) + + Returns: the size of the bin which contains the tail_avg. Smaller bin size + means more anomalous. + """ + + try: + series = scipy.array([x[1] for x in timeseries]) + t = tail_avg(timeseries) + h = np.histogram(series, bins=15) + bins = h[1] + for index, bin_size in enumerate(h[0]): + if bin_size <= 20: + # Is it in the first bin? + if index == 0: + if t <= bins[0]: + return True + # Is it in the current bin? + elif t >= bins[index] and t < bins[index + 1]: + return True + + return False + except: + return None
+ + +
[docs]def ks_test(timeseries): + """ + A timeseries is anomalous if 2 sample Kolmogorov-Smirnov test indicates + that data distribution for last 10 minutes is different from last hour. + It produces false positives on non-stationary series so Augmented + Dickey-Fuller test applied to check for stationarity. + """ + + try: + hour_ago = time() - 3600 + ten_minutes_ago = time() - 600 + reference = scipy.array([x[1] for x in timeseries if x[0] >= hour_ago and x[0] < ten_minutes_ago]) + probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_minutes_ago]) + + if reference.size < 20 or probe.size < 20: + return False + + ks_d, ks_p_value = scipy.stats.ks_2samp(reference, probe) + + if ks_p_value < 0.05 and ks_d > 0.5: + adf = sm.tsa.stattools.adfuller(reference, 10) + if adf[1] < 0.05: + return True + + return False + except: + return None
+ + +
[docs]def determine_median(array): + """ + Determine the median in an array of values + """ + + # logger.info('Running ' + str(get_function_name())) + try: + np_array = np.array(array) + except: + return False + try: + array_median = np.median(np_array) + return array_median + except: + return False + + return False
+ + +
[docs]def is_anomalously_anomalous(metric_name, ensemble, datapoint): + """ + This method runs a meta-analysis on the metric to determine whether the + metric has a past history of triggering. TODO: weight intervals based on datapoint + """ + # We want the datapoint to avoid triggering twice on the same data + new_trigger = [time(), datapoint] + + # Get the old history + raw_trigger_history = redis_conn.get('trigger_history.' + metric_name) + if not raw_trigger_history: + redis_conn.set('trigger_history.' + metric_name, packb([(time(), datapoint)])) + return True + + trigger_history = unpackb(raw_trigger_history) + + # Are we (probably) triggering on the same data? + if (new_trigger[1] == trigger_history[-1][1] and + new_trigger[0] - trigger_history[-1][0] <= 300): + return False + + # Update the history + trigger_history.append(new_trigger) + redis_conn.set('trigger_history.' + metric_name, packb(trigger_history)) + + # Should we surface the anomaly? + trigger_times = [x[0] for x in trigger_history] + intervals = [ + trigger_times[i + 1] - trigger_times[i] + for i, v in enumerate(trigger_times) + if (i + 1) < len(trigger_times) + ] + + series = pandas.Series(intervals) + mean = series.mean() + stdDev = series.std() + + return abs(intervals[-1] - mean) > 3 * stdDev
+ + +
[docs]def run_selected_algorithm(timeseries, metric_name): + """ + Filter timeseries and run selected algorithm. + """ + # Get rid of short series + if len(timeseries) < MIN_TOLERABLE_LENGTH: + raise TooShort() + + # Get rid of stale series + if time() - timeseries[-1][0] > STALE_PERIOD: + raise Stale() + + # Get rid of boring series + if len(set(item[1] for item in timeseries[-MAX_TOLERABLE_BOREDOM:])) == BOREDOM_SET_SIZE: + raise Boring() + + final_ensemble = [] + number_of_algorithms_triggered = 0 + number_of_algorithms_run = 0 + number_of_algorithms = len(ALGORITHMS) + maximum_false_count = number_of_algorithms - CONSENSUS + 1 + # logger.info('the maximum_false_count is %s, above which CONSENSUS cannot be achieved' % (str(maximum_false_count))) + consensus_possible = True + if skyline_app == 'analyzer_dev': + time_all_algorithms = True + else: + time_all_algorithms = False + + algorithm_tmp_file_prefix = '%s/%s.' % (SKYLINE_TMP_DIR, skyline_app) + + for algorithm in ALGORITHMS: + if consensus_possible: + + if send_algorithm_run_metrics: + algorithm_count_file = '%s%s.count' % (algorithm_tmp_file_prefix, algorithm) + algorithm_timings_file = '%s%s.timings' % (algorithm_tmp_file_prefix, algorithm) + + run_algorithm = [] + run_algorithm.append(algorithm) + number_of_algorithms_run += 1 + if send_algorithm_run_metrics: + start = timer() + try: + ensemble = [globals()[test_algorithm](timeseries) for test_algorithm in run_algorithm] + except: + # logger.error('%s failed' % (algorithm)) + ensemble = [None] + + if send_algorithm_run_metrics: + end = timer() + with open(algorithm_count_file, 'a') as f: + f.write('1\n') + with open(algorithm_timings_file, 'a') as f: + f.write('%.6f\n' % (end - start)) + else: + ensemble = [None] + # logger.info('CONSENSUS NOT ACHIEVABLE - skipping %s' % (str(algorithm))) + + if ensemble.count([True]) == 1: + result = True + number_of_algorithms_triggered += 1 + # logger.info('algorithm %s triggerred' % (str(algorithm))) + elif ensemble.count([False]) == 1: + result = False + elif ensemble.count([None]) == 1: + ensemble = None + else: + result = False + + final_ensemble.append(result) + + false_count = final_ensemble.count(False) + # logger.info('current false_count %s' % (str(false_count))) + if not time_all_algorithms: + if final_ensemble.count(False) >= maximum_false_count: + consensus_possible = False + # logger.info('CONSENSUS cannot be reached as %s algorithms have already not been triggered' % (str(false_count))) + skip_algorithms_count = number_of_algorithms - number_of_algorithms_run + # logger.info('skipping %s algorithms' % (str(skip_algorithms_count))) + + # logger.info('final_ensemble: %s' % (str(final_ensemble))) + + if not consensus_possible: + return False, final_ensemble, timeseries[-1][1] + + try: + # ensemble = [globals()[algorithm](timeseries) for algorithm in ALGORITHMS] + ensemble = final_ensemble + + threshold = len(ensemble) - CONSENSUS + if ensemble.count(False) <= threshold: + if ENABLE_SECOND_ORDER: + if is_anomalously_anomalous(metric_name, ensemble, timeseries[-1][1]): + return True, ensemble, timeseries[-1][1] + else: + return True, ensemble, timeseries[-1][1] + + return False, ensemble, timeseries[-1][1] + except: + logger.error("Algorithm error: " + traceback.format_exc()) + return False, [], 1
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/analyzer_dev/analyzer_dev.html b/docs/_build/html/_modules/analyzer_dev/analyzer_dev.html new file mode 100644 index 00000000..5d9f6d01 --- /dev/null +++ b/docs/_build/html/_modules/analyzer_dev/analyzer_dev.html @@ -0,0 +1,768 @@ + + + + + + + + + + + analyzer_dev.analyzer_dev — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for analyzer_dev.analyzer_dev

+import logging
+from Queue import Empty
+from redis import StrictRedis
+from time import time, sleep
+from threading import Thread
+from collections import defaultdict
+from multiprocessing import Process, Manager, Queue
+from msgpack import Unpacker, unpackb, packb
+import os
+from os import path, kill, getpid, system
+from math import ceil
+import traceback
+import operator
+import socket
+import re
+from sys import version_info
+
+import os.path
+import sys
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+from alerters import trigger_alert
+from algorithms_dev import run_selected_algorithm
+# from skyline import algorithm_exceptions
+from algorithm_exceptions import *
+
+try:
+    send_algorithm_run_metrics = settings.ENABLE_ALGORITHM_RUN_METRICS
+except:
+    send_algorithm_run_metrics = False
+if send_algorithm_run_metrics:
+    from algorithms_dev import determine_median
+
+# TODO if settings.ENABLE_CRUCIBLE: and ENABLE_PANORAMA
+#    from spectrum import push_to_crucible
+
+skyline_app = 'analyzer_dev'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+python_version = int(version_info[0])
+
+try:
+    SERVER_METRIC_PATH = '.%s' % settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.%s%s' % (skyline_app, SERVER_METRIC_PATH)
+
+
+
[docs]class Analyzer(Thread): + """ + The Analyzer class which controls the analyzer thread and spawned processes. + """ + + def __init__(self, parent_pid): + """ + Initialize the Analyzer + + Create the :obj:`self.anomalous_metrics` list + + Create the :obj:`self.exceptions_q` queue + + Create the :obj:`self.anomaly_breakdown_q` queue + + """ + super(Analyzer, self).__init__() + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.anomalous_metrics = Manager().list() + self.exceptions_q = Queue() + self.anomaly_breakdown_q = Queue() + self.mirage_metrics = Manager().list() + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def send_graphite_metric(self, name, value): + """ + Sends the skyline_app metrics to the `GRAPHITE_HOST` if a graphite + host is defined. + """ + if settings.GRAPHITE_HOST != '': + + skyline_app_metric = skyline_app_graphite_namespace + name + + sock = socket.socket() + sock.settimeout(10) + + # Handle connection error to Graphite #116 @etsy + # Fixed as per https://github.com/etsy/skyline/pull/116 and + # mlowicki:etsy_handle_connection_error_to_graphite + # Handle connection error to Graphite #7 @ earthgecko + # merged 1 commit into earthgecko:master from + # mlowicki:handle_connection_error_to_graphite on 16 Mar 2015 + try: + sock.connect((settings.GRAPHITE_HOST, settings.CARBON_PORT)) + sock.settimeout(None) + except socket.error: + sock.settimeout(None) + endpoint = '%s:%d' % (settings.GRAPHITE_HOST, + settings.CARBON_PORT) + logger.error("Can't connect to Graphite at %s" % endpoint) + return False + + # For the same reason as above + # sock.sendall('%s %s %i\n' % (name, value, time())) + try: + sock.sendall('%s %s %i\n' % (skyline_app_metric, value, time())) + sock.close() + return True + except: + endpoint = '%s:%d' % (settings.GRAPHITE_HOST, + settings.CARBON_PORT) + logger.error("Can't connect to Graphite at %s" % endpoint) + return False + + return False
+ +
[docs] def spin_process(self, i, unique_metrics): + """ + Assign a bunch of metrics for a process to analyze. + + Multiple get the assigned_metrics to the process from Redis. + + For each metric:\n + * unpack the `raw_timeseries` for the metric.\n + * Analyse each timeseries against `ALGORITHMS` to determine if it is\n + anomalous.\n + * If anomalous add it to the :obj:`self.anomalous_metrics` list\n + * Add what algorithms triggered to the :obj:`self.anomaly_breakdown_q` queue\n + + Add keys and values to the queue so the parent process can collate for:\n + * :py:obj:`self.anomaly_breakdown_q` + * :py:obj:`self.exceptions_q` + """ + + spin_start = time() + logger.info('spin_process started') + + # Discover assigned metrics + keys_per_processor = int(ceil(float(len(unique_metrics)) / float(settings.ANALYZER_PROCESSES))) + if i == settings.ANALYZER_PROCESSES: + assigned_max = len(unique_metrics) + else: + assigned_max = min(len(unique_metrics), i * keys_per_processor) + # Fix analyzer worker metric assignment #94 + # https://github.com/etsy/skyline/pull/94 @languitar:worker-fix + assigned_min = (i - 1) * keys_per_processor + assigned_keys = range(assigned_min, assigned_max) + # assigned_keys = range(300, 310) + + # Compile assigned metrics + assigned_metrics = [unique_metrics[index] for index in assigned_keys] + + # Check if this process is unnecessary + if len(assigned_metrics) == 0: + return + + # Multi get series + raw_assigned = self.redis_conn.mget(assigned_metrics) + + # Make process-specific dicts + exceptions = defaultdict(int) + anomaly_breakdown = defaultdict(int) + + # Distill timeseries strings into lists + for i, metric_name in enumerate(assigned_metrics): + self.check_if_parent_is_alive() + + # logger.info('analysing %s' % metric_name) + + try: + raw_series = raw_assigned[i] + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + + anomalous, ensemble, datapoint = run_selected_algorithm(timeseries, metric_name) + + # If it's anomalous, add it to list + if anomalous: + base_name = metric_name.replace(settings.FULL_NAMESPACE, '', 1) + metric = [datapoint, base_name] + self.anomalous_metrics.append(metric) + + # Get the anomaly breakdown - who returned True? + triggered_algorithms = [] + for index, value in enumerate(ensemble): + if value: + algorithm = settings.ALGORITHMS[index] + anomaly_breakdown[algorithm] += 1 + triggered_algorithms.append(algorithm) + + # It could have been deleted by the Roomba + except TypeError: + # logger.error('TypeError analysing %s' % metric_name) + exceptions['DeletedByRoomba'] += 1 + except TooShort: + # logger.error('TooShort analysing %s' % metric_name) + exceptions['TooShort'] += 1 + except Stale: + # logger.error('Stale analysing %s' % metric_name) + exceptions['Stale'] += 1 + except Boring: + # logger.error('Boring analysing %s' % metric_name) + exceptions['Boring'] += 1 + except: + # logger.error('Other analysing %s' % metric_name) + exceptions['Other'] += 1 + logger.info(traceback.format_exc()) + + # Add values to the queue so the parent process can collate + for key, value in anomaly_breakdown.items(): + self.anomaly_breakdown_q.put((key, value)) + + for key, value in exceptions.items(): + self.exceptions_q.put((key, value)) + + spin_end = time() - spin_start + logger.info('spin_process took %.2f seconds' % spin_end)
+ +
[docs] def run(self): + """ + Called when the process intializes. + + Determine if Redis is up and discover the number of `unique metrics`. + + Divide the `unique_metrics` between the number of `ANALYZER_PROCESSES` + and assign each process a set of metrics to analyse for anomalies. + + Wait for the processes to finish. + + Process the Determine whether if any anomalous metrics require:\n + * alerting on (and set `EXPIRATION_TIME` key in Redis for alert).\n + * feeding to another module e.g. mirage. + + Populated the webapp json the anomalous_metrics details. + + Log the details about the run to the skyline log. + + Send skyline.analyzer metrics to `GRAPHITE_HOST`, + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os.remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os.remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + if not os.path.exists(settings.SKYLINE_TMP_DIR): + if python_version == 2: + os.makedirs(settings.SKYLINE_TMP_DIR, 0750) + if python_version == 3: + os.makedirs(settings.SKYLINE_TMP_DIR, mode=0o750) + + # Initiate the algorithm timings if Analyzer is configured to send the + # algorithm_breakdown metrics with ENABLE_ALGORITHM_RUN_METRICS + algorithm_tmp_file_prefix = settings.SKYLINE_TMP_DIR + '/' + skyline_app + '.' + algorithms_to_time = [] + if send_algorithm_run_metrics: + algorithms_to_time = settings.ALGORITHMS + + while 1: + now = time() + + # Make sure Redis is up + try: + self.redis_conn.ping() + except: + logger.error('skyline can\'t connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + sleep(10) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + # Report app up + self.redis_conn.setex(skyline_app, 120, now) + + # Discover unique metrics + unique_metrics = list(self.redis_conn.smembers(settings.FULL_NAMESPACE + 'unique_metrics')) + + if len(unique_metrics) == 0: + logger.info('no metrics in redis. try adding some - see README') + sleep(10) + continue + + # Using count files rather that multiprocessing.Value to enable metrics for + # metrics for algorithm run times, etc + for algorithm in algorithms_to_time: + algorithm_count_file = algorithm_tmp_file_prefix + algorithm + '.count' + algorithm_timings_file = algorithm_tmp_file_prefix + algorithm + '.timings' + # with open(algorithm_count_file, 'a') as f: + with open(algorithm_count_file, 'w') as f: + pass + with open(algorithm_timings_file, 'w') as f: + pass + + # Spawn processes + pids = [] + pid_count = 0 + for i in range(1, settings.ANALYZER_PROCESSES + 1): + if i > len(unique_metrics): + logger.info('WARNING: skyline is set for more cores than needed.') + break + + p = Process(target=self.spin_process, args=(i, unique_metrics)) + pids.append(p) + pid_count += 1 + logger.info('starting %s of %s spin_process/es' % (str(pid_count), str(settings.ANALYZER_PROCESSES))) + p.start() + + # Send wait signal to zombie processes + # for p in pids: + # p.join() + # Self monitor processes and terminate if any spin_process has run + # for longer than 180 seconds + p_starts = time() + while time() - p_starts <= 180: + if any(p.is_alive() for p in pids): + # Just to avoid hogging the CPU + sleep(.1) + else: + # All the processes are done, break now. + time_to_run = time() - p_starts + logger.info('%s :: %s spin_process/es completed in %.2f seconds' % (skyline_app, str(settings.ANALYZER_PROCESSES), time_to_run)) + break + else: + # We only enter this if we didn't 'break' above. + logger.info('%s :: timed out, killing all spin_process processes' % (skyline_app)) + for p in pids: + p.terminate() + p.join() + + # Grab data from the queue and populate dictionaries + exceptions = dict() + anomaly_breakdown = dict() + while 1: + try: + key, value = self.anomaly_breakdown_q.get_nowait() + if key not in anomaly_breakdown.keys(): + anomaly_breakdown[key] = value + else: + anomaly_breakdown[key] += value + except Empty: + break + + while 1: + try: + key, value = self.exceptions_q.get_nowait() + if key not in exceptions.keys(): + exceptions[key] = value + else: + exceptions[key] += value + except Empty: + break + + # Push to panorama +# if len(self.panorama_anomalous_metrics) > 0: +# logger.info('to do - push to panorama') + + # Push to crucible +# if len(self.crucible_anomalous_metrics) > 0: +# logger.info('to do - push to crucible') + + # Write anomalous_metrics to static webapp directory + + # Using count files rather that multiprocessing.Value to enable metrics for + # metrics for algorithm run times, etc + for algorithm in algorithms_to_time: + algorithm_count_file = algorithm_tmp_file_prefix + algorithm + '.count' + algorithm_timings_file = algorithm_tmp_file_prefix + algorithm + '.timings' + + try: + algorithm_count_array = [] + with open(algorithm_count_file, 'r') as f: + for line in f: + value_string = line.replace('\n', '') + unquoted_value_string = value_string.replace("'", '') + float_value = float(unquoted_value_string) + algorithm_count_array.append(float_value) + except: + algorithm_count_array = False + + if not algorithm_count_array: + continue + + number_of_times_algorithm_run = len(algorithm_count_array) + logger.info( + 'algorithm run count - %s run %s times' % ( + algorithm, str(number_of_times_algorithm_run))) + if number_of_times_algorithm_run == 0: + continue + + try: + algorithm_timings_array = [] + with open(algorithm_timings_file, 'r') as f: + for line in f: + value_string = line.replace('\n', '') + unquoted_value_string = value_string.replace("'", '') + float_value = float(unquoted_value_string) + algorithm_timings_array.append(float_value) + except: + algorithm_timings_array = False + + if not algorithm_timings_array: + continue + + number_of_algorithm_timings = len(algorithm_timings_array) + logger.info( + 'algorithm timings count - %s has %s timings' % ( + algorithm, str(number_of_algorithm_timings))) + + if number_of_algorithm_timings == 0: + continue + + try: + _sum_of_algorithm_timings = sum(algorithm_timings_array) + except: + logger.error("sum error: " + traceback.format_exc()) + _sum_of_algorithm_timings = round(0.0, 6) + logger.error('error - sum_of_algorithm_timings - %s' % (algorithm)) + continue + + sum_of_algorithm_timings = round(_sum_of_algorithm_timings, 6) + # logger.info('sum_of_algorithm_timings - %s - %.16f seconds' % (algorithm, sum_of_algorithm_timings)) + + try: + _median_algorithm_timing = determine_median(algorithm_timings_array) + except: + _median_algorithm_timing = round(0.0, 6) + logger.error('error - _median_algorithm_timing - %s' % (algorithm)) + continue + median_algorithm_timing = round(_median_algorithm_timing, 6) + # logger.info('median_algorithm_timing - %s - %.16f seconds' % (algorithm, median_algorithm_timing)) + + logger.info( + 'algorithm timing - %s - total: %.6f - median: %.6f' % ( + algorithm, sum_of_algorithm_timings, + median_algorithm_timing)) + send_mertic_name = 'algorithm_breakdown.' + algorithm + '.timing.times_run' + self.send_graphite_metric(send_mertic_name, '%d' % number_of_algorithm_timings) + send_mertic_name = 'algorithm_breakdown.' + algorithm + '.timing.total_time' + self.send_graphite_metric(send_mertic_name, '%.6f' % sum_of_algorithm_timings) + send_mertic_name = 'algorithm_breakdown.' + algorithm + '.timing.median_time' + self.send_graphite_metric(send_mertic_name, '%.6f' % median_algorithm_timing) + + # Log progress + logger.info('seconds to run :: %.2f' % (time() - now)) + logger.info('total metrics :: %d' % len(unique_metrics)) + logger.info('total analyzed :: %d' % (len(unique_metrics) - sum(exceptions.values()))) + logger.info('total anomalies :: %d' % len(self.anomalous_metrics)) + logger.info('exception stats :: %s' % exceptions) + logger.info('anomaly breakdown :: %s' % anomaly_breakdown) + + # Log to Graphite + self.send_graphite_metric('run_time', '%.2f' % (time() - now)) + self.send_graphite_metric('total_analyzed', '%.2f' % (len(unique_metrics) - sum(exceptions.values()))) + self.send_graphite_metric('total_anomalies', '%d' % len(self.anomalous_metrics)) + self.send_graphite_metric('total_metrics', '%d' % len(unique_metrics)) + for key, value in exceptions.items(): + send_metric = 'exceptions.%s' % key + self.send_graphite_metric(send_metric, '%d' % value) + for key, value in anomaly_breakdown.items(): + send_metric = 'anomaly_breakdown.%s' % key + self.send_graphite_metric(send_metric, '%d' % value) + + # Check canary metric + raw_series = self.redis_conn.get(settings.FULL_NAMESPACE + settings.CANARY_METRIC) + if raw_series is not None: + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + time_human = (timeseries[-1][0] - timeseries[0][0]) / 3600 + projected = 24 * (time() - now) / time_human + + logger.info('canary duration :: %.2f' % time_human) + self.send_graphite_metric('duration', '%.2f' % time_human) + self.send_graphite_metric('projected', '%.2f' % projected) + + # Reset counters + self.anomalous_metrics[:] = [] + + # Sleep if it went too fast + # if time() - now < 5: + # logger.info('sleeping due to low run time...') + # sleep(10) + # @modified 20160504 - @earthgecko - development internal ref #1338, #1340) + # Etsy's original if this was a value of 5 seconds which does + # not make skyline Analyzer very efficient in terms of installations + # where 100s of 1000s of metrics are being analyzed. This lead to + # Analyzer running over several metrics multiple time in a minute + # and always working. Therefore this was changed from if you took + # less than 5 seconds to run only then sleep. This behaviour + # resulted in Analyzer analysing a few 1000 metrics in 9 seconds and + # then doing it again and again in a single minute. Therefore the + # ANALYZER_OPTIMUM_RUN_DURATION setting was added to allow this to + # self optimise in cases where skyline is NOT deployed to analyze + # 100s of 1000s of metrics. This relates to optimising performance + # for any deployments in the few 1000s and 60 second resolution + # area, e.g. smaller and local deployments. + process_runtime = time() - now + analyzer_optimum_run_duration = settings.ANALYZER_OPTIMUM_RUN_DURATION + if process_runtime < analyzer_optimum_run_duration: + sleep_for = (analyzer_optimum_run_duration - process_runtime) + # sleep_for = 60 + logger.info('sleeping for %.2f seconds due to low run time...' % sleep_for) + sleep(sleep_for)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/boundary/agent.html b/docs/_build/html/_modules/boundary/agent.html new file mode 100644 index 00000000..8a507d89 --- /dev/null +++ b/docs/_build/html/_modules/boundary/agent.html @@ -0,0 +1,373 @@ + + + + + + + + + + + boundary.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for boundary.agent

+import logging
+import sys
+import traceback
+from os import getpid
+from os.path import dirname, abspath, isdir
+from daemon import runner
+from time import sleep, time
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+from boundary import Boundary
+from boundary_algorithms import *
+
+skyline_app = 'boundary'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+
+
[docs]class BoundaryAgent(): + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + logger.info('agent starting skyline %s' % skyline_app) + Boundary(getpid()).start() + + while 1: + sleep(100)
+ + +
[docs]def run(): + """ + Start the Boundary agent. + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + # Make sure all the BOUNDARY_ALGORITHMS are valid + try: + if settings.BOUNDARY_ALGORITHMS: + configuration_error = False + for algorithm in settings.BOUNDARY_ALGORITHMS: + valid = True + if not isinstance(algorithm, str): + valid = False + + if not valid: + configuration_error = True + print ('configuration error in tuple, expected: str') + print('configuration error in BOUNDARY_ALGORITHMS tuple: %s' % str(algorithm)) + except: + try: + if configuration_error: + print ('There are configuration issues in BOUNDARY_ALGORITHMS in settings.py') + except: + print ('There are no BOUNDARY_ALGORITHMS in settings.py. try adding some, nothing to do') + sys.exit(1) + + # Make sure we can run all the algorithms + try: + timeseries = map(list, zip(map(float, range(int(time()) - 86400, int(time()) + 1)), [1] * 86401)) + ensemble = [globals()[algorithm]( + timeseries, 'test', 3600, 100, 300, 1 + ) for algorithm in settings.BOUNDARY_ALGORITHMS] + except KeyError as e: + print ('Algorithm %s deprecated or not defined; check settings.BOUNDARY_ALGORITHMS' % e) + sys.exit(1) + except Exception as e: + print ('Algorithm test run failed.') + traceback.print_exc() + sys.exit(1) + + # Make sure all the BOUNDARY_METRICS are valid + try: + if settings.BOUNDARY_METRICS: + configuration_error = False + for metric in settings.BOUNDARY_METRICS: + valid = True + strings = [] + strings.append(metric[0]) + strings.append(metric[1]) + strings.append(metric[7]) + for string in strings: + if not isinstance(string, str): + valid = False + + values = [] + values.append(metric[2]) + values.append(metric[3]) + values.append(metric[4]) + values.append(metric[5]) + values.append(metric[6]) + for value in values: + if not isinstance(value, int): + valid = False + + alert_via = metric[7] + for alerter in alert_via.split("|"): + if not isinstance(alerter, str): + valid = False + + if not valid: + configuration_error = True + print ('configuration error in tuple, expected: str, str, int, int, int, int, str') + print('configuration error in BOUNDARY_METRICS tuple: %s' % str(metric)) + except: + if configuration_error: + print ('There are configuration issues in BOUNDARY_METRICS in settings.py') + else: + print ('There are no BOUNDARY_METRICS in settings.py. try adding some, nothing to do') + + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(256, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + boundary = BoundaryAgent() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + boundary.run() + else: + daemon_runner = runner.DaemonRunner(boundary) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action()
+ +if __name__ == "__main__": + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/boundary/boundary.html b/docs/_build/html/_modules/boundary/boundary.html new file mode 100644 index 00000000..fc23ece5 --- /dev/null +++ b/docs/_build/html/_modules/boundary/boundary.html @@ -0,0 +1,1156 @@ + + + + + + + + + + + boundary.boundary — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for boundary.boundary

+import logging
+try:
+    from Queue import Empty
+except:
+    from queue import Empty
+from redis import StrictRedis
+from time import time, sleep
+from threading import Thread
+from collections import defaultdict
+from multiprocessing import Process, Manager, Queue
+from msgpack import Unpacker, unpackb, packb
+from os import path, kill, getpid, system, listdir
+from os.path import dirname, join, abspath, isfile
+from math import ceil
+import traceback
+import operator
+import socket
+import re
+import os
+import errno
+
+import sys
+import os.path
+import settings
+from skyline_functions import send_graphite_metric, write_data_to_file
+
+from boundary_alerters import trigger_alert
+from boundary_algorithms import run_selected_algorithm
+from algorithm_exceptions import *
+
+skyline_app = 'boundary'
+skyline_app_logger = skyline_app + 'Log'
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+python_version = int(sys.version_info[0])
+this_host = str(os.uname()[1])
+
+try:
+    SERVER_METRIC_PATH = '.' + settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.' + skyline_app + SERVER_METRIC_PATH
+
+REDIS_SOCKET = settings.REDIS_SOCKET_PATH
+BOUNDARY_METRICS = settings.BOUNDARY_METRICS
+FULL_NAMESPACE = settings.FULL_NAMESPACE
+ENABLE_BOUNDARY_DEBUG = settings.ENABLE_BOUNDARY_DEBUG
+try:
+    BOUNDARY_AUTOAGGRERATION = settings.BOUNDARY_AUTOAGGRERATION
+except:
+    BOUNDARY_AUTOAGGRERATION = False
+try:
+    BOUNDARY_AUTOAGGRERATION_METRICS = settings.BOUNDARY_AUTOAGGRERATION_METRICS
+except:
+    BOUNDARY_AUTOAGGRERATION_METRICS = (
+        ("auotaggeration_metrics_not_declared", 60)
+    )
+
+
+
[docs]class Boundary(Thread): + def __init__(self, parent_pid): + """ + Initialize the Boundary + """ + super(Boundary, self).__init__() + self.redis_conn = StrictRedis(unix_socket_path=REDIS_SOCKET) + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.boundary_metrics = Manager().list() + self.anomalous_metrics = Manager().list() + self.exceptions_q = Queue() + self.anomaly_breakdown_q = Queue() + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def unique_noHash(self, seq): + seen = set() + return [x for x in seq if str(x) not in seen and not seen.add(str(x))]
+ + # This is to make a dump directory in /tmp if ENABLE_BOUNDARY_DEBUG is True + # for dumping the metric timeseries data into for debugging purposes +
[docs] def mkdir_p(self, path): + try: + os.makedirs(path) + return True + except OSError as exc: + # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise
+ +
[docs] def spin_process(self, i, boundary_metrics): + """ + Assign a bunch of metrics for a process to analyze. + """ + # Determine assigned metrics + bp = settings.BOUNDARY_PROCESSES + bm_range = len(boundary_metrics) + keys_per_processor = int(ceil(float(bm_range) / float(bp))) + if i == settings.BOUNDARY_PROCESSES: + assigned_max = len(boundary_metrics) + else: + # This is a skyine bug, the original skyline code uses 1 as the + # beginning position of the index, python indices begin with 0 + # assigned_max = len(boundary_metrics) + # This closes the etsy/skyline pull request opened by @languitar on 17 Jun 2014 + # https://github.com/etsy/skyline/pull/94 Fix analyzer worker metric assignment + assigned_max = min(len(boundary_metrics), i * keys_per_processor) + assigned_min = (i - 1) * keys_per_processor + assigned_keys = range(assigned_min, assigned_max) + + # Compile assigned metrics + assigned_metrics_and_algos = [boundary_metrics[index] for index in assigned_keys] + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: printing assigned_metrics_and_algos') + for assigned_metric_and_algo in assigned_metrics_and_algos: + logger.info('debug :: assigned_metric_and_algo - %s' % str(assigned_metric_and_algo)) + + # Compile assigned metrics + assigned_metrics = [] + for i in assigned_metrics_and_algos: + assigned_metrics.append(i[0]) + + # unique unhashed things + def unique_noHash(seq): + seen = set() + return [x for x in seq if str(x) not in seen and not seen.add(str(x))] + + unique_assigned_metrics = unique_noHash(assigned_metrics) + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: unique_assigned_metrics - %s' % str(unique_assigned_metrics)) + logger.info('debug :: printing unique_assigned_metrics:') + for unique_assigned_metric in unique_assigned_metrics: + logger.info('debug :: unique_assigned_metric - %s' % str(unique_assigned_metric)) + + # Check if this process is unnecessary + if len(unique_assigned_metrics) == 0: + return + + # Multi get series + try: + raw_assigned = self.redis_conn.mget(unique_assigned_metrics) + except: + logger.error('error :: failed to mget assigned_metrics from redis') + return + + # Make process-specific dicts + exceptions = defaultdict(int) + anomaly_breakdown = defaultdict(int) + + # Reset boundary_algortims + all_boundary_algorithms = [] + for metric in BOUNDARY_METRICS: + all_boundary_algorithms.append(metric[1]) + + # The unique algorithms that are being used + boundary_algorithms = unique_noHash(all_boundary_algorithms) + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: boundary_algorithms - %s' % str(boundary_algorithms)) + + discover_run_metrics = [] + + # Distill metrics into a run list + for i, metric_name, in enumerate(unique_assigned_metrics): + self.check_if_parent_is_alive() + + try: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: unpacking timeseries for %s - %s' % (metric_name, str(i))) + raw_series = raw_assigned[i] + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + except Exception as e: + exceptions['Other'] += 1 + logger.error('error :: redis data error: ' + traceback.format_exc()) + logger.error('error :: %e' % e) + + base_name = metric_name.replace(FULL_NAMESPACE, '', 1) + + # Determine the metrics BOUNDARY_METRICS metric tuple settings + for metrick in BOUNDARY_METRICS: + CHECK_MATCH_PATTERN = metrick[0] + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + pattern_match = check_match_pattern.match(base_name) + metric_pattern_matched = False + if pattern_match: + metric_pattern_matched = True + algo_pattern_matched = False + for algo in boundary_algorithms: + for metric in BOUNDARY_METRICS: + CHECK_MATCH_PATTERN = metric[0] + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + pattern_match = check_match_pattern.match(base_name) + if pattern_match: + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: metric and algo pattern MATCHED - " + metric[0] + " | " + base_name + " | " + str(metric[1])) + metric_expiration_time = False + metric_min_average = False + metric_min_average_seconds = False + metric_trigger = False + algorithm = False + algo_pattern_matched = True + algorithm = metric[1] + try: + if metric[2]: + metric_expiration_time = metric[2] + except: + metric_expiration_time = False + try: + if metric[3]: + metric_min_average = metric[3] + except: + metric_min_average = False + try: + if metric[4]: + metric_min_average_seconds = metric[4] + except: + metric_min_average_seconds = 1200 + try: + if metric[5]: + metric_trigger = metric[5] + except: + metric_trigger = False + try: + if metric[6]: + alert_threshold = metric[6] + except: + alert_threshold = False + try: + if metric[7]: + metric_alerters = metric[7] + except: + metric_alerters = False + if metric_pattern_matched and algo_pattern_matched: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: added metric - %s, %s, %s, %s, %s, %s, %s, %s, %s' % (str(i), metric_name, str(metric_expiration_time), str(metric_min_average), str(metric_min_average_seconds), str(metric_trigger), str(alert_threshold), metric_alerters, algorithm)) + discover_run_metrics.append([i, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters, algorithm]) + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: printing discover_run_metrics') + for discover_run_metric in discover_run_metrics: + logger.info('debug :: discover_run_metrics - %s' % str(discover_run_metric)) + logger.info('debug :: build unique boundary metrics to analyze') + + # Determine the unique set of metrics to run + run_metrics = unique_noHash(discover_run_metrics) + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: printing run_metrics') + for run_metric in run_metrics: + logger.info('debug :: run_metrics - %s' % str(run_metric)) + + # Distill timeseries strings and submit to run_selected_algorithm + for metric_and_algo in run_metrics: + self.check_if_parent_is_alive() + + try: + raw_assigned_id = metric_and_algo[0] + metric_name = metric_and_algo[1] + base_name = metric_name.replace(FULL_NAMESPACE, '', 1) + metric_expiration_time = metric_and_algo[2] + metric_min_average = metric_and_algo[3] + metric_min_average_seconds = metric_and_algo[4] + metric_trigger = metric_and_algo[5] + alert_threshold = metric_and_algo[6] + metric_alerters = metric_and_algo[7] + algorithm = metric_and_algo[8] + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: unpacking timeseries for %s - %s' % (metric_name, str(raw_assigned_id))) + + raw_series = raw_assigned[metric_and_algo[0]] + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: unpacked OK - %s - %s' % (metric_name, str(raw_assigned_id))) + + autoaggregate = False + autoaggregate_value = 0 + + # Determine if the namespace is to be aggregated + if BOUNDARY_AUTOAGGRERATION: + for autoaggregate_metric in BOUNDARY_AUTOAGGRERATION_METRICS: + autoaggregate = False + autoaggregate_value = 0 + CHECK_MATCH_PATTERN = autoaggregate_metric[0] + base_name = metric_name.replace(FULL_NAMESPACE, '', 1) + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + pattern_match = check_match_pattern.match(base_name) + if pattern_match: + autoaggregate = True + autoaggregate_value = autoaggregate_metric[1] + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: BOUNDARY_AUTOAGGRERATION passed - %s - %s' % (metric_name, str(autoaggregate))) + + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'debug :: analysing - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s' % ( + metric_name, str(metric_expiration_time), + str(metric_min_average), + str(metric_min_average_seconds), + str(metric_trigger), str(alert_threshold), + metric_alerters, autoaggregate, + autoaggregate_value, algorithm) + ) + # Dump the the timeseries data to a file + timeseries_dump_dir = "/tmp/skyline/boundary/" + algorithm + self.mkdir_p(timeseries_dump_dir) + timeseries_dump_file = timeseries_dump_dir + "/" + metric_name + ".json" + with open(timeseries_dump_file, 'w+') as f: + f.write(str(timeseries)) + f.close() + + # Check if a metric has its own unique BOUNDARY_METRICS alert + # tuple, this allows us to paint an entire metric namespace with + # the same brush AND paint a unique metric or namespace with a + # different brush or scapel + has_unique_tuple = False + run_tupple = False + boundary_metric_tuple = (base_name, algorithm, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters) + wildcard_namespace = True + for metric_tuple in BOUNDARY_METRICS: + if not has_unique_tuple: + CHECK_MATCH_PATTERN = metric_tuple[0] + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + pattern_match = check_match_pattern.match(base_name) + if pattern_match: + if metric_tuple[0] == base_name: + wildcard_namespace = False + if not has_unique_tuple: + if boundary_metric_tuple == metric_tuple: + has_unique_tuple = True + run_tupple = True + if ENABLE_BOUNDARY_DEBUG: + logger.info('unique_tuple:') + logger.info('boundary_metric_tuple: %s' % str(boundary_metric_tuple)) + logger.info('metric_tuple: %s' % str(metric_tuple)) + + if not has_unique_tuple: + if wildcard_namespace: + if ENABLE_BOUNDARY_DEBUG: + logger.info('wildcard_namespace:') + logger.info('boundary_metric_tuple: %s' % str(boundary_metric_tuple)) + run_tupple = True + else: + if ENABLE_BOUNDARY_DEBUG: + logger.info('wildcard_namespace: BUT WOULD NOT RUN') + logger.info('boundary_metric_tuple: %s' % str(boundary_metric_tuple)) + + if ENABLE_BOUNDARY_DEBUG: + logger.info('WOULD RUN run_selected_algorithm = %s' % run_tupple) + + if run_tupple: + # Submit the timeseries and settings to run_selected_algorithm + anomalous, ensemble, datapoint, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters, algorithm = run_selected_algorithm( + timeseries, metric_name, + metric_expiration_time, + metric_min_average, + metric_min_average_seconds, + metric_trigger, + alert_threshold, + metric_alerters, + autoaggregate, + autoaggregate_value, + algorithm + ) + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: analysed - %s' % (metric_name)) + else: + anomalous = False + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: more unique metric tuple not analysed - %s' % (metric_name)) + + # If it's anomalous, add it to list + if anomalous: + anomalous_metric = [datapoint, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters, algorithm] + self.anomalous_metrics.append(anomalous_metric) + # Get the anomaly breakdown - who returned True? + triggered_algorithms = [] + for index, value in enumerate(ensemble): + if value: + anomaly_breakdown[algorithm] += 1 + triggered_algorithms.append(algorithm) + + # If Crucible or Panorama are enabled determine details + determine_anomaly_details = False + if settings.ENABLE_CRUCIBLE and settings.BOUNDARY_CRUCIBLE_ENABLED: + determine_anomaly_details = True + if settings.PANORAMA_ENABLED: + determine_anomaly_details = True + + if determine_anomaly_details: + metric_timestamp = str(int(timeseries[-1][0])) + from_timestamp = str(int(timeseries[1][0])) + timeseries_dir = base_name.replace('.', '/') + + # If Panorama is enabled - create a Panorama check + if settings.PANORAMA_ENABLED: + # Note: + # The values are enclosed is single quoted intentionally + # as the imp.load_source used results in a shift in the + # decimal position when double quoted, e.g. + # value = "5622.0" gets imported as + # 2016-03-02 12:53:26 :: 28569 :: metric variable - value - 562.2 + # single quoting results in the desired, + # 2016-03-02 13:16:17 :: 1515 :: metric variable - value - 5622.0 + added_at = str(int(time())) + source = 'graphite' + panaroma_anomaly_data = 'metric = \'%s\'\n' \ + 'value = \'%s\'\n' \ + 'from_timestamp = \'%s\'\n' \ + 'metric_timestamp = \'%s\'\n' \ + 'algorithms = [\'%s\']\n' \ + 'triggered_algorithms = [\'%s\']\n' \ + 'app = \'%s\'\n' \ + 'source = \'%s\'\n' \ + 'added_by = \'%s\'\n' \ + 'added_at = \'%s\'\n' \ + % (base_name, str(datapoint), from_timestamp, + metric_timestamp, str(algorithm), str(algorithm), + skyline_app, source, this_host, added_at) + + # Create an anomaly file with details about the anomaly + panaroma_anomaly_file = '%s/%s.%s.txt' % ( + settings.PANORAMA_CHECK_PATH, added_at, + base_name) + try: + write_data_to_file( + skyline_app, panaroma_anomaly_file, 'w', + panaroma_anomaly_data) + logger.info('added panorama anomaly file :: %s' % (panaroma_anomaly_file)) + except: + logger.error('error :: failed to add panorama anomaly file :: %s' % (panaroma_anomaly_file)) + logger.info(traceback.format_exc()) + + # If crucible is enabled - save timeseries and create a + # crucible check + if settings.ENABLE_CRUCIBLE and settings.BOUNDARY_CRUCIBLE_ENABLED: + crucible_anomaly_dir = settings.CRUCIBLE_DATA_FOLDER + '/' + timeseries_dir + '/' + metric_timestamp + if not os.path.exists(crucible_anomaly_dir): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(crucible_anomaly_dir, mode_arg) + + # Note: + # Due to only one algorithm triggering here the + # algorithm related arrays here are a different format + # to there output format in analyzer + + # Note: + # The value is enclosed is single quoted intentionally + # as the imp.load_source used in crucible results in a + # shift in the decimal position when double quoted, e.g. + # value = "5622.0" gets imported as + # 2016-03-02 12:53:26 :: 28569 :: metric variable - value - 562.2 + # single quoting results in the desired, + # 2016-03-02 13:16:17 :: 1515 :: metric variable - value - 5622.0 + + crucible_anomaly_data = 'metric = \'%s\'\n' \ + 'value = \'%s\'\n' \ + 'from_timestamp = \'%s\'\n' \ + 'metric_timestamp = \'%s\'\n' \ + 'algorithms = %s\n' \ + 'triggered_algorithms = %s\n' \ + 'anomaly_dir = \'%s\'\n' \ + 'graphite_metric = True\n' \ + 'run_crucible_tests = False\n' \ + 'added_by = \'%s\'\n' \ + 'added_at = \'%s\'\n' \ + % (base_name, str(datapoint), from_timestamp, + metric_timestamp, str(algorithm), + triggered_algorithms, crucible_anomaly_dir, + skyline_app, metric_timestamp) + + # Create an anomaly file with details about the anomaly + crucible_anomaly_file = '%s/%s.txt' % (crucible_anomaly_dir, base_name) + with open(crucible_anomaly_file, 'w') as fh: + fh.write(crucible_anomaly_data) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(crucible_anomaly_file, mode_arg) + logger.info('added crucible anomaly file :: %s/%s.txt' % (crucible_anomaly_dir, base_name)) + + # Create timeseries json file with the timeseries + json_file = '%s/%s.json' % (crucible_anomaly_dir, base_name) + timeseries_json = str(timeseries).replace('[', '(').replace(']', ')') + with open(json_file, 'w') as fh: + # timeseries + fh.write(timeseries_json) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(json_file, mode_arg) + logger.info('added crucible timeseries file :: %s/%s.json' % (crucible_anomaly_dir, base_name)) + + # Create a crucible check file + crucible_check_file = '%s/%s.%s.txt' % (settings.CRUCIBLE_CHECK_PATH, metric_timestamp, base_name) + with open(crucible_check_file, 'w') as fh: + fh.write(crucible_anomaly_data) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(crucible_check_file, mode_arg) + logger.info('added crucible check :: %s,%s' % (base_name, metric_timestamp)) + + # It could have been deleted by the Roomba + except TypeError: + exceptions['DeletedByRoomba'] += 1 + except TooShort: + exceptions['TooShort'] += 1 + except Stale: + exceptions['Stale'] += 1 + except Boring: + exceptions['Boring'] += 1 + except: + exceptions['Other'] += 1 + logger.info("exceptions['Other'] traceback follows:") + logger.info(traceback.format_exc()) + + # Add values to the queue so the parent process can collate + for key, value in anomaly_breakdown.items(): + self.anomaly_breakdown_q.put((key, value)) + + for key, value in exceptions.items(): + self.exceptions_q.put((key, value))
+ +
[docs] def run(self): + """ + Called when the process intializes. + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os.remove(skyline_app_logwait) + except OSError: + logger.error('error :: failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error :: bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os.remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error :: failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + while 1: + now = time() + + # Make sure Redis is up + try: + self.redis_conn.ping() + except: + logger.error('error :: skyline cannot connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + sleep(10) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + # Report app up + self.redis_conn.setex(skyline_app, 120, now) + + # Discover unique metrics + unique_metrics = list(self.redis_conn.smembers(settings.FULL_NAMESPACE + 'unique_metrics')) + + if len(unique_metrics) == 0: + logger.info('no metrics in redis. try adding some - see README') + sleep(10) + continue + + # Reset boundary_metrics + boundary_metrics = [] + + # Build boundary metrics + for metric_name in unique_metrics: + for metric in BOUNDARY_METRICS: + CHECK_MATCH_PATTERN = metric[0] + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + base_name = metric_name.replace(settings.FULL_NAMESPACE, '', 1) + pattern_match = check_match_pattern.match(base_name) + if pattern_match: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: boundary metric - pattern MATCHED - ' + metric[0] + " | " + base_name) + boundary_metrics.append([metric_name, metric[1]]) + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: boundary metrics - ' + str(boundary_metrics)) + + if len(boundary_metrics) == 0: + logger.info('no Boundary metrics in redis. try adding some - see README') + sleep(10) + continue + + # Spawn processes + pids = [] + for i in range(1, settings.BOUNDARY_PROCESSES + 1): + if i > len(boundary_metrics): + logger.info('WARNING: Skyline Boundary is set for more cores than needed.') + break + + p = Process(target=self.spin_process, args=(i, boundary_metrics)) + pids.append(p) + p.start() + + # Send wait signal to zombie processes + for p in pids: + p.join() + + # Grab data from the queue and populate dictionaries + exceptions = dict() + anomaly_breakdown = dict() + while 1: + try: + key, value = self.anomaly_breakdown_q.get_nowait() + if key not in anomaly_breakdown.keys(): + anomaly_breakdown[key] = value + else: + anomaly_breakdown[key] += value + except Empty: + break + + while 1: + try: + key, value = self.exceptions_q.get_nowait() + if key not in exceptions.keys(): + exceptions[key] = value + else: + exceptions[key] += value + except Empty: + break + + # Send alerts + if settings.BOUNDARY_ENABLE_ALERTS: + for anomalous_metric in self.anomalous_metrics: + datapoint = str(anomalous_metric[0]) + metric_name = anomalous_metric[1] + base_name = metric_name.replace(FULL_NAMESPACE, '', 1) + expiration_time = str(anomalous_metric[2]) + metric_trigger = str(anomalous_metric[5]) + alert_threshold = int(anomalous_metric[6]) + metric_alerters = anomalous_metric[7] + algorithm = anomalous_metric[8] + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: anomalous_metric - " + str(anomalous_metric)) + + # Determine how many times has the anomaly been seen if the + # ALERT_THRESHOLD is set to > 1 and create a cache key in + # redis to keep count so that alert_threshold can be honored + if alert_threshold == 0: + times_seen = 1 + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: alert_threshold - " + str(alert_threshold)) + + if alert_threshold == 1: + times_seen = 1 + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: alert_threshold - " + str(alert_threshold)) + + if alert_threshold > 1: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: alert_threshold - ' + str(alert_threshold)) + anomaly_cache_key_count_set = False + anomaly_cache_key_expiration_time = (int(alert_threshold) + 1) * 60 + anomaly_cache_key = 'anomaly_seen.%s.%s' % (algorithm, base_name) + try: + anomaly_cache_key_count = self.redis_conn.get(anomaly_cache_key) + if not anomaly_cache_key_count: + try: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: redis no anomaly_cache_key - ' + str(anomaly_cache_key)) + times_seen = 1 + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: redis setex anomaly_cache_key - ' + str(anomaly_cache_key)) + self.redis_conn.setex(anomaly_cache_key, anomaly_cache_key_expiration_time, packb(int(times_seen))) + logger.info('set anomaly seen key :: %s seen %s' % (anomaly_cache_key, str(times_seen))) + except Exception as e: + logger.error('error :: redis setex failed :: %s' % str(anomaly_cache_key)) + logger.error('error :: could not set key: %s' % e) + else: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: redis anomaly_cache_key retrieved OK - ' + str(anomaly_cache_key)) + anomaly_cache_key_count_set = True + except: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: redis failed - anomaly_cache_key retrieval failed - ' + str(anomaly_cache_key)) + anomaly_cache_key_count_set = False + + if anomaly_cache_key_count_set: + unpacker = Unpacker(use_list=False) + unpacker.feed(anomaly_cache_key_count) + raw_times_seen = list(unpacker) + times_seen = int(raw_times_seen[0]) + 1 + try: + self.redis_conn.setex(anomaly_cache_key, anomaly_cache_key_expiration_time, packb(int(times_seen))) + logger.info('error :: set anomaly seen key :: %s seen %s' % (anomaly_cache_key, str(times_seen))) + except: + times_seen = 1 + logger.error('error :: set anomaly seen key failed :: %s seen %s' % (anomaly_cache_key, str(times_seen))) + + # Alert the alerters if times_seen > alert_threshold + if times_seen >= alert_threshold: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: times_seen %s is greater than or equal to alert_threshold %s' % (str(times_seen), str(alert_threshold))) + for alerter in metric_alerters.split("|"): + # Determine alerter limits + send_alert = False + alerts_sent = 0 + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: checking alerter - %s' % alerter) + try: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: determining alerter_expiration_time for settings') + alerter_expiration_time_setting = settings.BOUNDARY_ALERTER_OPTS['alerter_expiration_time'][alerter] + alerter_expiration_time = int(alerter_expiration_time_setting) + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: determined alerter_expiration_time from settings - %s' % str(alerter_expiration_time)) + except: + # Set an arbitrary expiry time if not set + alerter_expiration_time = 160 + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: could not determine alerter_expiration_time from settings") + try: + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: determining alerter_limit from settings") + alerter_limit_setting = settings.BOUNDARY_ALERTER_OPTS['alerter_limit'][alerter] + alerter_limit = int(alerter_limit_setting) + alerter_limit_set = True + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: determined alerter_limit from settings - %s" % str(alerter_limit)) + except: + alerter_limit_set = False + send_alert = True + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: could not determine alerter_limit from settings") + + # If the alerter_limit is set determine how many + # alerts the alerter has sent + if alerter_limit_set: + alerter_sent_count_key = 'alerts_sent.%s' % (alerter) + try: + alerter_sent_count_key_data = self.redis_conn.get(alerter_sent_count_key) + if not alerter_sent_count_key_data: + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: redis no alerter key, no alerts sent for - " + str(alerter_sent_count_key)) + alerts_sent = 0 + send_alert = True + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: alerts_sent set to %s" % str(alerts_sent)) + logger.info("debug :: send_alert set to %s" % str(sent_alert)) + else: + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: redis alerter key retrieved, unpacking" + str(alerter_sent_count_key)) + unpacker = Unpacker(use_list=False) + unpacker.feed(alerter_sent_count_key_data) + raw_alerts_sent = list(unpacker) + alerts_sent = int(raw_alerts_sent[0]) + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: alerter %s alerts sent %s " % (str(alerter), str(alerts_sent))) + except: + logger.info("No key set - %s" % alerter_sent_count_key) + alerts_sent = 0 + send_alert = True + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: alerts_sent set to %s" % str(alerts_sent)) + logger.info("debug :: send_alert set to %s" % str(send_alert)) + + if alerts_sent < alerter_limit: + send_alert = True + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: alerts_sent %s is less than alerter_limit %s" % (str(alerts_sent), str(alerter_limit))) + logger.info("debug :: send_alert set to %s" % str(send_alert)) + + # Send alert + alerter_alert_sent = False + if send_alert: + cache_key = 'last_alert.boundary.%s.%s.%s' % (alerter, base_name, algorithm) + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: checking cache_key - %s" % cache_key) + try: + last_alert = self.redis_conn.get(cache_key) + if not last_alert: + try: + self.redis_conn.setex(cache_key, int(anomalous_metric[2]), packb(int(anomalous_metric[0]))) + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: key setex OK - %s' % (cache_key)) + trigger_alert(alerter, datapoint, base_name, expiration_time, metric_trigger, algorithm) + logger.info('alert sent :: %s - %s - via %s - %s' % (base_name, datapoint, alerter, algorithm)) + trigger_alert("syslog", datapoint, base_name, expiration_time, metric_trigger, algorithm) + logger.info('alert sent :: %s - %s - via syslog - %s' % (base_name, datapoint, algorithm)) + alerter_alert_sent = True + except Exception as e: + logger.error('error :: alert failed :: %s - %s - via %s - %s' % (base_name, datapoint, alerter, algorithm)) + logger.error('error :: could not send alert: %s' % str(e)) + trigger_alert('syslog', datapoint, base_name, expiration_time, metric_trigger, algorithm) + else: + if ENABLE_BOUNDARY_DEBUG: + logger.info("debug :: cache_key exists not alerting via %s for %s is less than alerter_limit %s" % (alerter, cache_key)) + trigger_alert("syslog", datapoint, base_name, expiration_time, metric_trigger, algorithm) + logger.info('alert sent :: %s - %s - via syslog - %s' % (base_name, datapoint, algorithm)) + except: + trigger_alert("syslog", datapoint, base_name, expiration_time, metric_trigger, algorithm) + logger.info('alert sent :: %s - %s - via syslog - %s' % (base_name, datapoint, algorithm)) + else: + trigger_alert("syslog", datapoint, base_name, expiration_time, metric_trigger, algorithm) + logger.info('alert sent :: %s - %s - via syslog - %s' % (base_name, datapoint, algorithm)) + + # Update the alerts sent for the alerter cache key, + # to allow for alert limiting + if alerter_alert_sent and alerter_limit_set: + try: + alerter_sent_count_key = 'alerts_sent.%s' % (alerter) + new_alerts_sent = int(alerts_sent) + 1 + self.redis_conn.setex(alerter_sent_count_key, alerter_expiration_time, packb(int(new_alerts_sent))) + logger.info('set %s - %s' % (alerter_sent_count_key, str(new_alerts_sent))) + except: + logger.error('error :: failed to set %s - %s' % (alerter_sent_count_key, str(new_alerts_sent))) + else: + # Always alert to syslog, even if alert_threshold is not + # breached or if send_alert is not True + trigger_alert("syslog", datapoint, base_name, expiration_time, metric_trigger, algorithm) + logger.info('alert sent :: %s - %s - via syslog - %s' % (base_name, datapoint, algorithm)) + + # Write anomalous_metrics to static webapp directory + if len(self.anomalous_metrics) > 0: + filename = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + with open(filename, 'w') as fh: + # Make it JSONP with a handle_data() function + anomalous_metrics = list(self.anomalous_metrics) + anomalous_metrics.sort(key=operator.itemgetter(1)) + fh.write('handle_data(%s)' % anomalous_metrics) + + run_time = time() - now + total_metrics = str(len(boundary_metrics)) + total_analyzed = str(len(boundary_metrics) - sum(exceptions.values())) + total_anomalies = str(len(self.anomalous_metrics)) + + # Log progress + logger.info('seconds to run :: %.2f' % run_time) + logger.info('total metrics :: %s' % total_metrics) + logger.info('total analyzed :: %s' % total_analyzed) + logger.info('total anomalies :: %s' % total_anomalies) + logger.info('exception stats :: %s' % exceptions) + logger.info('anomaly breakdown :: %s' % anomaly_breakdown) + + # Log to Graphite + graphite_run_time = '%.2f' % run_time + send_metric_name = skyline_app_graphite_namespace + '.run_time' + send_graphite_metric(skyline_app, send_metric_name, graphite_run_time) + + send_metric_name = skyline_app_graphite_namespace + '.total_analyzed' + send_graphite_metric(skyline_app, send_metric_name, total_analyzed) + + send_metric_name = skyline_app_graphite_namespace + '.total_anomalies' + send_graphite_metric(skyline_app, send_metric_name, total_anomalies) + + send_metric_name = skyline_app_graphite_namespace + '.total_metrics' + send_graphite_metric(skyline_app, send_metric_name, total_metrics) + for key, value in exceptions.items(): + send_metric_name = '%s.exceptions.%s' % (skyline_app_graphite_namespace, key) + send_graphite_metric(skyline_app, send_metric_name, str(value)) + for key, value in anomaly_breakdown.items(): + send_metric_name = '%s.anomaly_breakdown.%s' % (skyline_app_graphite_namespace, key) + send_graphite_metric(skyline_app, send_metric_name, str(value)) + + # Check canary metric + raw_series = self.redis_conn.get(settings.FULL_NAMESPACE + settings.CANARY_METRIC) + if raw_series is not None: + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = list(unpacker) + time_human = (timeseries[-1][0] - timeseries[0][0]) / 3600 + projected = 24 * (time() - now) / time_human + + logger.info('canary duration :: %.2f' % time_human) + send_metric_name = skyline_app_graphite_namespace + '.duration' + send_graphite_metric(skyline_app, send_metric_name, str(time_human)) + + send_metric_name = skyline_app_graphite_namespace + '.projected' + send_graphite_metric(skyline_app, send_metric_name, str(projected)) + + # Reset counters + self.anomalous_metrics[:] = [] + + # Only run once per + process_runtime = time() - now + try: + boundary_optimum_run_duration = settings.BOUNDARY_OPTIMUM_RUN_DURATION + except: + boundary_optimum_run_duration = 60 + + if process_runtime < boundary_optimum_run_duration: + sleep_for = (boundary_optimum_run_duration - process_runtime) + logger.info('sleeping %.2f for seconds' % sleep_for) + sleep(sleep_for)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/boundary/boundary_alerters.html b/docs/_build/html/_modules/boundary/boundary_alerters.html new file mode 100644 index 00000000..1685b68c --- /dev/null +++ b/docs/_build/html/_modules/boundary/boundary_alerters.html @@ -0,0 +1,445 @@ + + + + + + + + + + + boundary.boundary_alerters — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for boundary.boundary_alerters

+from smtplib import SMTP
+import boundary_alerters
+try:
+    import urllib2
+except ImportError:
+    import urllib.request
+    import urllib.error
+import re
+from requests.utils import quote
+
+import os.path
+import sys
+python_version = int(sys.version_info[0])
+if python_version == 2:
+    from email.MIMEMultipart import MIMEMultipart
+    from email.MIMEText import MIMEText
+    from email.MIMEImage import MIMEImage
+if python_version == 3:
+    from email.mime.multipart import MIMEMultipart
+    from email.mime.text import MIMEText
+    from email.mime.image import MIMEImage
+
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+"""
+Create any alerter you want here. The function is invoked from trigger_alert.
+4 arguments will be passed in as strings:
+datapoint, metric_name, expiration_time, algorithm
+"""
+
+# FULL_DURATION to hours so that Boundary surfaces the relevant timeseries data
+# in the graph
+try:
+    full_duration_seconds = int(settings.FULL_DURATION)
+except:
+    full_duration_seconds = 86400
+full_duration_in_hours = full_duration_seconds / 60 / 60
+
+try:
+    graphite_previous_hours = int(settings.BOUNDARY_SMTP_OPTS['graphite_previous_hours'])
+except:
+    graphite_previous_hours = full_duration_in_hours
+
+try:
+    graphite_graph_line_color = int(settings.BOUNDARY_SMTP_OPTS['graphite_graph_line_color'])
+except:
+    graphite_graph_line_color = 'pink'
+
+
+
[docs]def alert_smtp(datapoint, metric_name, expiration_time, metric_trigger, algorithm): + + sender = settings.BOUNDARY_SMTP_OPTS['sender'] + + matched_namespaces = [] + for namespace in settings.BOUNDARY_SMTP_OPTS['recipients']: + CHECK_MATCH_PATTERN = namespace + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + pattern_match = check_match_pattern.match(metric_name) + if pattern_match: + matched_namespaces.append(namespace) + matched_recipients = [] + for namespace in matched_namespaces: + for recipients in settings.BOUNDARY_SMTP_OPTS['recipients'][namespace]: + matched_recipients.append(recipients) + + def unique_noHash(seq): + seen = set() + return [x for x in seq if str(x) not in seen and not seen.add(str(x))] + + recipients = unique_noHash(matched_recipients) + + # Backwards compatibility + if type(recipients) is str: + recipients = [recipients] + + alert_algo = str(algorithm) + alert_context = alert_algo.upper() + + unencoded_graph_title = 'Skyline Boundary - %s at %s hours - %s - %s' % ( + alert_context, graphite_previous_hours, metric_name, datapoint) + graph_title_string = quote(unencoded_graph_title, safe='') + graph_title = '&title=%s' % graph_title_string + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, + graphite_previous_hours, metric_name, settings.GRAPHITE_GRAPH_SETTINGS, + graph_title, graphite_graph_line_color) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, graphite_previous_hours, + metric_name, settings.GRAPHITE_GRAPH_SETTINGS, graph_title, + graphite_graph_line_color) + + content_id = metric_name + image_data = None + if settings.BOUNDARY_SMTP_OPTS.get('embed-images'): + try: + image_data = urllib2.urlopen(link).read() + except urllib2.URLError: + image_data = None + + # If we failed to get the image or if it was explicitly disabled, + # use the image URL instead of the content. + if image_data is None: + img_tag = '<img src="%s"/>' % link + else: + img_tag = '<img src="cid:%s"/>' % content_id + + body = '%s :: %s <br> Next alert in: %s seconds <br> skyline Boundary alert - %s <br><a href="%s">%s</a>' % ( + datapoint, metric_name, expiration_time, alert_context, link, img_tag) + + for recipient in recipients: + msg = MIMEMultipart('alternative') + msg['Subject'] = '[Skyline alert] ' + 'Boundary ALERT - ' + alert_context + ' - ' + datapoint + ' - ' + metric_name + msg['From'] = sender + msg['To'] = recipient + + msg.attach(MIMEText(body, 'html')) + if image_data is not None: + msg_attachment = MIMEImage(image_data) + msg_attachment.add_header('Content-ID', '<%s>' % content_id) + msg.attach(msg_attachment) + + s = SMTP('127.0.0.1') + s.sendmail(sender, recipient, msg.as_string()) + s.quit()
+ + +
[docs]def alert_pagerduty(datapoint, metric_name, expiration_time, metric_trigger, algorithm): + if settings.PAGERDUTY_ENABLED: + import pygerduty + pager = pygerduty.PagerDuty(settings.BOUNDARY_PAGERDUTY_OPTS['subdomain'], settings.BOUNDARY_PAGERDUTY_OPTS['auth_token']) + pager.trigger_incident(settings.BOUNDARY_PAGERDUTY_OPTS['key'], 'Anomalous metric: %s (value: %s) - %s' % (metric_name, datapoint, algorithm)) + else: + pagerduty_not_enabled = True
+ + +
[docs]def alert_hipchat(datapoint, metric_name, expiration_time, metric_trigger, algorithm): + + if settings.HIPCHAT_ENABLED: + sender = settings.BOUNDARY_HIPCHAT_OPTS['sender'] + import hipchat + hipster = hipchat.HipChat(token=settings.BOUNDARY_HIPCHAT_OPTS['auth_token']) + + # Allow for absolute path metric namespaces but also allow for and match + # match wildcard namepaces if there is not an absolute path metric namespace + rooms = 'unknown' + notify_rooms = [] + matched_rooms = [] + try: + rooms = settings.BOUNDARY_HIPCHAT_OPTS['rooms'][metric_name] + notify_rooms.append(rooms) + except: + for room in settings.BOUNDARY_HIPCHAT_OPTS['rooms']: + print(room) + CHECK_MATCH_PATTERN = room + check_match_pattern = re.compile(CHECK_MATCH_PATTERN) + pattern_match = check_match_pattern.match(metric_name) + if pattern_match: + matched_rooms.append(room) + + if matched_rooms != []: + for i_metric_name in matched_rooms: + rooms = settings.BOUNDARY_HIPCHAT_OPTS['rooms'][i_metric_name] + notify_rooms.append(rooms) + + alert_algo = str(algorithm) + alert_context = alert_algo.upper() + + unencoded_graph_title = 'Skyline Boundary - %s at %s hours - %s - %s' % ( + alert_context, graphite_previous_hours, metric_name, datapoint) + graph_title_string = quote(unencoded_graph_title, safe='') + graph_title = '&title=%s' % graph_title_string + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, + graphite_previous_hours, metric_name, settings.GRAPHITE_GRAPH_SETTINGS, + graph_title, graphite_graph_line_color) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, graphite_previous_hours, + metric_name, settings.GRAPHITE_GRAPH_SETTINGS, graph_title, + graphite_graph_line_color) + + embed_graph = "<a href='" + link + "'><img height='308' src='" + link + "'>" + metric_name + "</a>" + + for rooms in notify_rooms: + for room in rooms: + hipster.method('rooms/message', method='POST', parameters={'room_id': room, 'from': 'skyline', 'color': settings.BOUNDARY_HIPCHAT_OPTS['color'], 'message': '%s - Boundary - %s - Anomalous metric: %s (value: %s) at %s hours %s' % (sender, algorithm, metric_name, datapoint, graphite_previous_hours, embed_graph)}) + else: + hipchat_not_enabled = True
+ + +
[docs]def alert_syslog(datapoint, metric_name, expiration_time, metric_trigger, algorithm): + if settings.SYSLOG_ENABLED: + import sys + import syslog + syslog_ident = settings.SYSLOG_OPTS['ident'] + message = str('Boundary - Anomalous metric: %s (value: %s) - %s' % (metric_name, datapoint, algorithm)) + if sys.version_info[:2] == (2, 6): + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + elif sys.version_info[:2] == (2, 7): + syslog.openlog(ident='skyline', logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + elif sys.version_info[:1] == (3): + syslog.openlog(ident='skyline', logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + else: + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + syslog.syslog(4, message) + else: + syslog_not_enabled = True
+ + +
[docs]def trigger_alert(alerter, datapoint, metric_name, expiration_time, metric_trigger, algorithm): + + if alerter == 'smtp': + strategy = 'alert_smtp' + else: + strategy = 'alert_%s' % alerter + + getattr(boundary_alerters, strategy)(datapoint, metric_name, expiration_time, metric_trigger, algorithm)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/boundary/boundary_algorithms.html b/docs/_build/html/_modules/boundary/boundary_algorithms.html new file mode 100644 index 00000000..046cd78e --- /dev/null +++ b/docs/_build/html/_modules/boundary/boundary_algorithms.html @@ -0,0 +1,593 @@ + + + + + + + + + + + boundary.boundary_algorithms — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+
    +
  • Docs »
  • + +
  • Module code »
  • + +
  • boundary.boundary_algorithms
  • +
  • + + + +
  • +
+
+
+
+
+ +

Source code for boundary.boundary_algorithms

+import pandas
+import numpy as np
+import scipy
+import statsmodels.api as sm
+import traceback
+import logging
+import re
+from time import time
+from msgpack import unpackb, packb
+from redis import StrictRedis
+
+import sys
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+from settings import (
+    FULL_DURATION,
+    MAX_TOLERABLE_BOREDOM,
+    MIN_TOLERABLE_LENGTH,
+    STALE_PERIOD,
+    REDIS_SOCKET_PATH,
+    BOREDOM_SET_SIZE,
+    ENABLE_BOUNDARY_DEBUG,
+)
+
+from algorithm_exceptions import *
+
+skyline_app = 'boundary'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+redis_conn = StrictRedis(unix_socket_path=REDIS_SOCKET_PATH)
+
+
+
[docs]def boundary_no_mans_land(): + """ + This is no man's land. Do anything you want in here, as long as you return a + boolean that determines whether the input timeseries is anomalous or not. + + To add an algorithm, define it here, and add its name to + :mod:`settings.BOUNDARY_ALGORITHMS`. + """ + return True
+ + +
[docs]def autoaggregate_ts(timeseries, autoaggregate_value): + """ + This is a utility function used to autoaggregate a timeseries. If a + timeseries data set has 6 datapoints per minute but only one data value + every minute then autoaggregate will aggregate every autoaggregate_value. + """ + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: autoaggregate_ts at %s seconds' % str(autoaggregate_value)) + + aggregated_timeseries = [] + + if len(timeseries) < 60: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: autoaggregate_ts - timeseries less than 60 datapoints, TooShort') + raise TooShort() + + int_end_timestamp = int(timeseries[-1][0]) + last_hour = int_end_timestamp - 3600 + last_timestamp = int_end_timestamp + next_timestamp = last_timestamp - int(autoaggregate_value) + start_timestamp = last_hour + + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: autoaggregate_ts - aggregating from %s to %s' % (str(start_timestamp), str(int_end_timestamp))) + + valid_timestamps = False + try: + valid_timeseries = int_end_timestamp - start_timestamp + if valid_timeseries == 3600: + valid_timestamps = True + except Exception as e: + logger.error('Algorithm error: %s' % traceback.format_exc()) + logger.error('error: %e' % e) + aggregated_timeseries = [] + return aggregated_timeseries + + if valid_timestamps: + try: + # Check sane variables otherwise we can just hang here in a while loop + while int(next_timestamp) > int(start_timestamp): + value = np.sum(scipy.array([int(x[1]) for x in timeseries if x[0] <= last_timestamp and x[0] > next_timestamp])) + aggregated_timeseries += ((last_timestamp, value),) + last_timestamp = next_timestamp + next_timestamp = last_timestamp - autoaggregate_value + aggregated_timeseries.reverse() + return aggregated_timeseries + except Exception as e: + logger.error('Algorithm error: %s' % traceback.format_exc()) + logger.error('error: %e' % e) + aggregated_timeseries = [] + return aggregated_timeseries + else: + logger.error('could not aggregate - timestamps not valid for aggregation') + aggregated_timeseries = [] + return aggregated_timeseries
+ + +
[docs]def less_than(timeseries, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger): + # timeseries, metric_name, metric_expiration_time, metric_min_average, + # metric_min_average_seconds, metric_trigger, autoaggregate, + # autoaggregate_value): + """ + A timeseries is anomalous if the datapoint is less than metric_trigger + """ + if len(timeseries) < 10: + return False + + if timeseries[-1][1] < metric_trigger: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: less_than - %s less_than %s' % ( + str(timeseries[-1][1]), str(metric_trigger))) + return True + + return False
+ + +
[docs]def greater_than(timeseries, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger): + """ + A timeseries is anomalous if the datapoint is greater than metric_trigger + """ + + if len(timeseries) < 10: + return False + + if timeseries[-1][1] > metric_trigger: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: grater_than - %s grater_than %s' % ( + str(timeseries[-1][1]), str(metric_trigger))) + return True + + return False
+ + +
[docs]def detect_drop_off_cliff(timeseries, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger): + """ + A timeseries is anomalous if the average of the last 10 datapoints is + <trigger> times greater than the last data point AND if has not experienced + frequent cliff drops in the last 10 datapoints. If the timeseries has + experienced 2 or more datapoints of equal or less values in the last 10 or + EXPIRATION_TIME datapoints or is less than a MIN_AVERAGE if set the + algorithm determines the datapoint as NOT anomalous but normal. + This algorithm is most suited to timeseries with most datapoints being > 100 + (e.g high rate). The arbitrary <trigger> values become more noisy with + lower value datapoints, but it still matches drops off cliffs. + """ + + if len(timeseries) < 30: + return False + + try: + int_end_timestamp = int(timeseries[-1][0]) + # Determine resolution of the data set + int_second_last_end_timestamp = int(timeseries[-2][0]) + resolution = int_end_timestamp - int_second_last_end_timestamp + ten_data_point_seconds = resolution * 10 + ten_datapoints_ago = int_end_timestamp - ten_data_point_seconds + + ten_datapoint_array = scipy.array([x[1] for x in timeseries if x[0] <= int_end_timestamp and x[0] > ten_datapoints_ago]) + ten_datapoint_array_len = len(ten_datapoint_array) + except: + return None + + if ten_datapoint_array_len > 3: + + ten_datapoint_min_value = np.amin(ten_datapoint_array) + + # DO NOT handle if negative integers are in the range, where is the + # bottom of the cliff if a range goes negative? Testing with a noisy + # sine wave timeseries that had a drop off cliff introduced to the + # postive data side, proved that this algorithm does work on timeseries + # with data values in the negative range + if ten_datapoint_min_value < 0: + return False + + # autocorrect if there are there are 0s in the data, like graphite expects + # 1 datapoint every 10 seconds, but the timeseries only has 1 every 60 seconds + + ten_datapoint_max_value = np.amax(ten_datapoint_array) + + # The algorithm should have already fired in 10 datapoints if the + # timeseries dropped off a cliff, these are all zero + if ten_datapoint_max_value == 0: + return False + + # If the lowest is equal to the highest, no drop off cliff + if ten_datapoint_min_value == ten_datapoint_max_value: + return False + +# if ten_datapoint_max_value < 10: +# return False + + ten_datapoint_array_sum = np.sum(ten_datapoint_array) + ten_datapoint_value = int(ten_datapoint_array[-1]) + ten_datapoint_average = ten_datapoint_array_sum / ten_datapoint_array_len + ten_datapoint_value = int(ten_datapoint_array[-1]) + + # if a metric goes up and down a lot and falls off a cliff frequently + # it is normal, not anomalous + try: + number_of_similar_datapoints = len(np.where(ten_datapoint_array <= ten_datapoint_min_value)) + except: + return None + + # Detect once only - to make this useful and not noisy the first one + # would have already fired and detected the drop + if number_of_similar_datapoints > 2: + return False + + # evaluate against 20 datapoints as well, reduces chatter on peaky ones + # tested with 60 as well and 20 is sufficient to filter noise + try: + twenty_data_point_seconds = resolution * 20 + twenty_datapoints_ago = int_end_timestamp - twenty_data_point_seconds + twenty_datapoint_array = scipy.array([x[1] for x in timeseries if x[0] <= int_end_timestamp and x[0] > twenty_datapoints_ago]) + number_of_similar_datapoints_in_twenty = len(np.where(twenty_datapoint_array <= ten_datapoint_min_value)) + if number_of_similar_datapoints_in_twenty > 2: + return False + except: + return None + + # Check if there is a similar data point in EXPIRATION_TIME + # Disabled as redis alert cache will filter on this +# if metric_expiration_time > twenty_data_point_seconds: +# expiration_time_data_point_seconds = metric_expiration_time +# expiration_time_datapoints_ago = int_end_timestamp - metric_expiration_time +# expiration_time_datapoint_array = scipy.array([x[1] for x in timeseries if x[0] <= int_end_timestamp and x[0] > expiration_time_datapoints_ago]) +# number_of_similar_datapoints_in_expiration_time = len(np.where(expiration_time_datapoint_array <= ten_datapoint_min_value)) +# if number_of_similar_datapoints_in_expiration_time > 2: +# return False + + if metric_min_average > 0 and metric_min_average_seconds > 0: + try: + min_average = metric_min_average + min_average_seconds = metric_min_average_seconds + min_average_data_point_seconds = resolution * min_average_seconds + # min_average_datapoints_ago = int_end_timestamp - (resolution * min_average_seconds) + min_average_datapoints_ago = int_end_timestamp - min_average_seconds + min_average_array = scipy.array([x[1] for x in timeseries if x[0] <= int_end_timestamp and x[0] > min_average_datapoints_ago]) + min_average_array_average = np.sum(min_average_array) / len(min_average_array) + if min_average_array_average < min_average: + return False + except: + return None + + if ten_datapoint_max_value < 101: + trigger = 15 + if ten_datapoint_max_value < 20: + trigger = ten_datapoint_average / 2 + if ten_datapoint_max_value > 100: + trigger = 100 + if ten_datapoint_value == 0: + # Cannot divide by 0, so set to 0.1 to prevent error + ten_datapoint_value = 0.1 + if ten_datapoint_value == 1: + trigger = 1 + if ten_datapoint_value == 1 and ten_datapoint_max_value < 10: + trigger = 0.1 + if ten_datapoint_value == 0.1 and ten_datapoint_average < 1 and ten_datapoint_array_sum < 7: + trigger = 7 + + ten_datapoint_result = ten_datapoint_average / ten_datapoint_value + if int(ten_datapoint_result) > trigger: + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'detect_drop_off_cliff - %s, ten_datapoint_value = %s, ten_datapoint_array_sum = %s, ten_datapoint_average = %s, trigger = %s, ten_datapoint_result = %s' % ( + str(int_end_timestamp), + str(ten_datapoint_value), + str(ten_datapoint_array_sum), + str(ten_datapoint_average), + str(trigger), str(ten_datapoint_result))) + return True + + return False
+ + +
[docs]def run_selected_algorithm( + timeseries, metric_name, metric_expiration_time, metric_min_average, + metric_min_average_seconds, metric_trigger, alert_threshold, + metric_alerters, autoaggregate, autoaggregate_value, algorithm): + """ + Filter timeseries and run selected algorithm. + """ + + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'debug :: assigning in algoritms.py - %s, %s' % ( + metric_name, algorithm)) + + # Get rid of short series + if len(timeseries) < MIN_TOLERABLE_LENGTH: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: TooShort - %s, %s' % (metric_name, algorithm)) + raise TooShort() + + # Get rid of stale series + if time() - timeseries[-1][0] > STALE_PERIOD: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: Stale - %s, %s' % (metric_name, algorithm)) + raise Stale() + + # Get rid of boring series + if len(set(item[1] for item in timeseries[-MAX_TOLERABLE_BOREDOM:])) == BOREDOM_SET_SIZE: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: Boring - %s, %s' % (metric_name, algorithm)) + raise Boring() + + if autoaggregate: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: auto aggregating %s for %s' % (metric_name, algorithm)) + try: + agg_timeseries = autoaggregate_ts(timeseries, autoaggregate_value) + aggregatation_failed = False + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'debug :: aggregated_timeseries returned %s for %s' % ( + metric_name, algorithm)) + except Exception as e: + agg_timeseries = [] + aggregatation_failed = True + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug error - autoaggregate excpection %s for %s' % (metric_name, algorithm)) + logger.error('Algorithm error: %s' % traceback.format_exc()) + logger.error('error: %e' % e) + + if len(agg_timeseries) > 10: + timeseries = agg_timeseries + else: + if ENABLE_BOUNDARY_DEBUG: + logger.info('debug :: TooShort - %s, %s' % (metric_name, algorithm)) + raise TooShort() + + if len(timeseries) < 10: + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'debug :: timeseries too short - %s - timeseries length - %s' % ( + metric_name, str(len(timeseries)))) + raise TooShort() + + try: + ensemble = [globals()[algorithm](timeseries, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger)] + if ensemble.count(True) == 1: + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'debug :: anomalous datapoint = %s - %s, %s, %s, %s, %s, %s, %s, %s' % ( + str(timeseries[-1][1]), + str(metric_name), str(metric_expiration_time), + str(metric_min_average), + str(metric_min_average_seconds), + str(metric_trigger), str(alert_threshold), + str(metric_alerters), str(algorithm)) + ) + return True, ensemble, timeseries[-1][1], metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters, algorithm + else: + if ENABLE_BOUNDARY_DEBUG: + logger.info( + 'debug :: not anomalous datapoint = %s - %s, %s, %s, %s, %s, %s, %s, %s' % ( + str(timeseries[-1][1]), + str(metric_name), str(metric_expiration_time), + str(metric_min_average), + str(metric_min_average_seconds), + str(metric_trigger), str(alert_threshold), + str(metric_alerters), str(algorithm)) + ) + return False, ensemble, timeseries[-1][1], metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters, algorithm + except: + logger.error('Algorithm error: %s' % traceback.format_exc()) + return False, [], 1, metric_name, metric_expiration_time, metric_min_average, metric_min_average_seconds, metric_trigger, alert_threshold, metric_alerters, algorithm
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/crucible/agent.html b/docs/_build/html/_modules/crucible/agent.html new file mode 100644 index 00000000..8721df12 --- /dev/null +++ b/docs/_build/html/_modules/crucible/agent.html @@ -0,0 +1,345 @@ + + + + + + + + + + + crucible.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for crucible.agent

+import logging
+import sys
+import traceback
+import os
+from os import getpid
+from os import getcwd, listdir, makedirs
+from os.path import dirname, abspath, isdir
+from daemon import runner
+from time import sleep, time
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+# from skyline import settings
+from crucible import Crucible
+from crucible_algorithms import run_algorithms
+
+python_version = int(sys.version_info[0])
+
+skyline_app = 'crucible'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+
+
[docs]class CrucibleAgent(): + """ + Initialize CrucibleAgent + """ + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + logger.info('agent starting skyline %s' % skyline_app) + Crucible(getpid()).start() + + while 1: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('CrucibleAgent alive') + sleep(100)
+ + +
[docs]def run(): + """ + Start the Crucible agent and ensure all the required directories exist, + creating the crucible directories if they do not exist + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + # Make sure the required directories exists + if not os.path.exists(settings.CRUCIBLE_CHECK_PATH): + try: + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.CRUCIBLE_CHECK_PATH, mode_arg) + except: + print ('failed to create directory - %s' % settings.CRUCIBLE_CHECK_PATH) + sys.exit(1) + + if not os.path.exists(settings.CRUCIBLE_DATA_FOLDER): + try: + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.CRUCIBLE_DATA_FOLDER, mode_arg) + except: + print ('failed to create directory - %s' % settings.CRUCIBLE_DATA_FOLDER) + sys.exit(1) + + failed_checks_dir = settings.CRUCIBLE_DATA_FOLDER + '/failed_checks' + if not os.path.exists(failed_checks_dir): + try: + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(failed_checks_dir, mode_arg) + except: + print ('failed to create directory - %s' % failed_checks_dir) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(256, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + crucible = CrucibleAgent() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + logger.info('starting skyline crucible via run') + crucible.run() + else: + logger.info('starting skyline crucible via daemon') + daemon_runner = runner.DaemonRunner(crucible) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action()
+ +if __name__ == '__main__': + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/crucible/crucible.html b/docs/_build/html/_modules/crucible/crucible.html new file mode 100644 index 00000000..c1ac72db --- /dev/null +++ b/docs/_build/html/_modules/crucible/crucible.html @@ -0,0 +1,1156 @@ + + + + + + + + + + + crucible.crucible — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for crucible.crucible

+import logging
+try:
+    from Queue import Empty
+except:
+    from queue import Empty
+from redis import StrictRedis
+import time
+from time import time, sleep
+from threading import Thread
+from multiprocessing import Process, Manager, Queue
+from msgpack import Unpacker, unpackb, packb
+import os
+from os.path import dirname, join, abspath, isfile
+from os import path, kill, getpid, system, getcwd, listdir, makedirs
+from sys import exit, version_info
+import traceback
+import re
+import socket
+import json
+import gzip
+import sys
+import requests
+try:
+    import urlparse
+except ImportError:
+    import urllib.parse
+try:
+    import urllib2
+except ImportError:
+    import urllib.request
+    import urllib.error
+import errno
+import imp
+import datetime
+import shutil
+
+import os.path
+# sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+# sys.path.insert(0, os.path.dirname(__file__))
+
+import settings
+from skyline_functions import mkdir_p, load_metric_vars, fail_check
+
+from crucible_algorithms import run_algorithms
+
+skyline_app = 'crucible'
+skyline_app_logger = skyline_app + 'Log'
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = settings.LOG_PATH + '/' + skyline_app + '.log'
+skyline_app_loglock = skyline_app_logfile + '.lock'
+skyline_app_logwait = skyline_app_logfile + '.wait'
+
+python_version = int(version_info[0])
+
+this_host = str(os.uname()[1])
+
+try:
+    SERVER_METRIC_PATH = '.' + settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.' + skyline_app + SERVER_METRIC_PATH
+
+FULL_NAMESPACE = settings.FULL_NAMESPACE
+ENABLE_CRUCIBLE_DEBUG = settings.ENABLE_CRUCIBLE_DEBUG
+crucible_data_folder = str(settings.CRUCIBLE_DATA_FOLDER)
+failed_checks_dir = crucible_data_folder + '/failed_checks'
+
+
+
[docs]class Crucible(Thread): + def __init__(self, parent_pid): + """ + Initialize Crucible + """ + super(Crucible, self).__init__() + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.process_list = Manager().list() + self.metric_variables = Manager().list() + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + +
[docs] def check_if_parent_is_alive(self): + """ + Check if the parent process is alive + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def spin_process(self, i, run_timestamp, metric_check_file): + """ + Assign a metric for a process to analyze. + + :param i: python process id + :param run_timestamp: the epoch timestamp at which this process was called + :param metric_check_file: full path to the metric check file + + :return: returns True + + """ + + child_process_pid = os.getpid() + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('child_process_pid - %s' % str(child_process_pid)) + + self.process_list.append(child_process_pid) + + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('processing metric check - %s' % metric_check_file) + + if not os.path.isfile(str(metric_check_file)): + logger.error('error :: file not found - metric_check_file - %s' % (str(metric_check_file))) + return + + check_file_name = os.path.basename(str(metric_check_file)) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('check_file_name - %s' % check_file_name) + check_file_timestamp = check_file_name.split('.', 1)[0] + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('check_file_timestamp - %s' % str(check_file_timestamp)) + check_file_metricname_txt = check_file_name.split('.', 1)[1] + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('check_file_metricname_txt - %s' % check_file_metricname_txt) + check_file_metricname = check_file_metricname_txt.replace('.txt', '') + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('check_file_metricname - %s' % check_file_metricname) + check_file_metricname_dir = check_file_metricname.replace('.', '/') + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('check_file_metricname_dir - %s' % check_file_metricname_dir) + + metric_failed_check_dir = failed_checks_dir + '/' + check_file_metricname_dir + '/' + check_file_timestamp + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric_failed_check_dir - %s' % metric_failed_check_dir) + + # failed_check_file = failed_checks_dir + '/' + check_file_name + failed_check_file = metric_failed_check_dir + '/' + check_file_name + + # Load and validate metric variables + try: + metric_vars = load_metric_vars(skyline_app, str(metric_check_file)) + except: + logger.error('error :: failed to import metric variables from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + # TBD - a failed check Panorama update will go here, perhaps txt + # files are not the only "queue" that will be used, both, but + # Panorama, may be just a part of Skyline Flux, the flux DB + # would allow for a very nice, distributed "queue" and a + # distributed Skyline workforce... + # Any Skyline node could just have one role, e.g. lots of + # Skyline nodes running crucible only and instead of reading + # the local filesystem for input, they could read the Flux DB + # queue or both... + return + + # Test metric variables + # We use a pythonic methodology to test if the variables are defined, + # this ensures that if any of the variables are not set for some reason + # we can handle unexpected data or situations gracefully and try and + # ensure that the process does not hang. + + # if len(str(metric_vars.metric)) == 0: + # if not metric_vars.metric: + try: + metric_vars.metric + except: + logger.error('error :: failed to read metric variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + else: + metric = str(metric_vars.metric) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - metric - %s' % metric) + + # if len(metric_vars.value) == 0: + # if not metric_vars.value: + try: + metric_vars.value + except: + logger.error('error :: failed to read value variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + else: + value = str(metric_vars.value) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - value - %s' % (value)) + + # if len(metric_vars.from_timestamp) == 0: + # if not metric_vars.from_timestamp: + try: + metric_vars.from_timestamp + except: + logger.error('error :: failed to read from_timestamp variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + else: + from_timestamp = str(metric_vars.from_timestamp) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - from_timestamp - %s' % from_timestamp) + + # if len(metric_vars.metric_timestamp) == 0: + # if not metric_vars.metric_timestamp: + try: + metric_vars.metric_timestamp + except: + logger.error('error :: failed to read metric_timestamp variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + else: + metric_timestamp = str(metric_vars.metric_timestamp) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - metric_timestamp - %s' % metric_timestamp) + + # if len(metric_vars.algorithms) == 0: + # if not metric_vars.algorithms: + try: + metric_vars.algorithms + except: + logger.error('error :: failed to read algorithms variable from check file setting to all' % (metric_check_file)) + algorithms = ['all'] + else: + algorithms = [] + for i_algorithm in metric_vars.algorithms: + algorithms.append(i_algorithm) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - algorithms - %s' % algorithms) + + # if len(metric_vars.anomaly_dir) == 0: + # if not metric_vars.anomaly_dir: + try: + metric_vars.anomaly_dir + except: + logger.error('error :: failed to read anomaly_dir variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + else: + anomaly_dir = str(metric_vars.anomaly_dir) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - anomaly_dir - %s' % anomaly_dir) + + # if len(str(metric_vars.graphite_metric)) == 0: + try: + metric_vars.graphite_metric + except: + logger.info('failed to read graphite_metric variable from check file setting to False') + # yes this is a string + graphite_metric = 'False' + else: + graphite_metric = str(metric_vars.graphite_metric) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - graphite_metric - %s' % graphite_metric) + + # if len(str(metric_vars.run_crucible_tests)) == 0: + try: + metric_vars.run_crucible_tests + except: + logger.info('failed to read run_crucible_tests variable from check file setting to False') + # yes this is a string + run_crucible_tests = 'False' + else: + run_crucible_tests = str(metric_vars.run_crucible_tests) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - run_crucible_tests - %s' % run_crucible_tests) + + try: + metric_vars.added_by + except: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('failed to read added_by variable from check file setting to crucible - set to crucible') + added_by = 'crucible' + else: + added_by = str(metric_vars.added_by) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - added_by - %s' % added_by) + + try: + metric_vars.run_script + except: + run_script = False + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - run_script - not present set to False') + else: + run_script = str(metric_vars.run_script) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric variable - run_script - %s' % run_script) + + # Only check if the metric does not a EXPIRATION_TIME key set, crucible + # uses the alert EXPIRATION_TIME for the relevant alert setting contexts + # whether that be analyzer, mirage, boundary, etc and sets its own + # cache_keys in redis. This prevents large amounts of data being added + # in terms of tieseries json and image files, crucible samples at the + # same EXPIRATION_TIME as alerts. + + source_app = 'crucible' + expiration_timeout = 1800 + remove_all_anomaly_files = False + check_expired = False + check_time = time() + + if added_by == 'analyzer' or added_by == 'mirage': + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('Will check %s ALERTS' % added_by) + if settings.ENABLE_ALERTS: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('Checking %s ALERTS' % added_by) + for alert in settings.ALERTS: + ALERT_MATCH_PATTERN = alert[0] + METRIC_PATTERN = metric + alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) + pattern_match = alert_match_pattern.match(METRIC_PATTERN) + if pattern_match: + source_app = added_by + expiration_timeout = alert[2] + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('matched - %s - %s - EXPIRATION_TIME is %s' % (source_app, metric, str(expiration_timeout))) + check_age = int(check_time) - int(metric_timestamp) + if int(check_age) > int(expiration_timeout): + check_expired = True + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('the check is older than EXPIRATION_TIME for the metric - not checking - check_expired') + + if added_by == 'boundary': + if settings.BOUNDARY_ENABLE_ALERTS: + for alert in settings.BOUNDARY_METRICS: + ALERT_MATCH_PATTERN = alert[0] + METRIC_PATTERN = metric + alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) + pattern_match = alert_match_pattern.match(METRIC_PATTERN) + if pattern_match: + source_app = 'boundary' + expiration_timeout = alert[2] + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('matched - %s - %s - EXPIRATION_TIME is %s' % (source_app, metric, str(expiration_timeout))) + check_age = int(check_time) - int(metric_timestamp) + if int(check_age) > int(expiration_timeout): + check_expired = True + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('the check is older than EXPIRATION_TIME for the metric - not checking - check_expired') + + cache_key = 'crucible.last_check.%s.%s' % (source_app, metric) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('cache_key - crucible.last_check.%s.%s' % (source_app, metric)) + + # Only use the cache_key EXPIRATION_TIME if this is not a request to + # run_crucible_tests on a timeseries + if run_crucible_tests == 'False': + if check_expired: + logger.info('check_expired - not checking Redis key') + last_check = True + else: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('Checking if cache_key exists') + try: + last_check = self.redis_conn.get(cache_key) + except Exception as e: + logger.error('error :: could not query cache_key for %s - %s - %s' % (alerter, metric, e)) + logger.info('all anomaly files will be removed') + remove_all_anomaly_files = True + + if not last_check: + try: + self.redis_conn.setex(cache_key, expiration_timeout, packb(value)) + logger.info('set cache_key for %s - %s with timeout of %s' % (source_app, metric, str(expiration_timeout))) + except Exception as e: + logger.error('error :: could not query cache_key for %s - %s - %s' % (alerter, metric, e)) + logger.info('all anomaly files will be removed') + remove_all_anomaly_files = True + else: + if check_expired: + logger.info('check_expired - all anomaly files will be removed') + remove_all_anomaly_files = True + else: + logger.info('cache_key is set and not expired for %s - %s - all anomaly files will be removed' % (source_app, metric)) + remove_all_anomaly_files = True + + # anomaly dir + if not os.path.exists(str(anomaly_dir)): + try: + mkdir_p(skyline_app, str(anomaly_dir)) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('created anomaly dir - %s' % str(anomaly_dir)) + except: + logger.error('error :: failed to create anomaly_dir - %s' % str(anomaly_dir)) + + if not os.path.exists(str(anomaly_dir)): + logger.error('error :: anomaly_dir does not exist') + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + else: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly dir exists - %s' % str(anomaly_dir)) + + failed_check_file = anomaly_dir + '/' + metric_timestamp + '.failed.check.' + metric + '.txt' + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('failed_check_file - %s' % str(failed_check_file)) + + # Retrieve data from graphite is necessary + anomaly_graph = anomaly_dir + '/' + metric + '.png' + anomaly_json = anomaly_dir + '/' + metric + '.json' + anomaly_json_gz = anomaly_json + '.gz' + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_graph - %s' % str(anomaly_graph)) + logger.info('anomaly_json - %s' % str(anomaly_json)) + logger.info('anomaly_json_gz - %s' % str(anomaly_json_gz)) + + # Some things added to crucible may not be added by a skyline app per se + # and if run_crucible_tests is string True then no anomaly files should + # be removed. + if run_crucible_tests == 'True': + remove_all_anomaly_files = False + + # Remove check and anomaly files if the metric has a EXPIRATION_TIME + # cache_key set + if remove_all_anomaly_files: + if os.path.isfile(anomaly_graph): + try: + os.remove(anomaly_graph) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_graph removed - %s' % str(anomaly_graph)) + except OSError: + pass + if os.path.isfile(anomaly_json): + try: + os.remove(anomaly_json) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_json removed - %s' % str(anomaly_json)) + except OSError: + pass + if os.path.isfile(anomaly_json_gz): + try: + os.remove(anomaly_json_gz) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_json_gz removed - %s' % str(anomaly_json_gz)) + except OSError: + pass + + anomaly_txt_file = anomaly_dir + '/' + metric + '.txt' + if os.path.isfile(anomaly_txt_file): + try: + os.remove(anomaly_txt_file) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_txt_file removed - %s' % str(anomaly_txt_file)) + except OSError: + pass + + # TBD - this data would have to be added to the panaorama DB before + # it is removed + if os.path.isfile(str(metric_check_file)): + try: + os.remove(str(metric_check_file)) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('metric_check_file removed - %s' % str(metric_check_file)) + except OSError: + pass + + if os.path.exists(str(anomaly_dir)): + try: + os.rmdir(str(anomaly_dir)) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_dir removed - %s' % str(anomaly_dir)) + except OSError: + pass + + logger.info('check and anomaly files removed') + return + + # Check if the image exists + if graphite_metric == 'True': + + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('graphite_metric - %s' % (graphite_metric)) + + # Graphite timeouts + connect_timeout = int(settings.GRAPHITE_CONNECT_TIMEOUT) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('connect_timeout - %s' % str(connect_timeout)) + + read_timeout = int(settings.GRAPHITE_READ_TIMEOUT) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('read_timeout - %s' % str(read_timeout)) + + graphite_until = datetime.datetime.fromtimestamp(int(metric_timestamp)).strftime('%H:%M_%Y%m%d') + graphite_from = datetime.datetime.fromtimestamp(int(from_timestamp)).strftime('%H:%M_%Y%m%d') + + # graphite URL + if settings.GRAPHITE_PORT != '': + url = settings.GRAPHITE_PROTOCOL + '://' + settings.GRAPHITE_HOST + ':' + settings.GRAPHITE_PORT + '/render/?from=' + graphite_from + '&until=' + graphite_until + '&target=' + metric + '&format=json' + else: + url = settings.GRAPHITE_PROTOCOL + '://' + settings.GRAPHITE_HOST + '/render/?from=' + graphite_from + '&until=' + graphite_until + '&target=' + metric + '&format=json' + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('graphite url - %s' % (url)) + + if not os.path.isfile(anomaly_graph): + logger.info('retrieving png - surfacing %s graph from graphite from %s to %s' % (metric, graphite_from, graphite_until)) + + image_url = url.replace('&format=json', '') + graphite_image_file = anomaly_dir + '/' + metric + '.png' + if 'width' not in image_url: + image_url += '&width=586' + if 'height' not in image_url: + image_url += '&height=308' + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('graphite image url - %s' % (image_url)) + image_url_timeout = int(connect_timeout) + + image_data = None + + try: + image_data = urllib2.urlopen(image_url, timeout=image_url_timeout).read() + logger.info('url OK - %s' % (image_url)) + except urllib2.URLError: + image_data = None + logger.error('error :: url bad - %s' % (image_url)) + + if image_data is not None: + with open(graphite_image_file, 'w') as f: + f.write(image_data) + logger.info('retrieved - %s' % (anomaly_graph)) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(graphite_image_file, mode_arg) + else: + logger.error('error :: failed to retrieved - %s' % (anomaly_graph)) + else: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_graph file exists - %s' % str(anomaly_graph)) + + if not os.path.isfile(anomaly_graph): + logger.error('error :: retrieve failed to surface %s graph from graphite' % (metric)) + else: + logger.info('graph image exists - %s' % (anomaly_graph)) + + # Check if the json exists + if not os.path.isfile(anomaly_json_gz): + if not os.path.isfile(anomaly_json): + logger.info('surfacing timeseries data for %s from graphite from %s to %s' % (metric, graphite_from, graphite_until)) + if requests.__version__ >= '2.4.0': + use_timeout = (int(connect_timeout), int(read_timeout)) + else: + use_timeout = int(connect_timeout) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('use_timeout - %s' % (str(use_timeout))) + + try: + r = requests.get(url, timeout=use_timeout) + js = r.json() + datapoints = js[0]['datapoints'] + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('data retrieved OK') + except: + datapoints = [[None, int(graphite_until)]] + logger.error('error :: data retrieval failed') + + converted = [] + for datapoint in datapoints: + try: + new_datapoint = [float(datapoint[1]), float(datapoint[0])] + converted.append(new_datapoint) + except: + continue + + with open(anomaly_json, 'w') as f: + f.write(json.dumps(converted)) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(anomaly_json, mode_arg) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('json file - %s' % anomaly_json) + + if not os.path.isfile(anomaly_json): + logger.error('error :: failed to surface %s json from graphite' % (metric)) + # Move metric check file + try: + shutil.move(metric_check_file, failed_check_file) + logger.info('moved check file to - %s' % failed_check_file) + except OSError: + logger.error('error :: failed to move check file to - %s' % failed_check_file) + pass + return + + # Check timeseries json exists - raw or gz + if not os.path.isfile(anomaly_json): + if not os.path.isfile(anomaly_json_gz): + logger.error('error :: no json data found' % (metric)) + # Move metric check file + try: + shutil.move(metric_check_file, failed_check_file) + logger.info('moved check file to - %s' % failed_check_file) + except OSError: + logger.error('error :: failed to move check file to - %s' % failed_check_file) + pass + return + else: + logger.info('timeseries json gzip exists - %s' % (anomaly_json_gz)) + else: + logger.info('timeseries json exists - %s' % (anomaly_json)) + + # If timeseries json and run_crucible_tests is str(False) gzip and + # return here as there is nothing further to do + if run_crucible_tests == 'False': + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('run_crucible_tests - %s' % run_crucible_tests) + # gzip the json timeseries data + if os.path.isfile(anomaly_json): + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('gzipping - %s' % anomaly_json) + try: + f_in = open(anomaly_json) + f_out = gzip.open(anomaly_json_gz, 'wb') + f_out.writelines(f_in) + f_out.close() + f_in.close() + os.remove(anomaly_json) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(anomaly_json_gz, mode_arg) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('gzipped - %s' % anomaly_json_gz) + try: + os.remove(metric_check_file) + logger.info('removed check file - %s' % metric_check_file) + except OSError: + pass + return + except: + logger.error('error :: Failed to gzip data file - %s' % str(traceback.print_exc())) + try: + os.remove(metric_check_file) + logger.info('removed check file - %s' % metric_check_file) + except OSError: + pass + return + + if os.path.isfile(anomaly_json_gz): + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('gzip exists - %s' % anomaly_json) + try: + os.remove(metric_check_file) + logger.info('removed check file - %s' % metric_check_file) + except OSError: + pass + return + nothing_to_do = 'true - for debug only' + + # self.check_if_parent_is_alive() + # Run crucible algorithms + logger.info('running crucible tests - %s' % (metric)) + + timeseries_dir = metric.replace('.', '/') + + if os.path.isfile(anomaly_json_gz): + if not os.path.isfile(anomaly_json): + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('ungzipping - %s' % anomaly_json_gz) + try: + # with gzip.open(anomaly_json_gz, 'rb') as fr: + fr = gzip.open(anomaly_json_gz, 'rb') + raw_timeseries = fr.read() + fr.close() + except Exception as e: + logger.error('error :: could not ungzip %s - %s' % (anomaly_json_gz, e)) + traceback.print_exc() + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('ungzipped') + logger.info('writing to - %s' % anomaly_json) + with open(anomaly_json, 'w') as fw: + fw.write(raw_timeseries) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_json done') + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(anomaly_json, mode_arg) + else: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('No gzip - %s' % anomaly_json_gz) + nothing_to_do = 'true - for debug only' + + if os.path.isfile(anomaly_json): + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomaly_json exists - %s' % anomaly_json) + + if os.path.isfile(anomaly_json): + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('loading timeseries from - %s' % anomaly_json) + with open(anomaly_json, 'r') as f: + timeseries = json.loads(f.read()) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('loaded timeseries from - %s' % anomaly_json) + else: + try: + logger.error('error :: file not found - %s' % anomaly_json) + shutil.move(metric_check_file, failed_check_file) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(failed_check_file, mode_arg) + logger.info('moved check file to - %s' % failed_check_file) + except OSError: + logger.error('error :: failed to move check file to - %s' % failed_check_file) + pass + return + + start_timestamp = int(timeseries[0][0]) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('start_timestamp - %s' % str(start_timestamp)) + end_timestamp = int(timeseries[-1][0]) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('end_timestamp - %s' % str(end_timestamp)) + + full_duration = end_timestamp - start_timestamp + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('full_duration - %s' % str(full_duration)) + + self.check_if_parent_is_alive() + + run_algorithms_start_timestamp = int(time()) + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('run_algorithms_start_timestamp - %s' % str(run_algorithms_start_timestamp)) + + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('run_algorithms - %s,%s,%s,%s,%s,%s' % (metric, str(end_timestamp), str(full_duration), anomaly_json, skyline_app, str(algorithms))) + try: + anomalous, ensemble = run_algorithms(timeseries, str(metric), end_timestamp, full_duration, str(anomaly_json), skyline_app, algorithms) + except: + logger.error('error :: run_algorithms failed - %s' % str(traceback.print_exc())) + + run_algorithms_end_timestamp = int(time()) + run_algorithms_seconds = run_algorithms_end_timestamp - run_algorithms_start_timestamp + + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomalous, ensemble - %s, %s' % (anomalous, str(ensemble))) + + if anomalous: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('anomalous - %s' % (anomalous)) + nothing_to_do = 'true - for debug only' + + logger.info('run_algorithms took %s seconds' % str(run_algorithms_seconds)) + + # Update anomaly file + crucible_data = 'crucible_tests_run = "%s"\n' \ + 'crucible_triggered_algorithms = %s\n' \ + 'tested_by = "%s"\n' \ + % (str(run_timestamp), str(ensemble), str(this_host)) + crucible_anomaly_file = '%s/%s.txt' % (anomaly_dir, metric) + with open(crucible_anomaly_file, 'a') as fh: + fh.write(crucible_data) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(crucible_anomaly_file, mode_arg) + logger.info('updated crucible anomaly file - %s/%s.txt' % (anomaly_dir, metric)) + + # gzip the json timeseries data after analysis + if os.path.isfile(anomaly_json): + if not os.path.isfile(anomaly_json_gz): + try: + f_in = open(anomaly_json) + f_out = gzip.open(anomaly_json_gz, 'wb') + f_out.writelines(f_in) + f_out.close() + f_in.close() + os.remove(anomaly_json) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(anomaly_json_gz, mode_arg) + logger.info('gzipped - %s' % (anomaly_json_gz)) + except: + logger.error('error :: Failed to gzip data file - %s' % str(traceback.print_exc())) + else: + os.remove(anomaly_json) + + if run_script: + if os.path.isfile(run_script): + logger.info('running - %s' % (run_script)) + os.system('%s %s' % (str(run_script), str(crucible_anomaly_file))) + + # Remove metric check file + try: + os.remove(metric_check_file) + logger.info('complete removed check file - %s' % (metric_check_file)) + except OSError: + pass
+ +
[docs] def run(self): + """ + Called when the process intializes. + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os.remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os.remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + logger.info('process intialized') + + while 1: + now = time() + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('process started - %s' % int(now)) + + # Make sure check_dir exists and has not been removed + try: + if settings.ENABLE_CRUCIBLE_DEBUG: + logger.info('checking check dir exists - %s' % settings.CRUCIBLE_CHECK_PATH) + os.path.exists(settings.CRUCIBLE_CHECK_PATH) + except: + logger.error('error :: check dir did not exist - %s' % settings.CRUCIBLE_CHECK_PATH) + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.CRUCIBLE_CHECK_PATH, mode_arg) + logger.info('check dir created - %s' % settings.CRUCIBLE_CHECK_PATH) + os.path.exists(settings.CRUCIBLE_CHECK_PATH) + # continue + + # Make sure Redis is up + try: + self.redis_conn.ping() + logger.info('connected to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + except: + logger.info('skyline can not connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + sleep(10) + logger.info('connecting to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + """ + Determine if any metric has been added to test + """ + while True: + + # Report app up + self.redis_conn.setex(skyline_app, 120, now) + + metric_var_files = [f for f in listdir(settings.CRUCIBLE_CHECK_PATH) if isfile(join(settings.CRUCIBLE_CHECK_PATH, f))] +# if len(metric_var_files) == 0: + if not metric_var_files: + logger.info('sleeping 10 no metric check files') + sleep(10) + + # Discover metric to analyze + metric_var_files = '' + metric_var_files = [f for f in listdir(settings.CRUCIBLE_CHECK_PATH) if isfile(join(settings.CRUCIBLE_CHECK_PATH, f))] +# if len(metric_var_files) > 0: + if metric_var_files: + break + + metric_var_files_sorted = sorted(metric_var_files) + metric_check_file = settings.CRUCIBLE_CHECK_PATH + "/" + str(metric_var_files_sorted[0]) + + logger.info('assigning check for processing - %s' % str(metric_var_files_sorted[0])) + + # Reset process_list + self.process_list[:] = [] + + # Spawn processes + pids = [] + spawned_pids = [] + pid_count = 0 + run_timestamp = int(now) + for i in range(1, CRUCIBLE_PROCESSES + 1): + p = Process(target=self.spin_process, args=(i, run_timestamp, str(metric_check_file))) + pids.append(p) + pid_count += 1 + logger.info('starting %s of %s spin_process/es' % (str(pid_count), str(settings.CRUCIBLE_PROCESSES))) + p.start() + spawned_pids.append(p.pid) + + # Send wait signal to zombie processes + # for p in pids: + # p.join() + # Self monitor processes and terminate if any spin_process has run + # for longer than CRUCIBLE_TESTS_TIMEOUT + p_starts = time() + while time() - p_starts <= settings.CRUCIBLE_TESTS_TIMEOUT: + if any(p.is_alive() for p in pids): + # Just to avoid hogging the CPU + sleep(.1) + else: + # All the processes are done, break now. + time_to_run = time() - p_starts + logger.info('%s :: %s spin_process/es completed in %.2f seconds' % (skyline_app, str(settings.CRUCIBLE_PROCESSES), time_to_run)) + break + else: + # We only enter this if we didn't 'break' above. + logger.info('%s :: timed out, killing all spin_process processes' % (skyline_app)) + for p in pids: + p.terminate() + p.join() + + while os.path.isfile(metric_check_file): + sleep(1)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/crucible/crucible_algorithms.html b/docs/_build/html/_modules/crucible/crucible_algorithms.html new file mode 100644 index 00000000..849aca45 --- /dev/null +++ b/docs/_build/html/_modules/crucible/crucible_algorithms.html @@ -0,0 +1,714 @@ + + + + + + + + + + + crucible.crucible_algorithms — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+
    +
  • Docs »
  • + +
  • Module code »
  • + +
  • crucible.crucible_algorithms
  • +
  • + + + +
  • +
+
+
+
+
+ +

Source code for crucible.crucible_algorithms

+import pandas
+import numpy as np
+import scipy
+import statsmodels.api as sm
+import matplotlib.pyplot as plt
+import traceback
+import logging
+import os
+import time
+from multiprocessing import Process
+from sys import version_info
+
+from os.path import dirname, join, abspath
+from os import makedirs
+
+import sys
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+from settings import (
+    ALGORITHMS,
+    ENABLE_CRUCIBLE_DEBUG,
+    MIRAGE_ALGORITHMS,
+    PANDAS_VERSION,
+)
+
+logger = logging.getLogger("crucibleLog")
+python_version = int(version_info[0])
+
+"""
+This is no man's land. Do anything you want in here,
+as long as you return a boolean that determines whether the input
+timeseries is anomalous or not.
+
+To add an algorithm, define it here, and add its name to settings.ALGORITHMS.
+It must be defined required parameters (even if your algorithm/function does not
+need them), as the run_algorithms function passes them to all ALGORITHMS defined
+in settings.ALGORITHMS.
+"""
+
+
+
[docs]def tail_avg(timeseries, end_timestamp, full_duration): + """ + This is a utility function used to calculate the average of the last three + datapoints in the series as a measure, instead of just the last datapoint. + It reduces noise, but it also reduces sensitivity and increases the delay + to detection. + """ + try: + t = (timeseries[-1][1] + timeseries[-2][1] + timeseries[-3][1]) / 3 + return t + except IndexError: + return timeseries[-1][1]
+ + +
[docs]def median_absolute_deviation(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the deviation of its latest datapoint with + respect to the median is X times larger than the median of deviations. + """ + + try: + series = pandas.Series([x[1] for x in timeseries]) + median = series.median() + demedianed = np.abs(series - median) + median_deviation = demedianed.median() + except: + return None + + # The test statistic is infinite when the median is zero, + # so it becomes super sensitive. We play it safe and skip when this happens. + if median_deviation == 0: + return False + + if PANDAS_VERSION < '0.17.0': + try: + test_statistic = demedianed.iget(-1) / median_deviation + except: + return None + else: + try: + test_statistic = demedianed.iat[-1] / median_deviation + except: + return None + + # Completely arbitary...triggers if the median deviation is + # 6 times bigger than the median + if test_statistic > 6: + return True + + # As per https://github.com/etsy/skyline/pull/104 by @rugger74 + # Although never seen this should return False if not > arbitary_value + # 20160523 @earthgecko + return False
+ + +
[docs]def grubbs(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the Z score is greater than the Grubb's score. + """ + + try: + series = scipy.array([x[1] for x in timeseries]) + stdDev = scipy.std(series) + mean = np.mean(series) + tail_average = tail_avg(timeseries, end_timestamp, full_duration) + z_score = (tail_average - mean) / stdDev + len_series = len(series) + threshold = scipy.stats.t.isf(.05 / (2 * len_series), len_series - 2) + threshold_squared = threshold * threshold + grubbs_score = ((len_series - 1) / np.sqrt(len_series)) * np.sqrt(threshold_squared / (len_series - 2 + threshold_squared)) + + return z_score > grubbs_score + except: + return None
+ + +
[docs]def first_hour_average(timeseries, end_timestamp, full_duration): + """ + Calcuate the simple average over 60 datapoints (maybe one hour), + FULL_DURATION seconds ago. + A timeseries is anomalous if the average of the last three datapoints + are outside of three standard deviations of this value. + """ + + try: + int_end_timestamp = int(timeseries[-1][0]) + int_start_timestamp = int(timeseries[0][0]) + int_full_duration = int_end_timestamp - int_start_timestamp + + # Determine data resolution + # last_hour_threshold = int_end_timestamp - (int_full_duration - 3600) + int_second_last_end_timestamp = int(timeseries[-2][0]) + resolution = int_end_timestamp - int_second_last_end_timestamp + ten_data_point_seconds = resolution * 10 + ten_datapoints_ago = int_end_timestamp - ten_data_point_seconds + sixty_data_point_seconds = resolution * 60 + sixty_datapoints_ago = int_end_timestamp - sixty_data_point_seconds + last_hour_threshold = int_end_timestamp - (int_full_duration - sixty_datapoints_ago) + + series = pandas.Series([x[1] for x in timeseries if x[0] < last_hour_threshold]) + mean = (series).mean() + stdDev = (series).std() + t = tail_avg(timeseries, end_timestamp, full_duration) + + return abs(t - mean) > 3 * stdDev + except: + return None + + return False
+ + +
[docs]def stddev_from_average(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than one standard + deviation of the average. This does not exponentially weight the MA and so + is better for detecting anomalies with respect to the entire series. + """ + try: + series = pandas.Series([x[1] for x in timeseries]) + mean = series.mean() + stdDev = series.std() + t = tail_avg(timeseries, end_timestamp, full_duration) + + return abs(t - mean) > 3 * stdDev + except: + return None + + return False
+ + +
[docs]def stddev_from_moving_average(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the moving average. This is better for finding anomalies with + respect to the short term trends. + """ + try: + series = pandas.Series([x[1] for x in timeseries]) + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=50) + stdDev = pandas.stats.moments.ewmstd(series, com=50) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).mean() + stdDev = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).std(bias=False) + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1) - expAverage.iget(-1)) > 3 * stdDev.iget(-1) + else: + return abs(series.iat[-1] - expAverage.iat[-1]) > 3 * stdDev.iat[-1] +# http://stackoverflow.com/questions/28757389/loc-vs-iloc-vs-ix-vs-at-vs-iat + except: + return None + + return False
+ + +
[docs]def mean_subtraction_cumulation(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the value of the next datapoint in the + series is farther than three standard deviations out in cumulative terms + after subtracting the mean from each data point. + """ + + try: + series = pandas.Series([x[1] if x[1] else 0 for x in timeseries]) + series = series - series[0:len(series) - 1].mean() + stdDev = series[0:len(series) - 1].std() + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=15) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=15).mean() + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1)) > 3 * stdDev + else: + return abs(series.iat[-1]) > 3 * stdDev + except: + return None + + return False
+ + +
[docs]def least_squares(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the average of the last three datapoints + on a projected least squares model is greater than three sigma. + """ + + try: + x = np.array([t[0] for t in timeseries]) + y = np.array([t[1] for t in timeseries]) + A = np.vstack([x, np.ones(len(x))]).T + results = np.linalg.lstsq(A, y) + residual = results[1] + m, c = np.linalg.lstsq(A, y)[0] + errors = [] + # Evaluate append once, not every time in the loop - this gains ~0.020 s on + # every timeseries potentially + append_error = errors.append + for i, value in enumerate(y): + projected = m * x[i] + c + error = value - projected + # errors.append(error) + append_error(error) + + if len(errors) < 3: + return False + + std_dev = scipy.std(errors) + t = (errors[-1] + errors[-2] + errors[-3]) / 3 + + return abs(t) > std_dev * 3 and round(std_dev) != 0 and round(t) != 0 + except: + return None + + return False
+ + +
[docs]def histogram_bins(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the average of the last three datapoints falls + into a histogram bin with less than 20 other datapoints (you'll need to tweak + that number depending on your data) + + Returns: the size of the bin which contains the tail_avg. Smaller bin size + means more anomalous. + """ + + try: + int_end_timestamp = int(timeseries[-1][0]) + int_start_timestamp = int(timeseries[0][0]) + int_full_duration = int_end_timestamp - int_start_timestamp + + series = scipy.array([x[1] for x in timeseries]) + t = tail_avg(timeseries, int_end_timestamp, int_full_duration) + h = np.histogram(series, bins=15) + bins = h[1] + for index, bin_size in enumerate(h[0]): + if bin_size <= 20: + # Is it in the first bin? + if index == 0: + if t <= bins[0]: + return True + # Is it in the current bin? + elif t >= bins[index] and t < bins[index + 1]: + return True + except: + return None + + return False
+ + +
[docs]def ks_test(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if 2 sample Kolmogorov-Smirnov test indicates + that data distribution for last 10 datapoints (might be 10 minutes) is + different from the last 60 datapoints (might be an hour). + It produces false positives on non-stationary series so Augmented + Dickey-Fuller test applied to check for stationarity. + """ + + try: + int_end_timestamp = int(timeseries[-1][0]) + + hour_ago = int_end_timestamp - 3600 + ten_minutes_ago = int_end_timestamp - 600 + # Determine resolution of the data set + # reference = scipy.array([x[1] for x in timeseries if x[0] >= hour_ago and x[0] < ten_minutes_ago]) + # probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_minutes_ago]) + int_second_last_end_timestamp = int(timeseries[-2][0]) + resolution = int_end_timestamp - int_second_last_end_timestamp + ten_data_point_seconds = resolution * 10 + ten_datapoints_ago = int_end_timestamp - ten_data_point_seconds + sixty_data_point_seconds = resolution * 60 + sixty_datapoints_ago = int_end_timestamp - sixty_data_point_seconds + reference = scipy.array([x[1] for x in timeseries if x[0] >= sixty_datapoints_ago and x[0] < ten_datapoints_ago]) + probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_datapoints_ago]) + + if reference.size < 20 or probe.size < 20: + return False + + ks_d, ks_p_value = scipy.stats.ks_2samp(reference, probe) + + if ks_p_value < 0.05 and ks_d > 0.5: + adf = sm.tsa.stattools.adfuller(reference, 10) + if adf[1] < 0.05: + return True + except: + return None + + return False
+ + +
[docs]def detect_drop_off_cliff(timeseries, end_timestamp, full_duration): + """ + A timeseries is anomalous if the average of the last ten datapoints is <trigger> + times greater than the last data point. This algorithm is most suited to + timeseries with most datapoints being > 100 (e.g high rate). The arbitrary + <trigger> values become more noisy with lower value datapoints, but it still + matches drops off cliffs. + """ + + try: + if len(timeseries) < 21: + return False + + int_end_timestamp = int(timeseries[-1][0]) + # Determine resolution of the data set + int_second_last_end_timestamp = int(timeseries[-2][0]) + resolution = int_end_timestamp - int_second_last_end_timestamp + ten_data_point_seconds = resolution * 10 + ten_datapoints_ago = int_end_timestamp - ten_data_point_seconds + + ten_datapoint_array = scipy.array([x[1] for x in timeseries if x[0] <= int_end_timestamp and x[0] > ten_datapoints_ago]) + ten_datapoint_array_len = len(ten_datapoint_array) + if ten_datapoint_array_len > 3: + # DO NOT handle if negative integers in range, where is the bottom of + # of the cliff if a range goes negative? The maths does not work either + ten_datapoint_min_value = np.amin(ten_datapoint_array) + if ten_datapoint_min_value < 0: + return False + ten_datapoint_max_value = np.amax(ten_datapoint_array) + if ten_datapoint_max_value < 10: + return False + ten_datapoint_array_sum = np.sum(ten_datapoint_array) + ten_datapoint_value = int(ten_datapoint_array[-1]) + ten_datapoint_average = ten_datapoint_array_sum / ten_datapoint_array_len + ten_datapoint_value = int(ten_datapoint_array[-1]) + ten_datapoint_max_value = np.amax(ten_datapoint_array) + if ten_datapoint_max_value == 0: + return False + if ten_datapoint_max_value < 101: + trigger = 15 + if ten_datapoint_max_value < 20: + trigger = ten_datapoint_average / 2 + if ten_datapoint_max_value < 1: + trigger = 0.1 + if ten_datapoint_max_value > 100: + trigger = 100 + if ten_datapoint_value == 0: + # Cannot divide by 0, so set to 0.1 to prevent error + ten_datapoint_value = 0.1 + if ten_datapoint_value == 1: + trigger = 1 + if ten_datapoint_value == 1 and ten_datapoint_max_value < 10: + trigger = 0.1 + if ten_datapoint_value == 0.1 and ten_datapoint_average < 1 and ten_datapoint_array_sum < 7: + trigger = 7 + # Filter low rate and variable between 0 and 100 metrics + if ten_datapoint_value <= 1 and ten_datapoint_array_sum < 100 and ten_datapoint_array_sum > 1: + all_datapoints_array = scipy.array([x[1] for x in timeseries]) + all_datapoints_max_value = np.amax(all_datapoints_array) + if all_datapoints_max_value < 100: + # print "max_value for all datapoints at - " + str(int_end_timestamp) + " - " + str(all_datapoints_max_value) + return False + ten_datapoint_result = ten_datapoint_average / ten_datapoint_value + if int(ten_datapoint_result) > trigger: + return True + except: + return None + + return False
+ + +""" +This is no longer no man's land, but feel free to play and try new stuff +""" + + +
[docs]def run_algorithms( + timeseries, timeseries_name, end_timestamp, full_duration, + timeseries_file, skyline_app, algorithms): + """ + Iteratively run algorithms. + """ + + results_dir = os.path.dirname(timeseries_file) + + if not os.path.exists(results_dir): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(results_dir, mode_arg) + + start_analysis = int(time.time()) + + triggered_algorithms = [] + anomalous = False + + if str(algorithms) == "['all']": + if skyline_app == 'analyzer': + check_algorithms = ALGORITHMS + if skyline_app == 'mirage': + check_algorithms = MIRAGE_ALGORITHMS + if skyline_app == 'boundary': + check_algorithms = algorithms + if skyline_app == 'crucible': + check_algorithms = ALGORITHMS.append('detect_drop_off_cliff') + else: + check_algorithms = algorithms + + logger.info('checking algoritms - %s' % (str(check_algorithms))) + + for algorithm in check_algorithms: + detected = '' + try: + x_vals = np.arange(len(timeseries)) + y_vals = np.array([y[1] for y in timeseries]) + # Match default graphite graph size + plt.figure(figsize=(5.86, 3.08), dpi=100) + plt.plot(x_vals, y_vals) + + # Start a couple datapoints in for the tail average + for index in range(10, len(timeseries)): + sliced = timeseries[:index] + anomaly = globals()[algorithm](sliced, end_timestamp, full_duration) + + # Point out the datapoint if it's anomalous + if anomaly: + plt.plot([index], [sliced[-1][1]], 'ro') + detected = "DETECTED" + + if detected == "DETECTED": + results_filename = join(results_dir + "/" + algorithm + "." + detected + ".png") + # logger.info('ANOMALY DETECTED :: %s' % (algorithm)) + anomalous = True + triggered_algorithms.append(algorithm) + else: + results_filename = join(results_dir + "/" + algorithm + ".png") + + plt.savefig(results_filename, dpi=100) + # logger.info('%s :: %s' % (algorithm, results_filename)) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(results_filename, mode_arg) + except: + logger.error('error :: %s' % (traceback.format_exc())) + logger.info('info :: error thrown in algorithm running and plotting - %s' % (str(algorithm))) + + end_analysis = int(time.time()) + seconds_to_run = end_analysis - start_analysis +# logger.info( +# 'analysis of %s at a full duration of %s took %s seconds' % +# (timeseries_name, str(full_duration), str(seconds_to_run))) + + return anomalous, triggered_algorithms
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/horizon/agent.html b/docs/_build/html/_modules/horizon/agent.html new file mode 100644 index 00000000..cafa26c3 --- /dev/null +++ b/docs/_build/html/_modules/horizon/agent.html @@ -0,0 +1,352 @@ + + + + + + + + + + + horizon.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for horizon.agent

+import logging
+import time
+import sys
+from os import getpid
+from os.path import dirname, abspath, isdir, join
+from multiprocessing import Queue
+from daemon import runner
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+from listen import Listen
+from roomba import Roomba
+from worker import Worker
+
+skyline_app = 'horizon'
+skyline_app_logger = '%sLog' % skyline_app
+# logger = logging.getLogger("HorizonLog")
+# TODO: http://stackoverflow.com/questions/6728236/exception-thrown-in-multiprocessing-pool-not-detected
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+
+
[docs]class Horizon(): + """ + Initializes Horizon + """ + + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + """ + Determine the `MAX_QUEUE_SIZE` for the listen process. + + Determine if horizon should populate the mini redis store for Oculus. + + Starts the defined number of `WORKER_PROCESSES`, with the first worker + populating the canary metric. + + Start the pickle (and UDP) listen processes. + + Start roomba. + """ + logger.info('agent starting skyline %s' % skyline_app) + listen_queue = Queue(maxsize=settings.MAX_QUEUE_SIZE) + pid = getpid() + + # If we're not using oculus, don't bother writing to mini + try: + skip_mini = True if settings.OCULUS_HOST == '' else False + except Exception: + skip_mini = True + + # Start the workers + for i in range(settings.WORKER_PROCESSES): + if i == 0: + logger.info('%s :: starting Worker - canary' % skyline_app) + Worker(listen_queue, pid, skip_mini, canary=True).start() + else: + logger.info('%s :: starting Worker' % skyline_app) + Worker(listen_queue, pid, skip_mini).start() + + # Start the listeners + logger.info('%s :: starting Listen - pickle' % skyline_app) + Listen(settings.PICKLE_PORT, listen_queue, pid, type="pickle").start() + logger.info('%s :: starting Listen - udp' % skyline_app) + Listen(settings.UDP_PORT, listen_queue, pid, type="udp").start() + + # Start the roomba + logger.info('%s :: starting Roomba' % skyline_app) + Roomba(pid, skip_mini).start() + + # Warn the Mac users + try: + listen_queue.qsize() + except NotImplementedError: + logger.info('WARNING: Queue().qsize() not implemented on Unix platforms like Mac OS X. Queue size logging will be unavailable.') + + # Keep yourself occupied, sucka + while 1: + time.sleep(100)
+ + +
[docs]def run(): + """ + Start the Horizon agent and logger. + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(100, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + horizon = Horizon() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + horizon.run() + else: + daemon_runner = runner.DaemonRunner(horizon) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action()
+ +if __name__ == "__main__": + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/horizon/listen.html b/docs/_build/html/_modules/horizon/listen.html new file mode 100644 index 00000000..fcd87899 --- /dev/null +++ b/docs/_build/html/_modules/horizon/listen.html @@ -0,0 +1,525 @@ + + + + + + + + + + + horizon.listen — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for horizon.listen

+import socket
+from os import kill, getpid
+try:
+    from Queue import Full
+except ImportError:
+    from queue import Full
+from multiprocessing import Process
+from struct import Struct, unpack
+from msgpack import unpackb
+import sys
+from time import time, sleep
+
+import logging
+import os.path
+from os import remove as os_remove
+import settings
+from skyline_functions import send_graphite_metric
+
+parent_skyline_app = 'horizon'
+child_skyline_app = 'listen'
+skyline_app_logger = '%sLog' % parent_skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app = '%s.%s' % (parent_skyline_app, child_skyline_app)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, parent_skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+try:
+    SERVER_METRIC_PATH = '.%s' % settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.%s%s.%s' % (
+    parent_skyline_app, SERVER_METRIC_PATH, child_skyline_app)
+
+python_version = int(sys.version_info[0])
+
+# SafeUnpickler taken from Carbon: https://github.com/graphite-project/carbon/blob/master/lib/carbon/util.py
+if python_version == 2:
+    try:
+        from cStringIO import StringIO
+    except ImportError:
+        from StringIO import StringIO
+if python_version == 3:
+    import io
+
+try:
+    import cPickle as pickle
+    USING_CPICKLE = True
+except:
+    import pickle
+    USING_CPICKLE = False
+
+# This whole song & dance is due to pickle being insecure
+# yet performance critical for carbon. We leave the insecure
+# mode (which is faster) as an option (USE_INSECURE_UNPICKLER).
+# The SafeUnpickler classes were largely derived from
+# http://nadiana.com/python-pickle-insecure
+if USING_CPICKLE:
+    class SafeUnpickler(object):
+        PICKLE_SAFE = {
+            'copy_reg': set(['_reconstructor']),
+            '__builtin__': set(['object']),
+        }
+
+        @classmethod
+        def find_class(cls, module, name):
+            if module not in cls.PICKLE_SAFE:
+                raise pickle.UnpicklingError('Attempting to unpickle unsafe module %s' % module)
+            __import__(module)
+            mod = sys.modules[module]
+            if name not in cls.PICKLE_SAFE[module]:
+                raise pickle.UnpicklingError('Attempting to unpickle unsafe class %s' % name)
+            return getattr(mod, name)
+
+        @classmethod
+        def loads(cls, pickle_string):
+            pickle_obj = pickle.Unpickler(StringIO(pickle_string))
+            pickle_obj.find_global = cls.find_class
+            return pickle_obj.load()
+
+else:
+
[docs] class SafeUnpickler(pickle.Unpickler): + PICKLE_SAFE = { + 'copy_reg': set(['_reconstructor']), + '__builtin__': set(['object']), + } + +
[docs] def find_class(self, module, name): + if module not in self.PICKLE_SAFE: + raise pickle.UnpicklingError('Attempting to unpickle unsafe module %s' % module) + __import__(module) + mod = sys.modules[module] + if name not in self.PICKLE_SAFE[module]: + raise pickle.UnpicklingError('Attempting to unpickle unsafe class %s' % name) + return getattr(mod, name)
+ + @classmethod +
[docs] def loads(cls, pickle_string): + return cls(StringIO(pickle_string)).load()
+# //SafeUnpickler + + +
[docs]class Listen(Process): + """ + The listener is responsible for listening on a port. + """ + def __init__(self, port, queue, parent_pid, type="pickle"): + super(Listen, self).__init__() + try: + self.ip = settings.HORIZON_IP + except AttributeError: + # Default for backwards compatibility + self.ip = socket.gethostname() + self.port = port + self.q = queue + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.type = type + + # Use the safe unpickler that comes with carbon rather than standard python pickle/cpickle + self.unpickler = SafeUnpickler + +
[docs] def gen_unpickle(self, infile): + """ + Generate a pickle from a stream + """ + try: + bunch = self.unpickler.loads(infile) + yield bunch + except EOFError: + return
+ +
[docs] def read_all(self, sock, n): + """ + Read n bytes from a stream + """ + data = '' + while n > 0: + # Break the loop when connection closes. #8 @earthgecko + # https://github.com/earthgecko/skyline/pull/8/files + # @earthgecko merged 1 commit into earthgecko:master from + # mlowicki:fix_infinite_loop on 16 Mar 2015 + # Break the loop when connection closes. #115 @etsy + chunk = sock.recv(n) + count = len(chunk) + + if count == 0: + break + + n -= count + data += chunk + return data
+ +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def listen_pickle(self): + """ + Listen for pickles over tcp + """ + while 1: + try: + # Set up the TCP listening socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((self.ip, self.port)) + s.setblocking(1) + s.listen(5) + logger.info('%s :: listening over tcp for pickles on %s' % (skyline_app, self.port)) + + (conn, address) = s.accept() + logger.info('%s :: connection from %s:%s' % (skyline_app, address[0], self.port)) + + chunk = [] + while 1: + self.check_if_parent_is_alive() + try: + length = Struct('!I').unpack(self.read_all(conn, 4)) + body = self.read_all(conn, length[0]) + + # Iterate and chunk each individual datapoint + for bunch in self.gen_unpickle(body): + for metric in bunch: + chunk.append(metric) + + # Queue the chunk and empty the variable + if len(chunk) > settings.CHUNK_SIZE: + try: + self.q.put(list(chunk), block=False) + chunk[:] = [] + + # Drop chunk if queue is full + except Full: + chunks_dropped = str(len(chunk)) + logger.info( + '%s :: pickle queue is full, dropping %s datapoints' + % (skyline_app, chunks_dropped)) +# self.send_graphite_metric( +# 'skyline.horizon.' + SERVER_METRIC_PATH + 'pickle_chunks_dropped', +# chunks_dropped) + send_metric_name = '%s.pickle_chunks_dropped' % skyline_app_graphite_namespace + send_graphite_metric(skyline_app, send_metric_name, chunks_dropped) + chunk[:] = [] + + except Exception as e: + logger.info(e) + logger.info('%s :: incoming pickle connection dropped, attempting to reconnect' % skyline_app) + break + + except Exception as e: + logger.info('%s :: can not connect to socket: %s' % (skyline_app, str(e))) + break
+ +
[docs] def listen_udp(self): + """ + Listen over udp for MessagePack strings + """ + while 1: + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.bind((self.ip, self.port)) + logger.info('%s :: listening over udp for messagepack on %s' % (skyline_app, self.port)) + + chunk = [] + while 1: + self.check_if_parent_is_alive() + data, addr = s.recvfrom(1024) + metric = unpackb(data) + chunk.append(metric) + + # Queue the chunk and empty the variable + if len(chunk) > settings.CHUNK_SIZE: + try: + self.q.put(list(chunk), block=False) + chunk[:] = [] + + # Drop chunk if queue is full + except Full: + chunks_dropped = str(len(chunk)) + logger.info( + '%s :: UDP queue is full, dropping %s datapoints' + % (skyline_app, chunks_dropped)) + send_metric_name = '%s.udp_chunks_dropped' % skyline_app_graphite_namespace + send_graphite_metric(skyline_app, send_metric_name, chunks_dropped) + chunk[:] = [] + + except Exception as e: + logger.info('%s :: cannot connect to socket: %s' % (skyline_app, str(e))) + break
+ +
[docs] def run(self): + """ + Called when process intializes. + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os_remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os_remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + logger.info('%s :: started listener' % skyline_app) + + if self.type == 'pickle': + self.listen_pickle() + elif self.type == 'udp': + self.listen_udp() + else: + logger.error('%s :: unknown listener format' % skyline_app)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/horizon/roomba.html b/docs/_build/html/_modules/horizon/roomba.html new file mode 100644 index 00000000..fc590ee8 --- /dev/null +++ b/docs/_build/html/_modules/horizon/roomba.html @@ -0,0 +1,516 @@ + + + + + + + + + + + horizon.roomba — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for horizon.roomba

+from os import kill, getpid
+from redis import StrictRedis, WatchError
+from multiprocessing import Process
+from threading import Thread
+from msgpack import Unpacker, packb
+try:
+    from types import TupleType
+except ImportError:
+    eliminated_in_python3 = True
+from time import time, sleep
+from math import ceil
+import traceback
+import logging
+
+import sys
+import os.path
+from os import remove as os_remove
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+parent_skyline_app = 'horizon'
+child_skyline_app = 'roomba'
+skyline_app_logger = '%sLog' % parent_skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app = '%s.%s' % (parent_skyline_app, child_skyline_app)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, parent_skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+python_version = int(sys.version_info[0])
+
+
+
[docs]class Roomba(Thread): + """ + The Roomba is responsible for deleting keys older than DURATION. + """ + def __init__(self, parent_pid, skip_mini): + super(Roomba, self).__init__() + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + self.daemon = True + self.parent_pid = parent_pid + self.skip_mini = skip_mini + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory. + """ + try: + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def vacuum(self, i, namespace, duration): + """ + Trim metrics that are older than settings.FULL_DURATION and purge old + metrics. + """ + begin = time() + logger.info('%s :: started vacuum' % (skyline_app)) + + # Discover assigned metrics + namespace_unique_metrics = '%sunique_metrics' % str(namespace) + unique_metrics = list(self.redis_conn.smembers(namespace_unique_metrics)) + keys_per_processor = int(ceil(float(len(unique_metrics)) / float(settings.ROOMBA_PROCESSES))) + if i == settings.ROOMBA_PROCESSES: + assigned_max = len(unique_metrics) + else: + assigned_max = min(len(unique_metrics), i * keys_per_processor) + assigned_min = (i - 1) * keys_per_processor + assigned_keys = range(assigned_min, assigned_max) + + # Compile assigned metrics + assigned_metrics = [unique_metrics[index] for index in assigned_keys] + + euthanized = 0 + blocked = 0 + trimmed_keys = 0 + active_keys = 0 + + for i in xrange(len(assigned_metrics)): + self.check_if_parent_is_alive() + + pipe = self.redis_conn.pipeline() + now = time() + key = assigned_metrics[i] + + try: + # WATCH the key + pipe.watch(key) + + # Everything below NEEDS to happen before another datapoint + # comes in. If your data has a very small resolution (<.1s), + # this technique may not suit you. + raw_series = pipe.get(key) + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = sorted([unpacked for unpacked in unpacker]) + + # Put pipe back in multi mode + pipe.multi() + + # There's one value. Purge if it's too old + try: + if python_version == 2: + if not isinstance(timeseries[0], TupleType): + if timeseries[0] < now - duration: + pipe.delete(key) + pipe.srem(namespace_unique_metrics, key) + pipe.execute() + euthanized += 1 + continue + if python_version == 3: + if not isinstance(timeseries[0], tuple): + if timeseries[0] < now - duration: + pipe.delete(key) + pipe.srem(namespace_unique_metrics, key) + pipe.execute() + euthanized += 1 + continue + except IndexError: + continue + + # Check if the last value is too old and purge + if timeseries[-1][0] < now - duration: + pipe.delete(key) + pipe.srem(namespace_unique_metrics, key) + pipe.execute() + euthanized += 1 + continue + + # Remove old datapoints and duplicates from timeseries + temp = set() + temp_add = temp.add + delta = now - duration + trimmed = [ + tuple for tuple in timeseries + if tuple[0] > delta and + tuple[0] not in temp and not + temp_add(tuple[0]) + ] + + # Purge if everything was deleted, set key otherwise + if len(trimmed) > 0: + # Serialize and turn key back into not-an-array + btrimmed = packb(trimmed) + if len(trimmed) <= 15: + value = btrimmed[1:] + elif len(trimmed) <= 65535: + value = btrimmed[3:] + trimmed_keys += 1 + else: + value = btrimmed[5:] + trimmed_keys += 1 + pipe.set(key, value) + active_keys += 1 + else: + pipe.delete(key) + pipe.srem(namespace_unique_metrics, key) + euthanized += 1 + + pipe.execute() + + except WatchError: + blocked += 1 + assigned_metrics.append(key) + except Exception as e: + # If something bad happens, zap the key and hope it goes away + pipe.delete(key) + pipe.srem(namespace_unique_metrics, key) + pipe.execute() + euthanized += 1 + logger.info(e) + logger.info('%s :: vacuum Euthanizing %s' % (skyline_app, key)) + finally: + pipe.reset() + + logger.info( + '%s :: vacuum operated on %s %d keys in %f seconds' % + (skyline_app, namespace, len(assigned_metrics), time() - begin)) + logger.info('%s :: vaccum %s keyspace is now %d keys' % (skyline_app, namespace, (len(assigned_metrics) - euthanized))) + logger.info('%s :: vaccum blocked %d times' % (skyline_app, blocked)) + logger.info('%s :: vacuum euthanized %d geriatric keys' % (skyline_app, euthanized)) + logger.info('%s :: vacuum processed %d active keys' % (skyline_app, active_keys)) + logger.info('%s :: vacuum potentially trimmed %d keys' % (skyline_app, trimmed_keys))
+ + # sleeping in the main process is more CPU efficient than sleeping + # in the vacuum def + # if (time() - begin < 30): + # logger.info(skyline_app + ' :: sleeping due to low run time...') + # sleep(10) + +
[docs] def run(self): + """ + Called when process initializes. + """ + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os_remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os_remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + logger.info('%s :: started roomba' % skyline_app) + + while 1: + now = time() + + # Make sure Redis is up + try: + self.redis_conn.ping() + except: + logger.error( + '%s :: roomba can\'t connect to redis at socket path %s' % + (skyline_app, settings.REDIS_SOCKET_PATH)) + sleep(10) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + # Spawn processes + pids = [] + for i in range(1, settings.ROOMBA_PROCESSES + 1): + if not self.skip_mini: + logger.info('%s :: starting vacuum process on mini namespace' % skyline_app) + p = Process(target=self.vacuum, args=(i, settings.MINI_NAMESPACE, settings.MINI_DURATION + settings.ROOMBA_GRACE_TIME)) + pids.append(p) + p.start() + + logger.info('%s :: starting vacuum process' % skyline_app) + p = Process(target=self.vacuum, args=(i, settings.FULL_NAMESPACE, settings.FULL_DURATION + settings.ROOMBA_GRACE_TIME)) + pids.append(p) + p.start() + + # Send wait signal to zombie processes + # for p in pids: + # p.join() + # deroomba - kill any lingering vacuum processes + # Changed to manage Roomba processes as edge cases related to I/O + # wait have been experienced that resulted in Roomba stalling so a + # ROOMBA_TIMEOUT setting was added and here we use the pattern + # described by http://stackoverflow.com/users/2073595/dano at + # http://stackoverflow.com/a/26064238 to monitor and kill any + # stalled processes rather than using p.join(TIMEOUT) - 20160505 + # @earthgecko ref 1342 + logger.info('%s :: allowing vacuum process/es %s seconds to run' % ( + skyline_app, str(settings.ROOMBA_TIMEOUT))) + start = time() + while time() - start <= settings.ROOMBA_TIMEOUT: + if any(p.is_alive() for p in pids): + # Just to avoid hogging the CPU + sleep(.1) + else: + # All the processes are done, break now. + time_to_run = time() - start + logger.info('%s :: vacuum processes completed in %.2f' % (skyline_app, time_to_run)) + break + else: + # We only enter this if we didn't 'break' above. + logger.info('%s :: timed out, killing all Roomba processes' % (skyline_app)) + for p in pids: + p.terminate() + p.join() + + # sleeping in the main process is more CPU efficient than sleeping + # in the vacuum def also roomba is quite CPU intensive so we only + # what to run roomba once every minute + process_runtime = time() - now + roomba_optimum_run_duration = 60 + if process_runtime < roomba_optimum_run_duration: + sleep_for = (roomba_optimum_run_duration - process_runtime) + logger.info('%s :: sleeping %.2f for due to low run time' % (skyline_app, sleep_for)) + sleep(sleep_for)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/horizon/worker.html b/docs/_build/html/_modules/horizon/worker.html new file mode 100644 index 00000000..a9640558 --- /dev/null +++ b/docs/_build/html/_modules/horizon/worker.html @@ -0,0 +1,420 @@ + + + + + + + + + + + horizon.worker — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for horizon.worker

+from os import kill, system
+from redis import StrictRedis, WatchError
+from multiprocessing import Process
+try:
+    from Queue import Empty
+except ImportError:
+    from queue import Empty
+from msgpack import packb
+from time import time, sleep
+
+import traceback
+import logging
+import socket
+
+import sys
+import os.path
+from os import remove as os_remove
+
+import settings
+from skyline_functions import send_graphite_metric
+
+parent_skyline_app = 'horizon'
+child_skyline_app = 'worker'
+skyline_app_logger = '%sLog' % parent_skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app = '%s.%s' % (parent_skyline_app, child_skyline_app)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, parent_skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+try:
+    SERVER_METRIC_PATH = '.%s' % settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.%s%s.%s' % (
+    parent_skyline_app, SERVER_METRIC_PATH, child_skyline_app)
+
+WORKER_DEBUG = False
+
+
+
[docs]class Worker(Process): + """ + The worker processes chunks from the queue and appends + the latest datapoints to their respective timesteps in Redis. + """ + def __init__(self, queue, parent_pid, skip_mini, canary=False): + super(Worker, self).__init__() + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + self.q = queue + self.parent_pid = parent_pid + self.daemon = True + self.canary = canary + self.skip_mini = skip_mini + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory. + """ + try: + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def in_skip_list(self, metric_name): + """ + Check if the metric is in SKIP_LIST. + """ + for to_skip in settings.SKIP_LIST: + if to_skip in metric_name: + return True + + return False
+ +
[docs] def run(self): + """ + Called when the process intializes. + """ + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os_remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os_remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + logger.info('%s :: started worker' % skyline_app) + + FULL_NAMESPACE = settings.FULL_NAMESPACE + MINI_NAMESPACE = settings.MINI_NAMESPACE + MAX_RESOLUTION = settings.MAX_RESOLUTION + full_uniques = '%sunique_metrics' % FULL_NAMESPACE + mini_uniques = '%sunique_metrics' % MINI_NAMESPACE + pipe = self.redis_conn.pipeline() + + last_send_to_graphite = time() + queue_sizes = [] + + # python-2.x and python3.x had while 1 and while True differently + # while 1: + running = True + while running: + + # Make sure Redis is up + try: + self.redis_conn.ping() + except: + logger.error('%s :: can\'t connect to redis at socket path %s' % (skyline_app, settings.REDIS_SOCKET_PATH)) + sleep(10) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + pipe = self.redis_conn.pipeline() + continue + + try: + # Get a chunk from the queue with a 15 second timeout + chunk = self.q.get(True, 15) + now = time() + + for metric in chunk: + + # Check if we should skip it + if self.in_skip_list(metric[0]): + continue + + # Bad data coming in + if metric[1][0] < now - MAX_RESOLUTION: + continue + + # Append to messagepack main namespace + key = ''.join((FULL_NAMESPACE, metric[0])) + pipe.append(key, packb(metric[1])) + pipe.sadd(full_uniques, key) + + if not self.skip_mini: + # Append to mini namespace + mini_key = ''.join((MINI_NAMESPACE, metric[0])) + pipe.append(mini_key, packb(metric[1])) + pipe.sadd(mini_uniques, mini_key) + + pipe.execute() + + except Empty: + logger.info('%s :: worker queue is empty and timed out' % skyline_app) + except WatchError: + logger.error('%s :: WatchError - %s' % (skyline_app, str(key))) + except NotImplementedError: + pass + except Exception as e: + logger.error('%s :: error: %s' % (skyline_app, str(e))) + + # Log progress + if self.canary: + logger.info('%s :: queue size at %d' % (skyline_app, self.q.qsize())) + queue_sizes.append(self.q.qsize()) + # Only send average queue mertics to graphite once per 10 seconds + now = time() + last_sent_graphite = now - last_send_to_graphite + if last_sent_graphite > 10: + number_queue_sizes = len(queue_sizes) + total_of_queue_sizes = sum(queue_sizes) + if total_of_queue_sizes > 0: + average_queue_size = total_of_queue_sizes / number_queue_sizes + else: + average_queue_size = 0 + logger.info('%s :: total queue size for the last 10 seconds - %s' % (skyline_app, str(total_of_queue_sizes))) + logger.info('%s :: total queue values known for the last 10 seconds - %s' % (skyline_app, str(number_queue_sizes))) + logger.info('%s :: average queue size for the last 10 seconds - %s' % (skyline_app, str(average_queue_size))) + # self.send_graphite_metric('skyline.horizon.' + SERVER_METRIC_PATH + 'queue_size', self.q.qsize()) +# self.send_graphite_metric('queue_size', average_queue_size) + send_metric_name = '%s.queue_size' % skyline_app_graphite_namespace + send_graphite_metric(skyline_app, send_metric_name, average_queue_size) + + # reset queue_sizes and last_sent_graphite + queue_sizes = [] + last_send_to_graphite = time()
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html new file mode 100644 index 00000000..661d2515 --- /dev/null +++ b/docs/_build/html/_modules/index.html @@ -0,0 +1,249 @@ + + + + + + + + + + + Overview: module code — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/logging.html b/docs/_build/html/_modules/logging.html new file mode 100644 index 00000000..76b61282 --- /dev/null +++ b/docs/_build/html/_modules/logging.html @@ -0,0 +1,1957 @@ + + + + + + + + + + + logging — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for logging

+# Copyright 2001-2014 by Vinay Sajip. All Rights Reserved.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation, and that the name of Vinay Sajip
+# not be used in advertising or publicity pertaining to distribution
+# of the software without specific, written prior permission.
+# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Logging package for Python. Based on PEP 282 and comments thereto in
+comp.lang.python.
+
+Copyright (C) 2001-2014 Vinay Sajip. All Rights Reserved.
+
+To use, simply 'import logging' and log away!
+"""
+
+import sys, os, time, cStringIO, traceback, warnings, weakref, collections
+
+__all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
+           'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO',
+           'LogRecord', 'Logger', 'LoggerAdapter', 'NOTSET', 'NullHandler',
+           'StreamHandler', 'WARN', 'WARNING', 'addLevelName', 'basicConfig',
+           'captureWarnings', 'critical', 'debug', 'disable', 'error',
+           'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass',
+           'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning']
+
+try:
+    import codecs
+except ImportError:
+    codecs = None
+
+try:
+    import thread
+    import threading
+except ImportError:
+    thread = None
+
+__author__  = "Vinay Sajip <vinay_sajip@red-dove.com>"
+__status__  = "production"
+# Note: the attributes below are no longer maintained.
+__version__ = "0.5.1.2"
+__date__    = "07 February 2010"
+
+#---------------------------------------------------------------------------
+#   Miscellaneous module data
+#---------------------------------------------------------------------------
+try:
+    unicode
+    _unicode = True
+except NameError:
+    _unicode = False
+
+# next bit filched from 1.5.2's inspect.py
+def currentframe():
+    """Return the frame object for the caller's stack frame."""
+    try:
+        raise Exception
+    except:
+        return sys.exc_info()[2].tb_frame.f_back
+
+if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3)
+# done filching
+
+#
+# _srcfile is used when walking the stack to check when we've got the first
+# caller stack frame.
+#
+_srcfile = os.path.normcase(currentframe.__code__.co_filename)
+
+# _srcfile is only used in conjunction with sys._getframe().
+# To provide compatibility with older versions of Python, set _srcfile
+# to None if _getframe() is not available; this value will prevent
+# findCaller() from being called.
+#if not hasattr(sys, "_getframe"):
+#    _srcfile = None
+
+#
+#_startTime is used as the base when calculating the relative time of events
+#
+_startTime = time.time()
+
+#
+#raiseExceptions is used to see if exceptions during handling should be
+#propagated
+#
+raiseExceptions = 1
+
+#
+# If you don't want threading information in the log, set this to zero
+#
+logThreads = 1
+
+#
+# If you don't want multiprocessing information in the log, set this to zero
+#
+logMultiprocessing = 1
+
+#
+# If you don't want process information in the log, set this to zero
+#
+logProcesses = 1
+
+#---------------------------------------------------------------------------
+#   Level related stuff
+#---------------------------------------------------------------------------
+#
+# Default levels and level names, these can be replaced with any positive set
+# of values having corresponding names. There is a pseudo-level, NOTSET, which
+# is only really there as a lower limit for user-defined levels. Handlers and
+# loggers are initialized with NOTSET so that they will log all messages, even
+# at user-defined levels.
+#
+
+CRITICAL = 50
+FATAL = CRITICAL
+ERROR = 40
+WARNING = 30
+WARN = WARNING
+INFO = 20
+DEBUG = 10
+NOTSET = 0
+
+_levelNames = {
+    CRITICAL : 'CRITICAL',
+    ERROR : 'ERROR',
+    WARNING : 'WARNING',
+    INFO : 'INFO',
+    DEBUG : 'DEBUG',
+    NOTSET : 'NOTSET',
+    'CRITICAL' : CRITICAL,
+    'ERROR' : ERROR,
+    'WARN' : WARNING,
+    'WARNING' : WARNING,
+    'INFO' : INFO,
+    'DEBUG' : DEBUG,
+    'NOTSET' : NOTSET,
+}
+
+def getLevelName(level):
+    """
+    Return the textual representation of logging level 'level'.
+
+    If the level is one of the predefined levels (CRITICAL, ERROR, WARNING,
+    INFO, DEBUG) then you get the corresponding string. If you have
+    associated levels with names using addLevelName then the name you have
+    associated with 'level' is returned.
+
+    If a numeric value corresponding to one of the defined levels is passed
+    in, the corresponding string representation is returned.
+
+    Otherwise, the string "Level %s" % level is returned.
+    """
+    return _levelNames.get(level, ("Level %s" % level))
+
+def addLevelName(level, levelName):
+    """
+    Associate 'levelName' with 'level'.
+
+    This is used when converting levels to text during message formatting.
+    """
+    _acquireLock()
+    try:    #unlikely to cause an exception, but you never know...
+        _levelNames[level] = levelName
+        _levelNames[levelName] = level
+    finally:
+        _releaseLock()
+
+def _checkLevel(level):
+    if isinstance(level, (int, long)):
+        rv = level
+    elif str(level) == level:
+        if level not in _levelNames:
+            raise ValueError("Unknown level: %r" % level)
+        rv = _levelNames[level]
+    else:
+        raise TypeError("Level not an integer or a valid string: %r" % level)
+    return rv
+
+#---------------------------------------------------------------------------
+#   Thread-related stuff
+#---------------------------------------------------------------------------
+
+#
+#_lock is used to serialize access to shared data structures in this module.
+#This needs to be an RLock because fileConfig() creates and configures
+#Handlers, and so might arbitrary user threads. Since Handler code updates the
+#shared dictionary _handlers, it needs to acquire the lock. But if configuring,
+#the lock would already have been acquired - so we need an RLock.
+#The same argument applies to Loggers and Manager.loggerDict.
+#
+if thread:
+    _lock = threading.RLock()
+else:
+    _lock = None
+
+def _acquireLock():
+    """
+    Acquire the module-level lock for serializing access to shared data.
+
+    This should be released with _releaseLock().
+    """
+    if _lock:
+        _lock.acquire()
+
+def _releaseLock():
+    """
+    Release the module-level lock acquired by calling _acquireLock().
+    """
+    if _lock:
+        _lock.release()
+
+#---------------------------------------------------------------------------
+#   The logging record
+#---------------------------------------------------------------------------
+
+class LogRecord(object):
+    """
+    A LogRecord instance represents an event being logged.
+
+    LogRecord instances are created every time something is logged. They
+    contain all the information pertinent to the event being logged. The
+    main information passed in is in msg and args, which are combined
+    using str(msg) % args to create the message field of the record. The
+    record also includes information such as when the record was created,
+    the source line where the logging call was made, and any exception
+    information to be logged.
+    """
+    def __init__(self, name, level, pathname, lineno,
+                 msg, args, exc_info, func=None):
+        """
+        Initialize a logging record with interesting information.
+        """
+        ct = time.time()
+        self.name = name
+        self.msg = msg
+        #
+        # The following statement allows passing of a dictionary as a sole
+        # argument, so that you can do something like
+        #  logging.debug("a %(a)d b %(b)s", {'a':1, 'b':2})
+        # Suggested by Stefan Behnel.
+        # Note that without the test for args[0], we get a problem because
+        # during formatting, we test to see if the arg is present using
+        # 'if self.args:'. If the event being logged is e.g. 'Value is %d'
+        # and if the passed arg fails 'if self.args:' then no formatting
+        # is done. For example, logger.warn('Value is %d', 0) would log
+        # 'Value is %d' instead of 'Value is 0'.
+        # For the use case of passing a dictionary, this should not be a
+        # problem.
+        # Issue #21172: a request was made to relax the isinstance check
+        # to hasattr(args[0], '__getitem__'). However, the docs on string
+        # formatting still seem to suggest a mapping object is required.
+        # Thus, while not removing the isinstance check, it does now look
+        # for collections.Mapping rather than, as before, dict.
+        if (args and len(args) == 1 and isinstance(args[0], collections.Mapping)
+            and args[0]):
+            args = args[0]
+        self.args = args
+        self.levelname = getLevelName(level)
+        self.levelno = level
+        self.pathname = pathname
+        try:
+            self.filename = os.path.basename(pathname)
+            self.module = os.path.splitext(self.filename)[0]
+        except (TypeError, ValueError, AttributeError):
+            self.filename = pathname
+            self.module = "Unknown module"
+        self.exc_info = exc_info
+        self.exc_text = None      # used to cache the traceback text
+        self.lineno = lineno
+        self.funcName = func
+        self.created = ct
+        self.msecs = (ct - long(ct)) * 1000
+        self.relativeCreated = (self.created - _startTime) * 1000
+        if logThreads and thread:
+            self.thread = thread.get_ident()
+            self.threadName = threading.current_thread().name
+        else:
+            self.thread = None
+            self.threadName = None
+        if not logMultiprocessing:
+            self.processName = None
+        else:
+            self.processName = 'MainProcess'
+            mp = sys.modules.get('multiprocessing')
+            if mp is not None:
+                # Errors may occur if multiprocessing has not finished loading
+                # yet - e.g. if a custom import hook causes third-party code
+                # to run when multiprocessing calls import. See issue 8200
+                # for an example
+                try:
+                    self.processName = mp.current_process().name
+                except StandardError:
+                    pass
+        if logProcesses and hasattr(os, 'getpid'):
+            self.process = os.getpid()
+        else:
+            self.process = None
+
+    def __str__(self):
+        return '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,
+            self.pathname, self.lineno, self.msg)
+
+    def getMessage(self):
+        """
+        Return the message for this LogRecord.
+
+        Return the message for this LogRecord after merging any user-supplied
+        arguments with the message.
+        """
+        if not _unicode: #if no unicode support...
+            msg = str(self.msg)
+        else:
+            msg = self.msg
+            if not isinstance(msg, basestring):
+                try:
+                    msg = str(self.msg)
+                except UnicodeError:
+                    msg = self.msg      #Defer encoding till later
+        if self.args:
+            msg = msg % self.args
+        return msg
+
+def makeLogRecord(dict):
+    """
+    Make a LogRecord whose attributes are defined by the specified dictionary,
+    This function is useful for converting a logging event received over
+    a socket connection (which is sent as a dictionary) into a LogRecord
+    instance.
+    """
+    rv = LogRecord(None, None, "", 0, "", (), None, None)
+    rv.__dict__.update(dict)
+    return rv
+
+#---------------------------------------------------------------------------
+#   Formatter classes and functions
+#---------------------------------------------------------------------------
+
+class Formatter(object):
+    """
+    Formatter instances are used to convert a LogRecord to text.
+
+    Formatters need to know how a LogRecord is constructed. They are
+    responsible for converting a LogRecord to (usually) a string which can
+    be interpreted by either a human or an external system. The base Formatter
+    allows a formatting string to be specified. If none is supplied, the
+    default value of "%s(message)\\n" is used.
+
+    The Formatter can be initialized with a format string which makes use of
+    knowledge of the LogRecord attributes - e.g. the default value mentioned
+    above makes use of the fact that the user's message and arguments are pre-
+    formatted into a LogRecord's message attribute. Currently, the useful
+    attributes in a LogRecord are described by:
+
+    %(name)s            Name of the logger (logging channel)
+    %(levelno)s         Numeric logging level for the message (DEBUG, INFO,
+                        WARNING, ERROR, CRITICAL)
+    %(levelname)s       Text logging level for the message ("DEBUG", "INFO",
+                        "WARNING", "ERROR", "CRITICAL")
+    %(pathname)s        Full pathname of the source file where the logging
+                        call was issued (if available)
+    %(filename)s        Filename portion of pathname
+    %(module)s          Module (name portion of filename)
+    %(lineno)d          Source line number where the logging call was issued
+                        (if available)
+    %(funcName)s        Function name
+    %(created)f         Time when the LogRecord was created (time.time()
+                        return value)
+    %(asctime)s         Textual time when the LogRecord was created
+    %(msecs)d           Millisecond portion of the creation time
+    %(relativeCreated)d Time in milliseconds when the LogRecord was created,
+                        relative to the time the logging module was loaded
+                        (typically at application startup time)
+    %(thread)d          Thread ID (if available)
+    %(threadName)s      Thread name (if available)
+    %(process)d         Process ID (if available)
+    %(message)s         The result of record.getMessage(), computed just as
+                        the record is emitted
+    """
+
+    converter = time.localtime
+
+    def __init__(self, fmt=None, datefmt=None):
+        """
+        Initialize the formatter with specified format strings.
+
+        Initialize the formatter either with the specified format string, or a
+        default as described above. Allow for specialized date formatting with
+        the optional datefmt argument (if omitted, you get the ISO8601 format).
+        """
+        if fmt:
+            self._fmt = fmt
+        else:
+            self._fmt = "%(message)s"
+        self.datefmt = datefmt
+
+    def formatTime(self, record, datefmt=None):
+        """
+        Return the creation time of the specified LogRecord as formatted text.
+
+        This method should be called from format() by a formatter which
+        wants to make use of a formatted time. This method can be overridden
+        in formatters to provide for any specific requirement, but the
+        basic behaviour is as follows: if datefmt (a string) is specified,
+        it is used with time.strftime() to format the creation time of the
+        record. Otherwise, the ISO8601 format is used. The resulting
+        string is returned. This function uses a user-configurable function
+        to convert the creation time to a tuple. By default, time.localtime()
+        is used; to change this for a particular formatter instance, set the
+        'converter' attribute to a function with the same signature as
+        time.localtime() or time.gmtime(). To change it for all formatters,
+        for example if you want all logging times to be shown in GMT,
+        set the 'converter' attribute in the Formatter class.
+        """
+        ct = self.converter(record.created)
+        if datefmt:
+            s = time.strftime(datefmt, ct)
+        else:
+            t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
+            s = "%s,%03d" % (t, record.msecs)
+        return s
+
+    def formatException(self, ei):
+        """
+        Format and return the specified exception information as a string.
+
+        This default implementation just uses
+        traceback.print_exception()
+        """
+        sio = cStringIO.StringIO()
+        traceback.print_exception(ei[0], ei[1], ei[2], None, sio)
+        s = sio.getvalue()
+        sio.close()
+        if s[-1:] == "\n":
+            s = s[:-1]
+        return s
+
+    def usesTime(self):
+        """
+        Check if the format uses the creation time of the record.
+        """
+        return self._fmt.find("%(asctime)") >= 0
+
+    def format(self, record):
+        """
+        Format the specified record as text.
+
+        The record's attribute dictionary is used as the operand to a
+        string formatting operation which yields the returned string.
+        Before formatting the dictionary, a couple of preparatory steps
+        are carried out. The message attribute of the record is computed
+        using LogRecord.getMessage(). If the formatting string uses the
+        time (as determined by a call to usesTime(), formatTime() is
+        called to format the event time. If there is exception information,
+        it is formatted using formatException() and appended to the message.
+        """
+        record.message = record.getMessage()
+        if self.usesTime():
+            record.asctime = self.formatTime(record, self.datefmt)
+        s = self._fmt % record.__dict__
+        if record.exc_info:
+            # Cache the traceback text to avoid converting it multiple times
+            # (it's constant anyway)
+            if not record.exc_text:
+                record.exc_text = self.formatException(record.exc_info)
+        if record.exc_text:
+            if s[-1:] != "\n":
+                s = s + "\n"
+            try:
+                s = s + record.exc_text
+            except UnicodeError:
+                # Sometimes filenames have non-ASCII chars, which can lead
+                # to errors when s is Unicode and record.exc_text is str
+                # See issue 8924.
+                # We also use replace for when there are multiple
+                # encodings, e.g. UTF-8 for the filesystem and latin-1
+                # for a script. See issue 13232.
+                s = s + record.exc_text.decode(sys.getfilesystemencoding(),
+                                               'replace')
+        return s
+
+#
+#   The default formatter to use when no other is specified
+#
+_defaultFormatter = Formatter()
+
+class BufferingFormatter(object):
+    """
+    A formatter suitable for formatting a number of records.
+    """
+    def __init__(self, linefmt=None):
+        """
+        Optionally specify a formatter which will be used to format each
+        individual record.
+        """
+        if linefmt:
+            self.linefmt = linefmt
+        else:
+            self.linefmt = _defaultFormatter
+
+    def formatHeader(self, records):
+        """
+        Return the header string for the specified records.
+        """
+        return ""
+
+    def formatFooter(self, records):
+        """
+        Return the footer string for the specified records.
+        """
+        return ""
+
+    def format(self, records):
+        """
+        Format the specified records and return the result as a string.
+        """
+        rv = ""
+        if len(records) > 0:
+            rv = rv + self.formatHeader(records)
+            for record in records:
+                rv = rv + self.linefmt.format(record)
+            rv = rv + self.formatFooter(records)
+        return rv
+
+#---------------------------------------------------------------------------
+#   Filter classes and functions
+#---------------------------------------------------------------------------
+
+class Filter(object):
+    """
+    Filter instances are used to perform arbitrary filtering of LogRecords.
+
+    Loggers and Handlers can optionally use Filter instances to filter
+    records as desired. The base filter class only allows events which are
+    below a certain point in the logger hierarchy. For example, a filter
+    initialized with "A.B" will allow events logged by loggers "A.B",
+    "A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If
+    initialized with the empty string, all events are passed.
+    """
+    def __init__(self, name=''):
+        """
+        Initialize a filter.
+
+        Initialize with the name of the logger which, together with its
+        children, will have its events allowed through the filter. If no
+        name is specified, allow every event.
+        """
+        self.name = name
+        self.nlen = len(name)
+
+    def filter(self, record):
+        """
+        Determine if the specified record is to be logged.
+
+        Is the specified record to be logged? Returns 0 for no, nonzero for
+        yes. If deemed appropriate, the record may be modified in-place.
+        """
+        if self.nlen == 0:
+            return 1
+        elif self.name == record.name:
+            return 1
+        elif record.name.find(self.name, 0, self.nlen) != 0:
+            return 0
+        return (record.name[self.nlen] == ".")
+
+class Filterer(object):
+    """
+    A base class for loggers and handlers which allows them to share
+    common code.
+    """
+    def __init__(self):
+        """
+        Initialize the list of filters to be an empty list.
+        """
+        self.filters = []
+
+    def addFilter(self, filter):
+        """
+        Add the specified filter to this handler.
+        """
+        if not (filter in self.filters):
+            self.filters.append(filter)
+
+    def removeFilter(self, filter):
+        """
+        Remove the specified filter from this handler.
+        """
+        if filter in self.filters:
+            self.filters.remove(filter)
+
+    def filter(self, record):
+        """
+        Determine if a record is loggable by consulting all the filters.
+
+        The default is to allow the record to be logged; any filter can veto
+        this and the record is then dropped. Returns a zero value if a record
+        is to be dropped, else non-zero.
+        """
+        rv = 1
+        for f in self.filters:
+            if not f.filter(record):
+                rv = 0
+                break
+        return rv
+
+#---------------------------------------------------------------------------
+#   Handler classes and functions
+#---------------------------------------------------------------------------
+
+_handlers = weakref.WeakValueDictionary()  #map of handler names to handlers
+_handlerList = [] # added to allow handlers to be removed in reverse of order initialized
+
+def _removeHandlerRef(wr):
+    """
+    Remove a handler reference from the internal cleanup list.
+    """
+    # This function can be called during module teardown, when globals are
+    # set to None. It can also be called from another thread. So we need to
+    # pre-emptively grab the necessary globals and check if they're None,
+    # to prevent race conditions and failures during interpreter shutdown.
+    acquire, release, handlers = _acquireLock, _releaseLock, _handlerList
+    if acquire and release and handlers:
+        acquire()
+        try:
+            if wr in handlers:
+                handlers.remove(wr)
+        finally:
+            release()
+
+def _addHandlerRef(handler):
+    """
+    Add a handler to the internal cleanup list using a weak reference.
+    """
+    _acquireLock()
+    try:
+        _handlerList.append(weakref.ref(handler, _removeHandlerRef))
+    finally:
+        _releaseLock()
+
+class Handler(Filterer):
+    """
+    Handler instances dispatch logging events to specific destinations.
+
+    The base handler class. Acts as a placeholder which defines the Handler
+    interface. Handlers can optionally use Formatter instances to format
+    records as desired. By default, no formatter is specified; in this case,
+    the 'raw' message as determined by record.message is logged.
+    """
+    def __init__(self, level=NOTSET):
+        """
+        Initializes the instance - basically setting the formatter to None
+        and the filter list to empty.
+        """
+        Filterer.__init__(self)
+        self._name = None
+        self.level = _checkLevel(level)
+        self.formatter = None
+        # Add the handler to the global _handlerList (for cleanup on shutdown)
+        _addHandlerRef(self)
+        self.createLock()
+
+    def get_name(self):
+        return self._name
+
+    def set_name(self, name):
+        _acquireLock()
+        try:
+            if self._name in _handlers:
+                del _handlers[self._name]
+            self._name = name
+            if name:
+                _handlers[name] = self
+        finally:
+            _releaseLock()
+
+    name = property(get_name, set_name)
+
+    def createLock(self):
+        """
+        Acquire a thread lock for serializing access to the underlying I/O.
+        """
+        if thread:
+            self.lock = threading.RLock()
+        else:
+            self.lock = None
+
+    def acquire(self):
+        """
+        Acquire the I/O thread lock.
+        """
+        if self.lock:
+            self.lock.acquire()
+
+    def release(self):
+        """
+        Release the I/O thread lock.
+        """
+        if self.lock:
+            self.lock.release()
+
+    def setLevel(self, level):
+        """
+        Set the logging level of this handler.
+        """
+        self.level = _checkLevel(level)
+
+    def format(self, record):
+        """
+        Format the specified record.
+
+        If a formatter is set, use it. Otherwise, use the default formatter
+        for the module.
+        """
+        if self.formatter:
+            fmt = self.formatter
+        else:
+            fmt = _defaultFormatter
+        return fmt.format(record)
+
+    def emit(self, record):
+        """
+        Do whatever it takes to actually log the specified logging record.
+
+        This version is intended to be implemented by subclasses and so
+        raises a NotImplementedError.
+        """
+        raise NotImplementedError('emit must be implemented '
+                                  'by Handler subclasses')
+
+    def handle(self, record):
+        """
+        Conditionally emit the specified logging record.
+
+        Emission depends on filters which may have been added to the handler.
+        Wrap the actual emission of the record with acquisition/release of
+        the I/O thread lock. Returns whether the filter passed the record for
+        emission.
+        """
+        rv = self.filter(record)
+        if rv:
+            self.acquire()
+            try:
+                self.emit(record)
+            finally:
+                self.release()
+        return rv
+
+    def setFormatter(self, fmt):
+        """
+        Set the formatter for this handler.
+        """
+        self.formatter = fmt
+
+    def flush(self):
+        """
+        Ensure all logging output has been flushed.
+
+        This version does nothing and is intended to be implemented by
+        subclasses.
+        """
+        pass
+
+    def close(self):
+        """
+        Tidy up any resources used by the handler.
+
+        This version removes the handler from an internal map of handlers,
+        _handlers, which is used for handler lookup by name. Subclasses
+        should ensure that this gets called from overridden close()
+        methods.
+        """
+        #get the module data lock, as we're updating a shared structure.
+        _acquireLock()
+        try:    #unlikely to raise an exception, but you never know...
+            if self._name and self._name in _handlers:
+                del _handlers[self._name]
+        finally:
+            _releaseLock()
+
+    def handleError(self, record):
+        """
+        Handle errors which occur during an emit() call.
+
+        This method should be called from handlers when an exception is
+        encountered during an emit() call. If raiseExceptions is false,
+        exceptions get silently ignored. This is what is mostly wanted
+        for a logging system - most users will not care about errors in
+        the logging system, they are more interested in application errors.
+        You could, however, replace this with a custom handler if you wish.
+        The record which was being processed is passed in to this method.
+        """
+        if raiseExceptions and sys.stderr:  # see issue 13807
+            ei = sys.exc_info()
+            try:
+                traceback.print_exception(ei[0], ei[1], ei[2],
+                                          None, sys.stderr)
+                sys.stderr.write('Logged from file %s, line %s\n' % (
+                                 record.filename, record.lineno))
+            except IOError:
+                pass    # see issue 5971
+            finally:
+                del ei
+
+class StreamHandler(Handler):
+    """
+    A handler class which writes logging records, appropriately formatted,
+    to a stream. Note that this class does not close the stream, as
+    sys.stdout or sys.stderr may be used.
+    """
+
+    def __init__(self, stream=None):
+        """
+        Initialize the handler.
+
+        If stream is not specified, sys.stderr is used.
+        """
+        Handler.__init__(self)
+        if stream is None:
+            stream = sys.stderr
+        self.stream = stream
+
+    def flush(self):
+        """
+        Flushes the stream.
+        """
+        self.acquire()
+        try:
+            if self.stream and hasattr(self.stream, "flush"):
+                self.stream.flush()
+        finally:
+            self.release()
+
+    def emit(self, record):
+        """
+        Emit a record.
+
+        If a formatter is specified, it is used to format the record.
+        The record is then written to the stream with a trailing newline.  If
+        exception information is present, it is formatted using
+        traceback.print_exception and appended to the stream.  If the stream
+        has an 'encoding' attribute, it is used to determine how to do the
+        output to the stream.
+        """
+        try:
+            msg = self.format(record)
+            stream = self.stream
+            fs = "%s\n"
+            if not _unicode: #if no unicode support...
+                stream.write(fs % msg)
+            else:
+                try:
+                    if (isinstance(msg, unicode) and
+                        getattr(stream, 'encoding', None)):
+                        ufs = u'%s\n'
+                        try:
+                            stream.write(ufs % msg)
+                        except UnicodeEncodeError:
+                            #Printing to terminals sometimes fails. For example,
+                            #with an encoding of 'cp1251', the above write will
+                            #work if written to a stream opened or wrapped by
+                            #the codecs module, but fail when writing to a
+                            #terminal even when the codepage is set to cp1251.
+                            #An extra encoding step seems to be needed.
+                            stream.write((ufs % msg).encode(stream.encoding))
+                    else:
+                        stream.write(fs % msg)
+                except UnicodeError:
+                    stream.write(fs % msg.encode("UTF-8"))
+            self.flush()
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            self.handleError(record)
+
+class FileHandler(StreamHandler):
+    """
+    A handler class which writes formatted logging records to disk files.
+    """
+    def __init__(self, filename, mode='a', encoding=None, delay=0):
+        """
+        Open the specified file and use it as the stream for logging.
+        """
+        #keep the absolute path, otherwise derived classes which use this
+        #may come a cropper when the current directory changes
+        if codecs is None:
+            encoding = None
+        self.baseFilename = os.path.abspath(filename)
+        self.mode = mode
+        self.encoding = encoding
+        self.delay = delay
+        if delay:
+            #We don't open the stream, but we still need to call the
+            #Handler constructor to set level, formatter, lock etc.
+            Handler.__init__(self)
+            self.stream = None
+        else:
+            StreamHandler.__init__(self, self._open())
+
+    def close(self):
+        """
+        Closes the stream.
+        """
+        self.acquire()
+        try:
+            try:
+                if self.stream:
+                    try:
+                        self.flush()
+                    finally:
+                        stream = self.stream
+                        self.stream = None
+                        if hasattr(stream, "close"):
+                            stream.close()
+            finally:
+                # Issue #19523: call unconditionally to
+                # prevent a handler leak when delay is set
+                StreamHandler.close(self)
+        finally:
+            self.release()
+
+    def _open(self):
+        """
+        Open the current base file with the (original) mode and encoding.
+        Return the resulting stream.
+        """
+        if self.encoding is None:
+            stream = open(self.baseFilename, self.mode)
+        else:
+            stream = codecs.open(self.baseFilename, self.mode, self.encoding)
+        return stream
+
+    def emit(self, record):
+        """
+        Emit a record.
+
+        If the stream was not opened because 'delay' was specified in the
+        constructor, open it before calling the superclass's emit.
+        """
+        if self.stream is None:
+            self.stream = self._open()
+        StreamHandler.emit(self, record)
+
+#---------------------------------------------------------------------------
+#   Manager classes and functions
+#---------------------------------------------------------------------------
+
+class PlaceHolder(object):
+    """
+    PlaceHolder instances are used in the Manager logger hierarchy to take
+    the place of nodes for which no loggers have been defined. This class is
+    intended for internal use only and not as part of the public API.
+    """
+    def __init__(self, alogger):
+        """
+        Initialize with the specified logger being a child of this placeholder.
+        """
+        #self.loggers = [alogger]
+        self.loggerMap = { alogger : None }
+
+    def append(self, alogger):
+        """
+        Add the specified logger as a child of this placeholder.
+        """
+        #if alogger not in self.loggers:
+        if alogger not in self.loggerMap:
+            #self.loggers.append(alogger)
+            self.loggerMap[alogger] = None
+
+#
+#   Determine which class to use when instantiating loggers.
+#
+_loggerClass = None
+
+def setLoggerClass(klass):
+    """
+    Set the class to be used when instantiating a logger. The class should
+    define __init__() such that only a name argument is required, and the
+    __init__() should call Logger.__init__()
+    """
+    if klass != Logger:
+        if not issubclass(klass, Logger):
+            raise TypeError("logger not derived from logging.Logger: "
+                            + klass.__name__)
+    global _loggerClass
+    _loggerClass = klass
+
+def getLoggerClass():
+    """
+    Return the class to be used when instantiating a logger.
+    """
+
+    return _loggerClass
+
+class Manager(object):
+    """
+    There is [under normal circumstances] just one Manager instance, which
+    holds the hierarchy of loggers.
+    """
+    def __init__(self, rootnode):
+        """
+        Initialize the manager with the root node of the logger hierarchy.
+        """
+        self.root = rootnode
+        self.disable = 0
+        self.emittedNoHandlerWarning = 0
+        self.loggerDict = {}
+        self.loggerClass = None
+
+    def getLogger(self, name):
+        """
+        Get a logger with the specified name (channel name), creating it
+        if it doesn't yet exist. This name is a dot-separated hierarchical
+        name, such as "a", "a.b", "a.b.c" or similar.
+
+        If a PlaceHolder existed for the specified name [i.e. the logger
+        didn't exist but a child of it did], replace it with the created
+        logger and fix up the parent/child references which pointed to the
+        placeholder to now point to the logger.
+        """
+        rv = None
+        if not isinstance(name, basestring):
+            raise TypeError('A logger name must be string or Unicode')
+        if isinstance(name, unicode):
+            name = name.encode('utf-8')
+        _acquireLock()
+        try:
+            if name in self.loggerDict:
+                rv = self.loggerDict[name]
+                if isinstance(rv, PlaceHolder):
+                    ph = rv
+                    rv = (self.loggerClass or _loggerClass)(name)
+                    rv.manager = self
+                    self.loggerDict[name] = rv
+                    self._fixupChildren(ph, rv)
+                    self._fixupParents(rv)
+            else:
+                rv = (self.loggerClass or _loggerClass)(name)
+                rv.manager = self
+                self.loggerDict[name] = rv
+                self._fixupParents(rv)
+        finally:
+            _releaseLock()
+        return rv
+
+    def setLoggerClass(self, klass):
+        """
+        Set the class to be used when instantiating a logger with this Manager.
+        """
+        if klass != Logger:
+            if not issubclass(klass, Logger):
+                raise TypeError("logger not derived from logging.Logger: "
+                                + klass.__name__)
+        self.loggerClass = klass
+
+    def _fixupParents(self, alogger):
+        """
+        Ensure that there are either loggers or placeholders all the way
+        from the specified logger to the root of the logger hierarchy.
+        """
+        name = alogger.name
+        i = name.rfind(".")
+        rv = None
+        while (i > 0) and not rv:
+            substr = name[:i]
+            if substr not in self.loggerDict:
+                self.loggerDict[substr] = PlaceHolder(alogger)
+            else:
+                obj = self.loggerDict[substr]
+                if isinstance(obj, Logger):
+                    rv = obj
+                else:
+                    assert isinstance(obj, PlaceHolder)
+                    obj.append(alogger)
+            i = name.rfind(".", 0, i - 1)
+        if not rv:
+            rv = self.root
+        alogger.parent = rv
+
+    def _fixupChildren(self, ph, alogger):
+        """
+        Ensure that children of the placeholder ph are connected to the
+        specified logger.
+        """
+        name = alogger.name
+        namelen = len(name)
+        for c in ph.loggerMap.keys():
+            #The if means ... if not c.parent.name.startswith(nm)
+            if c.parent.name[:namelen] != name:
+                alogger.parent = c.parent
+                c.parent = alogger
+
+#---------------------------------------------------------------------------
+#   Logger classes and functions
+#---------------------------------------------------------------------------
+
+class Logger(Filterer):
+    """
+    Instances of the Logger class represent a single logging channel. A
+    "logging channel" indicates an area of an application. Exactly how an
+    "area" is defined is up to the application developer. Since an
+    application can have any number of areas, logging channels are identified
+    by a unique string. Application areas can be nested (e.g. an area
+    of "input processing" might include sub-areas "read CSV files", "read
+    XLS files" and "read Gnumeric files"). To cater for this natural nesting,
+    channel names are organized into a namespace hierarchy where levels are
+    separated by periods, much like the Java or Python package namespace. So
+    in the instance given above, channel names might be "input" for the upper
+    level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.
+    There is no arbitrary limit to the depth of nesting.
+    """
+    def __init__(self, name, level=NOTSET):
+        """
+        Initialize the logger with a name and an optional level.
+        """
+        Filterer.__init__(self)
+        self.name = name
+        self.level = _checkLevel(level)
+        self.parent = None
+        self.propagate = 1
+        self.handlers = []
+        self.disabled = 0
+
+    def setLevel(self, level):
+        """
+        Set the logging level of this logger.
+        """
+        self.level = _checkLevel(level)
+
+    def debug(self, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with severity 'DEBUG'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
+        """
+        if self.isEnabledFor(DEBUG):
+            self._log(DEBUG, msg, args, **kwargs)
+
+    def info(self, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with severity 'INFO'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
+        """
+        if self.isEnabledFor(INFO):
+            self._log(INFO, msg, args, **kwargs)
+
+    def warning(self, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with severity 'WARNING'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1)
+        """
+        if self.isEnabledFor(WARNING):
+            self._log(WARNING, msg, args, **kwargs)
+
+    warn = warning
+
+    def error(self, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with severity 'ERROR'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.error("Houston, we have a %s", "major problem", exc_info=1)
+        """
+        if self.isEnabledFor(ERROR):
+            self._log(ERROR, msg, args, **kwargs)
+
+    def exception(self, msg, *args, **kwargs):
+        """
+        Convenience method for logging an ERROR with exception information.
+        """
+        kwargs['exc_info'] = 1
+        self.error(msg, *args, **kwargs)
+
+    def critical(self, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with severity 'CRITICAL'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.critical("Houston, we have a %s", "major disaster", exc_info=1)
+        """
+        if self.isEnabledFor(CRITICAL):
+            self._log(CRITICAL, msg, args, **kwargs)
+
+    fatal = critical
+
+    def log(self, level, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with the integer severity 'level'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
+        """
+        if not isinstance(level, int):
+            if raiseExceptions:
+                raise TypeError("level must be an integer")
+            else:
+                return
+        if self.isEnabledFor(level):
+            self._log(level, msg, args, **kwargs)
+
+    def findCaller(self):
+        """
+        Find the stack frame of the caller so that we can note the source
+        file name, line number and function name.
+        """
+        f = currentframe()
+        #On some versions of IronPython, currentframe() returns None if
+        #IronPython isn't run with -X:Frames.
+        if f is not None:
+            f = f.f_back
+        rv = "(unknown file)", 0, "(unknown function)"
+        while hasattr(f, "f_code"):
+            co = f.f_code
+            filename = os.path.normcase(co.co_filename)
+            if filename == _srcfile:
+                f = f.f_back
+                continue
+            rv = (co.co_filename, f.f_lineno, co.co_name)
+            break
+        return rv
+
+    def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
+        """
+        A factory method which can be overridden in subclasses to create
+        specialized LogRecords.
+        """
+        rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func)
+        if extra is not None:
+            for key in extra:
+                if (key in ["message", "asctime"]) or (key in rv.__dict__):
+                    raise KeyError("Attempt to overwrite %r in LogRecord" % key)
+                rv.__dict__[key] = extra[key]
+        return rv
+
+    def _log(self, level, msg, args, exc_info=None, extra=None):
+        """
+        Low-level logging routine which creates a LogRecord and then calls
+        all the handlers of this logger to handle the record.
+        """
+        if _srcfile:
+            #IronPython doesn't track Python frames, so findCaller raises an
+            #exception on some versions of IronPython. We trap it here so that
+            #IronPython can use logging.
+            try:
+                fn, lno, func = self.findCaller()
+            except ValueError:
+                fn, lno, func = "(unknown file)", 0, "(unknown function)"
+        else:
+            fn, lno, func = "(unknown file)", 0, "(unknown function)"
+        if exc_info:
+            if not isinstance(exc_info, tuple):
+                exc_info = sys.exc_info()
+        record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
+        self.handle(record)
+
+    def handle(self, record):
+        """
+        Call the handlers for the specified record.
+
+        This method is used for unpickled records received from a socket, as
+        well as those created locally. Logger-level filtering is applied.
+        """
+        if (not self.disabled) and self.filter(record):
+            self.callHandlers(record)
+
+    def addHandler(self, hdlr):
+        """
+        Add the specified handler to this logger.
+        """
+        _acquireLock()
+        try:
+            if not (hdlr in self.handlers):
+                self.handlers.append(hdlr)
+        finally:
+            _releaseLock()
+
+    def removeHandler(self, hdlr):
+        """
+        Remove the specified handler from this logger.
+        """
+        _acquireLock()
+        try:
+            if hdlr in self.handlers:
+                self.handlers.remove(hdlr)
+        finally:
+            _releaseLock()
+
+    def callHandlers(self, record):
+        """
+        Pass a record to all relevant handlers.
+
+        Loop through all handlers for this logger and its parents in the
+        logger hierarchy. If no handler was found, output a one-off error
+        message to sys.stderr. Stop searching up the hierarchy whenever a
+        logger with the "propagate" attribute set to zero is found - that
+        will be the last logger whose handlers are called.
+        """
+        c = self
+        found = 0
+        while c:
+            for hdlr in c.handlers:
+                found = found + 1
+                if record.levelno >= hdlr.level:
+                    hdlr.handle(record)
+            if not c.propagate:
+                c = None    #break out
+            else:
+                c = c.parent
+        if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning:
+            sys.stderr.write("No handlers could be found for logger"
+                             " \"%s\"\n" % self.name)
+            self.manager.emittedNoHandlerWarning = 1
+
+    def getEffectiveLevel(self):
+        """
+        Get the effective level for this logger.
+
+        Loop through this logger and its parents in the logger hierarchy,
+        looking for a non-zero logging level. Return the first one found.
+        """
+        logger = self
+        while logger:
+            if logger.level:
+                return logger.level
+            logger = logger.parent
+        return NOTSET
+
+    def isEnabledFor(self, level):
+        """
+        Is this logger enabled for level 'level'?
+        """
+        if self.manager.disable >= level:
+            return 0
+        return level >= self.getEffectiveLevel()
+
+    def getChild(self, suffix):
+        """
+        Get a logger which is a descendant to this one.
+
+        This is a convenience method, such that
+
+        logging.getLogger('abc').getChild('def.ghi')
+
+        is the same as
+
+        logging.getLogger('abc.def.ghi')
+
+        It's useful, for example, when the parent logger is named using
+        __name__ rather than a literal string.
+        """
+        if self.root is not self:
+            suffix = '.'.join((self.name, suffix))
+        return self.manager.getLogger(suffix)
+
+class RootLogger(Logger):
+    """
+    A root logger is not that different to any other logger, except that
+    it must have a logging level and there is only one instance of it in
+    the hierarchy.
+    """
+    def __init__(self, level):
+        """
+        Initialize the logger with the name "root".
+        """
+        Logger.__init__(self, "root", level)
+
+_loggerClass = Logger
+
+class LoggerAdapter(object):
+    """
+    An adapter for loggers which makes it easier to specify contextual
+    information in logging output.
+    """
+
+    def __init__(self, logger, extra):
+        """
+        Initialize the adapter with a logger and a dict-like object which
+        provides contextual information. This constructor signature allows
+        easy stacking of LoggerAdapters, if so desired.
+
+        You can effectively pass keyword arguments as shown in the
+        following example:
+
+        adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))
+        """
+        self.logger = logger
+        self.extra = extra
+
+    def process(self, msg, kwargs):
+        """
+        Process the logging message and keyword arguments passed in to
+        a logging call to insert contextual information. You can either
+        manipulate the message itself, the keyword args or both. Return
+        the message and kwargs modified (or not) to suit your needs.
+
+        Normally, you'll only need to override this one method in a
+        LoggerAdapter subclass for your specific needs.
+        """
+        kwargs["extra"] = self.extra
+        return msg, kwargs
+
+    def debug(self, msg, *args, **kwargs):
+        """
+        Delegate a debug call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        self.logger.debug(msg, *args, **kwargs)
+
+    def info(self, msg, *args, **kwargs):
+        """
+        Delegate an info call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        self.logger.info(msg, *args, **kwargs)
+
+    def warning(self, msg, *args, **kwargs):
+        """
+        Delegate a warning call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        self.logger.warning(msg, *args, **kwargs)
+
+    def error(self, msg, *args, **kwargs):
+        """
+        Delegate an error call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        self.logger.error(msg, *args, **kwargs)
+
+    def exception(self, msg, *args, **kwargs):
+        """
+        Delegate an exception call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        kwargs["exc_info"] = 1
+        self.logger.error(msg, *args, **kwargs)
+
+    def critical(self, msg, *args, **kwargs):
+        """
+        Delegate a critical call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        self.logger.critical(msg, *args, **kwargs)
+
+    def log(self, level, msg, *args, **kwargs):
+        """
+        Delegate a log call to the underlying logger, after adding
+        contextual information from this adapter instance.
+        """
+        msg, kwargs = self.process(msg, kwargs)
+        self.logger.log(level, msg, *args, **kwargs)
+
+    def isEnabledFor(self, level):
+        """
+        See if the underlying logger is enabled for the specified level.
+        """
+        return self.logger.isEnabledFor(level)
+
+root = RootLogger(WARNING)
+Logger.root = root
+Logger.manager = Manager(Logger.root)
+
+#---------------------------------------------------------------------------
+# Configuration classes and functions
+#---------------------------------------------------------------------------
+
+BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s"
+
+def basicConfig(**kwargs):
+    """
+    Do basic configuration for the logging system.
+
+    This function does nothing if the root logger already has handlers
+    configured. It is a convenience method intended for use by simple scripts
+    to do one-shot configuration of the logging package.
+
+    The default behaviour is to create a StreamHandler which writes to
+    sys.stderr, set a formatter using the BASIC_FORMAT format string, and
+    add the handler to the root logger.
+
+    A number of optional keyword arguments may be specified, which can alter
+    the default behaviour.
+
+    filename  Specifies that a FileHandler be created, using the specified
+              filename, rather than a StreamHandler.
+    filemode  Specifies the mode to open the file, if filename is specified
+              (if filemode is unspecified, it defaults to 'a').
+    format    Use the specified format string for the handler.
+    datefmt   Use the specified date/time format.
+    level     Set the root logger level to the specified level.
+    stream    Use the specified stream to initialize the StreamHandler. Note
+              that this argument is incompatible with 'filename' - if both
+              are present, 'stream' is ignored.
+
+    Note that you could specify a stream created using open(filename, mode)
+    rather than passing the filename and mode in. However, it should be
+    remembered that StreamHandler does not close its stream (since it may be
+    using sys.stdout or sys.stderr), whereas FileHandler closes its stream
+    when the handler is closed.
+    """
+    # Add thread safety in case someone mistakenly calls
+    # basicConfig() from multiple threads
+    _acquireLock()
+    try:
+        if len(root.handlers) == 0:
+            filename = kwargs.get("filename")
+            if filename:
+                mode = kwargs.get("filemode", 'a')
+                hdlr = FileHandler(filename, mode)
+            else:
+                stream = kwargs.get("stream")
+                hdlr = StreamHandler(stream)
+            fs = kwargs.get("format", BASIC_FORMAT)
+            dfs = kwargs.get("datefmt", None)
+            fmt = Formatter(fs, dfs)
+            hdlr.setFormatter(fmt)
+            root.addHandler(hdlr)
+            level = kwargs.get("level")
+            if level is not None:
+                root.setLevel(level)
+    finally:
+        _releaseLock()
+
+#---------------------------------------------------------------------------
+# Utility functions at module level.
+# Basically delegate everything to the root logger.
+#---------------------------------------------------------------------------
+
+def getLogger(name=None):
+    """
+    Return a logger with the specified name, creating it if necessary.
+
+    If no name is specified, return the root logger.
+    """
+    if name:
+        return Logger.manager.getLogger(name)
+    else:
+        return root
+
+#def getRootLogger():
+#    """
+#    Return the root logger.
+#
+#    Note that getLogger('') now does the same thing, so this function is
+#    deprecated and may disappear in the future.
+#    """
+#    return root
+
+def critical(msg, *args, **kwargs):
+    """
+    Log a message with severity 'CRITICAL' on the root logger.
+    """
+    if len(root.handlers) == 0:
+        basicConfig()
+    root.critical(msg, *args, **kwargs)
+
+fatal = critical
+
+def error(msg, *args, **kwargs):
+    """
+    Log a message with severity 'ERROR' on the root logger.
+    """
+    if len(root.handlers) == 0:
+        basicConfig()
+    root.error(msg, *args, **kwargs)
+
+def exception(msg, *args, **kwargs):
+    """
+    Log a message with severity 'ERROR' on the root logger,
+    with exception information.
+    """
+    kwargs['exc_info'] = 1
+    error(msg, *args, **kwargs)
+
+def warning(msg, *args, **kwargs):
+    """
+    Log a message with severity 'WARNING' on the root logger.
+    """
+    if len(root.handlers) == 0:
+        basicConfig()
+    root.warning(msg, *args, **kwargs)
+
+warn = warning
+
+def info(msg, *args, **kwargs):
+    """
+    Log a message with severity 'INFO' on the root logger.
+    """
+    if len(root.handlers) == 0:
+        basicConfig()
+    root.info(msg, *args, **kwargs)
+
+def debug(msg, *args, **kwargs):
+    """
+    Log a message with severity 'DEBUG' on the root logger.
+    """
+    if len(root.handlers) == 0:
+        basicConfig()
+    root.debug(msg, *args, **kwargs)
+
+def log(level, msg, *args, **kwargs):
+    """
+    Log 'msg % args' with the integer severity 'level' on the root logger.
+    """
+    if len(root.handlers) == 0:
+        basicConfig()
+    root.log(level, msg, *args, **kwargs)
+
+def disable(level):
+    """
+    Disable all logging calls of severity 'level' and below.
+    """
+    root.manager.disable = level
+
+def shutdown(handlerList=_handlerList):
+    """
+    Perform any cleanup actions in the logging system (e.g. flushing
+    buffers).
+
+    Should be called at application exit.
+    """
+    for wr in reversed(handlerList[:]):
+        #errors might occur, for example, if files are locked
+        #we just ignore them if raiseExceptions is not set
+        try:
+            h = wr()
+            if h:
+                try:
+                    h.acquire()
+                    h.flush()
+                    h.close()
+                except (IOError, ValueError):
+                    # Ignore errors which might be caused
+                    # because handlers have been closed but
+                    # references to them are still around at
+                    # application exit.
+                    pass
+                finally:
+                    h.release()
+        except:
+            if raiseExceptions:
+                raise
+            #else, swallow
+
+#Let's try and shutdown automatically on application exit...
+import atexit
+atexit.register(shutdown)
+
+# Null handler
+
+class NullHandler(Handler):
+    """
+    This handler does nothing. It's intended to be used to avoid the
+    "No handlers could be found for logger XXX" one-off warning. This is
+    important for library code, which may contain code to log events. If a user
+    of the library does not configure logging, the one-off warning might be
+    produced; to avoid this, the library developer simply needs to instantiate
+    a NullHandler and add it to the top-level logger of the library module or
+    package.
+    """
+    def handle(self, record):
+        pass
+
+    def emit(self, record):
+        pass
+
+    def createLock(self):
+        self.lock = None
+
+# Warnings integration
+
+_warnings_showwarning = None
+
+def _showwarning(message, category, filename, lineno, file=None, line=None):
+    """
+    Implementation of showwarnings which redirects to logging, which will first
+    check to see if the file parameter is None. If a file is specified, it will
+    delegate to the original warnings implementation of showwarning. Otherwise,
+    it will call warnings.formatwarning and will log the resulting string to a
+    warnings logger named "py.warnings" with level logging.WARNING.
+    """
+    if file is not None:
+        if _warnings_showwarning is not None:
+            _warnings_showwarning(message, category, filename, lineno, file, line)
+    else:
+        s = warnings.formatwarning(message, category, filename, lineno, line)
+        logger = getLogger("py.warnings")
+        if not logger.handlers:
+            logger.addHandler(NullHandler())
+        logger.warning("%s", s)
+
+def captureWarnings(capture):
+    """
+    If capture is true, redirect all warnings to the logging package.
+    If capture is False, ensure that warnings are not redirected to logging
+    but to their original destinations.
+    """
+    global _warnings_showwarning
+    if capture:
+        if _warnings_showwarning is None:
+            _warnings_showwarning = warnings.showwarning
+            warnings.showwarning = _showwarning
+    else:
+        if _warnings_showwarning is not None:
+            warnings.showwarning = _warnings_showwarning
+            _warnings_showwarning = None
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/mirage/agent.html b/docs/_build/html/_modules/mirage/agent.html new file mode 100644 index 00000000..807532d1 --- /dev/null +++ b/docs/_build/html/_modules/mirage/agent.html @@ -0,0 +1,307 @@ + + + + + + + + + + + mirage.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for mirage.agent

+import logging
+import sys
+import traceback
+from os import getpid
+from os.path import dirname, abspath, isdir
+from daemon import runner
+from time import sleep, time
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+from mirage import Mirage
+from mirage_algorithms import *
+
+skyline_app = 'mirage'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+
+
[docs]class MirageAgent(): + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + logger.info('agent starting skyline %s' % skyline_app) + Mirage(getpid()).start() + + while 1: + sleep(100)
+ + +
[docs]def run(): + """ + Start the Mirage agent. + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + # Make sure we can run all the algorithms + try: + timeseries = map(list, zip(map(float, range(int(time()) - 86400, int(time()) + 1)), [1] * 86401)) + second_order_resolution_seconds = 86400 + ensemble = [globals()[algorithm](timeseries, second_order_resolution_seconds) for algorithm in settings.MIRAGE_ALGORITHMS] + except KeyError as e: + print ('Algorithm %s deprecated or not defined; check settings.MIRAGE_ALGORITHMS' % e) + sys.exit(1) + except Exception as e: + print ('Algorithm test run failed.') + traceback.print_exc() + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + handler.setFormatter(formatter) + logger.addHandler(handler) + + mirage = MirageAgent() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + mirage.run() + else: + daemon_runner = runner.DaemonRunner(mirage) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action()
+ +if __name__ == "__main__": + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/mirage/mirage.html b/docs/_build/html/_modules/mirage/mirage.html new file mode 100644 index 00000000..289bff3a --- /dev/null +++ b/docs/_build/html/_modules/mirage/mirage.html @@ -0,0 +1,920 @@ + + + + + + + + + + + mirage.mirage — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for mirage.mirage

+import logging
+try:
+    from Queue import Empty
+except:
+    from queue import Empty
+from redis import StrictRedis
+from time import time, sleep
+from threading import Thread
+from collections import defaultdict
+from multiprocessing import Process, Manager, Queue
+from msgpack import Unpacker, unpackb, packb
+from os import path, kill, getpid, system
+from math import ceil
+import traceback
+import operator
+import socket
+import re
+# imports required for surfacing graphite JSON formatted timeseries for use in
+# Mirage
+import json
+import sys
+import requests
+try:
+    import urlparse
+except ImportError:
+    import urllib.parse
+import os
+import errno
+import imp
+from os import listdir
+import datetime
+
+# from skyline import settings
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+from skyline_functions import write_data_to_file
+
+from mirage_alerters import trigger_alert
+from negaters import trigger_negater
+from mirage_algorithms import run_selected_algorithm
+from algorithm_exceptions import *
+from os.path import dirname, join, abspath, isfile
+
+skyline_app = 'mirage'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+python_version = int(sys.version_info[0])
+
+this_host = str(os.uname()[1])
+
+try:
+    SERVER_METRIC_PATH = '.%s' % settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.%s%s' % (skyline_app, SERVER_METRIC_PATH)
+
+
+
[docs]class Mirage(Thread): + def __init__(self, parent_pid): + """ + Initialize the Mirage + """ + super(Mirage, self).__init__() + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.anomalous_metrics = Manager().list() + self.mirage_exceptions_q = Queue() + self.mirage_anomaly_breakdown_q = Queue() + self.not_anomalous_metrics = Manager().list() + self.metric_variables = Manager().list() + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ +
[docs] def mkdir_p(self, path): + try: + os.makedirs(path) + return True + # Python >2.5 + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise
+ +
[docs] def surface_graphite_metric_data(self, metric_name, graphite_from, graphite_until): + # We use absolute time so that if there is a lag in mirage the correct + # timeseries data is still surfaced relevant to the anomalous datapoint + # timestamp + if settings.GRAPHITE_PORT != '': + url = '%s://%s:%s/render/?from=%s&until=%s&target=%s&format=json' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, + str(settings.GRAPHITE_PORT), graphite_from, graphite_until, + metric_name) + else: + url = '%s://%s/render/?from=%s&until=%s&target=%s&format=json' % ( + settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, + graphite_from, graphite_until, metric_name) + r = requests.get(url) + js = r.json() + datapoints = js[0]['datapoints'] + + converted = [] + for datapoint in datapoints: + try: + new_datapoint = [float(datapoint[1]), float(datapoint[0])] + converted.append(new_datapoint) + except: + continue + + parsed = urlparse.urlparse(url) + target = urlparse.parse_qs(parsed.query)['target'][0] + + metric_data_folder = settings.MIRAGE_DATA_FOLDER + "/" + target + self.mkdir_p(metric_data_folder) + with open(metric_data_folder + "/" + target + '.json', 'w') as f: + f.write(json.dumps(converted)) + f.close() + return True + + return False
+ +
[docs] def load_metric_vars(self, filename): + if os.path.isfile(filename) == True: + f = open(filename) + global metric_vars + metric_vars = imp.load_source('metric_vars', '', f) + f.close() + return True + + return False
+ +
[docs] def spin_process(self, i, run_timestamp): + """ + Assign a metric for a process to analyze. + """ + + # Discover metric to analyze + metric_var_files = [f for f in listdir(settings.MIRAGE_CHECK_PATH) if isfile(join(settings.MIRAGE_CHECK_PATH, f))] + + # Check if this process is unnecessary + if len(metric_var_files) == 0: + return + + metric_var_files_sorted = sorted(metric_var_files) + metric_check_file = '%s/%s' % ( + settings.MIRAGE_CHECK_PATH, str(metric_var_files_sorted[0])) + + # Load metric variables + self.load_metric_vars(metric_check_file) + + # Test metric variables + if len(metric_vars.metric) == 0: + return + else: + metric = metric_vars.metric + metric_name = ['metric_name', metric_vars.metric] + self.metric_variables.append(metric_name) + if len(metric_vars.value) == 0: + return + else: + metric_value = ['metric_value', metric_vars.value] + self.metric_variables.append(metric_value) + if len(metric_vars.hours_to_resolve) == 0: + return + else: + hours_to_resolve = ['hours_to_resolve', metric_vars.hours_to_resolve] + self.metric_variables.append(hours_to_resolve) + if len(metric_vars.metric_timestamp) == 0: + return + else: + metric_timestamp = ['metric_timestamp', metric_vars.metric_timestamp] + self.metric_variables.append(metric_timestamp) + + # Ignore any metric check with a timestamp greater than 10 minutes ago + int_metric_timestamp = int(metric_vars.metric_timestamp) + int_run_timestamp = int(run_timestamp) + metric_timestamp_age = int_run_timestamp - int_metric_timestamp + if metric_timestamp_age > settings.MIRAGE_STALE_SECONDS: + logger.info('stale check :: %s check request is %s seconds old - discarding' % (metric_vars.metric, metric_timestamp_age)) + # Remove metric check file +# try: +# os.remove(metric_check_file) +# except OSError: +# pass +# return + if os.path.exists(metric_check_file): + os.remove(metric_check_file) + logger.info('removed %s' % (metric_check_file)) + else: + logger.info('could not remove %s' % (metric_check_file)) + + # Calculate hours second order resolution to seconds + second_order_resolution_seconds = int(metric_vars.hours_to_resolve) * 3600 + + # Calculate graphite from and until parameters from the metric timestamp + graphite_until = datetime.datetime.fromtimestamp(int(metric_vars.metric_timestamp)).strftime('%H:%M_%Y%m%d') + int_second_order_resolution_seconds = int(second_order_resolution_seconds) + second_resolution_timestamp = int_metric_timestamp - int_second_order_resolution_seconds + graphite_from = datetime.datetime.fromtimestamp(int(second_resolution_timestamp)).strftime('%H:%M_%Y%m%d') + + # Remove any old json file related to the metric + metric_json_file = '%s/%s/%s.json' % ( + settings.MIRAGE_DATA_FOLDER, str(metric_vars.metric), + str(metric_vars.metric)) + try: + os.remove(metric_json_file) + except OSError: + pass + + # Get data from graphite + logger.info( + 'retrieve data :: surfacing %s timeseries from graphite for %s seconds' % ( + metric_vars.metric, second_order_resolution_seconds)) + self.surface_graphite_metric_data(metric_vars.metric, graphite_from, graphite_until) + + # Check there is a json timeseries file to test + if not os.path.isfile(metric_json_file): + logger.error( + 'error :: retrieve failed - failed to surface %s timeseries from graphite' % ( + metric_vars.metric)) + # Remove metric check file + try: + os.remove(metric_check_file) + except OSError: + pass + return + else: + logger.info('retrieved data :: for %s at %s seconds' % ( + metric_vars.metric, second_order_resolution_seconds)) + + # Make process-specific dicts + exceptions = defaultdict(int) + anomaly_breakdown = defaultdict(int) + + self.check_if_parent_is_alive() + + with open((metric_json_file), 'r') as f: + timeseries = json.loads(f.read()) + logger.info('data points surfaced :: %s' % (len(timeseries))) + + try: + logger.info('analyzing :: %s at %s seconds' % (metric_vars.metric, second_order_resolution_seconds)) + anomalous, ensemble, datapoint = run_selected_algorithm(timeseries, metric_vars.metric, second_order_resolution_seconds) + + # If it's anomalous, add it to list + if anomalous: + base_name = metric.replace(settings.FULL_NAMESPACE, '', 1) + anomalous_metric = [datapoint, base_name] + self.anomalous_metrics.append(anomalous_metric) + logger.info('anomaly detected :: %s with %s' % (metric_vars.metric, metric_vars.value)) + # It runs so fast, this allows us to process 30 anomalies/min + sleep(2) + + # Get the anomaly breakdown - who returned True? + triggered_algorithms = [] + for index, value in enumerate(ensemble): + if value: + algorithm = settings.MIRAGE_ALGORITHMS[index] + anomaly_breakdown[algorithm] += 1 + triggered_algorithms.append(algorithm) + + # If Crucible or Panorama are enabled determine details + determine_anomaly_details = False + if settings.ENABLE_CRUCIBLE and settings.MIRAGE_CRUCIBLE_ENABLED: + determine_anomaly_details = True + if settings.PANORAMA_ENABLED: + determine_anomaly_details = True + + if determine_anomaly_details: + metric_timestamp = str(int(timeseries[-1][0])) + from_timestamp = str(int(timeseries[1][0])) + timeseries_dir = base_name.replace('.', '/') + + # If Panorama is enabled - create a Panorama check + if settings.PANORAMA_ENABLED: + if not os.path.exists(settings.PANORAMA_CHECK_PATH): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.PANORAMA_CHECK_PATH, mode_arg) + + # Note: + # The values are enclosed is single quoted intentionally + # as the imp.load_source used results in a shift in the + # decimal position when double quoted, e.g. + # value = "5622.0" gets imported as + # 2016-03-02 12:53:26 :: 28569 :: metric variable - value - 562.2 + # single quoting results in the desired, + # 2016-03-02 13:16:17 :: 1515 :: metric variable - value - 5622.0 + added_at = str(int(time())) + source = 'graphite' + panaroma_anomaly_data = 'metric = \'%s\'\n' \ + 'value = \'%s\'\n' \ + 'from_timestamp = \'%s\'\n' \ + 'metric_timestamp = \'%s\'\n' \ + 'algorithms = %s\n' \ + 'triggered_algorithms = %s\n' \ + 'app = \'%s\'\n' \ + 'source = \'%s\'\n' \ + 'added_by = \'%s\'\n' \ + 'added_at = \'%s\'\n' \ + % (base_name, str(datapoint), from_timestamp, + metric_timestamp, str(settings.MIRAGE_ALGORITHMS), + triggered_algorithms, skyline_app, source, + this_host, added_at) + + # Create an anomaly file with details about the anomaly + panaroma_anomaly_file = '%s/%s.%s.txt' % ( + settings.PANORAMA_CHECK_PATH, added_at, + base_name) + try: + write_data_to_file( + skyline_app, panaroma_anomaly_file, 'w', + panaroma_anomaly_data) + logger.info('added panorama anomaly file :: %s' % (panaroma_anomaly_file)) + except: + logger.error('error :: failed to add panorama anomaly file :: %s' % (panaroma_anomaly_file)) + logger.info(traceback.format_exc()) + + # If crucible is enabled - save timeseries and create a + # crucible check + if settings.ENABLE_CRUCIBLE and settings.MIRAGE_CRUCIBLE_ENABLED: + metric_timestamp = str(int(timeseries[-1][0])) + from_timestamp = str(int(timeseries[1][0])) + timeseries_dir = base_name.replace('.', '/') + crucible_anomaly_dir = settings.CRUCIBLE_DATA_FOLDER + '/' + timeseries_dir + '/' + metric_timestamp + if not os.path.exists(crucible_anomaly_dir): + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(crucible_anomaly_dir, mode_arg) + + # Note: + # The value is enclosed is single quoted intentionally + # as the imp.load_source used in crucible results in a + # shift in the decimal position when double quoted, e.g. + # value = "5622.0" gets imported as + # 2016-03-02 12:53:26 :: 28569 :: metric variable - value - 562.2 + # single quoting results in the desired, + # 2016-03-02 13:16:17 :: 1515 :: metric variable - value - 5622.0 + + crucible_anomaly_data = 'metric = \'%s\'\n' \ + 'value = \'%s\'\n' \ + 'from_timestamp = \'%s\'\n' \ + 'metric_timestamp = \'%s\'\n' \ + 'algorithms = %s\n' \ + 'triggered_algorithms = %s\n' \ + 'anomaly_dir = \'%s\'\n' \ + 'graphite_metric = True\n' \ + 'run_crucible_tests = False\n' \ + 'added_by = \'%s\'\n' \ + 'added_at = \'%s\'\n' \ + % (base_name, str(datapoint), from_timestamp, + metric_timestamp, str(settings.MIRAGE_ALGORITHMS), + triggered_algorithms, crucible_anomaly_dir, + skyline_app, metric_timestamp) + + # Create an anomaly file with details about the anomaly + crucible_anomaly_file = '%s/%s.txt' % (crucible_anomaly_dir, base_name) + try: + write_data_to_file( + skyline_app, crucible_anomaly_file, 'w', + crucible_anomaly_data) + logger.info('added crucible anomaly file :: %s' % (crucible_anomaly_file)) + except: + logger.error('error :: failed to add crucible anomaly file :: %s' % (crucible_anomaly_file)) + logger.info(traceback.format_exc()) + + # Create timeseries json file with the timeseries + json_file = '%s/%s.json' % (crucible_anomaly_dir, base_name) + timeseries_json = str(timeseries).replace('[', '(').replace(']', ')') + try: + write_data_to_file(skyline_app, json_file, 'w', timeseries_json) + logger.info('added crucible timeseries file :: %s' % (json_file)) + except: + logger.error('error :: failed to add crucible timeseries file :: %s' % (json_file)) + logger.info(traceback.format_exc()) + + # Create a crucible check file + crucible_check_file = '%s/%s.%s.txt' % (settings.CRUCIBLE_CHECK_PATH, metric_timestamp, base_name) + try: + write_data_to_file( + skyline_app, crucible_check_file, 'w', + crucible_anomaly_data) + logger.info('added crucible check :: %s,%s' % (base_name, metric_timestamp)) + except: + logger.error('error :: failed to add crucible check file :: %s' % (crucible_check_file)) + logger.info(traceback.format_exc()) + else: + base_name = metric.replace(settings.FULL_NAMESPACE, '', 1) + not_anomalous_metric = [datapoint, base_name] + self.not_anomalous_metrics.append(not_anomalous_metric) + logger.info('not anomalous :: %s with %s' % (metric_vars.metric, metric_vars.value)) + + # It could have been deleted by the Roomba + except TypeError: + exceptions['DeletedByRoomba'] += 1 + logger.info('exceptions :: DeletedByRoomba') + except TooShort: + exceptions['TooShort'] += 1 + logger.info('exceptions :: TooShort') + except Stale: + exceptions['Stale'] += 1 + logger.info('exceptions :: Stale') + except Boring: + exceptions['Boring'] += 1 + logger.info('exceptions :: Boring') + except: + exceptions['Other'] += 1 + logger.info('exceptions :: Other') + logger.info(traceback.format_exc()) + + # Add values to the queue so the parent process can collate + for key, value in anomaly_breakdown.items(): + self.mirage_anomaly_breakdown_q.put((key, value)) + + for key, value in exceptions.items(): + self.mirage_exceptions_q.put((key, value)) + + # Remove metric check file + try: + os.remove(metric_check_file) + except OSError: + pass
+ +
[docs] def run(self): + """ + Called when the process intializes. + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os.remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os.remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + while 1: + now = time() + + # Make sure Redis is up + try: + self.redis_conn.ping() + except: + logger.info('skyline can not connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + sleep(10) + logger.info('connecting to redis at socket path %s' % settings.REDIS_SOCKET_PATH) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + """ + Determine if any metric to analyze + """ + while True: + + # Report app up + self.redis_conn.setex(skyline_app, 120, now) + + metric_var_files = [f for f in listdir(settings.MIRAGE_CHECK_PATH) if isfile(join(settings.MIRAGE_CHECK_PATH, f))] + if len(metric_var_files) == 0: + logger.info('sleeping no metrics...') + sleep(10) + else: + sleep(1) + + # Clean up old files + now_timestamp = time() + stale_age = now_timestamp - settings.MIRAGE_STALE_SECONDS + for current_file in listdir(settings.MIRAGE_CHECK_PATH): + if os.path.isfile(settings.MIRAGE_CHECK_PATH + "/" + current_file): + t = os.stat(settings.MIRAGE_CHECK_PATH + "/" + current_file) + c = t.st_ctime + # delete file if older than a week + if c < stale_age: + os.remove(settings.MIRAGE_CHECK_PATH + "/" + current_file) + logger.info('removed %s' % (current_file)) + + # Discover metric to analyze + metric_var_files = '' + metric_var_files = [f for f in listdir(settings.MIRAGE_CHECK_PATH) if isfile(join(settings.MIRAGE_CHECK_PATH, f))] + if len(metric_var_files) > 0: + break + + metric_var_files_sorted = sorted(metric_var_files) + metric_check_file = settings.MIRAGE_CHECK_PATH + "/" + metric_var_files_sorted[0] + + logger.info('processing %s' % metric_var_files_sorted[0]) + + # Remove any existing algorithm.error files from any previous runs + # that did not cleanup for any reason + pattern = '%s.*.algorithm.error' % skyline_app + try: + for f in os.listdir(settings.SKYLINE_TMP_DIR): + if re.search(pattern, f): + try: + os.remove(os.path.join(settings.SKYLINE_TMP_DIR, f)) + logger.info('cleaning up old error file - %s' % (str(f))) + except OSError: + pass + except: + logger.error('failed to cleanup mirage_algorithm.error files - %s' % (traceback.format_exc())) + + # Spawn processes + pids = [] + spawned_pids = [] + pid_count = 0 + MIRAGE_PROCESSES = 1 + run_timestamp = int(now) + for i in range(1, MIRAGE_PROCESSES + 1): + p = Process(target=self.spin_process, args=(i, run_timestamp)) + pids.append(p) + pid_count += 1 + logger.info('starting %s of %s spin_process/es' % (str(pid_count), str(MIRAGE_PROCESSES))) + p.start() + spawned_pids.append(p.pid) + + # Send wait signal to zombie processes + # for p in pids: + # p.join() + # Self monitor processes and terminate if any spin_process has run + # for longer than 180 seconds - 20160512 @earthgecko + p_starts = time() + while time() - p_starts <= settings.MAX_ANALYZER_PROCESS_RUNTIME: + if any(p.is_alive() for p in pids): + # Just to avoid hogging the CPU + sleep(.1) + else: + # All the processes are done, break now. + time_to_run = time() - p_starts + logger.info('%s :: %s spin_process/es completed in %.2f seconds' % ( + skyline_app, str(MIRAGE_PROCESSES), time_to_run)) + break + else: + # We only enter this if we didn't 'break' above. + logger.info('%s :: timed out, killing all spin_process processes' % (skyline_app)) + for p in pids: + p.terminate() + p.join() + + # Log the last reported error by any algorithms that errored in the + # spawned processes from algorithms.py + for completed_pid in spawned_pids: + logger.info('spin_process with pid %s completed' % (str(completed_pid))) + for algorithm in settings.MIRAGE_ALGORITHMS: + algorithm_error_file = '%s/%s.%s.%s.algorithm.error' % ( + settings.SKYLINE_TMP_DIR, skyline_app, + str(completed_pid), algorithm) + if os.path.isfile(algorithm_error_file): + logger.info( + 'error - spin_process with pid %s has reported an error with the %s algorithm' % ( + str(completed_pid), algorithm)) + try: + with open(algorithm_error_file, 'r') as f: + error_string = f.read() + logger.error('%s' % str(error_string)) + except: + logger.error('failed to read %s error file' % algorithm) + try: + os.remove(algorithm_error_file) + except OSError: + pass + + # Grab data from the queue and populate dictionaries + exceptions = dict() + anomaly_breakdown = dict() + while 1: + try: + key, value = self.mirage_anomaly_breakdown_q.get_nowait() + if key not in anomaly_breakdown.keys(): + anomaly_breakdown[key] = value + else: + anomaly_breakdown[key] += value + except Empty: + break + + while 1: + try: + key, value = self.mirage_exceptions_q.get_nowait() + if key not in exceptions.keys(): + exceptions[key] = value + else: + exceptions[key] += value + except Empty: + break + + for metric_variable in self.metric_variables: + if metric_variable[0] == 'metric_name': + metric_name = metric_variable[1] + if metric_variable[0] == 'metric_value': + metric_value = metric_variable[1] + if metric_variable[0] == 'hours_to_resolve': + hours_to_resolve = metric_variable[1] + if metric_variable[0] == 'metric_timestamp': + metric_timestamp = metric_variable[1] + + logger.info('analysis done - %s' % metric_name) + + # Send alerts + # Calculate hours second order resolution to seconds + logger.info('analyzed at %s hours resolution' % hours_to_resolve) + second_order_resolution_seconds = int(hours_to_resolve) * 3600 + logger.info('analyzed at %s seconds resolution' % second_order_resolution_seconds) + + if settings.MIRAGE_ENABLE_ALERTS: + for alert in settings.ALERTS: + for metric in self.anomalous_metrics: + ALERT_MATCH_PATTERN = alert[0] + METRIC_PATTERN = metric[1] + alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) + pattern_match = alert_match_pattern.match(METRIC_PATTERN) + if pattern_match: + cache_key = 'mirage.last_alert.%s.%s' % (alert[1], metric[1]) + try: + last_alert = self.redis_conn.get(cache_key) + if not last_alert: + self.redis_conn.setex(cache_key, alert[2], packb(metric[0])) + trigger_alert(alert, metric, second_order_resolution_seconds) + logger.info('sent %s alert: For %s' % (alert[1], metric[1])) + except Exception as e: + logger.error('error :: could not send %s alert for %s: %s' % (alert[1], metric[1], e)) + + if settings.NEGATE_ANALYZER_ALERTS: + if len(self.anomalous_metrics) == 0: + for negate_alert in settings.ALERTS: + for not_anomalous_metric in self.not_anomalous_metrics: + NEGATE_ALERT_MATCH_PATTERN = negate_alert[0] + NOT_ANOMALOUS_METRIC_PATTERN = not_anomalous_metric[1] + alert_match_pattern = re.compile(NEGATE_ALERT_MATCH_PATTERN) + negate_pattern_match = alert_match_pattern.match(NOT_ANOMALOUS_METRIC_PATTERN) + if negate_pattern_match: + try: + logger.info('negate alert sent: For %s' % (not_anomalous_metric[1])) + trigger_negater(negate_alert, not_anomalous_metric, second_order_resolution_seconds, metric_value) + except Exception as e: + logger.error('error :: could not send alert: %s' % e) + + # Log progress + + if len(self.anomalous_metrics) > 0: + logger.info('seconds since last anomaly :: %.2f' % (time() - now)) + logger.info('total anomalies :: %d' % len(self.anomalous_metrics)) + logger.info('exception stats :: %s' % exceptions) + logger.info('anomaly breakdown :: %s' % anomaly_breakdown) + + # Reset counters + self.anomalous_metrics[:] = [] + self.not_anomalous_metrics[:] = [] + + # Reset metric_variables + self.metric_variables[:] = [] + + # Sleep if it went too fast + if time() - now < 1: + logger.info('sleeping due to low run time...') +# sleep(10) + sleep(1)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/mirage/mirage_alerters.html b/docs/_build/html/_modules/mirage/mirage_alerters.html new file mode 100644 index 00000000..6ea53642 --- /dev/null +++ b/docs/_build/html/_modules/mirage/mirage_alerters.html @@ -0,0 +1,397 @@ + + + + + + + + + + + mirage.mirage_alerters — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for mirage.mirage_alerters

+from smtplib import SMTP
+import mirage_alerters
+try:
+    import urllib2
+except ImportError:
+    import urllib.request
+    import urllib.error
+from requests.utils import quote
+
+import sys
+python_version = int(sys.version_info[0])
+if python_version == 2:
+    from email.MIMEMultipart import MIMEMultipart
+    from email.MIMEText import MIMEText
+    from email.MIMEImage import MIMEImage
+if python_version == 3:
+    from email.mime.multipart import MIMEMultipart
+    from email.mime.text import MIMEText
+    from email.mime.image import MIMEImage
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+"""
+Create any alerter you want here. The function will be invoked from trigger_alert.
+Two arguments will be passed, both of them tuples: alert and metric.
+
+alert: the tuple specified in your settings:
+    alert[0]: The matched substring of the anomalous metric
+    alert[1]: the name of the strategy being used to alert
+    alert[2]: The timeout of the alert that was triggered
+    alert[3]: The SECOND_ORDER_RESOLUTION_HOURS
+metric: information about the anomaly itself
+    metric[0]: the anomalous value
+    metric[1]: The full name of the anomalous metric
+"""
+
+# FULL_DURATION to hours so that Mirage can surface the relevant timeseries data
+# for analyzer comparison in the graph
+try:
+    full_duration_seconds = int(settings.FULL_DURATION)
+except:
+    full_duration_seconds = 86400
+full_duration_in_hours = full_duration_seconds / 60 / 60
+
+
+
[docs]def alert_smtp(alert, metric, second_order_resolution_seconds): + + # SECOND_ORDER_RESOLUTION_SECONDS to hours so that Mirage surfaces the + # relevant timeseries data in the graph + second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600 + + # For backwards compatibility + if '@' in alert[1]: + sender = settings.ALERT_SENDER + recipient = alert[1] + else: + sender = settings.SMTP_OPTS['sender'] + recipients = settings.SMTP_OPTS['recipients'][alert[0]] + + # Backwards compatibility + if type(recipients) is str: + recipients = [recipients] + + unencoded_graph_title = 'Skyline Mirage - ALERT at %s hours - anomalous data point - %s' % ( + second_order_resolution_in_hours, metric[0]) + + graph_title_string = quote(unencoded_graph_title, safe='') + graph_title = '&title=%s' % graph_title_string + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + + content_id = metric[1] + image_data = None + if settings.SMTP_OPTS.get('embed-images'): + try: + image_data = urllib2.urlopen(link).read() + except urllib2.URLError: + image_data = None + + # If we failed to get the image or if it was explicitly disabled, + # use the image URL instead of the content. + if image_data is None: + img_tag = '<img src="%s"/>' % link + else: + img_tag = '<img src="cid:%s"/>' % content_id + + body = 'Mirage ALERT <br> Anomalous metric: %s <br> value: %s at %s hours <br> Next alert in: %s seconds <br> <a href="%s">%s</a>' % (metric[1], metric[0], second_order_resolution_in_hours, alert[2], link, img_tag) + + for recipient in recipients: + msg = MIMEMultipart('alternative') + msg['Subject'] = '[Skyline alert] ' + 'Mirage ALERT - ' + metric[1] + msg['From'] = sender + msg['To'] = recipient + + msg.attach(MIMEText(body, 'html')) + if image_data is not None: + msg_attachment = MIMEImage(image_data) + msg_attachment.add_header('Content-ID', '<%s>' % content_id) + msg.attach(msg_attachment) + + s = SMTP('127.0.0.1') + s.sendmail(sender, recipient, msg.as_string()) + s.quit()
+ + +
[docs]def alert_pagerduty(alert, metric, second_order_resolution_seconds): + if settings.PAGERDUTY_ENABLED: + import pygerduty + pager = pygerduty.PagerDuty(settings.PAGERDUTY_OPTS['subdomain'], settings.PAGERDUTY_OPTS['auth_token']) + pager.trigger_incident(settings.PAGERDUTY_OPTS['key'], "Mirage alert - %s - %s" % (metric[0], metric[1])) + else: + pagerduty_not_enabled = True
+ + +
[docs]def alert_hipchat(alert, metric, second_order_resolution_seconds): + + # SECOND_ORDER_RESOLUTION_SECONDS to hours so that Mirage surfaces the + # relevant timeseries data in the graph + second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600 + + if settings.HIPCHAT_ENABLED: + sender = settings.HIPCHAT_OPTS['sender'] + import hipchat + hipster = hipchat.HipChat(token=settings.HIPCHAT_OPTS['auth_token']) + rooms = settings.HIPCHAT_OPTS['rooms'][alert[0]] + + unencoded_graph_title = 'Skyline Mirage - ALERT at %s hours - anomalous data point - %s' % ( + second_order_resolution_in_hours, metric[0]) + graph_title_string = quote(unencoded_graph_title, safe='') + graph_title = '&title=%s' % graph_title_string + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + embed_graph = "<a href='" + link + "'><img height='308' src='" + link + "'>" + metric[1] + "</a>" + + for room in rooms: + hipster.method('rooms/message', method='POST', parameters={'room_id': room, 'from': 'skyline', 'color': settings.HIPCHAT_OPTS['color'], 'message': '%s - Mirage - Anomalous metric: %s (value: %s) at %s hours %s' % (sender, metric[1], metric[0], second_order_resolution_in_hours, embed_graph)}) + else: + hipchat_not_enabled = True
+ + +
[docs]def alert_syslog(alert, metric, second_order_resolution_seconds): + if settings.SYSLOG_ENABLED: + import sys + import syslog + syslog_ident = settings.SYSLOG_OPTS['ident'] + message = str('Mirage - Anomalous metric: %s (value: %s)' % (metric[1], metric[0])) + if sys.version_info[:2] == (2, 6): + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + elif sys.version_info[:2] == (2, 7): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + elif sys.version_info[:1] == (3): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + else: + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + syslog.syslog(4, message) + else: + syslog_not_enabled = True
+ + +
[docs]def trigger_alert(alert, metric, second_order_resolution_seconds): + + if '@' in alert[1]: + strategy = 'alert_smtp' + else: + strategy = 'alert_%s' % alert[1] + + getattr(mirage_alerters, strategy)(alert, metric, second_order_resolution_seconds)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/mirage/mirage_algorithms.html b/docs/_build/html/_modules/mirage/mirage_algorithms.html new file mode 100644 index 00000000..21a1bcb5 --- /dev/null +++ b/docs/_build/html/_modules/mirage/mirage_algorithms.html @@ -0,0 +1,689 @@ + + + + + + + + + + + mirage.mirage_algorithms — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for mirage.mirage_algorithms

+import pandas
+import numpy as np
+import scipy
+import statsmodels.api as sm
+import traceback
+import logging
+from time import time
+import os.path
+import sys
+from os import getpid
+
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+from settings import (
+    MIRAGE_ALGORITHMS,
+    MIRAGE_CONSENSUS,
+    MIRAGE_DATA_FOLDER,
+    MIRAGE_ENABLE_SECOND_ORDER,
+    PANDAS_VERSION,
+    RUN_OPTIMIZED_WORKFLOW,
+    SKYLINE_TMP_DIR,
+)
+
+from algorithm_exceptions import *
+
+skyline_app = 'mirage'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+
+"""
+This is no man's land. Do anything you want in here,
+as long as you return a boolean that determines whether the input timeseries is
+anomalous or not.
+
+The key here is to return a True or False boolean.
+
+You should use the pythonic except mechanism to ensure any excpetions do not
+cause things to halt and the record_algorithm_error utility can be used to
+sample any algorithm errors to log.
+
+To add an algorithm, define it here, and add its name to settings.MIRAGE_ALGORITHMS.
+"""
+
+
+
[docs]def tail_avg(timeseries, second_order_resolution_seconds): + """ + This is a utility function used to calculate the average of the last three + datapoints in the series as a measure, instead of just the last datapoint. + It reduces noise, but it also reduces sensitivity and increases the delay + to detection. + """ + try: + t = (timeseries[-1][1] + timeseries[-2][1] + timeseries[-3][1]) / 3 + return t + except IndexError: + return timeseries[-1][1]
+ + +
[docs]def median_absolute_deviation(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the deviation of its latest datapoint with + respect to the median is X times larger than the median of deviations. + """ + + try: + series = pandas.Series([x[1] for x in timeseries]) + median = series.median() + demedianed = np.abs(series - median) + median_deviation = demedianed.median() + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + + # The test statistic is infinite when the median is zero, + # so it becomes super sensitive. We play it safe and skip when this happens. + if median_deviation == 0: + return False + + if PANDAS_VERSION < '0.17.0': + try: + test_statistic = demedianed.iget(-1) / median_deviation + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + else: + try: + test_statistic = demedianed.iat[-1] / median_deviation + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + + # Completely arbitary...triggers if the median deviation is + # 6 times bigger than the median + if test_statistic > 6: + return True + else: + return False
+ + +
[docs]def grubbs(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the Z score is greater than the Grubb's score. + """ + + try: + series = scipy.array([x[1] for x in timeseries]) + stdDev = scipy.std(series) + mean = np.mean(series) + tail_average = tail_avg(timeseries, second_order_resolution_seconds) + z_score = (tail_average - mean) / stdDev + len_series = len(series) + threshold = scipy.stats.t.isf(.05 / (2 * len_series), len_series - 2) + threshold_squared = threshold * threshold + grubbs_score = ((len_series - 1) / np.sqrt(len_series)) * np.sqrt(threshold_squared / (len_series - 2 + threshold_squared)) + + return z_score > grubbs_score + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def first_hour_average(timeseries, second_order_resolution_seconds): + """ + Calcuate the simple average over one hour, second order resolution seconds ago. + A timeseries is anomalous if the average of the last three datapoints + are outside of three standard deviations of this value. + """ + try: + last_hour_threshold = time() - (second_order_resolution_seconds - 3600) + series = pandas.Series([x[1] for x in timeseries if x[0] < last_hour_threshold]) + mean = (series).mean() + stdDev = (series).std() + t = tail_avg(timeseries, second_order_resolution_seconds) + + return abs(t - mean) > 3 * stdDev + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def stddev_from_average(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the average. This does not exponentially weight the MA and so + is better for detecting anomalies with respect to the entire series. + """ + + try: + series = pandas.Series([x[1] for x in timeseries]) + mean = series.mean() + stdDev = series.std() + t = tail_avg(timeseries, second_order_resolution_seconds) + + return abs(t - mean) > 3 * stdDev + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def stddev_from_moving_average(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the absolute value of the average of the latest + three datapoint minus the moving average is greater than three standard + deviations of the moving average. This is better for finding anomalies with + respect to the short term trends. + """ + try: + series = pandas.Series([x[1] for x in timeseries]) + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=50) + stdDev = pandas.stats.moments.ewmstd(series, com=50) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).mean() + stdDev = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=50).std(bias=False) + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1) - expAverage.iget(-1)) > 3 * stdDev.iget(-1) + else: + return abs(series.iat[-1] - expAverage.iat[-1]) > 3 * stdDev.iat[-1] +# http://stackoverflow.com/questions/28757389/loc-vs-iloc-vs-ix-vs-at-vs-iat + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def mean_subtraction_cumulation(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the value of the next datapoint in the + series is farther than three standard deviations out in cumulative terms + after subtracting the mean from each data point. + """ + + try: + series = pandas.Series([x[1] if x[1] else 0 for x in timeseries]) + series = series - series[0:len(series) - 1].mean() + stdDev = series[0:len(series) - 1].std() + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=15) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=15).mean() + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1)) > 3 * stdDev + else: + return abs(series.iat[-1]) > 3 * stdDev + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def least_squares(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the average of the last three datapoints + on a projected least squares model is greater than three sigma. + """ + + try: + x = np.array([t[0] for t in timeseries]) + y = np.array([t[1] for t in timeseries]) + A = np.vstack([x, np.ones(len(x))]).T + results = np.linalg.lstsq(A, y) + residual = results[1] + m, c = np.linalg.lstsq(A, y)[0] + errors = [] + # Evaluate append once, not every time in the loop - this gains ~0.020 s + # on every timeseries potentially @earthgecko #1310 + append_error = errors.append + + # Further a question exists related to performance and accruracy with + # regards to how many datapoints are in the sample, currently all datapoints + # are used but this may not be the ideal or most efficient computation or + # fit for a timeseries... @earthgecko is checking graphite... + for i, value in enumerate(y): + projected = m * x[i] + c + error = value - projected + # errors.append(error) # @earthgecko #1310 + append_error(error) + + if len(errors) < 3: + return False + + std_dev = scipy.std(errors) + t = (errors[-1] + errors[-2] + errors[-3]) / 3 + + return abs(t) > std_dev * 3 and round(std_dev) != 0 and round(t) != 0 + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def histogram_bins(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if the average of the last three datapoints falls + into a histogram bin with less than 20 other datapoints (you'll need to + tweak that number depending on your data) + + Returns: the size of the bin which contains the tail_avg. Smaller bin size + means more anomalous. + """ + + try: + series = scipy.array([x[1] for x in timeseries]) + t = tail_avg(timeseries, second_order_resolution_seconds) + h = np.histogram(series, bins=15) + bins = h[1] + for index, bin_size in enumerate(h[0]): + if bin_size <= 20: + # Is it in the first bin? + if index == 0: + if t <= bins[0]: + return True + # Is it in the current bin? + elif t >= bins[index] and t < bins[index + 1]: + return True + + return False + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None
+ + +
[docs]def ks_test(timeseries, second_order_resolution_seconds): + """ + A timeseries is anomalous if 2 sample Kolmogorov-Smirnov test indicates + that data distribution for last 10 minutes is different from last hour. + It produces false positives on non-stationary series so Augmented + Dickey-Fuller test applied to check for stationarity. + """ + + try: + hour_ago = time() - 3600 + ten_minutes_ago = time() - 600 + reference = scipy.array([x[1] for x in timeseries if x[0] >= hour_ago and x[0] < ten_minutes_ago]) + probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_minutes_ago]) + + if reference.size < 20 or probe.size < 20: + return False + + ks_d, ks_p_value = scipy.stats.ks_2samp(reference, probe) + + if ks_p_value < 0.05 and ks_d > 0.5: + adf = sm.tsa.stattools.adfuller(reference, 10) + if adf[1] < 0.05: + return True + + return False + except: + traceback_format_exc_string = traceback.format_exc() + algorithm_name = str(get_function_name()) + record_algorithm_error(algorithm_name, traceback_format_exc_string) + return None + + return False
+ +""" +THE END of NO MAN'S LAND + + +THE START of UTILITY FUNCTIONS + +""" + + +
[docs]def get_function_name(): + """ + This is a utility function is used to determine what algorithm is reporting + an algorithm error when the record_algorithm_error is used. + """ + return traceback.extract_stack(None, 2)[0][2]
+ + +
[docs]def record_algorithm_error(algorithm_name, traceback_format_exc_string): + """ + This utility function is used to facilitate the traceback from any algorithm + errors. The algorithm functions themselves we want to run super fast and + without fail in terms of stopping the function returning and not reporting + anything to the log, so the pythonic except is used to "sample" any + algorithm errors to a tmp file and report once per run rather than spewing + tons of errors into the log. + + .. note:: + algorithm errors tmp file clean up + the algorithm error tmp files are handled and cleaned up in + :class:`Analyzer` after all the spawned processes are completed. + + :param algorithm_name: the algoritm function name + :type algorithm_name: str + :param traceback_format_exc_string: the traceback_format_exc string + :type traceback_format_exc_string: str + :return: + - ``True`` the error string was written to the algorithm_error_file + - ``False`` the error string was not written to the algorithm_error_file + + :rtype: + - boolean + + """ + + current_process_pid = getpid() + algorithm_error_file = '%s/%s.%s.%s.algorithm.error' % ( + SKYLINE_TMP_DIR, skyline_app, str(current_process_pid), algorithm_name) + try: + with open(algorithm_error_file, 'w') as f: + f.write(str(traceback_format_exc_string)) + return True + except: + return False
+ + +
[docs]def determine_median(array): + """ + Determine the median in an array of values + """ + + # logger.info('Running ' + str(get_function_name())) + try: + np_array = np.array(array) + except: + return False + try: + array_median = np.median(np_array) + return array_median + except: + return False + + return False
+ + +
[docs]def is_anomalously_anomalous(metric_name, ensemble, datapoint): + """ + This method runs a meta-analysis on the metric to determine whether the + metric has a past history of triggering. TODO: weight intervals based on datapoint + """ + # We want the datapoint to avoid triggering twice on the same data + new_trigger = [time(), datapoint] + + # Get the old history + raw_trigger_history = redis_conn.get('mirage_trigger_history.' + metric_name) + if not raw_trigger_history: + redis_conn.set('mirage_trigger_history.' + metric_name, packb([(time(), datapoint)])) + return True + + trigger_history = unpackb(raw_trigger_history) + + # Are we (probably) triggering on the same data? + if (new_trigger[1] == trigger_history[-1][1] and + new_trigger[0] - trigger_history[-1][0] <= 300): + return False + + # Update the history + trigger_history.append(new_trigger) + redis_conn.set('mirage_trigger_history.' + metric_name, packb(trigger_history)) + + # Should we surface the anomaly? + trigger_times = [x[0] for x in trigger_history] + intervals = [ + trigger_times[i + 1] - trigger_times[i] + for i, v in enumerate(trigger_times) + if (i + 1) < len(trigger_times) + ] + + series = pandas.Series(intervals) + mean = series.mean() + stdDev = series.std() + + return abs(intervals[-1] - mean) > 3 * stdDev
+ + +
[docs]def run_selected_algorithm(timeseries, metric_name, second_order_resolution_seconds): + """ + Run selected algorithms + """ + try: + ensemble = [globals()[algorithm](timeseries, second_order_resolution_seconds) for algorithm in MIRAGE_ALGORITHMS] + threshold = len(ensemble) - MIRAGE_CONSENSUS + if ensemble.count(False) <= threshold: + if MIRAGE_ENABLE_SECOND_ORDER: + if is_anomalously_anomalous(metric_name, ensemble, timeseries[-1][1]): + return True, ensemble, timeseries[-1][1] + else: + return True, ensemble, timeseries[-1][1] + + return False, ensemble, timeseries[-1][1] + except: + logger.error('Algorithm error: %s' % traceback.format_exc()) + return False, [], 1
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/mirage/negaters.html b/docs/_build/html/_modules/mirage/negaters.html new file mode 100644 index 00000000..61d0fa09 --- /dev/null +++ b/docs/_build/html/_modules/mirage/negaters.html @@ -0,0 +1,392 @@ + + + + + + + + + + + mirage.negaters — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for mirage.negaters

+from smtplib import SMTP
+import sys
+python_version = int(sys.version_info[0])
+if python_version == 2:
+    from email.MIMEMultipart import MIMEMultipart
+    from email.MIMEText import MIMEText
+    from email.MIMEImage import MIMEImage
+if python_version == 3:
+    from email.mime.multipart import MIMEMultipart
+    from email.mime.text import MIMEText
+    from email.mime.image import MIMEImage
+import negaters
+try:
+    import urllib2
+except ImportError:
+    import urllib.request
+    import urllib.error
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+
+# from skyline import settings
+
+"""
+Create any alerter you want here. The function will be invoked from trigger_alert.
+Two arguments will be passed, both of them tuples: alert and metric.
+
+alert: the tuple specified in your settings:
+    alert[0]: The matched substring of the anomalous metric
+    alert[1]: the name of the strategy being used to alert
+    alert[2]: The timeout of the alert that was triggered
+    alert[3]: The SECOND_ORDER_RESOLUTION_HOURS
+metric: information about the anomaly itself
+    metric[0]: the anomalous value
+    metric[1]: The full name of the anomalous metric
+"""
+
+
+
[docs]def negate_analyzer_alert(alert, metric, second_order_resolution_seconds, metric_value): + + # FULL_DURATION to hours so that mirage can surface the relevant timeseries data + # for analyzer comparison in the graph + full_duration_in_hours = int(settings.FULL_DURATION) / 3600 + + # SECOND_ORDER_RESOLUTION_SECONDS to hours so that mirage surfaces the + # relevant timeseries data in the graph + second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600 + + # For backwards compatibility + if '@' in alert[1]: + sender = settings.ALERT_SENDER + recipient = alert[1] + else: + sender = settings.SMTP_OPTS['sender'] + recipients = settings.SMTP_OPTS['recipients'][alert[0]] + + # Backwards compatibility + if type(recipients) is str: + recipients = [recipients] + + graph_title = '&title=skyline%%20mirage%%20negation%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % (second_order_resolution_in_hours, metric[1], metric[0]) + analyzer_graph_title = '&title=skyline%%20analyzer%%20alert%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % (full_duration_in_hours, metric[1], metric_value) + + if settings.GRAPHITE_PORT != '': + link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=purple' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) + analyzer_link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, full_duration_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, analyzer_graph_title) + else: + link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=purple' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, second_order_resolution_in_hours, metric[1], graphite_settings) + analyzer_link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, full_duration_in_hours, metric[1], analyzer_graphite_settings) + + content_id = metric[1] + image_data = None + if settings.SMTP_OPTS.get('embed-images'): + try: + image_data = urllib2.urlopen(link).read() + except urllib2.URLError: + image_data = None + + # If we failed to get the image or if it was explicitly disabled, + # use the image URL instead of the content. + if image_data is None: + img_tag = '<img src="%s"/>' % link + else: + img_tag = '<img src="cid:%s"/>' % content_id + + analyzer_content_id = 'analyzer' + analyzer_image_data = None + if settings.SMTP_OPTS.get('embed-images'): + try: + analyzer_image_data = urllib2.urlopen(analyzer_link).read() + except urllib2.URLError: + analyzer_image_data = None + + # If we failed to get the image or if it was explicitly disabled, + # use the image URL instead of the content. + if analyzer_image_data is None: + analyzer_img_tag = '<img src="%s"/>' % analyzer_link + else: + analyzer_img_tag = '<img src="cid:%s"/>' % analyzer_content_id + + mirage_body = 'mirage alert NEGATION <br> analyzer reported %s as anomalous at %s over %s hours <br> <br> This metric is NOT anomalous at %s hours <br> <a href="%s">%s</a>' % (metric[1], metric_value, full_duration_in_hours, second_order_resolution_in_hours, link, img_tag) + analyzer_body = ' <br> <br> <br> Analyzer graph at %s hours for anomalous value: %s<br> <a href="%s">%s</a>' % (full_duration_in_hours, metric_value, analyzer_link, analyzer_img_tag) + if analyzer_image_data is not None: + body = mirage_body + analyzer_body + else: + body = mirage_body + + for recipient in recipients: + msg = MIMEMultipart('alternative') + msg['Subject'] = 'mirage alert NEGATION - ' + metric[1] + msg['From'] = sender + msg['To'] = recipient + + msg.attach(MIMEText(body, 'html')) + if image_data is not None: + msg_attachment = MIMEImage(image_data) + msg_attachment.add_header('Content-ID', '<%s>' % content_id) + msg.attach(msg_attachment) + if analyzer_image_data is not None: + analyzer_msg_attachment = MIMEImage(analyzer_image_data) + analyzer_msg_attachment.add_header('Content-ID', '<%s>' % analyzer_content_id) + msg.attach(analyzer_msg_attachment) + + s = SMTP('127.0.0.1') + s.sendmail(sender, recipient, msg.as_string()) + s.quit()
+ + +
[docs]def negate_hipchat(alert, metric, second_order_resolution_seconds, metric_value): + + # SECOND_ORDER_RESOLUTION_SECONDS to hours so that mirage surfaces the + # relevant timeseries data in the graph + second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600
+ # not really required + + +
[docs]def negate_syslog(alert, metric, second_order_resolution_seconds, metric_value): + import sys + import syslog + + # FULL_DURATION to hours so that mirage can surface the relevant timeseries data + # for analyzer comparison in the graph + full_duration_in_hours = int(settings.FULL_DURATION) / 3600 + + # SECOND_ORDER_RESOLUTION_SECONDS to hours so that mirage surfaces the + # relevant timeseries data in the graph + second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600 + + syslog_ident = settings.SYSLOG_OPTS['ident'] + '-mirage' + message = str("not anomalous at %s hours: %s (%s hours value: %s) - (%s hours value: %s)" % (second_order_resolution_in_hours, metric[1], full_duration_in_hours, metric_value, second_order_resolution_in_hours, metric[0])) + if sys.version_info[:2] == (2, 6): + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + elif sys.version_info[:2] == (2, 7): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + elif sys.version_info[:1] == (3): + syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) + else: + syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) + syslog.syslog(4, message)
+ + +
[docs]def trigger_negater(alert, metric, second_order_resolution_seconds, metric_value): + + if alert[1] == 'smtp': + strategy = 'negate_analyzer_alert' + else: + strategy = 'negate_' + alert[1] + + getattr(negaters, strategy)(alert, metric, second_order_resolution_seconds, metric_value)
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/panorama/agent.html b/docs/_build/html/_modules/panorama/agent.html new file mode 100644 index 00000000..3f5ce81e --- /dev/null +++ b/docs/_build/html/_modules/panorama/agent.html @@ -0,0 +1,344 @@ + + + + + + + + + + + panorama.agent — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for panorama.agent

+import logging
+import sys
+import traceback
+from os import getpid
+from os.path import dirname, abspath, isdir
+from daemon import runner
+from time import sleep, time
+from sys import version_info
+
+import mysql.connector
+from mysql.connector import errorcode
+
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+
+import settings
+
+from panorama import Panorama
+
+skyline_app = 'panorama'
+skyline_app_logger = skyline_app + 'Log'
+logger = logging.getLogger(skyline_app_logger)
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+python_version = int(version_info[0])
+
+# Database configuration
+try:
+    config = {'user': settings.PANORAMA_DBUSER,
+              'password': settings.PANORAMA_DBUSERPASS,
+              'host': settings.PANORAMA_DBHOST,
+              'port': settings.PANORAMA_DBPORT,
+              'database': settings.PANORAMA_DATABASE,
+              'raise_on_warnings': True}
+except:
+    print ('error: failed to determine database settings from settings.py')
+    sys.exit(1)
+
+
+
[docs]class PanoramaAgent(): + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + logger.info('starting skyline panorama') + Panorama(getpid()).start() + + while 1: + sleep(100)
+ +if __name__ == "__main__": + """ + Start the Panorama agent. + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + if not isdir(settings.PANORAMA_CHECK_PATH): + print ('Panorama check directory does not exist at %s' % settings.PANORAMA_CHECK_PATH) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(256, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + # Make sure mysql is available + mysql_up = False + try: + configuration_error = True + # Try connect to mysql + try: + cnx = mysql.connector.connect(**config) + configuration_error = False + mysql_up = True + cnx.close() + except mysql.connector.Error as err: + if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: + logger.error('error: something is wrong with your user name or password') + elif err.errno == errorcode.ER_BAD_DB_ERROR: + logger.error('error: the %s database does not exist' % settings.PANORAMA_DATABASE) + else: + logger.error('mysql error: %s' % str(err)) + except: + try: + if configuration_error: + print ('The database is not available') + except: + print ('The database is not available') + sys.exit(1) + + if not mysql_up: + sys.exit(1) + + panorama = PanoramaAgent() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + panorama.run() + else: + daemon_runner = runner.DaemonRunner(panorama) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/panorama/panorama.html b/docs/_build/html/_modules/panorama/panorama.html new file mode 100644 index 00000000..4e7e7192 --- /dev/null +++ b/docs/_build/html/_modules/panorama/panorama.html @@ -0,0 +1,1081 @@ + + + + + + + + + + + panorama.panorama — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for panorama.panorama

+import logging
+try:
+    from Queue import Empty
+except:
+    from queue import Empty
+from redis import StrictRedis
+from time import time, sleep
+from threading import Thread
+from collections import defaultdict
+from multiprocessing import Process, Manager, Queue
+from msgpack import Unpacker, unpackb, packb
+import os
+from os import path, kill, getpid, system, listdir
+from os.path import join, isfile
+from math import ceil
+import traceback
+import operator
+import socket
+import re
+import imp
+import shutil
+from sys import version_info
+import mysql.connector
+from mysql.connector import errorcode
+
+import settings
+from skyline_functions import send_graphite_metric, mkdir_p, load_metric_vars, fail_check
+
+# Converting one settings variable into a local variable, just because it is a
+# long string otherwise.
+try:
+    ENABLE_PANORAMA_DEBUG = settings.ENABLE_PANORAMA_DEBUG
+except:
+    logger.error('error :: cannot determine ENABLE_PANORAMA_DEBUG from settings' % skyline_app)
+    ENABLE_PANORAMA_DEBUG = False
+
+skyline_app = 'panorama'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+
+python_version = int(version_info[0])
+
+this_host = str(os.uname()[1])
+
+try:
+    SERVER_METRIC_PATH = '.%s' % settings.SERVER_METRICS_NAME
+    if SERVER_METRIC_PATH == '.':
+        SERVER_METRIC_PATH = ''
+except:
+    SERVER_METRIC_PATH = ''
+
+skyline_app_graphite_namespace = 'skyline.%s%s' % (skyline_app, SERVER_METRIC_PATH)
+
+failed_checks_dir = '%s_failed' % settings.PANORAMA_CHECK_PATH
+
+# Database configuration
+config = {'user': settings.PANORAMA_DBUSER,
+          'password': settings.PANORAMA_DBUSERPASS,
+          'host': settings.PANORAMA_DBHOST,
+          'port': settings.PANORAMA_DBPORT,
+          'database': settings.PANORAMA_DATABASE,
+          'raise_on_warnings': True}
+
+
+
[docs]class Panorama(Thread): + """ + The Panorama class which controls the panorama thread and spawned processes. + """ + + def __init__(self, parent_pid): + """ + Initialize Panorama + + Create the :obj:`self.anomalous_metrics` list + + """ + super(Panorama, self).__init__() + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + self.daemon = True + self.parent_pid = parent_pid + self.current_pid = getpid() + self.anomalous_metrics = Manager().list() + self.metric_variables = Manager().list() + self.mysql_conn = mysql.connector.connect(**config) + +
[docs] def check_if_parent_is_alive(self): + """ + Self explanatory + """ + try: + kill(self.current_pid, 0) + kill(self.parent_pid, 0) + except: + exit(0)
+ + """ + These are the panorama mysql functions used to surface and input panorama data + for timeseries. + """ + +
[docs] def mysql_select(self, select): + """ + Select data from mysql database + + :param select: the select string + :type select: str + :return: tuple + :rtype: tuple, boolean + + - **Example usage**:: + + query = 'select id, test from test' + result = self.mysql_select(query) + + - **Example of the 0 indexed results tuple, which can hold multiple results**:: + + >> print('results: %s' % str(results)) + results: [(1, u'test1'), (2, u'test2')] + + >> print('results[0]: %s' % str(results[0])) + results[0]: (1, u'test1') + + .. note:: + - If the MySQL query fails a boolean will be returned not a tuple + * ``False`` + * ``None`` + + """ + + try: + cnx = mysql.connector.connect(**config) + if ENABLE_PANORAMA_DEBUG: + logger.info('debug :: connected to mysql') + except mysql.connector.Error as err: + logger.error('error :: mysql error - %s' % str(err)) + logger.error('error :: failed to connect to mysql') + return False + + if cnx: + try: + if ENABLE_PANORAMA_DEBUG: + logger.info('debug :: %s' % (str(select))) + cursor = cnx.cursor() + query = ('%s' % (str(select))) + cursor.execute(query) + result = cursor.fetchall() + cursor.close() + cnx.close() + return result + except mysql.connector.Error as err: + logger.error('error :: mysql error - %s' % str(err)) + logger.error('error :: failed to query database - %s' % (str(select))) + try: + cnx.close() + return False + except: + return False + else: + if ENABLE_PANORAMA_DEBUG: + logger.error('error :: failed to connect to mysql') + + # Close the test mysql connection + try: + cnx.close() + return False + except: + return False + + return False
+ +
[docs] def mysql_insert(self, insert): + """ + Insert data into mysql table + + :param select: the insert string + :type select: str + :return: int + :rtype: int or boolean + + - **Example usage**:: + + query = 'insert into host (host) VALUES (\'this_host\')' + result = self.mysql_insert(query) + + .. note:: + - If the MySQL query fails a boolean will be returned not a tuple + * ``False`` + * ``None`` + + """ + + try: + cnx = mysql.connector.connect(**config) + if ENABLE_PANORAMA_DEBUG: + logger.info('debug :: connected to mysql') + except mysql.connector.Error as err: + logger.error('error :: mysql error - %s' % str(err)) + logger.error('error :: failed to connect to mysql') + raise + + if cnx: + try: + cursor = cnx.cursor() + cursor.execute(insert) + inserted_id = cursor.lastrowid + # Make sure data is committed to the database + cnx.commit() + cursor.close() + cnx.close() + return inserted_id + except mysql.connector.Error as err: + logger.error('error :: mysql error - %s' % str(err)) + logger.error('Failed to insert record') + cnx.close() + raise + else: + cnx.close() + return False + + return False
+ +
[docs] def spin_process(self, i, metric_check_file): + """ + Assign a metric anomaly to process. + + :param i: python process id + :param metric_check_file: full path to the metric check file + + :return: returns True + + """ + + child_process_pid = os.getpid() + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: child_process_pid - %s' % str(child_process_pid)) + + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: processing metric check - %s' % metric_check_file) + + if not os.path.isfile(str(metric_check_file)): + logger.error('error :: file not found - metric_check_file - %s' % (str(metric_check_file))) + return + + check_file_name = os.path.basename(str(metric_check_file)) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: check_file_name - %s' % check_file_name) + check_file_timestamp = check_file_name.split('.', 1)[0] + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: check_file_timestamp - %s' % str(check_file_timestamp)) + check_file_metricname_txt = check_file_name.split('.', 1)[1] + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: check_file_metricname_txt - %s' % check_file_metricname_txt) + check_file_metricname = check_file_metricname_txt.replace('.txt', '') + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: check_file_metricname - %s' % check_file_metricname) + check_file_metricname_dir = check_file_metricname.replace('.', '/') + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: check_file_metricname_dir - %s' % check_file_metricname_dir) + + metric_failed_check_dir = '%s/%s/%s' % (failed_checks_dir, check_file_metricname_dir, check_file_timestamp) + + failed_check_file = '%s/%s' % (metric_failed_check_dir, check_file_name) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: failed_check_file - %s' % failed_check_file) + + # Load and validate metric variables + try: + metric_vars = load_metric_vars(skyline_app, str(metric_check_file)) + except: + logger.info(traceback.format_exc()) + logger.error('error :: failed to load metric variables from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + # Test metric variables + # We use a pythonic methodology to test if the variables are defined, + # this ensures that if any of the variables are not set for some reason + # we can handle unexpected data or situations gracefully and try and + # ensure that the process does not hang. + try: + metric_vars.metric + metric = str(metric_vars.metric) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - metric - %s' % metric) + except: + logger.error('error :: failed to read metric variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.value + value = str(metric_vars.value) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - value - %s' % (value)) + except: + logger.error('error :: failed to read value variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.from_timestamp + from_timestamp = str(metric_vars.from_timestamp) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - from_timestamp - %s' % from_timestamp) + except: + logger.error('error :: failed to read from_timestamp variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.metric_timestamp + metric_timestamp = str(metric_vars.metric_timestamp) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - metric_timestamp - %s' % metric_timestamp) + except: + logger.error('error :: failed to read metric_timestamp variable from check file - %s' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.algorithms + algorithms = metric_vars.algorithms + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - algorithms - %s' % str(algorithms)) + except: + logger.error('error :: failed to read algorithms variable from check file setting to all' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.triggered_algorithms + triggered_algorithms = metric_vars.triggered_algorithms + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - triggered_algorithms - %s' % str(triggered_algorithms)) + except: + logger.error('error :: failed to read triggered_algorithms variable from check file setting to all' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.app + app = str(metric_vars.app) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - app - %s' % app) + except: + logger.error('error :: failed to read app variable from check file setting to all' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.source + source = str(metric_vars.source) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - source - %s' % source) + except: + logger.error('error :: failed to read source variable from check file setting to all' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.added_by + added_by = str(metric_vars.added_by) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - added_by - %s' % added_by) + except: + logger.error('error :: failed to read added_by variable from check file setting to all' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + try: + metric_vars.added_at + added_at = str(metric_vars.added_at) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: metric variable - added_at - %s' % added_at) + except: + logger.error('error :: failed to read added_at variable from check file setting to all' % (metric_check_file)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return + + record_anomaly = True + cache_key = '%s.last_check.%s.%s' % (skyline_app, app, metric) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: cache_key - %s.last_check.%s.%s' % ( + skyline_app, app, metric)) + try: + last_check = self.redis_conn.get(cache_key) + except Exception as e: + logger.error( + 'error :: could not query cache_key - %s.last_check.%s.%s - %s' % ( + skyline_app, app, metric, e)) + last_check = None + + if last_check: + record_anomaly = False + logger.info( + 'Panorama metric key not expired - %s.last_check.%s.%s' % ( + skyline_app, app, metric)) + + if not record_anomaly: + logger.info('not recording anomaly for - %s' % (metric)) + if os.path.isfile(str(metric_check_file)): + try: + os.remove(str(metric_check_file)) + logger.info('metric_check_file removed - %s' % str(metric_check_file)) + except OSError: + pass + + return + + # Determine id of something thing + def determine_id(table, key, value): + """ + Get the id of something from Redis or the database and insert a new + record if one does not exist for the value. + + :param table: table name + :param key: key name + :param value: value name + :type table: str + :type key: str + :type value: str + :return: int or boolean + + """ + + query_cache_key = '%s.mysql_ids.%s.%s.%s' % (skyline_app, table, key, value) + determined_id = None + redis_determined_id = None + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: query_cache_key - %s' % (query_cache_key)) + + try: + redis_known_id = self.redis_conn.get(query_cache_key) + except: + redis_known_id = None + + if redis_known_id: + unpacker = Unpacker(use_list=False) + unpacker.feed(redis_known_id) + redis_determined_id = list(unpacker) + + if redis_determined_id: + determined_id = int(redis_determined_id[0]) + + if determined_id: + if determined_id > 0: + return determined_id + + # Query MySQL + query = 'select id FROM %s WHERE %s=\'%s\'' % (table, key, value) + results = self.mysql_select(query) + + determined_id = 0 + if results: + determined_id = int(results[0][0]) + + if determined_id > 0: + # Set the key for a week + if not redis_determined_id: + try: + self.redis_conn.setex(query_cache_key, 604800, packb(determined_id)) + logger.info('set redis query_cache_key - %s - id: %s' % ( + query_cache_key, str(determined_id))) + except Exception as e: + logger.error(traceback.format_exc()) + logger.error('error :: failed to set query_cache_key - %s - id: %s' % ( + query_cache_key, str(determined_id))) + return int(determined_id) + + # INSERT because no known id + insert_query = 'insert into %s (%s) VALUES (\'%s\')' % (table, key, value) + logger.info('inserting %s into %s table' % (value, table)) + try: + results = self.mysql_insert(insert_query) + except: + logger.error(traceback.format_exc()) + logger.error('error :: failed to determine the id of %s from the insert' % (value)) + raise + + determined_id = 0 + if results: + determined_id = int(results) + else: + logger.error('error :: results not set') + raise + + if determined_id > 0: + # Set the key for a week + if not redis_determined_id: + try: + self.redis_conn.setex(query_cache_key, 604800, packb(determined_id)) + logger.info('set redis query_cache_key - %s - id: %s' % ( + query_cache_key, str(determined_id))) + except Exception as e: + logger.error(traceback.format_exc()) + logger.error('error :: failed to set query_cache_key - %s - id: %s' % ( + query_cache_key, str(determined_id))) + return determined_id + + logger.error('error :: failed to determine the inserted id for %s' % value) + return False + + try: + added_by_host_id = determine_id('hosts', 'host', added_by) + except: + logger.error('error :: failed to determine id of %s' % (added_by)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + try: + app_id = determine_id('apps', 'app', app) + except: + logger.error('error :: failed to determine id of %s' % (app)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + try: + source_id = determine_id('sources', 'source', source) + except: + logger.error('error :: failed to determine id of %s' % (source)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + try: + metric_id = determine_id('metrics', 'metric', metric) + except: + logger.error('error :: failed to determine id of %s' % (metric)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + algorithms_ids_csv = '' + for algorithm in algorithms: + try: + algorithm_id = determine_id('algorithms', 'algorithm', algorithm) + except: + logger.error('error :: failed to determine id of %s' % (algorithm)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + if algorithms_ids_csv == '': + algorithms_ids_csv = str(algorithm_id) + else: + new_algorithms_ids_csv = '%s,%s' % (algorithms_ids_csv, str(algorithm_id)) + algorithms_ids_csv = new_algorithms_ids_csv + + triggered_algorithms_ids_csv = '' + for triggered_algorithm in triggered_algorithms: + try: + triggered_algorithm_id = determine_id('algorithms', 'algorithm', triggered_algorithm) + except: + logger.error('error :: failed to determine id of %s' % (triggered_algorithm)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + if triggered_algorithms_ids_csv == '': + triggered_algorithms_ids_csv = str(triggered_algorithm_id) + else: + new_triggered_algorithms_ids_csv = '%s,%s' % ( + triggered_algorithms_ids_csv, str(triggered_algorithm_id)) + triggered_algorithms_ids_csv = new_triggered_algorithms_ids_csv + + logger.info('inserting anomaly') + try: + full_duration = int(metric_timestamp) - int(from_timestamp) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: full_duration - %s' % str(full_duration)) + except: + logger.error('error :: failed to determine full_duration') + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + try: + anomalous_datapoint = round(float(value), 6) + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: anomalous_datapoint - %s' % str(anomalous_datapoint)) + except: + logger.error('error :: failed to determine anomalous_datapoint') + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + try: + columns = '%s, %s, %s, %s, %s, %s, %s, %s, %s' % ( + 'metric_id', 'host_id', 'app_id', 'source_id', + 'anomaly_timestamp', 'anomalous_datapoint', 'full_duration', + 'algorithms_run', 'triggered_algorithms') + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: columns - %s' % str(columns)) + except: + logger.error('error :: failed to construct columns string') + logger.info(traceback.format_exc()) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + try: + query = 'insert into anomalies (%s) VALUES (%d, %d, %d, %d, %s, %.6f, %d, \'%s\', \'%s\')' % ( + columns, metric_id, added_by_host_id, app_id, source_id, + metric_timestamp, anomalous_datapoint, full_duration, + algorithms_ids_csv, triggered_algorithms_ids_csv) + except: + logger.error('error :: failed to construct insert query') + logger.info(traceback.format_exc()) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: anomaly insert - %s' % str(query)) + + try: + anomaly_id = self.mysql_insert(query) + logger.info('anomaly id - %d - created for %s at %s' % ( + anomaly_id, metric, metric_timestamp)) + except: + logger.error('error :: failed to insert anomaly %s at %s' % ( + anomaly_id, metric, metric_timestamp)) + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file)) + return False + + # Set anomaly record cache key + try: + self.redis_conn.setex( + cache_key, settings.PANORAMA_EXPIRY_TIME, packb(value)) + logger.info('set cache_key - %s.last_check.%s.%s - %s' % ( + skyline_app, app, metric, str(settings.PANORAMA_EXPIRY_TIME))) + except Exception as e: + logger.error( + 'error :: could not query cache_key - %s.last_check.%s.%s - %s' % ( + skyline_app, app, metric, e)) + + if os.path.isfile(str(metric_check_file)): + try: + os.remove(str(metric_check_file)) + logger.info('metric_check_file removed - %s' % str(metric_check_file)) + except OSError: + pass + + return anomaly_id
+ +
[docs] def run(self): + """ + Called when the process intializes. + + Determine if what is known in the Skyline DB + blah + + """ + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + logger.info('removing %s' % skyline_app_logwait) + os.remove(skyline_app_logwait) + except OSError: + logger.error('error :: failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() + log_wait_for = now + 5 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error :: bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os.remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error :: failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + # See if I am known in the DB, if so, what are my variables + # self.populate mysql + # What is my host id in the Skyline panorama DB? + # - if not known - INSERT hostname INTO hosts + # What are the known apps? + # - if returned make a dictionary + # What are the known algorithms? + # - if returned make a dictionary + + while 1: + now = time() + + # Make sure Redis is up + try: + self.redis_conn.ping() + if ENABLE_PANORAMA_DEBUG: + logger.info('debug :: connected to Redis') + except: + logger.error('error :: cannot connect to redis at socket path %s' % ( + settings.REDIS_SOCKET_PATH)) + sleep(30) + self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + continue + + # Report app up + try: + self.redis_conn.setex(skyline_app, 120, now) + logger.info('updated Redis key for %s up' % skyline_app) + except: + logger.error('error :: failed to update Redis key for %s up' % skyline_app) + + if ENABLE_PANORAMA_DEBUG: + # Make sure mysql is available + mysql_down = True + while mysql_down: + + query = 'SHOW TABLES' + results = self.mysql_select(query) + + if results: + mysql_down = False + logger.info('debug :: tested database query - OK') + else: + logger.error('error :: failed to query database') + sleep(30) + + if ENABLE_PANORAMA_DEBUG: + try: + query = 'SELECT id, test FROM test' + result = self.mysql_select(query) + logger.info('debug :: tested mysql SELECT query - OK') + logger.info('debug :: result: %s' % str(result)) + logger.info('debug :: result[0]: %s' % str(result[0])) + logger.info('debug :: result[1]: %s' % str(result[1])) +# Works +# 2016-06-10 19:07:23 :: 4707 :: result: [(1, u'test1')] + except: + logger.error( + 'error :: mysql error - %s' % + traceback.print_exc()) + logger.error('error :: failed to SELECT') + + # self.populate the database metatdata tables + # What is my host id in the Skyline panorama DB? + host_id = False + query = 'select id FROM hosts WHERE host=\'%s\'' % this_host + results = self.mysql_select(query) + if results: + host_id = results[0][0] + logger.info('host_id: %s' % str(host_id)) + else: + logger.info('failed to determine host id of %s' % this_host) + + # - if not known - INSERT hostname INTO host + if not host_id: + logger.info('inserting %s into hosts table' % this_host) + query = 'insert into hosts (host) VALUES (\'%s\')' % this_host + host_id = self.mysql_insert(query) + if host_id: + logger.info('new host_id: %s' % str(host_id)) + + if not host_id: + logger.error( + 'error :: failed to determine populate %s into the hosts table' % + this_host) + sleep(30) + continue + + # Like loop through the panorama dir and see if anyone has left you + # any work, etc + # Make sure check_dir exists and has not been removed + try: + if settings.ENABLE_PANORAMA_DEBUG: + logger.info('debug :: checking check dir exists - %s' % settings.PANORAMA_CHECK_PATH) + os.path.exists(settings.PANORAMA_CHECK_PATH) + except: + logger.error('error :: check dir did not exist - %s' % settings.PANORAMA_CHECK_PATH) + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(settings.PANORAMA_CHECK_PATH, mode_arg) + + logger.info('check dir created - %s' % settings.PANORAMA_CHECK_PATH) + os.path.exists(settings.PANORAMA_CHECK_PATH) + # continue + + """ + Determine if any metric has been added to add + """ + while True: + metric_var_files = False + try: + metric_var_files = [f for f in listdir(settings.PANORAMA_CHECK_PATH) if isfile(join(settings.PANORAMA_CHECK_PATH, f))] + except: + logger.error('error :: failed to list files in check dir') + logger.info(traceback.format_exc()) + + if not metric_var_files: + logger.info('sleeping 20 no metric check files') + sleep(20) + + # Discover metric anomalies to insert + metric_var_files = False + try: + metric_var_files = [f for f in listdir(settings.PANORAMA_CHECK_PATH) if isfile(join(settings.PANORAMA_CHECK_PATH, f))] + except: + logger.error('error :: failed to list files in check dir') + logger.info(traceback.format_exc()) + + if metric_var_files: + break + + metric_var_files_sorted = sorted(metric_var_files) + metric_check_file = '%s/%s' % (settings.PANORAMA_CHECK_PATH, str(metric_var_files_sorted[0])) + + logger.info('assigning anomaly for insertion - %s' % str(metric_var_files_sorted[0])) + + # Spawn processes + pids = [] + spawned_pids = [] + pid_count = 0 + now = time() + run_timestamp = int(now) + for i in range(1, settings.PANORAMA_PROCESSES + 1): + try: + p = Process(target=self.spin_process, args=(i, metric_check_file)) + pids.append(p) + pid_count += 1 + logger.info('starting %s of %s spin_process/es' % (str(pid_count), str(settings.PANORAMA_PROCESSES))) + p.start() + spawned_pids.append(p.pid) + except: + logger.error('error :: to start spin_process') + logger.info(traceback.format_exc()) + continue + + # Send wait signal to zombie processes + # for p in pids: + # p.join() + # Self monitor processes and terminate if any spin_process has run + # for longer than CRUCIBLE_TESTS_TIMEOUT + p_starts = time() + while time() - p_starts <= 20: + if any(p.is_alive() for p in pids): + # Just to avoid hogging the CPU + sleep(.1) + else: + # All the processes are done, break now. + time_to_run = time() - p_starts + logger.info( + '%s :: %s spin_process/es completed in %.2f seconds' % ( + skyline_app, str(settings.PANORAMA_PROCESSES), + time_to_run)) + break + else: + # We only enter this if we didn't 'break' above. + logger.info('%s :: timed out, killing all spin_process processes' % (skyline_app)) + for p in pids: + p.terminate() + p.join() + fail_check(skyline_app, metric_failed_check_dir, str(metric_check_file))
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/skyline_functions.html b/docs/_build/html/_modules/skyline_functions.html new file mode 100644 index 00000000..ff84e787 --- /dev/null +++ b/docs/_build/html/_modules/skyline_functions.html @@ -0,0 +1,937 @@ + + + + + + + + + + + skyline_functions — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for skyline_functions

+"""
+Skyline functions
+
+These are shared functions that are required in multiple modules.
+"""
+import logging
+import traceback
+from time import time
+import socket
+
+from os.path import dirname, join, abspath, isfile
+from os import path
+import json
+import requests
+try:
+    import urlparse
+except ImportError:
+    import urllib.parse
+try:
+    import urllib2
+except ImportError:
+    import urllib.request
+    import urllib.error
+import datetime
+
+import settings
+
+try:
+    from settings import GRAPHITE_HOST
+except:
+    GRAPHITE_HOST = ''
+try:
+    from settings import CARBON_PORT
+except:
+    CARBON_PORT = ''
+
+config = {'user': settings.PANORAMA_DBUSER,
+          'password': settings.PANORAMA_DBUSERPASS,
+          'host': settings.PANORAMA_DBHOST,
+          'port': settings.PANORAMA_DBPORT,
+          'database': settings.PANORAMA_DATABASE,
+          'raise_on_warnings': True}
+
+
+
[docs]def send_graphite_metric(current_skyline_app, metric, value): + """ + Sends the skyline_app metrics to the `GRAPHITE_HOST` if a graphite + host is defined. + + :param current_skyline_app: the skyline app using this function + :param metric: the metric namespace + :param value: the metric value + :return: ``True`` or ``False`` + :rtype: boolean + + """ + if GRAPHITE_HOST != '': + + sock = socket.socket() + sock.settimeout(10) + + # Handle connection error to Graphite #116 @etsy + # Fixed as per https://github.com/etsy/skyline/pull/116 and + # mlowicki:etsy_handle_connection_error_to_graphite + # Handle connection error to Graphite #7 @ earthgecko + # merged 1 commit into earthgecko:master from + # mlowicki:handle_connection_error_to_graphite on 16 Mar 2015 + try: + sock.connect((GRAPHITE_HOST, CARBON_PORT)) + sock.settimeout(None) + except socket.error: + sock.settimeout(None) + endpoint = '%s:%d' % (GRAPHITE_HOST, CARBON_PORT) + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + current_logger.error( + 'error :: cannot connect to Graphite at %s' % endpoint) + return False + + # For the same reason as above + # sock.sendall('%s %s %i\n' % (name, value, time())) + try: + sock.sendall('%s %s %i\n' % (metric, value, time())) + sock.close() + return True + except: + endpoint = '%s:%d' % (GRAPHITE_HOST, CARBON_PORT) + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + current_logger.error( + 'error :: could not send data to Graphite at %s' % endpoint) + return False + + return False
+ + +
[docs]def mkdir_p(path): + """ + Create nested directories. + + :param path: directory path to create + + :return: returns True + + """ + try: + os.getpid() + except: + import os + try: + python_version + except: + from sys import version_info + python_version = int(version_info[0]) + try: + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(path, mode_arg) + return True + # Python >2.5 + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise
+ + +
[docs]def load_metric_vars(current_skyline_app, metric_vars_file): + """ + Import the metric variables for a check from a metric check variables file + + :param metric_vars_file: the path and filename to the metric variables files + + :return: returns True + + """ + try: + os.getpid() + except: + import os + + try: + imp + except: + import imp + + if os.path.isfile(metric_vars_file): + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + current_logger.info( + 'loading metric variables from import - metric_check_file - %s' % ( + str(metric_vars_file))) + # Bug #1460: panorama check file fails + # global metric_vars + # current_logger.info('set global metric_vars') + with open(metric_vars_file) as f: + try: + metric_vars = imp.load_source('metric_vars', '', f) + if settings.ENABLE_DEBUG: + current_logger.info( + 'metric_vars determined - metric variable - metric - %s' % str(metric_vars.metric)) + except: + current_logger.info(traceback.format_exc()) + msg = 'failed to import metric variables - metric_check_file' + current_logger.error( + 'error :: %s - %s' % (msg, str(metric_vars_file))) + metric_vars = False + + return metric_vars
+ + +
[docs]def write_data_to_file(current_skyline_app, write_to_file, mode, data): + """ + Write date to a file + + :param current_skyline_app: the skyline app using this function + :param file: the path and filename to write the data into + :param mode: ``w`` to overwrite, ``a`` to append + :param data: the data to write to the file + + :return: returns True + + """ + try: + os.getpid() + except: + import os + + try: + python_version + except: + from sys import version_info + python_version = int(version_info[0]) + + file_dir = os.path.dirname(write_to_file) + if not os.path.exists(file_dir): + try: + if python_version == 2: + mode_arg = int('0755') + if python_version == 3: + mode_arg = mode=0o755 + os.makedirs(file_dir, mode_arg) + # Python >2.5 + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + + if not os.path.exists(file_dir): + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + current_logger.error( + 'error :: could not create directory - %s' % (str(file_dir))) + + try: + with open(write_to_file, mode) as fh: + fh.write(data) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(write_to_file, mode_arg) + + return True + except: + return False + + return False
+ + +
[docs]def fail_check(current_skyline_app, failed_check_dir, check_file_to_fail): + """ + Move a failed check file. + + :param current_skyline_app: the skyline app using this function + :param failed_check_dir: the directory where failed checks are moved to + :param check_file_to_fail: failed check file to move + + :return: ``True``, ``False`` ``pass`` + + """ + try: + os.getpid() + except: + import os + + try: + shutil + except: + import shutil + + try: + python_version + except: + from sys import version_info + python_version = int(version_info[0]) + + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + + if not os.path.exists(failed_check_dir): + try: + mkdir_p(failed_check_dir) + if settings.ENABLE_PANORAMA_DEBUG or settings.ENABLE_CRUCIBLE_DEBUG: + current_logger.info( + 'created failed_check_dir - %s' % str(failed_check_dir)) + except: + current_logger.error( + 'error :: failed to create failed_check_dir - %s' % + str(failed_check_dir)) + current_logger.info(traceback.format_exc()) + return False + + check_file_name = os.path.basename(str(check_file_to_fail)) + failed_check_file = '%s/%s' % (failed_check_dir, check_file_name) + + try: + shutil.move(check_file_to_fail, failed_check_file) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(failed_check_file, mode_arg) + + current_logger.info('moved check file to - %s' % failed_check_file) + return True + except OSError: + msg = 'failed to move check file to -%s' % failed_check_file + current_logger.error('error :: %s' % msg) + current_logger.info(traceback.format_exc()) + pass + + return False
+ + +
[docs]def alert_expiry_check(current_skyline_app, metric, metric_timestamp, added_by): + """ + Only check if the metric does not a EXPIRATION_TIME key set, panorama + uses the alert EXPIRATION_TIME for the relevant alert setting contexts + whether that be analyzer, mirage, boundary, etc and sets its own + cache_keys in redis. This prevents large amounts of data being added + in terms of duplicate anomaly records in Panorama and timeseries json and + image files in crucible samples so that anomalies are recorded at the same + EXPIRATION_TIME as alerts. + + :param current_skyline_app: the skyline app using this function + :param metric: metric name + :param added_by: which app requested the alert_expiry_check + + :return: ``True``, ``False`` ``pass`` + + - If in alert expiry period returns ```True``` + - If not in alert expiry period or unknown returns ```False``` + """ + + try: + re + except: + import re + + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + + cache_key = 'last_alert.%s.%s.%s' % (current_skyline_app, added_by, metric) + try: + last_alert = self.redis_conn.get(cache_key) + except: + current_logger.info(traceback.format_exc()) + current_logger.error( + 'error :: failed to query redis cache key - %s' % cache_key) + return False + + if added_by == 'analyzer' or added_by == 'mirage': + if settings.ENABLE_DEBUG: + current_logger.info('Will check %s ALERTS' % added_by) + if settings.ENABLE_ALERTS: + if settings.ENABLE_DEBUG: + current_logger.info('Checking %s ALERTS' % added_by) + + for alert in settings.ALERTS: + ALERT_MATCH_PATTERN = alert[0] + METRIC_PATTERN = metric + alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) + pattern_match = alert_match_pattern.match(METRIC_PATTERN) + if pattern_match: + expiration_timeout = alert[2] + if settings.ENABLE_DEBUG: + msg = 'matched - %s - %s - EXPIRATION_TIME is %s' % ( + added_by, metric, str(expiration_timeout)) + current_logger.info('%s' % msg) + check_age = int(check_time) - int(metric_timestamp) + if int(check_age) > int(expiration_timeout): + check_expired = True + if settings.ENABLE_DEBUG: + msg = 'the check is older than EXPIRATION_TIME for the metric - not checking - check_expired' + current_logger.info('%s' % msg) + + if added_by == 'boundary': + if settings.BOUNDARY_ENABLE_ALERTS: + for alert in settings.BOUNDARY_METRICS: + ALERT_MATCH_PATTERN = alert[0] + METRIC_PATTERN = metric + alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) + pattern_match = alert_match_pattern.match(METRIC_PATTERN) + if pattern_match: + source_app = 'boundary' + expiration_timeout = alert[2] + if settings.ENABLE_DEBUG: + msg = 'matched - %s - %s - EXPIRATION_TIME is %s' % ( + source_app, metric, str(expiration_timeout)) + current_logger.info('%s' % msg) + check_age = int(check_time) - int(metric_timestamp) + if int(check_age) > int(expiration_timeout): + check_expired = True + if settings.ENABLE_DEBUG: + msg = 'the check is older than EXPIRATION_TIME for the metric - not checking - check_expired' + current_logger.info('%s' % msg) + + cache_key = '%s.last_check.%s.%s' % (current_skyline_app, added_by, metric) + if settings.ENABLE_DEBUG: + current_logger.info( + 'debug :: cache_key - %s.last_check.%s.%s' % ( + current_skyline_app, added_by, metric)) + + # Only use the cache_key EXPIRATION_TIME if this is not a request to + # run_crucible_tests on a timeseries + if settings.ENABLE_DEBUG: + current_logger.info('debug :: checking if cache_key exists') + try: + last_check = self.redis_conn.get(cache_key) + except Exception as e: + current_logger.error( + 'error :: could not query cache_key for %s - %s - %s' % ( + alerter, metric, str(e))) + + if not last_check: + try: + self.redis_conn.setex(cache_key, expiration_timeout, packb(value)) + current_logger.info( + 'set cache_key for %s - %s with timeout of %s' % ( + source_app, metric, str(expiration_timeout))) + except Exception as e: + current_logger.error( + 'error :: could not query cache_key for %s - %s - %s' % ( + alerter, metric, str(e))) + current_logger.info('all anomaly files will be removed') + remove_all_anomaly_files = True + else: + if check_expired: + current_logger.info( + 'check_expired - all anomaly files will be removed') + remove_all_anomaly_files = True + else: + current_logger.info( + 'cache_key is set and not expired for %s - %s - all anomaly files will be removed' % ( + source_app, metric)) + remove_all_anomaly_files = True
+ + +
[docs]def get_graphite_metric( + current_skyline_app, metric, from_timestamp, until_timestamp, data_type, + output_object): + """ + Fetch data from graphite and return it as object or save it as file + + :param current_skyline_app: the skyline app using this function + :param metric: metric name + :param from_timestamp: unix timestamp + :param until_timestamp: unix timestamp + :param data_type: image or json + :param output_object: object or path and filename to save data as, if set to + object, the object is returned + """ + try: + os.getpid() + except: + import os + + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + +# if settings.ENABLE_DEBUG: + current_logger.info('graphite_metric - %s' % (metric)) + + # Graphite timeouts + connect_timeout = int(settings.GRAPHITE_CONNECT_TIMEOUT) + if settings.ENABLE_DEBUG: + current_logger.info('connect_timeout - %s' % str(connect_timeout)) + + current_logger.info('connect_timeout - %s' % str(connect_timeout)) + + read_timeout = int(settings.GRAPHITE_READ_TIMEOUT) + if settings.ENABLE_DEBUG: + current_logger.info('read_timeout - %s' % str(read_timeout)) + + current_logger.info('read_timeout - %s' % str(read_timeout)) + + graphite_until = datetime.datetime.fromtimestamp(int(until_timestamp)).strftime('%H:%M_%Y%m%d') + current_logger.info('graphite_until - %s' % str(graphite_until)) + + graphite_from = datetime.datetime.fromtimestamp(int(from_timestamp)).strftime('%H:%M_%Y%m%d') + output_format = data_type + + # graphite URL + if settings.GRAPHITE_PORT != '': + image_url = settings.GRAPHITE_PROTOCOL + '://' + settings.GRAPHITE_HOST + ':' + settings.GRAPHITE_PORT + '/render/?from=' + graphite_from + '&until=' + graphite_until + '&target=' + metric + url = image_url + '&format=' + output_format + else: + image_url = settings.GRAPHITE_PROTOCOL + '://' + settings.GRAPHITE_HOST + '/render/?from=' + graphite_from + '&until=' + graphite_until + '&target=' + metric + url = image_url + '&format=' + output_format + if settings.ENABLE_DEBUG: + current_logger.info('graphite url - %s' % (url)) + + current_logger.info('graphite url - %s' % (url)) + + get_image = False + if data_type == 'image' and output_object != 'object': + if not os.path.isfile(output_object): + get_image = True + else: + if settings.ENABLE_DEBUG: + current_logger.info('graph file exists - %s' % str(output_object)) + + if get_image: + current_logger.info( + 'retrieving png - surfacing %s graph from graphite from %s to %s' % ( + metric, str(graphite_from), str(graphite_until))) + + graphite_image_file = output_object + if 'width' not in image_url: + image_url += '&width=586' + if 'height' not in image_url: + image_url += '&height=308' + if settings.ENABLE_DEBUG: + current_logger.info('graphite image url - %s' % (str(image_url))) + image_url_timeout = int(connect_timeout) + + image_data = None + + try: + image_data = urllib2.urlopen(image_url, timeout=image_url_timeout).read() + current_logger.info('url OK - %s' % (image_url)) + except urllib2.URLError: + image_data = None + current_logger.error('error :: url bad - %s' % (image_url)) + + if image_data is not None: + with open(graphite_image_file, 'w') as f: + f.write(image_data) + current_logger.info('retrieved - %s' % (graphite_image_file)) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(graphite_image_file, mode_arg) + else: + current_logger.error( + 'error :: failed to retrieved - %s' % (graphite_image_file)) + + if not os.path.isfile(graphite_image_file): + msg = 'retrieve failed to surface %s graph from Graphite' % (metric) + current_logger.error('error :: %s' % msg) + + if data_type == 'json': + + if output_object != 'object': + if os.path.isfile(output_object): + return True + + msg = 'surfacing timeseries data for %s from graphite from %s to %s' % ( + metric, graphite_from, graphite_until) + current_logger.info('%s' % msg) + if requests.__version__ >= '2.4.0': + use_timeout = (int(connect_timeout), int(read_timeout)) + else: + use_timeout = int(connect_timeout) + if settings.ENABLE_DEBUG: + current_logger.info('use_timeout - %s' % (str(use_timeout))) + + try: + r = requests.get(url, timeout=use_timeout) + js = r.json() + datapoints = js[0]['datapoints'] + if settings.ENABLE_DEBUG: + current_logger.info('data retrieved OK') + except: + datapoints = [[None, int(graphite_until)]] + current_logger.error('error :: data retrieval failed') + + converted = [] + for datapoint in datapoints: + try: + new_datapoint = [float(datapoint[1]), float(datapoint[0])] + converted.append(new_datapoint) + except: + continue + + if output_object != 'object': + with open(output_object, 'w') as f: + f.write(json.dumps(converted)) + if python_version == 2: + mode_arg = int('0644') + if python_version == 3: + mode_arg = '0o644' + os.chmod(output_object, mode_arg) + if settings.ENABLE_DEBUG: + current_logger.info('json file - %s' % output_object) + else: + timeseries_json = json.dumps(converted) + return timeseries_json + + if not os.path.isfile(output_object): + current_logger.error( + 'error :: failed to surface %s json from graphite' % (metric)) + return False + else: + return True
+ +################################################################################ + + +
[docs]def mysql_select(current_skyline_app, select): + """ + Select data from mysql database + + :param current_skyline_app: the Skyline app that is calling the function + :param select: the select string + :type select: str + :return: tuple + :rtype: tuple, boolean + + - **Example usage**:: + + from skyline_functions import mysql_select + query = 'select id, metric from anomalies' + result = mysql_select(query) + + - **Example of the 0 indexed results tuple, which can hold multiple results**:: + + >> print('results: %s' % str(results)) + results: [(1, u'test1'), (2, u'test2')] + + >> print('results[0]: %s' % str(results[0])) + results[0]: (1, u'test1') + + .. note:: + - If the MySQL query fails a boolean will be returned not a tuple + * ``False`` + * ``None`` + + """ + ENABLE_DEBUG = False + try: + if settings.ENABLE_PANORAMA_DEBUG: + ENABLE_DEBUG = True + except: + nothing_to_do = True + try: + if settings.ENABLE_WEBAPP_DEBUG: + ENABLE_DEBUG = True + except: + nothing_to_do = True + + current_skyline_app_logger = current_skyline_app + 'Log' + current_logger = logging.getLogger(current_skyline_app_logger) + current_logger.info('debug :: entering mysql_select') + + try: + mysql.connector + except: + import mysql.connector + try: + conversion + except: + from mysql.connector import conversion + try: + re + except: + import re + + try: + cnx = mysql.connector.connect(**config) + if ENABLE_DEBUG: + current_logger.info('debug :: connected to mysql') + except mysql.connector.Error as err: + current_logger.error('error :: mysql error - %s' % str(err)) + current_logger.error('error :: failed to connect to mysql') + return False + + if cnx: + try: + if ENABLE_DEBUG: + current_logger.info('debug :: %s' % (str(select))) + + # NOTE: when a LIKE SELECT is made the query string is not run + # through the conversion.MySQLConverter().escape method as it + # it would not work and kept on returning mysql error - 1064 with + # multiple single quotes e.g. use near '\'carbon%\'' at line 1 + # Various patterns were attempted to no avail, it seems to be + # related to % character. pseudo basic HTTP auth has been added to + # the webapp just in someone does not secure it properly, a little + # defence in depth, so added WEBAPP_ALLOWED_IPS too. + pattern_match = None + try: + pattern_match = re.search(' LIKE ', str(select)) + except: + current_logger.error('error :: pattern_match - %s' % traceback_format_exc()) + if not pattern_match: + try: + pattern_match = re.search(' like ', str(select)) + except: + current_logger.error('error :: pattern_match - %s' % traceback_format_exc()) + + # TBD - not sure how to get it escaping safely + pattern_match = True + if pattern_match: + query = str(select) + current_logger.info('debug :: unescaped query - %s' % (str(query))) + cursor = cnx.cursor() + cursor.execute(query) + else: + query = conversion.MySQLConverter().escape(select) + current_logger.info('debug :: escaped query - %s' % (str(query))) + cursor = cnx.cursor() + cursor.execute(query.format(query)) + + # cursor = cnx.cursor() + # cursor.execute(query) + result = cursor.fetchall() + cursor.close() + cnx.close() + return result + except mysql.connector.Error as err: + current_logger.error('error :: mysql error - %s' % str(err)) + current_logger.error( + 'error :: failed to query database - %s' % (str(select))) + try: + cnx.close() + return False + except: + return False + else: + if ENABLE_DEBUG: + current_logger.error('error - failed to connect to mysql') + + # Close the test mysql connection + try: + cnx.close() + return False + except: + return False + + return False
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/webapp/backend.html b/docs/_build/html/_modules/webapp/backend.html new file mode 100644 index 00000000..f8275bab --- /dev/null +++ b/docs/_build/html/_modules/webapp/backend.html @@ -0,0 +1,610 @@ + + + + + + + + + + + webapp.backend — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for webapp.backend

+import logging
+import traceback
+import sys
+import re
+from os import path
+import string
+import operator
+from time import time, sleep
+
+from flask import Flask, request, render_template, redirect
+import mysql.connector
+from mysql.connector import errorcode
+
+import settings
+from skyline_functions import get_graphite_metric, mysql_select
+
+import skyline_version
+skyline_version = skyline_version.__absolute_version__
+
+skyline_app = 'webapp'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+
+REQUEST_ARGS = ['from_date',
+                'from_time',
+                'from_timestamp',
+                'until_date',
+                'until_time',
+                'until_timestamp',
+                'target',
+                'like_target',
+                'source',
+                'host',
+                'algorithm',
+                ]
+
+# Converting one settings variable into a local variable, just because it is a
+# long string otherwise.
+try:
+    ENABLE_WEBAPP_DEBUG = settings.ENABLE_PANORAMA_DEBUG
+except:
+    logger.error('error :: cannot determine ENABLE_PANORAMA_DEBUG from settings' % skyline_app)
+    ENABLE_WEBAPP_DEBUG = False
+
+
+
[docs]def panorama_request(): + """ + Gets the details of anomalies from the database, using the URL arguments + that are passed in by the :obj:`request.args` to build the MySQL select + query string and queries the database, parse the results and creates an + array of the anomalies that matched the query and creates the + ``panaroma.json`` file, then returns the array. The Webapp needs both the + array and the JSONP file to serve to the browser for the client side + ``panaroma.js``. + + :param None: determined from :obj:`request.args` + :return: array + :rtype: array + + .. note:: And creates ``panaroma.js`` for client side javascript + + """ + + logger.info('determining request args') + + def get_ids_from_rows(thing, rows): + found_ids = [] + for row in rows: + found_id = str(row[0]) + found_ids.append(int(found_id)) + + ids_first = string.replace(str(found_ids), '[', '') + in_ids = string.replace(str(ids_first), ']', '') + return in_ids + + try: + request_args_len = len(request.args) + except: + request_args_len = False + + latest_anomalies = False + if request_args_len == 0: + request_args_len = 'No request arguments passed' + # return str(request_args_len) + latest_anomalies = True + + metric = False + if metric: + logger.info('Getting db id for %s' % metric) + query = 'select id from metrics WHERE metric=\'%s\'' % metric + try: + result = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get id from db: %s' % traceback.format_exc()) + result = 'metric id not found in database' + + return str(result[0][0]) + + search_request = True + count_request = False + + if latest_anomalies: + logger.info('Getting latest anomalies') + query = 'select id, metric_id, anomalous_datapoint, anomaly_timestamp, full_duration, created_timestamp from anomalies ORDER BY id DESC LIMIT 10' + try: + rows = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get anomalies from db: %s' % traceback.format_exc()) + rows = [] + + if not latest_anomalies: + logger.info('Determining search parameters') + query_string = 'select id, metric_id, anomalous_datapoint, anomaly_timestamp, full_duration, created_timestamp from anomalies' + needs_and = False + + # If we have to '' a string we cannot escape the query it seems... + do_not_escape = False + if 'metric' in request.args: + metric = request.args.get('metric', None) + if metric and metric != 'all': + query = "select id from metrics WHERE metric='%s'" % (metric) + try: + found_id = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get app ids from db: %s' % traceback.format_exc()) + found_id = None + + if found_id: + target_id = str(found_id[0][0]) + if needs_and: + new_query_string = '%s AND metric_id=%s' % (query_string, target_id) + else: + new_query_string = '%s WHERE metric_id=%s' % (query_string, target_id) + query_string = new_query_string + needs_and = True + + if 'metric_like' in request.args: + metric_like = request.args.get('metric_like', None) + if metric_like and metric_like != 'all': + query = 'select id from metrics WHERE metric LIKE \'%s\'' % (str(metric_like)) + try: + rows = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get metric ids from db: %s' % traceback.format_exc()) + return False + + ids = get_ids_from_rows('metric', rows) + new_query_string = '%s WHERE metric_id IN (%s)' % (query_string, str(ids)) + query_string = new_query_string + needs_and = True + + if 'count_by_metric' in request.args: + count_by_metric = request.args.get('count_by_metric', None) + if count_by_metric and count_by_metric != 'false': + search_request = False + count_request = True + # query_string = 'SELECT metric_id, COUNT(*) FROM anomalies GROUP BY metric_id ORDER BY COUNT(*) DESC' + query_string = 'SELECT metric_id, COUNT(*) FROM anomalies' + needs_and = False + + if 'from_timestamp' in request.args: + from_timestamp = request.args.get('from_timestamp', None) + if from_timestamp and from_timestamp != 'all': + + if ":" in from_timestamp: + import time + import datetime + new_from_timestamp = time.mktime(datetime.datetime.strptime(from_timestamp, '%Y%m%d %H:%M').timetuple()) + from_timestamp = str(int(new_from_timestamp)) + + if needs_and: + new_query_string = '%s AND anomaly_timestamp >= %s' % (query_string, from_timestamp) + query_string = new_query_string + needs_and = True + else: + new_query_string = '%s WHERE anomaly_timestamp >= %s' % (query_string, from_timestamp) + query_string = new_query_string + needs_and = True + + if 'until_timestamp' in request.args: + until_timestamp = request.args.get('until_timestamp', None) + if until_timestamp and until_timestamp != 'all': + if ":" in until_timestamp: + import time + import datetime + new_until_timestamp = time.mktime(datetime.datetime.strptime(until_timestamp, '%Y%m%d %H:%M').timetuple()) + until_timestamp = str(int(new_until_timestamp)) + + if needs_and: + new_query_string = '%s AND anomaly_timestamp <= %s' % (query_string, until_timestamp) + query_string = new_query_string + needs_and = True + else: + new_query_string = '%s WHERE anomaly_timestamp <= %s' % (query_string, until_timestamp) + query_string = new_query_string + needs_and = True + + if 'app' in request.args: + app = request.args.get('app', None) + if app and app != 'all': + query = 'select id from apps WHERE app=\'%s\'' % (str(app)) + try: + found_id = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get app ids from db: %s' % traceback.format_exc()) + found_id = None + + if found_id: + target_id = str(found_id[0][0]) + if needs_and: + new_query_string = '%s AND app_id=%s' % (query_string, target_id) + else: + new_query_string = '%s WHERE app_id=%s' % (query_string, target_id) + + query_string = new_query_string + needs_and = True + + if 'source' in request.args: + source = request.args.get('source', None) + if source and source != 'all': + query = 'select id from sources WHERE source=\'%s\'' % (str(source)) + try: + found_id = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get source id from db: %s' % traceback.format_exc()) + found_id = None + + if found_id: + target_id = str(found_id[0][0]) + if needs_and: + new_query_string = '%s AND source_id=\'%s\'' % (query_string, target_id) + else: + new_query_string = '%s WHERE source_id=\'%s\'' % (query_string, target_id) + + query_string = new_query_string + needs_and = True + + if 'algorithm' in request.args: + algorithm = request.args.get('algorithm', None) + + # DISABLED as it is difficult match algorithm_id in the + # triggered_algorithms csv list + algorithm = 'all' + if algorithm and algorithm != 'all': + query = 'select id from algorithms WHERE algorithm LIKE \'%s\'' % (str(algorithm)) + try: + rows = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get algorithm ids from db: %s' % traceback.format_exc()) + rows = [] + + ids = get_ids_from_rows('algorithm', rows) + + if needs_and: + new_query_string = '%s AND algorithm_id IN (%s)' % (query_string, str(ids)) + else: + new_query_string = '%s WHERE algorithm_id IN (%s)' % (query_string, str(ids)) + query_string = new_query_string + needs_and = True + + if 'host' in request.args: + host = request.args.get('host', None) + if host and host != 'all': + query = 'select id from hosts WHERE host=\'%s\'' % (str(host)) + try: + found_id = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get host id from db: %s' % traceback.format_exc()) + found_id = None + + if found_id: + target_id = str(found_id[0][0]) + if needs_and: + new_query_string = '%s AND host_id=\'%s\'' % (query_string, target_id) + else: + new_query_string = '%s WHERE host_id=\'%s\'' % (query_string, target_id) + query_string = new_query_string + needs_and = True + + if 'limit' in request.args: + limit = request.args.get('limit', '10') + else: + limit = '10' + + if 'order' in request.args: + order = request.args.get('order', 'DESC') + else: + order = 'DESC' + + search_query = '%s ORDER BY id %s LIMIT %s' % ( + query_string, order, limit) + + if 'count_by_metric' in request.args: + count_by_metric = request.args.get('count_by_metric', None) + if count_by_metric and count_by_metric != 'false': + # query_string = 'SELECT metric_id, COUNT(*) FROM anomalies GROUP BY metric_id ORDER BY COUNT(*) DESC' + search_query = '%s GROUP BY metric_id ORDER BY COUNT(*) %s LIMIT %s' % ( + query_string, order, limit) + + try: + rows = mysql_select(skyline_app, search_query) + except: + logger.error('error :: failed to get anomalies from db: %s' % traceback.format_exc()) + rows = [] + + anomalies = [] + anomalous_metrics = [] + + if search_request: + anomalies_json = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + panorama_json = string.replace(str(anomalies_json), 'anomalies.json', 'panorama.json') + if ENABLE_WEBAPP_DEBUG: + logger.info('debug :: panorama_json - %s' % str(panorama_json)) + + for row in rows: + if search_request: + anomaly_id = str(row[0]) + metric_id = str(row[1]) + if count_request: + metric_id = str(row[0]) + anomaly_count = str(row[1]) + + query = 'select metric from metrics WHERE id=%s' % metric_id + try: + result = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get id from db: %s' % traceback.format_exc()) + continue + + metric = str(result[0][0]) + if search_request: + anomalous_datapoint = str(row[2]) + anomaly_timestamp = str(row[3]) + full_duration = str(row[4]) + created_timestamp = str(row[5]) + anomaly_data = (anomaly_id, metric, anomalous_datapoint, anomaly_timestamp, full_duration, created_timestamp) + anomalies.append([int(anomaly_id), str(metric), anomalous_datapoint, anomaly_timestamp, full_duration, created_timestamp]) + anomalous_metrics.append(str(metric)) + if count_request: + limit_argument = anomaly_count + if int(anomaly_count) > 200: + limit_argument = 200 + anomaly_data = (int(anomaly_count), metric, str(limit_argument)) + anomalies.append([int(anomaly_count), str(metric), str(limit_argument)]) + + anomalies.sort(key=operator.itemgetter(int(0))) + + if search_request: + with open(panorama_json, 'w') as fh: + pass + + # Write anomalous_metrics to static webapp directory + with open(panorama_json, 'a') as fh: + # Make it JSONP with a handle_data() function + fh.write('handle_data(%s)' % anomalies) + + if latest_anomalies: + return anomalies + else: + return search_query, anomalies
+ + +
[docs]def get_list(thing): + """ + Get a list of names for things in a database table. + + :param thing: the thing, e.g. 'algorithm' + :type thing: str + :return: array + :rtype: array, boolean + + """ + table = '%ss' % thing + query = 'select %s from %s' % (thing, table) + logger.info('select %s from %s' % (thing, table)) + try: + results = mysql_select(skyline_app, query) + except: + logger.error('error :: failed to get list of %ss from %s' % (thing, table)) + results = None + + logger.info('results: %s' % str(results)) + things = [] + for result in results: + things.append(str(result[0])) + + logger.info('things: %s' % str(things)) + + return things
+
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/webapp/webapp.html b/docs/_build/html/_modules/webapp/webapp.html new file mode 100644 index 00000000..fa1cf7a4 --- /dev/null +++ b/docs/_build/html/_modules/webapp/webapp.html @@ -0,0 +1,1030 @@ + + + + + + + + + + + webapp.webapp — Skyline 1.0.0-dev documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + + + + + +
+ +
+
+
+
+ +

Source code for webapp.webapp

+import redis
+import logging
+import simplejson as json
+import sys
+import re
+import traceback
+from msgpack import Unpacker
+from functools import wraps
+from flask import Flask, request, render_template, redirect, Response, abort
+from daemon import runner
+from os.path import dirname, abspath, isdir
+from os import path
+import string
+from os import remove as os_remove
+from time import time, sleep
+
+# @added 20160703 - Feature #1464: Webapp Redis browser
+import time
+from datetime import datetime, timedelta
+import os
+import base64
+from msgpack import unpackb, packb
+import string
+# flask things for rebrow
+from flask import session, g, url_for, flash, Markup, json
+
+from logging.handlers import TimedRotatingFileHandler, MemoryHandler
+
+import os.path
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
+sys.path.insert(0, os.path.dirname(__file__))
+import settings
+import skyline_version
+from skyline_functions import get_graphite_metric, write_data_to_file
+
+from backend import panorama_request, get_list
+
+skyline_version = skyline_version.__absolute_version__
+
+skyline_app = 'webapp'
+skyline_app_logger = '%sLog' % skyline_app
+logger = logging.getLogger(skyline_app_logger)
+skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+skyline_app_loglock = '%s.lock' % skyline_app_logfile
+skyline_app_logwait = '%s.wait' % skyline_app_logfile
+logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app)
+
+# werkzeug access log
+access_logger = logging.getLogger('werkzeug')
+
+REDIS_CONN = redis.StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH)
+
+ENABLE_WEBAPP_DEBUG = True
+
+app = Flask(__name__)
+app.config['PROPAGATE_EXCEPTIONS'] = True
+
+graph_url_string = str(settings.GRAPH_URL)
+PANORAMA_GRAPH_URL = re.sub('\/render\/.*', '', graph_url_string)
+
+
+@app.before_request
+
[docs]def limit_remote_addr(): + """ + This function is called to check if the requesting IP address is in the + settings.WEBAPP_ALLOWED_IPS array, if not 403. + """ + ip_allowed = False + for web_allowed_ip in settings.WEBAPP_ALLOWED_IPS: + if request.remote_addr == web_allowed_ip: + ip_allowed = True + + if not settings.WEBAPP_IP_RESTRICTED: + ip_allowed = True + + if not ip_allowed: + abort(403) # Forbidden
+ + +
[docs]def check_auth(username, password): + """This function is called to check if a username / + password combination is valid. + """ + if settings.WEBAPP_AUTH_ENABLED: + return username == settings.WEBAPP_AUTH_USER and password == settings.WEBAPP_AUTH_USER_PASSWORD + else: + return True
+ + +
[docs]def authenticate(): + """Sends a 401 response that enables basic auth""" + return Response( + 'Forbidden', 401, + {'WWW-Authenticate': 'Basic realm="Login Required"'})
+ + +
[docs]def requires_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + if settings.WEBAPP_AUTH_ENABLED: + auth = request.authorization + if not auth or not check_auth(auth.username, auth.password): + return authenticate() + return f(*args, **kwargs) + else: + return True + return decorated
+ + +@app.route("/") +@requires_auth +
[docs]def index(): + + s = time.time() + if 'uh_oh' in request.args: + try: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Testing uh_oh"), 200 + except: + error_string = traceback.format_exc() + logger.error('error :: failed to render uh_oh.html: %s' % str(error_string)) + return 'Uh oh ... a Skyline 500 :(', 500 + + try: + return render_template( + 'now.html', version=skyline_version, + duration=(time.time() - s)), 200 + except: + error_string = traceback.format_exc() + logger.error('error :: failed to render index.html: %s' % str(error_string)) + return 'Uh oh ... a Skyline 500 :(', 500
+ + +@app.route("/now") +@requires_auth +
[docs]def now(): + s = time.time() + try: + return render_template( + 'now.html', version=skyline_version, duration=(time.time() - s)), 200 + except: + error_string = traceback.format_exc() + logger.error('error :: failed to render now.html: %s' % str(error_string)) + return 'Uh oh ... a Skyline 500 :(', 500
+ + +@app.route("/anomalies.json") +
[docs]def anomalies(): + try: + anomalies_json = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + with open(anomalies_json, 'r') as f: + json_data = f.read() + except: + logger.error('error :: failed to get anomalies.json: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + return json_data, 200
+ + +@app.route("/panorama.json") +
[docs]def panorama_anomalies(): + try: + anomalies_json = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + panorama_json = string.replace(str(anomalies_json), 'anomalies.json', 'panorama.json') + logger.info('opening - %s' % panorama_json) + with open(panorama_json, 'r') as f: + json_data = f.read() + except: + logger.error('error :: failed to get panorama.json: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + return json_data, 200
+ + +@app.route("/app_settings") +
[docs]def app_settings(): + + try: + app_settings = {'GRAPH_URL': settings.GRAPH_URL, + 'OCULUS_HOST': settings.OCULUS_HOST, + 'FULL_NAMESPACE': settings.FULL_NAMESPACE, + 'SKYLINE_VERSION': skyline_version, + 'PANORAMA_ENABLED': settings.PANORAMA_ENABLED, + 'PANORAMA_DATABASE': settings.PANORAMA_DATABASE, + 'PANORAMA_DBHOST': settings.PANORAMA_DBHOST, + 'PANORAMA_DBPORT': settings.PANORAMA_DBPORT, + 'PANORAMA_DBUSER': settings.PANORAMA_DBUSER, + 'PANORAMA_DBUSERPASS': 'redacted', + 'PANORAMA_GRAPH_URL': PANORAMA_GRAPH_URL + } + except Exception as e: + error = "error: " + e + resp = json.dumps({'app_settings': error}) + return resp, 500 + + resp = json.dumps(app_settings) + return resp, 200
+ + +@app.route("/version") +
[docs]def version(): + + try: + version_settings = {'SKYLINE_VERSION': skyline_version} + resp = json.dumps(version_settings) + return resp, 200 + except: + return "Not Found", 404
+ + +@app.route("/api", methods=['GET']) +
[docs]def data(): + if 'metric' in request.args: + metric = request.args.get('metric', None) + try: + raw_series = REDIS_CONN.get(metric) + if not raw_series: + resp = json.dumps( + {'results': 'Error: No metric by that name - try /api?metric=' + settings.FULL_NAMESPACE + 'metric_namespace'}) + return resp, 404 + else: + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = [item[:2] for item in unpacker] + resp = json.dumps({'results': timeseries}) + return resp, 200 + except Exception as e: + error = "Error: " + e + resp = json.dumps({'results': error}) + return resp, 500 + + if 'graphite_metric' in request.args: + logger.info('processing graphite_metric api request') + for i in request.args: + key = str(i) + value = request.args.get(key, None) + logger.info('request argument - %s=%s' % (key, str(value))) + + valid_request = True + missing_arguments = [] + + metric = request.args.get('graphite_metric', None) + from_timestamp = request.args.get('from_timestamp', None) + until_timestamp = request.args.get('until_timestamp', None) + + if not metric: + valid_request = False + missing_arguments.append('graphite_metric') + logger.error('graphite_metric argument not found') + else: + logger.info('graphite_metric - %s' % metric) + + if not from_timestamp: + valid_request = False + missing_arguments.append('from_timestamp') + logger.error('from_timestamp argument not found') + else: + logger.info('from_timestamp - %s' % str(from_timestamp)) + + if not until_timestamp: + valid_request = False + missing_arguments.append('until_timestamp') + else: + logger.info('until_timestamp - %s' % str(until_timestamp)) + + if not valid_request: + error = 'Error: not all arguments where passed, missing ' + str(missing_arguments) + resp = json.dumps({'results': error}) + return resp, 404 + else: + logger.info('requesting data from graphite for %s from %s to %s' % ( + str(metric), str(from_timestamp), str(until_timestamp))) + + try: + timeseries = get_graphite_metric( + skyline_app, metric, from_timestamp, until_timestamp, 'json', + 'object') + except: + error = "Error: " + traceback.print_exc() + resp = json.dumps({'results': error}) + return resp, 500 + + resp = json.dumps({'results': timeseries}) + cleaned_resp = False + try: + format_resp_1 = string.replace(str(resp), '"[[', '[[') + cleaned_resp = string.replace(str(format_resp_1), ']]"', ']]') + except: + logger.error('error :: failed string replace resp: ' + traceback.format_exc()) + + if cleaned_resp: + return cleaned_resp, 200 + else: + resp = json.dumps( + {'results': 'Error: failed to generate timeseries'}) + return resp, 404 + + resp = json.dumps( + {'results': 'Error: No argument passed - try /api?metric= or /api?graphite_metric='}) + return resp, 404
+ + +@app.route("/docs") +@requires_auth +
[docs]def docs(): + s = time.time() + try: + return render_template( + 'docs.html', version=skyline_version, duration=(time.time() - s)), 200 + except: + return 'Uh oh ... a Skyline 500 :(', 500
+ + +@app.route("/panorama", methods=['GET']) +@requires_auth +
[docs]def panorama(): + if not settings.PANORAMA_ENABLED: + try: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Panorama is not enabled, please see the Panorama section in the docs and settings.py"), 200 + except: + return 'Uh oh ... a Skyline 500 :(', 500 + + s = time.time() + try: + apps = get_list('app') + except: + logger.error('error :: %s' % traceback.print_exc()) + apps = ['None'] + try: + sources = get_list('source') + except: + logger.error('error :: %s' % traceback.print_exc()) + sources = ['None'] + try: + algorithms = get_list('algorithm') + except: + logger.error('error :: %s' % traceback.print_exc()) + algorithms = ['None'] + try: + hosts = get_list('host') + except: + logger.error('error :: %s' % traceback.print_exc()) + hosts = ['None'] + + try: + request_args_len = len(request.args) + except: + request_args_len = 0 + + latest_anomalies = False + if request_args_len == 0: + latest_anomalies = True + try: + panorama_data = panorama_request() + # logger.info('panorama_data - %s' % str(panorama_data)) + return render_template( + 'panorama.html', anomalies=panorama_data, app_list=apps, + source_list=sources, algorithm_list=algorithms, + host_list=hosts, results='Latest anomalies', + version=skyline_version, duration=(time.time() - s)), 200 + except: + logger.error('error :: failed to get panorama: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + else: + search_request = True + count_request = 'false' + if 'count_by_metric' in request.args: + count_by_metric = request.args.get('count_by_metric', None) + if count_by_metric == 'true': + count_request = 'true' + try: + query, panorama_data = panorama_request() + # logger.info('panorama_data - %s' % str(panorama_data)) + results_string = 'Found anomalies for ' + query + return render_template( + 'panorama.html', anomalies=panorama_data, app_list=apps, + source_list=sources, algorithm_list=algorithms, + host_list=hosts, results=results_string, count_request=count_request, + version=skyline_version, duration=(time.time() - s)), 200 + except: + logger.error('error :: failed to get panorama: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500
+ + +# Feature #1448: Crucible web UI - @earthgecko +# Branch #868: crucible - @earthgecko +# This may actually need Django, perhaps this is starting to move outside the +# realms of Flask.. +@app.route("/crucible", methods=['GET']) +@requires_auth +
[docs]def crucible(): + crucible_web_ui_implemented = False + if crucible_web_ui_implemented: + try: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Sorry the Crucible web UI is not completed yet"), 200 + except: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Sorry the Crucible web UI is not completed yet"), 200
+ +# @added 20160703 - Feature #1464: Webapp Redis browser +# A port of Marian Steinbach's rebrow - https://github.com/marians/rebrow +# Description of info keys +# TODO: to be continued. +serverinfo_meta = { + 'aof_current_rewrite_time_sec': "Duration of the on-going <abbr title='Append-Only File'>AOF</abbr> rewrite operation if any", + 'aof_enabled': "Flag indicating <abbr title='Append-Only File'>AOF</abbr> logging is activated", + 'aof_last_bgrewrite_status': "Status of the last <abbr title='Append-Only File'>AOF</abbr> rewrite operation", + 'aof_last_rewrite_time_sec': "Duration of the last <abbr title='Append-Only File'>AOF</abbr> rewrite operation in seconds", + 'aof_last_write_status': "Status of last <abbr title='Append-Only File'>AOF</abbr> write operation", + 'aof_rewrite_in_progress': "Flag indicating a <abbr title='Append-Only File'>AOF</abbr> rewrite operation is on-going", + 'aof_rewrite_scheduled': "Flag indicating an <abbr title='Append-Only File'>AOF</abbr> rewrite operation will be scheduled once the on-going RDB save is complete", + 'arch_bits': 'Architecture (32 or 64 bits)', + 'blocked_clients': 'Number of clients pending on a blocking call (BLPOP, BRPOP, BRPOPLPUSH)', + 'client_biggest_input_buf': 'biggest input buffer among current client connections', + 'client_longest_output_list': None, + 'cmdstat_client': 'Statistics for the client command', + 'cmdstat_config': 'Statistics for the config command', + 'cmdstat_dbsize': 'Statistics for the dbsize command', + 'cmdstat_del': 'Statistics for the del command', + 'cmdstat_dump': 'Statistics for the dump command', + 'cmdstat_expire': 'Statistics for the expire command', + 'cmdstat_flushall': 'Statistics for the flushall command', + 'cmdstat_get': 'Statistics for the get command', + 'cmdstat_hgetall': 'Statistics for the hgetall command', + 'cmdstat_hkeys': 'Statistics for the hkeys command', + 'cmdstat_hmset': 'Statistics for the hmset command', + 'cmdstat_info': 'Statistics for the info command', + 'cmdstat_keys': 'Statistics for the keys command', + 'cmdstat_llen': 'Statistics for the llen command', + 'cmdstat_ping': 'Statistics for the ping command', + 'cmdstat_psubscribe': 'Statistics for the psubscribe command', + 'cmdstat_pttl': 'Statistics for the pttl command', + 'cmdstat_sadd': 'Statistics for the sadd command', + 'cmdstat_scan': 'Statistics for the scan command', + 'cmdstat_select': 'Statistics for the select command', + 'cmdstat_set': 'Statistics for the set command', + 'cmdstat_smembers': 'Statistics for the smembers command', + 'cmdstat_sscan': 'Statistics for the sscan command', + 'cmdstat_ttl': 'Statistics for the ttl command', + 'cmdstat_type': 'Statistics for the type command', + 'cmdstat_zadd': 'Statistics for the zadd command', + 'cmdstat_zcard': 'Statistics for the zcard command', + 'cmdstat_zrange': 'Statistics for the zrange command', + 'cmdstat_zremrangebyrank': 'Statistics for the zremrangebyrank command', + 'cmdstat_zrevrange': 'Statistics for the zrevrange command', + 'cmdstat_zscan': 'Statistics for the zscan command', + 'config_file': None, + 'connected_clients': None, + 'connected_slaves': None, + 'db0': None, + 'evicted_keys': None, + 'expired_keys': None, + 'gcc_version': None, + 'hz': None, + 'instantaneous_ops_per_sec': None, + 'keyspace_hits': None, + 'keyspace_misses': None, + 'latest_fork_usec': None, + 'loading': None, + 'lru_clock': None, + 'master_repl_offset': None, + 'mem_allocator': None, + 'mem_fragmentation_ratio': None, + 'multiplexing_api': None, + 'os': None, + 'process_id': None, + 'pubsub_channels': None, + 'pubsub_patterns': None, + 'rdb_bgsave_in_progress': None, + 'rdb_changes_since_last_save': None, + 'rdb_current_bgsave_time_sec': None, + 'rdb_last_bgsave_status': None, + 'rdb_last_bgsave_time_sec': None, + 'rdb_last_save_time': None, + 'redis_build_id': None, + 'redis_git_dirty': None, + 'redis_git_sha1': None, + 'redis_mode': None, + 'redis_version': None, + 'rejected_connections': None, + 'repl_backlog_active': None, + 'repl_backlog_first_byte_offset': None, + 'repl_backlog_histlen': None, + 'repl_backlog_size': None, + 'role': None, + 'run_id': None, + 'sync_full': None, + 'sync_partial_err': None, + 'sync_partial_ok': None, + 'tcp_port': None, + 'total_commands_processed': None, + 'total_connections_received': None, + 'uptime_in_days': None, + 'uptime_in_seconds': None, + 'used_cpu_sys': None, + 'used_cpu_sys_children': None, + 'used_cpu_user': None, + 'used_cpu_user_children': None, + 'used_memory': None, + 'used_memory_human': None, + 'used_memory_lua': None, + 'used_memory_peak': None, + 'used_memory_peak_human': None, + 'used_memory_rss': None +} + + +@app.route('/rebrow', methods=['GET', 'POST']) +# def login(): +
[docs]def rebrow(): + """ + Start page + """ + if request.method == 'POST': + # TODO: test connection, handle failures + host = request.form['host'] + port = int(request.form['port']) + db = int(request.form['db']) + url = url_for('rebrow_server_db', host=host, port=port, db=db) + return redirect(url) + else: + s = time.time() + return render_template( + 'rebrow_login.html', + version=skyline_version, + duration=(time.time() - s))
+ + +@app.route("/rebrow_server_db/<host>:<int:port>/<int:db>/") +
[docs]def rebrow_server_db(host, port, db): + """ + List all databases and show info on server + """ + s = time.time() + r = redis.StrictRedis(host=host, port=port, db=0) + info = r.info('all') + dbsize = r.dbsize() + return render_template( + 'rebrow_server_db.html', + host=host, + port=port, + db=db, + info=info, + dbsize=dbsize, + serverinfo_meta=serverinfo_meta, + version=skyline_version, + duration=(time.time() - s))
+ + +@app.route("/rebrow_keys/<host>:<int:port>/<int:db>/keys/", methods=['GET', 'POST']) +
[docs]def rebrow_keys(host, port, db): + """ + List keys for one database + """ + s = time.time() + r = redis.StrictRedis(host=host, port=port, db=db) + if request.method == 'POST': + action = request.form['action'] + app.logger.debug(action) + if action == 'delkey': + if request.form['key'] is not None: + result = r.delete(request.form['key']) + if result == 1: + flash('Key %s has been deleted.' % request.form['key'], category='info') + else: + flash('Key %s could not be deleted.' % request.form['key'], category='error') + return redirect(request.url) + else: + offset = int(request.args.get('offset', '0')) + perpage = int(request.args.get('perpage', '10')) + pattern = request.args.get('pattern', '*') + dbsize = r.dbsize() + keys = sorted(r.keys(pattern)) + limited_keys = keys[offset:(perpage + offset)] + types = {} + for key in limited_keys: + types[key] = r.type(key) + return render_template( + 'rebrow_keys.html', + host=host, + port=port, + db=db, + dbsize=dbsize, + keys=limited_keys, + types=types, + offset=offset, + perpage=perpage, + pattern=pattern, + num_keys=len(keys), + version=skyline_version, + duration=(time.time() - s))
+ + +@app.route("/rebrow_key/<host>:<int:port>/<int:db>/keys/<key>/") +
[docs]def rebrow_key(host, port, db, key): + """ + Show a specific key. + key is expected to be URL-safe base64 encoded + """ +# @added 20160703 - Feature #1464: Webapp Redis browser +# metrics encoded with msgpack + original_key = key + msg_pack_key = False + # if key.startswith('metrics.'): + # msg_packed_key = True + key = base64.urlsafe_b64decode(key.encode('utf8')) + s = time.time() + r = redis.StrictRedis(host=host, port=port, db=db) + dump = r.dump(key) + if dump is None: + abort(404) + # if t is None: + # abort(404) + size = len(dump) + del dump + t = r.type(key) + ttl = r.pttl(key) + if t == 'string': + # @modified 20160703 - Feature #1464: Webapp Redis browser + # metrics encoded with msgpack + # val = r.get(key) + try: + val = r.get(key) + except: + abort(404) + test_string = all(c in string.printable for c in val) + if not test_string: + raw_result = r.get(key) + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_result) + val = list(unpacker) + msg_pack_key = True + elif t == 'list': + val = r.lrange(key, 0, -1) + elif t == 'hash': + val = r.hgetall(key) + elif t == 'set': + val = r.smembers(key) + elif t == 'zset': + val = r.zrange(key, 0, -1, withscores=True) + return render_template( + 'rebrow_key.html', + host=host, + port=port, + db=db, + key=key, + value=val, + type=t, + size=size, + ttl=ttl / 1000.0, + now=datetime.utcnow(), + expiration=datetime.utcnow() + timedelta(seconds=ttl / 1000.0), + version=skyline_version, + duration=(time.time() - s), + msg_packed_key=msg_pack_key)
+ + +@app.template_filter('urlsafe_base64') +
[docs]def urlsafe_base64_encode(s): + if type(s) == 'Markup': + s = s.unescape() + s = s.encode('utf8') + s = base64.urlsafe_b64encode(s) + return Markup(s)
+# END rebrow + + +
[docs]class App(): + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + +
[docs] def run(self): + + # Log management to prevent overwriting + # Allow the bin/<skyline_app>.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os_remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() +# log_wait_for = now + 5 + log_wait_for = now + 1 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os_remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + try: + logger.info('starting %s - %s' % (skyline_app, skyline_version)) + except: + logger.info('starting %s - version UNKNOWN' % (skyline_app)) + logger.info('hosted at %s' % settings.WEBAPP_IP) + logger.info('running on port %d' % settings.WEBAPP_PORT) + + app.run(settings.WEBAPP_IP, settings.WEBAPP_PORT)
+ + +
[docs]def run(): + """ + Start the Webapp server + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(100, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + try: + settings.WEBAPP_SERVER + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_SERVER')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_SERVER')) + sys.exit(1) + try: + settings.WEBAPP_IP + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_IP')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_IP')) + sys.exit(1) + try: + settings.WEBAPP_PORT + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_PORT')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_PORT')) + sys.exit(1) + try: + settings.WEBAPP_AUTH_ENABLED + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_AUTH_ENABLED')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_AUTH_ENABLED')) + sys.exit(1) + try: + settings.WEBAPP_IP_RESTRICTED + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_IP_RESTRICTED')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_IP_RESTRICTED')) + sys.exit(1) + try: + settings.WEBAPP_AUTH_USER + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER')) + sys.exit(1) + try: + settings.WEBAPP_AUTH_USER_PASSWORD + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER_PASSWORD')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER_PASSWORD')) + sys.exit(1) + try: + settings.WEBAPP_ALLOWED_IPS + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_ALLOWED_IPS')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_ALLOWED_IPS')) + sys.exit(1) + + webapp = App() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + webapp.run() + else: + daemon_runner = runner.DaemonRunner(webapp) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action()
+ +if __name__ == "__main__": + run() +
+ +
+
+
+ + +
+ +
+

+ © Copyright 2013-2014, Etsy Inc; 2015, Abe Stanway; 2015-2016, Gary Wilson. + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_sources/alert-testing.txt b/docs/_build/html/_sources/alert-testing.txt new file mode 100644 index 00000000..17a6a6e5 --- /dev/null +++ b/docs/_build/html/_sources/alert-testing.txt @@ -0,0 +1,23 @@ +############# +Alert testing +############# + +The settings.py has a default non-existent metric namespace for testing your +alerters, in the ``skyline_test.alerters.test`` :mod:`settings.ALERTS` tuple. + +If you want to test your email, Hipchat room and Pagerduty OPTS are correctly +configured and working, do the following, once again using the Python-2.7.11 +virtualenv and documentation PATHs as an example: + +.. code-block:: bash + + PYTHON_VIRTUALENV_DIR="/opt/python_virtualenv" + PROJECT="skyline2711" + + cd "${PYTHON_VIRTUALENV_DIR}/projects/${PROJECT}" + source bin/activate + python /opt/skyline/github/skyline/utils/verify_alerts.py --trigger True --metric 'skyline_test.alerters.test' + deactivate + +You should get an alert on each alerter that you have enabled. Do note that the +graphs sent with the alerts will have **no data** in them. diff --git a/docs/_build/html/_sources/analyzer-optimizations.txt b/docs/_build/html/_sources/analyzer-optimizations.txt new file mode 100644 index 00000000..fb14a255 --- /dev/null +++ b/docs/_build/html/_sources/analyzer-optimizations.txt @@ -0,0 +1,285 @@ +====================== +Analyzer Optimizations +====================== + +The original implementation of Skyline has worked for years, almost +flawlessly it must be said. However the original implementation of +Skyline was patterned and somewhat even hard coded to run in a very +large and powerful setup in terms of the volume of metrics it was +handling and the server specs it was running on. + +This setup worked and ran OK on all metric volumes. However if Skyline +was setup in a smaller environment with a few 1000 metrics, the CPU +graphs and load\_avg stats of your Skyline server suggested that Skyline +did need a LOT of processing power. However this is no longer true. + +This was due to one single line of code at the end of Analyzer module, +which only slept if the runtime of an analysis was less than 5 seconds, +undoubtedly resulted in a lot of Skyline implementations seeing +constantly high CPU usage and load\_avg when running Skyline. As this +resulted in Analyzer running in say 19 seconds and then immediately +spawning again and again, etc. + +https://github.com/etsy/skyline/blob/master/src/analyzer/analyzer.py#L242 + +.. code-block:: python + + # Sleep if it went too fast + if time() - now < 5: + logger.info('sleeping due to low run time...') + sleep(10) + +Number of Analyzer processors +----------------------------- + +A number of optimizations have changed the required number of processors +to assign to :mod:`settings.ANALYZER_PROCESSES`, quite dramatically in some +cases. Specifically in cases where the number of metrics being analyzed is not +in the 10s of 1000s. + +Python multiprocessing is not very efficient if it is not need, in fact +the overall overhead of the spawned processes ends up greater than the +overhead of processing with a single process. For example, if we have a +few 1000 metrics and we have 4 processors assigned to Analyzer and the +process duration is 19 seconds, in the original Analyzer we would have +seen 4 CPUs running at 100% constantly (due to the above Sleep if it +went to fast). Even if we leave to Sleep if went too fast in and we +change to :mod:`settings.ANALYZER_PROCESSES` to 1, we will find that: + +- a) we now see 1 CPU running at 100% +- b) our duration has probably increased to about 27 seconds +- c) we use a little more memory on the single process + +When we optimize the sleep to match the environment with the +:mod:`settings.ANALYZER_OPTIMUM_RUN_DURATION` of in this case say 60 instead of +5, we will find that: + +- a) we now see 1 CPU running at a few percent, only spiking up for 27 seconds + +When we further optimize and use the :mod:`settings.RUN_OPTIMIZED_WORKFLOW` we +will find that: + +- a) we now see 1 CPU running at a few percent, only spiking up for 14 seconds +- b) our duration has probably decreased to about 50% + +Analyzer work rate +------------------ + +The original Analyzer analyzed all timeseries against all algorithms +which is the maximum possible work. In terms of the :mod:`settings.CONSENSUS` +based model, this is not the most efficient work rate. + +Performance tuning +------------------ + +A special thanks to `Snakeviz `__ +for a very useful Python profiling tool which enabled some minimal +changes in the code and substantial improvements in the performance, +along with `cprofilev `__ and +`vmprof `__. + +Using anomaly\_breakdown metrics graphs to tune the Analyzer workflow +--------------------------------------------------------------------- + +anomaly\_breakdown metrics were added to Skyline on 10 Jun 2014, yet +never merged into the main Etsy fork. However, in terms of performance +tuning and profiling Skyline they are quite useful. They provide us with +the ability to optimize the analysis of timeseries data based on 2 +simple criteria: + +1. Determine the algorithms that are triggered most frequently +2. Determine the computational expense of each algorithm (a development + addition) that adds ``algorithm_breakdown.*.timing.times_run`` and + the ``algorithm_breakdown.*.timing.total_time`` metrics to + skyline.analyzer. ``hostname`` graphite metric namespaces. + +We can use these data to determine the *efficiency* of the algorithms +and when this is applied to the Analyzer :mod:`settings.CONSENSUS` model we can +optimize ``algorithms.py`` to run in the most efficient manner possible. + +Originally algorithms.py simply analyzed every timeseries against every +``algorithm in ALGORITHMS`` and only checked the :mod:`settings.CONSENSUS` +threshold at the end. However a very small but effective optimization is to use +the above data to run the following optimizations. + +- The most frequently and least expensive :mod:`settings.CONSENSUS` number of + algorithms and then determine if :mod:`settings.CONSENSUS` can be achieved. + Currently there are 9 algorithms that Analyzer uses. However the same + optimization is valid if more algorithms were added. +- If our :mod:`settings.CONSENSUS` was 6 and Analyzer has not been able to + trigger any of the most frequently and least expensive 5 algorithms, then + there is no need to analyze the timeseries against the remaining + algorithms. This surprisingly reduces the work of Analyzer by ~xx% on average + (a lot). +- The cost of this optimization is that we lose the original + ``algorithm_breakdown.*`` metrics which this was evaluated and patterned + against. However two additional factors somewhat mitigate this but it is + definitely still skewed. The mitigations being that: + + - When a timeseries is anomalous more than one algorithm triggers + anyway. + - When an algorithm is triggered, more algorithms are run. Seeing as we + have optimized to have the least frequently triggered algorithms be + run later in the workflow, it stands to reason that a lot of the + time, they would not have triggered even if they were run. However it + is still skewed. + +These optimizations are now the default in ``settings.py``, however they +have been implemented with backwards compatibility and for the purpose +of running Analyzer without optimization of the algorithms to ensure +that they can be benchmarked again should any further algorithms ever be +added to Analyzer or any existing algorithms modified in any way. + +algorithm benchmarks +-------------------- + +analyzer\_dev can be/was used as a benchmarking module to determine the +execution times of algorithms. + +Considerations - approximation of timings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The algorithm benchmark timings are simply approximations of the real +times that the algorithm execution is undertaken in (float). + +tmpfs vs multiprocessing Value +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Recording the algorithm counts and timings without using multiprocessing Value +and associated overhead of locks, etc, etc. /tmp was opted for instead and the +variable :mod:`settings.SKYLINE_TMP_DIR` was added. In most cases /tmp is tmpfs +which is memory anyway so all the heavy lifting in terms of locking etc is +offloaded to the OS and modules do not have to incur the additional complexity +in Python. A simple yet effective win. Same same but different. There may be +some valid reasons for the use multiprocessing Value or Manager().list() + +Algorithms ranked by triggered count +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using the ``anomaly_breakdown`` metrics data it shows that on a plethora of +machine and application related metrics, we can determine the most +triggered algorithms by rank: + +1. stddev\_from\_average +2. mean\_subtraction\_cumulation +3. first\_hour\_average +4. histogram\_bins +5. least\_squares +6. grubbs +7. stddev\_from\_moving\_average +8. median\_absolute\_deviation +9. ks\_test + +Algorithms ranked by execution time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using the ``algorithm_breakdown`` metrics data we can determine the most +"expensive" algorithms by total time to run: + +1. least\_squares (avg: 0.563052576667) +2. stddev\_from\_moving\_average (avg: 0.48511087) +3. mean\_subtraction\_cumulation (avg: 0.453279348333) +4. median\_absolute\_deviation (avg: 0.25222528) +5. stddev\_from\_average (avg: 0.173473198333) +6. first\_hour\_average (avg: 0.151071298333) +7. grubbs (avg: 0.147807641667) +8. histogram\_bins (avg: 0.101075738333) +9. ks\_test (avg: 0.0979568116667) + +Performance weighting +~~~~~~~~~~~~~~~~~~~~~ + +If we change the order in which the timeseries are run through the +algorithms in Analyzer, we can improve the overall performance by +running the most expensive computational algorithms later in the +analysis. + +:: + + +-----------------------------+----------------+---------------------+ + | Algorithm | Triggered rank | Execution time rank | + +=============================+================+=====================+ + | histogram_bins | 4 | 8 | + +-----------------------------+----------------+---------------------+ + | first_hour_average | 3 | 6 | + +-----------------------------+----------------+---------------------+ + | stddev_from_average | 1 | 5 | + +-----------------------------+----------------+---------------------+ + | grubbs | 6 | 7 | + +-----------------------------+----------------+---------------------+ + | ks_test | 9 | 9 | + +-----------------------------+----------------+---------------------+ + | mean_subtraction_cumulation | 3 | 2 | + +-----------------------------+----------------+---------------------+ + | median_absolute_deviation | 8 | 4 | + +-----------------------------+----------------+---------------------+ + | stddev_from_moving_average | 7 | 2 | + +-----------------------------+----------------+---------------------+ + | least_squares | 5 | 1 | + +-----------------------------+----------------+---------------------+ + +:mod:`settings.RUN_OPTIMIZED_WORKFLOW` +-------------------------------------- + +The original version of Analyzer ran all timeseries through all +``ALGORITHMS`` like so: + +.. code-block:: python + + ensemble = [globals()[algorithm](timeseries) for algorithm in ALGORITHMS] + +After running all the algorithms, it then determined whether the last +datapoint for timeseries was anomalous. + +The optimized workflow uses the above triggered / execution time ranking +matrix to run as efficiently as possible and achieve the same results +(see caveat below) but up to ~50% quicker and less CPU cycles. This is +done by iterating through the algorithms in order based on their +respective matrix rankings and evaluating the whether :mod:`settings.CONSENSUS` +can be achieved or not. The least\_squares algorithm, which is the most +computationally expensive, now only runs if :mod:`settings.CONSENSUS` can be +achieved. + +The caveat to this is that this skews the ``anomaly_breakdown`` metrics. +However seeing as the ``anomaly_breakdown`` metrics were not part of the +original Analyzer this is a mute point. That said the performance tuning +and optimizations were made possible by these data, therefore it remains +possible to implement the original configuration and also time all +algorithms (see Development modes if you are interested). A word of +warning, if you have setup a Skyline implementation after the +:mod:`settings.RUN_OPTIMIZED_WORKFLOW` and you have > 1000 metrics running the +unoptimized workflow with the original 5 seconds may send the load\_avg +through the roof. + +The original Analyzer :mod:`settings.ALGORITHMS` setting was: + +.. code-block:: python + + ALGORITHMS = [ + 'first_hour_average', + 'mean_subtraction_cumulation', + 'stddev_from_average', + 'stddev_from_moving_average', + 'least_squares', + 'grubbs', + 'histogram_bins', + 'median_absolute_deviation', + 'ks_test', + ] + +The new optimized Analyzer :mod:`settings.ALGORITHMS` setting based on the above +performance weighing matrix is: + +.. code-block:: python + + ALGORITHMS = [ + 'histogram_bins', + 'first_hour_average', + 'stddev_from_average', + 'grubbs', + 'ks_test', + 'mean_subtraction_cumulation', + 'median_absolute_deviation', + 'stddev_from_moving_average', + 'least_squares', + ] diff --git a/docs/_build/html/_sources/analyzer.txt b/docs/_build/html/_sources/analyzer.txt new file mode 100644 index 00000000..d78b6fe0 --- /dev/null +++ b/docs/_build/html/_sources/analyzer.txt @@ -0,0 +1,129 @@ +======== +Analyzer +======== + +The Analyzer service is responsible for analyzing collected data. It has +a very simple divide-and-conquer strategy. It first checks Redis to get +the total number of metrics stored, and then it fires up a number of +processes equal to :mod:`settings.ANALYZER_PROCESSES`, assigning each +processes a number of metrics. Analyzing a metric is a very +CPU-intensive process, because each timeseries must be decoded from +Messagepack and then run through the algorithms. As such, it is +advisable to set :mod:`settings.ANALYZER_PROCESSES` to about the number of cores +you have - leaving a few for the Horizon service and for Redis. + +The original documentation and settings for skyline were based on: + + a flow of about 5k metrics coming in every second on average (with + 250k distinct metrics). We use a 32 core Sandy Bridge box, with 64 + gb of memory. We experience bursts of up to 70k TPS on Redis + +Skyline runs OK on much less. It can handle ~45000 metrics per minute on +a 4 vCore, 4GB RAM cloud SSD server, even before the introduction of the +:mod:`settings.RUN_OPTIMIZED_WORKFLOW` methodology. + +Do read the notes in ``settings.py`` related to the +:mod:`settings.ANALYZER_PROCESSES` :mod:`settings.ANALYZER_OPTIMUM_RUN_DURATION` +if you are only processing a few 1000 metrics with a data point every minute +then the optimum settings will most likely be something similar to: + +:: + + ANALYZER_PROCESSES = 1 + ANALYZER_OPTIMUM_RUN_DURATION = 60 + +Python multiprocessing is not very efficient if it is not need, in fact +the overall overhead of the spawned processes ends up greater than the +overhead of processing with a single process. + +See `Analyzer Optimizations `__ + +Algorithms +========== + +Skyline Analyzer was designed to handle a very large number of metrics, +for which picking models by hand would prove infeasible. As such, +Skyline Analyzer relies upon the consensus of an ensemble of a few +different algorithms. If the majority of algorithms agree that any given +metric is anomalous, the metric will be classified as anomalous. It may +then be surfaced to the Webapp or pushed to mirage, if Mirage is enabled and +configured for the namespace of the anomalous metric. + +Currently, Skyline does not come with very many algorithmic batteries +included. This is by design. Included are a few algorithms to get you +started, but you are not obligated to use them and are encouraged to +extend them to accommodate your particular data. Indeed, you are +ultimately responsible for using the proper statistical tools the +correct way with respect to your data. + +Of course, we welcome all pull requests containing additional algorithms +to make this tool as robust as possible. To this end, the algorithms +were designed to be very easy to extend and modify. All algorithms are +located in ``algorithms.py``. To add an algorithm to the ensemble, simply +define your algorithm and add the name of your :mod:`settings.ALGORITHMS`. +Make sure your algorithm returns either ``True``, ``False`` or ``None``, and be +sure to update the :mod:`settings.CONSENSUS` setting appropriately. + +Algorithm philosophy +==================== + +The basic algorithm is based on 3-sigma, derived from Shewhart's +`statistical process +control `__. +However, you are not limited to 3-sigma based algorithms if you do not +want to use them - as long as you return a boolean, you can add any sort +of algorithm you like to run on timeseries and vote. + +Explanation of Exceptions +========================= + +**TooShort**: The timeseries was too short, as defined in +:mod:`settings.MIN_TOLERABLE_LENGTH` + +**Incomplete**: The timeseries was less than :mod:`settings.FULL_DURATION` +seconds long + +**Stale**: The timeseries has not received a new metric in more than +:mod:`settings.STALE_PERIOD` seconds + +**Boring**: The timeseries has been the same value for the past +:mod:`settings.MAX_TOLERABLE_BOREDOM` seconds + +**Other**: There's probably an error in the code, if you've been making +changes or we have. + +Push to Mirage +============== + +Analyzer can push anomalous metrics that have a seasonality / +periodicity that is greater than :mod:`settings.FULL_DURATION` to the mirage +service, see `Mirage `__. + +What **Analyzer** does +====================== + +- Analyzer determines all unique metrics in Redis and divides them + between ``ANALYZER_PROCESSES`` to be analysed between + ``spin_process`` processes. +- The spawned ``spin_process`` processes pull the all timeseries for + their ``assigned_metrics`` they have been assigned from Redis and + iterate through each metric and analyze the timeseries against the + ``ALGORITHMS`` declared in the settings.py +- The ``spin_process`` will add any metric that it finds anomalous + (triggers ``CONSENSUS`` number of algorithms) to a list of + anomalous\_metrics. +- The parent Analyzer process will then check every metric in the + anomalous\_metrics list to see if: + + - If the metric matches an ``ALERT`` tuple in settings.py + - If a Mirage parameter is set in the tuple, then Analyzer does not + alert, but hands the metric off to Mirage by adding a Mirage check + file. + - If ``ENABLE_CRUCIBLE`` is True, Analyzer adds timeseries as a json + file and a Crucible check file. + - If no Mirage parameter, but the metric matches an ``ALERT`` tuple + namespace, Analyzer then checks if an Analyzer alert key exists for + the metric by querying the metric's Analyzer alert key in Redis. + - If no alert key, Analyzer sends alert/s to the configured alerters + and sets the metric's Analyzer alert key for ``EXPIRATION_TIME`` + seconds. diff --git a/docs/_build/html/_sources/boundary.txt b/docs/_build/html/_sources/boundary.txt new file mode 100644 index 00000000..c64c2d2d --- /dev/null +++ b/docs/_build/html/_sources/boundary.txt @@ -0,0 +1,148 @@ +======== +Boundary +======== + +Boundary is an extension of Skyline that enables very specific analysis +of specified metrics with specified algorithms, with specified alerts. + +Boundary was added to allow for threshold-like monitoring to the Skyline +model, it was specifically added to enable the detect\_drop\_off\_cliff +algorithm which could not be bolted nicely into Analyzer (although it +was attempted, it was ugly). While Analyzer allows for the passive +analysis of 1000s of metrics, its algorithms are not perfect. Boundary +allows for the use of the Skyline data and model as a scapel, not just a +sword. Just like Analyzer, Boundary has its own algorithms and +importantly, Boundary is *not* ``CONSENSUS`` based. This means that you +can match key metrics on "thresholds/limits" and somewhat dynamically +too. + +The Boundary concept is quite like Skyline backwards, enilyks. This is +because where Analyzer is almost all to one configuration, Boundary is +more one configuration to one or many. Where Analyzer is all metrics +through all algorithms, Boundary is each metric through one algorithm. +Analyzer uses a large range of the timeseries data, Boundary uses the +most recent (the now) portion of the timeseries data. + +Boundary currently has 3 defined algorithms: + +- detect\_drop\_off\_cliff +- less\_than +- greater\_than + +Boundary is run as a separate process just like Analyzer, horizon and +mirage. It was not envisaged to analyze all your metrics, but rather +your key metrics in an additional dimension/s. If it was run across all +of your metrics it would probably be: + +- VERY noisy +- CPU intensive + +If deployed only key metrics it has a very low footprint (9 seconds on +150 metrics with 2 processes assigned) and a high return. If deployed as +intended it should easily coexist with an existing Skyline +Analyzer/Mirage setup, with adding minimal load. This also allows one to +implement Boundary independently without changing, modifying or +impacting on a running Analyzer. + +Boundary alerting is similar to Analyzer alerting, but a bit more +featureful and introduces the ability to rate limit alerts per alerter +channel, as it is not beyond the realms of possibility that at some +point all your key metrics may drop off a cliff, but maybe 15 pagerduty +alerts every 30 minutes is sufficient, so alert rates are configurable. + +Configuration and running Boundary +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``settings.py`` has an independent setting blocks and has detailed information +on each setting in its docstring, the main difference from Analyzer being in +terms of number of variables that have to be declared in the alert tuples, e.g: + +.. code-block:: python + + BOUNDARY_METRICS = ( + # ('metric', 'algorithm', EXPIRATION_TIME, MIN_AVERAGE, MIN_AVERAGE_SECONDS, TRIGGER_VALUE, ALERT_THRESHOLD, 'ALERT_VIAS'), + ('metric1', 'detect_drop_off_cliff', 1800, 500, 3600, 0, 2, 'smtp'), + ('metric2.either', 'less_than', 3600, 0, 0, 15, 2, 'smtp|hipchat'), + ('nometric.other', 'greater_than', 3600, 0, 0, 100000, 1, 'smtp'), + ) + +Once ``settings.py`` has all the Boundary configuration done, start +Boundary: + +.. code-block:: bash + + /opt/skyline/github/skyline/bin/boundary.d start + + +detect\_drop\_off\_cliff algorithm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The detect\_drop\_off\_cliff algorithm provides a method for analysing a +timeseries to determine is the timeseries "dropped off a cliff". The +standard Skyline Analyzer algorithms do not detect the drop off cliff +pattern very well at all, testing with Crucible has proven. Further to +this, the ``CONSENSUS`` methodology used to determine whether a +timeseries deemed anomalous or not, means that even if one or two +algorithms did detect a drop off cliff type event in a timeseries, it +would not be flagged as anomalous if the ``CONSENSUS`` threshold was not +breached. + +The detect\_drop\_off\_cliff algorithm - does just what it says on the +tin. Although this may seem like setting and matching a threshold, it is +more effective than a threshold as it is dynamically set depending on +the data range. + +Some things to note about analyzing a timeseries with the algorithm are: + +- This algorithm is most suited (accurate) with timeseries where there is a + large range in the timeseries most datapoints are > 100 (e.g high rate). + Arbitrary ``trigger`` values in the algorithm do filter peaky low rate + timeseries, but they can become more noisy with lower value data points, as + significant cliff drops are from a lower height, however it still generally + matches drops off cliffs on low range metrics. +- The trigger tuning based on the timeseries sample range is fairly arbitrary, + but has been tested and does filter peaky noise in low range timeseries, which + filters most/lots of noise. +- The alogrithm is more suited to data sets which come from multiple sources, + e.g. an aggregation of a count from all servers, rather than from individual + sources, e.g. a single server's metric. The many are less likely to experience + false positive cliff drops, whereas the individual is more likely to + experience true cliff drops. +- **ONLY WORKS WITH**: + + - Positive, whole number timeseries data + - Does **not** currently work with negative integers in the timeseries values + (although it will not break, will just skip if a negative integer is encountered) + +For more info see: +`detect\_drop\_off\_cliff `__ + +What **Boundary** does +====================== + +Boundary is very similar in work flow to Analyzer in terms of how it +surfaces and analyzes metrics. + +- Boundary determines all unique metrics in Redis. +- Boundary determines what metrics should be analyzed from the + ``BOUNDARY_METRICS`` tuple in ``settings.py``, matching the defined + namespaces to all the unique\_metrics list from Redis. +- These are divided between the ``BOUNDARY_PROCESSES`` to be analyzed. +- Boundary's spawned processes pull the all timeseries for the metrics + they are assigned from Redis and iterate through each metric and + analyses it's timeseries against the algorithm declared for the + metric in the matching ``BOUNDARY_METRICS`` tuple/s in + ``settings.py`` +- The Boundary process will add any metric that it finds anomalous to a + list of anomalous\_metrics. +- The parent Boundary process will then check every metric in the + anomalous\_metrics list to see if: + + - An alert has been triggered for the metric within the last + ``EXPIRATION_TIME`` seconds by querying the metric's Boundary alert + key in Redis + - If no alert key is set, send alert/s to configured alerters and sets + the metric's Boundary alert key in for ``EXPIRATION_TIME`` seconds in + Redis. + - If no alert key is set and :mod:`settings.PANORAMA_ENABLED` is `True`, the + anomalous metrics details will be inserted into the database. diff --git a/docs/_build/html/_sources/building-documentation.txt b/docs/_build/html/_sources/building-documentation.txt new file mode 100644 index 00000000..7566169c --- /dev/null +++ b/docs/_build/html/_sources/building-documentation.txt @@ -0,0 +1,113 @@ +## Building documentation + +Currently [sphinx](http://www.sphinx-doc.org) and sphinx-apidoc is being used to +documented the code and project, however to get working docs, a small +modification is required to the files outputted by sphinx-apidoc. + +This is related to the package restructure undertaken to make the Skyline code +and layout more in line with a normal python package. Although this seems to +have been achieved, the small hack to the sphinx-apidoc output suggests that +this is not 100% correct. Further evidence of this is in terms of importing +from settings.py, the path needs to be appended in the code, which really should +not be required. However, it is working and in the future this should be +figured and fixed. Perhaps the below edit of the autogenerated .rst files +could be achieved with a sphinx `conf.py` setting, if anyone knows please do +let us know :) + +For now... + +### Your Python interpretor + +Open docs/conf.py and change the following to your python interpretor + +``` +# The PATH to YOUR python +sys.path.insert(0, os.path.abspath('/opt/python_virtualenv/projects/skyline2711/lib/python2.7/site-packages')) +``` + +### `.rst` and `.md` wtf? + +The documentation is written in both `.md` and `.rst` format, because it can be +thanks to the awesome of [sphinx](http://www.sphinx-doc.org). The original +Skyline documentation was written in `.md` for github. The documentation is +being ported over to `.rst` to allow for the full functionality of sphinx +documentation. + +### Build + +``` +APPDIR= + +cd "$APPDIR/docs" + +sphinx-apidoc --force -o "${APPDIR}/docs" "${APPDIR}/skyline" skyline + +# Inline edit all apidoc generated .rst files in docs/skyline.*rst +for i in $(find "${APPDIR}/docs" -type f -name "skyline.*rst") +do + cat "$i" > "${i}.original" + cat "${i}.original" | sed -e '/package/!s/automodule:: skyline\./automodule:: /g' > "$i" + rm -f "${i}.original" +done + +cd "$APPDIR/docs" +make clean +rm -rf _build/* +make html +for i in $(find "$APPDIR" -type f -name "*.pyc") +do + rm -f "$i" +done +``` + +Or something like this... + +``` +function build_docs() { + + # Arguments: + # APP_DIR - path to your Skyline dir, e.g. + # build_docs ~/github/earthgecko/skyline/develop/skyline + + if [ -n "$1" ]; then + APPDIR=$1 + fi + + if [ -z "$APPDIR" ]; then + echo "error: could not determine APPDIR - $APPDIR" + return 1 + fi + + if [ ! -d "$APPDIR/docs" ]; then + echo "error: directory not found - $APPDIR/docs" + return 1 + fi + cd "$APPDIR/docs" + echo "Building Skyline documentation - in $APPDIR/docs" + sphinx-apidoc --force -o "${APPDIR}/docs" "${APPDIR}/skyline" skyline + + # Inline edit all apidoc generated .rst files in docs/skyline.*rst + for i in $(find "${APPDIR}/docs" -type f -name "skyline.*rst") + do + cat "$i" > "${i}.org" + cat "${i}.org" | sed -e '/package/!s/automodule:: skyline\./automodule:: /g' > "$i" + rm -f "${i}.org" + done + + cd "$APPDIR/docs" + make clean + rm -rf _build/* + make html + for i in $(find "$APPDIR" -type f -name "*.pyc") + do + rm -f "$i" + done + for i in $(find "$APPDIR" -type d -name "__pycache__") + do + rm -rf "$i" + done + cd +} + +# build_docs +``` diff --git a/docs/_build/html/_sources/crucible.txt b/docs/_build/html/_sources/crucible.txt new file mode 100644 index 00000000..13bf043c --- /dev/null +++ b/docs/_build/html/_sources/crucible.txt @@ -0,0 +1,86 @@ +======== +Crucible +======== + +Crucible is an extension of Skyline based on Abe Stanway's +`Crucible `_ testing suite. Crucible has +been integrated into Skyline as a module and daemon to allow for the following: + +- Allowing for the ad-hoc analysis of a timeseries and generation of resultant + resources (e.g. plot images, etc). +- Allowing for the ad-hoc analysis of a timeseries by a specific or adhoc + defined algorithma nd generation of resultant resources (e.g. plot images, + etc). +- For storing the individual timeseries data for triggered anomalies. + +Be forewarned that Crucible can generated a substantial amount of data in the +timeseries json archives, especially if it is enabled in any of the following +contexts: + +* :mod:`settings.ANALYZER_CRUCIBLE_ENABLED` +* :mod:`settings.MIRAGE_CRUCIBLE_ENABLED` +* :mod:`settings.BOUNDARY_CRUCIBLE_ENABLED` + +Usage +===== + +Crucible is not necessarily meant for generic Analyzer, Mirage or Boundary +inputs although it does works and was patterned using data fed to it from +Analyzer. Crucible is more aimed and having the ability to add miscellaneous +timeseries and algorithms in an ad-hoc manner. Enabling the use of data +sources other than Graphite and Redis data and testing alogrithms in an ad-hoc +manner too. + +Crucible is enabled by default in its own settings block in ``settings.py``, but +it disabled default in each of the apps own settings. + +Why use `.txt` check files +-------------------------- + +The rationale behind offloading checks. + +A number of the Skyline daemons create txt check and metadata files, and json +timeseries files. + +For example Analyzer creates txt check files for Mirage, Crucible and Panorama. +These txt files are created in logical directory structures that mirror +Graphite's whisper storage directories for stats, etc. Often other timeseries +data sets that are not Graphite, machine or app related metrics, are also +structured in a directory or tree structure and follow similar naming +convention, which allows for this tree and txt files design to work with a large +number of other timeseries data sets too. + +Although there is an argument that checks and their related metadata could also +be queued through Redis or another queue application, using the local filesystem +is the simplest method to pass data between the Skyline modules, without +introducing additional dependencies. + +While ensuring that Redis is being queried as minimally as required to do +analysis. The shuffling of data and querying of "queues" is offloaded to the +filesystem. Resulting in each module being somewhat autonomous in terms of +managing its own work, decoupled from the other modules. + +Today's filesystems are more than capable of handling this load. The use of txt +files also provides an event history, which transient Redis data does not. + +Importantly in terms of Crucible ad-hoc testing, txt and json timeseries files +provide a simple and standardised method to push ad-hoc checks into Crucible. + +What **Crucible** does +====================== + +Crucible has 3 roles: + +1. Store resources (timeseries json and graph pngs) for triggered anomalies. +2. Run ad-hoc analysis on any timeseries and create matplotlib plots for the + run algorithms. +3. To update the Panorama database (tbd for future Panorama branch) + +Crucible can be used to analyse any triggered anomaly on an ad-hoc basis. The +timeseries is stored in gzipped json for triggered anomalies so that +retrospective full analysis can be carried out on a snapshot of the timeseries +as it was when the trigger/s fired without the timeseries being changed by +aggregation and retention operations. + +Crucible can create a large amount of data files and require significant disk +space. diff --git a/docs/_build/html/_sources/debian-and-vagrant-installation-tips.txt b/docs/_build/html/_sources/debian-and-vagrant-installation-tips.txt new file mode 100644 index 00000000..470d380e --- /dev/null +++ b/docs/_build/html/_sources/debian-and-vagrant-installation-tips.txt @@ -0,0 +1,104 @@ +## Debian and Vagrant Installation Tips + +Please note that this info is old and has not been updated since 25 Sep 2013 + +### Get a Wheezy box +From this useful [tutorial](http://dominique.broeglin.fr/2012/02/25/wheezy-64-vagrant-base-box.html) + +The previous version, Debian Squeeze, struggled with an easy installation of +Redis and you can get it working with back-ports. + +``` +vagrant box add wheezy64 http://dl.dropbox.com/u/937870/VMs/wheezy64.box +mkdir skyline +cd skyline +vagrant init wheezy64 +``` + +### Extra Vagrant configuration +Forward the port required for the web application + +``` +config.vm.network :forwarded_port, guest: 1500, host: 1500 +``` + +### Python requirements +You'll need **pip** to be able to add some of the requirements for python, as +well as **python-dev** without which some of the packages used with pip will +not work! + +``` +sudo apt-get install python-dev +sudo apt-get install python-pip +``` + +After that we can clone the project’s repository + + +``` +git clone https://github.com/etsy/skyline.git +``` + +Once we have the project we can change into the folder and start going through +all the python dependencies. + +``` +cd skyline +sudo pip install -r requirements.txt +sudo apt-get install python-numpy python-scipy python-scikits.statsmodels +sudo pip install patsy msgpack_python +``` + +### Skyline dependencies + +``` +sudo pip install -r requirements.txt +sudo apt-get install python-numpy python-scipy python-scikits.statsmodels +sudo pip install patsy msgpack_python +``` + +### Redis 2.6 on Debian Wheezy +Version 2.6 is available on the Wheezy back-port which means it will be +available in the next Debian stable version and was back ported to Wheezy. + +``` +sudo vim /etc/apt/sources.list +``` + +Add the back-port, replace **$YOUR_CONFIGURATION** according to your local +configuration. + +``` +deb http://**$YOUR_CONFIGURATION**.debian.org/debian/ wheezy-backports main +``` + +Update and install Redis-Server, don't forget to kill the process as it will be +started with the default config right after the installation. + +``` +sudo apt-get update +sudo apt-get -t wheezy-backports install redis-server +sudo pkill redis +``` + +### Skyline configuration +With all dependencies installed you can now copy an example for the settings +file. This will be the place to add your Graphite URL and other details but for +now we only need to update the IP bound to the web application so the Vagrant +host will display it. + +``` +cp src/settings.py.example src/settings.py +``` + +Edit file for bind. + +``` +vim src/settings.py +``` + +And change the IP to listen on host's requests. + +``` +WEBAPP_IP = '0.0.0.0' +``` diff --git a/docs/_build/html/_sources/development/index.txt b/docs/_build/html/_sources/development/index.txt new file mode 100644 index 00000000..933623ed --- /dev/null +++ b/docs/_build/html/_sources/development/index.txt @@ -0,0 +1,15 @@ +########### +Development +########### + +.. toctree:: + + webapp.rst + +DRY +### + +The current iteration of Skyline has a fair bit of repetition in some of the +functions, etc in each module. The process of consolidating these is ongoing, +however due to the nature of some of the things, some things do need slightly +different implementations. diff --git a/docs/_build/html/_sources/development/webapp.txt b/docs/_build/html/_sources/development/webapp.txt new file mode 100644 index 00000000..ecc41b0d --- /dev/null +++ b/docs/_build/html/_sources/development/webapp.txt @@ -0,0 +1,28 @@ +******************** +Development - Webapp +******************** + +Flask +===== + +The Skyline Webapp has arguably grown to the point were Flask may no longer +necessarily by the best choice for the frontend UI any more. For a number of +reasons, such as: + +* The frontend UI functionality is going to grow, with the addition of other + things requiring more visualizations. +* A high-level Python Web framework like Django may be more appropriate in the + long run. + +The reasons for sticking with Flask at this point are: + +* Because Flask is pretty cool. +* It is a microframework not a full blown web framework. +* It is probably simpler. +* Therefore, it keeps more focus at the "doing stuff" with Skyline, other and + Python side of the equation for now, rather than at writing a new web UI with + Django and porting the current stuff to Django. +* A fair bit of time has been spent adding new things with Flask. +* With gunicorn, Flask can. +* For now it is more than good enough. +* web development, one drop at a time diff --git a/docs/_build/html/_sources/getting-data-into-skyline.txt b/docs/_build/html/_sources/getting-data-into-skyline.txt new file mode 100644 index 00000000..1ec68779 --- /dev/null +++ b/docs/_build/html/_sources/getting-data-into-skyline.txt @@ -0,0 +1,109 @@ +========================= +Getting data into Skyline +========================= + +You currently have two options to get data into Skyline, via the Horizon +service: + +A note on time snyc +=================== + +Although it may seems obvious, it is important to note that any metrics +coming into Graphite and Skyline should come from synchronised sources. +If there is more than 60 seconds (or highest resolution metric), certain +things in Skyline will start to become less predictable, in terms of the +functioning of certain algorithms which expect very recent datapoints. +Time drift does decrease the accuracy and effectiveness of some +algorithms. In terms of machine related metrics, normal production grade +time snychronisation will suffice. + +TCP pickles +=========== + +Horizon was designed to support a stream of pickles from the Graphite +carbon-relay service, over port 2024 by default. Carbon relay is a +feature of Graphite that immediately forwards all incoming metrics to +another Graphite instance, for redundancy. In order to access this +stream, you simply need to point the carbon relay service to the box +where Horizon is running. In this way, Carbon-relay just thinks it's +relaying to another Graphite instance. In reality, it's relaying to +Skyline. + +Here are example Carbon configuration snippets: + +relay-rules.conf: + +:: + + [all] + pattern = .* + destinations = 127.0.0.1:2014, :2024 + + [default] + default = true + destinations = 127.0.0.1:2014:a, :2024:a + +carbon.conf: + +:: + + [relay] + RELAY_METHOD = rules + DESTINATIONS = 127.0.0.1:2014, :2024 + USE_FLOW_CONTROL = False + MAX_QUEUE_SIZE = 5000 + +A quick note about the carbon agents: Carbon-relay is meant to be the +primary metrics listener. The 127.0.0.1 destinations in the settings +tell it to relay all metrics locally, to a carbon-cache instance that is +presumably running. If you are currently running carbon-cache as your +primary listener, you will need to switch it so carbon-relay is primary +listener. + +Note the small MAX\_QUEUE\_SIZE - in older versions of Graphite, issues +can arise when a relayed host goes down. The queue will fill up, and +then when the relayed host starts listening again, Carbon will attempt +to flush the entire queue. This can block the event loop and crash +Carbon. A small queue size prevents this behavior. + +See `the +docs `__ +for a primer on Carbon relay. + +Of course, you don't need Graphite to use this listener - as long as you +pack and pickle your data correctly (you'll need to look at the source +code for the exact protocol), you'll be able to stream to this listener. + +UDP messagepack +=============== + +Horizon also accepts metrics in the form of messagepack encoded strings +over UDP, on port 2025. The format is +``[, [, ]]``. Simply encode your metrics +as messagepack and send them on their way. + +However a quick note, on the transport any metrics data over UDP.... +sorry if did you not get that. + +Adding a Listener +================= + +If neither of these listeners are acceptable, it's easy enough to extend +them. Add a method in listen.py and add a line in the horizon-agent that +points to your new listener. + +:mod:`settings.FULL_DURATION` +============================= + +Once you get real data flowing through your system, the Analyzer will be +able start analyzing for anomalies. + +.. note:: Do not expect to see anomalies or anything in the Webapp immediately + after starting the Skyline services. Realistically :mod:`settings.FULL_DURATION` + should have been passed, before you begin to assess any triggered anomalies, + after all :mod:`settings.FULL_DURATION` is the baseline. Although not all + algorithms utilize all the :mod:`settings.FULL_DURATION` data points, some do + and some use only 1 hour's worth. However the Analyzer log should still report + values in the exception stats, reporting how many metrics were boring, too + short, etc as soon as it is getting data for metrics that Horizon is populating + into Redis. diff --git a/docs/_build/html/_sources/getting-started.txt b/docs/_build/html/_sources/getting-started.txt new file mode 100644 index 00000000..b21d5120 --- /dev/null +++ b/docs/_build/html/_sources/getting-started.txt @@ -0,0 +1,123 @@ +Getting started +=============== + +At some point **soon** hopefully it will be `pip install skyline` but for now +see `Installation`_ **after** reviewing the below + +.. _Installation: ../html/installation.html + +Not just a program +------------------ + +Anomaly detection is not easy. + +> "It seems difficult" + +It helps if you start out with realistic expectations. Anomaly detection takes +time and effort, it is not simply a case of... + +**install = "all singing and dancing anomaly detection system"** + +There is no off-the-shelf, panacea, anomaly detection system or framework that +is going to provide you with "WOW" without some effort, learning and tuning +your system to your needs. + +It is not simple, it is not perfect. It is difficult. Skyline configuration is +probably on par in complexity with other similar "things" such as Riemann or an +Elasticsearch cluster perhaps... probably less complex, but not easy initially +either. + +That said, there is probably less of a learning curve and, upfront setup and +configuration and than other machine learning and like projects such as NuPIC, +scikit-learn, theano, tensorflow, keras, etc and all the similar ilk. + +Hopefully Skyline will interface with some of the above in the not too distant +future - see `roadmap`_ + +.. _roadmap: ../html/roadmap.html + +Anomaly detection is a journey not an app +----------------------------------------- + +Anomaly detection is partly about automated anomaly detection and partly about +knowing your metrics and timeseries patterns. Not all timeseries are created +equally. + +It helps to think of anomaly detection as an ongoing journey. Although ideally +it would be great to be able to computationally detect anomalies with a high +degree of certainty, there is no getting away from the fact that the more +**you** learn, "help" and tune your anomaly detection, the better it will become. + +The fact that Skyline **does** computationally detect anomalies with a +high degree of certainty, can be a problem in itself. **But** it is not +Skyline's fault that: + +- a lot of your metrics **are** anomalous +- that feeding all your metrics to Skyline and setting alerting on all your + metric namespaces is too noisy to be generally considered useful + +Enabling Skyline modules incrementally +-------------------------------------- + +Skyline's modules do different things differently and understanding the process +and pipeline helps to tune each Skyline module appropriately for your data. + +Each analysis based module, Analyzer, Mirage, Boundary and Crucible, have their +own specific configurations. These configurations are not extremely complex, but +they are not obvious or trivial either when you are starting out. Bringing +Skyline modules online incrementally over time, helps one to understand the +processes and their different configuration settings easier. Easier than trying +to get the whole stack up and running straight off. + +Start with Horizon, Analyzer, Webapp and Panorama +------------------------------------------------- + +It is advisable to only start the original Horizon, Analyzer and Webapp daemons +initially and take time to understand what Skyline is doing. Take some time to +tune Analyzer's `ALERTS` and learn the patterns in your metrics: + +- which metrics trigger anomalies? +- when the metrics trigger anomalies? +- why/what known events are triggering anomalies? +- are there seasonalities/periodicity in anomalies some metrics? +- what metrics are critical and what metrics are just "normal"/expected noise + +Panorama will help you view what things are triggering as anomalous. + +Once you have got an idea of what you want to anomaly detect and more +importantly, on what and when you want to alert, you can start to define the +settings for other Skyline modules such as Mirage and Boundary and bring them +online too. + +Add Mirage parameters to the `ALERTS` +------------------------------------- + +Once you have an overview of metrics that have seasonalities that are greater +than the `FULL_DURATION`, you can add their Mirage parameters to the `ALERTS` +tuples and start the Mirage daemon. + +Add Boundary settings +--------------------- + +You will know what your **key** metrics are and you can define their acceptable +boundaries and alerting channels in the `BOUNDARY_METRICS` tuples and start the +Boundary daemon. + +You might want Crucible, you probably do not +-------------------------------------------- + +By default Crucible is enabled in the ``settings.py`` however, for other Skyline +modules to send Crucible data, Crucible has to be enabled via the appropriate +``settings.py`` variable for each module. + +Crucible has 2 roles: + +1. Store resources (timeseries json and graph pngs) for triggered anomalies - note + this can consume a lot of disk space if enabled. +2. Run ad-hoc analysis on any timeseries and create matplotlib plots for the + run algorithms. + +It is not advisable to enable Crucible on any of the other modules unless you +really want to "see" anomalies in great depth. Crucible is enabled as there is +a Crucible frontend view on the roadmap that will allow the user to test any +timeseries of any metric directly through the UI. diff --git a/docs/_build/html/_sources/horizon.txt b/docs/_build/html/_sources/horizon.txt new file mode 100644 index 00000000..efa0f5eb --- /dev/null +++ b/docs/_build/html/_sources/horizon.txt @@ -0,0 +1,28 @@ +## Horizon + +The Horizon service is responsible for collecting, cleaning, and formatting +incoming data. It consists of Listeners, Workers, and Roombas. Horizon can be +started, stopped, and restarted with the `bin/horizon.d` script. + +### Listeners + +Listeners are responsible for listening to incoming data. There are currently +two types: a TCP-pickle listener on port 2024, and a UDP-messagepack listener +on port 2025. The Listeners are easily extendible. Once they read a metric from +their respective sockets, they put it on a shared queue that the Workers read +from. For more on the Listeners, see [Getting Data Into Skyline](getting-data-into-skyline.html). + +### Workers + +The workers are responsible for processing metrics off the queue and inserting +them into Redis. They work by popping metrics off of the queue, encoding them +into Messagepack, and appending them onto the respective Redis key of the metric. + +### Roombas + +The Roombas are responsible for trimming and cleaning the data in Redis. You +will only have a finite amount of memory on your server, and so if you just let +the Workers append all day long, you would run out of memory. The Roomba cycles +through each metric in Redis and cuts it down so it is as long as +`settings.FULL_DURATION`. It also dedupes and purges old metrics. + diff --git a/docs/_build/html/_sources/index.txt b/docs/_build/html/_sources/index.txt new file mode 100644 index 00000000..aad2d2b7 --- /dev/null +++ b/docs/_build/html/_sources/index.txt @@ -0,0 +1,47 @@ +.. role:: skyblue +.. role:: red +.. role:: brow + +:skyblue:`Sky`:red:`line` documentation +======================================= + +Contents: + +.. toctree:: + :maxdepth: 1 + + overview + requirements + getting-started + running-in-python-virtualenv + installation + upgrading + getting-data-into-skyline + alert-testing + horizon + analyzer + analyzer-optimizations + mirage + boundary + crucible + panorama + webapp + redis-integration + skyline-and-friends + logging + tuning-tips + monitoring-skyline + debian-and-vagrant-installation-tips + building-documentation + releases + whats-new + development/index.rst + roadmap + skyline + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_build/html/_sources/installation.txt b/docs/_build/html/_sources/installation.txt new file mode 100644 index 00000000..0e033f51 --- /dev/null +++ b/docs/_build/html/_sources/installation.txt @@ -0,0 +1,247 @@ +============ +Installation +============ + +Intended audience +----------------- + +Skyline is not really a ``localhost`` application, it needs lots of data, unless +you have a ``localhost`` Graphite or pickle Graphite to your localhost. + +Given the specific nature of Skyline, it is assumed that the audience will have +a certain level of technical knowledge, e.g. it is assumed that the user will be +familiar with the installation, configuration, operation and security practices +and considerations relating to the following components: + +- Graphite +- Redis +- MySQL +- Apache + +This installation document is specifically related to the required installs and +configurations of things that are directly related Skyline. For notes regarding +automation and configuration management see the section at the end of this page. + +``sudo`` +~~~~~~~~ + +Use ``sudo`` appropriately for your environment wherever necessary. + +Steps +----- + +**NOTE**: all the documentation and testing is based on running Skyline in a +Python-2.7.12 virtualenv, if you choose to deploy Skyline another way, you are +on your own. Although it is possible to run Skyline in a different type of +environment, it does not lend itself to repeatability or a common known state. + +- Create a python-2.7.12 virtualenv for Skyline to run in see `Running in + Python virtualenv `__ +- Setup firewall rules to restrict access to the following: + + - :mod:`settings.WEBAPP_IP` - default is 127.0.0.1 + - :mod:`settings.WEBAPP_PORT` - default 1500 + - The IP address and port being used to reverse proxy the Webapp (if implementing) e.g. :8080 + - The IP address and port being used by MySQL (if implementing) + - The IP address and ports 2024 and 2025 + - The IP address and port being used by Redis + +- Install Redis - see `Redis.io `__ +- Ensure Redis has socket enabled **with the following permissions** in your + redis.conf + +:: + + unixsocket /tmp/redis.sock + unixsocketperm 777 + +- Start Redis +- Make the required directories + +.. code-block:: bash + + mkdir /var/log/skyline + mkdir /var/run/skyline + mkdir /var/dump/ + + mkdir -p /opt/skyline/panorama/check + mkdir -p /opt/skyline/mirage/check + mkdir -p /opt/skyline/crucible/check + mkdir -p /opt/skyline/crucible/data + mkdir /etc/skyline + mkdir /tmp/skyline + +- git clone Skyline + +.. code-block:: bash + + mkdir -p /opt/skyline/github + cd /opt/skyline/github + git clone https://github.com/earthgecko/skyline.git + +- Once again using the Python-2.7.12 virtualenv, install the requirements using + the virtualenv pip, this can take a long time, the pandas install takes quite + a while. + +.. code-block:: bash + + PYTHON_MAJOR_VERSION="2.7" + PYTHON_VIRTUALENV_DIR="/opt/python_virtualenv" + PROJECT="skyline-py2712" + + cd "${PYTHON_VIRTUALENV_DIR}/projects/${PROJECT}" + source bin/activate + + # Install the mysql-connector-python package first on its own as due to it + # having to be downloaded and installed from MySQL, if it is not installed + # an install -r will fail as pip cannot find mysql-connector-python + bin/"pip${PYTHON_MAJOR_VERSION}" install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-1.2.3.zip#md5=6d42998cfec6e85b902d4ffa5a35ce86 + + # The MySQL download source can now be commented it out of requirements.txt + # vi /opt/skyline/github/skyline/requirements.txt + + # This can take lots and lots of minutes... + bin/"pip${PYTHON_MAJOR_VERSION}" install -r /opt/skyline/github/skyline/requirements.txt + +- Copy the ``skyline.conf`` and edit the ``USE_PYTHON`` as appropriate to your + setup if it is not using PATH + ``/opt/python_virtualenv/projects/skyline-py2712/bin/python2.7`` + +.. code-block:: bash + + cp /opt/skyline/github/skyline/etc/skyline.conf /etc/skyline/skyline.conf + vi /etc/skyline/skyline.conf # Set USE_PYTHON as appropriate to your setup + +- OPTIONAL but **recommended**, serving the Webapp via gunicorn with an Apache + reverse proxy. + + - Setup Apache (httpd) and see the example configuration file in your cloned + directory ``/opt/skyline/github/skyline/etc/skyline.httpd.conf.d.example`` + modify all the ```__ + - Add a user and password for HTTP authentication, e.g. + +.. code-block:: bash + + htpasswd -c /etc/httpd/conf.d/.skyline_htpasswd admin + +.. note:: Ensure that the user and password for Apache match the user and + password that you provide in `settings.py` for + :mod:`settings.WEBAPP_AUTH_USER` and :mod:`settings.WEBAPP_AUTH_USER_PASSWORD` + +- Deploy your Skyline Apache configuration file and restart httpd. +- Create the Skyline MySQL database for Panorama (see + `Panorama `__). Although this is optional, it is + **recommended** as Panorama does give you a full historical view of all the + triggered anomalies, which is very useful for providing insight into what, how + and when metrics are triggering as anomalous. +- Edit the ``settings.py`` file and enter your appropriate settings, + specifically ensure you set the following variables to the correct + setting for your environment, see the documentation links and docstrings in + the `settings.py` file forthe full descriptions of each variable: + + - :mod:`settings.GRAPHITE_HOST` + - :mod:`settings.GRAPHITE_PROTOCOL` + - :mod:`settings.GRAPHITE_PORT` + - :mod:`settings.SERVER_METRICS_NAME` + - :mod:`settings.CANARY_METRIC` + - :mod:`settings.ALERTS` + - :mod:`settings.SMTP_OPTS` + - :mod:`settings.HIPCHAT_OPTS` and :mod:`settings.PAGERDUTY_OPTS` if to be + used, if so ensure that :mod:`settings.HIPCHAT_ENABLED` and + :mod:`settings.PAGERDUTY_ENABLED` are set to ``True`` + - If you are deploying with a Skyline MySQL Panorama DB straight away ensure + that :mod:`settings.PANORAMA_ENABLED` is set to ``True`` and set all the + other Panorama related variables as appropriate. + - :mod:`settings.WEBAPP_AUTH_USER` + - :mod:`settings.WEBAPP_AUTH_USER_PASSWORD` + - :mod:`settings.WEBAPP_ALLOWED_IPS` + +.. code-block:: bash + + cd /opt/skyline/github/skyline/skyline + vi settings.py + +- If you are **upgrading**, at this point return to the + `Upgrading `__ page. +- Before you test Skyline by seeding Redis with some test data, ensure + that you have configured the firewall/iptables with the appropriate restricted + access. +- Start the Skyline apps + +.. code-block:: bash + + /opt/skyline/github/skyline/bin/horizon.d start + /opt/skyline/github/skyline/bin/analyzer.d start + /opt/skyline/github/skyline/bin/webapp.d start + # And Panorama if you have setup in the DB at this stage + /opt/skyline/github/skyline/bin/panorama.d start + +- Check the log files to ensure things are running and there are no + errors. + +.. code-block:: bash + + tail /var/log/skyline/*.log + +- Seed Redis with some test data + +.. code-block:: bash + + cd "${PYTHON_VIRTUALENV_DIR}/projects/${PROJECT}" + source bin/activate + bin/python2.7 /opt/skyline/github/skyline/utils/seed_data.py + deactivate + +- Check the Skyline Webapp frontend on the Skyline machine's IP address and the + appropriate port depending whether you are serving it proxied or direct, e.g + ``http://YOUR_SKYLINE_IP:8080`` or ``http://YOUR_SKYLINE_IP:1500``. The + ``horizon.test.udp`` metric anomaly should be in the dashboard after the + seed\_data.py is complete. If Panorama is set up you will be able to see that + in the /panorama view and in the :red:`re`:brow:`brow` view as well. + +- Check the log files again to ensure things are running and there are + no errors. + +- This will ensure that the Horizon service is properly set up and can + receive data. For real data, you have some options relating to + getting a data pickle from Graphite see `Getting data into + Skyline `__ + +- Once you have your ALERTS configured to test them see + `Alert testing `__ + +- If you have opted to not setup Panorama, later see setup + `Panorama `__ + +- For Mirage setup see `Mirage `__ + +- For Boundary setup see `Boundary `__ + +Automation and configuration management notes +============================================= + +The installation of packages in the ``requirements.txt`` can take a long time, +specifically the pandas build. This will usually take longer than the default +timeouts in most configuration management. + +That said, ``requirements.txt`` can be run in an idempotent manner, **however** +a few things need to be highlighted: + +1. A first time execution of ``bin/"pip${PYTHON_MAJOR_VERSION}" install -r /opt/skyline/github/skyline/requirements.txt`` + will timeout on configuration management. Therefore consider running this + manually first. Once pip has installed all the packages, the + ``requirements.txt`` will run idempotent with no issue and be used to + upgrade via a configuration management run when the ``requirements.txt`` is + updated with any new versions of packages (with the possible exception of + pandas). It is obviously possible to provision each requirement individually + directly in configuration management and not use pip to ``install -r`` the + ``requirements.txt``, however remember the the virtualenv pip needs to be used + and pandas needs a LONG timeout value, which not all package classes provide, + if you use an exec of any sort, ensure the pandas install has a long timeout. + +2. The mysql-connector-python package is pulled directly from MySQL as no pip + version exists. Therefore during the build process it is recommended to pip + install the MySQL source package first and then the line out comment in + ``requirements.txt``. The ``mysql-connector-python==1.2.3`` line then ensures + the dependency is fulfilled. diff --git a/docs/_build/html/_sources/logging.txt b/docs/_build/html/_sources/logging.txt new file mode 100644 index 00000000..0aae4aa4 --- /dev/null +++ b/docs/_build/html/_sources/logging.txt @@ -0,0 +1,112 @@ +# Logging + +A few considerations to note about logging. + +1. Logging Python multiprocessing threads is not easy. +2. Rotating Python TimedRotatingFileHandler logs in not easy. +3. logrotate on Python multiprocessing threads is not easy. +4. Logging something that eats through as much data and does as many things as + Skyline does is not easy. + +Skyline logging can be viewed as having 2 contexts: + +1. Logging the Skyline application operations - Skyline app log +2. Logging the details of anomalous timeseries - syslog + +Any long time users of Skyline will have undoubtedly run into a logging pain +with Skyline at some point. Whether that be in terms of logs being overwritten +or no errors being logged but a Skyline module broken or hung. +Although logging is very mature, especially in an ecosystem as mature as Python +and one may be led to believe it should be easy, however in reality it is +non-trivial. + +## A modified logging methodology + +The logging implementation in Skyline is quite complicated due to the +abovementioned reasons. It is important to note that some optimisations have +had to be added to the logging process which could be described as unintuitive AND +simply as un-log like. + +Therefore it is important to explain why it is necessary to use a modified +logging methodology and so that the user understands conceptually what is being +logged and what is not being logged. + +In terms of Analyzer if errors are encountered in any algorithms, we sample the +errors. This achieves a balance between reporting errors useful and not using +lots of I/O and disk space if something goes wrong. Skyline has the potential +to do a runaway log, but not reporting errors is not useful when you are trying +to pinpoint what is wrong. + +However, it is right that algorithms should just True or False and not impact on +the performance or operation of Skyline in anyway. + +This achieves a balance. + +## Skyline app logs + +Each Skyline app has its own log. These logs are important from a Skyline +perspective in terms of monitoring the Skyline processes. +See [Monitoring Skyline]((monitoring-skyline.html)) + +It is recommended to NOT stream the Skyline app logs through any logging +pipeline e.g. rsyslog, logstash, elasticsearch, etc. The syslog alert trigger +was added for this purpose. If you wish to parse and rate anomalies do so via +syslog (see below). + +Log rotation is handled by the Python TimedRotatingFileHandler and by default +keeps the 5 last logs: + +``` + handler = logging.handlers.TimedRotatingFileHandler( + settings.LOG_PATH + '/' + skyline_app + '.log', + when="midnight", + interval=1, + backupCount=5) +``` + +The Skyline app logs and the rotation is relatively cheap on disk space even on +production machines handling 10s of 1000s of metrics each. + +``` +=============================================================== +HOST:skyline-prod-4-96g-luk1 EXITCODE:0 STDOUT: +=============================================================== +38M /var/log/skyline +=============================================================== +=============================================================== +HOST:skyline-prod-3-40g-ruk2 EXITCODE:0 STDOUT: +=============================================================== +22M /var/log/skyline +=============================================================== +=============================================================== +HOST:skyline-prod-2-96g-luk1 EXITCODE:0 STDOUT: +=============================================================== +52M /var/log/skyline +=============================================================== +``` + +## Skyline app log preservation + +It should be noted that the bin/ bash scripts are used to ensure logs are +preserved and not overwritten by any Python multiprocessing processes or the +lack of `mode='a'` in the Python TimedRotatingFileHandler. It is for this +reason that the Skyline app logs should not be streamed through a logging +pipeline as logstash, et al as this in a logging pipeline with say rsyslog can +result in the log being pushed multiple times due to the following scenario: + +- Skyline app bin is called to start +- Skyline app bin makes a last log from the Skyline app log +- Skyline Python app is started, creates log with `mode=w` +- Skyline Python app pauses and the app bin script contenates last log and new + log file +- Skyline bin script exits and Skyline Python app continues writing to the log + +In terms of rsyslog pipelining this would result in the log being fully +submitted again on every restart. + +## syslog + +With this in mind a syslog alert trigger was added to Skyline apps to handle the +logging the details of anomalous timeseries to syslog, groking the syslog for +Skyline logs and rates etc is the way to go. Bearing in mind that only +anomalies from metrics with set alert tuples are logged. diff --git a/docs/_build/html/_sources/mirage.txt b/docs/_build/html/_sources/mirage.txt new file mode 100644 index 00000000..ec634f9d --- /dev/null +++ b/docs/_build/html/_sources/mirage.txt @@ -0,0 +1,280 @@ +###### +Mirage +###### + +The Mirage service is responsible for analyzing selected timeseries at custom +time ranges when a timeseries seasonality does not fit within +:mod:`settings.FULL_DURATION`. + +The Mirage aap allows for second order resolution analysis of metrics that +have a ``SECOND_ORDER_RESOLUTION_HOURS`` defined in their Analyzer alert tuple +:mod:`settings.ALERTS` setting. + +Analyzer's :mod:`settings.FULL_DURATION` somewhat limits Analyzer's usefulness +for metrics that have a seasonality / periodicity that is greater than +:mod:`settings.FULL_DURATION`. Increasing :mod:`settings.FULL_DURATION` to +anything above 24 hours (86400) is not necessarily realistic or useful, because +the greater the :mod:`settings.FULL_DURATION`, the greater memory required for +Redis and the longer Skyline analyzer will take to run. + +Mirage uses the user-defined seasonality for a metric +(``SECOND_ORDER_RESOLUTION_HOURS``) and if Analyzer finds a metric to be +anomalous at :mod:`settings.FULL_DURATION` and the metric alert tuple has +`SECOND_ORDER_RESOLUTION_HOURS` and :mod:`settings.ENABLE_MIRAGE` is ``True``, +Analyzer will push the metric variables to the Mirage check file for Mirage to +surface the metric's timeseries at its defined seasonality, in real time from +Graphite in json format and then analyze the timeseries to determine if the +datapoint that triggered analyzer, is anomalous at the metric's true +seasonality. + +What Mirage can and cannot do +============================= + +It is important to know that Mirage is not necessarily suited to make highly +variable less noisy e.g. spikey metrics. + +Mirage is more useful on fairly constant rate metrics which contain known +or expected seasonalities. For example take a metric such as +office.energy.consumation.per.hour, this type of metric would most likely have +2 common seasonalities. + +As an example we can use the `Department of Education +`_ +Sanctuary Buildings energy consumption public `data set +`_ +to demonstrate how Mirage and Analyzer views are different. The energy +consumption in an office building is a good example of a multi-seasonal data set. + +* Office hour peaks +* Out of hour troughs +* Weekend troughs +* Holidays +* There could be summer and winter seasonality too + +For now let us just consider the daily and weekly seasonality. + +.. plot:: + + # A bit of a contrived example... + import numpy as np + import matplotlib.pyplot as plt + import matplotlib.dates as mdates + import matplotlib.patches as mpatches + import csv + import string + import os + import datetime as dt + import urllib2 + + # Department for Education real-time energy data: October 2015 + # https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/476574/Real_time_energy_data_October.csv + datafile = '../examples/data/Real_time_energy_data_October.csv' + if not os.path.exists(datafile): + datafile = '/tmp/Real_time_energy_data_October.csv' + url = 'https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/476574/Real_time_energy_data_October.csv' + response = urllib2.urlopen(url) + with open(datafile, 'w') as fw: + fw.write(response.read()) + + values = [] + with open(datafile, 'rb') as csvfile: + reader = csv.reader(csvfile, delimiter=',') + for row in reader: + values_row = ', '.join(row) + values_only_string = string.replace(values_row, ' ', '') + values_list = values_only_string.split(',') + values.append(values_list) + + hours = [] + current_index = 2 + for index, value in enumerate(values): + if value[1] == 'Day/Time': + while current_index < 50: + hours.append(value[current_index]) + current_index += 1 + two_weeks = '01/10/2015 02/10/2015 03/10/2015 04/10/2015 05/10/2015 06/10/2015 07/10/2015 08/10/2015 09/10/2015 10/10/2015 11/10/2015 12/10/2015 13/10/2015 14/10/2015' + data = [] + for index, value in enumerate(values): + # if value[0] != 'Site' and value[1] != '31/10/2015': + if value[1] in two_weeks: + current_index = 2 + current_hour = 0 + while current_index < 50: + date = '%s %s' % (value[1], hours[current_hour]) + line_data = [date, value[current_index]] + data.append(line_data) + current_index += 1 + current_hour += 1 + + tmp_datafile = '/tmp/skyline.docs.mirage.energy_data.csv' + if os.path.exists(tmp_datafile): + os.remove(tmp_datafile) + + for element in data: + ts_line = '%s, %.2f\n' % (element[0], float(element[1])) + with open(tmp_datafile, 'a') as fw: + fw.write(ts_line) + + hours, consumption = np.loadtxt( + tmp_datafile, unpack=True, + delimiter=',', + converters={0: mdates.strpdate2num('%d/%m/%Y %H:%M')}) + + if os.path.exists(tmp_datafile): + os.remove(tmp_datafile) + + fig = plt.figure(figsize=(14, 5)) + + x_anno1 = dt.datetime.strptime('02/10/2015 06:00', '%d/%m/%Y %H:%M') + x_anno2 = dt.datetime.strptime('03/10/2015 06:00', '%d/%m/%Y %H:%M') + plt.annotate( + 'Analyzer at 86400\nFULL_DURATION\nwould probably fire\naround here', + xy=(x_anno2, 130), xycoords='data', + xytext=(0.2, 0.5), textcoords='axes fraction', + arrowprops=dict(facecolor='red', shrink=0.01), + horizontalalignment='right', verticalalignment='top') + + plt.axvspan(x_anno1, x_anno2, alpha=0.4, color='pink') + analyzer_full_duration = mpatches.Patch(color='pink', label='Analyzer FULL_DURATION') + plt.legend(handles=[analyzer_full_duration]) + + x_anno3 = dt.datetime.strptime('09/10/2015 06:00', '%d/%m/%Y %H:%M') + x_anno4 = dt.datetime.strptime('10/10/2015 06:00', '%d/%m/%Y %H:%M') + plt.annotate( + 'Analyzer at 86400\nFULL_DURATION\nwould probably fire\naround here', + xy=(x_anno4, 130), xycoords='data', + xytext=(0.7, 0.5), textcoords='axes fraction', + arrowprops=dict(facecolor='red', shrink=0.01), + horizontalalignment='right', verticalalignment='top') + + plt.axvspan(x_anno3, x_anno4, alpha=0.4, color='pink') + + x_anno5 = dt.datetime.strptime('03/10/2015 06:00', '%d/%m/%Y %H:%M') + x_anno6 = dt.datetime.strptime('09/10/2015 06:00', '%d/%m/%Y %H:%M') + plt.axvspan(x_anno5, x_anno6, alpha=0.4, color='blue') + x_anno7 = dt.datetime.strptime('02/10/2015 04:00', '%d/%m/%Y %H:%M') + x_anno8 = dt.datetime.strptime('02/10/2015 06:00', '%d/%m/%Y %H:%M') + plt.axvspan(x_anno7, x_anno8, alpha=0.4, color='blue') + x_anno9 = dt.datetime.strptime('10/10/2015 06:00', '%d/%m/%Y %H:%M') + x_anno10 = dt.datetime.strptime('10/10/2015 08:00', '%d/%m/%Y %H:%M') + plt.axvspan(x_anno9, x_anno10, alpha=0.4, color='blue') + + mirage_full_duration = mpatches.Patch(color='blue', label='Mirage FULL_DURATION') + + plt.annotate( + '', xy=(x_anno7, 370), xycoords='data', + xytext=(x_anno10, 370), textcoords='data', + arrowprops={'arrowstyle': '<->'}) + plt.text(x_anno1, 375, 'Mirage FULL_DURATION period') + + plt.annotate( + '', xy=(x_anno2, 310), xycoords='data', + xytext=(x_anno1, 310), textcoords='data', + arrowprops={'arrowstyle': '<->'}) + plt.text(x_anno1, 310, 'Analyzer FULL_DURATION period') + + plt.annotate( + '', xy=(x_anno3, 310), xycoords='data', + xytext=(x_anno4, 310), textcoords='data', + arrowprops={'arrowstyle': '<->'}) + plt.text(x_anno3, 310, 'Analyzer FULL_DURATION period') + + plt.legend(handles=[analyzer_full_duration, mirage_full_duration]) + + plt.title('Department of Education Sanctuary Buildings - energy consumption\nAn example of Skyline Analyzer and Mirage data views') + plt.figtext(0.99, 0.01, 'Sample data from https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/476574/Real_time_energy_data_October.csv', horizontalalignment='right') + plt.plot_date(x=hours, y=consumption, markersize=1.3) + plt.gcf().autofmt_xdate() + + plt.show() + +`Fullsize image <_images/mirage-1.png>`_ for a clearer picture. + +As we can see above, on a Saturday morning the energy consumption does not +increase as it normally does during the week days. Analyzer would probably find +the metric to be anomalous if :mod:`settings.ANALYZER_CRUCIBLE_ENABLED` was set +to 86400 (24 hours), Saturday morning would seem anomalous. + +However, if the metric's alert tuple was set up with a +``SECOND_ORDER_RESOLUTION_HOURS`` of 168, Mirage would analyze the data point +against a week's worth of data points and the Saturday and Sunday daytime data +points would have less probability of triggering as anomalous. *The above +image is plotted as if the Mirage ``SECOND_ORDER_RESOLUTION_HOURS`` was set to +172 hours just so that the trailing edges can be seen.* + +Mirage is a "tuning" tool for seasonal metrics and it is important to understand +that Mirage is probably using aggregated data (unless your Graphite is not using +retentions and aggregating) and due to this Mirage will lose some resolution +resulting in it being less sensitive to anomalies than Analyzer is. + +Setting up Mirage +================= + +By default Mirage is disabled, various Mirage options can be configured in the +``settings.py`` file and Analyzer and Mirage can be configured as appropriate +for your environment. + +Mirage requires some directories as per ``settings.py`` defines (these require +absolute path): + +.. code-block:: bash + + sudo mkdir -p $MIRAGE_CHECK_PATH + sudo mkdir -p $MIRAGE_DATA_FOLDER + + +Configure ``settings.py`` with some alert tuples that have the +``SECOND_ORDER_RESOLUTION_HOURS`` defined, e.g.: + +.. code-block:: python + + ALERTS = ( + ("skyline", "smtp", 1800), + ("stats_counts.http.rpm.publishers.*", "smtp", 300, 168), + ) + +And ensure that ``settings.py`` has Mirage options enabled, specifically the +basic ones: + +.. code-block:: python + + ENABLE_MIRAGE = True + ENABLE_FULL_DURATION_ALERTS = False + MIRAGE_ENABLE_ALERTS = True + +Start Mirage: + +.. code-block:: bash + + cd skyline/bin + sudo ./mirage.d start + + +Mirage allows for testing of real time data and algorithms in parallel to +Analyzer allowing for comparisons of different timeseries and/or algorithms. +Mirage was inspired by Crucible and the desire to extend the temporal data pools +available to Analyzer in an attempt to handle seasonality better, reduce noise +and increase signal, specifically on seasonal metrics. + +Mirage is rate limited to analyze 30 metrics per minute, this is by design and +desired. Surfacing data from Graphite and analyzing ~1000 data points in a +timeseries takes less than 1 second and is much less CPU intensive than +Analyzer in general, but it is probably sufficient to have 30 calls to Graphite +per minute. If a large number of metrics went anomalous, even with Mirage +discarding :mod:`settings.MIRAGE_STALE_SECONDS` checks due to processing limit, +signals would still be sent. + +What Mirage does +================ + +- Mirage watches for added check files. +- When a check is found, Mirage determines what the configured + ``SECOND_ORDER_RESOLUTION_HOURS`` is for the metric from the tuple in + :mod:`settings.ALERTS` +- Mirage queries graphite to surface the json data for the metric timeseries at + ``SECOND_ORDER_RESOLUTION_HOURS``. +- Mirage then analyses the retrieved metric timeseries against the configured + :mod:`settings.MIRAGE_ALGORITHMS`. +- If the metric is anomalous over ``SECOND_ORDER_RESOLUTION_HOURS`` then alerts + via the configured alerters for the matching metric :mod:`settings.ALERT` + tuple and sets the metric alert key for ``EXPIRATION_TIME`` seconds. diff --git a/docs/_build/html/_sources/modules.txt b/docs/_build/html/_sources/modules.txt new file mode 100644 index 00000000..ea946bab --- /dev/null +++ b/docs/_build/html/_sources/modules.txt @@ -0,0 +1,7 @@ +skyline +======= + +.. toctree:: + :maxdepth: 4 + + skyline diff --git a/docs/_build/html/_sources/monitoring-skyline.txt b/docs/_build/html/_sources/monitoring-skyline.txt new file mode 100644 index 00000000..709e8625 --- /dev/null +++ b/docs/_build/html/_sources/monitoring-skyline.txt @@ -0,0 +1,33 @@ +# Monitoring Skyline + +It should be noted that something should monitor the Skyline processes. +Over a long enough timeline Python or Redis or some I/O issue is going to lock +up and the Python process is just going to hang. This means that it is not +really sufficient to just monitor the process with something like monit. + +## KISS + +### Skyline logs + +Each Skyline app is expected to log at a certain intervals, by design for this +very purpose. So if a Skyline app has not written to its log in 120 seconds, it +has hung. So some simple bash scripts can be crond to restart the application +if the log has not been written to in 120 seconds. + +This very simple mechanism solves a very difficult problem to solve within +Python itself. It works. + +### The Graphite pickle + +The same can be said for the pickle from Graphite. The same simple methodology +can be employed on Graphite too, a simple script to determine the number of +carbon fullQueueDrops to the destinations metric for your skyline node/s e.g. +`carbon.relays.skyline-host-a.destinations.123_234_213_123:2024:None.fullQueueDrops` +If the number of drops is greater than x, restart carbon-cache or just the +carbon-relay if you have rcarbon-relay enabled, which you should have :) + +A further advantage having carbon-relay enabled independently of carbon-cache +is that implementing a monitor script on your `carbon.relays.*.fullQueueDrops` +metrics means that if carbon-cache itself has a pickle issue, it should be +resolved by a carbon-relay restart, just like Skyline Horizon is fixed by its +monitor. diff --git a/docs/_build/html/_sources/overview.txt b/docs/_build/html/_sources/overview.txt new file mode 100644 index 00000000..3d276ffe --- /dev/null +++ b/docs/_build/html/_sources/overview.txt @@ -0,0 +1,110 @@ +.. role:: skyblue +.. role:: red +.. role:: brow + +Overview +======== + +A brief history +--------------- + +Skyline was originally open sourced by `Etsy`_ as a real-time anomaly detection +system. It was originally built to enable passive monitoring of hundreds of +thousands of metrics, without the need to configure a model/thresholds for each +one, as you might do with Nagios. It was designed to be used wherever there are +a large quantity of high-resolution timeseries which need constant monitoring. +Once a metric stream was set up (from statsd, graphite or other), additional +metrics are automatically added to Skyline for analysis, anomaly detection, +alerting and briefly publishing in the Webapp frontend. `github/etsy`_ stopped +actively maintaining Skyline in 2014. + +Skyline - as a work in progress +------------------------------- + +`Etsy`_ found the "one size fits all approach" to anomaly detection wasn't +actually proving all that useful to them. + +There is some truth in that in terms of the one size fits all methodology that +Skyline was framed around. With hundreds of thousands of metrics this does make +Skyline fairly hard to tame, in terms of how useful it is and tuning the noise +is difficult. Tuning the noise to make it constantly useful and not just noisy, +removes the "without the need to configure a model/thresholds" element somewhat. + +So why continue developing Skyline? + +Because it works and because it is Python. Being Python based allows Skyline to +integrate with a lot of other awesome things in the machine learning and +scientific Python ecosystem. It's modularity makes it is very flexible. + +With some time and tuning, Skyline works! + +The architecture/pipeline works very well at doing what it does, it is based on +solid, battle tested components, Python and Redis. + +The new look Skyline apps +------------------------- + +* Horizon - feed metrics to Redis via a pickle input +* Analyzer - analyze metrics +* Mirage - analyze specific metrics at a custom time range +* Boundary - analyze specific timeseries for specific conditions +* Crucible - store anomalous timeseries resources and ad-hoc analysis of any + timeseries +* Panorama - anomalies database and historical views +* Webapp - frontend to view current and histroical anomalies and browse Redis + with :red:`re`:brow:`brow` + +Skyline is still a near real-time anomaly detection system, however it has +various modes of operation that are modular and self contained, so that only the +desired apps need to be enabled. + +Skyline can now be feed/query and analyze timeseries on an ad-hoc basis, on the +fly. This means Skyline can now be used to analyze and process static data too, +it is no longer just a machine/app metric fed system. + +What's new +---------- + +See `whats-new `__ for a comprehensive overview and description +of the latest version/s of Skyline. + +What's old +---------- + +It must be stated the original core of Skyline has not been altered in any way, +other than some fairly minimal Pythonic performance improvements, a bit of +optimization in terms of the logic used to reach :mod:`settings.CONSENSUS` and a +package restructure. In terms of the original Skyline Analyzer, it does the +same things just a little differently, hopefully better and a bit more. + +There is little point in trying to improve something as simple and elegant in +methodology and design as Skyline, which has worked so outstandingly well to +date. This is a testament to a number of things, in fact the sum of all it's +parts, `Etsy`_, Abe and co. did a great job in the conceptual design, +methodology and actual implementation of Skyline and they did it with very good +building blocks from the scientific community. + +The architecture in a nutshell +------------------------------ +Skyline uses to following technologies and libraries at its core: + +1. **Python** - the main skyline application language - `Python`_ +2. **Redis** - `Redis`_ an in-memory data structure store +3. **numpy** - `NumPy`_ is the fundamental package for scientific computing with Python +4. **scipy** - `SciPy`_ Library - Fundamental library for scientific computing +5. **pandas** - `pandas`_ - Python Data Analysis Library +6. **mysql/mariadb** - a database - `MySQL`_ or `MariaDB`_ +7. **:red:`re`:brow:`brow`** - Skyline uses a modified port of Marian + Steinbach's excellent `rebrow`_ + +.. _Etsy: https://www.etsy.com/ +.. _github/etsy: https://github.com/etsy/skyline +.. _whats-new: ../html/whats-new.html +.. _Python: https://www.python.org/ +.. _Redis: http://Redis.io/ +.. _NumPy: http://www.numpy.org/ +.. _SciPy: http://scipy.org/ +.. _pandas: http://pandas.pydata.org/ +.. _MySQL: https://www.mysql.com/ +.. _rebrow: https://github.com/marians/rebrow +.. _MariaDB: https://mariadb.org/ diff --git a/docs/_build/html/_sources/panorama.txt b/docs/_build/html/_sources/panorama.txt new file mode 100644 index 00000000..80e04653 --- /dev/null +++ b/docs/_build/html/_sources/panorama.txt @@ -0,0 +1,32 @@ +Panorama +======== + +The Panorama service is responsible for recording the metadata for each anomaly. + +It is important to remember that the Skyline analysis apps only alert on metrics +that have alert tuples set. Panorama records samples of all metrics that are +flagged as anomalous. Sampling at :mod:`settings.PANORAMA_EXPIRY_TIME`, the +default is 900 seconds. + +There is a Panorama view in the Skyline Webapp frontend UI to allow you to +search and view historical anomalies. + +Create a MySQL database +----------------------- + +You can install and run MySQL on the Skyline server or use any existing MySQL +server to create the database on. The Skyline server just has to be able to +access the database with the user and password you configure in ``settings.py`` +:mod:`settings.PANORAMA_DBUSER` and :mod:`settings.PANORAMA_DBUSERPASS` + +- See ``skyline.sql`` in your cloned Skyline repo for the schema creation script +- Enable Panorama and set the other Panorama settings in ``settings.py`` +- Start Panorama (use you appropriate PATH) - or go back to `Installation`_ and + continue with the installation steps and Panorama will be started later in the + installation process. + +.. code-block:: bash + + /opt/skyline/github/skyline/bin/panorama.d start + +.. _Installation: ../html/installation.html diff --git a/docs/_build/html/_sources/redis-integration.txt b/docs/_build/html/_sources/redis-integration.txt new file mode 100644 index 00000000..1f307cb3 --- /dev/null +++ b/docs/_build/html/_sources/redis-integration.txt @@ -0,0 +1,29 @@ +## Redis integration + +Part of the Horizon service's job is to input data into Redis. + +### How are timeseries stored in Redis? + +Skyline uses MessagePack to store data in Redis. When a data point comes in, a +Horizon worker will pack the datapoint with the schema [timestamp, value] into a +MessagePack-encoded binary string and make a redis.append() call to append this +string to the appropriate metric key. This way, we can make very easily make +many updates to Redis at once, and this is how Skyline is able to support a very +large firehose of metrics. + +One downside to this scheme is that once timeseries in Redis start to get very +long, Redis' performance suffers. Redis was not designed to contain very large +strings. We may one day switch to an alternative storage design as proposed by +Antirez - see https://github.com/antirez/redis-timeseries - but for now, this is +how it is. + +In addition to all the metrics, there are two specialized Redis sets: +'metrics.unique_metrics' and 'mini.unique_metrics.' These contain every key that +exists in Redis, and they are used by the Analyzer to make it easier for the +Analyzer to know what to mget from Redis (as opposed to doing a very expensive +keys * query) + +### What's with the 'mini' and 'metrics' namespaces? +The 'mini' namespace currently has two uses: it helps performance-wise for +Oculus, if you choose to use it, and it also is used by the webapp to display a +`settings.MINI_DURATION` seconds long view. diff --git a/docs/_build/html/_sources/releases.txt b/docs/_build/html/_sources/releases.txt new file mode 100644 index 00000000..dee7d610 --- /dev/null +++ b/docs/_build/html/_sources/releases.txt @@ -0,0 +1,8 @@ +Release Notes +============= + +.. toctree:: + :maxdepth: 1 + :glob: + + releases/1_0_0 diff --git a/docs/_build/html/_sources/releases/1_0_0.txt b/docs/_build/html/_sources/releases/1_0_0.txt new file mode 100644 index 00000000..f4b09864 --- /dev/null +++ b/docs/_build/html/_sources/releases/1_0_0.txt @@ -0,0 +1,107 @@ +=========================== +1.0.0 - the crucible branch +=========================== + +Including sphinx, setuptools and panorama branches + +This release contains a fair amount of changes and additions: + +- The addition of the crucible module into Skyline +- The addition of sphinx-apidoc documentation +- A new package layout to accommodate python-setuptools and + sphinx-apidoc +- A docs, Panorama and rebrow added to Webapp + +These changes are backwards compatible in so much as an existing Skyline +directory and file structure could be replaced by this release and a +valid settings.py being deployed, whether the settings.py was the old +format or the new format, the services for which the settings.py is +configured will start. In theory. However there is so much change in +this branch it probably cannot be stated with 100% certainty that +something may not work as expected. Therefore, to be safe and +transparent, lets say it is backwards incompatible. See `Whats +new <../whats-new.html>`__ for a full rundown since the last Etsy +commit. + +Crucible +~~~~~~~~ + +The release adds the integration of crucible to allow for the storing of +anomalous timeseries and related metadata. With the goal being able to +produce anomaly analysis resources on the fly and add ad-hoc timeseries +for crucible analysis. + +See `Crucible <../crucible.html>`__ + +sphinx documentation +~~~~~~~~~~~~~~~~~~~~ + +The code has been documented with sphinx-apidoc, which was dependent on +changes to the package structure, see below. + +The docs are served via the webapp at http://:/static/docs or from your +local repo in your browser at file:///docs/\_build/html/index.html + +setuptools +~~~~~~~~~~ + +A package restructure was undertaken to make skyline a more traditional +python package. Much of these changes were based on merging some of +@languitar changes for setuptools and more pythonic structure that was +submitted as: + +Provide a setuptools-based build infrastructure #93 - etsy/#91 + +Webapp +~~~~~~ + +The docs are now served by the Webapp and a menu referencing the +different available endpoints in Webapp was added. + +- Basic security restrictions added. +- gunicorn +- New Panorama endpoint +- New rebrow enadpoint + +Performance tuning +~~~~~~~~~~~~~~~~~~ + +See `Analyzer Optimizations <../analyzer-optimizations.html>`__ + +Analyzer optimizations +~~~~~~~~~~~~~~~~~~~~~~ + +See `Analyzer Optimizations <../analyzer-optimizations.html>`__ + +algorithm\_breakdown metrics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See `Analyzer Optimizations <../analyzer-optimizations.html>`__ + +Improved log handling +~~~~~~~~~~~~~~~~~~~~~ + +See `Logging <../logging.html>`__ + +Panorama +~~~~~~~~ + +See `Panorama <../panorama.html>`__ + +Known issues in this release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently sphinx-apidoc is being used to documentation the code, however +to get working docs, a small modification is required to the files +outputted by sphinx-apidoc. + +This is related to the package restructure undertaken to make the +Skyline code and layout more in line with a normal python package. +Although this seems to have been achieved, the small hack to the +sphinx-apidoc output suggests that this is not 100% correct. Further +evidence of this is in terms of importing from settings.py, the path +needs to be appended in the code, which really should not be required. +However, it is working and in the future this should be figured and +fixed. + +See `Building Documentation <../building-documentation.html>`__ diff --git a/docs/_build/html/_sources/requirements.txt b/docs/_build/html/_sources/requirements.txt new file mode 100644 index 00000000..daa55194 --- /dev/null +++ b/docs/_build/html/_sources/requirements.txt @@ -0,0 +1,135 @@ +============ +Requirements +============ + +The ideal requirements are: + +- Linux (and probably any environment that supports Python virtualenv + and bash) +- virtualenv +- Python-2.7.11 or Python-2.7.12 (running in an isolated vitualenv) +- Redis +- MySQL or mariadb [optional - required for Panorama] +- A Graphite implementation sending data would help :) + +``requirements.txt`` +#################### + +The ``requirements.txt`` file lists the required packages and the last +verified as working version with Python-2.7 within a virtaulenv. + +Recent changes in the pip environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The new pip, setuptools, distribute and virtualenv methodology that the +pypa community has adopted is in a bit of a state of flux in terms of +the setuptools version and virtualenv implementation of making the +``--distribute`` and ``--setuptools`` legacy parameters has caused pip +8.1.1 and setuptools 20.9.0 and virtualenv 15.0.1 to complain if +distribute is one of the requirements on packages that pip tries to +install. However, it is needed for Mirage if you are trying to +run/upgrade on Python-2.6 + +pandas +====== + +The ``PANDAS_VERSION`` variable was added to settings.py to handle +backwards compatability with any instances of Skyline that are run older +versions that perhaps cannot upgrade to a later version due to any +mainstream packaging restrictions. This ``PANDAS_VERSION`` is required +to run the applicable panda functions dependent on the version that is +use. + +- pandas 0.17.0 deprecated the pandas.Series.iget in favour of + ``.iloc[i]`` or ``.iat[i]`` +- pandas 0.18.0 introduced a change in the Exponentially-weighted + moving average function used in a number of algorithms + +.. code-block:: python + + if PANDAS_VERSION < '0.18.0': + expAverage = pandas.stats.moments.ewma(series, com=15) + else: + expAverage = pandas.Series.ewm(series, ignore_na=False, min_periods=0, adjust=True, com=15).mean() + + if PANDAS_VERSION < '0.17.0': + return abs(series.iget(-1)) > 3 * stdDev + else: + return abs(series.iat[-1]) > 3 * stdDev + +Skyline should be able to run on pandas versions 0.12.0 - 0.18.0 (or +later) + +Python-2.6 +========== + +Skyline can still run on Python-2.6. However deploying Skyline on +Python-2.6 requires jumping through some hoops. This is because of the +dependencies and pip packages moving a lot. At some point some older pip +package is not going to be available any longer and it will no longer be +possible, unless you are packaging the pip packages into your own packages, e.g. +with fpm or such. + +RedHat family 6.x +================= + +If you are locked into mainstream versions of things and cannot run +Skyline in an isolated virtualenv, but have to use the system Python and +pip. The following pip installs and versions are known to working, with +a caveat on the scipy needs to be installed via yum NOT pip and you need +to do the following: + +.. code-block:: bash + + pip install numpy==1.8.0 + yum install scipy + pip install --force-reinstall --ignore-installed numpy==1.8.0 + +Known working scipy rpm - scipy-0.7.2-8.el6.x86\_64 + +pip packages (these are the requirements and their dependencies), it is +possible that some of these pip packages will no longer exist or may not +exist in the future, this is documented here for info ONLY. + +:: + + argparse (1.2.1) + backports.ssl-match-hostname (3.4.0.2) + cycler (0.9.0) + distribute (0.7.3) + Flask (0.9) + hiredis (0.1.1) + iniparse (0.3.1) + iotop (0.3.2) + Jinja2 (2.7.2) + lockfile (0.9.1) + MarkupSafe (0.23) + matplotlib (1.5.0) + mock (1.0.1) + msgpack-python (0.4.2) + nose (0.10.4) + numpy (1.7.0) + ordereddict (1.2) + pandas (0.12.0) + patsy (0.2.1) + pip (1.5.4) + pycurl (7.19.0) + pygerduty (0.29.1) + pygpgme (0.1) + pyparsing (1.5.6) + python-daemon (1.6) + python-dateutil (2.3) + python-simple-hipchat (0.3.3) + pytz (2014.4) + redis (2.7.2) + requests (1.1.0) + scipy (0.7.2) + setuptools (11.3.1) + simplejson (2.0.9) + six (1.6.1) + statsmodels (0.5.0) + tornado (2.2.1) + unittest2 (0.5.1) + urlgrabber (3.9.1) + Werkzeug (0.9.4) + yum-metadata-parser (1.1.2) diff --git a/docs/_build/html/_sources/roadmap.txt b/docs/_build/html/_sources/roadmap.txt new file mode 100644 index 00000000..c67b84d3 --- /dev/null +++ b/docs/_build/html/_sources/roadmap.txt @@ -0,0 +1,167 @@ +======= +Roadmap +======= + +Things on the horizon :) + +This is not really a roadmap per se it is more just a collection of +ideas at the moment, in no particular order. + +Further performance improvements and Python-3.5 +=============================================== + +Further performance improvements where possible with the use of cython. +pypy is unfortunately not an overall viable route due to no real support +for scipy and limited numpy support. pypy would be a quick win if it +were possible, so cython where applicable is the obvious choice. + +Continue updating the code base to ensure that everything can be run on +>= Python-3.5.x + +Ionosphere +========== + +Functionality to allow the operator to flag false positives, with a view +to using machine learning to train Skyline on anomalies. Using the entire data +set for training it, perhaps even using an entire namespace to increase the +accuracy of the anomaly detection via multiple avenues of analysis. + +Meteor +====== + +Add the ability to inject data into a data set and test a metric, the workflow, +algorithms, alerters and the apps themselves,etc. + +Constellations +============== + +A pure Python implementation of the Oculus functionality, but not +necessarily exactly the same, but similar. Calculating in realtime using +the redis data and fingerprinting the slope/gradient/alphabet of the +last x datapoints of each timeseries on the fly and correlating / +finding similarities in the timeseries in terms of patterns. Perhaps +some inplementation of Cosine Similarity could be used. A pure Python +implementation of Oculus functionality within Skyline would remove the +requirement for the additional overheads of ruby and Elasticsearch. + +This would allow for the correlations to be determined for any metrics +at any point within the ``FULL_DURATION`` period. + +Skyline and NASA/FITS data +========================== + +There is a Skyline module in development that is specifically designed +for analysing Kepler FITS timeseries data. Although it specifically +focus at extracting lightcurve data from the Kepler llc.fits data files, +that in itself has added the functionality to convert any relevant FITS +based data file that has a timeseries element in it. This should extend +to other data types that the FITS data format (Flexible Image Transport +System) handles: + +- Much more than just another image format (such as JPEG or GIF) +- Used for the transport, analysis, and archival storage of scientific + data sets + +- Multi-dimensional arrays: 1D spectra, 2D images, 3D+ data cubes +- Tables containing rows and columns of information +- Header keywords provide descriptive information about the data + +It should be possible for Skyline to be able to ingest any FITS data +file or ingest an image file from a FITS file and feed it to scikit-image, +etc. + +NuPIC +===== + +Last year `NuPIC `__ was assessed in terms of +use in Internet advertising for an additional monitoring dimension. Although +the assessment was fairly brief, it's predictive capabilities were quite +impressive. Working with a 14 month data set for one advertising service it was +able to predict the service's ad\_requests per 10 minutes for the 14 month +period remarkably well given the nature of the specific domain. The result was +a little peaky, with a few large peaks. Evaluation of the event streams from the +timeframes around the large peaks in question, identified known causes for +those peaks, with a "noise filter" applied to ``exclude > x`` to reduce +those now known few noisy events and the result were indeed remarkable. In fact +NuPIC predicted our requests for 14 months within the bounds of ~5% error margin +and when it made and error, it learned. + +Truly remarkable given this is adtech data, 6.5 million request per +minute peaks, low troughs with sparse seasonalities (if there is such +a thing) at times. NuPIC and the Cortical Learning Algorithm are really +amazing, if not a little difficult upfront. + +NuPIC predictions and Graphite timeseries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Feeding NuPIC prediction data back into Graphite helps our own Neocortex +to visually analyse the results too (data courtesy of +`Coull `__). + +Original requests timeseries +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. figure:: images/radar.real.14.month.requests.png + :alt: Original requests timeseries data + + Original requests timeseries data + +NuPIC predicted timeseries +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. figure:: images/nupic.radar.predicted.14.month.requests.png + :alt: NuPIC predicted data + + NuPIC predicted data + +percentage error - positive and negative +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- positive - under predicted (we did more than predicted) +- negative - over predicted (we did less than predicted) + +asPercent(diffSeries(stats\_counts.radar.varnish.rpm.total,stats.nupic.predictions.radar.varnish.rpm.total),stats\_counts.radar.varnish.rpm.total) + +Drop the noisy for an average representation + +.. figure:: images/nupic.radar.real.predicted.difference.14.month.requests.png + :alt: NuPIC percentage error + + NuPIC percentage error + +Overlaid +^^^^^^^^ + +Real data, NuPIC predictions and percentage error (on the 2nd unticked y +axis as above) + +.. figure:: images/nupic.radar.real.predicted.difference.14.month.requests.overlayed.png + :alt: Real data, NuPIC predictions and percentage error + + Real data, NuPIC predictions and percentage error + +Quite amazing. It a not beyond the realms of possibility to have a Horizon +feeding specific metrics to various NuPIC HTM Cortical Learning +Algorithms models... + +Update the NAB Scoreboard +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Look at the automated running of the Numenta Anomaly Benchmark (NAB) +data and frequently determine the Standard Profile, Reward Low FP and +Reward Low FN scores (metrics). This will only aid and improve the +evaluation of any additional algorithms, methods or techniques that are +added or applied to Skyline in the future, e.g: + +- Does Mirage change the score? +- Does Boundary? +- Would the addition of pyculiarity as an "algorithm"? + (https://github.com/nicolasmiller/pyculiarity) + +Automated NAB benchmark metrics would be a nice thing to have :) + +Machine learning +================ + +Bring additional dimensions of machine learning capabilities into Skyline, too +many avenues to mention... diff --git a/docs/_build/html/_sources/running-in-python-virtualenv.txt b/docs/_build/html/_sources/running-in-python-virtualenv.txt new file mode 100644 index 00000000..1d320878 --- /dev/null +++ b/docs/_build/html/_sources/running-in-python-virtualenv.txt @@ -0,0 +1,111 @@ +====================================== +Running Skyline in a Python virtualenv +====================================== + +Running Skyline in a Python virtualenv is the recommended way to run +Skyline. This allows for Skyline apps and all dependencies to be +isolated from the system Python version and packages and allows Skyline +be be run with a specific version of Python and as importantly specific +versions of the dependencies packages. + +The possible overhead of adding Python virtualenv functionality to any +configuration management is probably worth the effort in the long run. + +``sudo`` +~~~~~~~~ + +Use ``sudo`` appropriately for your environment wherever necessary. + +HOWTO Python virtualenv Skyline +=============================== + +Dependencies +~~~~~~~~~~~~ + +Building Python versions from the Python sources in Python virtualenv +requires the following system dependencies: + +- RedHat family + +.. code-block:: bash + + yum -y install autoconf zlib-devel openssl-devel sqlite-devel bzip2-devel python-pip + +- Debian family + +.. code-block:: bash + + apt-get -y install autoconf zlib1g-dev libssl-dev libsqlite3-dev lib64bz2-dev python-pip + +virtualenv +~~~~~~~~~~ + +Regardless of your OS as long as you have pip installed you can install +virtualenv. *NOTE:* if you are using a version of Python virtualenv +already, this may not suit your setup. + +virtualenv must be >= 15.0.1 due to some recent changes in the pip and +setuptools, see *Recent changes in the pip environment* section in +`Requirements `__ +for more details. + +.. code-block:: bash + + pip install 'virtualenv>=15.0.1' + +Python version +~~~~~~~~~~~~~~ + +Below we use the PATH /opt/python\_virtualenv, which you can substitute +with any path you choose and we are going to use python-2.7.12: + +.. code-block:: bash + + PYTHON_VERSION="2.7.12" + PYTHON_MAJOR_VERSION="2.7" + PYTHON_VIRTUALENV_DIR="/opt/python_virtualenv" + PR0JECT="skyline-py2712" + + mkdir -p "${PYTHON_VIRTUALENV_DIR}/versions/${PYTHON_VERSION}" + mkdir -p "${PYTHON_VIRTUALENV_DIR}/projects" + + cd "${PYTHON_VIRTUALENV_DIR}/versions/${PYTHON_VERSION}" + wget -q "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" + tar -zxvf "Python-${PYTHON_VERSION}.tgz" + + cd ${PYTHON_VIRTUALENV_DIR}/versions/${PYTHON_VERSION}/Python-${PYTHON_VERSION} + ./configure --prefix=${PYTHON_VIRTUALENV_DIR}/versions/${PYTHON_VERSION} + make + make altinstall + +You will now have a Python-2.7.12 environment with the Python +executable: ``/opt/python_virtualenv/versions/2.7.12/bin/python2.7`` + +Create a Skyline Python virtualenv +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A word of warning relating to pip, setuptools and distribute if you have +opted not to use the above as you have Python virtualenv already. As of +virtualenv 15.0.1 the pip community adopted the new pkg\_resources +\_markerlib package structure, which means the following in the +virtualenv context: + +- distribute cannot be installed +- pip must be >=8.1.0 +- setuptools must be >=20.2.2 + +Once again using Python-2.7.12: + +.. code-block:: bash + + PYTHON_VERSION="2.7.12" + PYTHON_MAJOR_VERSION="2.7" + PYTHON_VIRTUALENV_DIR="/opt/python_virtualenv" + PROJECT="skyline-py2712" + + cd "${PYTHON_VIRTUALENV_DIR}/projects" + virtualenv --python="${PYTHON_VIRTUALENV_DIR}/versions/${PYTHON_VERSION}/python${PYTHON_MAJOR_VERSION}" "$PROJECT" + + +Make sure to add the ``/etc/skyline/skyline.conf`` file - see +`Installation `__ diff --git a/docs/_build/html/_sources/skyline-and-friends.txt b/docs/_build/html/_sources/skyline-and-friends.txt new file mode 100644 index 00000000..6af7de49 --- /dev/null +++ b/docs/_build/html/_sources/skyline-and-friends.txt @@ -0,0 +1,63 @@ +# Skyline and friends + +Skyline has some close relationships to a number of metric pipelining things. + +## Graphite - a close relationship + +Anyone having used Skyline may have wondered in the past why Skyline sent metrics +to [Graphite](https://github.com/graphite-project). One may have also wondered +why there was never a Statsd option, why just Graphite? + +It seems natural that Etsy might have had Skyline feed it metrics to Statsd as +an option at least. However, there never was a `STATSD_HOST` setting and this +is quite fortunate. + +The relationship between Graphite and Skyline is very close, as in they can +monitor each other through a direct feedback loop or interaction. If Statsd +was ever an option, it would add a degree of separation between the 2 which is +not required or desirable, although it would work. + +### Feedback loop + +Skyline's own metrics really are an important aspect of Skyline's operations +over time, in terms of: + +- monitoring Skyline +- monitoring performance in terms of: + + - Skyline's own running times, load, algorithm performance, etc + - being able monitoring the overall performance of your "things" over time + +- To the new user these things may seem like uninteresting, probably never to be + looked much metrics, however over time they will describe your ups and downs, + your highs and lows and hopefully add to your understanding of your "things" + +## Statsd + +[Statsd](https://github.com/etsy/stats) feeds graphite so it is quite handy. + +## BuckyServer + +Before tcp transport was added to Statsd was +[BuckyServer](https://github.com/HubSpot/BuckyServer) for long haul TCP +transport of your metrics to local Statsd -> Graphite. + +## Sensu + +[Sensu](https://sensuapp.org/) can feed Graphite - +[Sensu on github](https://github.com/sensu/sensu) + +## Riemann + +[Riemann.io](http://riemann.io) can feed Graphite - +[Riemann on github](https://github.com/riemann/riemann) + +## Logstash + +[Logstash](https://www.elastic.co/products/logstash) can feed Graphite - +[Logstash on github](https://github.com/elastic/logstash) + +## Many more + +There are a great deal of apps that can feed Skyline, this just just to mention +a few. diff --git a/docs/_build/html/_sources/skyline.analyzer.txt b/docs/_build/html/_sources/skyline.analyzer.txt new file mode 100644 index 00000000..d37e5057 --- /dev/null +++ b/docs/_build/html/_sources/skyline.analyzer.txt @@ -0,0 +1,46 @@ +skyline.analyzer package +======================== + +Submodules +---------- + +skyline.analyzer.agent module +----------------------------- + +.. automodule:: analyzer.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.analyzer.alerters module +-------------------------------- + +.. automodule:: analyzer.alerters + :members: + :undoc-members: + :show-inheritance: + +skyline.analyzer.algorithms module +---------------------------------- + +.. automodule:: analyzer.algorithms + :members: + :undoc-members: + :show-inheritance: + +skyline.analyzer.analyzer module +-------------------------------- + +.. automodule:: analyzer.analyzer + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: analyzer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.analyzer_dev.txt b/docs/_build/html/_sources/skyline.analyzer_dev.txt new file mode 100644 index 00000000..ff267d43 --- /dev/null +++ b/docs/_build/html/_sources/skyline.analyzer_dev.txt @@ -0,0 +1,46 @@ +skyline.analyzer_dev package +============================ + +Submodules +---------- + +skyline.analyzer_dev.agent module +--------------------------------- + +.. automodule:: analyzer_dev.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.analyzer_dev.alerters module +------------------------------------ + +.. automodule:: analyzer_dev.alerters + :members: + :undoc-members: + :show-inheritance: + +skyline.analyzer_dev.algorithms_dev module +------------------------------------------ + +.. automodule:: analyzer_dev.algorithms_dev + :members: + :undoc-members: + :show-inheritance: + +skyline.analyzer_dev.analyzer_dev module +---------------------------------------- + +.. automodule:: analyzer_dev.analyzer_dev + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: analyzer_dev + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.boundary.txt b/docs/_build/html/_sources/skyline.boundary.txt new file mode 100644 index 00000000..054a5851 --- /dev/null +++ b/docs/_build/html/_sources/skyline.boundary.txt @@ -0,0 +1,46 @@ +skyline.boundary package +======================== + +Submodules +---------- + +skyline.boundary.agent module +----------------------------- + +.. automodule:: boundary.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.boundary.boundary module +-------------------------------- + +.. automodule:: boundary.boundary + :members: + :undoc-members: + :show-inheritance: + +skyline.boundary.boundary_alerters module +----------------------------------------- + +.. automodule:: boundary.boundary_alerters + :members: + :undoc-members: + :show-inheritance: + +skyline.boundary.boundary_algorithms module +------------------------------------------- + +.. automodule:: boundary.boundary_algorithms + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: boundary + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.crucible.txt b/docs/_build/html/_sources/skyline.crucible.txt new file mode 100644 index 00000000..b8410b8b --- /dev/null +++ b/docs/_build/html/_sources/skyline.crucible.txt @@ -0,0 +1,38 @@ +skyline.crucible package +======================== + +Submodules +---------- + +skyline.crucible.agent module +----------------------------- + +.. automodule:: crucible.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.crucible.crucible module +-------------------------------- + +.. automodule:: crucible.crucible + :members: + :undoc-members: + :show-inheritance: + +skyline.crucible.crucible_algorithms module +------------------------------------------- + +.. automodule:: crucible.crucible_algorithms + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: crucible + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.horizon.txt b/docs/_build/html/_sources/skyline.horizon.txt new file mode 100644 index 00000000..484b6643 --- /dev/null +++ b/docs/_build/html/_sources/skyline.horizon.txt @@ -0,0 +1,46 @@ +skyline.horizon package +======================= + +Submodules +---------- + +skyline.horizon.agent module +---------------------------- + +.. automodule:: horizon.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.horizon.listen module +----------------------------- + +.. automodule:: horizon.listen + :members: + :undoc-members: + :show-inheritance: + +skyline.horizon.roomba module +----------------------------- + +.. automodule:: horizon.roomba + :members: + :undoc-members: + :show-inheritance: + +skyline.horizon.worker module +----------------------------- + +.. automodule:: horizon.worker + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: horizon + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.mirage.txt b/docs/_build/html/_sources/skyline.mirage.txt new file mode 100644 index 00000000..477b2d4e --- /dev/null +++ b/docs/_build/html/_sources/skyline.mirage.txt @@ -0,0 +1,54 @@ +skyline.mirage package +====================== + +Submodules +---------- + +skyline.mirage.agent module +--------------------------- + +.. automodule:: mirage.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.mirage.mirage module +---------------------------- + +.. automodule:: mirage.mirage + :members: + :undoc-members: + :show-inheritance: + +skyline.mirage.mirage_alerters module +------------------------------------- + +.. automodule:: mirage.mirage_alerters + :members: + :undoc-members: + :show-inheritance: + +skyline.mirage.mirage_algorithms module +--------------------------------------- + +.. automodule:: mirage.mirage_algorithms + :members: + :undoc-members: + :show-inheritance: + +skyline.mirage.negaters module +------------------------------ + +.. automodule:: mirage.negaters + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: mirage + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.panorama.txt b/docs/_build/html/_sources/skyline.panorama.txt new file mode 100644 index 00000000..97e40cac --- /dev/null +++ b/docs/_build/html/_sources/skyline.panorama.txt @@ -0,0 +1,30 @@ +skyline.panorama package +======================== + +Submodules +---------- + +skyline.panorama.agent module +----------------------------- + +.. automodule:: panorama.agent + :members: + :undoc-members: + :show-inheritance: + +skyline.panorama.panorama module +-------------------------------- + +.. automodule:: panorama.panorama + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: panorama + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.txt b/docs/_build/html/_sources/skyline.txt new file mode 100644 index 00000000..61fc838c --- /dev/null +++ b/docs/_build/html/_sources/skyline.txt @@ -0,0 +1,60 @@ +skyline package +=============== + +Subpackages +----------- + +.. toctree:: + + skyline.analyzer + skyline.analyzer_dev + skyline.boundary + skyline.crucible + skyline.horizon + skyline.mirage + skyline.panorama + skyline.webapp + +Submodules +---------- + +skyline.algorithm_exceptions module +----------------------------------- + +.. automodule:: algorithm_exceptions + :members: + :undoc-members: + :show-inheritance: + +skyline.settings module +----------------------- + +.. automodule:: settings + :members: + :undoc-members: + :show-inheritance: + +skyline.skyline_functions module +-------------------------------- + +.. automodule:: skyline_functions + :members: + :undoc-members: + :show-inheritance: + +skyline.skyline_version module +------------------------------ + +.. automodule:: skyline_version + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: skyline + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/skyline.webapp.txt b/docs/_build/html/_sources/skyline.webapp.txt new file mode 100644 index 00000000..6caf6d66 --- /dev/null +++ b/docs/_build/html/_sources/skyline.webapp.txt @@ -0,0 +1,38 @@ +skyline.webapp package +====================== + +Submodules +---------- + +skyline.webapp.backend module +----------------------------- + +.. automodule:: webapp.backend + :members: + :undoc-members: + :show-inheritance: + +skyline.webapp.gunicorn module +------------------------------ + +.. automodule:: webapp.gunicorn + :members: + :undoc-members: + :show-inheritance: + +skyline.webapp.webapp module +---------------------------- + +.. automodule:: webapp.webapp + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: webapp + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/tuning-tips.txt b/docs/_build/html/_sources/tuning-tips.txt new file mode 100644 index 00000000..bd833acc --- /dev/null +++ b/docs/_build/html/_sources/tuning-tips.txt @@ -0,0 +1,68 @@ +## Tuning tips + +Okay, so you've got everything all set up, data is flowing through, and...what? +You can't consume everything on time? Allow me to help: + +1. Try increasing `settings.CHUNK_SIZE` - this increases the size of a chunk of +metrics that gets added onto the queue. Bigger chunks == smaller network traffic. + +2. Try increasing `settings.WORKER_PROCESSES` - this will add more workers to +consume metrics off the queue and insert them into Redis. + +3. Try decreasing `settings.ANALYZER_PROCESSES` - this all runs on one box (for +now), so share the resources! + +4. Still can't fix the performance? Try reducing your `settings.FULL_DURATION`. +If this is set to be too long, Redis will buckle under the pressure. + +5. Is your analyzer taking too long? Maybe you need to make your algorithms +faster, or use fewer algorithms in your ensemble. + +6. Reduce your metrics! If you're using StatsD, it will spit out lots of +variations for each metric (sum, median, lower, upper, etc). These are largely +identical, so it might be worth it to put them in `settings.SKIP_LIST`. + +7. Disable Oculus - if you set settings.OCULUS_HOST to '', Skyline will not +write metrics into the `mini.` namespace - this should result in dramatic speed +improvements. At Etsy, they had a flow of about 5k metrics coming in every +second on average (with 250k distinct metrics). They used a 32 core Sandy Bridge +box, with 64 gb of memory. We experience bursts of up to 70k TPS on Redis. Here +are their relevant settings: + +``` +CHUNK_SIZE: 7000 +WORKER_PROCESSES: 2 +ANALYZER_PROCESSES: 25 +FULL_DURATION: 86400 +``` +## Smaller deployments + +Skyline runs OK on much less. It can handle ~45000 metrics per minute on a 4 +vCore, 4GB RAM cloud SSD server where the metric resolution is 1 datapoint per +60 seconds, it will run loaded, but OK. + +Do take note of the notes in settings.py related to the `settings.ANALYZER_PROCESSES` +and `settings.ANALYZER_OPTIMUM_RUN_DURATION` if you are only processing a few +1000 metrics with a datapoint every minute then the optimum settings will most +likely be something similar to: + +``` +ANALYZER_PROCESSES = 1 +ANALYZER_OPTIMUM_RUN_DURATION = 60 +``` + +# Reliability + +Skyline has been verified running in production against 6.5 million request per +minute peak internet advertising infrastructure, since 20131016. + +However it should be noted that something should monitor the Skyline processes. +Over a long enough timeline Python or Redis or some I/O issue is going to lock +up and the Python process is just going to hang. This means that it is not +really sufficient to just monitor the process with something like monit. + +Skyline has made some progress in monitoring its own process threads and keeping +itself running sanely, however not every possible issue can be mitigated against, +therefore some another external to Python monitoring can help. + +See [Monitoring Skyline](monitoring-skyline.html) diff --git a/docs/_build/html/_sources/upgrading.txt b/docs/_build/html/_sources/upgrading.txt new file mode 100644 index 00000000..5af34c64 --- /dev/null +++ b/docs/_build/html/_sources/upgrading.txt @@ -0,0 +1,143 @@ +========= +Upgrading +========= + +This section covers the steps involved in upgrading an existing Skyline +implementation. For the sake of fullness this describes the changes from +the last +`github/etsy/skyline `__ +commit to date. + +Things to note +============== + +This new version of Skyline sees a lot of changes, however although +certain things have changed and much has been added, whether these +changes are backwards incompatible in terms of functionality is +debatable. That said, there is a lot of change. Please review the following +key changes relating to upgrading. + +Directory structure changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to bring Skyline in line with a more standard Python package +structure the directory structure has had to changed to accommodate +sphinx autodoc and the setuptools framework. + +settings.py +~~~~~~~~~~~ + +The ``settings.py`` has had a face lift too. This will probably be the +largest initial change that any users upgrading from Skyline < 1.0.0 +will find. + +The format is much less friendly to the eye as it now has all the +comments in Python docstrings rather than just plain "#"ed lines. +Apologies for this, but for complete autodoc coverage and referencing it +is a necessary change. + +Analyzer optimizations +~~~~~~~~~~~~~~~~~~~~~~ + +There has been some fairly substantial performance improvements that may affect +your Analyzer settings. The optimizations should benefit any deployment, +however they will benefit smaller Skyline deployments more than very large +Skyline deployments. Finding the optimum settings for your Analyzer deployment +will require some evaluation of your Analyzer run_time, total_metrics and +:mod:`settings.ANALYZER_PROCESSES`. + +See `Analyzer Optimizations `__ and regardless of +whether your deployment is small or large the new +:mod:`settings.RUN_OPTIMIZED_WORKFLOW` timeseries analysis will improve +performance and benefit all deployments. + +Skyline graphite namespace for metrics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The original metrics namespace for skyline was ``skyline.analyzer``, +``skyline.horizon``, etc. Skyline now shards metrics by the Skyline +server host name. So before you start the new Skyline apps when +referenced below in the Upgrade steps, ensure you move/copy your whisper +files related to Skyline to their new namespaces, which are +``skyline..analyzer``, ``skyline..horizon``, etc. Or +use Graphite's whisper-fill.py to populate the new metric namespaces +from the old ones. + +Logging +~~~~~~~ + +There have been changes made in the logging methodology, see +`Logging `__. + +Clone and have a look +~~~~~~~~~~~~~~~~~~~~~ + +If you are quite familiar with your Skyline setup it would be useful to +clone the new Skyline and first have a look at the new structure and +assess how this impacts any of your deployment patterns, init, +supervisord, etc and et al. Diff the new settings.py and your existing +settings.py to see the additions and all the changes you need to make. + +Upgrade steps +============= + +These step describe an in-situ manual upgrade process (use sudo where +appropriate for your environment). + +Due to virtualenv being the default and recommended way to now run Skyline, in +order to upgrade most of the installation steps in the Installation +documentation are still appropriate. + +- Setup a Python virtualenv for Skyline - see `Running in Python + virtualenv - HOWTO `__ +- Go through the installation process, ignoring the Redis install part and + breaking out of installation where instructed to and returning to here. + +- Stop all your original Skyline apps, ensuring that something like monit or + supervisord, etc does not start them again. +- At this point you can either move your current Skyline directory and replace + it with the new Skyline directory **or** just use the new path and just start + the apps from their new location. +- If you want to move it, this is the PATH to **your** current Skyline, which + contains: + +:: + + skyline/bin + skyline/src + # etc + +- We are moving your skyline/ directory here. + +.. code-block:: bash + + SKYLINE_DIR= + mv "$SKYLINE_DIR" "${SKYLINE_DIR}.etsy.master" + mv /opt/skyline/github/skyline "$SKYLINE_DIR" + +- Copy or move your existing Skyline whisper files to their new + namespaces on your Graphite server/s (as mentioned above.) Or use + Graphite's whisper-fill.py to populate the new metric namespaces from + the old ones after starting the new Skyline apps. +- Start the Skyline apps, either from your path if moved or from the new + location, whichever you used + +.. code-block:: bash + + "$SKYLINE_DIR"/bin/horizon.d start + "$SKYLINE_DIR"/bin/analyzer.d start + "$SKYLINE_DIR"/bin/webapp.d start + "$SKYLINE_DIR"/bin/pnorama.d start # if you have the MySQL DB set up + +- Check the log files to ensure things are running and there are no errors. + +.. code-block:: bash + + tail /var/log/skyline/*.log + +- If you added the new ``skyline_test.alerters.test`` alerts tuples to your + ``settings.py`` you can test them now, see `Alert testing `__ +- Look at implementing the other new features at your leisure +- Panorama was probably the quickest win if you opted to not install it +- Boundary and Mirage will take a little assessment to see what metrics + you want to configure them for. diff --git a/docs/_build/html/_sources/webapp.txt b/docs/_build/html/_sources/webapp.txt new file mode 100644 index 00000000..f3df1854 --- /dev/null +++ b/docs/_build/html/_sources/webapp.txt @@ -0,0 +1,206 @@ +.. role:: skyblue +.. role:: red +.. role:: brow + +###### +Webapp +###### + +The Webapp service is a simple gunicorn/Flask based frontend that provides a web +UI to: + +* Visualise the latest anomalies that have been triggered. +* Visualise historic anomalies that have been recorded by Panorama. +* An api to query Redis for a metric timeseries which returns the timeseries in + json e.g. ``/api?metric=skyline.analyzer.hostname.total_metrics`` +* An api to query Graphite for a metric timeseries which returns the timeseries in + json that takes the following parameters: + + * ``graphite_metric`` - metric name + * ``from_timestamp`` - unix timestamp + * ``until_timestamp`` - unix timestamp + * e.g. ``/api?graphite_metric=skyline.analyzer.hostname.total_metrics&from_timestamp=1370975198&until_timestamp=1403204156`` + +* Publish the Skyline docs. +* Browse the Redis DB with a port of Marian Steinbach's excellent :red:`re`:brow:`brow` + https://github.com/marians/rebrow + +Deploying the Webapp +==================== + +Originally the Webapp was deployed behind the simple Flask development server, +however for numerous reasons, this is less than ideal. Although the Webapp can +still be run with Flask only, the recommended way to run the Webapp is via +gunicorn, which can be HTTP proxied by Apache or nginx, etc. The gunicorn +Webapp can be exposed just like the Flask Webapp, but it is recommended to run +it HTTP proxied. + +Using a production grade HTTP application +----------------------------------------- + +It must be noted and stated that you **should** consider running the Skyline +Webapp behind a production grade HTTP application, regardless of the +implemented basic security measures. Something like Apache or nginx serving the +Webapp via gunicorn. + +This may seem like overkill, however there are a number of valid reasons for +this. + +Production infrastructure +^^^^^^^^^^^^^^^^^^^^^^^^^ + +It is highly probable that Skyline will often be run on cloud based +infrastructure which is public and should therefore really be considered +production. + +Flask has a Deploying Options sections that covers running Flask apps in +production environments. See http://flask.pocoo.org/docs/0.11/deploying/ + +In addition to that, considering that the Webapp now has MySQL in the mix, this +element adds further reason to properly secure the environment. + +Apache and gunicorn +------------------- + +Although there are a number of options to run a production grade wsgi frontend, +the example here will document serving gunicorn via Apache reverse proxy with +authentication. Although Apache mod_wsgi may seem like the natural fit here, in +terms of virtualenv and Python ``make_altinstall``, gunicorn has much less +external dependencies. gunicorn can be easily installed and run in any +virtualenv, therefore it keeps it within the Skyline Python environment, rather +than offloading very complex Python and mod_wsgi compiles to the user, +orchestration and package management. + +Apache is a common enough pattern and gunicorn can be handled within the Skyline +package and requirements.txt + +See ``etc/skyline.httpd.conf.d.example`` for an example of an Apache conf.d +configuration file to serve the Webapp via gunicorn and reverse proxy on port +8080 with basic HTTP authentication and restricted IP access. Note that your +username and password must match in both the Apache htpasswd and the +:mod:`settings.WEBAPP_AUTH_USER`/:mod:`settings.WEBAPP_AUTH_USER_PASSWORD` +contexts as Apache will authenticate the user and forward on the authentication +details to gunicorn for the Webapp to also authenticate the user. +Authentication is enabled by default in ``settings.py``. + +Feel free to use nginx, lighttpd, et al. + +Securing the Webapp +=================== + +Firewall rules +-------------- + +The Webapp **should** be secured with proper firewall rules that restrict access +to the :mod:`settings.WEBAPP_IP` and :mod:`settings.WEBAPP_PORT` (and/or just +the reverse proxy port for gunicorn if being used) from trusted IP +addresses only. + +Basic security +-------------- + +There are some simple and basic security measures implemented with the Webapp. + +IP restricted access +-------------------- + +The default :mod:`settings.WEBAPP_ALLOWED_IPS` only allows from 127.0.0.1, add +your desired allowed IPs. + +psuedo basic HTTP auth +---------------------- + +There is single user that can access the web UI all access must be authenticated. + +Restricted by default +--------------------- + +These simple measures are an attempt to ensure that the Skyline web UI is not +totally open by default, but rather totally restricted by default. This adds a +bit of defense in depth and hopefully will mitigate against unauthorized access +in the event that some day, someone may have their firewall misconfigured in +some way, either through error or accident. + +These basic restrictions **DO NOT** replace the need for proper firewall rules +or a production grade HTTP application. + +Logging +^^^^^^^ + +Flask's development server is based on werkzeug, whose WSGIRequestHandler is, +in turn, based in the BaseHTTPServer from the standard lib. This means that +WSGIRequestHandler overrides the logging methods, log_request, log_error and +log_message, to use it's own logging.Logger. So there is no access logging in +Skyline Webapp log. It is possible to hack this around a bit, but this means +application error logging would get shifted from the Webapp log to the access +log, which is not ideal. + +Panorama web UI +=============== + +Basic function +-------------- + +The Panorama web UI allows you to search the anomalies recorded by Panorama in +the database. It currently allows you to search through the anomaly records by +various filters, which are converted into MySQL ``SELECT`` queries which +return the details regarding the anomalies found from the search criteria. The +Webapp then returns these to the browser and the client side javascript then +passes the relevant metric details to the Webapp api endpoint to surface the +metric timeseries from Graphite and the api returns the timeseries json to the +browser to graph the timeseries. + +Closest approximations +---------------------- + +The Panorama anomaly records only hold the details regarding the anomaly, not +the data. The Panorama UI takes the returned anomalies from a search and +retrieves the timeseries for the time period relevant to the anomaly from +Graphite on demand. The UI graphs the timeseries to visualise the context of +the anomaly, as best possible. Due to the fact that Panorama is storing anomaly +details in real time and the Panorama web UI is surfacing timeseries +historically, any Graphite aggregations in timeseries can result in the specific +anomalous datapoint not being present in the related timeseries. In these +instances the Panorama graphs will indicate this and visually present a closest +approximation of where the anomalous line would be, using a thicker, orange +horizontal line as the indicator, rather than the thinner, normal red horizontal +line. + +.. image:: images/panorama.closest.approximation.aggregrated.png + +:red:`re`:brow:`brow` +===================== + +Skyline uses a modified port of Marian Steinbach's excellent +:red:`re`:brow:`brow` Flask Redis browser - `rebrow`_. A modified port was used +for a number of reasons: + +* :red:`re`:brow:`brow` does not handle msg-pack encoded keys. +* The pubsub functionality was unneeded. +* Serving it in an iframe was bothersome. +* Having an additional dependency, app and service for another Flask app seemed + to be a bit of overkill. +* Having it native in the Skyline Webapp UI was neater and prettier. + +Please do clone https://github.com/marians/rebrow, just so Marian gets some +clones. + +With the addition of a number of Panorama and other app related keys, +:red:`re`:brow:`brow` adds a window into Redis, to allow for the verification of +key creation and providing a view of ``*last_alert.*`` and +``panorama.mysql_ids.*`` keys. + +Basic function +-------------- + +The Panorama web UI allows you to search the anomalies recorded by Panorama in +the database. It currently allows you to search through the anomaly records by +various filters, which are converted into MySQL ``SELECT`` queries which +return the details regarding the anomalies found from the search criteria. The +Webapp then returns these to the browser and the client side javascript then +passes the relevant metric details to the Webapp api endpoint to surface the +metric timeseries from Graphite and the api returns the timeseries json to the +browser to graph the timeseries. + + +.. _rebrow: https://github.com/marians/rebrow diff --git a/docs/_build/html/_sources/whats-new.txt b/docs/_build/html/_sources/whats-new.txt new file mode 100644 index 00000000..50c63073 --- /dev/null +++ b/docs/_build/html/_sources/whats-new.txt @@ -0,0 +1,97 @@ +# What's new + +## Skyline v1.0.0-beta - the crucible branch + +The crucible branch had an issue open called `Bug #982: Too much in crucible branch` + +> Too much in crucible branch +> +> I have added some pep20, sphinx docs and python package restructuring from @languitar etsy/skyline #93 - https://github.com/languitar/skyline/tree/setuptools - which turns skyline into a python package + +The reality was that it was too difficut to reverse engineer all the changes +into separate branches, so it contiuned unabated... + +This version of Skyline sees enough changes to worthy of a major version change. +That said the changes are/should be backwards compatible with older versions, +mostly (best efforts applied) with a caveat on the skyline graphite metrics +namespace and running Skyline under python virtualenv. + +### Conceptual changes + +The `FULL_DURATION` concept is a variable and it is a variable in more ways than +the settings.py context. Conceptually now `FULL_DURATION`, `full_duration`, +`full_duration_seconds` and `full_duration_in_hours` are variables in different +contexts. The `FULL_DURATION` concept was important in the Analyzer context, +but the concept of the full duration of a timeseries has become somewhat more +variably and is different within different scopes or apps within Skyline. It is +no longer really a single static variable, it is handled quite dynamically in a +number of contexts now. + +### Etsy to `time()` + +This whats new will cover all the new things that the crucible branch +introduces since the last Etsy commit on master of +[etsy/skyline](https://github.com/etsy/skyline), although not strictly accrurate, for the +purposes of generality it shall be assumed that no one is running the new Skyline +features that have not been merged to the Etsy Skyline. + +### anomaly_breakdown metrics + +### mirage + +See [Mirage](mirage.html) + +### boundary + +See [Boundary](boundary.html) + +### crucible + +See [Crucible](crucible.html) + +### sphinx documentation + +See [Building documentation](building-documentation.html) + +### Performance tuning + +See [Analyzer Optimisations](analyzer-optimisation.html) + +### Process management + +A number of the apps have had better process management handling added and the +parent process now spawns processes and terminates then if they have not +completed in `MAX_ANALYZER_PROCESS_RUNTIME`, `ROOMBA_TIMEOUT` and other apps +have this specified too, either using the `MAX_ANALYZER_PROCESS_RUNTIME` as a +hardcoded one where appropriate. This handles a very limited number of edge +cases where something that is host machine related causes the Python process to +hang. + +### Analyzer optimisations + +See [Analyzer Optimisations](analyzer-optimisation.html) + +### algorithm_breakdown metrics + +See [Analyzer Optimisations](analyzer-optimisation.html) + +### Improved log handling + +- Prevent log overwrites + +See [Logging](logging.html) + +### Webapp security + +Some simple and basic security was added to the Webapp now it can be enabled +to access a MySQL database in the Panorama context. + +- Only allow IP addresses in `WEBAPP_ALLOWED_IPS` +- There is now a single HTTP auth user `WEBAPP_AUTH_USER` and + `WEBAPP_AUTH_USER_PASSWORD` + +See [Webapp](webapp.html) + +### Panorama + +See [Panorama](panorama.html) diff --git a/docs/_build/html/_static/ajax-loader.gif b/docs/_build/html/_static/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..61faf8cab23993bd3e1560bff0668bd628642330 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css new file mode 100644 index 00000000..2b513f0c --- /dev/null +++ b/docs/_build/html/_static/basic.css @@ -0,0 +1,604 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_build/html/_static/comment-bright.png b/docs/_build/html/_static/comment-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..551517b8c83b76f734ff791f847829a760ad1903 GIT binary patch literal 3500 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 A2LJ#7 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/comment.png b/docs/_build/html/_static/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..92feb52b8824c6b0f59b658b1196c61de9162a95 GIT binary patch literal 3445 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 Xn!ZpXD%P(H00000NkvXXu0mjfa span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.2.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.wy-menu-vertical li span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.rst-content code.download span.pull-left:first-child,.pull-left.icon{margin-right:.3em}.fa.pull-right,.wy-menu-vertical li span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.rst-content code.download span.pull-right:first-child,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-remove:before,.fa-close:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:""}.fa-meanpath:before{content:""}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .rst-content p.caption .headerlink,.rst-content p.caption a .headerlink,a .rst-content tt.download span:first-child,.rst-content tt.download a span:first-child,a .rst-content code.download span:first-child,.rst-content code.download a span:first-child,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .btn span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.btn .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .rst-content p.caption .headerlink,.rst-content p.caption .btn .headerlink,.btn .rst-content tt.download span:first-child,.rst-content tt.download .btn span:first-child,.btn .rst-content code.download span:first-child,.rst-content code.download .btn span:first-child,.btn .icon,.nav .fa,.nav .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand,.nav .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .rst-content p.caption .headerlink,.rst-content p.caption .nav .headerlink,.nav .rst-content tt.download span:first-child,.rst-content tt.download .nav span:first-child,.nav .rst-content code.download span:first-child,.rst-content code.download .nav span:first-child,.nav .icon{display:inline}.btn .fa.fa-large,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .btn span.fa-large:first-child,.btn .rst-content code.download span.fa-large:first-child,.rst-content code.download .btn span.fa-large:first-child,.btn .fa-large.icon,.nav .fa.fa-large,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.nav .rst-content code.download span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .btn span.fa-spin:first-child,.btn .rst-content code.download span.fa-spin:first-child,.rst-content code.download .btn span.fa-spin:first-child,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.nav .rst-content code.download span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.wy-menu-vertical li span.btn.toctree-expand:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.rst-content code.download span.btn:first-child:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.rst-content code.download span.btn:first-child:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.rst-content tt.download .btn-mini span:first-child:before,.btn-mini .rst-content code.download span:first-child:before,.rst-content code.download .btn-mini span:first-child:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type="radio"][disabled],input[type="checkbox"][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{width:36px;height:12px;margin:12px 0;position:relative;border-radius:4px;background:#ccc;cursor:pointer;-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.wy-switch:before{position:absolute;content:"";display:block;width:18px;height:18px;border-radius:4px;background:#999;left:-3px;top:-3px;-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.wy-switch:after{content:"false";position:absolute;left:48px;display:block;font-size:12px;color:#ccc}.wy-switch.active{background:#1e8449}.wy-switch.active:before{left:24px;background:#27AE60}.wy-switch.active:after{content:"true"}.wy-switch.disabled,.wy-switch.active.disabled{cursor:not-allowed}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,.rst-content .toctree-wrapper p.caption,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2,.rst-content .toctree-wrapper p.caption{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt,.rst-content code{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9B59B6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#EAF2F5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs li code,.wy-breadcrumbs li .rst-content tt,.rst-content .wy-breadcrumbs li tt{padding:5px;border:none;background:none}.wy-breadcrumbs li code.literal,.wy-breadcrumbs li .rst-content tt.literal,.rst-content .wy-breadcrumbs li tt.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;margin-bottom:0;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#555;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li code,.wy-menu-vertical li .rst-content tt,.rst-content .wy-menu-vertical li tt{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:0.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.on a:hover span.toctree-expand,.wy-menu-vertical li.current>a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand{display:block;font-size:0.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current li.toctree-l2>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>ul{display:none}.wy-menu-vertical li.toctree-l1.current li.toctree-l2.current>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>ul{display:block}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{display:block;background:#c9c9c9;padding:0.4045em 4.045em}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3{font-size:0.9em}.wy-menu-vertical li.toctree-l3.current>a{background:#bdbdbd;padding:0.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{display:block;background:#bdbdbd;padding:0.4045em 5.663em;border-top:none;border-bottom:none}.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.toctree-l4{font-size:0.9em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#b3b3b3}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:0.809em;margin-bottom:0.809em;z-index:200;background-color:#2980B9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-side-nav-search>a img.logo,.wy-side-nav-search .wy-dropdown>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search>a.icon img.logo,.wy-side-nav-search .wy-dropdown>a.icon img.logo{margin-top:0.85em}.wy-side-nav-search>div.version{margin-top:-0.4045em;margin-bottom:0.809em;font-weight:normal;color:rgba(255,255,255,0.3)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}footer span.commit code,footer span.commit .rst-content tt,.rst-content footer span.commit tt{padding:0px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:1em;background:none;border:none;color:#999}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-side-scroll{width:auto}.wy-side-nav-search{width:auto}.wy-menu.wy-menu-vertical{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content .toctree-wrapper p.caption .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content .toctree-wrapper p.caption .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content p.caption .headerlink:after{visibility:visible;content:"";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content .toctree-wrapper p.caption:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink,.rst-content p.caption:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.docutils.citation tt,.rst-content table.docutils.citation code,.rst-content table.docutils.footnote tt,.rst-content table.docutils.footnote code{color:#555}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt,.rst-content tt,.rst-content code{color:#000;padding:2px 5px}.rst-content tt big,.rst-content tt em,.rst-content tt big,.rst-content code big,.rst-content tt em,.rst-content code em{font-size:100% !important;line-height:normal}.rst-content tt.literal,.rst-content tt.literal,.rst-content code.literal{color:#E74C3C}.rst-content tt.xref,a .rst-content tt,.rst-content tt.xref,.rst-content code.xref,a .rst-content tt,a .rst-content code{font-weight:bold;color:#404040}.rst-content a tt,.rst-content a tt,.rst-content a code{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:#555}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) code{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) code.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}.rst-content tt.download,.rst-content code.download{background:inherit;padding:inherit;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{margin-right:4px}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}@font-face{font-family:"Inconsolata";font-style:normal;font-weight:400;src:local("Inconsolata"),local("Inconsolata-Regular"),url(../fonts/Inconsolata-Regular.ttf) format("truetype")}@font-face{font-family:"Inconsolata";font-style:normal;font-weight:700;src:local("Inconsolata Bold"),local("Inconsolata-Bold"),url(../fonts/Inconsolata-Bold.ttf) format("truetype")}@font-face{font-family:"Lato";font-style:normal;font-weight:400;src:local("Lato Regular"),local("Lato-Regular"),url(../fonts/Lato-Regular.ttf) format("truetype")}@font-face{font-family:"Lato";font-style:normal;font-weight:700;src:local("Lato Bold"),local("Lato-Bold"),url(../fonts/Lato-Bold.ttf) format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:400;src:local("Roboto Slab Regular"),local("RobotoSlab-Regular"),url(../fonts/RobotoSlab-Regular.ttf) format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:700;src:local("Roboto Slab Bold"),local("RobotoSlab-Bold"),url(../fonts/RobotoSlab-Bold.ttf) format("truetype")} +/*# sourceMappingURL=theme.css.map */ diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js new file mode 100644 index 00000000..81634956 --- /dev/null +++ b/docs/_build/html/_static/doctools.js @@ -0,0 +1,287 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/docs/_build/html/_static/down-pressed.png b/docs/_build/html/_static/down-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..7c30d004b71b32bb2fc06b3bd4dc8278baab0946 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~&H|6fVxZ#d zAk65bF}ngN$X?><>&kwMor^(NtW3yF87Slz;1l8sq&LUMQwy<>&kwMol#tg zK_ydLmzem(vK1>2TzUEGl*lj!N<7$PCrdoWV0 z$w0*Ap!bZ4if7h;-yfL#MC0e;t{xY+$l~DX2EWYIPet1cohf^BdG+jXhtuq&W-0|c zKPmlKv-7OTjb}T)7@fTGd9y~u4{g8An;)c2U=w=nwQ7}zVDc>n+a literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..254c60bfbe2715ae2edca48ebccfd074deb8031d GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJXMsm#F#`j)FbFd;%$g$s6l5>) z^mS#w%FV~i&ZxO9L3Zxqw8>dd4I&zcKG){Yx14xKr0

ZQJ$m%mv17-NAAj}g)$7-<-@JMA z_U+TRK=AR}yLa#2zkmPX!-tO_KYsf3>Hq)#%qnY_1Fd8&3GxeO2wSmci|LJf=|BO- zByV>Yl`U*PX977no-U3d5|XS39sLdkFt8q|+|QqL_#ErUf6I%zFA7b%b>3$hFGGFs zc72AL|61pRJ1(+5wNdg|xP#*`gQ~lOnTFKiIjl#S3)+QV=h{~`9{M=hx#5uZ&-tIF sG!8onYS_8EFr8v&@CavkqYey&g)1epR*Fkm0PSV)boFyt=akR{044O6bN~PV literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/fonts/Inconsolata-Bold.ttf b/docs/_build/html/_static/fonts/Inconsolata-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..58c9fef3a01c899867e280f49283fbb8e57d631d GIT binary patch literal 66352 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%0Am60^i>^d(hPdT;qb{g1P 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 OG|;OYVSulL^Zx)kukT&} literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/fonts/Lato-Bold.ttf b/docs/_build/html/_static/fonts/Lato-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..74343694e2b2114272f38b1124813b972cb592e5 GIT binary patch literal 121788 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 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/fonts/Lato-Regular.ttf b/docs/_build/html/_static/fonts/Lato-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..04ea8efb1367727b081dea87e63818be0a4d02f0 GIT binary patch literal 120196 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�r(aJ zZ33HkgG;a`qEg`DFB$$iS~G&mOkPzQ`iwI*Hi_VuTD%Q52}^*S8tJ0oD`Q)6ne@hV&no+d+meobxEgh>q^H?C1wRm~aOxy}xvhKEZC9|$udEKnE zk`0xM8dlACYViYS9Z!_s^2LJk^$YX!3MS?j&YH92_LVEYbmOd{&F@uHN|qE%FRfWv zw5oDm`O>_J3ktKQue*QUr5yWjx1RVCx@5Kwoz3ky4!vH4gh=B$Dyy?G4Kp zFRGkhGOu9nY}`_xF+Om<>UHLI3G`M)LsP03l-Z&pGgA>ljk$Fc9HUVf$-`@bW&V7( z>2yK$blz%wtSyoUE=`WcWfeGNhyHu0h7N;Bv<{I>|DVbar>WA}%GKy~e{K0NoCp>V zyPjfVLjQ=6uZgYXGuNEA8H;-uL#$))5&o~M6N&{Bv_u;|iBP;58)?9moh`veMqvm* zZ{U#RKZTQrK;V|Ykw6Sbb=gJ??pf8}ptSfmi!&8&^WHK#5tj{DqUVpw1$@QSoosM} zd(*)YdRN;g(xS_eKK9!`i56CQgXD6sLJ|p|Dsvu%SWE<6ku>eW&fW5M8t`6%Gx-1R z7(vJ#8RdrlTTESr5YG2^xxp90nL=!R8YQ~=_`lFPq7$3?uv4r8(a78_hC_aFu}bKu z-{Qb=Q3`3bHqsyfC=UB(y7|5Nn-@=uG%sbA z-xT4f`qH(U7#@Z)dKer_FrmI;qKyGQydq(DVJByr7)_$#!1)&$nZOCFwU1=Q6}eqG zZ4cyz*{F%h$*^3}Jr3nbhXt+1^?ya4@(j?^7#gG|)5KNP{1thH&DA3#*}6idWMPAW z@WZFmh$WMv1uo?t771_ZA9gE-&!x?O2%Ae!srl;drGYcq=Zt!EdyL1KUv}a%p3&#g zjWO88c$al%d)3Gz-J_!xR4{!Zh%SMUdPdoU248kWA(N*{a~>GK@S-+KR7NWlAp|AF*r z4(4yA-^ur3ZB`6^h~4z<2nng?Ugdd3RTV|m^QvdhoIFW!7wi%}@uA=EU%znIliTZ#-oI@9O}l?k)p6-7^R^vsn6ajEO8!mv zE?V<<>u0H6_F|y(Zw`)?xKTaQ_#J$>tS_ApsQOx~^y`d2kD9)7SUT&`djD6$(wV=N z{<~r6tY0hrPNrkbwZ-^8VqqV^Q>rn;SYX}zpp#co4?G`W29&@a+)6IPA_s{UD}pX! z`gT!G4u-wJ)nJZT76@)a4aW6uVJxe8TT)i|uJWv;k^i>+-wyN02)F;`nh^=)p=$7K z#H@(xjVy3bWX`BcoHcW1(X1lwg&Q|E0cTW(_L`Tc!zLFNOyMp!4iGSHM_7rO0d2iv z@|~>H-vhdf$vZ7Et>#~y2lStLu*H;qdO1QJz2e_OEeKxtMSt1H)?9ul7ft4aZulWf zsp)*+gm5+?&F0PIR1;CBaX4d`8;UTOxBtWZg*@+ZMa#V6F=ZLoeSP5)9{aeeqht~0 z@f$DwQ<-`8`i2$vteR>TFTZ=$)WU&POYHY7`r%HV1378pvW&E67eDRiVURi5o5rVJ z{`UUQ*jARz+w%2+$wgZp+Ss%C@`XIM2c7q%_CUW3=LG*x?O|m4&SB|n57zs?8kSDI zx6f`X4!T5qhkvy zCVj#!1vh_gRe9RPsP@ZE?YQx>+cy6>+x#iX(&ms$2b3UM@(k!oN+lb5EWy4!?twZnrVx12?^~` z@RwoZR|G^t-GWd#M@YsN_9&dHiGSeux=aXHf$p$62{tWFQqOMu#9Yxf@%^>rPL@bX znFrVX*8YW=Q!L1ZYB?$ob`_gCy84=O#zcx$c1SwI_ zImn#D((I5l$d0uDh!Id$NXr8A5vd}^qSvscGyEdAA?6ys|>)Qy~6UHZ;e~32DApM-$;>bjC?KtyMU58dPh9b77SX&$!AK z#x$CXdzLYwF%3?UtB^soiHlYEo%CZct$G`?%esL+_X)Sld<7eHn70RMaGmNBOBoi>FuiW%Ny-?UtVpSb@>(B znN35g9cE5LksU({i^42}{p1~6<;G(3dsP!E=9`$L%wJrw*jtQ9IjQ=2Aa+lfy{U(Wgs(((CVr` zJ=bCVcFhB^ZxLb_oV}32`zOMTo?0O(?l8eGXnR z61?~Ptg}5E(S3W>`$S7C5CYese!{)Jm`c^V$7SZl-T4Y^WU zfR$40qOdDYlJ-D@&-88os za2v~rY}lR7L^g;$yLt((mJPKYN4G0as44v{DGn`DK9u&Nvy90c=X>8vCAEG1Rns(!XeN5GhjyRep1itK@t$AY1`QMtjM_R^ts z1CM|0?PImI$KL+h;{)qbtL{C%b?fo_7A&}rfA6gdb-I7Jul)-@GTo0Ww4$T&50?vC zU+G-6s`HiB)>pooMNJrEEzSlOWf8z@+OaH)MZ##CbqSv{26~>H8H=GxXwegE zvK$iew3ojjm%xpf)SBg=ToyAL<1*|{wTc4u4Exuxf+0(e5~Mk!Wa&0>LK&-9IYqg2 zz26YeBeNopTrgbu*JY*knC$RODL{<>0$ae={pHzWl%v&DY+}Q4!C-{j-U0@Pf3e-C z>c&yBg=00yI~NwCTxv%{oQLHL&oQ@fVlh?Kjc^O)tI52KvqCF|j}YL}=j1)y-;WhV z>kL}U+dpP#QSpz%78U1jsG60S5?i1(#e08PJnu()wjA0~Rdw&t+gpB6l>gn!NB@5J zq(xg7FWtI$(jNQgu!uPQ%0$6WdY`dK*86d0LfHHCb6W5JnkzlX zCkkVR-z)ynibZmni!=?G*`e=jet=I)i)lo@wUe|q`miQ`K&dSGov;^O;u6Y zg@74xXylcI7Kh;@__E=IEes-Up`*$@)=&5WTgH(KPbyDANR~#Mp-t6Dw@#fyBb(g}?)~>8j&HggP0=ogE27C6J@DVu?ZCM>P_nzEqZ1>(Vp{~+r5InTLg0i4_e|i_ z$rLk1E1uM}L`1MrdT<5sM1vSJbe11Z*|ds;K_8XJTKx>XoQ0A1B=mVGvKE}s*NyU$ z!|L(5H@hHswkQvtHuG=Tx*+}jxJlj_GrW`H?oVIv`G~K~xc;`{EH94io9fLfzU}%M zl*-wN@0;ItT*O}T^r7<#;>a3XfQHC6PCG^%5omtfx-ru_=b1NVI!c45K938#E7Q0$ z;tS@V9lz0Db({mld*XR2g3fJaA0AU#-aO5Stx^kkS4g<_v#o{nMaOCBP^}}Kv zTVb7la_7Y2>Z#MNFPS*8PTTamq^a?o86%QW<_*lnaPywgQkCe!yxx`M!j0`n3En58Ru1aY z132!`tUbn1D_4qs%(i}nv@rbyqQFq}T!xD(@{k0JpcfD$1d0c0Jv`|A6P#a%PRFr! zLa{S2Pd^`b3n2^OIa}r{4WR6bG88e3bm`2{(AjT-_0?X{FVtPDNEs#(7D+cm_5}(@6AIPB<^D%?`9<@F4K)9P&PO^mp+N zKM3^0aH+HJ5_@rQTW@zvx2enlqE5+Jv;(CAcud26xhq`(1)bP zezzjFq{N*I^C(EI&p&p40$|5c|>zVtP_?{f3KdtyDyqY-#lvC!tP ziW!^4OEOJkpUrLmp6wwF2(JBnwSD%9mwsz|s5$Vx|wk%Hc|wFg7kJHFCWDk9nWT+jw~KBuA_> z&XqiK?z{;T7v;pnjz!bJzB>EAn@b$OxAKMNU|T=q2Aq+K#DV-^nsuIs8J{t3!>aI% z9ly`poVO{Manh_n#xY}gm(Rw5cg-cn`(gP`xz>E&ziPJ70?5I3ljJa$=a6Kk!CQ7! zSs+&Ennd)>waaYgRfTDzENwd_OO-7RhtZ>KSCQv6i^F{M+66!J>Y{_}P$#yXs*~{Y zU8NvbNxo7It}NeGEAW$w2c6kD@Nh(;<7p$){9;vH=7jNSV{j)-tSf?d!Yn^CAC_na zoC$`BO*wWWeJnN_aAywgFNQ(3<9ixuUK|(^F~*t)uxFok#V2?RA4H%j!=CVS>jQRr z{!wWR`yjM^%pIZ81E=+cSI|Id&>he%IgA!;A6!(7k-%=Sz~sFw^sz0(ll3voAlbq| zljX&;${Usk3}<=pYsZyeBLoWlH9o?qY}C&+6~|~Lsc;_M^{ICJNwrs)#k?OU%aa5f znw#Xw+Bt2;q!p`I=Z>9PRGgE#^6|wprw-hgnp0dfb?m^s_U_EQXjiP=d=qE(jEx*; z=4F--Y`*kgsj2p8bH8k7uwLhwzpolIXZEa_({i)3a62FDl~uorwP5{*xPh6?v^gRs zPtN6z7?vrb z%x(;37tQPa}e?Gunt+p=>mZ(DuxwSoiKaht7sE$qHty-c+=7< ztOfB7A$UDU)h=`Q)R~JPUuh}X$^(mMOf&b4u4|W%m1pLeX^{hIsj220k!jRF8{@%D z<7dWi?HBAXU?f-`nky|olZx8WITw^O?1hPuAEq@N46?210hP&rXIXbHjhM3htZYli ziW_!+uB_~HyKh+0v8Bvb6a3c=_!oYKX}NjueaCv^UC_=o%8W;O#}(sh8M7GJYp78u zf@>^-I3+^6oPrBYiNk-vUMBv+rQ;0#Vik7SJ63_mBdC)jYqElh!r|1WRgFAG4^P9` z!)%llRy=~iDWCH1n@1k<5aAe^f_?s3v&SKxGbw2j|7In9aYS%u@H!z~I;2a8W^-~n zJNR!bVpI>l7xf*-QOR(wvDnyPY%?A-e^oW%e*ZnbJGXAG!du8o!&^cRu0xZnQ&!lslesL88$eEA-K#^at+^N`6E^tux+_8 z+)i}Lm(6-DzsmR6xe|7Ov-aIzkw-9&bUr!(8$w$1!2#EZSoYC5aya6L6(q$f2$f}7 zC=*0?L_4r)>QgdxREAG=|!6Z^qgG;I+UKQ;_y=61OqI4Nk9%;4dPjf%lE9Bal# zQ0CEY<%v1E%jwnzo8WZ-$~zR`HPUSnIb!ugXMxEj^=WTT8Q}mKnUn3A!kg}?Ty*#P zyu9^yFS>5WjdTA#eeQxOQ*g%m_&oldn{I21P9OWgpWkw&C$&0?3U9i5==FQn=Y4tP z>-Mdmnqx%ey0%Sud7IkSt#7Z*;{_HAYsP|4%u50Cs zt?Slz@Wqbx3vVeM2M^%F1snO|#s#19yX+s%b~pc3XS-w0U`ytlVYGAIM;_sR)g#+s zJ9rMD`I^oFM4pK_2o9&XU2))_`P#4}{%p8`akSI?kl!FY+4gMsq|mT62Hd_Temd$QF0 zYSo0LOR8{Pp0_wZZ^ks7AGu-j)awWP#%Sp?wo+sD|cx0}cdnm^gmx80% z$ZP(#99=HhMI1#HC@cSP_2;wKk}Imh&CBl4+J}Y(IBz>LHil_VoF!&SC2BM(F;@SR zRG{0)hPM4UBQ;paiZfHv%~b1o0`8OH`R%&2Oy?+P7Zv0ba(czLGw6;Lh}a+&gb=`HYEau~D&6%a{D@p?fawzA52r z?fAXJ-g5s>9$K;G=0(x&oY^@q{9|!qM)K5@gbDE_o{X1XN|`uiVoGG>lH2cE^^I3r zZjYakkUT9JgX${#^^R%K-3j3IjjFMknVC76Ia4NM(F5zGX=9T;^v!E*OI<2T9TOjC zcVH|JU2@1rxK13$OfG?;r<6=s?ug7nuJ{W+qT1 z&M>N0gnbqng_um-zvqA#hsWUH@Ch8R3zLqQadT4?0)246o+>izjB~E3TiGGl2TcsK zS$N$DsVOt&LPWk*mn)wD!9yPfV=D6Q+ z9Jh%5j=Syg7ju-UIqA6DJJuz?CuN&6HzNgIB^MfCPuS$Rkc9qIeZ9_G4*0+5&zd~+ zHpH$umi98S-RfP%(O9Fc*`vP;PaoE|!U>R)j`$em1Fk6WefZ9>BPB2-Q_;A_f$sut z&@2!AEkjqpqcAXU>>cIHO`_ z#nc=`dukjTI$;GTDcg;_bm$fg<i}VciiW>rgccIB zte?@gARsg|?T12Md>{WdxHN|dcsQ?d|0vo!UCS6y4;?FxrC!4}%CX`yc%gH}vx^*Q z-|ZiW>HjW{694$P^CtfHP4Ku5JleryqVcsVg?rHgYxRk|VOJqf27ze;6Evqf{f(O{ zWTtSichE1N8F~{A8~#2e>=Uk4RZR=M0okIqX|8RfT`y;?Git+L3eRI>HyMp;D_R%v ztxKP?&AhuID3*J(OF3@)Yo)3YzF&6Yw4LS-Le(2b~)B)7q0qF zJm0Cx4bK9@Gaj!*k!2HParlSUl|2_01Yk?iNd@j?wn&ur7Kk&JUizbaP$#5)9Ncng*T3@L#;FxINubKTS<8al4`Q_85 z@=Ut&Rr6OB%$-^`tt>Nxx216(k)r<8g)g6#hO4>J{X_R)hM;ud*X4|cm18fQt;YzK zoBPE1!2V=m=rd4`bl5e&E>28}F(pu3fPpBYnXQn@TFS zug}k4zy0#6$rZ)_+No6ahPhU|s zDQ|;+#R~t;bA$h$do%yNA#d-5qQ$wniwiTy&Z*AJ!vz+>znO(hFG4FC>>fB^uf>=k z+vu!vWu}iwcH^{$PX-V$taSz+&TBP`xfo0IMs9<{j{ zVede@ptDj_?uteUf;Tzy)1ZyS5+Ae-vCh0o+HM`1pp_`B1e-fk&hE`wJTb}^l{jJS zmWKFd_kzzpxIBB=1eZNJd16}WP4lNjMO^npe_X=)=s1^S+s;4#>Gr+1&T=QLh;=*7 z@nyFxR9|q$yx;x{`*S#jsMt)Z8kdodfQfuY&8HV<6zdk|DBgx0IumhaGTNR!$%)pM zmjz+XnS{=oXBMFSvFwUJMI1OyaNwF`xW{^CGPsP1lW{D>RQ$ zl@e5V7S`8O(6I!@XtaQt?5wF-?v%9HZ3T0)rezI0;YR9R zd9(2x7f79&h1S?@eAoVd#QpGT{Y%xDBpz0e`-#H3L_;;k>#pftG0dr`0f%!MYAE2n zwxOo5Yaccv8xK5pb25zy{$d@~8nfcI3{H!2Fn!TAepgjF^a<2!g!dPLUJ=*=35p0s zqZe4S1d(Qqf--!+urWzlxC;W;Az=2F6`5U(n^=ppy3MzI5BlC8_}Q)ILj(EuK6vlI zAI;^r4y?C-_5JrdUg)@Vse@+{*o;nNwf*6Ue?VM(iZQDSmlY%>!V^%H=}CYxIS|@G zZDI-Vz>brK-N=d{Rg=q=*d~UViDQD90lyyTciNqa;|7+_E*O`eVtywr>C&I29}OXR1uu^)MW#0EXBVh#b8DDf5oIH zeCf~s7%0O7gL@$B55RJ=kynLghz1KeO!CCJoOW7zPI0a-ih^h}8>cmBjuvfdALIjO zyWead_>%tvGdU{W;fRYi#}2%i=yJqI*}iWty7ZGvFWL()e>XKJF)=6Aw%E30Ms{Kj zq7Tf$fq~~*OBiC7;1Ww|h@^rxj=1g3( z!^|0V06T&eKiuMoz;r80omDi;({OV&$+lgW&Hlqdrse- zvns=BOHPUz8~^RcZCjokSZBY*?MO+!zT}oiwpX}Zx40wCZ*&%1%Cv93v>#OwsM8M| zXR&^JfH$w5 zMxZR*A$&Cb%8zhb8EQ#G_@VDD)zMfm$PQ-)G$F3N$8?*st9{;s)Bw|XaiGim^57X5fa|K@M2vO8 zL+Vy!Ds2X~PT?g~H(-oO6M?N$On?A}eqw^P?1oKcJXAzyS0tHRQ*n}iT9PXxtzz!# zlvvk<{OZCnakwQOcIxLiv+G&sNAMwMkS#0T`wz}Bww=xowBEFS#2tvNwZHW)XBJ_^vc$-%oL}UH z%_+IOnmIRx7dV?IaB1@xT-NOI;PU1%xU|{XJSLa_%EeXB)&gRRwn@&f z!**YLywW1`Hv{9XyGOk*fhmFPWOUqlKee+ung+dd8)E z`4yKZag4|L(Jx}7e&UM7wtkjp@LlA0pYzuU>8`B8;Y@L{Fk!R?lq&*(F$#rw4-ZVn zhQm<;lOaOZ;2d;q*w#gNe{q5BzK+=R=;o-1sEFq1^w^I7JmYjlI&Y1@T?e;1BVQg) zKaunk6VR=O(TCDl8`T1gF)$iwq>n-%2HDzWhd^Y}$61DHB8*u^oRCmp zMb;K1JI#VUU%Pwd`t>XCe#r4(cl*lb&oBGDALZ$HW}2rWlc4X=k)gZKtY$i54$ZsI zMy|P;r;;blN*gK4wva=WeN>1j#W^y%mCWLP@b~=7)z8^UYbTW;CVKK#3 zS$KiH8B8C<&2Erk)zC0l3I4H0cq7)M!()bbW)>{U;k=OZ#+*e3nHz#J!_Hl~3+Id* zH)mmPP8FA+t8!*|y)%L_!{eM$w)M`JF@HM4cv{{Y6^N(BtVw>UO{kCm|3%BP8iobS zI)|cVPi9XsZyPhcVrq6(S>f18Q_L-?Gb(elt4a%ZK)jOwC{ahc<0OrCLl;RHOe zUD%A3XqSI-z60BtWNf`kysR|ye=I7N9LT6M6gDgLWFC8-k+5G&$jLIF|Im|>viYf` zsS};={5Z~iE_v*a-ElzZ?8^~;^MK=-QTmuKqQ2s-{zQRb?rZ2{%wc`ZgJWVM6DOvz zkMTO5K_`<{ws9dknWc~v8b$qY4Kp!&GZrLcUq=5LJ-7@QKbQ_o7+eYT=s8j8**J~O zVNBL@j-ICr^M!>%FHQ$^z?(!Y$10M;sMPbK!GFZ_lEFj3rNV0ATH!k3Hc@O4HVT`B z&7#;MY!$W%+l3v%-NHR8L#MDy*e&c4_6i>s_Ni>%zBG7w-z+ zQ_2Uz4}~8iXWVWiWDUAhgD%ye%U}(<4A!7aHRv)}gD!(L=rUM?E`v4bGFXEy)u2l? z=u!>3a1;1HBAqqpQVqHg%fe^Ypvzzlx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0pvzzl zx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0pvzzlx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0 zpvzzlx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0pvzzlx(wE!%U}(<4A!8_U=6wq)}RY# zMiW_sE`v4bGFXEygEiO4%^z$MeR)r-8M4u2;_6lyig7r<6uL`}N$U=RL~5 zQ`jZ!7WN2xg%1n+MDbZY|5W&jQeIO&uM6K$&Tk6e7QQ2VPrrU3{80E2(i0$!mB47+ z0+Ilk@YyYl$2$qoZDOK+oh+mr5+H|v0G6UfBp7AjIswwy44e;Jfw_ZztyexipfNJy_ZX~q z585enX1#k1*1HF-mCvkq585oBS??aSL?Y|mgO*5Sy?f9SiL7^z!Fu2ZRzPIECt}Q!2aHBaBJ4Du-NN|6Dm*8k z1`|>Hd`{Fm8H300JV7`~Z)QWPiLiCdAxF>Cg|kq$M97f8777;)eg)4N3;1XAi%!foQB zLFiM;J+Qlp#tz}V%Arxe?ox_h?=&f;S*2Zs8tL?-X_kyM;Z%Ug5*S z1LEbN@R0DZ@QCoJ@R;zp@PzQB@Rabh@JZoQD*stMpA()JUJ&*RFABe_H@_$RzVHXa zr&a!E^v<&?;d6R^UihNk`Ki)hQp#(R%LI%tou^b1eGVY_v^2$3xGvp@GCiy)#)@Jop$=yeL&REYI(Nt9eFKkvmEy7k|o3LHjA>1wOQ_fH8`4v6?TvT2a{zCYM@_bYH zmP-4!p5GC^tCWxQ{4w%@qZi|c$>5jh7ABy~lM(g!0x(=QmJd`#G%#Yh9|%7L=1S{wrFFT|x?E{puCy*!T9+%W%azvU z!oIzVuc&po(mL!DfY#+o>vE-axzf5^oPqocq*Lp1A@i4k)Vf^Qd?K|j7b7$xwJsMU zG$OSw7b7$xwJsOd{tS>>mkT@3XKG!pL9NR*sCBup_RN!7my0pa3qWdJE^I!LT9*qu z&u41gH1O*O#-lHr2JVQ7O34^}7S9uelQ6cQ27dYGtie*?64084u_&=xxLR+n5w2BA zjh@%(`9?k8EWB6PtlV0Jt->~8yRbvJTXo6?i6+jyM;Z%Ug5*SdoSvT-{!}S1>G?I4>UH6J!ViQW0_PZxf!_dKLbov9n1_@E zXz3h`X!tw{Qk?@yo&n|vrwj9ig~H;&b@DjMeo0PIg`E&}qgx$g(VXyFE zVV_FKT9|{m6_K?t2XiYTYhe!NRz%jq9L%kVtc5w4TM=0cb1=6eKC3)g3v)1^BK}k< ztc5wSHk8I|D&gzGH&o^~g>OmDZ|nIT;k!zCPj7x8{80E2e$AJqMI1itPrfWI?stZ5 z%$KFjm!-{@rOlV6MK7l}X=(FiY4c@i^JQuCWoh$eY4c@i^JQuCWoh$eY4c@i^JQuC zWoh$eY4c@i^JQuCWoh$eY4c@i^JQuCWoh$eY4c@i^JQuCWoh$eY4c@i^JQuCWoh$e zY4c@i^JQrZ!0%3@0Nh0ZJ;Fq#Ocu_9rWIhO{}ONs^uGZ5MywXD6|NID>-`pCtFTSj zF6T?UF35C*xLTN&w zG@($MP$*3(lqM8P6AGmXh0=sVX+ohip-`GoC`~AoCKO5&3Z)5!(u6{3LZLLFP?}IE zO(>Km6iO2cr3r=7ghFXTp){dTnouZBD3m4?!4|!Yvwi53ERq+fNM4{Kd4Y<+-HS*s zfzB7nCsQP!Op$ytMe@lM$tP1JpG=W_GDXHCdVinrQQ>1kdhLqj8!3jI{fNJ3UtbIh zNo0>-tRBDE$iQs67(G5Q8*QyvJ$^CD$>-_9S%W2b&ewCHaM7R>DN6=t1D6V`h0Cxr zsMuJo+|~%!D!oR}>-2o1p4Si7;mw=%yg}vJICu|IHVtkE)+*(e!QFV?rsoFXy?Vb< z&rL#(D2h?1M2;wmQKv+XD2h?1M2;wmAysnJDeMw<3wwmU!iR+i#N9#RA>m=+5#dqc zG2wA>bV7JicuII$_@wYDmHDim&k4^9F9`dE7lq%|o8J?DU-$zdM=8bdfKleps)Wz! z{pW=*>Ybk|{UxQmCds@mQpoFp;C060{d0M>Qp&NaU!-E8l__RPMtweG6Xs7QCQXg=hK}yr9cx z`WC#<$zwqJ7Q6<13tnjEi$MAoywFTO)3@NoxO6L!z6Gy*3trgLui%-!1ury{NZ*3j zpl`t|-+~wV$(-q1@S;!Wuk09v1 zx8Q}Ak_-A4yas&>UW2{`uY3z$gT4hXd_Me@z6GyA-+~vOAEwZ^;Dv;V^euQHVIqAC zUPzco-+~u9OzQM4cwrfc^euQ{8Hn^Pcwrfc^euSdrzO(2;Dw)-NZ*1Nep(`Z3tsqX ziS#XaF_s|Gx8TKCf=J(j*Pw5~ixCB%>09t(L_wr)!HW?Ek-i15LEnPcpl`uz(6`_< z=v(kYH&}l97QE2m7lHIGcrofA(zoD+y<$3j3to&mC>i<|yas&>UW2{`FSLt#Lf?Yd zpl`toO=Aju3tsrOi1aOZ4f+%p@9d0)WA|{U@0`P9nT5yTb4ot`JAYC zCPD8?p>KSD*5JdyMJQn@^onnCZK4!2WHBRxqVONUbP^ zRuFdx8yv6s5Pd%r#C+kekf$Em7>-TVE;X5LuF|DzXNhMRHoTb8QMFaIU6cN zjr1Al?FXEYF^)ijs%g~CSz%yqUq6#xLMB|2KV9pjaW%6hh6mpYn4*3@^8~u4MLw% z?!io;9BqequX1SAue+4u*E>y0X%;Un!d79MuwB?8+%4qHs~mMrD79J5E6&@2FR~b$SPYO>7PYa(EKBe-X)$=*wdEo_Nzwn~)yL$6` z!tV=zAbeWoe?}BJk1L1%u&$pMzNmLNn=6MFG5s~+>%uoAt2c#jNjKis^E<+KmGYk6 z{6P4j@FV^DG3d^RUR?xIf96AfehkbJP8a423x!Jte~Dk03af={h3kat2Y-)WH((|> zAGJ^1rkopujlw439_7|4>=JehdxX8hhlR(L^9kWe;VI#1;giC%!gIp&!VAKF;YH!I z%I!HlKQH{LQeM*YYbw?2!uNz92tNc?KqkKca*ewJt%=Ar?h3RcBKwgF^&=JPM=I2V zRHz53fc@b+T;r~QP7=AsT>+gWa*ewJI!UBeuaH%*kX5gcRj-g$uaH%*kX5gcRj-g$ zuaH%*kX5gcRj+{kVQ#eN6|(0Qus=+pRj-tfx>7#sO8KZOQTs03~KI%&Os4HQynNA;dCFXbhl|Jf9NRJfhqppPXi1blcLV85{s4F2oB7M}A27T0( z27T0(27T0(nEkO-^ifwr4n+E>E75|OKYi4dnEjC=ebkkh_Yvu%u7rK&uk=w@!aft} zqh16V_5)o)w{Qbwy9n0r55QVI*DKv8yhpf0xJ&34z9D>5__pvJ;YYy5pmGT462>CW z#i%c)crm7044E^fQqNU--Z1z&QZ~Woxfo^O^Jb;j>noqoFMLDzrtodyJHn5E)uLN1 zy49G={}w5vTP?cPqFXJx)tI64SJJH(-D=UT7Ts#pJHH~`YSbx_bgLm}BI#B`&P39^ z9(~^;AR>2wh?oGz58e;-2uPZwTKMzNN3;*7G~Uca`!Ho|mfz zm#YSsgS&pDum+c_2A8V_m!telXALfgt`b>;%b}%2*5Gn*L}U#v2S-HK;Bs)(Z>)r6 zcow)+SS?&DTqis(JRv+OJS99Wd{TH;cusg;ctO}NyeNE5_`L8X;A(NVTHLJ`cdNzS zYH_z(+^rUOtHs@FakpCBtrmBy#ocOgw_4n-7I&+~-D+{SMpV{_${JBwBPwe|1viXC zo@+#9ji{^=l<3{K_aS{5a8!=WP z9uyuD9u^)E9u*!FJ|lcf-@XgnfRW@AK<2qYd2Ya*h$)kWOVDyRKtGAq!nMM6!e+hG zB5W153EPDo!rj8-djEv*r0|sRwD3vcS>ZY1dEo_Nzwn~)Y325uo}U-KBqT>0Fv}o* zjCVGIqo;u$;akFYft%FgHmSvJ0!Pmxg)MFq`eGtm+$Qv}M7Fq1=wXR$ahuS?64~N5 zp@${1#ce_lOJs}NgdUd27Pkp~E0Ha(R&uVDoNFcLTFDt}wkT~aO7%LBa;}w}Yf&mb zQ_i)RA@Uu{xfUJ+BIR5Qj{%W#u7$^dNIBP{{6xyR7Ud^W&b25%k#ep@`H7TsEy_=% zoNG~jBIR5QF8EA2*MbWo|T$nokl)&8u`>|T$nokl+OsH0_=$urip9%G)BKt>DKqo$Zn zk8VBci_i4y)`MRnV@>P9FOjjP^_bBT87*9o5dohWYg!L!90D#CGS;*n(jYR{v>xLh zQe>=YJw`i3#+ufn_DPYkruC?OB4bVKQTs&3n%1NCiHtR^hdhalHLZs{iHtR^hdhal zHLX{4U%jIH>J{BrujsydMfcS!y02c*ef5g&tB3qqLdKfbL;gg@n$|=9M8=xdL;gg@ zn$|=9M8=xdL;gg@n$|8)}t<162_X=qb`VyHLXWo@R_lu^%#Ru0*p1Shwia1 z7;9ROaTik*|xz-GatsYZG)92GM;T4>V?R7wr!{vV!M#>Y}-&T zM8>mi1MhrhJli&`FEHmpXHrRhYGoGzMoHmHl z265UTP8-B&gE(yvrw!t?L7X;-(*|+cAWj>^X@fXz5T^~|v_YIUh|>mf+8|CF#A$;# zZ4jpo&_I@loHmHl265UTP8-B&gE(!#O3KT4hnzNu(*|+cAWj>^X@fXz5T^~|bUQeG z+1L(FUj;H!XuH;rwu4_jGg4?f_$4w@Xgl~NawTy)_5SRf zj$W9^n2qh=n#h=q?ckdEFlJ*rxF#}YV>`GeGG=2txF#}YV>`I!Gh;TkYyD`u){nMp z{b)P5W}b}M*bc5)TE=W_2iHW#Y-|VDd}hpsPqxS>TjY~1^2rwYWQ%;VMLyXgpKOs& zw#X-2 zTjY~1^2rwYWQ%;VMLyXgpKOs&w#X-2`j4$R}ImlP&Ve z7Wrh0e6mG8*&?58kx#bBCtKu`E%M10`DBZHvPC}GBA;xLPqxS>TjY~1^2rwYWQ%;V zMLyXgpKOs&w#X-2`j4$R}ImlP$UjHCPS*Z=!Ir(2LsO zIsviVm~yY^DBD(7|%OIYlmp53~B&{8y zwL`Rapa%I4Y3)D_5=mPopePqbyIOEKj2>PopePBTCC0Xk!~?c^YMT8fAGJ zWqBH9c^YMT8fAGJWqBH9c^YMT8fAGJWqBH9c^YMT8fAGJWqBH9c^YMT8fAHQNvgXf z)m@V6E=iRuI;B|Gp;UKCs=FlBU6Sf9Np+W`x=T{sC8_R`RCh_LyCl_JlIku=)el_Hz4pBJIM`_1E_p818)!zN!i_|&x@T1E_p818)!zMT?|!v+zuLQB?cJ~T?pJ&FtG)Zx-u-ItezkYM z+Ph!v-LLlUS9@<#yJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9o zXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dq zQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dq2B*u6X7EU46l=3Qgw660HbYlmMG8HH&CpUJ zJ%r8BRX)>0*bLsGTk;S#Ljw2VnI6Js@Q&FIxMm7Hgw2>K6B)(YEDvF`JcP}V22c?g>!CH_heVKX$76zL&shGr7!A#8?b66qmqhGr7!A#9e1uvs3$W_bvk zAw!mm9>Qkm36UPcW_bvk6W7Rj(h zGHj6yTO`94$*@H-Y>^CGB*PZTuthR#kqlcT!xqV~MKWxW3|l0_7Rj(hGHj6yTO`94 z$*@H-Y>^CGB*PZTuthR#kqlcT!xqV~MKWxW3|l0_7Rj(hGHj6yTO`94$*@H-Y>^CG zB*PZTuthR#kqlcT!xqV~MKWxW3|l0_7Rj(hGHj6yTO`94$*@&2Y?TaKCBs(9uvIc_ zl?+=Y!&b?#RWfXq3|l3`R>`ncGHjI$TP4F*$*@&2Y?TaKCBs(9uvIc_l?+=Y!&b?# zRWfXq3|l3`R>`ncGHjI$TP4F*$*@&2Y?TaKCBs(9uvIc_l?+=Y!&b?#RWfXq3|l3` zR>`ncGHjI$TP4F*$*@&2Y?TaKCBs(9uvIc_l?>Y?!#2sVO)_kg4BI5bHp#F}GHjC! z+a$v_$*@f_Y?BPzB*QkzuuU>-lMLG=!#2sVO)_kg4BI5bHp#F}GHjC!+a$v_$*@f_ zY?BPzB*QkzuuU>-lMLG=!#2sVO)_kg4BI5bHp#F}GHjC!+a$v_$*@f_Y?BPzB*Qkz zuuU>-lMLG=!#2sVO)_kg4BI5bHp#GEGHjO&+a<$x$*^5AY?ln%CBt^fuw622mkiq_ z!*cSh_%4n*#Z*n=GIH1Sy_#k3 zh0K{7XW4rpPa*Kv+TW)8rQdqDXu%kb*H%Q6xW^N zx>HUUxb76!o#MJvTz87=PI28S zt~rQdqDXu%kb*H%Q6xUtix=UPliR&(L-6gKO#C4ar?h@Bs z;<`&*cZusRaor`ZyTo;uxb70yUE;b+Tz84c{*InYeOI&w}>n?HK zC9b=~b(gsA64zbgx=UPliR&(L-6gKO#C4ar?h@Bs;<{U0cZ=(8aosJhyTx_4xb7C$ z*kuUa=oZ)A;u^7m(By7$-7T)W#dWv1?iSbG;<{U0cZ=(8aosJhyTx_4xb7C$-Qv1i zTz8A>ZgJf$uDiu`x47;W*WKc}TU>XG>uzz~Ev~!8b+@?g7T4Y4x<_31i0dA4-6O7h z#C4Cj?h)5L;<`s%_lWBraor=Xd&G5*xb6|xJ>t4YT=$6U9&z0xu6x9FkGSp;*FEC8 zM_l)a>mG64Bd&YIb&t625!XH9x<_31i0dA4-6O7h#C4Cj?h)5L;<`s%_loOYaosDf zd&PCHxb79#z2dr8T=$CWUUA(ku6xCGuek0N*S+GpS6ugs>t1o)E3SLRb+5SY71zDu zx>sEHitAo+-7Btp#dWW^?iJU);<{H{_loOYaosDfd&PCHxb79#z2dr8T=$CWN5Jn3 z#v|bFB_Jbv9|3nvVPx+kC>xQHy^o-5L`L>Lg0c}A+4~5}Mr35~BcR9}7}@&>@_)hD zr~LOR|9#4TpYq?Q{P!vUeae5I^53WY_bLB<%735o->3ZdDgS-Sf1mPyRQW%u{2x{R zk1GF1mH(s4|54@tsPcbQ`9G@sA65R3D*s27|D($PQRV-r@_$VEKc@U2Q~r-B|HqX7 zW6J+A<^P!Se@yv5ru-jM{*NjD$CUqL%KtIt|CsXcQ*2%zsMNy8!PvY$L@5v%o7acl zc{7l)d3~VEcNm+8Q$K`^t?I)#=M0dsd3_k|@R_lBeTvQN!)S-kjLqwVSLXrX66ML* zygqn!h>XqaQ*2(JV)Odo=V3Zy^ZMZJ;WK0N`VhJL0FbeHeHa(*12Q(R4=dDuAY=3T zu&%=LGd8aeBMKs8^ZMYk#Qqt01c{8z>%&NczcMzj556M4!`Qq&NR_-WHm?s-B{DXz z4^kyEHm?s-B{DXz4`V7KWApkjrXn&nuMcA?B4hLV;5&K%cu;sqcv#5TygvAjh>Xqa zgYRfFkg<7v@Es8uo7V^55s|TZeTa5u`5Bwn2M-dFv3Y&)AQAh87ln+?>qE3Nk+FGw zh;}A2Hm}ckMid#F*9XrMzh!J*AEKT44rBBB;9p|OYr@xsjLqvq+$@o?d3}hRMr%aI=Jg@knZGhNZ@+BaerVmRc;?7$zii!p*}DDEy%&(ek=uS~9pB-| zZNF^Ye%ZSHvUU4q>-Njm?U$|FFI%@?wr;;{-G14+{jzoYQM1g4Be(siSt3Vn`%$w* zj@%C@MHX3bb&( zj*z?@5|u-ua!6FLBC2%qa!6DTiOL~SIV38FMCFjE91@j7qH;)74vWfRQ8_GL4vWfR zQ8_FshehSEcsVR8hehSEs2moR!=iFnR1S;EVNp3ODn~@+h^QP9l_R2ZL{yH5$`MgH zA}U8j<%p;p5tSpNazs>)h{_RBIU*`YMCGWc92J$LqH%t16?BH+>U`Rk#TM(ME8W~o)Fy=qI*JgPl)abw9v2M zP0~Ffx+g^Ugy^0S-4mjFLUd1v?g`O7A-X3-_k`%45Z#lads1{yitb6#Jt?{;MfarW zo)q1aqI*(wPm1nI(LE`;Cq?(9=$;halcIZ4bWe)zNzpwex~D|DqI+6&PmAtp(LF7?r$zU) z=$;nc)1rG?bWe-!Y0*6`x~E0=wCJ7|-P59bT69l~?rG6|3UvF8r$Cp;_Wl%d{w#VptEAGyUyR+i%thhTX z?#_z4v*PZoxH~KE&WgLU;_j@tJ1g$aio3Jo?yR^wEAGyUyR+i%oamkt-E*RQPIS+S z?m5vtC%WfE_nhdS6Ww#7droxEiS9YkJtw;7ME9KNo)g`3qI*ts&x`JP(LFD^=SBCt z=$;qd^P+oRbkB?KdC@&Dy5~jryy%`6-SeV*UUbik?s?HYFS_SN_k!qN5Zw!+dqH$B zi0%c^y&$?5ME8Q|UJ%_2qI*GfFNp31(Y+wL7ex1h=w1-r3!-~Lbo)iOUv&FLw_kMo zMYmsc`$e~3bo)iOUv&FLw_kMoMYmsc`$e~3bo)iOUv&FLw_kKGita_xy(qdDMfalU zUKHJnqI*$vFN*F((Y+|T7e)7?=w1}v|F6BXfs*UI&-xu%UzTn8BTA$AD-$_&>{wdK zj^o%$abjC>Y_ZrOcI^lasR>36 zL5zi0ZkGjemthd52$P+ey)$IP4BW$V4h_%mxigkz$7$7WdnB{cnRo8qx%a;J|MC9s zec$*0KF?9td(`zFb-hPj?@`x#)b$E&QL#}NbuNj<$6W6**L%$M9&^3NT<nFFE49BpKO5#Cu8gQWEiAQoWQ! zyyG7KJnr$&;~xJ!?(xs#9{)V<@z3KP|2*#T&*L8dJnr$&DSOwHy=%%|J!S8jvUg3{ zyQb`2Q}(VYd)Jh`Ys%g=W$&7@cTL%=r|ex*_Ub8n*Oa|$%HB0)?|Mb`mV;MRFXd$} zuTVeY71jHMN-)P>k)MpJ{HXW*IlT$Lq8cglK7P!{7hD#-r!0SaMcSi0=>0$C?Wg_g zU-BMM#a@wRDd)VW?y})ynl2-kEtd=awd1nuGS>cSTXx!(owjACrI}OO&$DdWmYud` zr)}A3TXx!(owjACZP{sCcG{Mmwq>Vn*=bvL#@3m!b!Kdx8Cz$@)|s(&W^A1qTW7}B znXz?dY@HcfXU5i-2`PkQvBvOy^6(TDEQhwjmb?$L+t(TDEQhwjmb?$L+t z(TDEQhwjmb?$L+t(TDEQhsp_gQrDzMAG${$x-J=iP zqYvGq58a~=-J=f`TR4Uuedr#2=pKFO9)0K@edr#2=pKFO9)0K@edr#2=pOy7{OV{h zYyX**|GcbiR@=?Wb0}GDH!Gi^WVPL_yoHk0cC+#gN>!)h{RIQ(? z^;5Nes@6}{`l(t!RqLl}{nV_Vn)M?@J>6l=`l(qz!r<{9^i#8bYSvH9`l(qzHS4El z{nV_Vn)Oq&erncF&HAZXKQ-&8X8qLcKQ-&8X8);KKQ-&8X8qKxpPKbkvwmvUPtE$N zSwA)Fr)K@ste=|oQ?q_*)=$m)saZcY>!)V@)U2PH^;5HcYSvH9`kB+yKR=TA1AKiP2pq*e?nf3l&LqVYAg6iV`?8)_+(tlem+T~H%yHyUael&sxoNGH@t z{$xWsp=9kwLpq^k?M6d7p=9kwLoJAswHpn!AWGJ5G}MA9S-a8DxbO8U!3l5ZiAnjK z%U^Z*yi0Jx8_u6>sKs%Itlem+#Zj_$qoEc@`3;w>-Ds%AQL=WUp%zEU+Kq-<93^Wv z8hR#jW!7#q)cV-Z+Kq-!n7HryrO!n7HryrOZ8`(CX$v-O!KN+Pv;~{CVAB?C+Ja5ROpW~7f=yenX$v-O z!KN+Pv;~{CVAB?C+Ja46uxSf6ZNa83*t7+kwqVm1Y}$fNTd-*hHf_PCEjX`eb8RrM zI711>^1SSLKqc7P^RgNx7|Zjr8YLLZ^Nu<5iaCSYroS`q{?5GO412&>p4WS9MJ3qU z^NKuN8I0w5*^3g4<#}0+Z7`PSWjnUN;Om32JTHq-g0Va=i%^2GJTHq-(i@tWPS^%x zdEPyvd1-*_fU!KUh{Tn_Se{obq6A}kUfSSSFqY?~4N5SUBl~k?e~#?Wk^MQcKS%cG z$o?GJpCkKoWPgtA&yoE(vOh=m=g9u770dGH$o?GJpCkKoWPgtA&yoE(vOh=m=g9sX z*`Fi(b7X&x?9Y+?IkG=T_UFj{9NC{E`*UP}j_l8o{W-EfNA~B){v6q#Bl~k?e~#?W zk^MQcKS%cG$o?GJpCkKoWPgtA&yoE(vOh=m=g9sX*`Fi(b7X&x?9Y+?IkG=T_UFj{ z9NC{E`*UP}j_l8o{W-EfNA~B){v6q#Bm1-XRuqw1wqVN^Y}tY>Td-vdwrs(cE!eUJ zTee`!7HrvqEnBc<3$|=Qtu~hhTee`!7HrvqEnBc<3$|>*mMz$_1zWaY%NA_ef-PIH zWec`!!Imx9vISeVV9OS4*@7)wuw@IjY{8Z-*s=v%wqVN^Y}tY>Td-vdwrs(cE!eUJ zTee`!7HrvqEnBc<3$|>*mMz$_1zWaY%NA_ef-PIHWec`!!Imx9vISeVV9OS4*@7)w zuw@IjY{8Z-*s=v%wqVN^Y}tZsN2InRQri)!?TFNNL~1)CwH=Y#j!11sq_!hc+Yzbl zh}3pOYC9se9g*6ONNq=?wj)y85vlEn)OJK_J0i6mk=l+(ZAYZGEOH`fJ0i6mk=l+( zZAYZGBU0NDsqKi=c0_7BBDEcn+KxzVN2InRQri)!?TFNNL~1)CwH=Y#jz|lB8(ffA zPX`Nr8(i?);DS8-fcEg-yCC1@SKfOUtcL}^4KDa?aKUeb3w|41@Y~>m-v$@_Hn`xo z!3Dn!F33x{0`I*G@={9Pdl%%Tl)U#Y$V(}C?_H25u+4k#g5L%g{5H7Yx4{L!4KDa? za6xwGK6&q5@Y~>me1JW?_b$lp(?Q3Qb}VVfl6EX<$C7p|X~&XwENRD*b}VVfl6EX< z$C7p|X~&XwENRD*b}VVfl6EX<$C7p|X~&XwENRD*b}VVfl6EX<$C7p|X~&XwENRD* zb}VVfl6EX<$C7p|X~&XwEos-1b}ebwl6Ea=*OGQEY1fi=Eos-1b}ebwl6Ea=*OGQE zY1fi=Eos-1b}ebwl6Ea=*OGQEY1fi=Eos-1b}ebwl6Ea=*OGQEY1fi=Eos-1b}ebw zl6Ea=*OGQEsh9&5jblgS*wHw4G>#pOV@KoI(KvQAjvb9-N8{MhICeCS9gSm0S9XdF8l$BxFaqjBtL96K7vj>fU0aqMUuI~vE1#<8Pu>}VW28pn>tv7>S9 zXdF8l$BxFaqjBtL96K7vj>fU0aqMUuI~vE1#<8Pu>}VW28pn>tv7>S9XdF8l$BxFa zqjBtL96K7vj>fU0aqMUuI~vE1#<8Pu>}VW28pn>tv7>S9XdF8l$BxFaqjBtL96K7v zj>fU0aqMUuI~p&_S9b=B@={7>QWj;$X_X)gEy{vbm8=_Dl4H?$}Vvdy}oMR~7Q zTX`mBQ9gV?+svda%6o-JAire~GbxLT%#_TeEXsD&!c5Ad{Fyz>q%3+SWl`2-KQk$d z@^gM=CS_6fqDIyYEy`Y$tQ%UCy(n2Xv?zN~vTkV6GbxLnNm=wv%A#5Ucg0M~qO?cJ zOvU?i|MX-K#xwm~$@aXE#_qJCC8&_9*+pB_4uKr$cdv&mJP5+u}f+yEJ*4w`} z*tX`Iz3q#G>(|tK+v`@nxOQc4`;uVWW#8&;Um6Tw*6wXzmipQCpX_a~4{pDF_P)Zz z!D4P?w4Ad z^ry0=Y;j+9IDLO%yqtb0GnP$n8qAann+DQ@xuNWMNk2`F4`++%@@O{w$b*CFhbOY* z$30XTL(D#Q`wQpe5Tm1IjOi`@Afo@sA3rTG@34`_EKT8IF#j_ExoJw{9GxWNtcV6;p|wZxHnzc{g$E+^zZDA zI(dVNPG7c^ktT{cE;*6iof*oecW1_O`Ge`YE4B50)ibE?Y`VbwinAcK2jnweQNv zvbvd4L3W!cl(XaIT!tTp3gf%;xuJ4yd?YLNF%D$7_uj=1b7?uZR50ulpCd=vlK65x%n#gAkCY9>SBf0TR z9>wR%rL)hL4~$3|rQB25(m?uAInYo(Q!3?#bo@lIFi|L$lXO!rt^)X0P;{-Ip7d9(RuD+N{P7k!AyF0Hk-$o^6LGPvpl+4s?JGE7Oq?@=(ke2I5||FEXoy%1;mh? z<;<>JK37h(f(sWWOXJy6X<)Qmp4h&1>;C=w)fy%oAqIvDV_V;9f8TjaTa!le*?xQ8 z`iF@>!*WHX@5zdVzAFgq=Wl)rE%B%kghPMy=l#u3~IZqUp9$J zgX?phtd8ScYMgYL$|KP;Iz_RMRaeTB{X?J*rw)b(<%KP~wN{OQ-s zosOs;q%5kAvt>S0;y22>U03g4C6V~_b@;8X&$l|}hu_&YA^3kpXFsm{D(QZ>`|;qG zo+WbHhkmhVqNjrHy=blV>)B%MpT3v={tA)eK51AWhb+&ZDw{c0Dx^xgNzv#-m!KUx~v*_mFRcAbed`2QoaUjIHe>1xSc zCao^9_04V>4@%34)o5{8kyewI>{t&y;K7OP+;3i=@t$T%Ix=Z*6WYR~Xooo;m7Fo8 z{o}euUVDf9+qg8BmXD!tVH2t33~I)+`75pMQT;}}v{W>NH>HEmX&*-AJ(c7?ceTxVBw7idy%dQRlFZ=ekHs#5yf$a?c~a9@^-8 zJokn6-nwqCRh0CJXW&CmX}e@AJlcyuL$0x;z33~sKDwe+QZu?jqosfdl*rA#A#024 zCwkwfeOzOg{zhA8`{<<3#K&pfi3f5uWFn>_Q4$Y1x2*phx!d)R>8fY82yBh7j{1Ad z_lCauaXst$NBlRfiCBD(u6Vi*+Ct(($iWu((${9m=Vu*7-rQF6s;}VRh&6k4E_aue z6gfwrj*J3Q?<{g%w#QA6^4;M#a9&%VC*4r@TP}`FJhyKR z?t-H-@=C6SjnDKIBu%0Mt(NFYI~Y*S_~3-%!&d#@@BhiOfi{uEBXmZr8PgHxdi>cI z=ZU`ctl0b6H@%N+{S$ldx#^sx;mzy9d0XNscDB7rvLjwFA)8};qF7$vM86SP=3%|> z!F%^czxacl7X&Ljk`XB5@j$Y5cA0voMmARHsEafXvnp7v5ty~X#mX4GM57{?X$0hQ z@#0=7?$LJy8-jNRKNh?z*ciM!xH`BdxHh;>v*quR{@<&o&<)~b{Bf}xzF!jFtg)M0 z)Q-03uDAMK_%?Ax-7cn|JH+mCr$#fk>&@&ZgrR=7#xXyp2zjqOY==;t?+-p9%;X1y zox&L&4E~G|h9B1Z+Mm^X*dxKCLW+G%Z*!j(QtD@fPWl&uzbI_Z&jwEhnbbvE@$|F7 zUkU!X);ep3Tcb!nAN-x**MjF#8eK}Q5^Bxg(%5=8_=P}gcT=l_-wpmp@T}IK{IB4D zYW(zY@DG(sIu-neTIx{nj3UnOs5PDlemnR#$^`w_!Hi}`cWdPE-v<9K_(!tJ_k+Ko z+^N~%-^-S*;J*urV2`Y}SFJIxcKj={-RB$&N{S3+#m@bj^E{xZ-$CUh3I{28G5EW| z&jddm918xjW>dZw98IlBtquNf@O!C?Q|p5N5WE)rx6~!6OM|Zm|2TD7YJKosWySnf z>hjbTsVh@gDL1AOJg*F>|C)M7@SlQLgZ~_y3jSm8U$oZs8(Kl}&ETIZpZJB~p9KF( z7(SKYuLi#!yc~R6<7eMWZ3w=k5!zpt&A$@-ZOu6Rs?Y?!8vOm>uM1D$mx6zm`Z48_ z|I5_7QX5n6PFQnHNQ{K9hVs z<*#+087`DFLtJ>>Q?GBj_S^2eYvso$i-lD?viWj`UANtF$6Z%=d`f-Su}tr@kz!_F zc7QR1%Z78hyHc*?zb;8`-P`M2nKv&V$`ywu$9Ctl2a=yOz)~1bHg^@(zxU?)*YCxD zUR+dnJUOPE%MT~VTsb;9K9VU;j^#6x<>bfYTBS_Btag}EySpk_^MfC#m8JAIj4wNb z6+7-9d^A{}&y>g2&Q{W1bp*ek-qx9WH4GN4cez2o@q1D-qDrgRC}oh^IqtLe1@Vga zUG%AoUR||r)v?u2tl6-(a`EW8d)MVJx$2VdUV7uD?e$l!fAR9-6&tVEb7k$StKV^A z!}s3#!n;1R@!E~gzx&X;U%qbj_V$~?#T7q zuRs2tPWqYm4!n2vh7~sq-tgu3-TuCx{_(Hfxbyw*d;jm;K-iecRKwZMyBm2iJe_(GPy*gQsr4@%H@f-~P}QAKLYy7w%Yn z$KV~$-tqkp-}~WT{BZrwJMaABUBh?%gS%$8-?;tA_V0b#nNZ|Gkp)E#6d6#@=|lF@^ZJnabVMJrp2&G3OE1z?A`020R&HML(bq97`}P!Hxkp2Fw`nV!(<4CkBid@L|Al1jEtS^nu+7ZX=kD z;5CBP2u>pyjo>qa_X5@nIE-MtfbU}2Zs%gREtXQ;R$; zvb4z2B14OnD`aPpn?+_8d0Av-k&{J67Wr6YW08yXe$SGV_dvEUxxQpPkncdY1Gx^# z9Y5n4adLaf>?N<4tX^_@$>=4Ym#hYI8pvoMpMh)!av8{EAdi78267l?buM|kWbKl( zOU5pF3uG;jt4qcL`3ht!T$noDtM}Y(dO{rV^Y$`r!|w;XAKZR0`@!o6s~?Nrhw-44nIQwAigRc*^KDhcG*KF#iwGB%j z9DOkK!OsT|8{B*_^A)uX+Ab)#F!I622X-smYcTP_!v_l=9DFeF!M_Ll9^89h&^6)R zgLMziJs9`k+kGc84>n_y-k{38$pm>4d z1%ekS*`RlU-34+N=nr5&fcyaN1E>#RK7jZD-UDb4U_F4*1$Q~j<)Cwc%>}*#=nkNA zf$R|L8&roooFPDd0NDZL29Oy*UI1AEV>kM3rV?qH8<-lMbk=I$bTjIQvx!sZG)Jlyaw!@~;?D?FU=Fv7zJ4;wsO@G!x{0}l&4 z9Plu}!~YKZJFKcOzr*_u>pPt9u%*J43NPvvlIVmanvp~^lIVn_5E`sLASQvA1X>bU zNgySGlLSf<7)c-`fsb^nY|MJ#*W|6Q$y;BOr@kgneNB3JReE?;dU(}*g|HRE+zwMA zJcV$!!%+xBA^e1}6T(dhGa69MF?nBH3-unJcFM*Lqr+%iyeQDuPhd~|wblB73PKP-i-gH>g;Y^1y9lmtf(&0)6 z69_~g@PI%A0t*NvAmae3u5;JI*U!+x*R2J3&tW}>^Bl%=_|9QFhwB`sb9l~SIfvsM zhI9DMFN}CPAfMhRpI$4UUMrv8C!gM@mMMlZ*%AhF_{U)%hkN`{ec&C3bsWxd7{}oo zhix3LahS&88HZ&Yj&T^q;TMNp{MU3Y%;NBh!zvD^IE>;i=mVQLT;ec^!y^uhI2__I zh{GQadpO+TFo(k%4r};@q2CQ^58qLHIJ+kdxA&cTKK!WOD@@+-c*Eiihc~@W-oyX= z>v0wC?$fdLNyXMDRU24kPpigf)dHXO8e(|5Vd;jW8-{N9xnbvqn;T|sc)4Naenj6e za>K_B8~3O64HGv!+^}%N!3_g9{M)c^!@UjjHoV(EDmKHj4a+th+c0dyuMN94+}bc} z!>bLeHk{fpYQv`un>Jk9Floc14U0A$+AwJUrs{<~8}4kFv*FE#H5<o^5&`h8LPK z!lcgvR`V&fw4YOaJgm2uO=@9>)v~^#7Ij1|>hy>N+|T_{iO1AdlfK3&wU$%8zQ#87 zHMXhMoKimn<^}p0r__25E2bV+Og*fadRQ^_uwv?A#ni)UAxG3gj;MtkQ42Yu7IH)_ z28USHwx+vGha zXY2;B7(rqLhY=J;Fc?8#1b-3qMUWb>Iqx-~F81GT;6m=d-|P_j!2LQ8Oa>4cKv;zP z80KTJ6+u=6R}oZ2Fcm>m1bqSQ1&|lOT>wWB6h$x;K~Mxg5%ff`6G2V{HxblCFcU#c z1TPV^M6eP;N(3hnl*AwPJ_ITvn268m19}432_Pqcga{5ID2QMnf`ADAA?Sx-AA)=c z?jfj$U>?4x&ryBAMgZ#&q(g8HK{*8D5Ih9X5I{Es+Yn?!a1B88G>a9 zk|8*TpcsN-*wqL8LeLAh$s$L47I<0Oe_7A9m!x#~ru%7E<%vLi@A-~@pZ1V#`DLEr;z&{aTLe9j0mAo+pg$9jIS^gz-BzYlg? zu=}3$)y0UV<3NdO=mTC~qz_nqAoVT%6|Yj%5^u0R@Y}*}3%4!Iw(#1*Y73_=jJELE z!e$GXEljrX*ur89hb;`YOQkayYT>7aD-)(ncrszhgd-D%O!zTj$AlXbW=wc7Va0?K z6GlwHrMa)AxysVpZ|SdinY;ilD0rMs>V4s)V*E+P_>+q9Cl%vQD#o8w zj6bQ^d{UkOOOcqO^p^7*ub&B{D?FpHjKVPrlPmn9u#3Vi3bQD@qOgj>DGH+~e4?<4 z!X*lmC_JLDh{7QXgDCu=u!q7O3UesDp|FO+846=4e4+ijS=9cOx|@pR&^m5?;9}jN z`&nAwI{&0Tf1VD@^IYdL&vky!7@gmDcVUd(EVqYqX%AQFdGdgIO%Lcf@_;kX;6Q`{ z5e7^6D`Av`PZBmsxFlhcghvt8^Bsw}PdUbJ%wUHOEYS| ze3c(QzqB-ZS^61-4Bw( z^-jq;r$fp+IiwtfvT_j0%0VbA2cfJyltaoxIix(4L&`@eFVWok(rae<9drWyV z>%z|_yYMAAzvo}h@4H)`={uL1zGZEli?%MzRmRIUJ{KD=YwKLJbz!!M^ZN<5e0P6% z9Ej!CdoHc_?K={Z64u!&tpT`1YXJJVap1y%3U_l44X%#__huY8@ZZ3G`_n>w>tnwC zg188l+S5W)`@FE-Kz{o^Hl ziV)uZUhtgo&YllE6;O#?|T7Oz2q*>$Jx;Y`e~>tDy|x=1KoEA&?%w@XX{!r2qb9#~|- zjV?Fmb0eAHHkWsnCMQb4-7a_Z(7a9~dr@1;b%GnlHmH4Twf=u&Z|_FeloIk@a_rqY z;@;pAQ02gr152)tA}8#)l#utp2wNf4ze|OQw;~u!tx@@~KAUyrpR%4)vgUolxBHxI d3)lz{Xfc9M412p literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/fonts/RobotoSlab-Bold.ttf b/docs/_build/html/_static/fonts/RobotoSlab-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..df5d1df2730433013f41bf2698cbe249b075aa02 GIT binary patch literal 170616 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 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.eot b/docs/_build/html/_static/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..84677bc0c5f37f1fac9d87548c4554b5c91717cf GIT binary patch literal 56006 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�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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.ttf b/docs/_build/html/_static/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..96a3639cdde5e8ab459c6380e3b9524ee81641dc GIT binary patch literal 112160 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 zÄ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 V*eEM;|ho{trTA6=?tf literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..628b6a52a87e62c6f22426e17c01f6a303aa194e GIT binary patch literal 65452 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 q%u^)w#YX9ZIvhtu?9Cy6YRi6f6G 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 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/jquery-1.11.1.js b/docs/_build/html/_static/jquery-1.11.1.js new file mode 100644 index 00000000..d4b67f7e --- /dev/null +++ b/docs/_build/html/_static/jquery-1.11.1.js @@ -0,0 +1,10308 @@ +/*! + * jQuery JavaScript Library v1.11.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-05-01T17:42Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var deletedIds = []; + +var slice = deletedIds.slice; + +var concat = deletedIds.concat; + +var push = deletedIds.push; + +var indexOf = deletedIds.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + version = "1.11.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1, IE<9 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: deletedIds.sort, + splice: deletedIds.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( support.ownLast ) { + for ( key in obj ) { + return hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1, IE<9 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( indexOf ) { + return indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + while ( j < len ) { + first[ i++ ] = second[ j++ ]; + } + + // Support: IE<9 + // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) + if ( len !== len ) { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: function() { + return +( new Date() ); + }, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.19 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-04-18 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "

"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowclip^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * Clean-up method for dom ready events + */ +function detach() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } +} + +/** + * The ready event handler and self cleanup method + */ +function completed() { + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + + +var strundefined = typeof undefined; + + + +// Support: IE<9 +// Iteration over object's inherited properties before its own +var i; +for ( i in jQuery( support ) ) { + break; +} +support.ownLast = i !== "0"; + +// Note: most support tests are defined in their respective modules. +// false until the test is run +support.inlineBlockNeedsLayout = false; + +// Execute ASAP in case we need to set body.style.zoom +jQuery(function() { + // Minified: var a,b,c,d + var val, div, body, container; + + body = document.getElementsByTagName( "body" )[ 0 ]; + if ( !body || !body.style ) { + // Return for frameset docs that don't have a body + return; + } + + // Setup + div = document.createElement( "div" ); + container = document.createElement( "div" ); + container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; + body.appendChild( container ).appendChild( div ); + + if ( typeof div.style.zoom !== strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; + + support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; + if ( val ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); +}); + + + + +(function() { + var div = document.createElement( "div" ); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( elem ) { + var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], + nodeType = +elem.nodeType || 1; + + // Do not set data on non-element DOM nodes because it will not be cleared (#8335). + return nodeType !== 1 && nodeType !== 9 ? + false : + + // Nodes accept data unless otherwise specified; rejection can be conditional + !noData || noData !== true && elem.getAttribute("classid") === noData; +}; + + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements (space-suffixed to avoid Object.prototype collisions) + // throw uncatchable exceptions if you attempt to set expando properties + noData: { + "applet ": true, + "embed ": true, + // ...but Flash objects (which have this classid) *can* handle expandos + "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; +}; +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + // Minified: var a,b,c + var input = document.createElement( "input" ), + div = document.createElement( "div" ), + fragment = document.createDocumentFragment(); + + // Setup + div.innerHTML = "
a"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = + document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: IE < 9, Android < 4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!support.noCloneEvent || !support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + deletedIds.push( id ); + } + } + } + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( " + {% include '/static/docs/index.html' ignore missing %} + + + +--> + +
+ +
+ + +{% endblock %} diff --git a/skyline/webapp/templates/layout.html b/skyline/webapp/templates/layout.html new file mode 100644 index 00000000..170015a1 --- /dev/null +++ b/skyline/webapp/templates/layout.html @@ -0,0 +1,119 @@ + + + + + + + Skyline + + + + + + + + + + + + + + + + +
+ {% block body %}{% endblock %} +
+ +
+ + + {% if count_request == 'true' %} + + + {% else %} + + + + + + + {% endif %} + + {% for item in anomalies %} + + + {% if count_request == 'true' %} + + {% else %} + + + + + + {% endif %} + + {% endfor %} +
{{ results }}
anomaly countPanorama metric linkanomaly idmetricdatapointtimestampfull_durationcreated
{{ item[0] }} + {{ item[1] }} + {{ item[1] }} {{ item[2] }} {{ item[3] }} {{ item[4] }} {{ item[5] }}
+ +

+ {% if count_request != 'true' %} +

+
+ anomaly id: +
+
+ metric: +
+ +
+ anomalous data point: +
+
+
full duration: +
+
+
created date: +
+ + +
+
+
+
+ time shifted (hours): +
+
Timeseries at full duration (time shifted) for the anomalous datapoint
+
+
+ +
+ +
anomaly_id graphite_link
+
metric
+
Original, unaggregated anomalous datapoint
+
+
+
+ {% endif %} +{% endif %} +
+ + + + +{% endblock %} diff --git a/skyline/webapp/templates/rebrow_key.html b/skyline/webapp/templates/rebrow_key.html new file mode 100644 index 00000000..dbf2fcdf --- /dev/null +++ b/skyline/webapp/templates/rebrow_key.html @@ -0,0 +1,125 @@ +{% extends "layout.html" %} +{% block body %} + + + +

key :: {{ key }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
key{{ key }}
base64 encoded key{{ key|urlsafe_base64 }}
Type{{ type }}
msg-pack encoded{{ msg_packed_key }}
Size{{ size }} Bytes + + {% if size > (1024*1024) %} + ({{ "%.1f"|format(size / 1024.0 / 1024.0) }} MB) + {% elif size > 1024 %} + ({{ "%.1f"|format(size / 1024.0) }} KB) + {% endif %} +
Expiration + {% if ttl < 0 %} + No expiration set + {% else %} + {{ ttl }} Seconds from now ({{ expiration }}) + {% endif %} +
+ + {% if type == "string" %} + +

String Value

+ {{ value }} + + {% elif type == "list" %} + +

Values

+
    + {% for item in value %} +
  1. {{ item }}
  2. + {% endfor %} +
+ + {% elif type == "set" %} + +

Values

+
    + {% for item in value|sort %} +
  • {{ item }}
  • + {% endfor %} +
+ + {% elif type == "hash" %} + +

Hash keys and values

+ + + + + + + + + + {% for item in value|dictsort %} + + + + + + {% endfor %} + +
KeyValue
{{ item[0] }}{{ item[1] }}
+ + {% elif type == "zset" %} + +

Sorted set entries

+ + + + + + + + + + {% for item in value %} + + + + + + {% endfor %} + +
RankScoreValue
{{ loop.index0 }}{{ item[1] }}{{ item[0] }}
+ + {% endif %} + +{% endblock %} diff --git a/skyline/webapp/templates/rebrow_keys.html b/skyline/webapp/templates/rebrow_keys.html new file mode 100644 index 00000000..9287cdb0 --- /dev/null +++ b/skyline/webapp/templates/rebrow_keys.html @@ -0,0 +1,90 @@ +{% extends "layout.html" %} +{% block body %} + + + + + + +{% if pattern != "*" %} +

Keys matching {{ pattern }}

+

{{ num_keys }} out of {{ dbsize }} matched

+{% else %} +

All Keys

+

{{ num_keys }} Keys available

+{% endif %} + + + + + + + + + + + + + {% for key in keys %} + + + + + + + {% endfor %} + +
#TypeKey
{{ loop.index + offset }}{{ types[key] }}{{ key }}
+ + {% if num_keys > perpage %} +
    + {% if offset > 0 %} +
  • «
  • + {% endif %} +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 5
  • + {% if num_keys > (offset + perpage) %} +
  • »
  • + {% endif %} +
+ {% endif %} + +{% endblock %} diff --git a/skyline/webapp/templates/rebrow_login.html b/skyline/webapp/templates/rebrow_login.html new file mode 100644 index 00000000..ca38a10b --- /dev/null +++ b/skyline/webapp/templates/rebrow_login.html @@ -0,0 +1,53 @@ +{% extends "layout.html" %} +{% block body %} + + + +
+ +
+ +

+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +

If unsure, use 0 (zero).

+
+
+ +
+
+ +
+
+
+
+ +
+
+ +{% endblock %} diff --git a/skyline/webapp/templates/rebrow_server_db.html b/skyline/webapp/templates/rebrow_server_db.html new file mode 100644 index 00000000..959f78ee --- /dev/null +++ b/skyline/webapp/templates/rebrow_server_db.html @@ -0,0 +1,131 @@ +{% extends "layout.html" %} +{% block body %} + + + + + + + + + + +
+ +
+ +

Used Memory: {{ info["used_memory_human"] }}, peak: {{ info["used_memory_peak_human"] }}

+ +

{{ dbsize }} Keys available.

+ +
Find keys matching pattern:
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + {% for item in info|dictsort %} + {% if "cmdstat_" not in item[0] %} + + + + + + {% endif%} + {% endfor %} + +
KeyValueDescription +
{{ item[0] }}{{ item[1] }} + {% if item[0] in serverinfo_meta and serverinfo_meta[item[0]] != None %} + {{ serverinfo_meta[item[0]]|safe }} + {% endif%} +
+ +
+
+ + + + + + + + + + + {% for item in info|dictsort %} + {% if item[1]["calls"] %} + + + + + + + + {% endif%} + {% endfor %} + +
CommandCallsCall ShareDuration (Microseconds) + Duration/Call +
{{ item[0]|replace("cmdstat_", "") }}{{ item[1]["calls"] }}{{ "%.2f"|format(item[1]["calls"] / info["total_commands_processed"] * 100) }} %{{ item[1]["usec"] }}{{ "%.1f"|format(item[1]["usec_per_call"]) }}
+
+
+ +
+
+ +{% endblock %} diff --git a/skyline/webapp/templates/uh_oh.html b/skyline/webapp/templates/uh_oh.html new file mode 100644 index 00000000..89a73aca --- /dev/null +++ b/skyline/webapp/templates/uh_oh.html @@ -0,0 +1,39 @@ + + + + + + + Skyline + + + + + + + + +{% if message %} +
+
+ uh oh there is a problem...

+ Message: {{ message }} +
+
+{% endif %} + + diff --git a/skyline/webapp/webapp.py b/skyline/webapp/webapp.py new file mode 100644 index 00000000..8b4cb97d --- /dev/null +++ b/skyline/webapp/webapp.py @@ -0,0 +1,809 @@ +import redis +import logging +import simplejson as json +import sys +import re +import traceback +from msgpack import Unpacker +from functools import wraps +from flask import Flask, request, render_template, redirect, Response, abort +from daemon import runner +from os.path import dirname, abspath, isdir +from os import path +import string +from os import remove as os_remove +from time import time, sleep + +# @added 20160703 - Feature #1464: Webapp Redis browser +import time +from datetime import datetime, timedelta +import os +import base64 +from msgpack import unpackb, packb +import string +# flask things for rebrow +from flask import session, g, url_for, flash, Markup, json + +from logging.handlers import TimedRotatingFileHandler, MemoryHandler + +import os.path +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) +sys.path.insert(0, os.path.dirname(__file__)) +import settings +import skyline_version +from skyline_functions import get_graphite_metric, write_data_to_file + +from backend import panorama_request, get_list + +skyline_version = skyline_version.__absolute_version__ + +skyline_app = 'webapp' +skyline_app_logger = '%sLog' % skyline_app +logger = logging.getLogger(skyline_app_logger) +skyline_app_logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app) +skyline_app_loglock = '%s.lock' % skyline_app_logfile +skyline_app_logwait = '%s.wait' % skyline_app_logfile +logfile = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + +# werkzeug access log +access_logger = logging.getLogger('werkzeug') + +REDIS_CONN = redis.StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) + +ENABLE_WEBAPP_DEBUG = True + +app = Flask(__name__) +app.config['PROPAGATE_EXCEPTIONS'] = True + +graph_url_string = str(settings.GRAPH_URL) +PANORAMA_GRAPH_URL = re.sub('\/render\/.*', '', graph_url_string) + + +@app.before_request +def limit_remote_addr(): + """ + This function is called to check if the requesting IP address is in the + settings.WEBAPP_ALLOWED_IPS array, if not 403. + """ + ip_allowed = False + for web_allowed_ip in settings.WEBAPP_ALLOWED_IPS: + if request.remote_addr == web_allowed_ip: + ip_allowed = True + + if not settings.WEBAPP_IP_RESTRICTED: + ip_allowed = True + + if not ip_allowed: + abort(403) # Forbidden + + +def check_auth(username, password): + """This function is called to check if a username / + password combination is valid. + """ + if settings.WEBAPP_AUTH_ENABLED: + return username == settings.WEBAPP_AUTH_USER and password == settings.WEBAPP_AUTH_USER_PASSWORD + else: + return True + + +def authenticate(): + """Sends a 401 response that enables basic auth""" + return Response( + 'Forbidden', 401, + {'WWW-Authenticate': 'Basic realm="Login Required"'}) + + +def requires_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + if settings.WEBAPP_AUTH_ENABLED: + auth = request.authorization + if not auth or not check_auth(auth.username, auth.password): + return authenticate() + return f(*args, **kwargs) + else: + return True + return decorated + + +@app.route("/") +@requires_auth +def index(): + + s = time.time() + if 'uh_oh' in request.args: + try: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Testing uh_oh"), 200 + except: + error_string = traceback.format_exc() + logger.error('error :: failed to render uh_oh.html: %s' % str(error_string)) + return 'Uh oh ... a Skyline 500 :(', 500 + + try: + return render_template( + 'now.html', version=skyline_version, + duration=(time.time() - s)), 200 + except: + error_string = traceback.format_exc() + logger.error('error :: failed to render index.html: %s' % str(error_string)) + return 'Uh oh ... a Skyline 500 :(', 500 + + +@app.route("/now") +@requires_auth +def now(): + s = time.time() + try: + return render_template( + 'now.html', version=skyline_version, duration=(time.time() - s)), 200 + except: + error_string = traceback.format_exc() + logger.error('error :: failed to render now.html: %s' % str(error_string)) + return 'Uh oh ... a Skyline 500 :(', 500 + + +@app.route("/anomalies.json") +def anomalies(): + try: + anomalies_json = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + with open(anomalies_json, 'r') as f: + json_data = f.read() + except: + logger.error('error :: failed to get anomalies.json: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + return json_data, 200 + + +@app.route("/panorama.json") +def panorama_anomalies(): + try: + anomalies_json = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) + panorama_json = string.replace(str(anomalies_json), 'anomalies.json', 'panorama.json') + logger.info('opening - %s' % panorama_json) + with open(panorama_json, 'r') as f: + json_data = f.read() + except: + logger.error('error :: failed to get panorama.json: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + return json_data, 200 + + +@app.route("/app_settings") +def app_settings(): + + try: + app_settings = {'GRAPH_URL': settings.GRAPH_URL, + 'OCULUS_HOST': settings.OCULUS_HOST, + 'FULL_NAMESPACE': settings.FULL_NAMESPACE, + 'SKYLINE_VERSION': skyline_version, + 'PANORAMA_ENABLED': settings.PANORAMA_ENABLED, + 'PANORAMA_DATABASE': settings.PANORAMA_DATABASE, + 'PANORAMA_DBHOST': settings.PANORAMA_DBHOST, + 'PANORAMA_DBPORT': settings.PANORAMA_DBPORT, + 'PANORAMA_DBUSER': settings.PANORAMA_DBUSER, + 'PANORAMA_DBUSERPASS': 'redacted', + 'PANORAMA_GRAPH_URL': PANORAMA_GRAPH_URL + } + except Exception as e: + error = "error: " + e + resp = json.dumps({'app_settings': error}) + return resp, 500 + + resp = json.dumps(app_settings) + return resp, 200 + + +@app.route("/version") +def version(): + + try: + version_settings = {'SKYLINE_VERSION': skyline_version} + resp = json.dumps(version_settings) + return resp, 200 + except: + return "Not Found", 404 + + +@app.route("/api", methods=['GET']) +def data(): + if 'metric' in request.args: + metric = request.args.get('metric', None) + try: + raw_series = REDIS_CONN.get(metric) + if not raw_series: + resp = json.dumps( + {'results': 'Error: No metric by that name - try /api?metric=' + settings.FULL_NAMESPACE + 'metric_namespace'}) + return resp, 404 + else: + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_series) + timeseries = [item[:2] for item in unpacker] + resp = json.dumps({'results': timeseries}) + return resp, 200 + except Exception as e: + error = "Error: " + e + resp = json.dumps({'results': error}) + return resp, 500 + + if 'graphite_metric' in request.args: + logger.info('processing graphite_metric api request') + for i in request.args: + key = str(i) + value = request.args.get(key, None) + logger.info('request argument - %s=%s' % (key, str(value))) + + valid_request = True + missing_arguments = [] + + metric = request.args.get('graphite_metric', None) + from_timestamp = request.args.get('from_timestamp', None) + until_timestamp = request.args.get('until_timestamp', None) + + if not metric: + valid_request = False + missing_arguments.append('graphite_metric') + logger.error('graphite_metric argument not found') + else: + logger.info('graphite_metric - %s' % metric) + + if not from_timestamp: + valid_request = False + missing_arguments.append('from_timestamp') + logger.error('from_timestamp argument not found') + else: + logger.info('from_timestamp - %s' % str(from_timestamp)) + + if not until_timestamp: + valid_request = False + missing_arguments.append('until_timestamp') + else: + logger.info('until_timestamp - %s' % str(until_timestamp)) + + if not valid_request: + error = 'Error: not all arguments where passed, missing ' + str(missing_arguments) + resp = json.dumps({'results': error}) + return resp, 404 + else: + logger.info('requesting data from graphite for %s from %s to %s' % ( + str(metric), str(from_timestamp), str(until_timestamp))) + + try: + timeseries = get_graphite_metric( + skyline_app, metric, from_timestamp, until_timestamp, 'json', + 'object') + except: + error = "Error: " + traceback.print_exc() + resp = json.dumps({'results': error}) + return resp, 500 + + resp = json.dumps({'results': timeseries}) + cleaned_resp = False + try: + format_resp_1 = string.replace(str(resp), '"[[', '[[') + cleaned_resp = string.replace(str(format_resp_1), ']]"', ']]') + except: + logger.error('error :: failed string replace resp: ' + traceback.format_exc()) + + if cleaned_resp: + return cleaned_resp, 200 + else: + resp = json.dumps( + {'results': 'Error: failed to generate timeseries'}) + return resp, 404 + + resp = json.dumps( + {'results': 'Error: No argument passed - try /api?metric= or /api?graphite_metric='}) + return resp, 404 + + +@app.route("/docs") +@requires_auth +def docs(): + s = time.time() + try: + return render_template( + 'docs.html', version=skyline_version, duration=(time.time() - s)), 200 + except: + return 'Uh oh ... a Skyline 500 :(', 500 + + +@app.route("/panorama", methods=['GET']) +@requires_auth +def panorama(): + if not settings.PANORAMA_ENABLED: + try: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Panorama is not enabled, please see the Panorama section in the docs and settings.py"), 200 + except: + return 'Uh oh ... a Skyline 500 :(', 500 + + s = time.time() + try: + apps = get_list('app') + except: + logger.error('error :: %s' % traceback.print_exc()) + apps = ['None'] + try: + sources = get_list('source') + except: + logger.error('error :: %s' % traceback.print_exc()) + sources = ['None'] + try: + algorithms = get_list('algorithm') + except: + logger.error('error :: %s' % traceback.print_exc()) + algorithms = ['None'] + try: + hosts = get_list('host') + except: + logger.error('error :: %s' % traceback.print_exc()) + hosts = ['None'] + + try: + request_args_len = len(request.args) + except: + request_args_len = 0 + + latest_anomalies = False + if request_args_len == 0: + latest_anomalies = True + try: + panorama_data = panorama_request() + # logger.info('panorama_data - %s' % str(panorama_data)) + return render_template( + 'panorama.html', anomalies=panorama_data, app_list=apps, + source_list=sources, algorithm_list=algorithms, + host_list=hosts, results='Latest anomalies', + version=skyline_version, duration=(time.time() - s)), 200 + except: + logger.error('error :: failed to get panorama: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + else: + search_request = True + count_request = 'false' + if 'count_by_metric' in request.args: + count_by_metric = request.args.get('count_by_metric', None) + if count_by_metric == 'true': + count_request = 'true' + try: + query, panorama_data = panorama_request() + # logger.info('panorama_data - %s' % str(panorama_data)) + results_string = 'Found anomalies for ' + query + return render_template( + 'panorama.html', anomalies=panorama_data, app_list=apps, + source_list=sources, algorithm_list=algorithms, + host_list=hosts, results=results_string, count_request=count_request, + version=skyline_version, duration=(time.time() - s)), 200 + except: + logger.error('error :: failed to get panorama: ' + traceback.format_exc()) + return 'Uh oh ... a Skyline 500 :(', 500 + + +# Feature #1448: Crucible web UI - @earthgecko +# Branch #868: crucible - @earthgecko +# This may actually need Django, perhaps this is starting to move outside the +# realms of Flask.. +@app.route("/crucible", methods=['GET']) +@requires_auth +def crucible(): + crucible_web_ui_implemented = False + if crucible_web_ui_implemented: + try: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Sorry the Crucible web UI is not completed yet"), 200 + except: + return render_template( + 'uh_oh.html', version=skyline_version, + message="Sorry the Crucible web UI is not completed yet"), 200 + +# @added 20160703 - Feature #1464: Webapp Redis browser +# A port of Marian Steinbach's rebrow - https://github.com/marians/rebrow +# Description of info keys +# TODO: to be continued. +serverinfo_meta = { + 'aof_current_rewrite_time_sec': "Duration of the on-going AOF rewrite operation if any", + 'aof_enabled': "Flag indicating AOF logging is activated", + 'aof_last_bgrewrite_status': "Status of the last AOF rewrite operation", + 'aof_last_rewrite_time_sec': "Duration of the last AOF rewrite operation in seconds", + 'aof_last_write_status': "Status of last AOF write operation", + 'aof_rewrite_in_progress': "Flag indicating a AOF rewrite operation is on-going", + 'aof_rewrite_scheduled': "Flag indicating an AOF rewrite operation will be scheduled once the on-going RDB save is complete", + 'arch_bits': 'Architecture (32 or 64 bits)', + 'blocked_clients': 'Number of clients pending on a blocking call (BLPOP, BRPOP, BRPOPLPUSH)', + 'client_biggest_input_buf': 'biggest input buffer among current client connections', + 'client_longest_output_list': None, + 'cmdstat_client': 'Statistics for the client command', + 'cmdstat_config': 'Statistics for the config command', + 'cmdstat_dbsize': 'Statistics for the dbsize command', + 'cmdstat_del': 'Statistics for the del command', + 'cmdstat_dump': 'Statistics for the dump command', + 'cmdstat_expire': 'Statistics for the expire command', + 'cmdstat_flushall': 'Statistics for the flushall command', + 'cmdstat_get': 'Statistics for the get command', + 'cmdstat_hgetall': 'Statistics for the hgetall command', + 'cmdstat_hkeys': 'Statistics for the hkeys command', + 'cmdstat_hmset': 'Statistics for the hmset command', + 'cmdstat_info': 'Statistics for the info command', + 'cmdstat_keys': 'Statistics for the keys command', + 'cmdstat_llen': 'Statistics for the llen command', + 'cmdstat_ping': 'Statistics for the ping command', + 'cmdstat_psubscribe': 'Statistics for the psubscribe command', + 'cmdstat_pttl': 'Statistics for the pttl command', + 'cmdstat_sadd': 'Statistics for the sadd command', + 'cmdstat_scan': 'Statistics for the scan command', + 'cmdstat_select': 'Statistics for the select command', + 'cmdstat_set': 'Statistics for the set command', + 'cmdstat_smembers': 'Statistics for the smembers command', + 'cmdstat_sscan': 'Statistics for the sscan command', + 'cmdstat_ttl': 'Statistics for the ttl command', + 'cmdstat_type': 'Statistics for the type command', + 'cmdstat_zadd': 'Statistics for the zadd command', + 'cmdstat_zcard': 'Statistics for the zcard command', + 'cmdstat_zrange': 'Statistics for the zrange command', + 'cmdstat_zremrangebyrank': 'Statistics for the zremrangebyrank command', + 'cmdstat_zrevrange': 'Statistics for the zrevrange command', + 'cmdstat_zscan': 'Statistics for the zscan command', + 'config_file': None, + 'connected_clients': None, + 'connected_slaves': None, + 'db0': None, + 'evicted_keys': None, + 'expired_keys': None, + 'gcc_version': None, + 'hz': None, + 'instantaneous_ops_per_sec': None, + 'keyspace_hits': None, + 'keyspace_misses': None, + 'latest_fork_usec': None, + 'loading': None, + 'lru_clock': None, + 'master_repl_offset': None, + 'mem_allocator': None, + 'mem_fragmentation_ratio': None, + 'multiplexing_api': None, + 'os': None, + 'process_id': None, + 'pubsub_channels': None, + 'pubsub_patterns': None, + 'rdb_bgsave_in_progress': None, + 'rdb_changes_since_last_save': None, + 'rdb_current_bgsave_time_sec': None, + 'rdb_last_bgsave_status': None, + 'rdb_last_bgsave_time_sec': None, + 'rdb_last_save_time': None, + 'redis_build_id': None, + 'redis_git_dirty': None, + 'redis_git_sha1': None, + 'redis_mode': None, + 'redis_version': None, + 'rejected_connections': None, + 'repl_backlog_active': None, + 'repl_backlog_first_byte_offset': None, + 'repl_backlog_histlen': None, + 'repl_backlog_size': None, + 'role': None, + 'run_id': None, + 'sync_full': None, + 'sync_partial_err': None, + 'sync_partial_ok': None, + 'tcp_port': None, + 'total_commands_processed': None, + 'total_connections_received': None, + 'uptime_in_days': None, + 'uptime_in_seconds': None, + 'used_cpu_sys': None, + 'used_cpu_sys_children': None, + 'used_cpu_user': None, + 'used_cpu_user_children': None, + 'used_memory': None, + 'used_memory_human': None, + 'used_memory_lua': None, + 'used_memory_peak': None, + 'used_memory_peak_human': None, + 'used_memory_rss': None +} + + +@app.route('/rebrow', methods=['GET', 'POST']) +# def login(): +def rebrow(): + """ + Start page + """ + if request.method == 'POST': + # TODO: test connection, handle failures + host = request.form['host'] + port = int(request.form['port']) + db = int(request.form['db']) + url = url_for('rebrow_server_db', host=host, port=port, db=db) + return redirect(url) + else: + s = time.time() + return render_template( + 'rebrow_login.html', + version=skyline_version, + duration=(time.time() - s)) + + +@app.route("/rebrow_server_db/://") +def rebrow_server_db(host, port, db): + """ + List all databases and show info on server + """ + s = time.time() + r = redis.StrictRedis(host=host, port=port, db=0) + info = r.info('all') + dbsize = r.dbsize() + return render_template( + 'rebrow_server_db.html', + host=host, + port=port, + db=db, + info=info, + dbsize=dbsize, + serverinfo_meta=serverinfo_meta, + version=skyline_version, + duration=(time.time() - s)) + + +@app.route("/rebrow_keys/://keys/", methods=['GET', 'POST']) +def rebrow_keys(host, port, db): + """ + List keys for one database + """ + s = time.time() + r = redis.StrictRedis(host=host, port=port, db=db) + if request.method == 'POST': + action = request.form['action'] + app.logger.debug(action) + if action == 'delkey': + if request.form['key'] is not None: + result = r.delete(request.form['key']) + if result == 1: + flash('Key %s has been deleted.' % request.form['key'], category='info') + else: + flash('Key %s could not be deleted.' % request.form['key'], category='error') + return redirect(request.url) + else: + offset = int(request.args.get('offset', '0')) + perpage = int(request.args.get('perpage', '10')) + pattern = request.args.get('pattern', '*') + dbsize = r.dbsize() + keys = sorted(r.keys(pattern)) + limited_keys = keys[offset:(perpage + offset)] + types = {} + for key in limited_keys: + types[key] = r.type(key) + return render_template( + 'rebrow_keys.html', + host=host, + port=port, + db=db, + dbsize=dbsize, + keys=limited_keys, + types=types, + offset=offset, + perpage=perpage, + pattern=pattern, + num_keys=len(keys), + version=skyline_version, + duration=(time.time() - s)) + + +@app.route("/rebrow_key/://keys//") +def rebrow_key(host, port, db, key): + """ + Show a specific key. + key is expected to be URL-safe base64 encoded + """ +# @added 20160703 - Feature #1464: Webapp Redis browser +# metrics encoded with msgpack + original_key = key + msg_pack_key = False + # if key.startswith('metrics.'): + # msg_packed_key = True + key = base64.urlsafe_b64decode(key.encode('utf8')) + s = time.time() + r = redis.StrictRedis(host=host, port=port, db=db) + dump = r.dump(key) + if dump is None: + abort(404) + # if t is None: + # abort(404) + size = len(dump) + del dump + t = r.type(key) + ttl = r.pttl(key) + if t == 'string': + # @modified 20160703 - Feature #1464: Webapp Redis browser + # metrics encoded with msgpack + # val = r.get(key) + try: + val = r.get(key) + except: + abort(404) + test_string = all(c in string.printable for c in val) + if not test_string: + raw_result = r.get(key) + unpacker = Unpacker(use_list=False) + unpacker.feed(raw_result) + val = list(unpacker) + msg_pack_key = True + elif t == 'list': + val = r.lrange(key, 0, -1) + elif t == 'hash': + val = r.hgetall(key) + elif t == 'set': + val = r.smembers(key) + elif t == 'zset': + val = r.zrange(key, 0, -1, withscores=True) + return render_template( + 'rebrow_key.html', + host=host, + port=port, + db=db, + key=key, + value=val, + type=t, + size=size, + ttl=ttl / 1000.0, + now=datetime.utcnow(), + expiration=datetime.utcnow() + timedelta(seconds=ttl / 1000.0), + version=skyline_version, + duration=(time.time() - s), + msg_packed_key=msg_pack_key) + + +@app.template_filter('urlsafe_base64') +def urlsafe_base64_encode(s): + if type(s) == 'Markup': + s = s.unescape() + s = s.encode('utf8') + s = base64.urlsafe_b64encode(s) + return Markup(s) +# END rebrow + + +class App(): + def __init__(self): + self.stdin_path = '/dev/null' + self.stdout_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.stderr_path = '%s/%s.log' % (settings.LOG_PATH, skyline_app) + self.pidfile_path = '%s/%s.pid' % (settings.PID_PATH, skyline_app) + self.pidfile_timeout = 5 + + def run(self): + + # Log management to prevent overwriting + # Allow the bin/.d to manage the log + if os.path.isfile(skyline_app_logwait): + try: + os_remove(skyline_app_logwait) + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_logwait) + pass + + now = time() +# log_wait_for = now + 5 + log_wait_for = now + 1 + while now < log_wait_for: + if os.path.isfile(skyline_app_loglock): + sleep(.1) + now = time() + else: + now = log_wait_for + 1 + + logger.info('starting %s run' % skyline_app) + if os.path.isfile(skyline_app_loglock): + logger.error('error - bin/%s.d log management seems to have failed, continuing' % skyline_app) + try: + os_remove(skyline_app_loglock) + logger.info('log lock file removed') + except OSError: + logger.error('error - failed to remove %s, continuing' % skyline_app_loglock) + pass + else: + logger.info('bin/%s.d log management done' % skyline_app) + + try: + logger.info('starting %s - %s' % (skyline_app, skyline_version)) + except: + logger.info('starting %s - version UNKNOWN' % (skyline_app)) + logger.info('hosted at %s' % settings.WEBAPP_IP) + logger.info('running on port %d' % settings.WEBAPP_PORT) + + app.run(settings.WEBAPP_IP, settings.WEBAPP_PORT) + + +def run(): + """ + Start the Webapp server + """ + if not isdir(settings.PID_PATH): + print ('pid directory does not exist at %s' % settings.PID_PATH) + sys.exit(1) + + if not isdir(settings.LOG_PATH): + print ('log directory does not exist at %s' % settings.LOG_PATH) + sys.exit(1) + + logger.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + handler = logging.handlers.TimedRotatingFileHandler( + logfile, + when="midnight", + interval=1, + backupCount=5) + + memory_handler = logging.handlers.MemoryHandler(100, + flushLevel=logging.DEBUG, + target=handler) + handler.setFormatter(formatter) + logger.addHandler(memory_handler) + + try: + settings.WEBAPP_SERVER + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_SERVER')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_SERVER')) + sys.exit(1) + try: + settings.WEBAPP_IP + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_IP')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_IP')) + sys.exit(1) + try: + settings.WEBAPP_PORT + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_PORT')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_PORT')) + sys.exit(1) + try: + settings.WEBAPP_AUTH_ENABLED + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_AUTH_ENABLED')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_AUTH_ENABLED')) + sys.exit(1) + try: + settings.WEBAPP_IP_RESTRICTED + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_IP_RESTRICTED')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_IP_RESTRICTED')) + sys.exit(1) + try: + settings.WEBAPP_AUTH_USER + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER')) + sys.exit(1) + try: + settings.WEBAPP_AUTH_USER_PASSWORD + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER_PASSWORD')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_AUTH_USER_PASSWORD')) + sys.exit(1) + try: + settings.WEBAPP_ALLOWED_IPS + except: + logger.error('error :: failed to determine %s from settings.py' % str('WEBAPP_ALLOWED_IPS')) + print ('Failed to determine %s from settings.py' % str('WEBAPP_ALLOWED_IPS')) + sys.exit(1) + + webapp = App() + + if len(sys.argv) > 1 and sys.argv[1] == 'run': + webapp.run() + else: + daemon_runner = runner.DaemonRunner(webapp) + daemon_runner.daemon_context.files_preserve = [handler.stream] + daemon_runner.do_action() + +if __name__ == "__main__": + run() diff --git a/src/analyzer/algorithms.py b/src/analyzer/algorithms.py deleted file mode 100644 index 8ee5c80f..00000000 --- a/src/analyzer/algorithms.py +++ /dev/null @@ -1,301 +0,0 @@ -import pandas -import numpy as np -import scipy -import statsmodels.api as sm -import traceback -import logging -from time import time -from msgpack import unpackb, packb -from redis import StrictRedis - -from settings import ( - ALGORITHMS, - CONSENSUS, - FULL_DURATION, - MAX_TOLERABLE_BOREDOM, - MIN_TOLERABLE_LENGTH, - STALE_PERIOD, - REDIS_SOCKET_PATH, - ENABLE_SECOND_ORDER, - BOREDOM_SET_SIZE, -) - -from algorithm_exceptions import * - -logger = logging.getLogger("AnalyzerLog") -redis_conn = StrictRedis(unix_socket_path=REDIS_SOCKET_PATH) - -""" -This is no man's land. Do anything you want in here, -as long as you return a boolean that determines whether the input -timeseries is anomalous or not. - -To add an algorithm, define it here, and add its name to settings.ALGORITHMS. -""" - - -def tail_avg(timeseries): - """ - This is a utility function used to calculate the average of the last three - datapoints in the series as a measure, instead of just the last datapoint. - It reduces noise, but it also reduces sensitivity and increases the delay - to detection. - """ - try: - t = (timeseries[-1][1] + timeseries[-2][1] + timeseries[-3][1]) / 3 - return t - except IndexError: - return timeseries[-1][1] - - -def median_absolute_deviation(timeseries): - """ - A timeseries is anomalous if the deviation of its latest datapoint with - respect to the median is X times larger than the median of deviations. - """ - - series = pandas.Series([x[1] for x in timeseries]) - median = series.median() - demedianed = np.abs(series - median) - median_deviation = demedianed.median() - - # The test statistic is infinite when the median is zero, - # so it becomes super sensitive. We play it safe and skip when this happens. - if median_deviation == 0: - return False - - test_statistic = demedianed.iget(-1) / median_deviation - - # Completely arbitary...triggers if the median deviation is - # 6 times bigger than the median - if test_statistic > 6: - return True - - -def grubbs(timeseries): - """ - A timeseries is anomalous if the Z score is greater than the Grubb's score. - """ - - series = scipy.array([x[1] for x in timeseries]) - stdDev = scipy.std(series) - mean = np.mean(series) - tail_average = tail_avg(timeseries) - z_score = (tail_average - mean) / stdDev - len_series = len(series) - threshold = scipy.stats.t.isf(.05 / (2 * len_series), len_series - 2) - threshold_squared = threshold * threshold - grubbs_score = ((len_series - 1) / np.sqrt(len_series)) * np.sqrt(threshold_squared / (len_series - 2 + threshold_squared)) - - return z_score > grubbs_score - - -def first_hour_average(timeseries): - """ - Calcuate the simple average over one hour, FULL_DURATION seconds ago. - A timeseries is anomalous if the average of the last three datapoints - are outside of three standard deviations of this value. - """ - last_hour_threshold = time() - (FULL_DURATION - 3600) - series = pandas.Series([x[1] for x in timeseries if x[0] < last_hour_threshold]) - mean = (series).mean() - stdDev = (series).std() - t = tail_avg(timeseries) - - return abs(t - mean) > 3 * stdDev - - -def stddev_from_average(timeseries): - """ - A timeseries is anomalous if the absolute value of the average of the latest - three datapoint minus the moving average is greater than three standard - deviations of the average. This does not exponentially weight the MA and so - is better for detecting anomalies with respect to the entire series. - """ - series = pandas.Series([x[1] for x in timeseries]) - mean = series.mean() - stdDev = series.std() - t = tail_avg(timeseries) - - return abs(t - mean) > 3 * stdDev - - -def stddev_from_moving_average(timeseries): - """ - A timeseries is anomalous if the absolute value of the average of the latest - three datapoint minus the moving average is greater than three standard - deviations of the moving average. This is better for finding anomalies with - respect to the short term trends. - """ - series = pandas.Series([x[1] for x in timeseries]) - expAverage = pandas.stats.moments.ewma(series, com=50) - stdDev = pandas.stats.moments.ewmstd(series, com=50) - - return abs(series.iget(-1) - expAverage.iget(-1)) > 3 * stdDev.iget(-1) - - -def mean_subtraction_cumulation(timeseries): - """ - A timeseries is anomalous if the value of the next datapoint in the - series is farther than three standard deviations out in cumulative terms - after subtracting the mean from each data point. - """ - - series = pandas.Series([x[1] if x[1] else 0 for x in timeseries]) - series = series - series[0:len(series) - 1].mean() - stdDev = series[0:len(series) - 1].std() - expAverage = pandas.stats.moments.ewma(series, com=15) - - return abs(series.iget(-1)) > 3 * stdDev - - -def least_squares(timeseries): - """ - A timeseries is anomalous if the average of the last three datapoints - on a projected least squares model is greater than three sigma. - """ - - x = np.array([t[0] for t in timeseries]) - y = np.array([t[1] for t in timeseries]) - A = np.vstack([x, np.ones(len(x))]).T - results = np.linalg.lstsq(A, y) - residual = results[1] - m, c = np.linalg.lstsq(A, y)[0] - errors = [] - for i, value in enumerate(y): - projected = m * x[i] + c - error = value - projected - errors.append(error) - - if len(errors) < 3: - return False - - std_dev = scipy.std(errors) - t = (errors[-1] + errors[-2] + errors[-3]) / 3 - - return abs(t) > std_dev * 3 and round(std_dev) != 0 and round(t) != 0 - - -def histogram_bins(timeseries): - """ - A timeseries is anomalous if the average of the last three datapoints falls - into a histogram bin with less than 20 other datapoints (you'll need to tweak - that number depending on your data) - - Returns: the size of the bin which contains the tail_avg. Smaller bin size - means more anomalous. - """ - - series = scipy.array([x[1] for x in timeseries]) - t = tail_avg(timeseries) - h = np.histogram(series, bins=15) - bins = h[1] - for index, bin_size in enumerate(h[0]): - if bin_size <= 20: - # Is it in the first bin? - if index == 0: - if t <= bins[0]: - return True - # Is it in the current bin? - elif t >= bins[index] and t < bins[index + 1]: - return True - - return False - - -def ks_test(timeseries): - """ - A timeseries is anomalous if 2 sample Kolmogorov-Smirnov test indicates - that data distribution for last 10 minutes is different from last hour. - It produces false positives on non-stationary series so Augmented - Dickey-Fuller test applied to check for stationarity. - """ - - hour_ago = time() - 3600 - ten_minutes_ago = time() - 600 - reference = scipy.array([x[1] for x in timeseries if x[0] >= hour_ago and x[0] < ten_minutes_ago]) - probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_minutes_ago]) - - if reference.size < 20 or probe.size < 20: - return False - - ks_d, ks_p_value = scipy.stats.ks_2samp(reference, probe) - - if ks_p_value < 0.05 and ks_d > 0.5: - adf = sm.tsa.stattools.adfuller(reference, 10) - if adf[1] < 0.05: - return True - - return False - - -def is_anomalously_anomalous(metric_name, ensemble, datapoint): - """ - This method runs a meta-analysis on the metric to determine whether the - metric has a past history of triggering. TODO: weight intervals based on datapoint - """ - # We want the datapoint to avoid triggering twice on the same data - new_trigger = [time(), datapoint] - - # Get the old history - raw_trigger_history = redis_conn.get('trigger_history.' + metric_name) - if not raw_trigger_history: - redis_conn.set('trigger_history.' + metric_name, packb([(time(), datapoint)])) - return True - - trigger_history = unpackb(raw_trigger_history) - - # Are we (probably) triggering on the same data? - if (new_trigger[1] == trigger_history[-1][1] and - new_trigger[0] - trigger_history[-1][0] <= 300): - return False - - # Update the history - trigger_history.append(new_trigger) - redis_conn.set('trigger_history.' + metric_name, packb(trigger_history)) - - # Should we surface the anomaly? - trigger_times = [x[0] for x in trigger_history] - intervals = [ - trigger_times[i + 1] - trigger_times[i] - for i, v in enumerate(trigger_times) - if (i + 1) < len(trigger_times) - ] - - series = pandas.Series(intervals) - mean = series.mean() - stdDev = series.std() - - return abs(intervals[-1] - mean) > 3 * stdDev - - -def run_selected_algorithm(timeseries, metric_name): - """ - Filter timeseries and run selected algorithm. - """ - # Get rid of short series - if len(timeseries) < MIN_TOLERABLE_LENGTH: - raise TooShort() - - # Get rid of stale series - if time() - timeseries[-1][0] > STALE_PERIOD: - raise Stale() - - # Get rid of boring series - if len(set(item[1] for item in timeseries[-MAX_TOLERABLE_BOREDOM:])) == BOREDOM_SET_SIZE: - raise Boring() - - try: - ensemble = [globals()[algorithm](timeseries) for algorithm in ALGORITHMS] - threshold = len(ensemble) - CONSENSUS - if ensemble.count(False) <= threshold: - if ENABLE_SECOND_ORDER: - if is_anomalously_anomalous(metric_name, ensemble, timeseries[-1][1]): - return True, ensemble, timeseries[-1][1] - else: - return True, ensemble, timeseries[-1][1] - - return False, ensemble, timeseries[-1][1] - except: - logging.error("Algorithm error: " + traceback.format_exc()) - return False, [], 1 diff --git a/src/analyzer/analyzer-agent.py b/src/analyzer/analyzer-agent.py deleted file mode 100644 index 8ce2b0c2..00000000 --- a/src/analyzer/analyzer-agent.py +++ /dev/null @@ -1,79 +0,0 @@ -import logging -import sys -import traceback -from os import getpid -from os.path import dirname, abspath, isdir -from daemon import runner -from time import sleep, time - -# @added 20150914 - added log rotation -from logging.handlers import TimedRotatingFileHandler - -# add the shared settings file to namespace -sys.path.insert(0, dirname(dirname(abspath(__file__)))) -import settings -from analyzer import Analyzer - - -class AnalyzerAgent(): - def __init__(self): - self.stdin_path = '/dev/null' - self.stdout_path = settings.LOG_PATH + '/analyzer.log' - self.stderr_path = settings.LOG_PATH + '/analyzer.log' - self.pidfile_path = settings.PID_PATH + '/analyzer.pid' - self.pidfile_timeout = 5 - - def run(self): - logger.info('starting skyline analyzer') - Analyzer(getpid()).start() - - while 1: - sleep(100) - -if __name__ == "__main__": - """ - Start the Analyzer agent. - """ - if not isdir(settings.PID_PATH): - print 'pid directory does not exist at %s' % settings.PID_PATH - sys.exit(1) - - if not isdir(settings.LOG_PATH): - print 'log directory does not exist at %s' % settings.LOG_PATH - sys.exit(1) - - # Make sure we can run all the algorithms - try: - from algorithms import * - timeseries = map(list, zip(map(float, range(int(time()) - 86400, int(time()) + 1)), [1] * 86401)) - ensemble = [globals()[algorithm](timeseries) for algorithm in settings.ALGORITHMS] - except KeyError as e: - print "Algorithm %s deprecated or not defined; check settings.ALGORITHMS" % e - sys.exit(1) - except Exception as e: - print "Algorithm test run failed." - traceback.print_exc() - sys.exit(1) - - analyzer = AnalyzerAgent() - - logger = logging.getLogger("AnalyzerLog") - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s :: %(process)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - handler = logging.handlers.TimedRotatingFileHandler( - settings.LOG_PATH + '/analyzer.log', - when="midnight", - interval=1, - backupCount=5) - - logger.addHandler(handler) - - handler.setFormatter(formatter) - logger.addHandler(handler) - - if len(sys.argv) > 1 and sys.argv[1] == 'run': - analyzer.run() - else: - daemon_runner = runner.DaemonRunner(analyzer) - daemon_runner.daemon_context.files_preserve = [handler.stream] - daemon_runner.do_action() diff --git a/src/analyzer/analyzer.py b/src/analyzer/analyzer.py deleted file mode 100644 index 56584dce..00000000 --- a/src/analyzer/analyzer.py +++ /dev/null @@ -1,286 +0,0 @@ -import logging -from Queue import Empty -from redis import StrictRedis -from time import time, sleep -from threading import Thread -from collections import defaultdict -from multiprocessing import Process, Manager, Queue -from msgpack import Unpacker, unpackb, packb -from os import path, kill, getpid, system -from math import ceil -import traceback -import operator -import socket -import settings -import re - -from alerters import trigger_alert -from algorithms import run_selected_algorithm -from algorithm_exceptions import * - -logger = logging.getLogger("AnalyzerLog") - -try: - SERVER_METRIC_PATH = settings.SERVER_METRICS_NAME + '.' -except: - SERVER_METRIC_PATH = '' - - -class Analyzer(Thread): - def __init__(self, parent_pid): - """ - Initialize the Analyzer - """ - super(Analyzer, self).__init__() - self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - self.daemon = True - self.parent_pid = parent_pid - self.current_pid = getpid() - self.anomalous_metrics = Manager().list() - self.exceptions_q = Queue() - self.anomaly_breakdown_q = Queue() - - def check_if_parent_is_alive(self): - """ - Self explanatory - """ - try: - kill(self.current_pid, 0) - kill(self.parent_pid, 0) - except: - exit(0) - - def send_graphite_metric(self, name, value): - if settings.GRAPHITE_HOST != '': - sock = socket.socket() - - try: - sock.connect((settings.GRAPHITE_HOST, settings.CARBON_PORT)) - except socket.error: - endpoint = '%s:%d' % (settings.GRAPHITE_HOST, - settings.CARBON_PORT) - logger.error("Can't connect to Graphite at %s" % endpoint) - return False - - sock.sendall('%s %s %i\n' % (name, value, time())) - sock.close() - return True - - return False - - def spin_process(self, i, unique_metrics): - """ - Assign a bunch of metrics for a process to analyze. - """ - # Discover assigned metrics - keys_per_processor = int(ceil(float(len(unique_metrics)) / float(settings.ANALYZER_PROCESSES))) - if i == settings.ANALYZER_PROCESSES: - assigned_max = len(unique_metrics) - else: - assigned_max = min(len(unique_metrics), i * keys_per_processor) - assigned_min = (i - 1) * keys_per_processor - assigned_keys = range(assigned_min, assigned_max) - - # Compile assigned metrics - assigned_metrics = [unique_metrics[index] for index in assigned_keys] - - # Check if this process is unnecessary - if len(assigned_metrics) == 0: - return - - # Multi get series - raw_assigned = self.redis_conn.mget(assigned_metrics) - - # Make process-specific dicts - exceptions = defaultdict(int) - anomaly_breakdown = defaultdict(int) - - # Distill timeseries strings into lists - for i, metric_name in enumerate(assigned_metrics): - self.check_if_parent_is_alive() - - try: - raw_series = raw_assigned[i] - unpacker = Unpacker(use_list=False) - unpacker.feed(raw_series) - timeseries = list(unpacker) - - anomalous, ensemble, datapoint = run_selected_algorithm(timeseries, metric_name) - - # If it's anomalous, add it to list - if anomalous: - base_name = metric_name.replace(settings.FULL_NAMESPACE, '', 1) - metric = [datapoint, base_name] - self.anomalous_metrics.append(metric) - - # Get the anomaly breakdown - who returned True? - for index, value in enumerate(ensemble): - if value: - algorithm = settings.ALGORITHMS[index] - anomaly_breakdown[algorithm] += 1 - - # It could have been deleted by the Roomba - except TypeError: - exceptions['DeletedByRoomba'] += 1 - except TooShort: - exceptions['TooShort'] += 1 - except Stale: - exceptions['Stale'] += 1 - except Boring: - exceptions['Boring'] += 1 - except: - exceptions['Other'] += 1 - logger.info(traceback.format_exc()) - - # Add values to the queue so the parent process can collate - for key, value in anomaly_breakdown.items(): - self.anomaly_breakdown_q.put((key, value)) - - for key, value in exceptions.items(): - self.exceptions_q.put((key, value)) - - def run(self): - """ - Called when the process intializes. - """ - while 1: - now = time() - - # Make sure Redis is up - try: - self.redis_conn.ping() - except: - logger.error('skyline can\'t connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) - sleep(10) - self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - continue - - # Discover unique metrics - unique_metrics = list(self.redis_conn.smembers(settings.FULL_NAMESPACE + 'unique_metrics')) - - if len(unique_metrics) == 0: - logger.info('no metrics in redis. try adding some - see README') - sleep(10) - continue - - # Spawn processes - pids = [] - for i in range(1, settings.ANALYZER_PROCESSES + 1): - if i > len(unique_metrics): - logger.info('WARNING: skyline is set for more cores than needed.') - break - - p = Process(target=self.spin_process, args=(i, unique_metrics)) - pids.append(p) - p.start() - - # Send wait signal to zombie processes - for p in pids: - p.join() - - # Grab data from the queue and populate dictionaries - exceptions = dict() - anomaly_breakdown = dict() - while 1: - try: - key, value = self.anomaly_breakdown_q.get_nowait() - if key not in anomaly_breakdown.keys(): - anomaly_breakdown[key] = value - else: - anomaly_breakdown[key] += value - except Empty: - break - - while 1: - try: - key, value = self.exceptions_q.get_nowait() - if key not in exceptions.keys(): - exceptions[key] = value - else: - exceptions[key] += value - except Empty: - break - - # Send alerts - if settings.ENABLE_ALERTS: - for alert in settings.ALERTS: - for metric in self.anomalous_metrics: - ALERT_MATCH_PATTERN = alert[0] - METRIC_PATTERN = metric[1] - alert_match_pattern = re.compile(ALERT_MATCH_PATTERN) - pattern_match = alert_match_pattern.match(METRIC_PATTERN) - if pattern_match: - cache_key = 'last_alert.%s.%s' % (alert[1], metric[1]) - try: - last_alert = self.redis_conn.get(cache_key) - if not last_alert: - try: - SECOND_ORDER_RESOLUTION_FULL_DURATION = alert[3] - logger.info('mirage check :: %s' % (metric[1])) - # Write anomalous metric to test at second - # order resolution by crucible to the check - # file - metric_timestamp = int(time()) - anomaly_check_file = '%s/%s.%s.txt' % (settings.MIRAGE_CHECK_PATH, metric_timestamp, metric[1]) - with open(anomaly_check_file, 'w') as fh: - # metric_name, anomalous datapoint, hours to resolve, timestamp - fh.write('metric = "%s"\nvalue = "%s"\nhours_to_resolve = "%s"\nmetric_timestamp = "%s"\n' % (metric[1], metric[0], alert[3], metric_timestamp)) - logger.info('added mirage check :: %s,%s,%s' % (metric[1], metric[0], alert[3])) - if settings.ENABLE_FULL_DURATION_ALERTS: - self.redis_conn.setex(cache_key, alert[2], packb(metric[0])) - trigger_alert(alert, metric) - except: - self.redis_conn.setex(cache_key, alert[2], packb(metric[0])) - trigger_alert(alert, metric) - except Exception as e: - logger.error("couldn't send alert: %s" % e) - - # Write anomalous_metrics to static webapp directory - if len(self.anomalous_metrics) > 0: - filename = path.abspath(path.join(path.dirname(__file__), '..', settings.ANOMALY_DUMP)) - with open(filename, 'w') as fh: - # Make it JSONP with a handle_data() function - anomalous_metrics = list(self.anomalous_metrics) - anomalous_metrics.sort(key=operator.itemgetter(1)) - fh.write('handle_data(%s)' % anomalous_metrics) - - # Log progress - logger.info('seconds to run :: %.2f' % (time() - now)) - logger.info('total metrics :: %d' % len(unique_metrics)) - logger.info('total analyzed :: %d' % (len(unique_metrics) - sum(exceptions.values()))) - logger.info('total anomalies :: %d' % len(self.anomalous_metrics)) - logger.info('exception stats :: %s' % exceptions) - logger.info('anomaly breakdown :: %s' % anomaly_breakdown) - - # Log to Graphite - self.send_graphite_metric('skyline.analyzer.' + SERVER_METRIC_PATH + 'run_time', '%.2f' % (time() - now)) - self.send_graphite_metric('skyline.analyzer.' + SERVER_METRIC_PATH + 'total_analyzed', '%.2f' % (len(unique_metrics) - sum(exceptions.values()))) - self.send_graphite_metric('skyline.analyzer.' + SERVER_METRIC_PATH + 'total_anomalies', '%d' % len(self.anomalous_metrics)) - self.send_graphite_metric('skyline.analyzer.' + SERVER_METRIC_PATH + 'total_metrics', '%d' % len(unique_metrics)) - for key, value in exceptions.items(): - send_metric = 'skyline.analyzer.' + SERVER_METRIC_PATH + 'exceptions.%s' % key - self.send_graphite_metric(send_metric, '%d' % value) - for key, value in anomaly_breakdown.items(): - send_metric = 'skyline.analyzer.' + SERVER_METRIC_PATH + 'anomaly_breakdown.%s' % key - self.send_graphite_metric(send_metric, '%d' % value) - - # Check canary metric - raw_series = self.redis_conn.get(settings.FULL_NAMESPACE + settings.CANARY_METRIC) - if raw_series is not None: - unpacker = Unpacker(use_list=False) - unpacker.feed(raw_series) - timeseries = list(unpacker) - time_human = (timeseries[-1][0] - timeseries[0][0]) / 3600 - projected = 24 * (time() - now) / time_human - - logger.info('canary duration :: %.2f' % time_human) - self.send_graphite_metric('skyline.analyzer.' + SERVER_METRIC_PATH + 'duration', '%.2f' % time_human) - self.send_graphite_metric('skyline.analyzer.' + SERVER_METRIC_PATH + 'projected', '%.2f' % projected) - - # Reset counters - self.anomalous_metrics[:] = [] - - # Sleep if it went too fast - if time() - now < 5: - logger.info('sleeping due to low run time...') - sleep(10) diff --git a/src/boundary/alerters.py b/src/boundary/alerters.py deleted file mode 100644 index fb506523..00000000 --- a/src/boundary/alerters.py +++ /dev/null @@ -1,190 +0,0 @@ -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from email.MIMEImage import MIMEImage -from smtplib import SMTP -import alerters -import settings -import urllib2 -import re - -""" -Create any alerter you want here. The function is invoked from trigger_alert. -4 arguments will be passed in as strings: -datapoint, metric_name, expiration_time, algorithm -""" - -# FULL_DURATION to hours so that boundary surfaces the relevant timeseries data -# in the graph -full_duration_in_hours = int(settings.FULL_DURATION) / 3600 - -try: - graphite_previous_hours = int(settings.BOUNDARY_SMTP_OPTS['graphite_previous_hours']) -except: - graphite_previous_hours = full_duration_in_hours - -try: - graphite_graph_line_color = int(settings.BOUNDARY_SMTP_OPTS['graphite_graph_line_color']) -except: - graphite_graph_line_color = 'pink' - - -def alert_smtp(datapoint, metric_name, expiration_time, metric_trigger, algorithm): - - sender = settings.BOUNDARY_SMTP_OPTS['sender'] - - matched_namespaces = [] - for namespace in settings.BOUNDARY_SMTP_OPTS['recipients']: - CHECK_MATCH_PATTERN = namespace - check_match_pattern = re.compile(CHECK_MATCH_PATTERN) - pattern_match = check_match_pattern.match(metric_name) - if pattern_match: - matched_namespaces.append(namespace) - matched_recipients = [] - for namespace in matched_namespaces: - for recipients in settings.BOUNDARY_SMTP_OPTS['recipients'][namespace]: - matched_recipients.append(recipients) - - def unique_noHash(seq): - seen = set() - return [x for x in seq if str(x) not in seen and not seen.add(str(x))] - - recipients = unique_noHash(matched_recipients) - - # Backwards compatibility - if type(recipients) is str: - recipients = [recipients] - - alert_algo = str(algorithm) - alert_context = alert_algo.upper() - - graph_title = '&title=skyline%%20boundary%%20%s%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % ( - alert_context, graphite_previous_hours, metric_name, datapoint) - - if settings.GRAPHITE_PORT != '': - link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( - settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, - graphite_previous_hours, metric_name, settings.GRAPHITE_GRAPH_SETTINGS, - graph_title, graphite_graph_line_color) - else: - link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( - settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, graphite_previous_hours, - metric_name, settings.GRAPHITE_GRAPH_SETTINGS, graph_title, - graphite_graph_line_color) - - content_id = metric_name - image_data = None - if settings.BOUNDARY_SMTP_OPTS.get('embed-images'): - try: - image_data = urllib2.urlopen(link).read() - except urllib2.URLError: - image_data = None - - # If we failed to get the image or if it was explicitly disabled, - # use the image URL instead of the content. - if image_data is None: - img_tag = '' % link - else: - img_tag = '' % content_id - - body = '%s :: %s
Next alert in: %s seconds
skyline boundary alert - %s
%s' % ( - datapoint, metric_name, expiration_time, alert_context, link, img_tag) - - for recipient in recipients: - msg = MIMEMultipart('alternative') - msg['Subject'] = 'boundary ' + alert_context + ' - ' + datapoint + ' - ' + metric_name - msg['From'] = sender - msg['To'] = recipient - - msg.attach(MIMEText(body, 'html')) - if image_data is not None: - msg_attachment = MIMEImage(image_data) - msg_attachment.add_header('Content-ID', '<%s>' % content_id) - msg.attach(msg_attachment) - - s = SMTP('127.0.0.1') - s.sendmail(sender, recipient, msg.as_string()) - s.quit() - - -def alert_pagerduty(datapoint, metric_name, expiration_time, metric_trigger, algorithm): - import pygerduty - pager = pygerduty.PagerDuty(settings.BOUNDARY_PAGERDUTY_OPTS['subdomain'], settings.BOUNDARY_PAGERDUTY_OPTS['auth_token']) - pager.trigger_incident(settings.BOUNDARY_PAGERDUTY_OPTS['key'], 'Anomalous metric: %s (value: %s) - %s' % (metric_name, datapoint, algortihm)) - - -def alert_hipchat(datapoint, metric_name, expiration_time, metric_trigger, algorithm): - - sender = settings.BOUNDARY_HIPCHAT_OPTS['sender'] - import hipchat - hipster = hipchat.HipChat(token=settings.BOUNDARY_HIPCHAT_OPTS['auth_token']) - - # Allow for absolute path metric namespaces but also allow for and match - # match wildcard namepaces if there is not an absolute path metric namespace - rooms = 'unknown' - notify_rooms = [] - matched_rooms = [] - try: - rooms = settings.BOUNDARY_HIPCHAT_OPTS['rooms'][metric_name] - notify_rooms.append(rooms) - except: - for room in settings.BOUNDARY_HIPCHAT_OPTS['rooms']: - print(room) - CHECK_MATCH_PATTERN = room - check_match_pattern = re.compile(CHECK_MATCH_PATTERN) - pattern_match = check_match_pattern.match(metric_name) - if pattern_match: - matched_rooms.append(room) - - if matched_rooms != []: - for i_metric_name in matched_rooms: - rooms = settings.BOUNDARY_HIPCHAT_OPTS['rooms'][i_metric_name] - notify_rooms.append(rooms) - - alert_algo = str(algorithm) - alert_context = alert_algo.upper() - - graph_title = '&title=skyline%%20boundary%%20%s%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % ( - alert_context, graphite_previous_hours, metric_name, datapoint) - - if settings.GRAPHITE_PORT != '': - link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( - settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, - graphite_previous_hours, metric_name, settings.GRAPHITE_GRAPH_SETTINGS, - graph_title, graphite_graph_line_color) - else: - link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=%s' % ( - settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, graphite_previous_hours, - metric_name, settings.GRAPHITE_GRAPH_SETTINGS, graph_title, - graphite_graph_line_color) - - embed_graph = "" + metric_name + "" - - for rooms in notify_rooms: - for room in rooms: - hipster.method('rooms/message', method='POST', parameters={'room_id': room, 'from': 'skyline', 'color': settings.BOUNDARY_HIPCHAT_OPTS['color'], 'message': '%s - boundary - %s - Anomalous metric: %s (value: %s) at %s hours %s' % (sender, algorithm, metric_name, datapoint, graphite_previous_hours, embed_graph)}) - - -def alert_syslog(datapoint, metric_name, expiration_time, metric_trigger, algorithm): - import sys - import syslog - syslog_ident = settings.SYSLOG_OPTS['ident'] - message = str('boundary - Anomalous metric: %s (value: %s) - %s' % (metric_name, datapoint, algorithm)) - if sys.version_info[:2] == (2, 6): - syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) - elif sys.version_info[:2] == (2, 7): - syslog.openlog(ident='skyline', logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) - elif sys.version_info[:1] == (3): - syslog.openlog(ident='skyline', logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) - else: - syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) - syslog.syslog(4, message) - - -def trigger_alert(alerter, datapoint, metric_name, expiration_time, metric_trigger, algorithm): - - if alerter == 'smtp': - strategy = 'alert_smtp' - else: - strategy = 'alert_' + alerter - - getattr(alerters, strategy)(datapoint, metric_name, expiration_time, metric_trigger, algorithm) diff --git a/src/boundary/algorithm_exceptions.py b/src/boundary/algorithm_exceptions.py deleted file mode 100644 index 3342af97..00000000 --- a/src/boundary/algorithm_exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -class TooShort(Exception): - pass - - -class Stale(Exception): - pass - - -class Boring(Exception): - pass diff --git a/src/horizon/roomba.py b/src/horizon/roomba.py deleted file mode 100644 index 66224c1b..00000000 --- a/src/horizon/roomba.py +++ /dev/null @@ -1,180 +0,0 @@ -from os import kill -from redis import StrictRedis, WatchError -from multiprocessing import Process -from threading import Thread -from msgpack import Unpacker, packb -from types import TupleType -from time import time, sleep - -import logging -import settings - -logger = logging.getLogger("HorizonLog") - - -class Roomba(Thread): - """ - The Roomba is responsible for deleting keys older than DURATION. - """ - def __init__(self, parent_pid, skip_mini): - super(Roomba, self).__init__() - self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - self.daemon = True - self.parent_pid = parent_pid - self.skip_mini = skip_mini - - def check_if_parent_is_alive(self): - """ - Self explanatory. - """ - try: - kill(self.parent_pid, 0) - except: - exit(0) - - def vacuum(self, i, namespace, duration): - """ - Trim metrics that are older than settings.FULL_DURATION and - purge old metrics. - """ - begin = time() - - # Discover assigned metrics - unique_metrics = list(self.redis_conn.smembers(namespace + 'unique_metrics')) - keys_per_processor = len(unique_metrics) / settings.ROOMBA_PROCESSES - assigned_max = i * keys_per_processor - assigned_min = assigned_max - keys_per_processor - assigned_keys = range(assigned_min, assigned_max) - - # Compile assigned metrics - assigned_metrics = [unique_metrics[index] for index in assigned_keys] - - euthanized = 0 - blocked = 0 - for i in xrange(len(assigned_metrics)): - self.check_if_parent_is_alive() - - pipe = self.redis_conn.pipeline() - now = time() - key = assigned_metrics[i] - - try: - # WATCH the key - pipe.watch(key) - - # Everything below NEEDS to happen before another datapoint - # comes in. If your data has a very small resolution (<.1s), - # this technique may not suit you. - raw_series = pipe.get(key) - unpacker = Unpacker(use_list=False) - unpacker.feed(raw_series) - timeseries = sorted([unpacked for unpacked in unpacker]) - - # Put pipe back in multi mode - pipe.multi() - - # There's one value. Purge if it's too old - try: - if not isinstance(timeseries[0], TupleType): - if timeseries[0] < now - duration: - pipe.delete(key) - pipe.srem(namespace + 'unique_metrics', key) - pipe.execute() - euthanized += 1 - continue - except IndexError: - continue - - # Check if the last value is too old and purge - if timeseries[-1][0] < now - duration: - pipe.delete(key) - pipe.srem(namespace + 'unique_metrics', key) - pipe.execute() - euthanized += 1 - continue - - # Remove old datapoints and duplicates from timeseries - temp = set() - temp_add = temp.add - delta = now - duration - trimmed = [ - tuple for tuple in timeseries - if tuple[0] > delta and - tuple[0] not in temp and not - temp_add(tuple[0]) - ] - - # Purge if everything was deleted, set key otherwise - if len(trimmed) > 0: - # Serialize and turn key back into not-an-array - btrimmed = packb(trimmed) - if len(trimmed) <= 15: - value = btrimmed[1:] - elif len(trimmed) <= 65535: - value = btrimmed[3:] - else: - value = btrimmed[5:] - pipe.set(key, value) - else: - pipe.delete(key) - pipe.srem(namespace + 'unique_metrics', key) - euthanized += 1 - - pipe.execute() - - except WatchError: - blocked += 1 - assigned_metrics.append(key) - except Exception as e: - # If something bad happens, zap the key and hope it goes away - pipe.delete(key) - pipe.srem(namespace + 'unique_metrics', key) - pipe.execute() - euthanized += 1 - logger.info(e) - logger.info("Euthanizing " + key) - finally: - pipe.reset() - - logger.info('operated on %s in %f seconds' % (namespace, time() - begin)) - logger.info('%s keyspace is %d' % (namespace, (len(assigned_metrics) - euthanized))) - logger.info('blocked %d times' % blocked) - logger.info('euthanized %d geriatric keys' % euthanized) - - if (time() - begin < 30): - logger.info('sleeping due to low run time...') - sleep(10) - - def run(self): - """ - Called when process initializes. - """ - logger.info('started roomba') - - while 1: - now = time() - - # Make sure Redis is up - try: - self.redis_conn.ping() - except: - logger.error('roomba can\'t connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) - sleep(10) - self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - continue - - # Spawn processes - pids = [] - for i in range(1, settings.ROOMBA_PROCESSES + 1): - if not self.skip_mini: - p = Process(target=self.vacuum, args=(i, settings.MINI_NAMESPACE, settings.MINI_DURATION + settings.ROOMBA_GRACE_TIME)) - pids.append(p) - p.start() - - p = Process(target=self.vacuum, args=(i, settings.FULL_NAMESPACE, settings.FULL_DURATION + settings.ROOMBA_GRACE_TIME)) - pids.append(p) - p.start() - - # Send wait signal to zombie processes - for p in pids: - p.join() diff --git a/src/horizon/worker.py b/src/horizon/worker.py deleted file mode 100644 index efef52b8..00000000 --- a/src/horizon/worker.py +++ /dev/null @@ -1,128 +0,0 @@ -from os import kill, system -from redis import StrictRedis, WatchError -from multiprocessing import Process -from Queue import Empty -from msgpack import packb -from time import time, sleep - -import logging -import socket -import settings - -logger = logging.getLogger("HorizonLog") - -try: - SERVER_METRIC_PATH = settings.SERVER_METRICS_NAME + '.' -except: - SERVER_METRIC_PATH = '' - - -class Worker(Process): - """ - The worker processes chunks from the queue and appends - the latest datapoints to their respective timesteps in Redis. - """ - def __init__(self, queue, parent_pid, skip_mini, canary=False): - super(Worker, self).__init__() - self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - self.q = queue - self.parent_pid = parent_pid - self.daemon = True - self.canary = canary - self.skip_mini = skip_mini - - def check_if_parent_is_alive(self): - """ - Self explanatory. - """ - try: - kill(self.parent_pid, 0) - except: - exit(0) - - def in_skip_list(self, metric_name): - """ - Check if the metric is in SKIP_LIST. - """ - for to_skip in settings.SKIP_LIST: - if to_skip in metric_name: - return True - - return False - - def send_graphite_metric(self, name, value): - if settings.GRAPHITE_HOST != '': - sock = socket.socket() - sock.connect((settings.GRAPHITE_HOST, settings.CARBON_PORT)) - sock.sendall('%s %s %i\n' % (name, value, time())) - sock.close() - return True - - return False - - def run(self): - """ - Called when the process intializes. - """ - logger.info('started worker') - - FULL_NAMESPACE = settings.FULL_NAMESPACE - MINI_NAMESPACE = settings.MINI_NAMESPACE - MAX_RESOLUTION = settings.MAX_RESOLUTION - full_uniques = FULL_NAMESPACE + 'unique_metrics' - mini_uniques = MINI_NAMESPACE + 'unique_metrics' - pipe = self.redis_conn.pipeline() - - while 1: - - # Make sure Redis is up - try: - self.redis_conn.ping() - except: - logger.error('worker can\'t connect to redis at socket path %s' % settings.REDIS_SOCKET_PATH) - sleep(10) - self.redis_conn = StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - pipe = self.redis_conn.pipeline() - continue - - try: - # Get a chunk from the queue with a 15 second timeout - chunk = self.q.get(True, 15) - now = time() - - for metric in chunk: - - # Check if we should skip it - if self.in_skip_list(metric[0]): - continue - - # Bad data coming in - if metric[1][0] < now - MAX_RESOLUTION: - continue - - # Append to messagepack main namespace - key = ''.join((FULL_NAMESPACE, metric[0])) - pipe.append(key, packb(metric[1])) - pipe.sadd(full_uniques, key) - - if not self.skip_mini: - # Append to mini namespace - mini_key = ''.join((MINI_NAMESPACE, metric[0])) - pipe.append(mini_key, packb(metric[1])) - pipe.sadd(mini_uniques, mini_key) - - pipe.execute() - - # Log progress - if self.canary: - logger.info('queue size at %d' % self.q.qsize()) - self.send_graphite_metric('skyline.horizon.' + SERVER_METRIC_PATH + 'queue_size', self.q.qsize()) - - except Empty: - logger.info('worker queue is empty and timed out') - except WatchError: - logger.error(key) - except NotImplementedError: - pass - except Exception as e: - logger.error("worker error: " + str(e)) diff --git a/src/mirage/alerters.py b/src/mirage/alerters.py deleted file mode 100644 index 7284f1a3..00000000 --- a/src/mirage/alerters.py +++ /dev/null @@ -1,137 +0,0 @@ -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from email.MIMEImage import MIMEImage -from smtplib import SMTP -import alerters -import settings -import urllib2 - -""" -Create any alerter you want here. The function will be invoked from trigger_alert. -Two arguments will be passed, both of them tuples: alert and metric. - -alert: the tuple specified in your settings: - alert[0]: The matched substring of the anomalous metric - alert[1]: the name of the strategy being used to alert - alert[2]: The timeout of the alert that was triggered - alert[3]: The SECOND_ORDER_RESOLUTION_HOURS -metric: information about the anomaly itself - metric[0]: the anomalous value - metric[1]: The full name of the anomalous metric -""" - -# FULL_DURATION to hours so that mirage can surface the relevant timeseries data -# for analyzer comparison in the graph -full_duration_in_hours = int(settings.FULL_DURATION) / 60 / 60 - - -def alert_smtp(alert, metric, second_order_resolution_seconds): - - # SECOND_ORDER_RESOLUTION_SECONDS to hours so that mirage surfaces the - # relevant timeseries data in the graph - second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600 - - # For backwards compatibility - if '@' in alert[1]: - sender = settings.ALERT_SENDER - recipient = alert[1] - else: - sender = settings.SMTP_OPTS['sender'] - recipients = settings.SMTP_OPTS['recipients'][alert[0]] - - # Backwards compatibility - if type(recipients) is str: - recipients = [recipients] - - graph_title = '&title=skyline%%20mirage%%20ALERT%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % (second_order_resolution_in_hours, metric[1], metric[0]) - - if settings.GRAPHITE_PORT != '': - link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) - else: - link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) - - content_id = metric[1] - image_data = None - if settings.SMTP_OPTS.get('embed-images'): - try: - image_data = urllib2.urlopen(link).read() - except urllib2.URLError: - image_data = None - - # If we failed to get the image or if it was explicitly disabled, - # use the image URL instead of the content. - if image_data is None: - img_tag = '' % link - else: - img_tag = '' % content_id - - body = 'mirage ALERT
Anomalous metric: %s
value: %s at %s hours
Next alert in: %s seconds
%s' % (metric[1], metric[0], second_order_resolution_in_hours, alert[2], link, img_tag) - - for recipient in recipients: - msg = MIMEMultipart('alternative') - msg['Subject'] = 'mirage ALERT - ' + metric[1] - msg['From'] = sender - msg['To'] = recipient - - msg.attach(MIMEText(body, 'html')) - if image_data is not None: - msg_attachment = MIMEImage(image_data) - msg_attachment.add_header('Content-ID', '<%s>' % content_id) - msg.attach(msg_attachment) - - s = SMTP('127.0.0.1') - s.sendmail(sender, recipient, msg.as_string()) - s.quit() - - -def alert_pagerduty(alert, metric, second_order_resolution_seconds): - import pygerduty - pager = pygerduty.PagerDuty(settings.PAGERDUTY_OPTS['subdomain'], settings.PAGERDUTY_OPTS['auth_token']) - pager.trigger_incident(settings.PAGERDUTY_OPTS['key'], "Mirage alert - %s - %s" % (metric[0], metric[1])) - - -def alert_hipchat(alert, metric, second_order_resolution_seconds): - - # SECOND_ORDER_RESOLUTION_SECONDS to hours so that mirage surfaces the - # relevant timeseries data in the graph - second_order_resolution_in_hours = int(second_order_resolution_seconds) / 3600 - - sender = settings.HIPCHAT_OPTS['sender'] - import hipchat - hipster = hipchat.HipChat(token=settings.HIPCHAT_OPTS['auth_token']) - rooms = settings.HIPCHAT_OPTS['rooms'][alert[0]] - graph_title = '&title=skyline%%20mirage%%20ALERT%%20at%%20%s%%20hours%%0A%s%%20-%%20%s' % (second_order_resolution_in_hours, metric[1], metric[0]) - if settings.GRAPHITE_PORT != '': - link = '%s://%s:%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, settings.GRAPHITE_PORT, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) - else: - link = '%s://%s/render/?from=-%shour&target=cactiStyle(%s)%s%s&colorList=orange' % (settings.GRAPHITE_PROTOCOL, settings.GRAPHITE_HOST, second_order_resolution_in_hours, metric[1], settings.GRAPHITE_GRAPH_SETTINGS, graph_title) - embed_graph = "" + metric[1] + "" - - for room in rooms: - hipster.method('rooms/message', method='POST', parameters={'room_id': room, 'from': 'skyline', 'color': settings.HIPCHAT_OPTS['color'], 'message': '%s - mirage - Anomalous metric: %s (value: %s) at %s hours %s' % (sender, metric[1], metric[0], second_order_resolution_in_hours, embed_graph)}) - - -def alert_syslog(alert, metric, second_order_resolution_seconds): - import sys - import syslog - syslog_ident = settings.SYSLOG_OPTS['ident'] + '-mirage' - message = str("Anomalous metric: %s (value: %s)" % (metric[1], metric[0])) - if sys.version_info[:2] == (2, 6): - syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) - elif sys.version_info[:2] == (2, 7): - syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) - elif sys.version_info[:1] == (3): - syslog.openlog(ident="skyline", logoption=syslog.LOG_PID, facility=syslog.LOG_LOCAL4) - else: - syslog.openlog(syslog_ident, syslog.LOG_PID, syslog.LOG_LOCAL4) - syslog.syslog(4, message) - - -def trigger_alert(alert, metric, second_order_resolution_seconds): - - if '@' in alert[1]: - strategy = 'alert_smtp' - else: - strategy = 'alert_' + alert[1] - - getattr(alerters, strategy)(alert, metric, second_order_resolution_seconds) diff --git a/src/mirage/algorithm_exceptions.py b/src/mirage/algorithm_exceptions.py deleted file mode 100644 index 3342af97..00000000 --- a/src/mirage/algorithm_exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -class TooShort(Exception): - pass - - -class Stale(Exception): - pass - - -class Boring(Exception): - pass diff --git a/src/mirage/algorithms.py b/src/mirage/algorithms.py deleted file mode 100644 index d4fd9963..00000000 --- a/src/mirage/algorithms.py +++ /dev/null @@ -1,283 +0,0 @@ -import pandas -import numpy as np -import scipy -import statsmodels.api as sm -import traceback -import logging -from time import time - -from settings import ( - MIRAGE_ALGORITHMS, - MIRAGE_CONSENSUS, - MIRAGE_DATA_FOLDER, - MIRAGE_ENABLE_SECOND_ORDER, -) - -from algorithm_exceptions import * - -logger = logging.getLogger("MirageLog") - -""" -This is no man's land. Do anything you want in here, -as long as you return a boolean that determines whether the input -timeseries is anomalous or not. - -To add an algorithm, define it here, and add its name to settings.MIRAGE_ALGORITHMS. -""" - - -def tail_avg(timeseries, second_order_resolution_seconds): - """ - This is a utility function used to calculate the average of the last three - datapoints in the series as a measure, instead of just the last datapoint. - It reduces noise, but it also reduces sensitivity and increases the delay - to detection. - """ - try: - t = (timeseries[-1][1] + timeseries[-2][1] + timeseries[-3][1]) / 3 - return t - except IndexError: - return timeseries[-1][1] - - -def median_absolute_deviation(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the deviation of its latest datapoint with - respect to the median is X times larger than the median of deviations. - """ - - series = pandas.Series([x[1] for x in timeseries]) - median = series.median() - demedianed = np.abs(series - median) - median_deviation = demedianed.median() - - # The test statistic is infinite when the median is zero, - # so it becomes super sensitive. We play it safe and skip when this happens. - if median_deviation == 0: - return False - - test_statistic = demedianed.iget(-1) / median_deviation - - # Completely arbitary...triggers if the median deviation is - # 6 times bigger than the median - if test_statistic > 6: - return True - else: - return False - - -def grubbs(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the Z score is greater than the Grubb's score. - """ - - series = scipy.array([x[1] for x in timeseries]) - stdDev = scipy.std(series) - mean = np.mean(series) - tail_average = tail_avg(timeseries, second_order_resolution_seconds) - z_score = (tail_average - mean) / stdDev - len_series = len(series) - threshold = scipy.stats.t.isf(.05 / (2 * len_series), len_series - 2) - threshold_squared = threshold * threshold - grubbs_score = ((len_series - 1) / np.sqrt(len_series)) * np.sqrt(threshold_squared / (len_series - 2 + threshold_squared)) - - return z_score > grubbs_score - - -def first_hour_average(timeseries, second_order_resolution_seconds): - """ - Calcuate the simple average over one hour, second order resolution seconds ago. - A timeseries is anomalous if the average of the last three datapoints - are outside of three standard deviations of this value. - """ - last_hour_threshold = time() - (second_order_resolution_seconds - 3600) - series = pandas.Series([x[1] for x in timeseries if x[0] < last_hour_threshold]) - mean = (series).mean() - stdDev = (series).std() - t = tail_avg(timeseries, second_order_resolution_seconds) - - return abs(t - mean) > 3 * stdDev - - -def stddev_from_average(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the absolute value of the average of the latest - three datapoint minus the moving average is greater than three standard - deviations of the average. This does not exponentially weight the MA and so - is better for detecting anomalies with respect to the entire series. - """ - series = pandas.Series([x[1] for x in timeseries]) - mean = series.mean() - stdDev = series.std() - t = tail_avg(timeseries, second_order_resolution_seconds) - - return abs(t - mean) > 3 * stdDev - - -def stddev_from_moving_average(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the absolute value of the average of the latest - three datapoint minus the moving average is greater than three standard - deviations of the moving average. This is better for finding anomalies with - respect to the short term trends. - """ - series = pandas.Series([x[1] for x in timeseries]) - expAverage = pandas.stats.moments.ewma(series, com=50) - stdDev = pandas.stats.moments.ewmstd(series, com=50) - - return abs(series.iget(-1) - expAverage.iget(-1)) > 3 * stdDev.iget(-1) - - -def mean_subtraction_cumulation(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the value of the next datapoint in the - series is farther than three standard deviations out in cumulative terms - after subtracting the mean from each data point. - """ - - series = pandas.Series([x[1] if x[1] else 0 for x in timeseries]) - series = series - series[0:len(series) - 1].mean() - stdDev = series[0:len(series) - 1].std() - expAverage = pandas.stats.moments.ewma(series, com=15) - - return abs(series.iget(-1)) > 3 * stdDev - - -def least_squares(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the average of the last three datapoints - on a projected least squares model is greater than three sigma. - """ - - x = np.array([t[0] for t in timeseries]) - y = np.array([t[1] for t in timeseries]) - A = np.vstack([x, np.ones(len(x))]).T - results = np.linalg.lstsq(A, y) - residual = results[1] - m, c = np.linalg.lstsq(A, y)[0] - errors = [] - for i, value in enumerate(y): - projected = m * x[i] + c - error = value - projected - errors.append(error) - - if len(errors) < 3: - return False - - std_dev = scipy.std(errors) - t = (errors[-1] + errors[-2] + errors[-3]) / 3 - - return abs(t) > std_dev * 3 and round(std_dev) != 0 and round(t) != 0 - - -def histogram_bins(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if the average of the last three datapoints falls - into a histogram bin with less than 20 other datapoints (you'll need to tweak - that number depending on your data) - - Returns: the size of the bin which contains the tail_avg. Smaller bin size - means more anomalous. - """ - - series = scipy.array([x[1] for x in timeseries]) - t = tail_avg(timeseries, second_order_resolution_seconds) - h = np.histogram(series, bins=15) - bins = h[1] - for index, bin_size in enumerate(h[0]): - if bin_size <= 20: - # Is it in the first bin? - if index == 0: - if t <= bins[0]: - return True - # Is it in the current bin? - elif t >= bins[index] and t < bins[index + 1]: - return True - - return False - - -def ks_test(timeseries, second_order_resolution_seconds): - """ - A timeseries is anomalous if 2 sample Kolmogorov-Smirnov test indicates - that data distribution for last 10 minutes is different from last hour. - It produces false positives on non-stationary series so Augmented - Dickey-Fuller test applied to check for stationarity. - """ - - hour_ago = time() - 3600 - ten_minutes_ago = time() - 600 - reference = scipy.array([x[1] for x in timeseries if x[0] >= hour_ago and x[0] < ten_minutes_ago]) - probe = scipy.array([x[1] for x in timeseries if x[0] >= ten_minutes_ago]) - - if reference.size < 20 or probe.size < 20: - return False - - ks_d, ks_p_value = scipy.stats.ks_2samp(reference, probe) - - if ks_p_value < 0.05 and ks_d > 0.5: - adf = sm.tsa.stattools.adfuller(reference, 10) - if adf[1] < 0.05: - return True - - return False - - -def is_anomalously_anomalous(metric_name, ensemble, datapoint): - """ - This method runs a meta-analysis on the metric to determine whether the - metric has a past history of triggering. TODO: weight intervals based on datapoint - """ - # We want the datapoint to avoid triggering twice on the same data - new_trigger = [time(), datapoint] - - # Get the old history - raw_trigger_history = redis_conn.get('mirage_trigger_history.' + metric_name) - if not raw_trigger_history: - redis_conn.set('mirage_trigger_history.' + metric_name, packb([(time(), datapoint)])) - return True - - trigger_history = unpackb(raw_trigger_history) - - # Are we (probably) triggering on the same data? - if (new_trigger[1] == trigger_history[-1][1] and - new_trigger[0] - trigger_history[-1][0] <= 300): - return False - - # Update the history - trigger_history.append(new_trigger) - redis_conn.set('mirage_trigger_history.' + metric_name, packb(trigger_history)) - - # Should we surface the anomaly? - trigger_times = [x[0] for x in trigger_history] - intervals = [ - trigger_times[i + 1] - trigger_times[i] - for i, v in enumerate(trigger_times) - if (i + 1) < len(trigger_times) - ] - - series = pandas.Series(intervals) - mean = series.mean() - stdDev = series.std() - - return abs(intervals[-1] - mean) > 3 * stdDev - - -def run_selected_algorithm(timeseries, metric_name, second_order_resolution_seconds): - """ - Run selected algorithms - """ - try: - ensemble = [globals()[algorithm](timeseries, second_order_resolution_seconds) for algorithm in MIRAGE_ALGORITHMS] - threshold = len(ensemble) - MIRAGE_CONSENSUS - if ensemble.count(False) <= threshold: - if MIRAGE_ENABLE_SECOND_ORDER: - if is_anomalously_anomalous(metric_name, ensemble, timeseries[-1][1]): - return True, ensemble, timeseries[-1][1] - else: - return True, ensemble, timeseries[-1][1] - - return False, ensemble, timeseries[-1][1] - except: - logger.error("Algorithm error: " + traceback.format_exc()) - return False, [], 1 diff --git a/src/settings.py.example b/src/settings.py.example deleted file mode 100644 index 0b990d24..00000000 --- a/src/settings.py.example +++ /dev/null @@ -1,489 +0,0 @@ -""" -Shared settings -""" - -# The path for the Redis unix socket -REDIS_SOCKET_PATH = '/tmp/redis.sock' - -# The Skyline logs directory. Do not include a trailing slash. -LOG_PATH = '/var/log/skyline' - -# The Skyline pids directory. Do not include a trailing slash. -PID_PATH = '/var/run/skyline' - -# Metrics will be prefixed with this value in Redis. -FULL_NAMESPACE = 'metrics.' - -# Enable additional debug logging - useful for development only, this should -# definitely be set to False on as production system -ENABLE_DEBUG = False - -# The Horizon agent will make T'd writes to both the full namespace and the -# mini namespace. Oculus gets its data from everything in the mini namespace. -MINI_NAMESPACE = 'mini.' - -# This is the rolling duration that will be stored in Redis. Be sure to pick a -# value that suits your memory capacity, your CPU capacity, and your overall -# metrics count. Longer durations take a longer to analyze, but they can -# help the algorithms reduce the noise and provide more accurate anomaly -# detection. -FULL_DURATION = 86400 - -# This is the duration of the 'mini' namespace, if you are also using the -# Oculus service. It is also the duration of data that is displayed in the -# web app 'mini' view. -MINI_DURATION = 3600 - -# If you have a Graphite host set up, set this metric to get graphs on -# Skyline and Horizon. Don't include http:// since this is used for carbon host as well. -GRAPHITE_HOST = 'your_graphite_host.com' - -# Graphite host protocol - http or https -GRAPHITE_PROTOCOL = 'http' - -# Graphite host port - for a specific port if graphite runs on a port other than -# 80, e.g. '8888' -GRAPHITE_PORT = '' - -# These are graphite settings in terms of alert graphs - this is defaulted to a -# format that is more colourblind friendly than the default graphite graphs -GRAPHITE_GRAPH_SETTINGS = '&width=588&height=308&bgcolor=000000&fontBold=true&fgcolor=C0C0C0' - -# If you have a Graphite host set up, set its Carbon port. -CARBON_PORT = 2003 - -# If you have Oculus set up, set this metric to set the clickthrough on the -# webapp. Include http://. If you don't want to use Oculus, set this to an -# empty string. If you comment this out, Skyline won't work! Speed improvements -# will occur when Oculus support is disabled. -OCULUS_HOST = 'http://your_oculus_host.com' - -# This is to allow for multiple skyline nodes to send metrics to a graphite -# instance on the skyline namespace sharded by this setting, like carbon.relays. -# If you want multiple skyline hosts, set the hostname of the skyline here e.g. -# skyline.analyzer.run_time -# skyline.analyzer.skyline-01.run_time -SERVER_METRICS_NAME = '' - -""" -Analyzer settings -""" - -# This is the location the Skyline agent will write the anomalies file to disk. -# It needs to be in a location accessible to the webapp. -ANOMALY_DUMP = 'webapp/static/dump/anomalies.json' - -# This is the location the Skyline agent will write the second order resolution -# anomalies to check to file on disk - absolute path -MIRAGE_CHECK_PATH = '/opt/skyline/mirage/check' - -# This is the number of processes that the Skyline analyzer will spawn. -# Analysis is a very CPU-intensive procedure. You will see optimal results -# if you set ANALYZER_PROCESSES to several less than the total number of -# CPUs on your box. Be sure to leave some CPU room for the Horizon workers, -# and for Redis. -ANALYZER_PROCESSES = 5 - -# This is the duration, in seconds, for a metric to become 'stale' and for -# the analyzer to ignore it until new datapoints are added. 'Staleness' means -# that a datapoint has not been added for STALE_PERIOD seconds. -STALE_PERIOD = 500 - -# This is the minimum length of a timeseries, in datapoints, for the analyzer -# to recognize it as a complete series. -MIN_TOLERABLE_LENGTH = 1 - -# Sometimes a metric will continually transmit the same number. There's no need -# to analyze metrics that remain boring like this, so this setting determines -# the amount of boring datapoints that will be allowed to accumulate before the -# analyzer skips over the metric. If the metric becomes noisy again, the -# analyzer will stop ignoring it. -MAX_TOLERABLE_BOREDOM = 100 - -# By default, the analyzer skips a metric if it it has transmitted a single -# number MAX_TOLERABLE_BOREDOM times. Change this setting if you wish the size -# of the ignored set to be higher (ie, ignore the metric if there have only -# been two different values for the past MAX_TOLERABLE_BOREDOM datapoints). -# This is useful for timeseries that often oscillate between two values. -BOREDOM_SET_SIZE = 1 - -# The canary metric should be a metric with a very high, reliable resolution -# that you can use to gauge the status of the system as a whole. -CANARY_METRIC = 'statsd.numStats' - -# These are the algorithms that the Analyzer will run. To add a new algorithm, -# you must both define the algorithm in algorithms.py and add its name here. -ALGORITHMS = [ - 'first_hour_average', - 'mean_subtraction_cumulation', - 'stddev_from_average', - 'stddev_from_moving_average', - 'least_squares', - 'grubbs', - 'histogram_bins', - 'median_absolute_deviation', - 'ks_test', -] - -# This is the number of algorithms that must return True before a metric is -# classified as anomalous. -CONSENSUS = 6 - -# This is to enable second order anomalies. This is an experimental feature, so -# it's turned off by default. -ENABLE_SECOND_ORDER = False - -# This enables analyzer alerting. -ENABLE_ALERTS = True - -# This enables analyzer to output for mirage -ENABLE_MIRAGE = False - -# This enables FULL_DURATION alerting - if True analyzer will send alerts on -# any alert tuple that have a SECOND_ORDER_RESOLUTION_HOURS defined, if False -# analyzer will only add a mirage check and allow mirage to do the alerting -ENABLE_FULL_DURATION_ALERTS = True - -# This is the config for which metrics to alert on and which strategy to use for each. -# Alerts will not fire twice within EXPIRATION_TIME, even if they trigger again. -# Schema: ( -# ('metric1', 'smtp', EXPIRATION_TIME), -# ('metric2', 'pagerduty', EXPIRATION_TIME), -# ('metric3', 'hipchat', EXPIRATION_TIME), -# ('stats', 'syslog', EXPIRATION_TIME), -# Wildcard namespaces can be used as well -# ('metric4.thing.*.requests', 'stmp', EXPIRATION_TIME), -# mirage - SECOND_ORDER_RESOLUTION_HOURS - if added and mirage is enabled -# ('metric5.thing.*.rpm', 'smtp', EXPIRATION_TIME, SECOND_ORDER_RESOLUTION_HOURS), -# ) -ALERTS = ( - ('skyline', 'smtp', 1800), -) - -# Each alert module requires additional information. -SMTP_OPTS = { - # This specifies the sender of email alerts. - 'sender': 'skyline-alerts@etsy.com', - # recipients is a dictionary mapping metric names - # (exactly matching those listed in ALERTS) to an array of e-mail addresses - 'recipients': { - 'skyline': ['abe@etsy.com', 'you@yourcompany.com'], - }, - 'embed-images': True, -} - -# HipChat alerts require python-simple-hipchat -HIPCHAT_OPTS = { - 'auth_token': 'hipchat_auth_token', - 'sender': 'hostname or identifier', - # list of hipchat room_ids to notify about each anomaly - # (similar to SMTP_OPTS['recipients']) - 'rooms': { - 'skyline': (12345,), - }, - # Background color of hipchat messages - # (One of 'yellow', 'red', 'green', 'purple', 'gray', or 'random'.) - 'color': 'purple', -} - -# PagerDuty alerts require pygerduty -PAGERDUTY_OPTS = { - # Your pagerduty subdomain and auth token - 'subdomain': 'example', - 'auth_token': 'your_pagerduty_auth_token', - # Service API key (shown on the detail page of a 'Generic API' service) - 'key': 'your_pagerduty_service_api_key', -} - -# syslog alerts requires an ident -# Adds a LOG_WARNING message to the LOG_LOCAL4 which is local and will ship to -# any syslog or rsyslog down the line. The EXPIRATION_TIME for the syslog alert -# method should be set to 1 to fire every anomaly into the syslog -SYSLOG_OPTS = { - 'ident': 'skyline', -} - - -""" -Horizon settings -""" -# This is the number of worker processes that will consume from the Horizon -# queue. -WORKER_PROCESSES = 2 - -# The IP address for Horizon to listen on. Defaults to gethostname() -# HORIZON_IP = '0.0.0.0' - -# This is the port that listens for Graphite pickles over TCP, sent by Graphite's -# carbon-relay agent. -PICKLE_PORT = 2024 - -# This is the port that listens for Messagepack-encoded UDP packets. -UDP_PORT = 2025 - -# This is how big a 'chunk' of metrics will be before they are added onto -# the shared queue for processing into Redis. If you are noticing that Horizon -# is having trouble consuming metrics, try setting this value a higher. -CHUNK_SIZE = 10 - -# This is the maximum allowable length of the processing queue before new -# chunks are prevented from being added. If you consistently fill up the -# processing queue, a higher MAX_QUEUE_SIZE will not save you. It most likely -# means that the workers do not have enough CPU alotted in order to process the -# queue on time. Try increasing CHUNK_SIZE, decreasing ANALYZER_PROCESSES, or -# decreasing ROOMBA_PROCESSES. -MAX_QUEUE_SIZE = 500 - -# This is the number of Roomba processes that will be spawned to trim -# timeseries in order to keep them at FULL_DURATION. Keep this number small, -# as it is not important that metrics be exactly FULL_DURATION *all* the time. -ROOMBA_PROCESSES = 1 - -# Normally Roomba will clean up everything that is older than FULL_DURATION -# if you have metrics that are not coming in every second, it can happen -# that you'll end up with INCOMPLETE metrics. -# With this setting Roomba will clean up evertyhing that is older than -# FULL_DURATION + ROOMBA_GRACE_TIME -ROOMBA_GRACE_TIME = 600 - -# The Horizon agent will ignore incoming datapoints if their timestamp -# is older than MAX_RESOLUTION seconds ago. -MAX_RESOLUTION = 1000 - -# These are metrics that, for whatever reason, you do not want to store -# in Skyline. The Listener will check to see if each incoming metrics -# contains anything in the skip list. It is generally wise to skip entire -# namespaces by adding a '.' at the end of the skipped item - otherwise -# you might skip things you don't intend to. For example the default -# skyline.analyzer.anomaly_breakdown. which MUST be skipped to prevent crazy -# feedback. -SKIP_LIST = [ - 'skyline.analyzer.anomaly_breakdown.', - 'example.statsd.metric', - 'another.example.metric', - # Skip anomaly_breakdown - 'skyline.analyzer.*.anomaly_breakdown', - # if you use statsd, these can result in many near-equal series - # '_90', - # '.lower', - # '.upper', - # '.median', - # '.count_ps', - # '.sum', -] - - -""" -Mirage settings -""" - -# This is the path for the Mirage data folder where timeseries data that has -# been surfaced will be written - absolute path -MIRAGE_DATA_FOLDER = '/opt/skyline/mirage/data' - -# These are the algorithms that Mirage will run. To add a new algorithm, -# you must both define the algorithm in algorithms.py and add its name here. -MIRAGE_ALGORITHMS = [ - 'first_hour_average', - 'mean_subtraction_cumulation', - 'stddev_from_average', - 'stddev_from_moving_average', - 'least_squares', - 'grubbs', - 'histogram_bins', - 'median_absolute_deviation', - 'ks_test', -] - -# The number of seconds after which a check is considered stale and discarded. -MIRAGE_STALE_SECONDS = 120 - -# This is the number of algorithms that must return True before a metric is -# classified as anomalous. -MIRAGE_CONSENSUS = 6 - -# This is to enable second order anomalies. This is an experimental feature, so -# it's turned off by default. -ENABLE_SECOND_ORDER = False - -# This enables mirage alerting. -MIRAGE_ENABLE_ALERTS = False - -# This is to enables mirage to negate analyzer alerts, mirage will send out an -# alert for every anomaly that analyzer sends to mirage that is NOT anomalous at -# the SECOND_ORDER_RESOLUTION_HOURS with a SECOND_ORDER_RESOLUTION_HOURS graph -# and the analyzer FULL_DURATION graph embedded. Mostly for testing and -# comparison of analysis at different time ranges and/or algorithms -NEGATE_ANALYZER_ALERTS = False - - -""" -Boundary settings -""" - -# This is the number of processes that boundary will spawn. -# Seeing as boundary analysis is focused at specific metrics this should be less -# than the number of ANALYZER_PROCESSES, be sure to leave some CPU room for -# Analyzer, Horizon workers and Redis. -BOUNDARY_PROCESSES = 2 - -# Enable additional debug logging - useful for development only, this should -# definitely be set to False on as production system - LOTS of output -ENABLE_BOUNDARY_DEBUG = False - -# These are the algorithms that boundary can run. To add a new algorithm, -# you must both define the algorithm in algorithms.py and add its name here. -BOUNDARY_ALGORITHMS = [ - 'detect_drop_off_cliff', - 'greater_than', - 'less_than', -] - -# This enables boundary alerting -BOUNDARY_ENABLE_ALERTS = False - -# This is the config for metrics to analyse with the boundary algorithms. -# It is advisable that you only specify high rate metrics and global -# metrics here, although the algoritms should work with low rate metrics, the -# smaller the range, the smaller a cliff drop of change is, meaning more noise, -# however some algorithms are pre-tuned to use different trigger values on -# different ranges to pre-filter some noise. -# Schema: ( -# ('metric1', 'algorithm1', EXPIRATION_TIME, MIN_AVERAGE, MIN_AVERAGE_SECONDS, TRIGGER_VALUE, ALERT_THRESHOLD, 'ALERT_VIAS'), -# ('metric2', 'algorithm2', EXPIRATION_TIME, MIN_AVERAGE, MIN_AVERAGE_SECONDS, TRIGGER_VALUE, ALERT_THRESHOLD, 'ALERT_VIAS'), -# Wildcard namespaces can be used as well -# ('metric.thing.*.requests', 'algorithm1', EXPIRATION_TIME, MIN_AVERAGE, MIN_AVERAGE_SECONDS, TRIGGER_VALUE, ALERT_THRESHOLD, 'ALERT_VIAS'), -# ) -# Algorithm variables (all are required): -# EXPIRATION_TIME: Alerts will not fire twice within this amount of seconds, -# even if they trigger again. -# MIN_AVERAGE: the minimum average value to evaluate for detect_drop_off_cliff, -# in less_than and greater_than set to 0 -# MIN_AVERAGE_SECONDS: the seconds to calculate the minimum average value over -# in detect_drop_off_cliff. So if MIN_AVERAGE set to 100 -# and MIN_AVERAGE_SECONDS to 3600 a metric will only be -# analysed if the average value of the metric over 3600 -# seconds is greater than 100. In less_than and -# greater_than set to 0 -# TRIGGER_VALUE: then less_than or greater_than trigger value set to 0 for -# detect_drop_off_cliff -# ALERT_THRESHOLD: alert after detected x times. This allows you to set how many -# times a timeseries has to be detected by the algorithm as -# anomalous before alerting on it. The nature of distributed -# metric collection, storage and analysis can have a lag every -# now and then due to latency, I/O pause, etc. boundary algorithms -# can be sensitive to this not unexpectedly. This setting should -# be 1, maybe 2 maximum to ensure that signals are not being -# surpressed. Try 1 if you are getting the occassional false -# positive, try 2. NOTE: any greater_than metrics should have -# this as 1. -# ALERT_VIAS: pipe separated alerters to send to. -# -# Wildcard and absolute metric paths. Currently the only supported metric -# namespaces are a parent namespace and an absolute metric path e.g. -# ('stats_counts.someapp.things', 'detect_drop_off_cliff', 1800, 500, 3600, 0, 2, 'smtp'), -# ('stats_counts.someapp.things.an_important_thing.requests', 'detect_drop_off_cliff', 600, 100, 3600, 0, 2, 'smtp|pagerduty'), -# ('stats_counts.otherapp.things.*.requests', 'detect_drop_off_cliff', 600, 500, 3600, 0, 2, 'smtp|hipchat'), -# In the above all stats_counts.someapp.things* would be painted with a 1800 -# EXPIRATION_TIME and 500 MIN_AVERAGE, but those values would be overridden by -# 600 and 100 stats_counts.someapp.things.an_important_thing.requests and -# pagerduty added. -BOUNDARY_METRICS = ( - # ('metric', 'algorithm', EXPIRATION_TIME, MIN_AVERAGE, MIN_AVERAGE_SECONDS, TRIGGER_VALUE, ALERT_THRESHOLD, 'ALERT_VIAS'), - ('metric1', 'detect_drop_off_cliff', 1800, 500, 3600, 0, 2, 'smtp'), - ('metric2.either', 'less_than', 3600, 0, 0, 15, 2, 'smtp|hipchat'), - ('nometric.other', 'greater_than', 3600, 0, 0, 100000, 1, 'smtp'), -) - -# The BOUNDARY_AUTOAGGRERATION function used to autoaggregate a timeseries. -# If a timeseries dataset has 6 datapoints per minute but only one data value -# every minute then autoaggregate can be used to aggregate the required sample. -BOUNDARY_AUTOAGGRERATION = False - -# Declare the namespace and aggregation value in seconds by which you want the -# timeseries aggregated, e.g METRIC_NAMESPACE, AGGREGATION_VALUE -# To aggregate a timeseries to minutely values use 60 as the AGGREGATION_VALUE -# e.g. sum metric datapoints by minute -BOUNDARY_AUTOAGGRERATION_METRICS = ( - ('nometrics.either', 60), -) - -# boundary Alerting -# Because you may want to alert multiple channels on each metric and algorithm, -# booundary has its own alerting settings, similar to analyzer. However due to -# the nature of boundary and it algorithms it could be VERY noisy and expensive -# if all your metrics dropped off a cliff, so boundary introduces alerting the -# ability to limit overall alerts to an alerter channel. These limits use the -# same methodology that the alerts use, but we key each alerter too. -BOUNDARY_ALERTER_OPTS = { - # When an alert is sent as key is set with in the alert namespaces with an - # expiration value - 'alerter_expiration_time': { - 'smtp': 60, - 'pagerduty': 1800, - 'hipchat': 1800, - }, - # If alerter keys >= limit in the above alerter_expiration_time do not alert - # to this channel until keys < limit - 'alerter_limit': { - 'smtp': 100, - 'pagerduty': 15, - 'hipchat': 30, - }, -} - -# Each alert module requires additional information. -BOUNDARY_SMTP_OPTS = { - # This specifies the sender of email alerts. - 'sender': 'skyline-boundary@example.com', - # recipients is a dictionary mapping metric names - # (exactly matching those listed in ALERTS) to an array of e-mail addresses - 'recipients': { - 'nometrics': ['me@example.com', 'you@some-company.com'], - 'nometrics.either': ['you@example.com', 'another@some-company.com'], - }, - 'embed-images': True, - # Send graphite graphs at the most meaningful resolution if different from - # FULL_DURATION - 'graphite_previous_hours': 7, - 'graphite_graph_line_color': 'pink', -} - -# HipChat alerts require python-simple-hipchat -BOUNDARY_HIPCHAT_OPTS = { - 'auth_token': 'hipchat_auth_token', - 'sender': 'hostname or identifier', - # list of hipchat room_ids to notify about each anomaly - # (similar to SMTP_OPTS['recipients']) - # Wildcard metric namespacing is allowed - 'rooms': { - 'nometrics': (12345,), - }, - # Background color of hipchat messages - # (One of 'yellow', 'red', 'green', 'purple', 'gray', or 'random'.) - 'color': 'purple', - # Send graphite graphs at the most meaningful resolution if different from - # FULL_DURATION - 'graphite_previous_hours': 7, - 'graphite_graph_line_color': 'pink', -} - -# PagerDuty alerts require pygerduty -BOUNDARY_PAGERDUTY_OPTS = { - # Your pagerduty subdomain and auth token - 'subdomain': 'example', - 'auth_token': 'your_pagerduty_auth_token', - # Service API key (shown on the detail page of a 'Generic API' service) - 'key': 'your_pagerduty_service_api_key', -} - - -""" -Webapp settings -""" - -# The IP address for the webapp -WEBAPP_IP = '127.0.0.1' - -# The port for the webapp -WEBAPP_PORT = 1500 diff --git a/src/webapp/static/css/bootstrap.min.css b/src/webapp/static/css/bootstrap.min.css deleted file mode 100644 index 140f731d..00000000 --- a/src/webapp/static/css/bootstrap.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover{color:#808080}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret{border-top-color:#555;border-bottom-color:#555}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/src/webapp/static/css/font-awesome.min.css b/src/webapp/static/css/font-awesome.min.css deleted file mode 100644 index d4e45b3c..00000000 --- a/src/webapp/static/css/font-awesome.min.css +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Font Awesome 3.0.2 - * the iconic font designed for use with Twitter Bootstrap - * ------------------------------------------------------- - * The full suite of pictographic icons, examples, and documentation - * can be found at: http://fortawesome.github.com/Font-Awesome/ - * - * License - * ------------------------------------------------------- - * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL - * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - - * http://opensource.org/licenses/mit-license.html - * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ - * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: - * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" - - * Contact - * ------------------------------------------------------- - * Email: dave@davegandy.com - * Twitter: http://twitter.com/fortaweso_me - * Work: Lead Product Designer @ http://kyruus.com - */ - -@font-face{ - font-family:'FontAwesome'; - src:url('../font/fontawesome-webfont.eot?v=3.0.1'); - src:url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), - url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'), - url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); - font-weight:normal; - font-style:normal } - -[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}a [class^="icon-"],a [class*=" icon-"]{display:inline-block}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}.btn [class^="icon-"],.nav [class^="icon-"],.btn [class*=" icon-"],.nav [class*=" icon-"]{display:inline}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}li [class^="icon-"],.nav li [class^="icon-"],li [class*=" icon-"],.nav li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li [class^="icon-"].icon-large,.nav li [class^="icon-"].icon-large,li [class*=" icon-"].icon-large,.nav li [class*=" icon-"].icon-large{width:1.5625em}ul.icons{list-style-type:none;text-indent:-0.75em}ul.icons li [class^="icon-"],ul.icons li [class*=" icon-"]{width:.75em}.icon-muted{color:#eee}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-moz-document url-prefix(){.icon-spin{height:.9em}.btn .icon-spin{height:auto}.icon-spin.icon-large{height:1.25em}.btn .icon-spin.icon-large{height:.75em}}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"} \ No newline at end of file diff --git a/src/webapp/static/css/skyline.css b/src/webapp/static/css/skyline.css deleted file mode 100644 index 38c140bc..00000000 --- a/src/webapp/static/css/skyline.css +++ /dev/null @@ -1,278 +0,0 @@ -body { - background: #f5f5f1; -} - -.selected { - background: #CCE9F0; -} - -.selected > a > .name { - color: black; -} -.selected .count { - color: black; -} -.selected .oculus { - color: black; -} - -.sub { - width: 100%; - padding: 3px 0px; - color: gray; -} - -.oculus { - margin-left: -20px; -} - -.container { - padding-left: 40px; - padding-right: 40px; -} - -.name { - display: inline-block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - padding-right: 20px; - max-width: 66%; - padding-left: 5px; -} -.count { - margin-top: 1px; - float: right; - padding-right: 5px; -} - -.badge { - background: #67BED3; -} - -.container { - width: 1400px; -} - -a { - color: gray; -} - -a:hover { - color: gray; - text-decoration: none; -} - -.oculus:hover { - color: #728BC0; -} - -#navbar { - height: 35px; - background: #1C1C1C; -} - -#logo { - font-weight: bold; - font-size: 25px; - color: #F9F9F9; - position: relative; - top: 6px; -} - -#wat { - position: relative; - top: 4px; - left: 5px; -} - -#wat:hover { - color: white; -} - -#title_container{ - padding-top: 25px; - padding-bottom: 20px; - font-weight: bold; - font-size: 20px; - text-align: left; -} - -#graph_title { - float: left; - display: inline-block; - max-width: 85%; - text-overflow: ellipsis; - overflow: hidden; - height: 24px; -} - -#graph_link { - padding-top: 3px; - font-size: 16px; - display: inline-block; - margin: 0 auto; - text-align: center; - padding-left: 10px; - float: left; - display: inline-block; -} - -#deviance { - float: right; - display: inline-block; -} - -#legend { - font-weight: bold; - font-size: 15px; - text-align: left; - padding-bottom: 25px; - margin: 0 5px 0 5px; -} - -#legend_name { - display: inline-block; - float: left; -} - -#legend_coefficient { - display: inline-block; - float: right; -} - -.metric_container { - display: inline-block; - position: relative; - float: left; - padding-bottom: 30px; -} - -#m8, #m9 { - margin-right: 0; -} - -#graph_container { - height: 280px; - width: 100%; - margin-bottom: 90px; - margin-top: 15px; - border-radius: 4px; -} - -#mini_graphite { - height: 200px; - width: 100%; -} - -#graphite { - height: 140px; - width: 100%; -} - -#metrics_listings { - border: 1px solid #DDD; - border-radius: 4px; - background: #fff; - height: 100%; - overflow-y: scroll; -} - -#metrics_main { - padding: 0px; - margin: 0px; - height: 400px; - padding-bottom: 50px; -} - -#m0 > .sub:first-child { - border-top-left-radius: 4px; -} - -@media (max-width: 1450px) { - .sub { - font-size: 12px; - } - - .count { - font-size: 12px; - } - - .badge { - padding: 2px 5px; - font-size: 10px; - line-height 11px; - } - - .container { - width: 1040px; - } - - #metrics_main { - height: 390px; - } - - #mini_graphite { - height: 145px; - width: 100%; - } - - #graphite { - height: 100px; - width: 100%; - } - - #graph_container { - height: 245px; - } -} - -.dygraph-axis-label-y1 { - display: none; -} - -#mini { - height: 340px; - width: 100%; - margin-bottom: 15px; - margin-top: 5px; - padding-bottom: 7px; - padding-right: 5px; - border-radius: 4px; - background: white; - border: 1px solid #DDD; -} - -#graph { - height: 160px; - width: 100%; - margin-bottom: 15px; - margin-top: 5px; - padding-bottom: 7px; - padding-right: 5px; - background: white; - border-radius: 4px; - border: 1px solid #DDD; -} - -.duration { - font-size: 12px; - padding-left: 5px; -} - -#mini_label, #big_label { - display: inline; - font-size: 12px; - float: right; - margin-top: -20px; -} - -//#mini > div > div { -// width: 15px; -// text-overflow: ellipsis; -//} -// -//#graph > div > div { -// width: 15px; -// text-overflow: ellipsis; -//} diff --git a/src/webapp/static/font/FontAwesome.otf b/src/webapp/static/font/FontAwesome.otf deleted file mode 100644 index 64049bf2e79940063b59be135872baadc37df6f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48748 zcmce;33yXQ_b{F`x%Z|wP?jdxCcXETMG)DgpaLoih+1|cyR@ZE7uqIilI~6SeP7bO zh1wQcwz3MyqA2i!Rz^7PaG@6lh3l}MC+63OvZtJlwZsM=tYN+j@4A_)$S4KeL{ z+R`JD$onJ`55Gu#NZ9Q^4iv+?7WmGOgo2SdOb(>sy<}2ktTk!YGm6(F61pGSjED_M zGD&4?;FZJyU)m57tDme~I|ASbOC+*5lhIq$ zMuY#HnX_jT=nt)l(T7;{&^TOg_O}}St&w{Fzxuc6L#?EGxDj5$n{WW_Z#9R6>0?98 z(f%P;t2t^%yjaCxv_^&MEzrB!>Oa)|(vSqb|MCz+Sh8e^#3(UIk|kzIlq5nDDY3$m zJ|vkfnIoACsV602@P3Cx59#$#%L4E9l2~~9OP-Y&B%$!u4DHEpi1;o{GE;091Iy=s z4^+~^3~ep&HA@oUm(+Y3S{k6;lafU7oe^N`CF|fDA&CbFAyD^e$!f_eiB7TtYCR=c z0686$Et6~l`d$rnb&?HG-wcqWL}>mn_L(q)+0dhVCJ#Z)7^o8>_U;}>xY))Y`Zhv6 zE6kkC@qfYw8F47o7=|7$&i1c1Bv0l*N(dIhpnj}4@@Oa{SRv(6fW>%74MQk5H8^#68;a>)RId;}0Dm-xW%K1q+{gybLNCrscc1owOO^Zk$a zuj=34AKo9;Z|G0&FYn*g-_w7r|DFEN`+x1f{hQZs>N|wN`b)b1)>6_xx_^BC;{G-L z!Tk~aJNr%jnf;ahZT(03FZ93L|7CywZ<60e155r&BDu5W&YC-mKmYdgn$OpaR*q&z zD@HS;>Cu!#GTLLbbhKpDol&<(4UYPK)NiBuN8KFt>!@GEm74#*MTP(W_KgJiI|~%x zDe;hHN=(uVa6V*85~xhVssH=;p7Iw6u|VCs^+=k0#X3Xjdtf9(xnZ~tX-nRbC#etR z8knA#XEG%ZiS>r^Vn6q!NjrI_*nTmoM+jODPm&%Up9o(pyK_f`3u%CRw~k!$SO52n z@$OvimJPRq`lJnkd$%pTBYleFfpYSMe2O?9l6L3szNbCz+>vKW=D|1qzs~9ZkY79m zXCA^~0 zhV$?w_2lrp`ySF_`=RI1x&na>!GvO4&>|tDqFdz@|!X1*n3v;;se|=_3)TGYv zy#MQ%VQ!-de0YWr!-KkVvF{;RL*=9H_T{dxh5X`s>kZ?HtULL7WJ(spm%tYD!{r1H z73!WJq#q#p;jawplC%g5p2IXm#yE_JyK}j>4C<0{@g4Ll!h<}(xjROHH3PmQ<(ZO| z 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

d^-+(?(Bwcv``WwKADc0C#_}15X55&`&&;0n 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 diff --git a/src/webapp/static/font/fontawesome-webfont.svg b/src/webapp/static/font/fontawesome-webfont.svg deleted file mode 100755 index ba0afe5e..00000000 --- a/src/webapp/static/font/fontawesome-webfont.svg +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/webapp/static/font/fontawesome-webfont.ttf b/src/webapp/static/font/fontawesome-webfont.ttf deleted file mode 100755 index d46172476a3c7caf4f44946e3c40218559f3edfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55096 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%0D$gSMd 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

R}45_uzO@F4>M&+fi#l7 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�BcG>P)^DXFH|n4bmKVUj~;gSQkVFL7%ihZM>XC4 yQMX@enqCS@LyX>zjG!}8S``+2*O*S81puws$XSmd6%#S@(X+rh{^NpC5dI5Q|KZmF diff --git a/src/webapp/static/font/fontawesome-webfont.woff b/src/webapp/static/font/fontawesome-webfont.woff deleted file mode 100755 index 3c89ae09b88b38d3bc8563ca69f7f401b7301f45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29380 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 diff --git a/src/webapp/static/js/dygraph-combined.js b/src/webapp/static/js/dygraph-combined.js deleted file mode 100644 index 3156c2ca..00000000 --- a/src/webapp/static/js/dygraph-combined.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! @license Copyright 2011 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ -Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(a,c,b){if(typeof(b)=="undefined"){b=10}for(;parseInt(a,10)1;b/=10){a=c.toString()+a}return a.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(a){return Date.ext.locales[a.locale].a[a.getDay()]},A:function(a){return Date.ext.locales[a.locale].A[a.getDay()]},b:function(a){return Date.ext.locales[a.locale].b[a.getMonth()]},B:function(a){return Date.ext.locales[a.locale].B[a.getMonth()]},c:"toLocaleString",C:function(a){return Date.ext.util.xPad(parseInt(a.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(a){return Date.ext.util.xPad(parseInt(Date.ext.util.G(a)/100,10),0)},G:function(c){var e=c.getFullYear();var b=parseInt(Date.ext.formats.V(c),10);var a=parseInt(Date.ext.formats.W(c),10);if(a>b){e++}else{if(a===0&&b>=52){e--}}return e},H:["getHours","0"],I:function(b){var a=b.getHours()%12;return Date.ext.util.xPad(a===0?12:a,0)},j:function(c){var a=c-new Date(""+c.getFullYear()+"/1/1 GMT");a+=c.getTimezoneOffset()*60000;var b=parseInt(a/60000/60/24,10)+1;return Date.ext.util.xPad(b,0,100)},m:function(a){return Date.ext.util.xPad(a.getMonth()+1,0)},M:["getMinutes","0"],p:function(a){return Date.ext.locales[a.locale].p[a.getHours()>=12?1:0]},P:function(a){return Date.ext.locales[a.locale].P[a.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(a){var b=a.getDay();return b===0?7:b},U:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=6-e.getDay();var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0)},V:function(e){var c=parseInt(Date.ext.formats.W(e),10);var a=(new Date(""+e.getFullYear()+"/1/1")).getDay();var b=c+(a>4||a<=1?0:1);if(b==53&&(new Date(""+e.getFullYear()+"/12/31")).getDay()<4){b=1}else{if(b===0){b=Date.ext.formats.V(new Date(""+(e.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(b,0)},w:"getDay",W:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=7-Date.ext.formats.u(e);var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0,10)},y:function(a){return Date.ext.util.xPad(a.getFullYear()%100,0)},Y:"getFullYear",z:function(c){var b=c.getTimezoneOffset();var a=Date.ext.util.xPad(parseInt(Math.abs(b/60),10),0);var e=Date.ext.util.xPad(b%60,0);return(b>0?"-":"+")+a+e},Z:function(a){return a.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(a){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(a){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var c=this;while(a.match(/%[cDhnrRtTxXzZ]/)){a=a.replace(/%([cDhnrRtTxXzZ])/g,function(e,d){var g=Date.ext.aggregates[d];return(g=="locale"?Date.ext.locales[c.locale][d]:g)})}var b=a.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(e,d){var g=Date.ext.formats[d];if(typeof(g)=="string"){return c[g]()}else{if(typeof(g)=="function"){return g.call(c,c)}else{if(typeof(g)=="object"&&typeof(g[0])=="string"){return Date.ext.util.xPad(c[g[0]](),g[1])}else{return d}}}});c=null;return b};"use strict";function RGBColorParser(f){this.ok=false;if(f.charAt(0)=="#"){f=f.substr(1,6)}f=f.replace(/ /g,"");f=f.toLowerCase();var b={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};for(var g in b){if(f==g){f=b[g]}}var e=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(i){return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(i){return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(i){return[parseInt(i[1]+i[1],16),parseInt(i[2]+i[2],16),parseInt(i[3]+i[3],16)]}}];for(var c=0;c255)?255:this.r);this.g=(this.g<0||isNaN(this.g))?0:((this.g>255)?255:this.g);this.b=(this.b<0||isNaN(this.b))?0:((this.b>255)?255:this.b);this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var l=this.r.toString(16);var k=this.g.toString(16);var i=this.b.toString(16);if(l.length==1){l="0"+l}if(k.length==1){k="0"+k}if(i.length==1){i="0"+i}return"#"+l+k+i}}function printStackTrace(b){b=b||{guess:true};var c=b.e||null,e=!!b.guess;var d=new printStackTrace.implementation(),a=d.run(c);return(e)?d.guessAnonymousFunctions(a):a}printStackTrace.implementation=function(){};printStackTrace.implementation.prototype={run:function(a,b){a=a||this.createException();b=b||this.mode(a);if(b==="other"){return this.other(arguments.callee)}else{return this[b](a)}},createException:function(){try{this.undef()}catch(a){return a}},mode:function(a){if(a["arguments"]&&a.stack){return"chrome"}else{if(typeof a.message==="string"&&typeof window!=="undefined"&&window.opera){if(!a.stacktrace){return"opera9"}if(a.message.indexOf("\n")>-1&&a.message.split("\n").length>a.stacktrace.split("\n").length){return"opera9"}if(!a.stack){return"opera10a"}if(a.stacktrace.indexOf("called from line")<0){return"opera10b"}return"opera11"}else{if(a.stack){return"firefox"}}}return"other"},instrumentFunction:function(b,d,e){b=b||window;var a=b[d];b[d]=function c(){e.call(this,printStackTrace().slice(4));return b[d]._instrumented.apply(this,arguments)};b[d]._instrumented=a},deinstrumentFunction:function(a,b){if(a[b].constructor===Function&&a[b]._instrumented&&a[b]._instrumented.constructor===Function){a[b]=a[b]._instrumented}},chrome:function(b){var a=(b.stack+"\n").replace(/^\S[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^([^\(]+?)([\n$])/gm,"{anonymous}()@$1$2").replace(/^Object.\s*\(([^\)]+)\)/gm,"{anonymous}()@$1").split("\n");a.pop();return a},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")},opera11:function(g){var a="{anonymous}",h=/^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;var k=g.stacktrace.split("\n"),l=[];for(var c=0,f=k.length;c/,"$1").replace(//,a);l.push(b+"@"+j+" -- "+k[c+1].replace(/^\s+/,""))}}return l},opera10b:function(g){var a="{anonymous}",h=/^(.*)@(.+):(\d+)$/;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c=0){l=l.substr(0,c)}if(l){b=l+b;d=k.exec(b);if(d&&d[1]){return d[1]}d=g.exec(b);if(d&&d[1]){return d[1]}d=h.exec(b);if(d&&d[1]){return d[1]}}}return"(?)"}};CanvasRenderingContext2D.prototype.installPattern=function(e){if(typeof(this.isPatternInstalled)!=="undefined"){throw"Must un-install old line pattern before installing a new one."}this.isPatternInstalled=true;var g=[0,0];var b=[];var f=this.beginPath;var d=this.lineTo;var c=this.moveTo;var a=this.stroke;this.uninstallPattern=function(){this.beginPath=f;this.lineTo=d;this.moveTo=c;this.stroke=a;this.uninstallPattern=undefined;this.isPatternInstalled=undefined};this.beginPath=function(){b=[];f.call(this)};this.moveTo=function(h,i){b.push([[h,i]]);c.call(this,h,i)};this.lineTo=function(h,j){var i=b[b.length-1];i.push([h,j])};this.stroke=function(){if(b.length===0){a.call(this);return}for(var p=0;pt){var q=e[m];if(g[1]){t+=g[1]}else{t+=q}if(t>r){g=[m,t-r];t=r}else{g=[(m+1)%e.length,0]}if(m%2===0){d.call(this,t,0)}else{c.call(this,t,0)}m=(m+1)%e.length}this.restore();l=h,u=s}}a.call(this);b=[]}};CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."};var DygraphOptions=(function(){var a=function(b){this.dygraph_=b;this.yAxes_=[];this.xAxis_={};this.series_={};this.global_=this.dygraph_.attrs_;this.user_=this.dygraph_.user_attrs_||{};this.highlightSeries_=this.get("highlightSeriesOpts")||{};this.reparseSeries()};a.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1};a.axisToIndex_=function(b){if(typeof(b)=="string"){if(a.AXIS_STRING_MAPPINGS_.hasOwnProperty(b)){return a.AXIS_STRING_MAPPINGS_[b]}throw"Unknown axis : "+b}if(typeof(b)=="number"){if(b===0||b===1){return b}throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(typeof(b)=="object"){throw"Using objects for axis specification is not supported inside the 'series' option."}if(b){throw"Unknown axis : "+b}return 0};a.prototype.reparseSeries=function(){var g=this.get("labels");if(!g){return}this.labels=g.slice(1);this.yAxes_=[{series:[],options:{}}];this.xAxis_={options:{}};this.series_={};var h=!this.user_.series;if(h){var c=0;for(var j=0;j1){Dygraph.update(this.yAxes_[1].options,f.y2||{})}Dygraph.update(this.xAxis_.options,f.x||{})};a.prototype.get=function(c){var b=this.getGlobalUser_(c);if(b!==null){return b}return this.getGlobalDefault_(c)};a.prototype.getGlobalUser_=function(b){if(this.user_.hasOwnProperty(b)){return this.user_[b]}return null};a.prototype.getGlobalDefault_=function(b){if(this.global_.hasOwnProperty(b)){return this.global_[b]}if(Dygraph.DEFAULT_ATTRS.hasOwnProperty(b)){return Dygraph.DEFAULT_ATTRS[b]}return null};a.prototype.getForAxis=function(d,e){var f;var i;if(typeof(e)=="number"){f=e;i=f===0?"y":"y2"}else{if(e=="y1"){e="y"}if(e=="y"){f=0}else{if(e=="y2"){f=1}else{if(e=="x"){f=-1}else{throw"Unknown axis "+e}}}i=e}var g=(f==-1)?this.xAxis_:this.yAxes_[f];if(g){var h=g.options;if(h.hasOwnProperty(d)){return h[d]}}var c=this.getGlobalUser_(d);if(c!==null){return c}var b=Dygraph.DEFAULT_ATTRS.axes[i];if(b.hasOwnProperty(d)){return b[d]}return this.getGlobalDefault_(d)};a.prototype.getForSeries=function(c,e){if(e===this.dygraph_.highlightSet_){if(this.highlightSeries_.hasOwnProperty(c)){return this.highlightSeries_[c]}}if(!this.series_.hasOwnProperty(e)){throw"Unknown series: "+e}var d=this.series_[e];var b=d.options;if(b.hasOwnProperty(c)){return b[c]}return this.getForAxis(c,d.yAxis)};a.prototype.numAxes=function(){return this.yAxes_.length};a.prototype.axisForSeries=function(b){return this.series_[b].yAxis};a.prototype.axisOptions=function(b){return this.yAxes_[b].options};a.prototype.seriesForAxis=function(b){return this.yAxes_[b].series};a.prototype.seriesNames=function(){return this.labels_};a.prototype.indexOfSeries=function(b){return this.series_[b].idx};return a})();"use strict";var DygraphLayout=function(a){this.dygraph_=a;this.datasets=[];this.setNames=[];this.annotations=[];this.yAxes_=null;this.points=null;this.xTicks_=null;this.yTicks_=null};DygraphLayout.prototype.attr_=function(a){return this.dygraph_.attr_(a)};DygraphLayout.prototype.addDataset=function(a,b){this.datasets.push(b);this.setNames.push(a)};DygraphLayout.prototype.getPlotArea=function(){return this.area_};DygraphLayout.prototype.computePlotArea=function(){var a={x:0,y:0};a.w=this.dygraph_.width_-a.x-this.attr_("rightGap");a.h=this.dygraph_.height_;var b={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(c){var d={x:a.x,y:a.y,w:c,h:a.h};a.x+=c;a.w-=c;return d},reserveSpaceRight:function(c){var d={x:a.x+a.w-c,y:a.y,w:c,h:a.h};a.w-=c;return d},reserveSpaceTop:function(c){var d={x:a.x,y:a.y,w:a.w,h:c};a.y+=c;a.h-=c;return d},reserveSpaceBottom:function(c){var d={x:a.x,y:a.y+a.h-c,w:a.w,h:c};a.h-=c;return d},chartRect:function(){return{x:a.x,y:a.y,w:a.w,h:a.h}}};this.dygraph_.cascadeEvents_("layout",b);this.area_=a};DygraphLayout.prototype.setAnnotations=function(d){this.annotations=[];var e=this.attr_("xValueParser")||function(a){return a};for(var c=0;c=0)&&(f<=1)){this.xticks.push([f,b])}}this.yticks=[];for(d=0;d=0)&&(f<=1)){this.yticks.push([d,f,b])}}}};DygraphLayout.prototype.evaluateWithError=function(){this.evaluate();if(!(this.attr_("errorBars")||this.attr_("customBars"))){return}var h=0;for(var a=0;a=0;e--){if(f.childNodes[e].className==g){f.removeChild(f.childNodes[e])}}var c=document.bgColor;var d=this.dygraph_.graphDiv;while(d!=document){var a=d.currentStyle.backgroundColor;if(a&&a!="transparent"){c=a;break}d=d.parentNode}function b(j){if(j.w===0||j.h===0){return}var i=document.createElement("div");i.className=g;i.style.backgroundColor=c;i.style.position="absolute";i.style.left=j.x+"px";i.style.top=j.y+"px";i.style.width=j.w+"px";i.style.height=j.h+"px";f.appendChild(i)}var h=this.area;b({x:0,y:0,w:h.x,h:this.height});b({x:h.x,y:0,w:this.width-h.x,h:h.y});b({x:h.x+h.w,y:0,w:this.width-h.x-h.w,h:this.height});b({x:h.x,y:h.y+h.h,w:this.width-h.x,h:this.height-h.h-h.y})};DygraphCanvasRenderer._getIteratorPredicate=function(a){return a?DygraphCanvasRenderer._predicateThatSkipsEmptyPoints:null};DygraphCanvasRenderer._predicateThatSkipsEmptyPoints=function(b,a){return b[a].yval!==null};DygraphCanvasRenderer._drawStyledLine=function(i,a,m,q,f,n,d){var h=i.dygraph;var c=h.getOption("stepPlot",i.setName);if(!Dygraph.isArrayLike(q)){q=null}var l=h.getOption("drawGapEdgePoints",i.setName);var o=i.points;var k=Dygraph.createIterator(o,0,o.length,DygraphCanvasRenderer._getIteratorPredicate(h.getOption("connectSeparatedPoints")));var j=q&&(q.length>=2);var p=i.drawingContext;p.save();if(j){p.installPattern(q)}var b=DygraphCanvasRenderer._drawSeries(i,k,m,d,f,l,c,a);DygraphCanvasRenderer._drawPointsOnLine(i,b,n,a,d);if(j){p.uninstallPattern()}p.restore()};DygraphCanvasRenderer._drawSeries=function(v,t,m,h,p,s,g,q){var b=null;var w=null;var k=null;var j;var o;var l=[];var f=true;var n=v.drawingContext;n.beginPath();n.strokeStyle=q;n.lineWidth=m;var c=t.array_;var u=t.end_;var a=t.predicate_;for(var r=t.start_;r=0;C--){if(!D.visibility()[C]){I.splice(C,1)}}var h=(function(){for(var e=0;e=0;u--){var n=I[u];if(!D.getOption("fillGraph",n)){continue}var p=D.getOption("stepPlot",n);var x=q[u];var f=D.axisPropertiesForSeries(n);var d=1+f.minyval*f.yscale;if(d<0){d=0}else{if(d>1){d=1}}d=E.h*d+E.y;var B=c[u];var A=Dygraph.createIterator(B,0,B.length,DygraphCanvasRenderer._getIteratorPredicate(D.getOption("connectSeparatedPoints")));var m=NaN;var l=[-1,-1];var t;var b=new RGBColorParser(x);var H="rgba("+b.r+","+b.g+","+b.b+","+y+")";w.fillStyle=H;w.beginPath();var j,G=true;while(A.hasNext){var v=A.next();if(!Dygraph.isOK(v.y)){m=NaN;continue}if(k){if(!G&&j==v.xval){continue}else{G=false;j=v.xval}a=s[v.canvasx];var o;if(a===undefined){o=d}else{if(r){o=a[0]}else{o=a}}t=[v.canvasy,o];if(p){if(l[0]===-1){s[v.canvasx]=[v.canvasy,d]}else{s[v.canvasx]=[v.canvasy,l[0]]}}else{s[v.canvasx]=v.canvasy}}else{t=[v.canvasy,d]}if(!isNaN(m)){w.moveTo(m,l[0]);if(p){w.lineTo(v.canvasx,l[0])}else{w.lineTo(v.canvasx,t[0])}if(r&&a){w.lineTo(v.canvasx,a[1])}else{w.lineTo(v.canvasx,t[1])}w.lineTo(m,l[1]);w.closePath()}l=t;m=v.canvasx}r=p;w.fill()}};"use strict";var Dygraph=function(d,c,b,a){if(a!==undefined){this.warn("Using deprecated four-argument dygraph constructor");this.__old_init__(d,c,b,a)}else{this.__init__(d,c,b)}};Dygraph.NAME="Dygraph";Dygraph.VERSION="1.2";Dygraph.__repr__=function(){return"["+this.NAME+" "+this.VERSION+"]"};Dygraph.toString=function(){return this.__repr__()};Dygraph.DEFAULT_ROLL_PERIOD=1;Dygraph.DEFAULT_WIDTH=480;Dygraph.DEFAULT_HEIGHT=320;Dygraph.ANIMATION_STEPS=12;Dygraph.ANIMATION_DURATION=200;Dygraph.KMB_LABELS=["K","M","B","T","Q"];Dygraph.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"];Dygraph.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"];Dygraph.numberValueFormatter=function(s,a,u,l){var e=a("sigFigs");if(e!==null){return Dygraph.floatFormat(s,e)}var c=a("digitsAfterDecimal");var o=a("maxNumberWidth");var b=a("labelsKMB");var r=a("labelsKMG2");var q;if(s!==0&&(Math.abs(s)>=Math.pow(10,o)||Math.abs(s)=0;h--,d/=f){if(p>=d){q=Dygraph.round_(s/d,c)+t[h];break}}if(r){var i=String(s.toExponential()).split("e-");if(i.length===2&&i[1]>=3&&i[1]<=24){if(i[1]%3>0){q=Dygraph.round_(i[0]/Dygraph.pow(10,(i[1]%3)),c)}else{q=Number(i[0]).toFixed(2)}q+=m[Math.floor(i[1]/3)-1]}}}return q};Dygraph.numberAxisLabelFormatter=function(a,d,c,b){return Dygraph.numberValueFormatter(a,c,b)};Dygraph.dateString_=function(e){var i=Dygraph.zeropad;var h=new Date(e);var f=""+h.getFullYear();var g=i(h.getMonth()+1);var a=i(h.getDate());var c="";var b=h.getHours()*3600+h.getMinutes()*60+h.getSeconds();if(b){c=" "+Dygraph.hmsString_(e)}return f+"/"+g+"/"+a+c};Dygraph.dateAxisFormatter=function(b,c){if(c>=Dygraph.DECADAL){return b.strftime("%Y")}else{if(c>=Dygraph.MONTHLY){return b.strftime("%b %y")}else{var a=b.getHours()*3600+b.getMinutes()*60+b.getSeconds()+b.getMilliseconds();if(a===0||c>=Dygraph.DAILY){return new Date(b.getTime()+3600*1000).strftime("%d%b")}else{return Dygraph.hmsString_(b.getTime())}}}};Dygraph.Plotters=DygraphCanvasRenderer._Plotters;Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:0.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsShowZeroValues:true,labelsKMB:false,labelsKMG2:false,showLabelsOnHighlight:true,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,xAxisLabelWidth:50,yAxisLabelWidth:50,rightGap:5,showRoller:false,xValueParser:Dygraph.dateParser,delimiter:",",sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false,fillGraph:false,fillAlpha:0.15,connectSeparatedPoints:false,stackedGraph:false,hideOverlayOnMouseOut:true,legend:"onmouseover",stepPlot:false,avoidMinZero:false,xRangePad:0,yRangePad:null,drawAxesAtZero:false,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:true,drawYAxis:true,axisLineColor:"black",axisLineWidth:0.3,gridLineWidth:0.3,axisLabelColor:"black",axisLabelFont:"Arial",axisLabelWidth:50,drawYGrid:true,drawXGrid:true,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:false,showRangeSelector:false,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",plotter:[Dygraph.Plotters.fillPlotter,Dygraph.Plotters.errorPlotter,Dygraph.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:60,axisLabelFormatter:Dygraph.dateAxisFormatter,valueFormatter:Dygraph.dateString_,ticker:null},y:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,ticker:null},y2:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,ticker:null}}};Dygraph.HORIZONTAL=1;Dygraph.VERTICAL=2;Dygraph.PLUGINS=[];Dygraph.addedAnnotationCSS=false;Dygraph.prototype.__old_init__=function(f,d,e,b){if(e!==null){var a=["Date"];for(var c=0;c=0;d--){var f=a[d][0];var h=a[d][1];h.call(f,g);if(g.propagationStopped){break}}}return g.defaultPrevented};Dygraph.prototype.isZoomed=function(a){if(a===null||a===undefined){return this.zoomed_x_||this.zoomed_y_}if(a==="x"){return this.zoomed_x_}if(a==="y"){return this.zoomed_y_}throw"axis parameter is ["+a+"] must be null, 'x' or 'y'."};Dygraph.prototype.toString=function(){var a=this.maindiv_;var b=(a&&a.id)?a.id:a;return"[Dygraph "+b+"]"};Dygraph.prototype.attr_=function(b,a){return a?this.attributes_.getForSeries(b,a):this.attributes_.get(b)};Dygraph.prototype.getOption=function(a,b){return this.attr_(a,b)};Dygraph.prototype.getOptionForAxis=function(a,b){return this.attributes_.getForAxis(a,b)};Dygraph.prototype.optionsViewForAxis_=function(b){var a=this;return function(c){var d=a.user_attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(typeof(a.user_attrs_[c])!="undefined"){return a.user_attrs_[c]}d=a.attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(b=="y"&&a.axes_[0].hasOwnProperty(c)){return a.axes_[0][c]}else{if(b=="y2"&&a.axes_[1].hasOwnProperty(c)){return a.axes_[1][c]}}return a.attr_(c)}};Dygraph.prototype.rollPeriod=function(){return this.rollPeriod_};Dygraph.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()};Dygraph.prototype.xAxisExtremes=function(){var d=this.attr_("xRangePad")/this.plotter_.area.w;if(this.numRows()===0){return[0-d,1+d]}var c=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(d){var a=b-c;c-=a*d;b+=a*d}return[c,b]};Dygraph.prototype.yAxisRange=function(a){if(typeof(a)=="undefined"){a=0}if(a<0||a>=this.axes_.length){return null}var b=this.axes_[a];return[b.computedValueRange[0],b.computedValueRange[1]]};Dygraph.prototype.yAxisRanges=function(){var a=[];for(var b=0;bthis.rawData_.length){return null}if(a<0||a>this.rawData_[b].length){return null}return this.rawData_[b][a]};Dygraph.prototype.createInterface_=function(){var a=this.maindiv_;this.graphDiv=document.createElement("div");this.graphDiv.style.width=this.width_+"px";this.graphDiv.style.height=this.height_+"px";this.graphDiv.style.textAlign="left";a.appendChild(this.graphDiv);this.canvas_=Dygraph.createCanvas();this.canvas_.style.position="absolute";this.canvas_.width=this.width_;this.canvas_.height=this.height_;this.canvas_.style.width=this.width_+"px";this.canvas_.style.height=this.height_+"px";this.canvas_ctx_=Dygraph.getContext(this.canvas_);this.hidden_=this.createPlotKitCanvas_(this.canvas_);this.hidden_ctx_=Dygraph.getContext(this.hidden_);this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_=this.createMouseEventElement_();this.layout_=new DygraphLayout(this);var b=this;this.mouseMoveHandler_=function(c){b.mouseMove_(c)};this.mouseOutHandler_=function(f){var d=f.target||f.fromElement;var c=f.relatedTarget||f.toElement;if(Dygraph.isElementContainedBy(d,b.graphDiv)&&!Dygraph.isElementContainedBy(c,b.graphDiv)){b.mouseOut_(f)}};this.addEvent(window,"mouseout",this.mouseOutHandler_);this.addEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);if(!this.resizeHandler_){this.resizeHandler_=function(c){b.resize()};this.addEvent(window,"resize",this.resizeHandler_)}};Dygraph.prototype.destroy=function(){var b=function(e){while(e.hasChildNodes()){b(e.firstChild);e.removeChild(e.firstChild)}};if(this.registeredEvents_){for(var a=0;a=0;--b){var m=this.layout_.points[b];for(var g=0;g=l.length){continue}var m=l[e];if(!Dygraph.isValidPoint(m)){continue}var j=m.canvasy;if(i>m.canvasx&&e+10){var a=(i-m.canvasx)/o;j+=a*(k.canvasy-m.canvasy)}}}else{if(i0){var n=l[e-1];if(Dygraph.isValidPoint(n)){var o=m.canvasx-n.canvasx;if(o>0){var a=(m.canvasx-i)/o;j+=a*(n.canvasy-m.canvasy)}}}}if(d===0||j=0){var j=0;var k=this.attr_("labels");for(h=1;hj){j=c}}var l=this.previousVerticalX_;n.clearRect(l-j-1,0,2*j+2,this.height_)}}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_){Dygraph.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_)}if(this.selPoints_.length>0){var b=this.selPoints_[0].canvasx;n.save();for(h=0;h=0){if(d!=this.lastRow_){c=true}this.lastRow_=d;for(var b=0;b=0){c=true}this.lastRow_=-1}if(this.selPoints_.length){this.lastx_=this.selPoints_[0].xval}else{this.lastx_=-1}if(g!==undefined){if(this.highlightSet_!==g){c=true}this.highlightSet_=g}if(f!==undefined){this.lockedSet_=f}if(c){this.updateSelection_(undefined)}return c};Dygraph.prototype.mouseOut_=function(a){if(this.attr_("unhighlightCallback")){this.attr_("unhighlightCallback")(a)}if(this.attr_("hideOverlayOnMouseOut")&&!this.lockedSet_){this.clearSelection()}};Dygraph.prototype.clearSelection=function(){this.cascadeEvents_("deselect",{});this.lockedSet_=false;if(this.fadeLevel){this.animateSelection_(-1);return}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel=0;this.selPoints_=[];this.lastx_=-1;this.lastRow_=-1;this.highlightSet_=null};Dygraph.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1){return -1}for(var c=0;cg){a=g}if(ef){f=e}if(h===null||af){f=g}if(h===null||g=1;t--){if(!this.visibility()[t-1]){continue}var l=[];for(s=0;s=z&&e===null){e=q}if(l[q][0]<=g){y=q}}if(e===null){e=0}if(e>0){e--}if(y===null){y=l.length-1}if(yn[1]){n[1]=c[h]}if(c[h]=0;--q){if(!f[q]){continue}for(s=0;s=0;t--){if(!f[t]){continue}f[t][s][1]=NaN}}}break}}return[f,a,r]};Dygraph.prototype.drawGraph_=function(){var a=new Date();var e=this.is_initial_draw_;this.is_initial_draw_=false;this.layout_.removeAllDatasets();this.setColors_();this.attrs_.pointSize=0.5*this.attr_("highlightCircleSize");var j=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_);var d=j[0];var k=j[1];this.boundaryIds_=j[2];this.setIndexByName_={};var h=this.attr_("labels");if(h.length>0){this.setIndexByName_[h[0]]=0}var f=0;for(var g=1;g0){C=0}if(z<0){z=0}}if(C==Infinity){C=0}if(z==-Infinity){z=1}w=z-C;if(w===0){if(z!==0){w=Math.abs(z)}else{z=1;w=1}}var h,G;if(B){if(b){h=z+A*w;G=C}else{var D=Math.exp(Math.log(w)*A);h=z*D;G=C/D}}else{h=z+A*w;G=C-A*w;if(b&&!this.attr_("avoidMinZero")){if(G<0&&C>=0){G=0}if(h>0&&z<=0){h=0}}}c.extremeRange=[G,h]}if(c.valueWindow){c.computedValueRange=[c.valueWindow[0],c.valueWindow[1]]}else{if(c.valueRange){var e=g(c.valueRange[0])?c.extremeRange[0]:c.valueRange[0];var d=g(c.valueRange[1])?c.extremeRange[1]:c.valueRange[1];if(!b){if(c.logscale){var D=Math.exp(Math.log(w)*A);e*=D;d/=D}else{w=d-e;e-=w*A;d+=w*A}}c.computedValueRange=[e,d]}else{c.computedValueRange=c.extremeRange}}var q=this.optionsViewForAxis_("y"+(x?"2":""));var E=q("ticker");if(x===0||c.independentTicks){c.ticks=E(c.computedValueRange[0],c.computedValueRange[1],this.height_,q,this)}else{var o=this.axes_[0];var l=o.ticks;var m=o.computedValueRange[1]-o.computedValueRange[0];var H=c.computedValueRange[1]-c.computedValueRange[0];var f=[];for(var u=0;u=0){k-=l[z-d][1][0];h-=l[z-d][1][1]}var C=l[z][0];var w=h?k/h:0;if(this.attr_("errorBars")){if(this.attr_("wilsonInterval")){if(h){var s=w<0?0:w,u=h;var B=t*Math.sqrt(s*(1-s)/u+t*t/(4*u*u));var a=1+t*t/h;G=(s+t*t/(2*h)-B)/a;o=(s+t*t/(2*h)+B)/a;b[z]=[C,[s*e,(s-G)*e,(o-s)*e]]}else{b[z]=[C,[0,0,0]]}}else{A=h?t*Math.sqrt(w*(1-w)/h):1;b[z]=[C,[e*w,e*A,e*A]]}}else{b[z]=[C,e*w]}}}else{if(this.attr_("customBars")){G=0;var D=0;o=0;var g=0;for(z=0;z=0){var r=l[z-d];if(r[1][1]!==null&&!isNaN(r[1][1])){G-=r[1][0];D-=r[1][1];o-=r[1][2];g-=1}}if(g){b[z]=[l[z][0],[1*D/g,1*(D-G)/g,1*(o-D)/g]]}else{b[z]=[l[z][0],[null,null,null]]}}}else{if(!this.attr_("errorBars")){if(d==1){return l}for(z=0;z0&&(b[c-1]!="e"&&b[c-1]!="E"))||b.indexOf("/")>=0||isNaN(parseFloat(b))){a=true}else{if(b.length==8&&b>"19700101"&&b<"20371231"){a=true}}this.setXAxisOptions_(a)};Dygraph.prototype.setXAxisOptions_=function(a){if(a){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{this.attrs_.xValueParser=function(b){return parseFloat(b)};this.attrs_.axes.x.valueFormatter=function(b){return b};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}};Dygraph.prototype.parseFloat_=function(a,c,b){var e=parseFloat(a);if(!isNaN(e)){return e}if(/^ *$/.test(a)){return null}if(/^ *nan *$/i.test(a)){return NaN}var d="Unable to parse '"+a+"' as a number";if(b!==null&&c!==null){d+=" on line "+(1+c)+" ('"+b+"') of CSV."}this.error(d);return null};Dygraph.prototype.parseCSV_=function(t){var r=[];var s=Dygraph.detectLineDelimiter(t);var a=t.split(s||"\n");var g,k;var p=this.attr_("delimiter");if(a[0].indexOf(p)==-1&&a[0].indexOf("\t")>=0){p="\t"}var b=0;if(!("labels" in this.user_attrs_)){b=1;this.attrs_.labels=a[0].split(p);this.attributes_.reparseSeries()}var o=0;var m;var q=false;var c=this.attr_("labels").length;var f=false;for(var l=b;l0&&h[0]0){j=String.fromCharCode(65+(i-1)%26)+j.toLowerCase();i=Math.floor((i-1)/26)}return j};var h=w.getNumberOfColumns();var g=w.getNumberOfRows();var f=w.getColumnType(0);if(f=="date"||f=="datetime"){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{if(f=="number"){this.attrs_.xValueParser=function(i){return parseFloat(i)};this.attrs_.axes.x.valueFormatter=function(i){return i};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}else{this.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+f+"')");return null}}var m=[];var t={};var s=false;var q,o;for(q=1;q0&&e[0]0){this.setAnnotations(a,true)}this.attributes_.reparseSeries()};Dygraph.prototype.start_=function(){var d=this.file_;if(typeof d=="function"){d=d()}if(Dygraph.isArrayLike(d)){this.rawData_=this.parseArray_(d);this.predraw_()}else{if(typeof d=="object"&&typeof d.getColumnRange=="function"){this.parseDataTable_(d);this.predraw_()}else{if(typeof d=="string"){var c=Dygraph.detectLineDelimiter(d);if(c){this.loadedEvent_(d)}else{var b=new XMLHttpRequest();var a=this;b.onreadystatechange=function(){if(b.readyState==4){if(b.status===200||b.status===0){a.loadedEvent_(b.responseText)}}};b.open("GET",d,true);b.send(null)}}else{this.error("Unknown data format: "+(typeof d))}}}};Dygraph.prototype.updateOptions=function(e,b){if(typeof(b)=="undefined"){b=false}var d=e.file;var c=Dygraph.mapLegacyOptions_(e);if("rollPeriod" in c){this.rollPeriod_=c.rollPeriod}if("dateWindow" in c){this.dateWindow_=c.dateWindow;if(!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_x_=(c.dateWindow!==null)}}if("valueRange" in c&&!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_y_=(c.valueRange!==null)}var a=Dygraph.isPixelChangingOptionList(this.attr_("labels"),c);Dygraph.updateDeep(this.user_attrs_,c);this.attributes_.reparseSeries();if(d){this.file_=d;if(!b){this.start_()}}else{if(!b){if(a){this.predraw_()}else{this.renderGraph_(false)}}}};Dygraph.mapLegacyOptions_=function(c){var a={};for(var b in c){if(b=="file"){continue}if(c.hasOwnProperty(b)){a[b]=c[b]}}var e=function(g,f,h){if(!a.axes){a.axes={}}if(!a.axes[g]){a.axes[g]={}}a.axes[g][f]=h};var d=function(f,g,h){if(typeof(c[f])!="undefined"){Dygraph.warn("Option "+f+" is deprecated. Use the "+h+" option for the "+g+" axis instead. (e.g. { axes : { "+g+" : { "+h+" : ... } } } (see http://dygraphs.com/per-axis.html for more information.");e(g,h,c[f]);delete a[f]}};d("xValueFormatter","x","valueFormatter");d("pixelsPerXLabel","x","pixelsPerLabel");d("xAxisLabelFormatter","x","axisLabelFormatter");d("xTicker","x","ticker");d("yValueFormatter","y","valueFormatter");d("pixelsPerYLabel","y","pixelsPerLabel");d("yAxisLabelFormatter","y","axisLabelFormatter");d("yTicker","y","ticker");return a};Dygraph.prototype.resize=function(d,b){if(this.resize_lock){return}this.resize_lock=true;if((d===null)!=(b===null)){this.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero.");d=b=null}var a=this.width_;var c=this.height_;if(d){this.maindiv_.style.width=d+"px";this.maindiv_.style.height=b+"px";this.width_=d;this.height_=b}else{this.width_=this.maindiv_.clientWidth;this.height_=this.maindiv_.clientHeight}if(a!=this.width_||c!=this.height_){this.maindiv_.innerHTML="";this.roller_=null;this.attrs_.labelsDiv=null;this.createInterface_();if(this.annotations_.length){this.layout_.setAnnotations(this.annotations_)}this.createDragInterface_();this.predraw_()}this.resize_lock=false};Dygraph.prototype.adjustRoll=function(a){this.rollPeriod_=a;this.predraw_()};Dygraph.prototype.visibility=function(){if(!this.attr_("visibility")){this.attrs_.visibility=[]}while(this.attr_("visibility").length=a.length){this.warn("invalid series number in setVisibility: "+b)}else{a[b]=c;this.predraw_()}};Dygraph.prototype.size=function(){return{width:this.width_,height:this.height_}};Dygraph.prototype.setAnnotations=function(b,a){Dygraph.addAnnotationRule();this.annotations_=b;if(!this.layout_){this.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a drawCallback. See dygraphs.com/tests/annotation.html");return}this.layout_.setAnnotations(this.annotations_);if(!a){this.predraw_()}};Dygraph.prototype.annotations=function(){return this.annotations_};Dygraph.prototype.getLabels=function(){var a=this.attr_("labels");return a?a.slice():null};Dygraph.prototype.indexFromSetName=function(a){return this.setIndexByName_[a]};Dygraph.prototype.datasetIndexFromSetName_=function(a){return this.datasetIndex_[this.indexFromSetName(a)]};Dygraph.addAnnotationRule=function(){if(Dygraph.addedAnnotationCSS){return}var f="border: 1px solid black; background-color: white; text-align: center;";var e=document.createElement("style");e.type="text/css";document.getElementsByTagName("head")[0].appendChild(e);for(var b=0;bb){return -1}if(i===null||i===undefined){i=0}var h=function(j){return j>=0&&ja){if(i>0){f=g-1;if(h(f)&&d[f]a){return g}}return Dygraph.binarySearch(a,d,i,g+1,b)}}}return -1};Dygraph.dateParser=function(a){var b;var c;if(a.search("-")==-1||a.search("T")!=-1||a.search("Z")!=-1){c=Dygraph.dateStrToMillis(a);if(c&&!isNaN(c)){return c}}if(a.search("-")!=-1){b=a.replace("-","/","g");while(b.search("-")!=-1){b=b.replace("-","/")}c=Dygraph.dateStrToMillis(b)}else{if(a.length==8){b=a.substr(0,4)+"/"+a.substr(4,2)+"/"+a.substr(6,2);c=Dygraph.dateStrToMillis(b)}else{c=Dygraph.dateStrToMillis(a)}}if(!c||isNaN(c)){Dygraph.error("Couldn't parse "+a+" as a date")}return c};Dygraph.dateStrToMillis=function(a){return new Date(a).getTime()};Dygraph.update=function(b,c){if(typeof(c)!="undefined"&&c!==null){for(var a in c){if(c.hasOwnProperty(a)){b[a]=c[a]}}}return b};Dygraph.updateDeep=function(b,d){function c(e){return(typeof Node==="object"?e instanceof Node:typeof e==="object"&&typeof e.nodeType==="number"&&typeof e.nodeName==="string")}if(typeof(d)!="undefined"&&d!==null){for(var a in d){if(d.hasOwnProperty(a)){if(d[a]===null){b[a]=null}else{if(Dygraph.isArrayLike(d[a])){b[a]=d[a].slice()}else{if(c(d[a])){b[a]=d[a]}else{if(typeof(d[a])=="object"){if(typeof(b[a])!="object"||b[a]===null){b[a]={}}Dygraph.updateDeep(b[a],d[a])}else{b[a]=d[a]}}}}}}}return b};Dygraph.isArrayLike=function(b){var a=typeof(b);if((a!="object"&&!(a=="function"&&typeof(b.item)=="function"))||b===null||typeof(b.length)!="number"||b.nodeType===3){return false}return true};Dygraph.isDateLike=function(a){if(typeof(a)!="object"||a===null||typeof(a.getTime)!="function"){return false}return true};Dygraph.clone=function(c){var b=[];for(var a=0;a=g){return}Dygraph.requestAnimFrame.call(window,function(){var l=new Date().getTime();var j=l-b;d=i;i=Math.floor(j/f);var k=i-d;var m=(i+k)>e;if(m||(i>=e)){h(e);a()}else{if(k!==0){h(i)}c()}})})()};Dygraph.isPixelChangingOptionList=function(h,e){var d={annotationClickHandler:true,annotationDblClickHandler:true,annotationMouseOutHandler:true,annotationMouseOverHandler:true,axisLabelColor:true,axisLineColor:true,axisLineWidth:true,clickCallback:true,digitsAfterDecimal:true,drawCallback:true,drawHighlightPointCallback:true,drawPoints:true,drawPointCallback:true,drawXGrid:true,drawYGrid:true,fillAlpha:true,gridLineColor:true,gridLineWidth:true,hideOverlayOnMouseOut:true,highlightCallback:true,highlightCircleSize:true,interactionModel:true,isZoomedIgnoreProgrammaticZoom:true,labelsDiv:true,labelsDivStyles:true,labelsDivWidth:true,labelsKMB:true,labelsKMG2:true,labelsSeparateLines:true,labelsShowZeroValues:true,legend:true,maxNumberWidth:true,panEdgeFraction:true,pixelsPerYLabel:true,pointClickCallback:true,pointSize:true,rangeSelectorPlotFillColor:true,rangeSelectorPlotStrokeColor:true,showLabelsOnHighlight:true,showRoller:true,sigFigs:true,strokeWidth:true,underlayCallback:true,unhighlightCallback:true,xAxisLabelFormatter:true,xTicker:true,xValueFormatter:true,yAxisLabelFormatter:true,yValueFormatter:true,zoomCallback:true};var a=false;var b={};if(h){for(var f=1;fc.boundedDates[1]){h=h-(a-c.boundedDates[1]);a=h+c.dateRange}}k.dateWindow_=[h,a];if(c.is2DPan){var d=c.dragEndY-c.dragStartY;for(var j=0;j=10&&e.dragDirection==Dygraph.HORIZONTAL){var f=Math.min(e.dragStartX,e.dragEndX),k=Math.max(e.dragStartX,e.dragEndX);f=Math.max(f,b.x);k=Math.min(k,b.x+b.w);if(f=10&&e.dragDirection==Dygraph.VERTICAL){var j=Math.min(e.dragStartY,e.dragEndY),a=Math.max(e.dragStartY,e.dragEndY);j=Math.max(j,b.y);a=Math.min(a,b.y+b.h);if(j1){d.startTimeForDoubleTapMs=null}var h=[];for(var c=0;c=2){d.initialPinchCenter={pageX:0.5*(h[0].pageX+h[1].pageX),pageY:0.5*(h[0].pageY+h[1].pageY),dataX:0.5*(h[0].dataX+h[1].dataX),dataY:0.5*(h[0].dataY+h[1].dataY)};var a=180/Math.PI*Math.atan2(d.initialPinchCenter.pageY-h[0].pageY,h[0].pageX-d.initialPinchCenter.pageX);a=Math.abs(a);if(a>90){a=90-a}d.touchDirections={x:(a<(90-45/2)),y:(a>45/2)}}}d.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}};Dygraph.Interaction.moveTouch=function(n,q,d){d.startTimeForDoubleTapMs=null;var p,l=[];for(p=0;p=2){var e=(a[1].pageX-j.pageX);w=(l[1].pageX-h.pageX)/e;var v=(a[1].pageY-j.pageY);c=(l[1].pageY-h.pageY)/v}}w=Math.min(8,Math.max(0.125,w));c=Math.min(8,Math.max(0.125,c));var u=false;if(d.touchDirections.x){q.dateWindow_=[j.dataX-m.dataX+(d.initialRange.x[0]-j.dataX)/w,j.dataX-m.dataX+(d.initialRange.x[1]-j.dataX)/w];u=true}if(d.touchDirections.y){for(p=0;p<1;p++){var b=q.axes_[p];var s=q.attributes_.getForAxis("logscale",p);if(s){}else{b.valueWindow=[j.dataY-m.dataY+(d.initialRange.y[0]-j.dataY)/c,j.dataY-m.dataY+(d.initialRange.y[1]-j.dataY)/c];u=true}}}q.drawGraph_(false);if(u&&l.length>1&&q.attr_("zoomCallback")){var r=q.xAxisRange();q.attr_("zoomCallback")(r[0],r[1],q.yAxisRanges())}};Dygraph.Interaction.endTouch=function(e,d,c){if(e.touches.length!==0){Dygraph.Interaction.startTouch(e,d,c)}else{if(e.changedTouches.length==1){var a=new Date().getTime();var b=e.changedTouches[0];if(c.startTimeForDoubleTapMs&&a-c.startTimeForDoubleTapMs<500&&c.doubleTapX&&Math.abs(c.doubleTapX-b.screenX)<50&&c.doubleTapY&&Math.abs(c.doubleTapY-b.screenY)<50){d.resetZoom()}else{c.startTimeForDoubleTapMs=a;c.doubleTapX=b.screenX;c.doubleTapY=b.screenY}}}};Dygraph.Interaction.defaultModel={mousedown:function(c,b,a){if(c.button&&c.button==2){return}a.initializeMouseDown(c,b,a);if(c.altKey||c.shiftKey){Dygraph.startPan(c,b,a)}else{Dygraph.startZoom(c,b,a)}},mousemove:function(c,b,a){if(a.isZooming){Dygraph.moveZoom(c,b,a)}else{if(a.isPanning){Dygraph.movePan(c,b,a)}}},mouseup:function(c,b,a){if(a.isZooming){Dygraph.endZoom(c,b,a)}else{if(a.isPanning){Dygraph.endPan(c,b,a)}}},touchstart:function(c,b,a){Dygraph.Interaction.startTouch(c,b,a)},touchmove:function(c,b,a){Dygraph.Interaction.moveTouch(c,b,a)},touchend:function(c,b,a){Dygraph.Interaction.endTouch(c,b,a)},mouseout:function(c,b,a){if(a.isZooming){a.dragEndX=null;a.dragEndY=null;b.clearZoomRect_()}},dblclick:function(c,b,a){if(a.cancelNextDblclick){a.cancelNextDblclick=false;return}if(c.altKey||c.shiftKey){return}b.resetZoom()}};Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel;Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel;Dygraph.endZoom=Dygraph.Interaction.endZoom;Dygraph.moveZoom=Dygraph.Interaction.moveZoom;Dygraph.startZoom=Dygraph.Interaction.startZoom;Dygraph.endPan=Dygraph.Interaction.endPan;Dygraph.movePan=Dygraph.Interaction.movePan;Dygraph.startPan=Dygraph.Interaction.startPan;Dygraph.Interaction.nonInteractiveModel_={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a)},mouseup:function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}}};Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a);Dygraph.startPan(c,b,a)},mousemove:function(c,b,a){if(a.isPanning){Dygraph.movePan(c,b,a)}},mouseup:function(c,b,a){if(a.isPanning){Dygraph.endPan(c,b,a)}}};"use strict";Dygraph.TickList=undefined;Dygraph.Ticker=undefined;Dygraph.numericLinearTicks=function(d,c,i,g,f,h){var e=function(a){if(a==="logscale"){return false}return g(a)};return Dygraph.numericTicks(d,c,i,e,f,h)};Dygraph.numericTicks=function(F,E,u,p,d,q){var z=(p("pixelsPerLabel"));var G=[];var C,A,t,y;if(q){for(C=0;C=y/4){for(var r=H;r>=l;r--){var m=Dygraph.PREFERRED_LOG_TICK_VALUES[r];var k=Math.log(m/F)/Math.log(E/F)*u;var D={v:m};if(s===null){s={tickValue:m,pixel_coord:k}}else{if(Math.abs(k-s.pixel_coord)>=z){s={tickValue:m,pixel_coord:k}}else{D.label=""}}G.push(D)}G.reverse()}}if(G.length===0){var g=p("labelsKMG2");var n,h;if(g){n=[1,2,4,8,16,32,64,128,256];h=16}else{n=[1,2,5,10,20,50,100];h=10}var w=Math.ceil(u/z);var o=Math.abs(E-F)/w;var v=Math.floor(Math.log(o)/Math.log(h));var f=Math.pow(h,v);var I,x,c,e;for(A=0;Az){break}}if(x>c){I*=-1}for(C=0;C=0){return Dygraph.getDateAxis(e,c,d,g,f)}else{return[]}};Dygraph.SECONDLY=0;Dygraph.TWO_SECONDLY=1;Dygraph.FIVE_SECONDLY=2;Dygraph.TEN_SECONDLY=3;Dygraph.THIRTY_SECONDLY=4;Dygraph.MINUTELY=5;Dygraph.TWO_MINUTELY=6;Dygraph.FIVE_MINUTELY=7;Dygraph.TEN_MINUTELY=8;Dygraph.THIRTY_MINUTELY=9;Dygraph.HOURLY=10;Dygraph.TWO_HOURLY=11;Dygraph.SIX_HOURLY=12;Dygraph.DAILY=13;Dygraph.WEEKLY=14;Dygraph.MONTHLY=15;Dygraph.QUARTERLY=16;Dygraph.BIANNUAL=17;Dygraph.ANNUAL=18;Dygraph.DECADAL=19;Dygraph.CENTENNIAL=20;Dygraph.NUM_GRANULARITIES=21;Dygraph.SHORT_SPACINGS=[];Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]=1000*1;Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]=1000*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]=1000*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]=1000*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY]=1000*30;Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]=1000*60;Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]=1000*60*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]=1000*60*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]=1000*60*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY]=1000*60*30;Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]=1000*3600;Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]=1000*3600*2;Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]=1000*3600*6;Dygraph.SHORT_SPACINGS[Dygraph.DAILY]=1000*86400;Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]=1000*604800;Dygraph.PREFERRED_LOG_TICK_VALUES=function(){var c=[];for(var b=-39;b<=39;b++){var a=Math.pow(10,b);for(var d=1;d<=9;d++){var e=a*d;c.push(e)}}return c}();Dygraph.pickDateTickGranularity=function(d,c,j,h){var g=(h("pixelsPerLabel"));for(var f=0;f=g){return f}}return -1};Dygraph.numDateTicks=function(e,b,g){if(g=Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]);for(m=p;m<=l;m+=c){var A=new Date(m);if(e&&A.getTimezoneOffset()!=B){var k=A.getTimezoneOffset()-B;m+=k*60*1000;A=new Date(m);B=A.getTimezoneOffset();if(new Date(m+c).getTimezoneOffset()!=B){m+=c;A=new Date(m);B=A.getTimezoneOffset()}}C.push({v:m,label:w(A,a,n,z)})}}else{var f;var r=1;if(a==Dygraph.MONTHLY){f=[0,1,2,3,4,5,6,7,8,9,10,11]}else{if(a==Dygraph.QUARTERLY){f=[0,3,6,9]}else{if(a==Dygraph.BIANNUAL){f=[0,6]}else{if(a==Dygraph.ANNUAL){f=[0]}else{if(a==Dygraph.DECADAL){f=[0];r=10}else{if(a==Dygraph.CENTENNIAL){f=[0];r=100}else{Dygraph.warn("Span of dates is too long")}}}}}}var v=new Date(p).getFullYear();var q=new Date(l).getFullYear();var b=Dygraph.zeropad;for(var u=v;u<=q;u++){if(u%r!==0){continue}for(var s=0;sl){continue}C.push({v:m,label:w(new Date(m),a,n,z)})}}}return C};if(Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes["x"]&&Dygraph.DEFAULT_ATTRS.axes["y"]&&Dygraph.DEFAULT_ATTRS.axes["y2"]){Dygraph.DEFAULT_ATTRS.axes["x"]["ticker"]=Dygraph.dateTicker;Dygraph.DEFAULT_ATTRS.axes["y"]["ticker"]=Dygraph.numericTicks;Dygraph.DEFAULT_ATTRS.axes["y2"]["ticker"]=Dygraph.numericTicks}Dygraph.Plugins={};Dygraph.Plugins.Annotations=(function(){var a=function(){this.annotations_=[]};a.prototype.toString=function(){return"Annotations Plugin"};a.prototype.activate=function(b){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}};a.prototype.detachLabels=function(){for(var c=0;cu.x+u.w||l.canvasyu.y+u.h){continue}var w=l.annotation;var n=6;if(w.hasOwnProperty("tickHeight")){n=w.tickHeight}var j=document.createElement("div");for(var A in x){if(x.hasOwnProperty(A)){j.style[A]=x[A]}}if(!w.hasOwnProperty("icon")){j.className="dygraphDefaultAnnotation"}if(w.hasOwnProperty("cssClass")){j.className+=" "+w.cssClass}var m=w.hasOwnProperty("width")?w.width:16;var k=w.hasOwnProperty("height")?w.height:16;if(w.hasOwnProperty("icon")){var z=document.createElement("img");z.src=w.icon;z.width=m;z.height=k;j.appendChild(z)}else{if(l.annotation.hasOwnProperty("shortText")){j.appendChild(document.createTextNode(l.annotation.shortText))}}var c=l.canvasx-m/2;j.style.left=c+"px";var f=0;if(w.attachAtBottom){var d=(u.y+u.h-k-n);if(q[c]){d-=q[c]}else{q[c]=0}q[c]+=(n+k);f=d}else{f=l.canvasy-k-n}j.style.top=f+"px";j.style.width=m+"px";j.style.height=k+"px";j.title=l.annotation.text;j.style.color=t.colorsMap_[l.name];j.style.borderColor=t.colorsMap_[l.name];w.div=j;t.addEvent(j,"click",b("clickHandler","annotationClickHandler",l,this));t.addEvent(j,"mouseover",b("mouseOverHandler","annotationMouseOverHandler",l,this));t.addEvent(j,"mouseout",b("mouseOutHandler","annotationMouseOutHandler",l,this));t.addEvent(j,"dblclick",b("dblClickHandler","annotationDblClickHandler",l,this));h.appendChild(j);this.annotations_.push(j);var o=v.drawingContext;o.save();o.strokeStyle=t.colorsMap_[l.name];o.beginPath();if(!w.attachAtBottom){o.moveTo(l.canvasx,l.canvasy);o.lineTo(l.canvasx,l.canvasy-2-n)}else{var d=f+k;o.moveTo(l.canvasx,d);o.lineTo(l.canvasx,d+n)}o.closePath();o.stroke();o.restore()}};a.prototype.destroy=function(){this.detachLabels()};return a})();Dygraph.Plugins.Axes=(function(){var a=function(){this.xlabels_=[];this.ylabels_=[]};a.prototype.toString=function(){return"Axes Plugin"};a.prototype.activate=function(b){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}};a.prototype.layout=function(f){var d=f.dygraph;if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceLeft(b)}if(d.getOption("drawXAxis")){var c;if(d.getOption("xAxisHeight")){c=d.getOption("xAxisHeight")}else{c=d.getOptionForAxis("axisLabelFontSize","x")+2*d.getOption("axisTickSize")}f.reserveSpaceBottom(c)}if(d.numAxes()==2){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceRight(b)}else{if(d.numAxes()>2){d.error("Only two y-axes are supported at this time. (Trying to use "+d.numAxes()+")")}}};a.prototype.detachLabels=function(){function b(d){for(var c=0;c0){var h=F.numAxes();for(D=0;Dd){s.style.bottom="0px"}else{s.style.top=z+"px"}if(E[0]===0){s.style.left=(G.x-F.getOption("yAxisLabelWidth")-F.getOption("axisTickSize"))+"px";s.style.textAlign="right"}else{if(E[0]==1){s.style.left=(G.x+G.w+F.getOption("axisTickSize"))+"px";s.style.textAlign="left"}}s.style.width=F.getOption("yAxisLabelWidth")+"px";v.appendChild(s);this.ylabels_.push(s)}var n=this.ylabels_[0];var k=F.getOptionForAxis("axisLabelFontSize","y");var q=parseInt(n.style.top,10)+k;if(q>d-k){n.style.top=(parseInt(n.style.top,10)-k/2)+"px"}}var c;if(F.getOption("drawAxesAtZero")){var w=F.toPercentXCoord(0);if(w>1||w<0||isNaN(w)){w=0}c=B(G.x+w*G.w)}else{c=B(G.x)}j.strokeStyle=F.getOptionForAxis("axisLineColor","y");j.lineWidth=F.getOptionForAxis("axisLineWidth","y");j.beginPath();j.moveTo(c,A(G.y));j.lineTo(c,A(G.y+G.h));j.closePath();j.stroke();if(F.numAxes()==2){j.strokeStyle=F.getOptionForAxis("axisLineColor","y2");j.lineWidth=F.getOptionForAxis("axisLineWidth","y2");j.beginPath();j.moveTo(A(G.x+G.w),A(G.y));j.lineTo(A(G.x+G.w),A(G.y+G.h));j.closePath();j.stroke()}}if(F.getOption("drawXAxis")){if(I.xticks){for(D=0;DJ){l=J-F.getOption("xAxisLabelWidth");s.style.textAlign="right"}if(l<0){l=0;s.style.textAlign="left"}s.style.left=l+"px";s.style.width=F.getOption("xAxisLabelWidth")+"px";v.appendChild(s);this.xlabels_.push(s)}}j.strokeStyle=F.getOptionForAxis("axisLineColor","x");j.lineWidth=F.getOptionForAxis("axisLineWidth","x");j.beginPath();var b;if(F.getOption("drawAxesAtZero")){var w=F.toPercentYCoord(0,0);if(w>1||w<0){w=1}b=A(G.y+w*G.h)}else{b=A(G.y+G.h)}j.moveTo(B(G.x),b);j.lineTo(B(G.x+G.w),b);j.closePath();j.stroke()}j.restore()};return a})();Dygraph.Plugins.ChartLabels=(function(){var c=function(){this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};c.prototype.toString=function(){return"ChartLabels Plugin"};c.prototype.activate=function(d){return{layout:this.layout,didDrawChart:this.didDrawChart}};var b=function(d){var e=document.createElement("div");e.style.position="absolute";e.style.left=d.x+"px";e.style.top=d.y+"px";e.style.width=d.w+"px";e.style.height=d.h+"px";return e};c.prototype.detachLabels_=function(){var e=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_];for(var d=0;d":" ")}m=w.getOption("strokePattern",z[u]);s=d(m,q.color,f);r+=""+s+" "+z[u]+""}return r}var A=w.optionsViewForAxis_("x");var o=A("valueFormatter");r=o(p,A,z[0],w);if(r!==""){r+=":"}var v=[];var j=w.numAxes();for(u=0;u"}var q=w.getPropertiesForSeries(t.name);var n=v[q.axis-1];var y=n("valueFormatter");var e=y(t.yval,n,t.name,w);var h=(t.name==B)?" class='highlight'":"";r+=" "+t.name+": "+e+""}return r};d=function(s,h,r){var e=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(e){return"—"}if(!s||s.length<=1){return'

'}var l,k,f,o;var g=0,q=0;var p=[];var n;for(l=0;l<=s.length;l++){g+=s[l%s.length]}n=Math.floor(r/(g-s[0]));if(n>1){for(l=0;l'}}return m};return c})();Dygraph.Plugins.RangeSelector=(function(){var a=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera;this.hasTouchInterface_=typeof(TouchEvent)!="undefined";this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion);this.interfaceCreated_=false};a.prototype.toString=function(){return"RangeSelector Plugin"};a.prototype.activate=function(b){this.dygraph_=b;this.isUsingExcanvas_=b.isUsingExcanvas_;if(this.getOption_("showRangeSelector")){this.createInterface_()}return{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}};a.prototype.destroy=function(){this.bgcanvas_=null;this.fgcanvas_=null;this.leftZoomHandle_=null;this.rightZoomHandle_=null;this.iePanOverlay_=null};a.prototype.getOption_=function(b){return this.dygraph_.getOption(b)};a.prototype.setDefaultOption_=function(b,c){return this.dygraph_.attrs_[b]=c};a.prototype.createInterface_=function(){this.createCanvases_();if(this.isUsingExcanvas_){this.createIEPanOverlay_()}this.createZoomHandles_();this.initInteraction_();if(this.getOption_("animatedZooms")){this.dygraph_.warn("Animated zooms and range selector are not compatible; disabling animatedZooms.");this.dygraph_.updateOptions({animatedZooms:false},true)}this.interfaceCreated_=true;this.addToGraph_()};a.prototype.addToGraph_=function(){var b=this.graphDiv_=this.dygraph_.graphDiv;b.appendChild(this.bgcanvas_);b.appendChild(this.fgcanvas_);b.appendChild(this.leftZoomHandle_);b.appendChild(this.rightZoomHandle_)};a.prototype.removeFromGraph_=function(){var b=this.graphDiv_;b.removeChild(this.bgcanvas_);b.removeChild(this.fgcanvas_);b.removeChild(this.leftZoomHandle_);b.removeChild(this.rightZoomHandle_);this.graphDiv_=null};a.prototype.reserveSpace_=function(b){if(this.getOption_("showRangeSelector")){b.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)}};a.prototype.renderStaticLayer_=function(){if(!this.updateVisibility_()){return}this.resize_();this.drawStaticLayer_()};a.prototype.renderInteractiveLayer_=function(){if(!this.updateVisibility_()||this.isChangingRange_){return}this.placeZoomHandles_();this.drawInteractiveLayer_()};a.prototype.updateVisibility_=function(){var b=this.getOption_("showRangeSelector");if(b){if(!this.interfaceCreated_){this.createInterface_()}else{if(!this.graphDiv_||!this.graphDiv_.parentNode){this.addToGraph_()}}}else{if(this.graphDiv_){this.removeFromGraph_();var c=this.dygraph_;setTimeout(function(){c.width_=0;c.resize()},1)}}return b};a.prototype.resize_=function(){function d(e,f){e.style.top=f.y+"px";e.style.left=f.x+"px";e.width=f.w;e.height=f.h;e.style.width=e.width+"px";e.style.height=e.height+"px"}var c=this.dygraph_.layout_.getPlotArea();var b=this.getOption_("xAxisHeight")||(this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize"));this.canvasRect_={x:c.x,y:c.y+c.h+b+4,w:c.w,h:this.getOption_("rangeSelectorHeight")};d(this.bgcanvas_,this.canvasRect_);d(this.fgcanvas_,this.canvasRect_)};a.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas();this.bgcanvas_.className="dygraph-rangesel-bgcanvas";this.bgcanvas_.style.position="absolute";this.bgcanvas_.style.zIndex=9;this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_);this.fgcanvas_=Dygraph.createCanvas();this.fgcanvas_.className="dygraph-rangesel-fgcanvas";this.fgcanvas_.style.position="absolute";this.fgcanvas_.style.zIndex=9;this.fgcanvas_.style.cursor="default";this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)};a.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div");this.iePanOverlay_.style.position="absolute";this.iePanOverlay_.style.backgroundColor="white";this.iePanOverlay_.style.filter="alpha(opacity=0)";this.iePanOverlay_.style.display="none";this.iePanOverlay_.style.cursor="move";this.fgcanvas_.appendChild(this.iePanOverlay_)};a.prototype.createZoomHandles_=function(){var b=new Image();b.className="dygraph-rangesel-zoomhandle";b.style.position="absolute";b.style.zIndex=10;b.style.visibility="hidden";b.style.cursor="col-resize";if(/MSIE 7/.test(navigator.userAgent)){b.width=7;b.height=14;b.style.backgroundColor="white";b.style.border="1px solid #333333"}else{b.width=9;b.height=16;b.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="}if(this.isMobileDevice_){b.width*=2;b.height*=2}this.leftZoomHandle_=b;this.rightZoomHandle_=b.cloneNode(false)};a.prototype.initInteraction_=function(){var o=this;var i=this.isIE_?document:window;var u=0;var v=null;var s=false;var d=false;var g=!this.isMobileDevice_&&!this.isUsingExcanvas_;var k=new Dygraph.IFrameTarp();var p,f,r,j,w,h,x,t,q,c,l;var e,n,m;p=function(C){var B=o.dygraph_.xAxisExtremes();var z=(B[1]-B[0])/o.canvasRect_.w;var A=B[0]+(C.leftHandlePos-o.canvasRect_.x)*z;var y=B[0]+(C.rightHandlePos-o.canvasRect_.x)*z;return[A,y]};f=function(y){Dygraph.cancelEvent(y);s=true;u=y.clientX;v=y.target?y.target:y.srcElement;if(y.type==="mousedown"||y.type==="dragstart"){Dygraph.addEvent(i,"mousemove",r);Dygraph.addEvent(i,"mouseup",j)}o.fgcanvas_.style.cursor="col-resize";k.cover();return true};r=function(C){if(!s){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var y;if(v==o.leftZoomHandle_){y=B.leftHandlePos+z;y=Math.min(y,B.rightHandlePos-v.width-3);y=Math.max(y,o.canvasRect_.x)}else{y=B.rightHandlePos+z;y=Math.min(y,o.canvasRect_.x+o.canvasRect_.w);y=Math.max(y,B.leftHandlePos+v.width+3)}var A=v.width/2;v.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){w()}return true};j=function(y){if(!s){return false}s=false;k.uncover();Dygraph.removeEvent(i,"mousemove",r);Dygraph.removeEvent(i,"mouseup",j);o.fgcanvas_.style.cursor="default";if(!g){w()}return true};w=function(){try{var z=o.getZoomHandleStatus_();o.isChangingRange_=true;if(!z.isZoomed){o.dygraph_.resetZoom()}else{var y=p(z);o.dygraph_.doZoomXDates_(y[0],y[1])}}finally{o.isChangingRange_=false}};h=function(A){if(o.isUsingExcanvas_){return A.srcElement==o.iePanOverlay_}else{var z=o.leftZoomHandle_.getBoundingClientRect();var y=z.left+z.width/2;z=o.rightZoomHandle_.getBoundingClientRect();var B=z.left+z.width/2;return(A.clientX>y&&A.clientX=o.canvasRect_.x+o.canvasRect_.w){y=o.canvasRect_.x+o.canvasRect_.w;E=y-D}else{E+=z;y+=z}}var A=o.leftZoomHandle_.width/2;o.leftZoomHandle_.style.left=(E-A)+"px";o.rightZoomHandle_.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){c()}return true};q=function(y){if(!d){return false}d=false;Dygraph.removeEvent(i,"mousemove",t);Dygraph.removeEvent(i,"mouseup",q);if(!g){c()}return true};c=function(){try{o.isChangingRange_=true;o.dygraph_.dateWindow_=p(o.getZoomHandleStatus_());o.dygraph_.drawGraph_(false)}finally{o.isChangingRange_=false}};l=function(y){if(s||d){return}var z=h(y)?"move":"default";if(z!=o.fgcanvas_.style.cursor){o.fgcanvas_.style.cursor=z}};e=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(f(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(r(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{j(y)}}};n=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(x(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(t(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{q(y)}}};m=function(B,A){var z=["touchstart","touchend","touchmove","touchcancel"];for(var y=0;y1&&v[t][1]!==null){m=typeof v[t][1]!="number";if(m){d=[];h=[];for(r=0;r0)){b=Math.min(b,g);c=Math.max(c,g)}}var o=0.25;if(u){c=Dygraph.log10(c);c+=c*o;b=Dygraph.log10(b);for(t=0;tthis.canvasRect_.x||b+1=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/src/webapp/templates/index.html b/src/webapp/templates/index.html deleted file mode 100644 index 54b70847..00000000 --- a/src/webapp/templates/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - skyline - - - - - - - - - - - -
-
- Select a metric -
-
-
1 hour:
-
-
-
24 hours:
-
-
-
-
-
- metric name - anomalous datapoint -
-
-
-
- - - diff --git a/src/webapp/webapp.py b/src/webapp/webapp.py deleted file mode 100644 index 70cf2775..00000000 --- a/src/webapp/webapp.py +++ /dev/null @@ -1,98 +0,0 @@ -import redis -import logging -import simplejson as json -import sys -from msgpack import Unpacker -from flask import Flask, request, render_template -from daemon import runner -from os.path import dirname, abspath - -from logging.handlers import TimedRotatingFileHandler - -# add the shared settings file to namespace -sys.path.insert(0, dirname(dirname(abspath(__file__)))) -import settings - -REDIS_CONN = redis.StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) - -app = Flask(__name__) -app.config['PROPAGATE_EXCEPTIONS'] = True - - -@app.route("/") -def index(): - return render_template('index.html'), 200 - - -@app.route("/app_settings") -def app_settings(): - - app_settings = {'GRAPH_URL': settings.GRAPH_URL, - 'OCULUS_HOST': settings.OCULUS_HOST, - 'FULL_NAMESPACE': settings.FULL_NAMESPACE, - } - - resp = json.dumps(app_settings) - return resp, 200 - - -@app.route("/api", methods=['GET']) -def data(): - metric = request.args.get('metric', None) - try: - raw_series = REDIS_CONN.get(metric) - if not raw_series: - resp = json.dumps({'results': 'Error: No metric by that name'}) - return resp, 404 - else: - unpacker = Unpacker(use_list=False) - unpacker.feed(raw_series) - timeseries = [item[:2] for item in unpacker] - resp = json.dumps({'results': timeseries}) - return resp, 200 - except Exception as e: - error = "Error: " + e - resp = json.dumps({'results': error}) - return resp, 500 - - -class App(): - def __init__(self): - self.stdin_path = '/dev/null' - self.stdout_path = settings.LOG_PATH + '/webapp.log' - self.stderr_path = settings.LOG_PATH + '/webapp.log' - self.pidfile_path = settings.PID_PATH + '/webapp.pid' - self.pidfile_timeout = 5 - - def run(self): - - logger.info('starting webapp') - logger.info('hosted at %s' % settings.WEBAPP_IP) - logger.info('running on port %d' % settings.WEBAPP_PORT) - - app.run(settings.WEBAPP_IP, settings.WEBAPP_PORT) - -if __name__ == "__main__": - """ - Start the server - """ - - webapp = App() - - logger = logging.getLogger("AppLog") - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - handler = logging.handlers.TimedRotatingFileHandler( - settings.LOG_PATH + '/webapp.log', - when="midnight", - interval=1, - backupCount=5) - handler.setFormatter(formatter) - logger.addHandler(handler) - - if len(sys.argv) > 1 and sys.argv[1] == 'run': - webapp.run() - else: - daemon_runner = runner.DaemonRunner(webapp) - daemon_runner.daemon_context.files_preserve = [handler.stream] - daemon_runner.do_action() diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index 30b37b4c..a827f298 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -1,14 +1,18 @@ import unittest2 as unittest from mock import Mock, patch from time import time - +import os.path import sys -from os.path import dirname, abspath -sys.path.insert(0, dirname(dirname(abspath(__file__))) + '/src') -sys.path.insert(0, dirname(dirname(abspath(__file__))) + '/src/analyzer') +#from skyline.analyzer import algorithms +#from skyline import settings + +current_dir = os.path.dirname(os.path.realpath(__file__)) +parent_dir = os.path.join(os.path.dirname(os.path.realpath(current_dir))) +skyline_dir = parent_dir + '/skyline' +sys.path.append(skyline_dir) -import algorithms +from analyzer import algorithms import settings @@ -17,7 +21,7 @@ class TestAlgorithms(unittest.TestCase): Test all algorithms with a common, simple/known anomalous data set """ - def _addSkip(self, test, reason): + def _addSkip(self, test, tail_avg, reason): print reason def data(self, ts): diff --git a/tests/test_crucible_algorithms.py b/tests/test_crucible_algorithms.py new file mode 100644 index 00000000..a2f23303 --- /dev/null +++ b/tests/test_crucible_algorithms.py @@ -0,0 +1,142 @@ +import unittest2 as unittest +from mock import Mock, patch +from time import time +import os.path +import sys + +current_dir = os.path.dirname(os.path.realpath(__file__)) +parent_dir = os.path.join(os.path.dirname(os.path.realpath(current_dir))) +skyline_dir = parent_dir + '/skyline' +sys.path.append(skyline_dir) + +from crucible import crucible_algorithms +import settings + + +class TestAlgorithms(unittest.TestCase): + """ + Test all algorithms with a common, simple/known anomalous data set + """ + + def _addSkip(self, test, tail_avg, reason): + print reason + + def anomaly_json(self, ts): + """ + Mostly ones (1), with a final value of 1000 + """ + timeseries = map(list, zip(map(float, range(int(ts) - 86400, int(ts) + 1)), [1] * 86401)) + timeseries[-1][1] = 1000 + timeseries[-2][1] = 1 + timeseries[-3][1] = 1 + +# IN PROGRESS need to make a json anomaly file and a crucible anomaly file + converted = [] + for datapoint in timeseries: + try: + new_datapoint = [float(datapoint[1]), float(datapoint[0])] + converted.append(new_datapoint) + except: + continue + + with open(anomaly_json, 'w') as f: + f.write(json.dumps(converted)) + if python_version == 2: + os.chmod(anomaly_json, 0644) + if python_version == 3: + os.chmod(anomaly_json, 0o644) + + def data(self, ts): + """ + Mostly ones (1), with a final value of 1000 + """ + timeseries = map(list, zip(map(float, range(int(ts) - 86400, int(ts) + 1)), [1] * 86401)) + timeseries[-1][1] = 1000 + timeseries[-2][1] = 1 + timeseries[-3][1] = 1 + + return ts, timeseries + + def test_tail_avg(self): + _, timeseries = self.data(time()) + self.assertEqual(crucible_algorithms.tail_avg(timeseries), 334) + + def test_grubbs(self): +# _, timeseries = self.data(time()) + now = time() + _, timeseries = self.data(now) + start_timestamp = timeseries[0][0] + end_timestamp = timeseries[-1][0] + full_duration = end_timestamp - start_timestamp + self.assertTrue(crucible_algorithms.grubbs(timeseries, end_timestamp, full_duration)) + + @patch.object(crucible_algorithms, 'time') + def test_first_hour_average(self, timeMock): + timeMock.return_value, timeseries = self.data(time()) + self.assertTrue(crucible_algorithms.first_hour_average(timeseries)) + + def test_stddev_from_average(self): + # _, timeseries = self.data(time()) + # self.assertTrue(crucible_algorithms.stddev_from_average(timeseries)) + now = time() + _, timeseries = self.data(now) + start_timestamp = timeseries[0][0] + end_timestamp = timeseries[-1][0] + full_duration = end_timestamp - start_timestamp + + _, timeseries = self.data(time()) + self.assertTrue(crucible_algorithms.stddev_from_average(timeseries, end_timestamp, full_duration)) + + def test_stddev_from_moving_average(self): + _, timeseries = self.data(time()) + self.assertTrue(crucible_algorithms.stddev_from_moving_average(timeseries)) + + def test_mean_subtraction_cumulation(self): + _, timeseries = self.data(time()) + self.assertTrue(crucible_algorithms.mean_subtraction_cumulation(timeseries)) + + @patch.object(crucible_algorithms, 'time') + def test_least_squares(self, timeMock): + timeMock.return_value, timeseries = self.data(time()) + self.assertTrue(crucible_algorithms.least_squares(timeseries)) + + def test_histogram_bins(self): + _, timeseries = self.data(time()) + self.assertTrue(crucible_algorithms.histogram_bins(timeseries)) + + @patch.object(crucible_algorithms, 'time') + def test_run_selected_algorithm(self, timeMock): + timeMock.return_value, timeseries = self.data(time()) + result, ensemble, datapoint = crucible_algorithms.run_selected_algorithm(timeseries, "test.metric") + self.assertTrue(result) + self.assertTrue(len(filter(None, ensemble)) >= settings.CONSENSUS) + self.assertEqual(datapoint, 1000) + + @unittest.skip('Fails inexplicable in certain environments.') + @patch.object(crucible_algorithms, 'CONSENSUS') + @patch.object(crucible_algorithms, 'ALGORITHMS') + @patch.object(crucible_algorithms, 'time') + def test_run_selected_algorithm_runs_novel_algorithm(self, timeMock, + algorithmsListMock, consensusMock): + """ + Assert that a user can add their own custom algorithm. + + This mocks out settings.ALGORITHMS and settings.CONSENSUS to use only a + single custom-defined function (alwaysTrue) + """ + algorithmsListMock.__iter__.return_value = ['alwaysTrue'] + consensusMock = 1 + timeMock.return_value, timeseries = self.data(time()) + + alwaysTrue = Mock(return_value=True) + with patch.dict(crucible_algorithms.__dict__, {'alwaysTrue': alwaysTrue}): + result, ensemble, tail_avg = algorithms.run_selected_algorithm(timeseries) + + alwaysTrue.assert_called_with(timeseries) + self.assertTrue(result) + self.assertEqual(ensemble, [True]) + self.assertEqual(tail_avg, 334) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_imports.py b/tests/test_imports.py new file mode 100644 index 00000000..0e1c9de9 --- /dev/null +++ b/tests/test_imports.py @@ -0,0 +1,146 @@ +""" +Test class for Skyline all imports +""" + +import sys +import os.path +import os +import re +import mock +import traceback + +python_version = int(sys.version_info[0]) + +if python_version == 2: + from mock import Mock as MagicMock +if python_version == 3: + from unittest.mock import MagicMock + +current_dir = os.path.dirname(os.path.realpath(__file__)) +print ('current_dir: %s' % current_dir) +parent_dir = os.path.join(os.path.dirname(os.path.realpath(current_dir))) +print ('parent_dir: %s' % parent_dir) +skyline_dir = parent_dir + '/skyline' +print ('skyline_dir: %s' % skyline_dir) +sys.path.append(skyline_dir) + + +class Mock(MagicMock): + @classmethod + def __getattr__(cls, name): + return Mock() + +MOCK_MODULES = ['pygerduty', 'python-simple-hipchat'] +if python_version == 2: + sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) +if python_version == 3: + sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) + +import settings + +print (settings.ALGORITHMS) + +skyline_settings_file = skyline_dir + '/settings.py' + +skyline_apps = ['analyzer', 'horizon', 'webapp', 'mirage', 'boundary', + 'crucible', 'panorama'] + +for skyline_app in skyline_apps: + + skyline_dir_app = parent_dir + '/skyline/' + skyline_app + sys.path.append(skyline_dir_app) + + skyline_python_files = [] + + for path, dirs, files in os.walk(skyline_dir_app): + for filename in files: + pattern = re.compile("^.*\.py$") + filename_match = pattern.match(str(filename)) + if filename_match: + python_file = os.path.join(path, filename) + skyline_python_files.append(python_file) + + import_tmp_file = '%s/%s_imports.py' % (current_dir, skyline_app) + import_tmp_file_pyc = '%s/%s_imports.pyc' % (current_dir, skyline_app) + if os.path.exists(import_tmp_file): + os.remove(import_tmp_file) + + with open(import_tmp_file, 'w') as f: + pass + + first_line = 'print ("Testing %s imports")\n' % skyline_app + with open(import_tmp_file, 'w') as f: + f.write(first_line) + + traceback_line = 'import traceback\n' + with open(import_tmp_file, 'a') as f: + f.write(traceback_line) + + skyline_app_class = skyline_app.title() + app_import_line = 'from %s import %s' % (skyline_app, skyline_app_class) + + for skyline_python_file in skyline_python_files: + if str(skyline_python_file) == skyline_settings_file: + continue + + try_line = 'try:\n' + except_line = 'except:\n' + with open(skyline_python_file, 'r') as f: + for line in f: + import_line = str(line).lstrip() + if 'import ' in import_line: + if 'from settings ' in import_line: + continue + elif 'import settings' in import_line: + continue + elif app_import_line in import_line: + continue + else: + if import_line.startswith(('from ', 'import ')): + try_import_line = ' ' + str(import_line) + except_import_line1 = ' print(traceback.format_exc())\n' + except_import_line2 = ' print ("Failed to import")\n' + with open(import_tmp_file, 'a') as f: + f.write(try_line) + f.write(try_import_line) + f.write(except_line) + f.write(except_import_line1) + f.write(except_import_line2) + + if skyline_app in 'analyzer mirage boundary crucible': + dir_line = 'sys.path.append(\'%s\')\n' % skyline_dir_app + try_app_import_line = ' %s\n' % app_import_line + except_import_line1 = ' print(traceback.format_exc())\n' + except_import_line2 = ' print ("Failed to import")\n' + with open(import_tmp_file, 'a') as f: + f.write('import sys\n') + f.write(dir_line) + f.write(try_line) + f.write(try_app_import_line) + f.write(except_line) + f.write(except_import_line1) + f.write(except_import_line2) + + try: + if skyline_app == 'analyzer': + import analyzer_imports + if skyline_app == 'horizon': + import horizon_imports + if skyline_app == 'webapp': + import webapp_imports + if skyline_app == 'mirage': + import mirage_imports + if skyline_app == 'boundary': + import boundary_imports + if skyline_app == 'crucible': + import crucible_imports + if skyline_app == 'panorama': + import panorama_imports + if os.path.exists(import_tmp_file): + os.remove(import_tmp_file) + if os.path.exists(import_tmp_file_pyc): + os.remove(import_tmp_file_pyc) + print ('%s imports OK' % (skyline_app)) + except: + print (traceback.format_exc()) + print ('Failed to import %s imports' % (skyline_app)) diff --git a/utils/continuity.py b/utils/continuity.py index c25ef12c..00b06d23 100644 --- a/utils/continuity.py +++ b/utils/continuity.py @@ -5,13 +5,13 @@ from os.path import dirname, abspath # add the shared settings file to namespace -sys.path.insert(0, ''.join((dirname(dirname(abspath(__file__))), "/src"))) -import settings +sys.path.insert(0, ''.join((dirname(dirname(abspath(__file__))), ""))) +from skyline import settings metric = 'horizon.test.udp' -def check_continuity(metric, mini = False): +def check_continuity(metric, mini=False): r = redis.StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) if mini: raw_series = r.get(settings.MINI_NAMESPACE + metric) diff --git a/utils/seed_data.py b/utils/seed_data.py index 087e39a1..7f9146f2 100755 --- a/utils/seed_data.py +++ b/utils/seed_data.py @@ -9,6 +9,7 @@ from os.path import dirname, join, realpath from multiprocessing import Manager, Process, log_to_stderr from struct import Struct, pack +import traceback import redis import msgpack @@ -18,7 +19,8 @@ __location__ = realpath(join(os.getcwd(), dirname(__file__))) # Add the shared settings file to namespace. -sys.path.insert(0, join(__location__, '..', 'src')) +sys.path.insert(0, join(__location__, '..', 'skyline')) +# ignoreErrorCodes E402 import settings @@ -27,7 +29,48 @@ class NoDataException(Exception): def seed(): - print 'Loading data over UDP via Horizon...' + + print 'notice :: testing the Horizon parameters' + + if not settings.UDP_PORT: + print 'error :: could not determine the settings.UDP_PORT, please check you settings.py' + else: + print 'info :: settings.UDP_PORT :: ' + str(settings.UDP_PORT) + + horizon_params_ok = False + horizon_use_ip = False + connect_test_metric = 'horizon.test.params' + connect_test_datapoint = 1 + packet = msgpack.packb((connect_test_metric, connect_test_datapoint)) + test_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + try: + test_sock.sendto(packet, (socket.gethostname(), settings.UDP_PORT)) + horizon_params_ok = True + print 'notice :: Horizon parameters OK' + except Exception as e: + print 'warning :: there is an issue with the Horizon parameters' + traceback.print_exc() + print 'info :: this is possibly a hostname related issue' + print 'notice :: trying on 127.0.0.1' + + if not horizon_params_ok: + try: + test_sock.sendto(packet, ('127.0.0.1', settings.UDP_PORT)) + horizon_params_ok = True + horizon_use_ip = '127.0.0.1' + print 'notice :: using 127.0.0.1 - OK' + except Exception as e: + print 'warn :: there is an issue with the Horizon parameters' + traceback.print_exc() + print 'warn :: Horizon is not available on UDP via 127.0.0.1' + + if not horizon_params_ok: + print 'error :: please check your HORIZON related settings in settings.py and restart the Horizon service' + sys.exit(1) + + print 'notice :: pushing 8665 datapoints over UDP to Horizon' + print 'info :: this takes a while...' metric = 'horizon.test.udp' metric_set = 'unique_metrics' initial = int(time.time()) - settings.MAX_RESOLUTION @@ -36,14 +79,25 @@ def seed(): data = json.loads(f.read()) series = data['results'] sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - + datapoints_sent = 0 + update_user_output = 0 for datapoint in series: datapoint[0] = initial initial += 1 packet = msgpack.packb((metric, datapoint)) - sock.sendto(packet, (socket.gethostname(), settings.UDP_PORT)) - print "Connecting to Redis..." + if not horizon_use_ip: + sock.sendto(packet, (socket.gethostname(), settings.UDP_PORT)) + else: + sock.sendto(packet, (horizon_use_ip, settings.UDP_PORT)) + + update_user_output += 1 + datapoints_sent += 1 + if update_user_output == 1000: + update_user_output = 0 + print 'notice :: ' + str(datapoints_sent) + ' datapoints sent' + + print 'notice :: connecting to Redis to query data and validate Horizon populated Redis with data' r = redis.StrictRedis(unix_socket_path=settings.REDIS_SOCKET_PATH) time.sleep(5) @@ -56,7 +110,7 @@ def seed(): if x is None: raise NoDataException - #Ignore the mini namespace if OCULUS_HOST isn't set. + # Ignore the mini namespace if OCULUS_HOST isn't set. if settings.OCULUS_HOST != "": x = r.smembers(settings.MINI_NAMESPACE + metric_set) if x is None: @@ -66,10 +120,15 @@ def seed(): if x is None: raise NoDataException - print "Congratulations! The data made it in. The Horizon pipeline seems to be working." + print 'info :: Congratulations! The data made it in. The Horizon pipeline is working.' + print 'info :: If your analyzer and webapp were started you should be able to see a triggered anomaly for horizon.test.udp' + print ('info :: at http://%s:%s' % (str(settings.WEBAPP_IP), str(settings.WEBAPP_PORT))) except NoDataException: - print "Woops, looks like the metrics didn't make it into Horizon. Try again?" + print 'error :: Woops, looks like the data did not make it into Horizon. Try again?' + print 'info :: please check your settings.py and ensure that the Horizon and Redis settings are correct.' + print 'info :: ensure Redis is available via socket in your redis.conf' + print 'info :: restart these services and try again' -if __name__ == "__main__": +if __name__ == '__main__': seed() diff --git a/utils/verify_alerts.py b/utils/verify_alerts.py index cc631b82..14dde24e 100644 --- a/utils/verify_alerts.py +++ b/utils/verify_alerts.py @@ -10,13 +10,9 @@ __location__ = realpath(join(os.getcwd(), dirname(__file__))) # Add the shared settings file to namespace. -sys.path.insert(0, join(__location__, '..', 'src')) +sys.path.insert(0, join(__location__, '..', 'skyline')) import settings -# Add the analyzer file to namespace. -sys.path.insert(0, join(__location__, '..', 'src', 'analyzer')) -from alerters import trigger_alert - parser = OptionParser() parser.add_option("-t", "--trigger", dest="trigger", default=False, help="Actually trigger the appropriate alerts (default is False)") @@ -26,6 +22,11 @@ (options, args) = parser.parse_args() + +# Analyzer alerts +sys.path.insert(0, join(__location__, '..', 'skyline', 'analyzer')) +from alerters import trigger_alert + try: alerts_enabled = settings.ENABLE_ALERTS alerts = settings.ALERTS @@ -33,16 +34,79 @@ print "Exception: Check your settings file for the existence of ENABLE_ALERTS and ALERTS" sys.exit() +try: + syslog_enabled = settings.SYSLOG_ENABLED +except: + syslog_enabled = False + print 'Verifying alerts for: "' + options.metric + '"' # Send alerts if alerts_enabled: for alert in alerts: if alert[0] in options.metric: - print ' Testing against "' + alert[0] + '" to send via ' + alert[1] + "...triggered" + print ' Testing Analyzer alerting - against "' + alert[0] + '" to send via ' + alert[1] + "...triggered" if options.trigger: - trigger_alert(alert, options.metric) + metric = (0, options.metric) + trigger_alert(alert, metric) + if syslog_enabled: + print ' Testing Analyzer alerting - against "' + alert[0] + '" to send via syslog ' + "...triggered" + alert = (alert[0], 'syslog') + trigger_alert(alert, metric) else: - print ' Testing against "' + alert[0] + '" to send via ' + alert[1] + "..." + print ' Testing Analyzer alerting - against "' + alert[0] + '" to send via ' + alert[1] + "..." else: print 'Alerts are disabled' + +# Mirage alerts +try: + mirage_enabled = settings.ENABLE_MIRAGE + mirage_alerts_enabled = settings.MIRAGE_ENABLE_ALERTS +except: + mirage_alerts_enabled = False + +if mirage_alerts_enabled: + sys.path.insert(0, join(__location__, '..', 'skyline', 'mirage')) + import mirage_alerters + from mirage_alerters import trigger_alert + if mirage_alerts_enabled: + for alert in alerts: + if alert[0] in options.metric: + print ' Testing Mirage alerting - against "' + alert[0] + '" to send via ' + alert[1] + "...triggered" + if options.trigger: + metric = (0, options.metric) + trigger_alert(alert, metric, 86400) + if syslog_enabled: + print ' Testing Mirage alerting - against "' + alert[0] + '" to send via syslog ' + "...triggered" + alert = (alert[0], 'syslog') + trigger_alert(alert, metric, 86400) + else: + print ' Testing Mirage alerting - against "' + alert[0] + '" to send via ' + alert[1] + "..." +else: + print 'Mirage alerts are disabled' + +# Boundary alerts +try: + boundary_alerts_enabled = settings.BOUNDARY_ENABLE_ALERTS + boundary_alerts = settings.ALERTS +except: + boundary_alerts_enabled = False + +if boundary_alerts_enabled: + sys.path.insert(0, join(__location__, '..', 'skyline', 'boundary')) + from boundary_alerters import trigger_alert + if boundary_alerts_enabled: + for alert in alerts: + if alert[0] in options.metric: + print ' Testing against "' + alert[0] + '" ...triggered' + if options.trigger: + print ' Testing against "' + alert[0] + '" to send via smtp' + trigger_alert('smtp', '0', alert[0], '1', '1', 'greater_than') + print ' Testing against "' + alert[0] + '" to send via hipchat' + trigger_alert('hipchat', '0', alert[0], '1', '1', 'greater_than') + print ' Testing against "' + alert[0] + '" to send via pagerduty' + trigger_alert('pagerduty', '0', alert[0], '1', '1', 'greater_than') + print ' Testing against "' + alert[0] + '" to send via syslog' + trigger_alert('syslog', '0', alert[0], '1', '1', 'greater_than') +else: + print 'Boundary alerts are disabled'