src\wily\commands\graph.py
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- | Draw graph in HTML for a specific metric.
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | TODO: Add multiple lines for multiple files
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | from pathlib import Path
-- -- --- --- --- --- --- --- ------- ------- ------- | from typing import Optional, Tuple, Union
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | import plotly.graph_objs as go
-- -- --- --- --- --- --- --- ------- ------- ------- | import plotly.offline
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily import format_datetime, logger
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.config.types import WilyConfig
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.operators import resolve_metric, resolve_metric_as_tuple
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.state import State
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def metric_parts(metric):
-- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """Convert a metric name into the operator and metric names."""
-- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | operator, met = resolve_metric_as_tuple(metric)
-- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return operator.name, met.name
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | def graph(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | config: WilyConfig,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | path: str,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | metrics: Union[Tuple[str], Tuple[str, str]],
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | output: Optional[str] = None,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x_axis: Optional[str] = None,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | changes: bool = True,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | text: bool = False,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | aggregate: bool = False,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | ) -> None:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | """
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | Graph information about the cache and runtime.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param config: The configuration.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param path: The path to the files.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param metrics: The Y and Z-axis metrics to report on.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param output: Save report to specified path instead of opening browser.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param x_axis: Name of metric for x-axis or "history".
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param changes: Only graph changes.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param text: Show commit message inline in graph.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | :param aggregate: Aggregate values for graph.
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | """
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | logger.debug("Running graph command")
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | data = []
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | state = State(config)
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if x_axis is None:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x_axis = "history"
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x_operator = x_key = ""
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | else:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x_operator, x_key = metric_parts(x_axis)
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | y_metric = resolve_metric(metrics[0])
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | title = f"{x_axis.capitalize()} of {y_metric.description} for {path}{' aggregated' if aggregate else ''}"
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if not aggregate:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | tracked_files = set()
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | for rev in state.index[state.default_archiver].revisions:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | tracked_files.update(rev.revision.tracked_files)
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | paths = {
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | tracked_file
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | for tracked_file in tracked_files
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if tracked_file.startswith(path)
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | } or {path}
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | else:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | paths = {path}
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | operator, key = metric_parts(metrics[0])
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if len(metrics) == 1: # only y-axis
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z_axis = z_operator = z_key = ""
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | else:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z_axis = resolve_metric(metrics[1])
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z_operator, z_key = metric_parts(metrics[1])
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | for path_ in paths:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | current_path = str(Path(path_))
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x = []
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | y = []
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z = []
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | labels = []
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | last_y = None
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | for rev in state.index[state.default_archiver].revisions:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | try:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | val = rev.get(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | config, state.default_archiver, operator, current_path, key
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if val != last_y or not changes:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | y.append(val)
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if z_axis:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z.append(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | rev.get(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | config,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | state.default_archiver,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z_operator,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | current_path,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | z_key,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if x_axis == "history":
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x.append(format_datetime(rev.revision.date))
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | else:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x.append(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | rev.get(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | config,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | state.default_archiver,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x_operator,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | current_path,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x_key,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | labels.append(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | f"{rev.revision.author_name} <br>{rev.revision.message}"
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | last_y = val
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | except KeyError:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | # missing data
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | pass
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 |
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | # Create traces
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | trace = go.Scatter(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | x=x,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | y=y,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | mode="lines+markers+text" if text else "lines+markers",
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | name=f"{path_}",
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | ids=state.index[state.default_archiver].revision_keys,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | text=labels,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | marker={
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | "size": 0 if z_axis is None else z,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | "color": list(range(len(y))),
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | # "colorscale": "Viridis",
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | },
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | xcalendar="gregorian",
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | hoveron="points+fills",
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | ) # type: ignore
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | data.append(trace)
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | if output:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | filename = output
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | auto_open = False
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | else:
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | filename = "wily-report.html"
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | auto_open = True
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | plotly.offline.plot(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | {
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | "data": data,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | "layout": go.Layout(
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | title=title,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | xaxis={"title": x_axis},
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | yaxis={"title": y_metric.description},
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | ), # type: ignore
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | },
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | auto_open=auto_open,
-- 19 005 014 009 016 019 025 0106.20 0303.42 0002.86 | filename=filename,
-- -- 005 014 009 016 019 025 0106.20 0303.42 0002.86 | )