From 7dc4f3022d1843063dce621ca09c80bb64cdb8c1 Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 1 Oct 2014 18:48:09 +0200 Subject: Improve diagnose.py reusability --- client/diagnose.py | 240 +++++++++++++++++++++++++++-------------------------- 1 file changed, 123 insertions(+), 117 deletions(-) (limited to 'client/diagnose.py') diff --git a/client/diagnose.py b/client/diagnose.py index fb19294..0483c22 100755 --- a/client/diagnose.py +++ b/client/diagnose.py @@ -1,139 +1,145 @@ -#!/usr/bin/env python2 +# -*- coding: utf-8-*- +import os +import sys import time -import re import socket -import os -import jasperpath import subprocess +import pkgutil import logging -import sys -from distutils.spawn import find_executable +if sys.version_info < (3, 3): + from distutils.spawn import find_executable +else: + from shutil import which as find_executable + +import pip.req import pip.util +import jasperpath logger = logging.getLogger(__name__) -class Diagnostics: - - """ - Set of diagnostics to be run for determining the health of the - host running Jasper - - To add new checks, add a boolean returning method with a name that starts - with `check_` - """ - - @classmethod - def check_network_connection(cls): - try: - # see if we can resolve the host name -- tells us if there is - # a DNS listening - host = socket.gethostbyname("www.google.com") - # connect to the host -- tells us if the host is actually - # reachable - socket.create_connection((host, 80), 2) - except Exception: - return False - else: - return True - - @classmethod - def check_phonetisaurus_dictionary_file(cls): - return os.path.isfile(os.path.join(jasperpath.APP_PATH, "..", - "phonetisaurus/g014b2b.fst")) - - @classmethod - def check_phonetisaurus_program(cls): - return cls.do_check_program('phonetisaurus-g2p') - - @classmethod - def check_espeak_program(cls): - return cls.do_check_program('espeak') - - @classmethod - def check_say_program(cls): - return cls.do_check_program('say') - - @classmethod - def do_check_program(cls, program): - return find_executable(program) is not None - - @classmethod - def check_all_pip_requirements_installed(cls): - distributions = pip.util.get_installed_distributions() - requirements_lines = [line.strip() for line in - open('requirements.txt').readlines()] - requirements = [name.split('==')[0] for name in - list(filter(None, requirements_lines))] - installed_packages = [pkg.project_name for pkg in list(distributions)] - missing_packages = [pkg for pkg in requirements - if pkg not in installed_packages] - if missing_packages: - logger.info("Missing packages: "+', '.join(missing_packages)) - return False - else: - return True - - @classmethod - def info_git_revision(cls): - return subprocess.check_output(['git', 'rev-parse', 'HEAD']) - - -class DiagnosticRunner: - +def check_network_connection(server="www.google.com"): + logger = logging.getLogger(__name__) + logger.debug("Checking network connection to server '%s'...", server) + try: + # see if we can resolve the host name -- tells us if there is + # a DNS listening + host = socket.gethostbyname(server) + # connect to the host -- tells us if the host is actually + # reachable + socket.create_connection((host, 80), 2) + except Exception: + logger.debug("Network connection not working") + return False + else: + logger.debug("Network connection working") + return True + + +def check_executable(executable): + logger = logging.getLogger(__name__) + logger.debug("Checking executable '%s'...", executable) + executable_path = find_executable(executable) + found = executable_path is not None + if found: + logger.debug("Executable '%s' found: '%s'", executable, + executable_path) + else: + logger.debug("Executable '%s' not found", executable) + return found + + +def check_python_import(package_or_module): + logger = logging.getLogger(__name__) + logger.debug("Checking python import '%s'...", package_or_module) + loader = pkgutil.get_loader(package_or_module) + found = loader is not None + if found: + logger.debug("Python %s '%s' found: %r", + "package" if loader.is_package(package_or_module) + else "module", package_or_module, loader.get_filename()) + else: + logger.debug("Python import '%s' not found", package_or_module) + return found + + +def get_pip_requirements(fname=os.path.join(jasperpath.LIB_PATH, + 'requirements.txt')): + logger = logging.getLogger(__name__) + if os.access(fname, os.R_OK): + reqs = list(pip.req.parse_requirements(fname)) + logger.debug("Found %d PIP requirements in file '%s'", len(reqs), + fname) + return reqs + else: + logger.debug("PIP requirements file '%s' not found or not readable", + fname) + + +def get_git_revision(): + logger = logging.getLogger(__name__) + if not check_executable('git'): + logger.warning("'git' command not found, git revision not detectable") + return None + output = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() + if not output: + logger.warning("Couldn't detect git revision (not a git repository?)") + return None + return output + + +class DiagnosticRunner(object): """ Performs a series of checks against the system, printing the results to the console and also saving them to diagnostic.log """ - def __init__(self, diagnostics): - self.diagnostics = diagnostics + def __init__(self): + self._logger = logging.getLogger(__name__) def run(self): - self.initialize_log() - self.perform_checks() - - def perform_checks(self): - self.failed_checks = 0 - for check in self.select_methods('check'): - self.do_check(check) - for info in self.select_methods('info'): - self.get_info(info) - if self.failed_checks == 0: + self._logger.info("Starting jasper diagnostic at %s", + time.strftime("%c")) + self._logger.info("Git revision: %r", get_git_revision()) + + failed_checks = 0 + + if not check_network_connection(): + failed_checks += 1 + + for executable in ['phonetisaurus-g2p', 'espeak', 'say']: + if not check_executable(executable): + self._logger.warning("Executable '%s' is missing in $PATH", + executable) + failed_checks += 1 + + for req in get_pip_requirements(): + self._logger.debug("Checking PIP package '%s'...", req.name) + if not req.check_if_exists(): + self._logger.warning("PIP package '%s' is missing", req.name) + failed_checks += 1 + else: + self._logger.debug("PIP package '%s' found", req.name) + + for fname in [os.path.join(jasperpath.APP_PATH, os.pardir, + "phonetisaurus", "g014b2b.fst")]: + self._logger.debug("Checking file '%s'...", fname) + if not os.access(fname, os.R_OK): + self._logger.warning("File '%s' is missing", fname) + failed_checks += 1 + else: + self._logger.debug("File '%s' found", fname) + + if not failed_checks: logger.info("All checks passed") else: - logger.info("%d checks failed" % self.failed_checks) - - def select_methods(self, prefix): - def is_match(method_name): - return (callable(getattr(self.diagnostics, method_name)) and - re.match(r"\A" + prefix + "_", method_name)) - - return [method_name for method_name in dir(self.diagnostics) - if is_match(method_name)] - - def initialize_log(self): - logger.info("Starting jasper diagnostic at %s" % time.strftime("%c")) - - def get_info(self, info_name): - message = info_name.replace("info_", "").replace("_", " ") - info_method = getattr(self.diagnostics, info_name) - info = info_method() - logger.info("%s: %s" % (message, info)) - - def do_check(self, check_name): - message = check_name.replace("check_", "").replace("_", " ") - check = getattr(self.diagnostics, check_name) - if check(): - result = "OK" - else: - self.failed_checks += 1 - result = "FAILED" + logger.info("%d checks failed" % failed_checks) - logger.info("Checking %s... %s" % (message, result)) + return failed_checks if __name__ == '__main__': - logging.basicConfig(stream=sys.stdout, level=logging.INFO) - - DiagnosticRunner(Diagnostics).run() + logging.basicConfig(stream=sys.stdout) + logger = logging.getLogger() + logger.setLevel(logging.DEBUG if '--debug' in sys.argv else logging.INFO) + DiagnosticRunner().run() -- cgit v1.3.1 From fc68f5323584c8307872b02a75b76fe671f26cd4 Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 1 Oct 2014 19:22:58 +0200 Subject: Set loglevel at least to info during diagnose run and add '--diagnose' argument to jasper.py --- client/diagnose.py | 8 +++++++- jasper.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'client/diagnose.py') diff --git a/client/diagnose.py b/client/diagnose.py index 0483c22..acbec5e 100755 --- a/client/diagnose.py +++ b/client/diagnose.py @@ -98,6 +98,11 @@ class DiagnosticRunner(object): self._logger = logging.getLogger(__name__) def run(self): + # Set loglevel of this module least to info + loglvl = self._logger.getEffectiveLevel() + if loglvl == logging.NOTSET or loglvl > logging.INFO: + self._logger.setLevel(logging.INFO) + self._logger.info("Starting jasper diagnostic at %s", time.strftime("%c")) self._logger.info("Git revision: %r", get_git_revision()) @@ -141,5 +146,6 @@ class DiagnosticRunner(object): if __name__ == '__main__': logging.basicConfig(stream=sys.stdout) logger = logging.getLogger() - logger.setLevel(logging.DEBUG if '--debug' in sys.argv else logging.INFO) + if '--debug' in sys.argv: + logger.setLevel(logging.DEBUG) DiagnosticRunner().run() diff --git a/jasper.py b/jasper.py index 5061510..1e3484b 100755 --- a/jasper.py +++ b/jasper.py @@ -20,6 +20,8 @@ parser.add_argument('--local', action='store_true', help='Use text input instead of a real microphone') parser.add_argument('--no-network-check', action='store_true', help='Disable the network connection check') +parser.add_argument('--diagnose', action='store_true', + help='Run diagnose and exit') parser.add_argument('--debug', action='store_true', help='Show debug messages') args = parser.parse_args() @@ -135,6 +137,11 @@ if __name__ == "__main__": logger.warning("Network not connected. This may prevent Jasper from " + "running properly.") + if args.diagnose: + diag = diagnose.DiagnosticRunner() + failed_checks = diag.run() + sys.exit(0 if not failed_checks else 1) + try: app = Jasper() except Exception: -- cgit v1.3.1 From 20ab84c617256a969a8b81ca72470d3ff871e554 Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 1 Oct 2014 19:25:09 +0200 Subject: Remove executable flag from diagnose --- client/diagnose.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 client/diagnose.py (limited to 'client/diagnose.py') diff --git a/client/diagnose.py b/client/diagnose.py old mode 100755 new mode 100644 -- cgit v1.3.1 From c182a08f3e1578f83d9c024a9c5b404d04afec4b Mon Sep 17 00:00:00 2001 From: schneefux Date: Thu, 2 Oct 2014 15:08:23 +0200 Subject: Transform DiagnosticRunner.run() into a single function --- client/diagnose.py | 82 +++++++++++++++++++++++++----------------------------- jasper.py | 3 +- 2 files changed, 39 insertions(+), 46 deletions(-) (limited to 'client/diagnose.py') diff --git a/client/diagnose.py b/client/diagnose.py index acbec5e..b51ffc4 100644 --- a/client/diagnose.py +++ b/client/diagnose.py @@ -87,60 +87,54 @@ def get_git_revision(): return None return output - -class DiagnosticRunner(object): +def run(): """ - Performs a series of checks against the system, printing the results to the - console and also saving them to diagnostic.log + Performs a series of checks against the system and writes the results to + the logging system. """ + logger = logging.getLogger(__name__) - def __init__(self): - self._logger = logging.getLogger(__name__) + # Set loglevel of this module least to info + loglvl = logger.getEffectiveLevel() + if loglvl == logging.NOTSET or loglvl > logging.INFO: + logger.setLevel(logging.INFO) - def run(self): - # Set loglevel of this module least to info - loglvl = self._logger.getEffectiveLevel() - if loglvl == logging.NOTSET or loglvl > logging.INFO: - self._logger.setLevel(logging.INFO) + logger.info("Starting jasper diagnostic at %s" % time.strftime("%c")) + logger.info("Git revision: %r", get_git_revision()) - self._logger.info("Starting jasper diagnostic at %s", - time.strftime("%c")) - self._logger.info("Git revision: %r", get_git_revision()) + failed_checks = 0 - failed_checks = 0 + if not check_network_connection(): + failed_checks += 1 + + for executable in ['phonetisaurus-g2p', 'espeak', 'say']: + if not check_executable(executable): + logger.warning("Executable '%s' is missing in $PATH", executable) + failed_checks += 1 - if not check_network_connection(): + for req in get_pip_requirements(): + logger.debug("Checking PIP package '%s'...", req.name) + if not req.check_if_exists(): + logger.warning("PIP package '%s' is missing", req.name) failed_checks += 1 + else: + logger.debug("PIP package '%s' found", req.name) - for executable in ['phonetisaurus-g2p', 'espeak', 'say']: - if not check_executable(executable): - self._logger.warning("Executable '%s' is missing in $PATH", - executable) - failed_checks += 1 - - for req in get_pip_requirements(): - self._logger.debug("Checking PIP package '%s'...", req.name) - if not req.check_if_exists(): - self._logger.warning("PIP package '%s' is missing", req.name) - failed_checks += 1 - else: - self._logger.debug("PIP package '%s' found", req.name) - - for fname in [os.path.join(jasperpath.APP_PATH, os.pardir, - "phonetisaurus", "g014b2b.fst")]: - self._logger.debug("Checking file '%s'...", fname) - if not os.access(fname, os.R_OK): - self._logger.warning("File '%s' is missing", fname) - failed_checks += 1 - else: - self._logger.debug("File '%s' found", fname) - - if not failed_checks: - logger.info("All checks passed") + for fname in [os.path.join(jasperpath.APP_PATH, os.pardir, "phonetisaurus", + "g014b2b.fst")]: + logger.debug("Checking file '%s'...", fname) + if not os.access(fname, os.R_OK): + logger.warning("File '%s' is missing", fname) + failed_checks += 1 else: - logger.info("%d checks failed" % failed_checks) + logger.debug("File '%s' found", fname) + + if not failed_checks: + logger.info("All checks passed") + else: + logger.info("%d checks failed" % failed_checks) - return failed_checks + return failed_checks if __name__ == '__main__': @@ -148,4 +142,4 @@ if __name__ == '__main__': logger = logging.getLogger() if '--debug' in sys.argv: logger.setLevel(logging.DEBUG) - DiagnosticRunner().run() + run() diff --git a/jasper.py b/jasper.py index 1e3484b..d116ce9 100755 --- a/jasper.py +++ b/jasper.py @@ -138,8 +138,7 @@ if __name__ == "__main__": "running properly.") if args.diagnose: - diag = diagnose.DiagnosticRunner() - failed_checks = diag.run() + failed_checks = diagnose.run() sys.exit(0 if not failed_checks else 1) try: -- cgit v1.3.1 From 89d76f2a2a14e8dd7d574a2dba608b92a43210ee Mon Sep 17 00:00:00 2001 From: schneefux Date: Thu, 2 Oct 2014 15:18:21 +0200 Subject: Added docstrings to diagnose functions --- client/diagnose.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'client/diagnose.py') diff --git a/client/diagnose.py b/client/diagnose.py index b51ffc4..df12df7 100644 --- a/client/diagnose.py +++ b/client/diagnose.py @@ -19,6 +19,16 @@ logger = logging.getLogger(__name__) def check_network_connection(server="www.google.com"): + """ + Checks if jasper can connect a network server. + + Arguments: + server -- (optional) the server to connect with (Default: + "www.google.com") + + Returns: + True or False + """ logger = logging.getLogger(__name__) logger.debug("Checking network connection to server '%s'...", server) try: @@ -37,6 +47,15 @@ def check_network_connection(server="www.google.com"): def check_executable(executable): + """ + Checks if an executable exists in $PATH. + + Arguments: + executable -- the name of the executable (e.g. "echo") + + Returns: + True or False + """ logger = logging.getLogger(__name__) logger.debug("Checking executable '%s'...", executable) executable_path = find_executable(executable) @@ -50,6 +69,15 @@ def check_executable(executable): def check_python_import(package_or_module): + """ + Checks if a python package or module is importable. + + Arguments: + package_or_module -- the package or module name to check + + Returns: + True or False + """ logger = logging.getLogger(__name__) logger.debug("Checking python import '%s'...", package_or_module) loader = pkgutil.get_loader(package_or_module) @@ -65,6 +93,17 @@ def check_python_import(package_or_module): def get_pip_requirements(fname=os.path.join(jasperpath.LIB_PATH, 'requirements.txt')): + """ + Gets the PIP requirements from a text file. If the files does not exists + or is not readable, it returns None + + Arguments: + fname -- (optional) the requirement text file (Default: + "client/requirements.txt") + + Returns: + A list of pip requirement objects or None + """ logger = logging.getLogger(__name__) if os.access(fname, os.R_OK): reqs = list(pip.req.parse_requirements(fname)) @@ -77,6 +116,13 @@ def get_pip_requirements(fname=os.path.join(jasperpath.LIB_PATH, def get_git_revision(): + """ + Gets the current git revision hash as hex string. If the git executable is + missing or git is unable to get the revision, None is returned + + Returns: + A hex string or None + """ logger = logging.getLogger(__name__) if not check_executable('git'): logger.warning("'git' command not found, git revision not detectable") @@ -87,10 +133,14 @@ def get_git_revision(): return None return output + def run(): """ Performs a series of checks against the system and writes the results to the logging system. + + Returns: + The number of failed checks as integer """ logger = logging.getLogger(__name__) -- cgit v1.3.1