app/models/nightly.php (198 lines of code) (raw):
<?php
declare(strict_types=1);
use BzKarma\Scoring;
use ReleaseInsights\{Bugzilla, Json, URL, Request, Utils};
Request::waitingPage('load');
/*
We need previous and next days for navigation and changelog
The requester date is already in the $date variable
*/
$today = date('Ymd');
$requested_date = Utils::getDate();
$previous_date = date('Ymd', strtotime($requested_date . ' -1 day'));
$next_date = date('Ymd', strtotime($requested_date . ' +1 day'));
// We may have to display a warning message because an external resource is down
$warning = '';
// Get nightlies for the GET Request (or today's nightly)
$nightlies = include MODELS . 'api/nightly.php';
// Store a value for the View title
$display_date = strtotime($requested_date);
$fallback_nightly = false;
// This is a fallback mechanism for Buildhub which sometimes takes hours to have the latest nightly
if (empty($nightlies)) {
// Get the latest nightly build ID, used as a tooltip on the nightly version number
$latest_nightly = Json::load(
URL::Archive->value . 'pub/firefox/nightly/latest-mozilla-central/firefox-' . FIREFOX_NIGHTLY . '.en-US.win64.json',
900
);
// We want to make sure that the latest nightly from archive.mozilla.org is not yesterday's nightly
if (isset($latest_nightly['buildid']) && $today === date('Ymd', strtotime((string) $latest_nightly['buildid']))) {
$nightlies = [
$latest_nightly['buildid'] => [
'revision' => $latest_nightly['moz_source_stamp'],
'version' => FIREFOX_NIGHTLY,
],
];
$fallback_nightly = true;
}
unset($latest_nightly);
}
// We now fetch the previous day nightlies because we need them for changelogs
$_GET['date'] = $previous_date;
$nightlies_day_before = include MODELS . 'api/nightly.php';
/*
If we didn't ship any nightly the day before, check previous days.
We don't go further than 7 days back.
*/
if (empty($nightlies_day_before)) {
foreach(range(1,7) as $i) {
$_GET['date'] = date('Ymd', strtotime($requested_date . " -{$i} days"));
$nightlies_day_before = include MODELS . 'api/nightly.php';
if (! empty($nightlies_day_before)) {
break;
}
}
}
// Associate nightly with nightly-1
$nightly_pairs = [];
$i = true;
$previous_changeset = null;
foreach ($nightlies as $buildid => $changeset) {
// The first build of the day is to associate with yesterday's last build
if ($i === true) {
$nightly_pairs[] = [
'buildid' => $buildid,
'changeset' => $changeset['revision'],
'version' => $changeset['version'],
'prev_changeset' => end($nightlies_day_before)['revision'],
];
$i = false;
$previous_changeset = $changeset['revision'];
continue;
}
$nightly_pairs[] = [
'buildid' => $buildid,
'changeset' => $changeset['revision'],
'version' => $changeset['version'],
'prev_changeset' => $previous_changeset,
];
$previous_changeset = $changeset['revision'];
}
$build_crashes = [];
$top_sigs = [];
// We fetch crashes from Socorro for the last 10 days only
$days_elapsed = date_diff(date_create(date($today)), date_create($requested_date))->days;
if ($days_elapsed < 10) {
foreach ($nightly_pairs as $dataset) {
$build_crashes[$dataset['buildid']] = Utils::getCrashesForBuildID($dataset['buildid'])['total'] ?? null;
if (is_null($build_crashes[$dataset['buildid']])) {
$warning = "Socorro is not providing data";
continue;
}
}
foreach ($nightly_pairs as $dataset) {
$sig = Utils::getCrashesForBuildID($dataset['buildid'])['facets']['signature'] ?? null;
if (is_null($sig)) {
$warning = "Socorro is not providing data";
continue;
}
$top_sigs[$dataset['buildid']] = array_splice($sig, 0, 20);
}
}
$bug_list = [];
$bug_list_karma = [];
$bug_list_karma_details = [];
foreach ($nightly_pairs as $dataset) {
$bugs = Bugzilla::getBugsFromHgWeb(
URL::Mercurial->value
. 'mozilla-central/json-pushes?fromchange='
. $dataset['prev_changeset']
. '&tochange='
. $dataset['changeset']
. '&full&version=2'
)['total'];
$bug_list_karma = array_unique([...$bugs,...$bug_list_karma]);
// There were no bugs in the build, it is the same as the previous one
if (empty($bugs)) {
$bug_list[$dataset['buildid']] = [
'bugs' => null,
'url' => '',
'count' => 0,
];
continue;
}
$url = Bugzilla::getBugListLink($bugs);
// Bugzilla REST API https://wiki.mozilla.org/Bugzilla:REST_API
$bug_list_details = Json::load(URL::Bugzilla->value . 'rest/bug?include_fields=id,summary,priority,severity,keywords,product,component,type,duplicates,regressions,cf_webcompat_priority,cf_performance_impact,cf_tracking_firefox' . NIGHTLY . ',cf_tracking_firefox' . BETA . ',cf_tracking_firefox' . RELEASE . ',cf_status_firefox' . NIGHTLY . ',cf_status_firefox' . BETA . ',cf_status_firefox' . RELEASE . ',cc,see_also&bug_id=' . implode('%2C', $bugs));
$bug_list[$dataset['buildid']] = [
'bugs' => $bug_list_details['bugs'],
'url' => $url,
'count' => is_countable($bugs) ? count($bugs) : 0,
];
$bug_list_karma_details = [...$bug_list_details['bugs'], ...$bug_list_karma_details];
}
// Create the real bug list Karma
sort($bug_list_karma);
$bug_list_karma = array_map('intval', $bug_list_karma);
$bug_list_karma = array_values($bug_list_karma);
$bug_list_karma = array_flip($bug_list_karma);
$scores = new Scoring($bug_list_karma_details, RELEASE);
// The $bug_list_karma array has bug numbers as keys and score (ints) as values
foreach ($bug_list_karma as $key => $value) {
$bug_list_karma[$key] = [
'score' => $scores->getBugScore($key),
'details' => $scores->getBugScoreDetails($key),
];
}
$known_top_crashes = [
'IPCError-browser | ShutDownKill | mozilla::ipc::MessagePump::Run',
'IPCError-browser | ShutDownKill | NtYieldExecution',
'IPCError-browser | ShutDownKill | EMPTY: no crashing thread identified; ERROR_NO_MINIDUMP_HEADER',
'IPCError-browser | ShutDownKill',
'OOM | small',
];
$top_sigs_worth_a_bug = [];
foreach ($top_sigs as $values) {
foreach ($values as $target) {
if (in_array($target['term'], $known_top_crashes)) {
continue;
}
if (isset($top_sigs_worth_a_bug[$target['term']])){
$top_sigs_worth_a_bug[$target['term']] += $target['count'];
} else {
$top_sigs_worth_a_bug[$target['term']] = $target['count'];
}
}
}
// We take 10 crashes for a day as a treshold
$top_sigs_worth_a_bug = array_filter($top_sigs_worth_a_bug, fn($n) => $n > 10);
// We escape weird crash signature characters for url use
$top_sigs_worth_a_bug = array_keys($top_sigs_worth_a_bug);
$top_sigs_worth_a_bug = array_map('urlencode', $top_sigs_worth_a_bug);
// Query bugs for signatures
$crash_bugs = [];
if (! empty($top_sigs_worth_a_bug)) {
foreach ($top_sigs_worth_a_bug as $sig) {
$bugs_for_top_sigs = Utils::getBugsforCrashSignature($sig, 30)['hits']; // short 30s cache intended
$tmp = array_column($bugs_for_top_sigs, 'id');
if (!empty($tmp)) {
$crash_bugs[urldecode($sig)] = max(
array_unique(
array_column($bugs_for_top_sigs, 'id')
)
);
}
}
}
// In this section, we extract outstanding bugs
$outstanding_bugs = [];
foreach ($bug_list as $key => $values) {
if (empty($values['bugs'])) {
// We may have a build with no patch from Bugzilla
continue;
}
foreach ($values['bugs'] as $bug_details) {
// Old bugs fixed are often interesting
if ($bug_details['id'] < 1_500_000) {
$outstanding_bugs[$key]['bugs'][] = $bug_details;
continue;
}
// Enhancements are potentiol release notes additions
if ($bug_details['type'] == 'enhancement') {
$outstanding_bugs[$key]['bugs'][] = $bug_details;
continue;
}
// High karma
if ($bug_list_karma[$bug_details['id']]['score'] > 15) {
$outstanding_bugs[$key]['bugs'][] = $bug_details;
}
}
}
Request::waitingPage('hide');
return [
$display_date,
$nightly_pairs,
$build_crashes,
$top_sigs,
$crash_bugs,
$bug_list,
$bug_list_karma,
$outstanding_bugs,
$previous_date,
$requested_date,
$next_date,
$today,
$known_top_crashes,
$fallback_nightly,
$warning,
];