dashboard/app: show info about duplicates and similar bugs

Show info about duplicates and similar bugs in other kernels
on the bug page.
This commit is contained in:
Dmitry Vyukov 2017-12-04 09:00:28 +01:00
parent 48359b9777
commit 96ca35f4c7
5 changed files with 203 additions and 43 deletions

View File

@ -1,3 +1,10 @@
{{/*
Copyright 2017 syzkaller project authors. All rights reserved.
Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
Page with details about a single bug.
*/}}
<!doctype html>
<html>
<head>
@ -7,18 +14,21 @@
<body>
{{template "header" .Header}}
Title: {{.Bug.Title}}<br>
Namespace: {{.Bug.Namespace}}<br>
Crashes: {{.Bug.NumCrashes}}<br>
First: {{formatTime .Bug.FirstTime}}<br>
Last: {{formatTime .Bug.LastTime}}<br>
Reporting: {{if .Bug.Link}}<a href="{{.Bug.Link}}">{{.Bug.Status}}</a>{{else}}{{.Bug.Status}}{{end}}<br>
Commits: {{.Bug.Commits}}<br>
Patched on: {{.Bug.PatchedOn}}<br>
Missing on: {{.Bug.MissingOn}}<br>
<b>[{{.Bug.Namespace}}] {{.Bug.Title}}</b><br>
Status: {{if .Bug.ExternalLink}}<a href="{{.Bug.ExternalLink}}">{{.Bug.Status}}</a>{{else}}{{.Bug.Status}}{{end}}<br>
{{if .Bug.Commits}}
Commits: {{.Bug.Commits}}<br>
Patched on: {{.Bug.PatchedOn}}, missing on: {{.Bug.MissingOn}}<br>
{{end}}
First: {{formatLateness $.Now $.Bug.FirstTime}}, last: {{formatLateness $.Now $.Bug.LastTime}}<br>
<br>
{{template "bug_list" .DupOf}}
{{template "bug_list" .Dups}}
{{template "bug_list" .Similar}}
<table class="list_table">
<caption>Crashes:</caption>
<caption>Crashes ({{.Bug.NumCrashes}}):</caption>
<tr>
<th>Manager</th>
<th>Time</th>

View File

