|
| 1 | +from collections import defaultdict |
| 2 | +import matplotlib.pyplot as plt |
| 3 | +from github import Github |
| 4 | +from matplotlib.ticker import MultipleLocator, FuncFormatter |
| 5 | + |
| 6 | +GITHUB_TOKEN = "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" |
| 7 | +g = Github(GITHUB_TOKEN) |
| 8 | + |
| 9 | +ORGANIZATION = "ivelum" |
| 10 | +REPOSITORY = "teamplify" |
| 11 | +START_WORKFLOW = "test.yaml" |
| 12 | +END_WORKFLOW = "deploy.yaml" |
| 13 | +LAST_N_RUNS = 50 |
| 14 | + |
| 15 | + |
| 16 | +def get_time_series(): |
| 17 | + repo = g.get_organization(ORGANIZATION).get_repo(REPOSITORY) |
| 18 | + |
| 19 | + runs_by_sha = defaultdict(lambda: dict(workflow_names=[], start=None, end=None)) |
| 20 | + for workflow_name in [START_WORKFLOW, END_WORKFLOW]: |
| 21 | + workflow = repo.get_workflow(workflow_name) |
| 22 | + |
| 23 | + all_runs = iter(workflow.get_runs(status="completed")) |
| 24 | + |
| 25 | + for _ in range(LAST_N_RUNS): |
| 26 | + workflow_run = next(all_runs) |
| 27 | + |
| 28 | + if workflow_run.status != "completed": |
| 29 | + continue |
| 30 | + |
| 31 | + if workflow_run.conclusion != "success": |
| 32 | + continue |
| 33 | + |
| 34 | + stats = runs_by_sha[workflow_run.head_sha] |
| 35 | + |
| 36 | + stats["workflow_names"].append(workflow_name) |
| 37 | + |
| 38 | + if not stats["start"] or stats["start"] > workflow_run.created_at: |
| 39 | + stats["start"] = workflow_run.created_at |
| 40 | + |
| 41 | + if not stats["end"] or stats["end"] < workflow_run.updated_at: |
| 42 | + stats["end"] = workflow_run.updated_at |
| 43 | + |
| 44 | + with_start_and_end = [ |
| 45 | + stats |
| 46 | + for stats in runs_by_sha.values() |
| 47 | + if set(stats["workflow_names"]) == {START_WORKFLOW, END_WORKFLOW} |
| 48 | + ] |
| 49 | + by_start = sorted(with_start_and_end, key=lambda stats: stats["start"]) |
| 50 | + |
| 51 | + starts = [] |
| 52 | + durations = [] |
| 53 | + for stats in by_start: |
| 54 | + starts.append(stats["start"].strftime("%-dth\n%H:%M")) |
| 55 | + duration = stats["end"] - stats["start"] |
| 56 | + durations.append(duration.seconds) |
| 57 | + |
| 58 | + return starts, durations |
| 59 | + |
| 60 | + |
| 61 | +def format_seconds(total_seconds, position=None): |
| 62 | + minutes = total_seconds // 60 |
| 63 | + seconds = total_seconds % 60 |
| 64 | + return f"{minutes:.0f}:{seconds:02.0f}" |
| 65 | + |
| 66 | + |
| 67 | +if __name__ == "__main__": |
| 68 | + plt.style.use("seaborn-v0_8") |
| 69 | + |
| 70 | + starts, durations = get_time_series() |
| 71 | + |
| 72 | + ax = plt.subplot() |
| 73 | + bars = ax.bar(starts, durations) |
| 74 | + ax.bar_label(bars, labels=[format_seconds(duration) for duration in durations]) |
| 75 | + ax.yaxis.set_major_locator(MultipleLocator(base=60)) |
| 76 | + ax.yaxis.set_major_formatter(FuncFormatter(format_seconds)) |
| 77 | + ax.set_ylim(bottom=0) |
| 78 | + plt.show() |
0 commit comments