diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-12-04 09:00:28 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-12-04 09:00:28 +0100 |
| commit | 96ca35f4c7ac6fecc3f129eb340cfd29f18cfdf1 (patch) | |
| tree | f4c830e9674623545f6da3401e47222a34677335 | |
| parent | 48359b97770f794600de4b58aab8aa069ee993db (diff) | |
dashboard/app: show info about duplicates and similar bugs
Show info about duplicates and similar bugs in other kernels
on the bug page.
| -rw-r--r-- | dashboard/app/bug.html | 30 | ||||
| -rw-r--r-- | dashboard/app/main.go | 139 | ||||
| -rw-r--r-- | dashboard/app/main.html | 30 | ||||
| -rw-r--r-- | dashboard/app/templates.html | 36 | ||||
| -rw-r--r-- | dashboard/app/util_test.go | 11 |
5 files changed, 203 insertions, 43 deletions
diff --git a/dashboard/app/bug.html b/dashboard/app/bug.html index 8ca0b5dd7..1836a2bc0 100644 --- a/dashboard/app/bug.html +++ b/dashboard/app/bug.html @@ -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> diff --git a/dashboard/app/main.go b/dashboard/app/main.go index 70e5f05b6..1091abbeb 100644 --- a/dashboard/app/main.go +++ b/dashboard/app/main.go @@ -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 } diff --git a/dashboard/app/main.html b/dashboard/app/main.html index 6a9c84f75..abdc632f5 100644 --- a/dashboard/app/main.html +++ b/dashboard/app/main.html @@ -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> diff --git a/dashboard/app/templates.html b/dashboard/app/templates.html new file mode 100644 index 000000000..6fe5e011a --- /dev/null +++ b/dashboard/app/templates.html @@ -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}} diff --git a/dashboard/app/util_test.go b/dashboard/app/util_test.go index 5c868b1b5..c9b8f8688 100644 --- a/dashboard/app/util_test.go +++ b/dashboard/app/util_test.go @@ -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) |