@ -61,13 +61,19 @@ type uiBuild struct {
type uiBugPage struct {
Header *uiHeader
Now time.Time
Bug *uiBug
DupOf *uiBugGroup
Dups *uiBugGroup
Similar *uiBugGroup
Crashes []*uiCrash
}
type uiBugGroup struct {
Namespace string
Bugs []*uiBug
Now time.Time
Caption string
ShowNamespace bool
Bugs []*uiBug
}
type uiBug struct {
@ -184,14 +190,38 @@ func handleBug(c context.Context, w http.ResponseWriter, r *http.Request) error
if err != nil {
return err
}
var dupOf *uiBugGroup
if bug.DupOf != "" {
dup := new(Bug)
if err := datastore.Get(c, datastore.NewKey(c, "Bug", bug.DupOf, 0, nil), dup); err != nil {
return err
}
dupOf = &uiBugGroup{
Now: timeNow(c),
Caption: "Duplicate of",
Bugs: []*uiBug{createUIBug(c, dup, state, managers)},
}
}
uiBug := createUIBug(c, bug, state, managers)
crashes, err := loadCrashesForBug(c, bug)
if err != nil {
return err
}
dups, err := loadDupsForBug(c, bug, state, managers)
if err != nil {
return err
}
similar, err := loadSimilarBugs(c, bug, state)
if err != nil {
return err
}
data := &uiBugPage{
Header: h,
Now: timeNow(c),
Bug: uiBug,
DupOf: dupOf,
Dups: dups,
Similar: similar,
Crashes: crashes,
}
return serveTemplate(w, "bug.html", data)
@ -237,25 +267,111 @@ func fetchBugs(c context.Context) ([]*uiBugGroup, error) {
uiBug := createUIBug(c, bug, state, managers[bug.Namespace])
groups[bug.Namespace] = append(groups[bug.Namespace], uiBug)
}
now := timeNow(c)
var res []*uiBugGroup
for ns, bugs := range groups {
sort.Sort(uiBugSorter(bugs))
res = append(res, &uiBugGroup{
Namespace: ns,
Bugs: bugs,
Now: now,
Caption: fmt.Sprintf("%v (%v)", ns, len(bugs)),
Bugs: bugs,
})
}
sort.Sort(uiBugGroupSorter(res))
return res, nil
}
func createUIBug(c context.Context, bug *Bug, state *ReportingState, managers []string) *uiBug {
_, _, _, reportingIdx, status, link, err := needReport(c, "", state, bug)
func loadDupsForBug(c context.Context, bug *Bug, state *ReportingState, managers []string) (
*uiBugGroup, error) {
bugHash := bugKeyHash(bug.Namespace, bug.Title, bug.Seq)
var dups []*Bug
_, err := datastore.NewQuery("Bug").
Filter("Status=", BugStatusDup).
Filter("DupOf=", bugHash).
GetAll(c, &dups)
if err != nil {
status = err.Error()
return nil, err
}
if status == "" {
status = "???"
var results []*uiBug
for _, dup := range dups {
results = append(results, createUIBug(c, dup, state, managers))
}
group := &uiBugGroup{
Now: timeNow(c),
Caption: "Duplicates",
Bugs: results,
}
return group, nil
}
func loadSimilarBugs(c context.Context, bug *Bug, state *ReportingState) (*uiBugGroup, error) {
var similar []*Bug
_, err := datastore.NewQuery("Bug").
Filter("Title=", bug.Title).
GetAll(c, &similar)
if err != nil {
return nil, err
}
managers := make(map[string][]string)
var results []*uiBug
for _, similar := range similar {
if similar.Namespace == bug.Namespace && similar.Seq == bug.Seq {
continue
}
if managers[similar.Namespace] == nil {
mgrs, err := managerList(c, similar.Namespace)
if err != nil {
return nil, err
}
managers[similar.Namespace] = mgrs
}
results = append(results, createUIBug(c, similar, state, managers[similar.Namespace]))
}
group := &uiBugGroup{
Now: timeNow(c),
Caption: "Similar Bugs",
ShowNamespace: true,
Bugs: results,
}
return group, nil
}
func createUIBug(c context.Context, bug *Bug, state *ReportingState, managers []string) *uiBug {
reportingIdx, status, link := 0, "", ""
var err error
if bug.Status == BugStatusOpen {
_, _, _, reportingIdx, status, link, err = needReport(c, "", state, bug)
if err != nil {
status = err.Error()
}
if status == "" {
status = "???"
}
} else {
for i := range bug.Reporting {
bugReporting := &bug.Reporting[i]
if i == len(bug.Reporting)-1 ||
bug.Status == BugStatusInvalid && !bug.Reporting[i].Closed.IsZero() &&
bug.Reporting[i+1].Closed.IsZero() ||
(bug.Status == BugStatusFixed || bug.Status == BugStatusDup) &&
bug.Reporting[i].Closed.IsZero() {
reportingIdx = i
link = bugReporting.Link
switch bug.Status {
case BugStatusInvalid:
status = "invalid"
case BugStatusFixed:
status = "fixed"
case BugStatusDup:
status = "dup"
default:
status = fmt.Sprintf("unknown (%v)", bug.Status)
}
status = fmt.Sprintf("%v: closed as %v on %v",
bugReporting.Name, status, formatTime(bug.Closed))
break
}
}
}
id := bugKeyHash(bug.Namespace, bug.Title, bug.Seq)
uiBug := &uiBug{
@ -499,6 +615,9 @@ type uiBugSorter []*uiBug
func (a uiBugSorter) Len() int { return len(a) }
func (a uiBugSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a uiBugSorter) Less(i, j int) bool {
if a[i].Namespace != a[j].Namespace {
return a[i].Namespace < a[j].Namespace
}
if a[i].ReportingIndex != a[j].ReportingIndex {
return a[i].ReportingIndex > a[j].ReportingIndex
}
@ -515,4 +634,4 @@ type uiBugGroupSorter []*uiBugGroup
func (a uiBugGroupSorter) Len() int { return len(a) }
func (a uiBugGroupSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a uiBugGroupSorter) Less(i, j int) bool { return a[i].Namespace < a[j].Namespace }
func (a uiBugGroupSorter) Less(i, j int) bool { return a[i].Caption < a[j].Caption }

View File

@ -1,3 +1,10 @@
{{/*
Copyright 2017 syzkaller project authors. All rights reserved.
Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
Main page.
*/}}
<!doctype html>
<html>
<head>
@ -101,28 +108,7 @@
<br><br>
{{range $group := $.BugGroups}}
<table class="list_table">
<caption>{{.Namespace}} ({{len $group.Bugs}}):</caption>
<tr>
<th>Title</th>
<th>Count</th>
<th>Repro</th>
<th>Last</th>
<th>Status</th>
<th>Patched</th>
</tr>
{{range $b := $group.Bugs}}
<tr>
<td class="title"><a href="{{$b.Link}}">{{$b.Title}}</a></td>
<td class="stat {{if $b.NumCrashesBad}}bad{{end}}">{{$b.NumCrashes}}</td>
<td class="stat">{{formatReproLevel $b.ReproLevel}}</td>
<td class="stat">{{formatLateness $.Now $b.LastTime}}</td>
<td class="status">{{if $b.Link}}<a href="{{$b.ExternalLink}}">{{$b.Status}}</a>{{else}}{{$b.Status}}{{end}}</td>
<td class="patched" title="{{$b.Commits}}">{{if $b.Commits}}{{len $b.PatchedOn}}/{{len $b.MissingOn}}{{end}}</td>
</tr>
{{end}}
</table>
<br><br>
{{template "bug_list" $group}}
{{end}}
</body>
</html>

View File

@ -0,0 +1,36 @@
{{/*
Copyright 2017 syzkaller project authors. All rights reserved.
Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
*/}}
{{/* List of bugs, invoked with *uiBugGroup */}}
{{define "bug_list"}}
{{if .}}
{{if .Bugs}}
<table class="list_table">
<caption>{{$.Caption}}:</caption>
<tr>
{{if $.ShowNamespace}}<th>Kernel</th>{{end}}
<th>Title</th>
<th>Count</th>
<th>Repro</th>
<th>Last</th>
<th>Patched</th>
<th>Status</th>
</tr>
{{range $b := .Bugs}}
<tr>
{{if $.ShowNamespace}}<td>{{$b.Namespace}}</td>{{end}}
<td class="title"><a href="{{$b.Link}}">{{$b.Title}}</a></td>
<td class="stat {{if $b.NumCrashesBad}}bad{{end}}">{{$b.NumCrashes}}</td>
<td class="stat">{{formatReproLevel $b.ReproLevel}}</td>
<td class="stat">{{formatLateness $.Now $b.LastTime}}</td>
<td class="patched" title="{{$b.Commits}}">{{if $b.Commits}}{{len $b.PatchedOn}}/{{len $b.MissingOn}}{{end}}</td>
<td class="status">{{if $b.Link}}<a href="{{$b.ExternalLink}}">{{$b.Status}}</a>{{else}}{{$b.Status}}{{end}}</td>
</tr>
{{end}}
</table>
<br>
{{end}}
{{end}}
{{end}}

View File

@ -28,6 +28,7 @@ import (
"golang.org/x/net/context"
"google.golang.org/appengine"
"google.golang.org/appengine/aetest"
"google.golang.org/appengine/datastore"
aemail "google.golang.org/appengine/mail"
"google.golang.org/appengine/user"
)
@ -93,8 +94,16 @@ func caller(skip int) string {
func (c *Ctx) Close() {
if !c.t.Failed() {
// Ensure that we can render bugs in the final test state.
// Ensure that we can render main page and all bugs in the final test state.
c.expectOK(c.GET("/"))
var bugs []*Bug
keys, err := datastore.NewQuery("Bug").GetAll(c.ctx, &bugs)
if err != nil {
c.t.Errorf("ERROR: failed to query bugs: %v", err)
}
for _, key := range keys {
c.expectOK(c.GET(fmt.Sprintf("/bug?id=%v", key.StringID())))
}
c.expectOK(c.GET("/email_poll"))
for len(c.emailSink) != 0 {
c.t.Errorf("ERROR: leftover email: %v", (<-c.emailSink).Body)