You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

run.py 9.4 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # Copyright 2019 Huawei Technologies Co., Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ============================================================================
  15. """Web service entrance."""
  16. import os
  17. import re
  18. import shlex
  19. import stat
  20. import subprocess
  21. import sys
  22. import time
  23. from enum import Enum, unique
  24. from gunicorn.glogging import Logger
  25. from mindinsight.backend.config import WEB_CONFIG_DIR
  26. from mindinsight.conf import settings
  27. from mindinsight.utils.log import setup_logger
  28. MINDBOARD_APP_MODULE = "mindinsight.backend.application:APP"
  29. GUNICORN_LOGGER = "mindinsight.backend.run.GunicornLogger"
  30. _MIN_PORT = 1
  31. _MAX_PORT = 65535
  32. @unique
  33. class ServerStateEnum(Enum):
  34. """
  35. The service startup status are as follows: "unknown", "failed" and "success"
  36. """
  37. UNKNOWN = "unknown"
  38. FAILED = "failed"
  39. SUCCESS = "success"
  40. def _get_file_size(file_path):
  41. """
  42. Get the file size.
  43. Args:
  44. file_path (str): The file path.
  45. Returns:
  46. int, the file size. If file is not existed, then return 0.
  47. """
  48. try:
  49. file_size = os.path.getsize(file_path)
  50. except FileNotFoundError:
  51. file_size = 0
  52. return file_size
  53. def _is_match_one(sub_string_list, src_string):
  54. """
  55. Whether the sub-string in the list can match the source string.
  56. Args:
  57. sub_string_list (list): The sub-string list.
  58. src_string (str): The source string.
  59. Returns:
  60. bool, if matched return True, else return False.
  61. """
  62. for match_info in sub_string_list:
  63. if match_info in src_string:
  64. return True
  65. return False
  66. def _check_stat_from_log(pid, log_info):
  67. """
  68. Determine the service startup status based on the log information.
  69. Args:
  70. pid (int): The gunicorn process ID.
  71. log_info (str): The output log of service startup.
  72. Returns:
  73. str, the state value that is one of the follows: "unknown", "failed" and "success".
  74. """
  75. server_state = ServerStateEnum.UNKNOWN.value
  76. # should be synchronized to startup log in gunicorn post_worker_init hook
  77. # refer to mindinsight/backend/config/gunicorn_conf.py
  78. match_success_info = "Server pid: %d, start to listening." % pid
  79. common_failed_info_list = [
  80. "[ERROR] Retrying in 1 second",
  81. "[INFO] Reason: App failed to load",
  82. "[ERROR] Exception in worker process"
  83. ]
  84. re_pattern = "\\[ERROR\\].+%s.+%d" % \
  85. (settings.HOST, int(settings.PORT))
  86. # matched failed output log by fuzzy match
  87. if re.search(re_pattern, log_info) or \
  88. _is_match_one(common_failed_info_list, log_info):
  89. server_state = ServerStateEnum.FAILED.value
  90. if match_success_info in log_info:
  91. server_state = ServerStateEnum.SUCCESS.value
  92. return server_state
  93. def _get_error_log_path():
  94. """
  95. Get gunicorn error log path.
  96. Returns:
  97. str, the path of error log.
  98. """
  99. path = os.path.join(settings.WORKSPACE, 'log/gunicorn/error.{}.log'.format(settings.PORT))
  100. errorlog_abspath = os.path.realpath(path)
  101. return errorlog_abspath
  102. def _get_access_log_path():
  103. """Get gunicorn access log path."""
  104. access_log_path = os.path.join(settings.WORKSPACE, 'log/gunicorn/access.{}.log'.format(settings.PORT))
  105. access_log_path = os.path.realpath(access_log_path)
  106. return access_log_path
  107. def _check_state_from_log(pid, log_abspath, start_pos=0):
  108. """
  109. Check the service startup status based on the log file.
  110. Args:
  111. pid (int): The gunicorn process ID.
  112. log_abspath (str): Absolute path of the log file.
  113. start_pos (int): Offset position of the log file.
  114. Returns:
  115. dict, a dict with "state" and "prompt_message" key.
  116. The value of the "state" key is as follows:"unknown", "failed" and "success".
  117. The value of the "prompt_message" key is a list of prompt messages.
  118. """
  119. state_result = {"state": ServerStateEnum.UNKNOWN.value, "prompt_message": []}
  120. prompt_messages = []
  121. with open(log_abspath) as f_log:
  122. f_log.seek(start_pos)
  123. for line in f_log.readlines():
  124. log_result = _check_stat_from_log(pid, line)
  125. # ignore "unknown" result
  126. if log_result != ServerStateEnum.UNKNOWN.value:
  127. state_result["state"] = log_result
  128. if log_result == ServerStateEnum.FAILED.value:
  129. prompt_messages.append(line.strip())
  130. prompt_messages.append(
  131. "more failed details in log: %s" % log_abspath)
  132. break
  133. if log_result == ServerStateEnum.UNKNOWN.value:
  134. prompt_messages.append(
  135. "more details in log: %s" % log_abspath)
  136. state_result["prompt_message"].append(
  137. "service start state: %s" % state_result["state"])
  138. for prompt_message in prompt_messages:
  139. state_result["prompt_message"].append(prompt_message)
  140. return state_result
  141. def _check_server_start_stat(pid, log_abspath, log_pos):
  142. """
  143. Checking the Server Startup Status.
  144. Args:
  145. pid (int): The gunicorn process ID.
  146. log_abspath (str): The log file path.
  147. Returns:
  148. dict, an dict object that contains the state and prompt_message fields.
  149. The state values are as follows: "unknown", "failed" and "success".
  150. """
  151. state_result = {"state": ServerStateEnum.UNKNOWN.value, "prompt_message": []}
  152. # return unknown when not config gunicorn error log file
  153. if not log_abspath:
  154. return state_result
  155. # sleep 1 second for gunicorn master to be ready
  156. time.sleep(1)
  157. try_cnt = 0
  158. try_cnt_max = 2
  159. while try_cnt < try_cnt_max:
  160. time.sleep(1)
  161. try_cnt += 1
  162. file_size = _get_file_size(log_abspath)
  163. if file_size > log_pos:
  164. state_result.update(_check_state_from_log(pid, log_abspath, log_pos))
  165. break
  166. if not state_result['prompt_message']:
  167. state_result["prompt_message"].append(
  168. "service start state: %s" % state_result["state"])
  169. return state_result
  170. class GunicornLogger(Logger):
  171. """Rewrite gunicorn default logger."""
  172. def __init__(self, cfg):
  173. self.cfg = cfg
  174. self.access_log = setup_logger('gunicorn', 'access', formatter='%(message)s')
  175. self.error_log = setup_logger('gunicorn', 'error', formatter=self.error_fmt)
  176. access_log_path = _get_access_log_path()
  177. error_log_path = _get_error_log_path()
  178. os.chmod(access_log_path, stat.S_IREAD | stat.S_IWRITE)
  179. os.chmod(error_log_path, stat.S_IREAD | stat.S_IWRITE)
  180. super(GunicornLogger, self).__init__(cfg)
  181. def now(self):
  182. """Get log format."""
  183. return time.strftime('[%Y-%m-%d-%H:%M:%S %z]')
  184. def setup(self, cfg):
  185. """Rewrite the setup method of Logger, and we don't need to do anything"""
  186. def start():
  187. """Start web service."""
  188. gunicorn_conf_file = os.path.join(WEB_CONFIG_DIR, "gunicorn_conf.py")
  189. cmd = "gunicorn " \
  190. "-b {host}:{port} {app_module} " \
  191. "-c {conf_file} " \
  192. "--logger-class {logger_class} " \
  193. "--access-logformat {log_format}"\
  194. .format(host=settings.HOST,
  195. port=settings.PORT,
  196. conf_file=gunicorn_conf_file,
  197. app_module=MINDBOARD_APP_MODULE,
  198. logger_class=GUNICORN_LOGGER,
  199. log_format=settings.GUNICORN_ACCESS_FORMAT
  200. )
  201. error_log_abspath = _get_error_log_path()
  202. # Init the logger file
  203. setup_logger('gunicorn', 'error')
  204. log_handler = open(error_log_abspath, 'a+')
  205. pre_log_pos = _get_file_size(error_log_abspath)
  206. # start server
  207. process = subprocess.Popen(
  208. shlex.split(cmd),
  209. shell=False,
  210. # Change stdout to DEVNULL to prevent broken pipe error when creating new processes.
  211. stdin=subprocess.DEVNULL,
  212. stdout=log_handler,
  213. stderr=subprocess.STDOUT
  214. )
  215. # sleep 1 second for gunicorn application to load modules
  216. time.sleep(1)
  217. # check if gunicorn application is running
  218. console = setup_logger('mindinsight', 'console', console=True, logfile=False, formatter='%(message)s')
  219. if process.poll() is not None:
  220. console.error("Start MindInsight failed. See log for details, log path: %s.", error_log_abspath)
  221. sys.exit(1)
  222. else:
  223. state_result = _check_server_start_stat(process.pid, error_log_abspath, pre_log_pos)
  224. # print gunicorn start state to stdout
  225. label = 'Web address:'
  226. format_args = label, settings.HOST, str(settings.PORT), settings.URL_PATH_PREFIX
  227. console.info('%s http://%s:%s%s', *format_args)
  228. for line in state_result["prompt_message"]:
  229. console.info(line)
  230. if state_result["state"] == ServerStateEnum.FAILED.value:
  231. sys.exit(1)
  232. if __name__ == '__main__':
  233. start()