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 +++++++++++++++++++++++++++-------------------------- client/test.py | 10 +-- jasper.py | 6 +- 3 files changed, 130 insertions(+), 126 deletions(-) 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() diff --git a/client/test.py b/client/test.py index d9ca8f5..d774eb7 100644 --- a/client/test.py +++ b/client/test.py @@ -13,8 +13,8 @@ import g2p import brain import jasperpath import tts +import diagnose from stt import TranscriptionMode -from diagnose import Diagnostics DEFAULT_PROFILE = { 'prefers_email': False, @@ -154,7 +154,7 @@ class TestModules(unittest.TestCase): inputs = [] self.runConversation(query, inputs, Time) - @unittest.skipIf(not Diagnostics.check_network_connection(), + @unittest.skipIf(not diagnose.check_network_connection(), "No internet connection") def testGmail(self): key = 'gmail_password' @@ -167,7 +167,7 @@ class TestModules(unittest.TestCase): inputs = [] self.runConversation(query, inputs, Gmail) - @unittest.skipIf(not Diagnostics.check_network_connection(), + @unittest.skipIf(not diagnose.check_network_connection(), "No internet connection") def testHN(self): from modules import HN @@ -180,7 +180,7 @@ class TestModules(unittest.TestCase): outputs = self.runConversation(query, inputs, HN) self.assertTrue("front-page articles" in outputs[1]) - @unittest.skipIf(not Diagnostics.check_network_connection(), + @unittest.skipIf(not diagnose.check_network_connection(), "No internet connection") def testNews(self): from modules import News @@ -193,7 +193,7 @@ class TestModules(unittest.TestCase): outputs = self.runConversation(query, inputs, News) self.assertTrue("top headlines" in outputs[1]) - @unittest.skipIf(not Diagnostics.check_network_connection(), + @unittest.skipIf(not diagnose.check_network_connection(), "No internet connection") def testWeather(self): from modules import Weather diff --git a/jasper.py b/jasper.py index 27a8de8..5061510 100755 --- a/jasper.py +++ b/jasper.py @@ -8,8 +8,7 @@ import logging import yaml import argparse -from client.diagnose import Diagnostics -from client import vocabcompiler, tts, stt, jasperpath +from client import vocabcompiler, tts, stt, jasperpath, diagnose # Add jasperpath.LIB_PATH to sys.path sys.path.append(jasperpath.LIB_PATH) @@ -132,8 +131,7 @@ if __name__ == "__main__": if args.debug: logger.setLevel(logging.DEBUG) - if (not args.no_network_check and - not Diagnostics.check_network_connection()): + if not args.no_network_check and not diagnose.check_network_connection(): logger.warning("Network not connected. This may prevent Jasper from " + "running properly.") -- cgit v1.3.1