experiments: include experiment timestamps in dvc exp show output (#4389)

* experiments: include experiment commit times in show

* --no-timestamp can be used to hide timestamps

* update tests

* clean up kwargs
This commit is contained in:
Peter Rowlands (변기호)
2020-08-17 17:35:43 +03:00
committed by GitHub
parent c4bc4c83e8
commit 70ddb7c931
3 changed files with 110 additions and 46 deletions
+60 -46
View File
@@ -90,12 +90,41 @@ def _collect_names(all_experiments, **kwargs):
def _collect_rows(
base_rev, experiments, metric_names, param_names, precision=None
base_rev,
experiments,
metric_names,
param_names,
precision=DEFAULT_PRECISION,
no_timestamp=False,
):
from flatten_json import flatten
for i, (rev, exp) in enumerate(experiments.items()):
row = []
style = None
queued = "*" if exp.get("queued", False) else ""
if no_timestamp:
timestamp = ""
else:
commit_time = exp.get("timestamp")
timestamp = f" ({commit_time})" if commit_time is not None else ""
if precision is None:
precision = DEFAULT_PRECISION
if rev == "baseline":
row.append(f"{base_rev}{timestamp}")
style = "bold"
elif i < len(experiments) - 1:
row.append(f"├── {queued}{rev[:7]}{timestamp}")
else:
row.append(f"└── {queued}{rev[:7]}{timestamp}")
_extend_row(
row, metric_names, exp.get("metrics", {}).items(), precision
)
_extend_row(row, param_names, exp.get("params", {}).items(), precision)
yield row, style
def _extend_row(row, names, items, precision):
from flatten_json import flatten
def _round(val):
if isinstance(val, float):
@@ -103,40 +132,22 @@ def _collect_rows(
return val
def _extend(row, names, items):
if not items:
row.extend(["-"] * len(names))
return
if not items:
row.extend(["-"] * len(names))
return
for fname, item in items:
if isinstance(item, dict):
item = flatten(item, ".")
else:
item = {fname: item}
for name in names:
if name in item:
value = item[name]
text = str(_round(value)) if value is not None else "-"
row.append(text)
else:
row.append("-")
for i, (rev, exp) in enumerate(experiments.items()):
row = []
style = None
queued = "*" if exp.get("queued", False) else ""
if rev == "baseline":
row.append(f"{base_rev}")
style = "bold"
elif i < len(experiments) - 1:
row.append(f"├── {queued}{rev[:7]}")
for fname, item in items:
if isinstance(item, dict):
item = flatten(item, ".")
else:
row.append(f"└── {queued}{rev[:7]}")
_extend(row, metric_names, exp.get("metrics", {}).items())
_extend(row, param_names, exp.get("params", {}).items())
yield row, style
item = {fname: item}
for name in names:
if name in item:
value = item[name]
text = str(_round(value)) if value is not None else "-"
row.append(text)
else:
row.append("-")
def _parse_list(param_list):
@@ -150,15 +161,15 @@ def _parse_list(param_list):
return ret
def _show_experiments(all_experiments, console, precision=None, **kwargs):
def _show_experiments(all_experiments, console, **kwargs):
from rich.table import Table
from dvc.scm.git import Git
include_metrics = _parse_list(kwargs.get("include_metrics", []))
exclude_metrics = _parse_list(kwargs.get("exclude_metrics", []))
include_params = _parse_list(kwargs.get("include_params", []))
exclude_params = _parse_list(kwargs.get("exclude_params", []))
include_metrics = _parse_list(kwargs.pop("include_metrics", []))
exclude_metrics = _parse_list(kwargs.pop("exclude_metrics", []))
include_params = _parse_list(kwargs.pop("include_params", []))
exclude_params = _parse_list(kwargs.pop("exclude_params", []))
metric_names, param_names = _collect_names(
all_experiments,
@@ -180,11 +191,7 @@ def _show_experiments(all_experiments, console, precision=None, **kwargs):
base_rev = base_rev[:7]
for row, _, in _collect_rows(
base_rev,
experiments,
metric_names,
param_names,
precision=precision,
base_rev, experiments, metric_names, param_names, **kwargs,
):
table.add_row(*row)
@@ -223,6 +230,7 @@ class CmdExperimentsShow(CmdBase):
exclude_metrics=self.args.exclude_metrics,
include_params=self.args.include_params,
exclude_params=self.args.exclude_params,
no_timestamp=self.args.no_timestamp,
)
if not self.args.no_pager:
@@ -400,6 +408,12 @@ def add_parser(subparsers, parent_parser):
help="Exclude the specified params from output table.",
metavar="<params_list>",
)
experiments_show_parser.add_argument(
"--no-timestamp",
action="store_true",
default=False,
help="Do not show experiment timestamps.",
)
experiments_show_parser.set_defaults(func=CmdExperimentsShow)
EXPERIMENTS_CHECKOUT_HELP = "Checkout experiments."
+7
View File
@@ -1,6 +1,7 @@
import logging
import re
from collections import OrderedDict, defaultdict
from datetime import datetime
from dvc.repo import locked
from dvc.repo.metrics.show import _collect_metrics, _read_metrics
@@ -15,6 +16,12 @@ EXP_RE = re.compile(r"(?P<rev_sha>[a-f0-9]{7})-(?P<exp_sha>[a-f0-9]+)")
def _collect_experiment(repo, branch, stash=False):
res = defaultdict(dict)
for rev in repo.brancher(revs=[branch]):
if rev == "workspace":
res["timestamp"] = None
else:
commit = repo.scm.repo.rev_parse(rev)
res["timestamp"] = datetime.fromtimestamp(commit.committed_date)
configs = _collect_configs(repo)
params = _read_params(repo, configs, rev)
if params:
+43
View File
@@ -1,3 +1,6 @@
from datetime import datetime
from dvc.dvcfile import PIPELINE_FILE
from tests.func.test_repro_multistage import COPY_SCRIPT
@@ -16,5 +19,45 @@ def test_show_simple(tmp_dir, scm, dvc):
"metrics": {"metrics.yaml": {"foo": 1}},
"params": {"params.yaml": {"foo": 1}},
"queued": False,
"timestamp": None,
}
}
def test_show_experiment(tmp_dir, scm, dvc):
tmp_dir.gen("copy.py", COPY_SCRIPT)
tmp_dir.gen("params.yaml", "foo: 1")
dvc.run(
cmd="python copy.py params.yaml metrics.yaml",
metrics_no_cache=["metrics.yaml"],
params=["foo"],
name="foo",
)
scm.add(["copy.py", "params.yaml", "metrics.yaml", "dvc.yaml", "dvc.lock"])
scm.commit("baseline")
baseline_rev = scm.get_rev()
timestamp = datetime.fromtimestamp(
scm.repo.rev_parse(baseline_rev).committed_date
)
dvc.reproduce(PIPELINE_FILE, experiment=True, params=["foo=2"])
results = dvc.experiments.show()
expected_baseline = {
"metrics": {"metrics.yaml": {"foo": 1}},
"params": {"params.yaml": {"foo": 1}},
"queued": False,
"timestamp": timestamp,
}
expected_params = {"foo": 2}
assert set(results.keys()) == {"workspace", baseline_rev}
experiments = results[baseline_rev]
assert len(experiments) == 2
for rev, exp in experiments.items():
if rev == "baseline":
assert exp == expected_baseline
else:
assert exp["metrics"]["metrics.yaml"] == expected_params
assert exp["params"]["params.yaml"] == expected_params
assert exp["timestamp"] > timestamp