1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-07 10:43:27 -04:00
faceswap/lib/sysinfo.py
torzdf 6a3b674bef
Rebase code (#1326)
* 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
2023-06-27 11:27:47 +01:00

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