mirror of
https://github.com/deepfakes/faceswap
synced 2025-06-07 10:43:27 -04:00
* Remove tensorflow_probability requirement * setup.py - fix progress bars * requirements.txt: Remove pre python 3.9 packages * update apple requirements.txt * update INSTALL.md * Remove python<3.9 code * setup.py - fix Windows Installer * typing: python3.9 compliant * Update pytest and readthedocs python versions * typing fixes * Python Version updates - Reduce max version to 3.10 - Default to 3.10 in installers - Remove incompatible 3.11 tests * Update dependencies * Downgrade imageio dep for Windows * typing: merge optional unions and fixes * Updates - min python version 3.10 - typing to python 3.10 spec - remove pre-tf2.10 code - Add conda tests * train: re-enable optimizer saving * Update dockerfiles * Update setup.py - Apple Conda deps to setup.py - Better Cuda + dependency handling * bugfix: Patch logging to prevent Autograph errors * Update dockerfiles * Setup.py - Setup.py - stdout to utf-8 * Add more OSes to github Actions * suppress mac-os end to end test
436 lines
16 KiB
Python
436 lines
16 KiB
Python
#!/usr/bin python3
|
|
""" Obtain information about the running system, environment and GPU. """
|
|
|
|
import json
|
|
import locale
|
|
import os
|
|
import platform
|
|
import sys
|
|
|
|
from subprocess import PIPE, Popen
|
|
|
|
import psutil
|
|
|
|
from lib.gpu_stats import GPUStats, GPUInfo
|
|
from lib.utils import get_backend
|
|
from setup import CudaCheck
|
|
|
|
|
|
class _SysInfo(): # pylint:disable=too-few-public-methods
|
|
""" Obtain information about the System, Python and GPU """
|
|
def __init__(self) -> None:
|
|
self._state_file = _State().state_file
|
|
self._configs = _Configs().configs
|
|
self._system = {"platform": platform.platform(),
|
|
"system": platform.system().lower(),
|
|
"machine": platform.machine(),
|
|
"release": platform.release(),
|
|
"processor": platform.processor(),
|
|
"cpu_count": os.cpu_count()}
|
|
self._python = {"implementation": platform.python_implementation(),
|
|
"version": platform.python_version()}
|
|
self._gpu = self._get_gpu_info()
|
|
self._cuda_check = CudaCheck()
|
|
|
|
@property
|
|
def _encoding(self) -> str:
|
|
""" str: The system preferred encoding """
|
|
return locale.getpreferredencoding()
|
|
|
|
@property
|
|
def _is_conda(self) -> bool:
|
|
""" bool: `True` if running in a Conda environment otherwise ``False``. """
|
|
return ("conda" in sys.version.lower() or
|
|
os.path.exists(os.path.join(sys.prefix, 'conda-meta')))
|
|
|
|
@property
|
|
def _is_linux(self) -> bool:
|
|
""" bool: `True` if running on a Linux system otherwise ``False``. """
|
|
return self._system["system"] == "linux"
|
|
|
|
@property
|
|
def _is_macos(self) -> bool:
|
|
""" bool: `True` if running on a macOS system otherwise ``False``. """
|
|
return self._system["system"] == "darwin"
|
|
|
|
@property
|
|
def _is_windows(self) -> bool:
|
|
""" bool: `True` if running on a Windows system otherwise ``False``. """
|
|
return self._system["system"] == "windows"
|
|
|
|
@property
|
|
def _is_virtual_env(self) -> bool:
|
|
""" bool: `True` if running inside a virtual environment otherwise ``False``. """
|
|
if not self._is_conda:
|
|
retval = (hasattr(sys, "real_prefix") or
|
|
(hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix))
|
|
else:
|
|
prefix = os.path.dirname(sys.prefix)
|
|
retval = os.path.basename(prefix) == "envs"
|
|
return retval
|
|
|
|
@property
|
|
def _ram_free(self) -> int:
|
|
""" int: The amount of free RAM in bytes. """
|
|
return psutil.virtual_memory().free
|
|
|
|
@property
|
|
def _ram_total(self) -> int:
|
|
""" int: The amount of total RAM in bytes. """
|
|
return psutil.virtual_memory().total
|
|
|
|
@property
|
|
def _ram_available(self) -> int:
|
|
""" int: The amount of available RAM in bytes. """
|
|
return psutil.virtual_memory().available
|
|
|
|
@property
|
|
def _ram_used(self) -> int:
|
|
""" int: The amount of used RAM in bytes. """
|
|
return psutil.virtual_memory().used
|
|
|
|
@property
|
|
def _fs_command(self) -> str:
|
|
""" str: The command line command used to execute faceswap. """
|
|
return " ".join(sys.argv)
|
|
|
|
@property
|
|
def _installed_pip(self) -> str:
|
|
""" str: The list of installed pip packages within Faceswap's scope. """
|
|
with Popen(f"{sys.executable} -m pip freeze", shell=True, stdout=PIPE) as pip:
|
|
installed = pip.communicate()[0].decode(self._encoding, errors="replace").splitlines()
|
|
return "\n".join(installed)
|
|
|
|
@property
|
|
def _installed_conda(self) -> str:
|
|
""" str: The list of installed Conda packages within Faceswap's scope. """
|
|
if not self._is_conda:
|
|
return ""
|
|
with Popen("conda list", shell=True, stdout=PIPE, stderr=PIPE) as conda:
|
|
stdout, stderr = conda.communicate()
|
|
if stderr:
|
|
return "Could not get package list"
|
|
installed = stdout.decode(self._encoding, errors="replace").splitlines()
|
|
return "\n".join(installed)
|
|
|
|
@property
|
|
def _conda_version(self) -> str:
|
|
""" str: The installed version of Conda, or `N/A` if Conda is not installed. """
|
|
if not self._is_conda:
|
|
return "N/A"
|
|
with Popen("conda --version", shell=True, stdout=PIPE, stderr=PIPE) as conda:
|
|
stdout, stderr = conda.communicate()
|
|
if stderr:
|
|
return "Conda is used, but version not found"
|
|
version = stdout.decode(self._encoding, errors="replace").splitlines()
|
|
return "\n".join(version)
|
|
|
|
@property
|
|
def _git_branch(self) -> str:
|
|
""" str: The git branch that is currently being used to execute Faceswap. """
|
|
with Popen("git status", shell=True, stdout=PIPE, stderr=PIPE) as git:
|
|
stdout, stderr = git.communicate()
|
|
if stderr:
|
|
return "Not Found"
|
|
branch = stdout.decode(self._encoding,
|
|
errors="replace").splitlines()[0].replace("On branch ", "")
|
|
return branch
|
|
|
|
@property
|
|
def _git_commits(self) -> str:
|
|
""" str: The last 5 git commits for the currently running Faceswap. """
|
|
with Popen("git log --pretty=oneline --abbrev-commit -n 5",
|
|
shell=True, stdout=PIPE, stderr=PIPE) as git:
|
|
stdout, stderr = git.communicate()
|
|
if stderr:
|
|
return "Not Found"
|
|
commits = stdout.decode(self._encoding, errors="replace").splitlines()
|
|
return ". ".join(commits)
|
|
|
|
@property
|
|
def _cuda_version(self) -> str:
|
|
""" str: The installed CUDA version. """
|
|
# TODO Handle multiple CUDA installs
|
|
retval = self._cuda_check.cuda_version
|
|
if not retval:
|
|
retval = "No global version found"
|
|
if self._is_conda:
|
|
retval += ". Check Conda packages for Conda Cuda"
|
|
return retval
|
|
|
|
@property
|
|
def _cudnn_version(self) -> str:
|
|
""" str: The installed cuDNN version. """
|
|
retval = self._cuda_check.cudnn_version
|
|
if not retval:
|
|
retval = "No global version found"
|
|
if self._is_conda:
|
|
retval += ". Check Conda packages for Conda cuDNN"
|
|
return retval
|
|
|
|
def _get_gpu_info(self) -> GPUInfo:
|
|
""" Obtain GPU Stats. If an error is raised, swallow the error, and add to GPUInfo output
|
|
|
|
Returns
|
|
-------
|
|
:class:`~lib.gpu_stats.GPUInfo`
|
|
The information on connected GPUs
|
|
"""
|
|
try:
|
|
retval = GPUStats(log=False).sys_info
|
|
except Exception as err: # pylint:disable=broad-except
|
|
err_string = f"{type(err)}: {err}"
|
|
retval = GPUInfo(vram=[],
|
|
vram_free=[],
|
|
driver="N/A",
|
|
devices=[f"Error obtaining GPU Stats: '{err_string}'"],
|
|
devices_active=[])
|
|
return retval
|
|
|
|
def full_info(self) -> str:
|
|
""" Obtain extensive system information stats, formatted into a human readable format.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The system information for the currently running system, formatted for output to
|
|
console or a log file.
|
|
"""
|
|
retval = "\n============ System Information ============\n"
|
|
sys_info = {"backend": get_backend(),
|
|
"os_platform": self._system["platform"],
|
|
"os_machine": self._system["machine"],
|
|
"os_release": self._system["release"],
|
|
"py_conda_version": self._conda_version,
|
|
"py_implementation": self._python["implementation"],
|
|
"py_version": self._python["version"],
|
|
"py_command": self._fs_command,
|
|
"py_virtual_env": self._is_virtual_env,
|
|
"sys_cores": self._system["cpu_count"],
|
|
"sys_processor": self._system["processor"],
|
|
"sys_ram": self._format_ram(),
|
|
"encoding": self._encoding,
|
|
"git_branch": self._git_branch,
|
|
"git_commits": self._git_commits,
|
|
"gpu_cuda": self._cuda_version,
|
|
"gpu_cudnn": self._cudnn_version,
|
|
"gpu_driver": self._gpu.driver,
|
|
"gpu_devices": ", ".join([f"GPU_{idx}: {device}"
|
|
for idx, device in enumerate(self._gpu.devices)]),
|
|
"gpu_vram": ", ".join(
|
|
f"GPU_{idx}: {int(vram)}MB ({int(vram_free)}MB free)"
|
|
for idx, (vram, vram_free) in enumerate(zip(self._gpu.vram,
|
|
self._gpu.vram_free))),
|
|
"gpu_devices_active": ", ".join([f"GPU_{idx}"
|
|
for idx in self._gpu.devices_active])}
|
|
for key in sorted(sys_info.keys()):
|
|
retval += (f"{key + ':':<20} {sys_info[key]}\n")
|
|
retval += "\n=============== Pip Packages ===============\n"
|
|
retval += self._installed_pip
|
|
if self._is_conda:
|
|
retval += "\n\n============== Conda Packages ==============\n"
|
|
retval += self._installed_conda
|
|
retval += self._state_file
|
|
retval += "\n\n================= Configs =================="
|
|
retval += self._configs
|
|
return retval
|
|
|
|
def _format_ram(self) -> str:
|
|
""" Format the RAM stats into Megabytes to make it more readable.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The total, available, used and free RAM displayed in Megabytes
|
|
"""
|
|
retval = []
|
|
for name in ("total", "available", "used", "free"):
|
|
value = getattr(self, f"_ram_{name}")
|
|
value = int(value / (1024 * 1024))
|
|
retval.append(f"{name.capitalize()}: {value}MB")
|
|
return ", ".join(retval)
|
|
|
|
|
|
def get_sysinfo() -> str:
|
|
""" Obtain extensive system information stats, formatted into a human readable format.
|
|
If an error occurs obtaining the system information, then the error message is returned
|
|
instead.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The system information for the currently running system, formatted for output to
|
|
console or a log file.
|
|
"""
|
|
try:
|
|
retval = _SysInfo().full_info()
|
|
except Exception as err: # pylint: disable=broad-except
|
|
retval = f"Exception occured trying to retrieve sysinfo: {str(err)}"
|
|
raise
|
|
return retval
|
|
|
|
|
|
class _Configs(): # pylint:disable=too-few-public-methods
|
|
""" Parses the config files in /faceswap/config and outputs the information stored within them
|
|
in a human readable format. """
|
|
|
|
def __init__(self) -> None:
|
|
self.config_dir = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "config")
|
|
self.configs = self._get_configs()
|
|
|
|
def _get_configs(self) -> str:
|
|
""" Obtain the formatted configurations from the config folder.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The current configuration in the config files formatted in a human readable format
|
|
"""
|
|
try:
|
|
config_files = [os.path.join(self.config_dir, cfile)
|
|
for cfile in os.listdir(self.config_dir)
|
|
if os.path.basename(cfile) == ".faceswap"
|
|
or os.path.splitext(cfile)[1] == ".ini"]
|
|
return self._parse_configs(config_files)
|
|
except FileNotFoundError:
|
|
return ""
|
|
|
|
def _parse_configs(self, config_files: list[str]) -> str:
|
|
""" Parse the given list of config files into a human readable format.
|
|
|
|
Parameters
|
|
----------
|
|
config_files: list
|
|
A list of paths to the faceswap config files
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The current configuration in the config files formatted in a human readable format
|
|
"""
|
|
formatted = ""
|
|
for cfile in config_files:
|
|
fname = os.path.basename(cfile)
|
|
ext = os.path.splitext(cfile)[1]
|
|
formatted += f"\n--------- {fname} ---------\n"
|
|
if ext == ".ini":
|
|
formatted += self._parse_ini(cfile)
|
|
elif fname == ".faceswap":
|
|
formatted += self._parse_json(cfile)
|
|
return formatted
|
|
|
|
def _parse_ini(self, config_file: str) -> str:
|
|
""" Parse an ``.ini`` formatted config file into a human readable format.
|
|
|
|
Parameters
|
|
----------
|
|
config_file: str
|
|
The path to the config.ini file
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The current configuration in the config file formatted in a human readable format
|
|
"""
|
|
formatted = ""
|
|
with open(config_file, "r", encoding="utf-8", errors="replace") as cfile:
|
|
for line in cfile.readlines():
|
|
line = line.strip()
|
|
if line.startswith("#") or not line:
|
|
continue
|
|
item = line.split("=")
|
|
if len(item) == 1:
|
|
formatted += f"\n{item[0].strip()}\n"
|
|
else:
|
|
formatted += self._format_text(item[0], item[1])
|
|
return formatted
|
|
|
|
def _parse_json(self, config_file: str) -> str:
|
|
""" Parse an ``.json`` formatted config file into a formatted string.
|
|
|
|
Parameters
|
|
----------
|
|
config_file: str
|
|
The path to the config.json file
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
The current configuration in the config file formatted as a python dictionary
|
|
"""
|
|
formatted: str = ""
|
|
with open(config_file, "r", encoding="utf-8", errors="replace") as cfile:
|
|
conf_dict = json.load(cfile)
|
|
for key in sorted(conf_dict.keys()):
|
|
formatted += self._format_text(key, conf_dict[key])
|
|
return formatted
|
|
|
|
@staticmethod
|
|
def _format_text(key: str, value: str) -> str:
|
|
"""Format a key value pair into a consistently spaced string output for display.
|
|
|
|
Parameters
|
|
----------
|
|
key: str
|
|
The label for this display item
|
|
value: str
|
|
The value for this display item
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The formatted key value pair for display
|
|
"""
|
|
return f"{key.strip() + ':':<25} {value.strip()}\n"
|
|
|
|
|
|
class _State(): # pylint:disable=too-few-public-methods
|
|
""" Parses the state file in the current model directory, if the model is training, and
|
|
formats the content into a human readable format. """
|
|
def __init__(self) -> None:
|
|
self._model_dir = self._get_arg("-m", "--model-dir")
|
|
self._trainer = self._get_arg("-t", "--trainer")
|
|
self.state_file = self._get_state_file()
|
|
|
|
@property
|
|
def _is_training(self) -> bool:
|
|
""" bool: ``True`` if this function has been called during a training session
|
|
otherwise ``False``. """
|
|
return len(sys.argv) > 1 and sys.argv[1].lower() == "train"
|
|
|
|
@staticmethod
|
|
def _get_arg(*args: str) -> str | None:
|
|
""" Obtain the value for a given command line option from sys.argv.
|
|
|
|
Returns
|
|
-------
|
|
str or ``None``
|
|
The value of the given command line option, if it exists, otherwise ``None``
|
|
"""
|
|
cmd = sys.argv
|
|
for opt in args:
|
|
if opt in cmd:
|
|
return cmd[cmd.index(opt) + 1]
|
|
return None
|
|
|
|
def _get_state_file(self) -> str:
|
|
""" Parses the model's state file and compiles the contents into a human readable string.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The state file formatted into a human readable format
|
|
"""
|
|
if not self._is_training or self._model_dir is None or self._trainer is None:
|
|
return ""
|
|
fname = os.path.join(self._model_dir, f"{self._trainer}_state.json")
|
|
if not os.path.isfile(fname):
|
|
return ""
|
|
|
|
retval = "\n\n=============== State File =================\n"
|
|
with open(fname, "r", encoding="utf-8", errors="replace") as sfile:
|
|
retval += sfile.read()
|
|
return retval
|
|
|
|
|
|
sysinfo = get_sysinfo() # pylint: disable=invalid-name
|