Print dvc version info for debugging (#4536)

* Print dvc version info for debugging

fix #4095
1. seperate info from dvc version.
2. print dvc version info when some unexpected error occured.

* Psutil NoneType error

* Update tests/func/test_version.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Move `package` outside dvc/utils/pkg.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
This commit is contained in:
karajan1001
2020-09-13 04:08:20 +03:00
committed by GitHub
co-authored by Ruslan Kuprieiev
parent cf7e534e2d
commit 8b16af965c
6 changed files with 221 additions and 202 deletions
+3 -138
View File
@@ -1,153 +1,18 @@
import argparse
import itertools
import logging
import os
import pathlib
import platform
import uuid
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.scm.base import SCMError
from dvc.system import System
from dvc.utils import error_link
from dvc.utils.pkg import PKG
from dvc.version import __version__
try:
import psutil
except ImportError:
psutil = None
from dvc.info import get_dvc_info
logger = logging.getLogger(__name__)
class CmdVersion(CmdBaseNoRepo):
def run(self):
from dvc.repo import Repo
package = PKG
if PKG is None:
package = ""
else:
package = f"({PKG})"
info = [
f"DVC version: {__version__} {package}",
"---------------------------------",
f"Platform: Python {platform.python_version()} on "
f"{platform.platform()}",
f"Supports: {self.get_supported_remotes()}",
]
try:
repo = Repo()
root_directory = repo.root_dir
# cache_dir might not exist yet (e.g. after `dvc init`), and we
# can't auto-create it, as it might cause issues if the user
# later decides to enable shared cache mode with
# `dvc config cache.shared group`.
if os.path.exists(repo.cache.local.cache_dir):
info.append(
"Cache types: {}".format(
self.get_linktype_support_info(repo)
)
)
if psutil:
fs_type = self.get_fs_type(repo.cache.local.cache_dir)
info.append(f"Cache directory: {fs_type}")
else:
info.append("Cache types: " + error_link("no-dvc-cache"))
except NotDvcRepoError:
root_directory = os.getcwd()
except SCMError:
root_directory = os.getcwd()
info.append("Repo: dvc, git (broken)")
else:
if psutil:
fs_root = self.get_fs_type(os.path.abspath(root_directory))
info.append(f"Workspace directory: {fs_root}")
info.append("Repo: {}".format(_get_dvc_repo_info(repo)))
logger.info("\n".join(info))
dvc_info = get_dvc_info()
logger.info(dvc_info)
return 0
@staticmethod
def get_fs_type(path):
partition = {
pathlib.Path(part.mountpoint): (part.fstype + " on " + part.device)
for part in psutil.disk_partitions(all=True)
}
path = pathlib.Path(path)
for parent in itertools.chain([path], path.parents):
if parent in partition:
return partition[parent]
return ("unknown", "none")
@staticmethod
def get_linktype_support_info(repo):
links = {
"reflink": (System.reflink, None),
"hardlink": (System.hardlink, System.is_hardlink),
"symlink": (System.symlink, System.is_symlink),
}
fname = "." + str(uuid.uuid4())
src = os.path.join(repo.cache.local.cache_dir, fname)
open(src, "w").close()
dst = os.path.join(repo.root_dir, fname)
cache = []
for name, (link, is_link) in links.items():
try:
link(src, dst)
status = "supported"
if is_link and not is_link(dst):
status = "broken"
os.unlink(dst)
except DvcException:
status = "not supported"
if status == "supported":
cache.append(name)
os.remove(src)
return ", ".join(cache)
@staticmethod
def get_supported_remotes():
from dvc.tree import TREES
supported_remotes = []
for tree_cls in TREES:
if not tree_cls.get_missing_deps():
supported_remotes.append(tree_cls.scheme)
if len(supported_remotes) == len(TREES):
return "All remotes"
if len(supported_remotes) == 1:
return supported_remotes
return ", ".join(supported_remotes)
def _get_dvc_repo_info(repo):
if repo.config.get("core", {}).get("no_scm", False):
return "dvc (no_scm)"
if repo.root_dir != repo.scm.root_dir:
return "dvc (subdir), git"
return "dvc, git"
def add_parser(subparsers, parent_parser):
VERSION_HELP = (
+136
View File
@@ -0,0 +1,136 @@
import itertools
import os
import pathlib
import platform
import uuid
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.repo import Repo
from dvc.scm.base import SCMError
from dvc.system import System
from dvc.tree import TREES
from dvc.utils import error_link
from dvc.utils.pkg import PKG
from dvc.version import __version__
try:
import psutil
except ImportError:
psutil = None
if PKG is None:
package = ""
else:
package = f"({PKG})"
def get_dvc_info():
info = [
f"DVC version: {__version__} {package}",
"---------------------------------",
f"Platform: Python {platform.python_version()} on "
f"{platform.platform()}",
f"Supports: {_get_supported_remotes()}",
]
try:
repo = Repo()
# cache_dir might not exist yet (e.g. after `dvc init`), and we
# can't auto-create it, as it might cause issues if the user
# later decides to enable shared cache mode with
# `dvc config cache.shared group`.
if os.path.exists(repo.cache.local.cache_dir):
info.append(
"Cache types: {}".format(_get_linktype_support_info(repo))
)
if psutil:
fs_type = get_fs_type(repo.cache.local.cache_dir)
info.append(f"Cache directory: {fs_type}")
else:
info.append("Cache types: " + error_link("no-dvc-cache"))
except NotDvcRepoError:
pass
except SCMError:
info.append("Repo: dvc, git (broken)")
else:
root_directory = repo.root_dir
if psutil:
fs_root = get_fs_type(os.path.abspath(root_directory))
info.append(f"Workspace directory: {fs_root}")
info.append("Repo: {}".format(_get_dvc_repo_info(repo)))
return "\n".join(info)
def _get_linktype_support_info(repo):
links = {
"reflink": (System.reflink, None),
"hardlink": (System.hardlink, System.is_hardlink),
"symlink": (System.symlink, System.is_symlink),
}
fname = "." + str(uuid.uuid4())
src = os.path.join(repo.cache.local.cache_dir, fname)
open(src, "w").close()
dst = os.path.join(repo.root_dir, fname)
cache = []
for name, (link, is_link) in links.items():
try:
link(src, dst)
status = "supported"
if is_link and not is_link(dst):
status = "broken"
os.unlink(dst)
except DvcException:
status = "not supported"
if status == "supported":
cache.append(name)
os.remove(src)
return ", ".join(cache)
def _get_supported_remotes():
supported_remotes = []
for tree_cls in TREES:
if not tree_cls.get_missing_deps():
supported_remotes.append(tree_cls.scheme)
if len(supported_remotes) == len(TREES):
return "All remotes"
if len(supported_remotes) == 1:
return supported_remotes
return ", ".join(supported_remotes)
def get_fs_type(path):
partition = {
pathlib.Path(part.mountpoint): (part.fstype + " on " + part.device)
for part in psutil.disk_partitions(all=True)
}
path = pathlib.Path(path)
for parent in itertools.chain([path], path.parents):
if parent in partition:
return partition[parent]
return ("unknown", "none")
def _get_dvc_repo_info(self):
if self.config.get("core", {}).get("no_scm", False):
return "dvc (no_scm)"
if self.root_dir != self.scm.root_dir:
return "dvc (subdir), git"
return "dvc, git"
+4
View File
@@ -98,8 +98,12 @@ def main(argv=None): # noqa: C901
extra={"tb_only": True},
)
else:
from dvc.info import get_dvc_info
logger.exception("unexpected error")
logger.info(FOOTER)
dvc_info = get_dvc_info()
logger.info(dvc_info)
ret = 255
try:
+2 -61
View File
@@ -1,72 +1,13 @@
import os
import re
import shutil
import pytest
from dvc.command.version import psutil
from dvc.main import main
@pytest.mark.parametrize("scm_init", [True, False])
def test_info_in_repo(scm_init, tmp_dir, caplog):
tmp_dir.init(scm=scm_init, dvc=True)
# Create `.dvc/cache`, that is needed to check supported link types.
os.mkdir(tmp_dir.dvc.cache.local.cache_dir)
def test_(tmp_dir, dvc, scm, caplog):
assert main(["version"]) == 0
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", caplog.text)
assert re.search(r"Platform: Python \d\.\d+\.\d+ on .*", caplog.text)
assert re.search(r"Supports: .*", caplog.text)
assert re.search(r"Cache types: .*", caplog.text)
if scm_init:
assert "Repo: dvc, git" in caplog.text
else:
assert "Repo: dvc (no_scm)" in caplog.text
def test_info_in_subdir(tmp_dir, scm, caplog):
dvc_subdir = tmp_dir / "subdir"
dvc_subdir.mkdir()
with dvc_subdir.chdir():
dvc_subdir.init(scm=False, dvc=True)
with dvc_subdir.dvc.config.edit() as conf:
del conf["core"]["no_scm"]
assert main(["version"]) == 0
assert "Repo: dvc (subdir), git" in caplog.text
def test_info_in_broken_git_repo(tmp_dir, dvc, scm, caplog):
shutil.rmtree(dvc.scm.dir)
assert main(["version"]) == 0
assert "Repo: dvc, git (broken)" in caplog.text
@pytest.mark.skipif(psutil is None, reason="No psutil.")
def test_fs_info_in_repo(tmp_dir, dvc, caplog):
os.mkdir(dvc.cache.local.cache_dir)
assert main(["version"]) == 0
assert re.search(r"Cache directory: .* on .*", caplog.text)
assert re.search(r"Workspace directory: .* on .*", caplog.text)
def test_info_outside_of_repo(tmp_dir, caplog):
assert main(["version"]) == 0
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", caplog.text)
assert re.search(r"Platform: Python \d\.\d+\.\d+ on .*", caplog.text)
assert re.search(r"Supports: .*", caplog.text)
assert not re.search(r"Cache types: .*", caplog.text)
assert "Repo:" not in caplog.text
@pytest.mark.skipif(psutil is None, reason="No psutil.")
def test_fs_info_outside_of_repo(tmp_dir, caplog):
assert main(["version"]) == 0
assert "Repo: dvc, git" in caplog.text
+2 -3
View File
@@ -6,7 +6,7 @@ import tempfile
import pytest
from dvc.command.version import CmdVersion
from dvc.info import get_fs_type
from dvc.system import System
here = os.path.abspath(os.path.dirname(__file__))
@@ -85,8 +85,7 @@ def test_walk(tmp_path, ssh_connection):
@pytest.mark.skipif(
CmdVersion.get_fs_type(tempfile.gettempdir())[0]
not in ["xfs", "apfs", "btrfs"],
get_fs_type(tempfile.gettempdir())[0] not in ["xfs", "apfs", "btrfs"],
reason="Reflinks only work in specified file systems",
)
def test_reflink(tmp_dir, ssh_connection):
+74
View File
@@ -0,0 +1,74 @@
import os
import re
import shutil
import pytest
from dvc.info import get_dvc_info, psutil
@pytest.mark.parametrize("scm_init", [True, False])
def test_info_in_repo(scm_init, tmp_dir):
tmp_dir.init(scm=scm_init, dvc=True)
# Create `.dvc/cache`, that is needed to check supported link types.
os.mkdir(tmp_dir.dvc.cache.local.cache_dir)
dvc_info = get_dvc_info()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info)
assert re.search(r"Platform: Python \d\.\d+\.\d+ on .*", dvc_info)
assert re.search(r"Supports: .*", dvc_info)
assert re.search(r"Cache types: .*", dvc_info)
if scm_init:
assert "Repo: dvc, git" in dvc_info
else:
assert "Repo: dvc (no_scm)" in dvc_info
def test_info_in_subdir(tmp_dir, scm, caplog):
dvc_subdir = tmp_dir / "subdir"
dvc_subdir.mkdir()
with dvc_subdir.chdir():
dvc_subdir.init(scm=False, dvc=True)
with dvc_subdir.dvc.config.edit() as conf:
del conf["core"]["no_scm"]
dvc_info = get_dvc_info()
assert "Repo: dvc (subdir), git" in dvc_info
def test_info_in_broken_git_repo(tmp_dir, dvc, scm, caplog):
shutil.rmtree(dvc.scm.dir)
dvc_info = get_dvc_info()
assert "Repo: dvc, git (broken)" in dvc_info
@pytest.mark.skipif(psutil is None, reason="No psutil.")
def test_fs_info_in_repo(tmp_dir, dvc, caplog):
os.mkdir(dvc.cache.local.cache_dir)
dvc_info = get_dvc_info()
assert re.search(r"Cache directory: .* on .*", dvc_info)
assert re.search(r"Workspace directory: .* on .*", dvc_info)
def test_info_outside_of_repo(tmp_dir, caplog):
dvc_info = get_dvc_info()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info)
assert re.search(r"Platform: Python \d\.\d+\.\d+ on .*", dvc_info)
assert re.search(r"Supports: .*", dvc_info)
assert not re.search(r"Cache types: .*", dvc_info)
assert "Repo:" not in dvc_info
@pytest.mark.skipif(psutil is None, reason="No psutil.")
def test_fs_info_outside_of_repo(tmp_dir, caplog):
dvc_info = get_dvc_info()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info)
assert re.search(r"Platform: Python \d\.\d+\.\d+ on .*", dvc_info)
assert re.search(r"Supports: .*", dvc_info)