-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdebug_builds.py
More file actions
executable file
·164 lines (131 loc) · 6.06 KB
/
debug_builds.py
File metadata and controls
executable file
·164 lines (131 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python3
"""
Debug script to analyze workflow run durations.
Useful for finding cancelled runs and understanding build duration patterns.
"""
import os
from datetime import datetime, timedelta
from github import Github, Auth
if __name__ == "__main__":
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
raise ValueError("GITHUB_TOKEN environment variable not set.")
auth = Auth.Token(github_token)
g = Github(auth=auth)
repo = g.get_repo("open-telemetry/opentelemetry-java-instrumentation")
# Find both Build workflows
workflows = repo.get_workflows()
build_workflows = []
for wf in workflows:
if wf.name == "Build" or "build.yml" in wf.path:
build_workflows.append(wf)
print(f"Found workflow: {wf.name} ({wf.path})")
elif wf.name == "Build pull request" or "build-pull-request.yml" in wf.path:
build_workflows.append(wf)
print(f"Found workflow: {wf.name} ({wf.path})")
# Look back 12 hours by default, or use environment variable
lookback_hours = int(os.environ.get("DEBUG_LOOKBACK_HOURS", "12"))
since = datetime.now() - timedelta(hours=lookback_hours)
date_filter = since.strftime("%Y-%m-%dT%H:%M:%S")
print("\n" + "="*100)
print(f"Analyzing workflow runs from the last {lookback_hours} hours")
print("="*100)
all_runs = []
for build_workflow in build_workflows:
print(f"\nProcessing workflow: {build_workflow.name}")
runs = build_workflow.get_runs(created=f">={date_filter}")
for run in runs:
# Filter to match collect_workflow_metrics.py logic
if run.event == "pull_request":
pass
elif run.event == "push" and run.head_branch == "main":
pass
else:
continue
if run.status != "completed":
continue
try:
timing = run.timing()
if timing and hasattr(timing, 'run_duration_ms'):
duration_minutes = timing.run_duration_ms / 1000 / 60
else:
duration_minutes = 0
all_runs.append({
'run': run,
'duration': duration_minutes,
'workflow': build_workflow.name
})
except Exception as e:
print(f" Error getting timing for run {run.id}: {e}")
continue
# Sort by duration
all_runs.sort(key=lambda x: x['duration'])
print(f"\n{'='*100}")
print(f"Total completed runs found: {len(all_runs)}")
print(f"{'='*100}\n")
# Show shortest runs first (likely cancelled or failed early)
print("SHORTEST DURATION RUNS (First 20):")
print("-" * 100)
print(f"{'Run #':<10} {'Conclusion':<12} {'Duration':<12} {'Workflow':<25} {'Event':<15} {'Branch':<30}")
print("-" * 100)
for item in all_runs[:20]:
run = item['run']
duration = item['duration']
workflow = item['workflow']
# Color-code conclusion for readability
conclusion_display = run.conclusion or "unknown"
if run.conclusion == "cancelled":
conclusion_display = f"🚫 {run.conclusion}"
elif run.conclusion == "failure":
conclusion_display = f"❌ {run.conclusion}"
elif run.conclusion == "success":
conclusion_display = f"✅ {run.conclusion}"
print(f"{run.run_number:<10} {conclusion_display:<20} {duration:>6.1f} min {workflow:<25} {run.event:<15} {run.head_branch[:28]:<30}")
print(f"\n{'='*100}")
print("DETAILED VIEW OF SHORT-DURATION RUNS:")
print("-" * 100)
for item in all_runs[:10]:
run = item['run']
duration = item['duration']
workflow = item['workflow']
print(f"\n📊 Run #{run.run_number} (ID: {run.id})")
print(f" Workflow: {workflow}")
print(f" Conclusion: {run.conclusion} {'🚫 CANCELLED' if run.conclusion == 'cancelled' else '❌ FAILED' if run.conclusion == 'failure' else '✅ SUCCESS'}")
print(f" Duration: {duration:.1f} minutes")
print(f" Status: {run.status}")
print(f" Event: {run.event}")
print(f" Branch: {run.head_branch}")
print(f" Created: {run.created_at}")
print(f" URL: {run.html_url}")
# Show statistics
print(f"\n{'='*100}")
print("DURATION STATISTICS BY CONCLUSION:")
print("-" * 100)
durations_by_conclusion = {}
for item in all_runs:
conclusion = item['run'].conclusion or "unknown"
if conclusion not in durations_by_conclusion:
durations_by_conclusion[conclusion] = []
durations_by_conclusion[conclusion].append(item['duration'])
for conclusion, durations in sorted(durations_by_conclusion.items()):
avg = sum(durations) / len(durations)
min_dur = min(durations)
max_dur = max(durations)
count = len(durations)
icon = "🚫" if conclusion == "cancelled" else "❌" if conclusion == "failure" else "✅" if conclusion == "success" else "❓"
print(f"{icon} {conclusion:<12} {count:>3} runs | avg: {avg:>6.1f} min | min: {min_dur:>6.1f} min | max: {max_dur:>6.1f} min")
# Show runs that would be filtered
print(f"\n{'='*100}")
print("FILTERING SUMMARY:")
print("-" * 100)
cancelled_count = sum(1 for item in all_runs if item['run'].conclusion == "cancelled")
would_process = len(all_runs) - cancelled_count
print(f"Total runs found: {len(all_runs)}")
print(f"Cancelled runs (filtered): {cancelled_count} 🚫")
print(f"Runs that would be processed: {would_process} ✅")
if cancelled_count > 0:
cancelled_durations = [item['duration'] for item in all_runs if item['run'].conclusion == "cancelled"]
avg_cancelled = sum(cancelled_durations) / len(cancelled_durations)
print(f"\nCancelled runs average duration: {avg_cancelled:.1f} minutes")
print(f"These cancelled runs would have skewed your metrics!")
print(f"\n{'='*100}")