summaryrefslogtreecommitdiff
path: root/buildscripts/resmokelib/testing/symbolizer_service.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildscripts/resmokelib/testing/symbolizer_service.py')
-rw-r--r--buildscripts/resmokelib/testing/symbolizer_service.py294
1 files changed, 0 insertions, 294 deletions
diff --git a/buildscripts/resmokelib/testing/symbolizer_service.py b/buildscripts/resmokelib/testing/symbolizer_service.py
deleted file mode 100644
index 9e78b0dd6b7..00000000000
--- a/buildscripts/resmokelib/testing/symbolizer_service.py
+++ /dev/null
@@ -1,294 +0,0 @@
-"""Symbolize stacktraces inside test logs."""
-from __future__ import annotations
-
-import os
-import subprocess
-import sys
-import time
-from datetime import timedelta
-from threading import Lock
-
-from typing import List, Optional, NamedTuple
-
-from buildscripts.resmokelib import config as _config
-from buildscripts.resmokelib.flags import HANG_ANALYZER_CALLED
-from buildscripts.resmokelib.testing.testcases.interface import TestCase
-
-# This lock prevents different resmoke jobs from symbolizing stacktraces concurrently,
-# which includes downloading the debug symbols, that can be reused by other resmoke jobs
-_lock = Lock()
-
-STACKTRACE_FILE_EXTENSION = ".stacktrace"
-SYMBOLIZE_RETRY_TIMEOUT_SECS = timedelta(minutes=4).total_seconds()
-
-
-class ResmokeSymbolizerConfig(NamedTuple):
- """
- Resmoke symbolizer config.
-
- * evg_task_id: evergreen task ID resmoke runs on
- * client_id: symbolizer client ID
- * client_secret: symbolizer client secret
- """
-
- evg_task_id: Optional[str]
- client_id: Optional[str]
- client_secret: Optional[str]
-
- @classmethod
- def from_resmoke_config(cls) -> ResmokeSymbolizerConfig:
- """
- Make resmoke symbolizer config from a global resmoke config.
-
- :return: resmoke symbolizer config
- """
- return cls(
- evg_task_id=_config.EVERGREEN_TASK_ID,
- client_id=_config.SYMBOLIZER_CLIENT_ID,
- client_secret=_config.SYMBOLIZER_CLIENT_SECRET,
- )
-
- @staticmethod
- def is_windows() -> bool:
- """
- Whether we are on Windows.
-
- :return: True if on Windows
- """
- return sys.platform == "win32" or sys.platform == "cygwin"
-
- @staticmethod
- def is_macos() -> bool:
- """
- Whether we are on MacOS.
-
- :return: True if on MacOS.
- """
- return sys.platform == "darwin"
-
-
-class ResmokeSymbolizer:
- """Symbolize stacktraces inside test logs."""
-
- def __init__(self, config: Optional[ResmokeSymbolizerConfig] = None,
- symbolizer_service: Optional[SymbolizerService] = None,
- file_service: Optional[FileService] = None):
- """Initialize instance."""
-
- self.config = config if config is not None else ResmokeSymbolizerConfig.from_resmoke_config(
- )
- self.symbolizer_service = symbolizer_service if symbolizer_service is not None else SymbolizerService(
- )
- self.file_service = file_service if file_service is not None else FileService()
-
- def symbolize_test_logs(self, test: TestCase,
- symbolize_retry_timeout: float = SYMBOLIZE_RETRY_TIMEOUT_SECS) -> None:
- """
- Perform all necessary actions to symbolize and write output to test logs.
-
- :param test: resmoke test case
- :param symbolize_retry_timeout: the timeout for symbolizer retries
- """
- if not self.should_symbolize(test):
- return
-
- dbpath = self.get_stacktrace_dir(test)
- if dbpath is None:
- return
-
- test.logger.info("Looking for stacktrace files in '%s'", dbpath)
- files = self.collect_stacktrace_files(dbpath)
- if not files:
- test.logger.info("No failure logs/stacktrace files found, skipping symbolization")
- return
-
- with _lock:
- test.logger.info("Found stacktrace files. \nBEGIN Symbolization")
- test.logger.info("Stacktrace files: %s", files)
-
- start_time = time.perf_counter()
- for file_path in files:
- test.logger.info("Working on: %s", file_path)
- symbolizer_script_timeout = int(symbolize_retry_timeout -
- (time.perf_counter() - start_time))
- symbolized_out = self.symbolizer_service.run_symbolizer_script(
- file_path, symbolizer_script_timeout)
- test.logger.info(symbolized_out)
- if time.perf_counter() - start_time > symbolize_retry_timeout:
- break
-
- # To avoid performing the same actions on these files again, we remove them
- self.file_service.remove_all(files)
-
- test.logger.info("\nEND Symbolization \nSymbolization process completed. ")
-
- def should_symbolize(self, test: TestCase) -> bool:
- """
- Check whether we should perform symbolization process.
-
- :param test: resmoke test case
- :return: whether we should symbolize
- """
- if self.config.evg_task_id is None:
- test.logger.info("Not running in Evergreen, skipping symbolization")
- return False
-
- if self.config.client_id is None or self.config.client_secret is None:
- test.logger.info("Symbolizer client secret and/or client ID are absent,"
- " skipping symbolization")
- return False
-
- if self.config.is_windows():
- test.logger.info("Running on Windows, skipping symbolization")
- return False
-
- if self.config.is_macos():
- test.logger.info("Running on MacOS, skipping symbolization")
- return False
-
- if HANG_ANALYZER_CALLED.is_set():
- test.logger.info(
- "Hang analyzer has been called, skipping symbolization to meet timeout constraints."
- )
- return False
-
- return True
-
- def get_stacktrace_dir(self, test: TestCase) -> Optional[str]:
- """
- Get dbpath from test case.
-
- :param test: resmoke test case
- :return: dbpath or None
- """
- if not hasattr(test, "fixture") or test.fixture is None:
- test.logger.info("Test fixture is not available, could not get dbpath")
- return None
-
- dbpath = test.fixture.get_dbpath_prefix()
- if not self.file_service.check_path_exists(dbpath):
- test.logger.info("dbpath '%s' directory not found", dbpath)
- return None
-
- return dbpath
-
- def collect_stacktrace_files(self, dir_path: str) -> List[str]:
- """
- Collect all stacktrace files which are not empty and return their full paths.
-
- :param dir_path: directory to look into
- :return: list of stacktrace files paths
- """
-
- files = self.file_service.find_all_children_recursively(dir_path)
- files = self.file_service.filter_by_extension(files, STACKTRACE_FILE_EXTENSION)
- self.file_service.remove_empty(files)
- files = self.file_service.filter_out_non_files(files)
-
- return files
-
-
-class FileService:
- """A service for working with files."""
-
- @staticmethod
- def find_all_children_recursively(dir_path: str) -> List[str]:
- """
- Find all children files in directory recursively.
-
- :param dir_path: directory path
- :return: list of all children files
- """
- children_in_dir = []
- for parent, _, children in os.walk(dir_path):
- children_in_dir.extend(os.path.join(parent, child) for child in children)
- return children_in_dir
-
- @staticmethod
- def filter_by_extension(files: List[str], extension: str) -> List[str]:
- """
- Filter files by extension.
-
- :param files: list of file paths
- :param extension: file extension
- :return: filtered list of file paths
- """
- return [f for f in files if f.endswith(extension)]
-
- @staticmethod
- def filter_out_non_files(files: List[str]) -> List[str]:
- """
- Filter out non files.
-
- :param files: list of paths
- :return: filtered list of file paths
- """
- return [f for f in files if os.path.isfile(f)]
-
- @staticmethod
- def remove_empty(files: List[str]) -> None:
- """
- Delete files that are empty.
-
- :param files: list of paths
- """
- for file in [f for f in files if os.stat(f).st_size == 0]:
- os.remove(file)
-
- @staticmethod
- def remove_all(files: List[str]) -> None:
- """
- Delete all files.
-
- :param files: list of paths
- """
- for file in files:
- os.remove(file)
-
- @staticmethod
- def check_path_exists(path: str) -> bool:
- """
- Check that file or directory exists.
-
- :param path: file or directory path
- :return: whether path exists
- """
- return os.path.exists(path)
-
-
-class SymbolizerService:
- """Wrapper around symbolizer script."""
-
- @staticmethod
- def run_symbolizer_script(full_file_path: str, retry_timeout_secs: int) -> str:
- """
- Symbolize given file and return symbolized output as string.
-
- :param full_file_path: stacktrace file path
- :param retry_timeout_secs: the timeout for symbolizer to retry
- :return: symbolized output as string
- """
-
- symbolizer_args = [
- "db-contrib-tool",
- "symbolize",
- "--client-secret",
- _config.SYMBOLIZER_CLIENT_SECRET,
- "--client-id",
- _config.SYMBOLIZER_CLIENT_ID,
- "--total-seconds-for-retries",
- str(retry_timeout_secs),
- ]
-
- with open(full_file_path) as file_obj:
- symbolizer_process = subprocess.Popen(args=symbolizer_args, close_fds=True,
- stdin=file_obj, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
-
- try:
- output, _ = symbolizer_process.communicate(timeout=retry_timeout_secs)
- except subprocess.TimeoutExpired:
- symbolizer_process.kill()
- output, _ = symbolizer_process.communicate()
-
- return output.strip().decode()