|
@@ -24,8 +24,10 @@ from labours.readers import read_input
|
|
|
|
|
|
|
|
|
def list_matplotlib_styles() -> List[str]:
|
|
|
- script = "import sys; from matplotlib import pyplot; " \
|
|
|
- "sys.stdout.write(repr(pyplot.style.available))"
|
|
|
+ script = (
|
|
|
+ "import sys; from matplotlib import pyplot; "
|
|
|
+ "sys.stdout.write(repr(pyplot.style.available))"
|
|
|
+ )
|
|
|
styles = eval(subprocess.check_output([sys.executable, "-c", script]))
|
|
|
styles.remove("classic")
|
|
|
return ["default", "classic"] + styles
|
|
@@ -33,53 +35,109 @@ def list_matplotlib_styles() -> List[str]:
|
|
|
|
|
|
def parse_args() -> Namespace:
|
|
|
parser = argparse.ArgumentParser()
|
|
|
- parser.add_argument("-o", "--output", default="",
|
|
|
- help="Path to the output file/directory (empty for display). "
|
|
|
- "If the extension is JSON, the data is saved instead of "
|
|
|
- "the real image.")
|
|
|
- parser.add_argument("-i", "--input", default="-",
|
|
|
- help="Path to the input file (- for stdin).")
|
|
|
- parser.add_argument("-f", "--input-format", default="auto", choices=["yaml", "pb", "auto"])
|
|
|
- parser.add_argument("--font-size", default=12, type=int,
|
|
|
- help="Size of the labels and legend.")
|
|
|
- parser.add_argument("--style", default="ggplot", choices=list_matplotlib_styles(),
|
|
|
- help="Plot style to use.")
|
|
|
+ parser.add_argument(
|
|
|
+ "-o",
|
|
|
+ "--output",
|
|
|
+ default="",
|
|
|
+ help="Path to the output file/directory (empty for display). "
|
|
|
+ "If the extension is JSON, the data is saved instead of "
|
|
|
+ "the real image.",
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "-i", "--input", default="-", help="Path to the input file (- for stdin)."
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "-f", "--input-format", default="auto", choices=["yaml", "pb", "auto"]
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--font-size", default=12, type=int, help="Size of the labels and legend."
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--style",
|
|
|
+ default="ggplot",
|
|
|
+ choices=list_matplotlib_styles(),
|
|
|
+ help="Plot style to use.",
|
|
|
+ )
|
|
|
parser.add_argument("--backend", help="Matplotlib backend to use.")
|
|
|
- parser.add_argument("--background", choices=["black", "white"], default="white",
|
|
|
- help="Plot's general color scheme.")
|
|
|
+ parser.add_argument(
|
|
|
+ "--background",
|
|
|
+ choices=["black", "white"],
|
|
|
+ default="white",
|
|
|
+ help="Plot's general color scheme.",
|
|
|
+ )
|
|
|
parser.add_argument("--size", help="Axes' size in inches, for example \"12,9\"")
|
|
|
- parser.add_argument("--relative", action="store_true",
|
|
|
- help="Occupy 100%% height for every measurement.")
|
|
|
+ parser.add_argument(
|
|
|
+ "--relative",
|
|
|
+ action="store_true",
|
|
|
+ help="Occupy 100%% height for every measurement.",
|
|
|
+ )
|
|
|
parser.add_argument("--tmpdir", help="Temporary directory for intermediate files.")
|
|
|
- parser.add_argument("-m", "--mode", dest="modes", default=[], action="append",
|
|
|
- choices=["burndown-project", "burndown-file", "burndown-person",
|
|
|
- "overwrites-matrix", "ownership", "couples-files",
|
|
|
- "couples-people", "couples-shotness", "shotness", "sentiment",
|
|
|
- "devs", "devs-efforts", "old-vs-new", "run-times",
|
|
|
- "languages", "devs-parallel", "all"],
|
|
|
- help="What to plot. Can be repeated, e.g. "
|
|
|
- "-m burndown-project -m run-times")
|
|
|
parser.add_argument(
|
|
|
- "--resample", default="year",
|
|
|
+ "-m",
|
|
|
+ "--mode",
|
|
|
+ dest="modes",
|
|
|
+ default=[],
|
|
|
+ action="append",
|
|
|
+ choices=[
|
|
|
+ "burndown-project",
|
|
|
+ "burndown-file",
|
|
|
+ "burndown-person",
|
|
|
+ "overwrites-matrix",
|
|
|
+ "ownership",
|
|
|
+ "couples-files",
|
|
|
+ "couples-people",
|
|
|
+ "couples-shotness",
|
|
|
+ "shotness",
|
|
|
+ "sentiment",
|
|
|
+ "devs",
|
|
|
+ "devs-efforts",
|
|
|
+ "old-vs-new",
|
|
|
+ "run-times",
|
|
|
+ "languages",
|
|
|
+ "devs-parallel",
|
|
|
+ "all",
|
|
|
+ ],
|
|
|
+ help="What to plot. Can be repeated, e.g. " "-m burndown-project -m run-times",
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--resample",
|
|
|
+ default="year",
|
|
|
help="The way to resample the time series. Possible values are: "
|
|
|
- "\"month\", \"year\", \"no\", \"raw\" and pandas offset aliases ("
|
|
|
- "http://pandas.pydata.org/pandas-docs/stable/timeseries.html"
|
|
|
- "#offset-aliases).")
|
|
|
- dateutil_url = "https://dateutil.readthedocs.io/en/stable/parser.html#dateutil.parser.parse"
|
|
|
- parser.add_argument("--start-date",
|
|
|
- help="Start date of time-based plots. Any format is accepted which is "
|
|
|
- "supported by %s" % dateutil_url)
|
|
|
- parser.add_argument("--end-date",
|
|
|
- help="End date of time-based plots. Any format is accepted which is "
|
|
|
- "supported by %s" % dateutil_url)
|
|
|
- parser.add_argument("--disable-projector", action="store_true",
|
|
|
- help="Do not run Tensorflow Projector on couples.")
|
|
|
- parser.add_argument("--max-people", default=20, type=int,
|
|
|
- help="Maximum number of developers in overwrites matrix and people plots.")
|
|
|
- parser.add_argument("--order-ownership-by-time", action="store_true",
|
|
|
- help="Sort developers in the ownership plot according to their first "
|
|
|
- "appearance in the history. The default is sorting by the number of "
|
|
|
- "commits.")
|
|
|
+ "\"month\", \"year\", \"no\", \"raw\" and pandas offset aliases ("
|
|
|
+ "http://pandas.pydata.org/pandas-docs/stable/timeseries.html"
|
|
|
+ "#offset-aliases).",
|
|
|
+ )
|
|
|
+ dateutil_url = (
|
|
|
+ "https://dateutil.readthedocs.io/en/stable/parser.html#dateutil.parser.parse"
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--start-date",
|
|
|
+ help="Start date of time-based plots. Any format is accepted which is "
|
|
|
+ "supported by %s" % dateutil_url,
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--end-date",
|
|
|
+ help="End date of time-based plots. Any format is accepted which is "
|
|
|
+ "supported by %s" % dateutil_url,
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--disable-projector",
|
|
|
+ action="store_true",
|
|
|
+ help="Do not run Tensorflow Projector on couples.",
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--max-people",
|
|
|
+ default=20,
|
|
|
+ type=int,
|
|
|
+ help="Maximum number of developers in overwrites matrix and people plots.",
|
|
|
+ )
|
|
|
+ parser.add_argument(
|
|
|
+ "--order-ownership-by-time",
|
|
|
+ action="store_true",
|
|
|
+ help="Sort developers in the ownership plot according to their first "
|
|
|
+ "appearance in the history. The default is sorting by the number of "
|
|
|
+ "commits.",
|
|
|
+ )
|
|
|
args = parser.parse_args()
|
|
|
return args
|
|
|
|
|
@@ -90,23 +148,35 @@ def main() -> None:
|
|
|
header = reader.get_header()
|
|
|
name = reader.get_name()
|
|
|
|
|
|
- burndown_warning = "Burndown stats were not collected. Re-run hercules with --burndown."
|
|
|
- burndown_files_warning = \
|
|
|
- "Burndown stats for files were not collected. Re-run hercules with " \
|
|
|
+ burndown_warning = (
|
|
|
+ "Burndown stats were not collected. Re-run hercules with --burndown."
|
|
|
+ )
|
|
|
+ burndown_files_warning = (
|
|
|
+ "Burndown stats for files were not collected. Re-run hercules with "
|
|
|
"--burndown --burndown-files."
|
|
|
- burndown_people_warning = \
|
|
|
- "Burndown stats for people were not collected. Re-run hercules with " \
|
|
|
+ )
|
|
|
+ burndown_people_warning = (
|
|
|
+ "Burndown stats for people were not collected. Re-run hercules with "
|
|
|
"--burndown --burndown-people."
|
|
|
- couples_warning = "Coupling stats were not collected. Re-run hercules with --couples."
|
|
|
- shotness_warning = "Structural hotness stats were not collected. Re-run hercules with " \
|
|
|
- "--shotness. Also check --languages - the output may be empty."
|
|
|
- sentiment_warning = "Sentiment stats were not collected. Re-run hercules with --sentiment."
|
|
|
+ )
|
|
|
+ couples_warning = (
|
|
|
+ "Coupling stats were not collected. Re-run hercules with --couples."
|
|
|
+ )
|
|
|
+ shotness_warning = (
|
|
|
+ "Structural hotness stats were not collected. Re-run hercules with "
|
|
|
+ "--shotness. Also check --languages - the output may be empty."
|
|
|
+ )
|
|
|
+ sentiment_warning = (
|
|
|
+ "Sentiment stats were not collected. Re-run hercules with --sentiment."
|
|
|
+ )
|
|
|
devs_warning = "Devs stats were not collected. Re-run hercules with --devs."
|
|
|
|
|
|
def run_times():
|
|
|
rt = reader.get_run_times()
|
|
|
pandas = import_pandas()
|
|
|
- series = pandas.to_timedelta(pandas.Series(rt).sort_values(ascending=False), unit="s")
|
|
|
+ series = pandas.to_timedelta(
|
|
|
+ pandas.Series(rt).sort_values(ascending=False), unit="s"
|
|
|
+ )
|
|
|
df = pandas.concat([series, series / series.sum()], axis=1)
|
|
|
df.columns = ["time", "ratio"]
|
|
|
print(df)
|
|
@@ -117,9 +187,16 @@ def main() -> None:
|
|
|
except KeyError:
|
|
|
print("project: " + burndown_warning)
|
|
|
return
|
|
|
- plot_burndown(args, "project",
|
|
|
- *load_burndown(full_header, *reader.get_project_burndown(),
|
|
|
- resample=args.resample, interpolation_progress=True))
|
|
|
+ plot_burndown(
|
|
|
+ args,
|
|
|
+ "project",
|
|
|
+ *load_burndown(
|
|
|
+ full_header,
|
|
|
+ *reader.get_project_burndown(),
|
|
|
+ resample=args.resample,
|
|
|
+ interpolation_progress=True,
|
|
|
+ ),
|
|
|
+ )
|
|
|
|
|
|
def files_burndown():
|
|
|
try:
|
|
@@ -139,27 +216,44 @@ def main() -> None:
|
|
|
print(burndown_warning)
|
|
|
return
|
|
|
try:
|
|
|
- plot_many_burndown(args, "person", full_header, reader.get_people_burndown())
|
|
|
+ plot_many_burndown(
|
|
|
+ args, "person", full_header, reader.get_people_burndown()
|
|
|
+ )
|
|
|
except KeyError:
|
|
|
print("people: " + burndown_people_warning)
|
|
|
|
|
|
def overwrites_matrix():
|
|
|
try:
|
|
|
|
|
|
- plot_overwrites_matrix(args, name, *load_overwrites_matrix(
|
|
|
- *reader.get_people_interaction(), max_people=args.max_people))
|
|
|
+ plot_overwrites_matrix(
|
|
|
+ args,
|
|
|
+ name,
|
|
|
+ *load_overwrites_matrix(
|
|
|
+ *reader.get_people_interaction(), max_people=args.max_people
|
|
|
+ ),
|
|
|
+ )
|
|
|
people, matrix = load_overwrites_matrix(
|
|
|
- *reader.get_people_interaction(), max_people=1000000, normalize=False)
|
|
|
+ *reader.get_people_interaction(), max_people=1000000, normalize=False
|
|
|
+ )
|
|
|
from scipy.sparse import csr_matrix
|
|
|
+
|
|
|
matrix = matrix[:, 1:]
|
|
|
matrix = numpy.triu(matrix) + numpy.tril(matrix).T
|
|
|
matrix = matrix + matrix.T
|
|
|
matrix = csr_matrix(matrix)
|
|
|
try:
|
|
|
- write_embeddings("overwrites", args.output, not args.disable_projector,
|
|
|
- *train_embeddings(people, matrix, tmpdir=args.tmpdir))
|
|
|
+ write_embeddings(
|
|
|
+ "overwrites",
|
|
|
+ args.output,
|
|
|
+ not args.disable_projector,
|
|
|
+ *train_embeddings(people, matrix, tmpdir=args.tmpdir),
|
|
|
+ )
|
|
|
except AttributeError as e:
|
|
|
- print("Training the embeddings is not possible: %s: %s", type(e).__name__, e)
|
|
|
+ print(
|
|
|
+ "Training the embeddings is not possible: %s: %s",
|
|
|
+ type(e).__name__,
|
|
|
+ e,
|
|
|
+ )
|
|
|
except KeyError:
|
|
|
print("overwrites_matrix: " + burndown_people_warning)
|
|
|
|
|
@@ -170,33 +264,49 @@ def main() -> None:
|
|
|
print(burndown_warning)
|
|
|
return
|
|
|
try:
|
|
|
- plot_ownership(args, name, *load_ownership(
|
|
|
- full_header, *reader.get_ownership_burndown(), max_people=args.max_people,
|
|
|
- order_by_time=args.order_ownership_by_time))
|
|
|
+ plot_ownership(
|
|
|
+ args,
|
|
|
+ name,
|
|
|
+ *load_ownership(
|
|
|
+ full_header,
|
|
|
+ *reader.get_ownership_burndown(),
|
|
|
+ max_people=args.max_people,
|
|
|
+ order_by_time=args.order_ownership_by_time,
|
|
|
+ ),
|
|
|
+ )
|
|
|
except KeyError:
|
|
|
print("ownership: " + burndown_people_warning)
|
|
|
|
|
|
def couples_files():
|
|
|
try:
|
|
|
- write_embeddings("files", args.output, not args.disable_projector,
|
|
|
- *train_embeddings(*reader.get_files_coocc(),
|
|
|
- tmpdir=args.tmpdir))
|
|
|
+ write_embeddings(
|
|
|
+ "files",
|
|
|
+ args.output,
|
|
|
+ not args.disable_projector,
|
|
|
+ *train_embeddings(*reader.get_files_coocc(), tmpdir=args.tmpdir),
|
|
|
+ )
|
|
|
except KeyError:
|
|
|
print(couples_warning)
|
|
|
|
|
|
def couples_people():
|
|
|
try:
|
|
|
- write_embeddings("people", args.output, not args.disable_projector,
|
|
|
- *train_embeddings(*reader.get_people_coocc(),
|
|
|
- tmpdir=args.tmpdir))
|
|
|
+ write_embeddings(
|
|
|
+ "people",
|
|
|
+ args.output,
|
|
|
+ not args.disable_projector,
|
|
|
+ *train_embeddings(*reader.get_people_coocc(), tmpdir=args.tmpdir),
|
|
|
+ )
|
|
|
except KeyError:
|
|
|
print(couples_warning)
|
|
|
|
|
|
def couples_shotness():
|
|
|
try:
|
|
|
- write_embeddings("shotness", args.output, not args.disable_projector,
|
|
|
- *train_embeddings(*reader.get_shotness_coocc(),
|
|
|
- tmpdir=args.tmpdir))
|
|
|
+ write_embeddings(
|
|
|
+ "shotness",
|
|
|
+ args.output,
|
|
|
+ not args.disable_projector,
|
|
|
+ *train_embeddings(*reader.get_shotness_coocc(), tmpdir=args.tmpdir),
|
|
|
+ )
|
|
|
except KeyError:
|
|
|
print(shotness_warning)
|
|
|
|
|
@@ -214,7 +324,9 @@ def main() -> None:
|
|
|
except KeyError:
|
|
|
print(sentiment_warning)
|
|
|
return
|
|
|
- show_sentiment_stats(args, reader.get_name(), args.resample, reader.get_header()[0], data)
|
|
|
+ show_sentiment_stats(
|
|
|
+ args, reader.get_name(), args.resample, reader.get_header()[0], data
|
|
|
+ )
|
|
|
|
|
|
def devs():
|
|
|
try:
|
|
@@ -222,8 +334,13 @@ def main() -> None:
|
|
|
except KeyError:
|
|
|
print(devs_warning)
|
|
|
return
|
|
|
- show_devs(args, reader.get_name(), *reader.get_header(), *data,
|
|
|
- max_people=args.max_people)
|
|
|
+ show_devs(
|
|
|
+ args,
|
|
|
+ reader.get_name(),
|
|
|
+ *reader.get_header(),
|
|
|
+ *data,
|
|
|
+ max_people=args.max_people,
|
|
|
+ )
|
|
|
|
|
|
def devs_efforts():
|
|
|
try:
|
|
@@ -231,8 +348,13 @@ def main() -> None:
|
|
|
except KeyError:
|
|
|
print(devs_warning)
|
|
|
return
|
|
|
- show_devs_efforts(args, reader.get_name(), *reader.get_header(), *data,
|
|
|
- max_people=args.max_people)
|
|
|
+ show_devs_efforts(
|
|
|
+ args,
|
|
|
+ reader.get_name(),
|
|
|
+ *reader.get_header(),
|
|
|
+ *data,
|
|
|
+ max_people=args.max_people,
|
|
|
+ )
|
|
|
|
|
|
def old_vs_new():
|
|
|
try:
|
|
@@ -266,8 +388,12 @@ def main() -> None:
|
|
|
except KeyError:
|
|
|
print(devs_warning)
|
|
|
return
|
|
|
- show_devs_parallel(args, reader.get_name(), *reader.get_header(),
|
|
|
- load_devs_parallel(ownership, couples, devs, args.max_people))
|
|
|
+ show_devs_parallel(
|
|
|
+ args,
|
|
|
+ reader.get_name(),
|
|
|
+ *reader.get_header(),
|
|
|
+ load_devs_parallel(ownership, couples, devs, args.max_people),
|
|
|
+ )
|
|
|
|
|
|
modes = {
|
|
|
"run-times": run_times,
|
|
@@ -311,7 +437,7 @@ def main() -> None:
|
|
|
|
|
|
print("Running: %s" % mode)
|
|
|
# `args.mode` is required for path determination in the mode functions
|
|
|
- args.mode = ("all" if all_mode else mode)
|
|
|
+ args.mode = "all" if all_mode else mode
|
|
|
try:
|
|
|
modes[mode]()
|
|
|
except ImportError as ie:
|