-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
frontend: add failed-to-succeeded-stats command
Fix #2779
- Loading branch information
Showing
2 changed files
with
143 additions
and
0 deletions.
There are no files selected for viewing
141 changes: 141 additions & 0 deletions
141
frontend/coprs_frontend/commands/failed_to_succeeded_stats.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
""" | ||
Generate failed to succeeded stats | ||
""" | ||
|
||
import os | ||
from datetime import datetime, timedelta | ||
from sqlalchemy.orm import joinedload, selectinload, load_only, contains_eager | ||
|
||
import click | ||
import pygal | ||
from coprs import models | ||
|
||
|
||
@click.command() | ||
@click.option("--dest", "-D", required=True, help="Result directory") | ||
def failed_to_succeeded_stats(dest): | ||
""" | ||
Generate failed to succeeded stats | ||
""" | ||
print("Please wait, this will take at least 20 minutes.") | ||
categories = { | ||
"immediately": 0, | ||
"seconds": 0, | ||
"minutes": 0, | ||
"hours": 0, | ||
"days": 0, | ||
"weeks": 0, | ||
} | ||
tuples = failed_to_succeeded_tuples() | ||
for failed, succeeded in tuples: | ||
delta = datetime.fromtimestamp(succeeded) \ | ||
- datetime.fromtimestamp(failed) | ||
categories[delta_to_category(delta)] += 1 | ||
|
||
os.makedirs(dest, exist_ok=True) | ||
path = os.path.join(dest, "failed-to-succeeded-stats.svg") | ||
generate_graph(categories, path) | ||
print("Created: {0}".format(path)) | ||
|
||
|
||
def get_builds(): | ||
""" | ||
Return list of all builds | ||
""" | ||
query = ( | ||
models.Build.query | ||
.join(models.Package) | ||
.join(models.Copr) | ||
.order_by(models.Build.id) | ||
) | ||
return query.all() | ||
|
||
|
||
def builds_per_package(): | ||
""" | ||
Return a `dict` where keys are package IDs and values are lists | ||
of all their builds. | ||
""" | ||
builds = get_builds() | ||
result = {} | ||
for build in builds: | ||
result.setdefault(build.package_id, []) | ||
result[build.package_id].append(build) | ||
return result | ||
|
||
|
||
def failed_to_succeeded_tuples(): | ||
""" | ||
Return a list of tuples. First value of each tuple is when the package | ||
failed, and the second value is when it succeeded. | ||
""" | ||
tuples = [] | ||
for builds in builds_per_package().values(): | ||
if len(builds) <= 1: | ||
# This package has only one build | ||
# Not dealing with this now. | ||
continue | ||
|
||
failed = None | ||
succeeded = None | ||
|
||
for build in builds: | ||
if not build.ended_on: | ||
continue | ||
|
||
if build.state == "failed": | ||
failed = build | ||
|
||
elif build.state == "succeeded" and failed: | ||
succeeded = build | ||
|
||
if failed and succeeded: | ||
assert failed.id < succeeded.id | ||
tuples.append((failed.ended_on, succeeded.ended_on)) | ||
failed = None | ||
succeeded = None | ||
return tuples | ||
|
||
|
||
def delta_to_category(delta): | ||
""" | ||
Convert timedelta into a custom time category | ||
""" | ||
seconds = delta.total_seconds() | ||
if seconds < 0: | ||
return "immediately" | ||
if seconds < 60: | ||
return "seconds" | ||
if seconds < 60 * 60: | ||
return "minutes" | ||
if seconds < 60 * 60 * 24: | ||
return "hours" | ||
if seconds < 60 * 60 * 24 * 7: | ||
return "days" | ||
return "weeks" | ||
|
||
|
||
def generate_graph(data, path): | ||
""" | ||
Generate graph from the data | ||
""" | ||
title = "How long before devs submit a successfull package after a failure?" | ||
chart = pygal.Bar( | ||
title=title, | ||
print_values=True, | ||
legend_at_bottom=True, | ||
) | ||
for key, value in data.items(): | ||
label = label_for_group(key) | ||
chart.add(label, value) | ||
chart.render_to_file(path) | ||
return path | ||
|
||
|
||
def label_for_group(key): | ||
""" | ||
User-friendly labels for the graph | ||
""" | ||
if key == "immediately": | ||
return "Before it finished" | ||
return key.capitalize() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters