Skip to content

Commit 8ede0c2

Browse files
Add new rule for monitoring performance alert resolution changes (#2535)
Co-authored-by: Suhaib Mujahid <[email protected]>
1 parent 100a72b commit 8ede0c2

File tree

5 files changed

+249
-0
lines changed

5 files changed

+249
-0
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3+
# You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
from datetime import timedelta
6+
7+
from libmozdata import utils as lmdutils
8+
from libmozdata.bugzilla import BugzillaUser
9+
10+
from bugbot.bzcleaner import BzCleaner
11+
from bugbot.constants import BOT_MAIN_ACCOUNT
12+
13+
14+
class PerfAlertResolvedRegression(BzCleaner):
15+
def __init__(self, *args, **kwargs):
16+
super().__init__(*args, **kwargs)
17+
self.extra_ni = {}
18+
19+
def description(self):
20+
return "PerfAlert regressions whose resolution has changed recently"
21+
22+
def columns(self):
23+
return [
24+
"id",
25+
"summary",
26+
"status",
27+
"status_author",
28+
"resolution",
29+
"resolution_comment",
30+
"resolution_previous",
31+
]
32+
33+
def get_extra_for_needinfo_template(self):
34+
return self.extra_ni
35+
36+
def get_bz_params(self, date):
37+
end_date = lmdutils.get_date_ymd("today")
38+
start_date = end_date - timedelta(1)
39+
40+
fields = [
41+
"id",
42+
"history",
43+
"comments.text",
44+
"comments.creation_time",
45+
"comments.author",
46+
]
47+
48+
# Find all bugs that have perf-alert, and regression in their keywords. Search
49+
# for bugs that have been changed in the last day. Only look for bugs after
50+
# October 1st, 2024 to prevent triggering comments on older performance regressions
51+
params = {
52+
"include_fields": fields,
53+
"f3": "creation_ts",
54+
"o3": "greaterthan",
55+
"v3": "2024-10-01T00:00:00Z",
56+
"f1": "regressed_by",
57+
"o1": "isnotempty",
58+
"f2": "keywords",
59+
"o2": "allwords",
60+
"v2": ["regression", "perf-alert"],
61+
"f4": "resolution",
62+
"o4": "changedafter",
63+
"v4": start_date,
64+
"f5": "resolution",
65+
"o5": "changedbefore",
66+
"v5": end_date,
67+
}
68+
69+
return params
70+
71+
def should_needinfo(self, bug_comments, status_time):
72+
# Check if the bugbot has already needinfo'ed on the bug since
73+
# the last status change before making one
74+
for comment in bug_comments[::-1]:
75+
if comment["creation_time"] <= status_time:
76+
break
77+
78+
if comment["author"] == BOT_MAIN_ACCOUNT:
79+
if (
80+
"could you provide a comment explaining the resolution?"
81+
in comment["text"]
82+
):
83+
# Bugbot has already commented on this bug since the last
84+
# status change. No need to comment again since this was
85+
# just a resolution change
86+
return False
87+
88+
return True
89+
90+
def get_resolution_history(self, bug):
91+
bug_info = {}
92+
93+
# Get the last resolution change that was made in this bug
94+
for change in bug["history"][::-1]:
95+
# Get the most recent resolution change first, this is because
96+
# it could have changed since the status was changed and by who
97+
if not bug_info.get("resolution"):
98+
for specific_change in change["changes"]:
99+
if specific_change["field_name"] == "resolution":
100+
bug_info["resolution"] = specific_change["added"]
101+
bug_info["resolution_previous"] = (
102+
specific_change["removed"].strip() or "---"
103+
)
104+
bug_info["resolution_time"] = change["when"]
105+
break
106+
107+
if bug_info.get("resolution"):
108+
# Find the status that the bug was resolved to, and by who
109+
for specific_change in change["changes"]:
110+
if specific_change["field_name"] == "status" and specific_change[
111+
"added"
112+
] in ("RESOLVED", "REOPENED"):
113+
bug_info["status"] = specific_change["added"]
114+
bug_info["status_author"] = change["who"]
115+
bug_info["status_time"] = change["when"]
116+
break
117+
118+
if bug_info.get("status"):
119+
break
120+
121+
return bug_info
122+
123+
def set_autofix(self, bugs):
124+
for bug_id, bug_info in bugs.items():
125+
if bug_info["needinfo"]:
126+
self.extra_ni[bug_id] = {
127+
"resolution": bug_info["resolution"],
128+
"status": bug_info["status"],
129+
}
130+
self.add_auto_ni(
131+
bug_id,
132+
{
133+
"mail": bug_info["status_author"],
134+
"nickname": bug_info["nickname"],
135+
},
136+
)
137+
138+
def get_needinfo_nicks(self, bugs):
139+
def _user_handler(user, data):
140+
data[user["name"]] = user["nick"]
141+
142+
authors_to_ni = set()
143+
for bug_id, bug_info in bugs.items():
144+
if bug_info["needinfo"]:
145+
authors_to_ni.add(bug_info["status_author"])
146+
147+
if not authors_to_ni:
148+
return
149+
150+
user_emails_to_names = {}
151+
BugzillaUser(
152+
user_names=list(authors_to_ni),
153+
include_fields=["nick", "name"],
154+
user_handler=_user_handler,
155+
user_data=user_emails_to_names,
156+
).wait()
157+
158+
for bug_id, bug_info in bugs.items():
159+
if bug_info["needinfo"]:
160+
bug_info["nickname"] = user_emails_to_names[bug_info["status_author"]]
161+
162+
def handle_bug(self, bug, data):
163+
# Match all the resolutions with resolution comments if they exist
164+
bug_id = str(bug["id"])
165+
bug_comments = bug["comments"]
166+
bug_history = self.get_resolution_history(bug)
167+
168+
# Sometimes a resolution comment is not provided so use a default
169+
bug_history["needinfo"] = False
170+
bug_history["resolution_comment"] = "N/A"
171+
for comment in bug_comments[::-1]:
172+
if (
173+
comment["creation_time"] == bug_history["status_time"]
174+
and comment["author"] == bug_history["status_author"]
175+
):
176+
bug_history["resolution_comment"] = comment["text"]
177+
break
178+
else:
179+
bug_history["needinfo"] = self.should_needinfo(
180+
bug_comments, bug_history["status_time"]
181+
)
182+
183+
data[bug_id] = bug_history
184+
185+
return bug
186+
187+
def get_bugs(self, *args, **kwargs):
188+
bugs = super().get_bugs(*args, **kwargs)
189+
self.get_needinfo_nicks(bugs)
190+
self.set_autofix(bugs)
191+
return bugs
192+
193+
194+
if __name__ == "__main__":
195+
PerfAlertResolvedRegression().run()

configs/rules.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,5 +472,8 @@
472472
},
473473
"perfalert_inactive_regression": {
474474
"additional_receivers": ["[email protected]", "[email protected]"]
475+
},
476+
"perfalert_resolved_regression": {
477+
"additional_receivers": ["[email protected]", "[email protected]"]
475478
}
476479
}

scripts/cron_run_daily.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ python -m bugbot.rules.performancebug --production
1818
# Try to detect potential performance alerts that have been inactive for too long
1919
python -m bugbot.rules.perfalert_inactive_regression --production
2020

21+
# Send an email about all performance alerts who were recently resolved
22+
python -m bugbot.rules.perfalert_resolved_regression --production
23+
2124
# Send a mail if the logs are not empty
2225
# MUST ALWAYS BE THE LAST COMMAND
2326
python -m bugbot.log --send
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<p>The following bug list contains performance alerts whose resolution has changed in the last week:</p>
2+
<table {{ table_attrs }}>
3+
<thead>
4+
<tr>
5+
<th>Bug</th>
6+
<th>Summary</th>
7+
<th>Status</th>
8+
<th>Resolution</th>
9+
</tr>
10+
</thead>
11+
<tbody>
12+
{% for i, (bugid, summary, status, status_author, resolution, resolution_comment, resolution_previous) in enumerate(data) -%}
13+
<tr {% if i % 2 == 0 %}bgcolor="#E0E0E0"
14+
{% endif -%}
15+
>
16+
<td>
17+
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{ bugid }}">{{ bugid }}</a>
18+
</td>
19+
<td>
20+
<table>
21+
<tr>
22+
<td>{{ summary | e }}</td>
23+
</tr>
24+
<tr>
25+
<td>
26+
<b>Resolution Comment:</b> {{ resolution_comment }}
27+
</td>
28+
</tr>
29+
<tr>
30+
<td>
31+
<i>Resolved by {{ status_author }}</i>
32+
</td>
33+
</tr>
34+
</table>
35+
</td>
36+
<td>{{ status }}</td>
37+
<td>{{ resolution_previous }} -> {{ resolution }}</td>
38+
</tr>
39+
{% endfor -%}
40+
</tbody>
41+
</table>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
A comment containing a reason for why the performance regression was {{ "resolved as " + extra[bugid]["resolution"] if extra[bugid]["status"].lower() == "resolved" else "reopened" }} could not be found. It should provided when the status of the bug is changed.
2+
3+
:{{ nickname }}, since you resolved this bug, could you provide a comment explaining the resolution? If one has already been provided, this needinfo can be ignored/removed.
4+
5+
If you need additional information/help, reach out in [#perftest](https://matrix.to/#/#perftest:mozilla.org), or [#perfsheriffs](https://matrix.to/#/#perfsheriffs:mozilla.org) on Element.
6+
7+
{{ documentation }}

0 commit comments

Comments
 (0)