| # Copyright 2023 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """An implementation of the RemoteexecConfig proto interface.""" |
| |
| import datetime |
| import getpass |
| import json |
| import logging |
| import os |
| from pathlib import Path |
| import shlex |
| from typing import List |
| |
| from chromite.lib import cros_build_lib |
| from chromite.lib import osutils |
| from chromite.utils import hostname_util |
| |
| |
| # Synced with "log_dir" and "proxy_log_dir" in ./sdk/reclient_cfgs/reproxy.cfg |
| _REMOTEEXEC_LOG_BASE_DIR = Path("/tmp") |
| _REMOTEEXEC_LOG_DIRNAME_PATTERN = "reclient-*" |
| |
| _REMOTEEXEC_LOG_FILE_PATTERN = ( |
| "*.INFO.*", |
| "reproxy_*.INFO", |
| "*.rrpl", |
| ) |
| |
| |
| class LogsArchiver: |
| """Manages archiving remoteexec log files.""" |
| |
| def __init__(self, build_log_dir: Path, dest_dir: Path) -> None: |
| """Initializes the archiver. |
| |
| Args: |
| build_log_dir: path to the build directory. |
| dest_dir: path to the target directory to which logs are written. |
| """ |
| self.remoteexec_log_dir_for_testing = None |
| self._dest_base_dir = dest_dir |
| self._build_log_dir = build_log_dir |
| osutils.SafeMakedirs(self._dest_base_dir) |
| |
| def archive(self) -> List[str]: |
| """Archives all log files. |
| |
| Returns: |
| A list of compressed log file paths. |
| """ |
| return self.archive_remoteexec_logs() + self.archive_ninja_log() |
| |
| def archive_remoteexec_logs(self) -> List[str]: |
| """Archives remoteexec log files. |
| |
| Returns: |
| A list of compressed log file paths. |
| """ |
| |
| log_dir_pattern = ( |
| self.remoteexec_log_dir_for_testing or _REMOTEEXEC_LOG_BASE_DIR |
| ) |
| log_dirs = sorted(log_dir_pattern.glob(_REMOTEEXEC_LOG_DIRNAME_PATTERN)) |
| |
| archived_log_files = [] |
| for log_dir in log_dirs: |
| logging.info("Processing log dir: %s", log_dir) |
| |
| for pattern in _REMOTEEXEC_LOG_FILE_PATTERN: |
| archived_log_files += self._archive_files(log_dir, pattern) |
| |
| return archived_log_files |
| |
| def _archive_files(self, directory: Path, pattern: str) -> List[Path]: |
| """Archives files matched with pattern, with gzip'ing. |
| |
| Args: |
| directory: directory of the files. |
| pattern: matching path pattern (eg. "*.INFO"). |
| |
| Returns: |
| A list of compressed log file paths. |
| """ |
| # Find files matched with the |pattern| in |directory|. Sort for |
| # stabilization. |
| paths = sorted(directory.glob(pattern)) |
| if not paths: |
| logging.warning("No glob files matched with %s", pattern) |
| |
| result = [] |
| for path in paths: |
| archived_filename = f"{path.name}.gz" |
| log_label = f"{directory.name}/{archived_filename}" |
| logging.info("Compressing %s", log_label) |
| |
| dest_filepath = ( |
| self._dest_base_dir / directory.name / archived_filename |
| ) |
| osutils.SafeMakedirs(dest_filepath.parent) |
| cros_build_lib.CompressFile(path, dest_filepath) |
| |
| osutils.SafeUnlink(path) |
| |
| result.append(log_label) |
| |
| return result |
| |
| def archive_ninja_log(self) -> List[str]: |
| """Archives ninja_log file and its related metadata. |
| |
| This archives the ninja_log file generated by ninja to build Chrome. |
| Also, it appends some related metadata at the end of the file following |
| '# end of ninja log' marker. |
| |
| Returns: |
| The list of the archived files. |
| """ |
| ninja_log_path = os.path.join(self._build_log_dir, "ninja_log") |
| if not os.path.exists(ninja_log_path): |
| logging.warning("ninja_log is not found: %s", ninja_log_path) |
| return [] |
| ninja_log_content = osutils.ReadFile(ninja_log_path) |
| |
| try: |
| st = os.stat(ninja_log_path) |
| ninja_log_mtime = datetime.datetime.fromtimestamp(st.st_mtime) |
| except OSError: |
| logging.exception("Failed to get timestamp: %s", ninja_log_path) |
| return [] |
| |
| ninja_log_info = self._build_ninja_info() |
| |
| # Append metadata at the end of the log content. |
| ninja_log_content += "# end of ninja log\n" + json.dumps(ninja_log_info) |
| |
| # Aligned with goma_utils in chromium bot. |
| pid = os.getpid() |
| |
| ninja_log_path = self._build_log_dir / ( |
| "ninja_log.%s.%s.%s.%d" |
| % ( |
| getpass.getuser(), |
| hostname_util.get_host_name(), |
| ninja_log_mtime.strftime("%Y%m%d-%H%M%S"), |
| pid, |
| ) |
| ) |
| osutils.WriteFile(ninja_log_path, ninja_log_content) |
| |
| archived_filename = os.path.basename(ninja_log_path) + ".gz" |
| archived_path = self._dest_base_dir / archived_filename |
| cros_build_lib.CompressFile(ninja_log_path, archived_path) |
| |
| return [archived_filename] |
| |
| def _build_ninja_info(self): |
| """Reads metadata for the ninja run. |
| |
| Each metadata should be written into a dedicated file in the log |
| directory. Read the info, and build the dict containing metadata. |
| |
| Returns: |
| A dict of the metadata. |
| """ |
| |
| info = {"platform": "chromeos"} |
| |
| command_path = self._build_log_dir / "ninja_command" |
| if os.path.exists(command_path): |
| info["cmdline"] = shlex.split( |
| osutils.ReadFile(command_path).strip() |
| ) |
| |
| cwd_path = self._build_log_dir / "ninja_cwd" |
| if os.path.exists(cwd_path): |
| info["cwd"] = osutils.ReadFile(cwd_path).strip() |
| |
| exit_path = self._build_log_dir / "ninja_exit" |
| if os.path.exists(exit_path): |
| info["exit"] = int(osutils.ReadFile(exit_path).strip()) |
| |
| env_path = self._build_log_dir / "ninja_env" |
| if os.path.exists(env_path): |
| # env is null-byte separated, and has a trailing null byte. |
| content = osutils.ReadFile(env_path).rstrip("\0") |
| info["env"] = dict( |
| line.split("=", 1) for line in content.split("\0") |
| ) |
| |
| return info |