syz-dash: add search over reports

- add claimed status for bugs
- add search over all reports
- some other minor improvements
This commit is contained in:
Dmitry Vyukov 2017-03-05 14:45:59 +01:00
parent 895e034a4d
commit a091396fc1
11 changed files with 208 additions and 52 deletions

View File

@ -68,6 +68,7 @@ const (
BugStatusReported
BugStatusFixed
BugStatusUnclear
BugStatusClaimed
BugStatusClosed = 1000 + iota
BugStatusDeleted
)
@ -82,6 +83,8 @@ func statusToString(status int) string {
return "fixed"
case BugStatusUnclear:
return "unclear"
case BugStatusClaimed:
return "claimed"
case BugStatusClosed:
return "closed"
case BugStatusDeleted:
@ -101,6 +104,8 @@ func stringToStatus(status string) (int, error) {
return BugStatusFixed, nil
case "unclear":
return BugStatusUnclear, nil
case "claimed":
return BugStatusClaimed, nil
case "closed":
return BugStatusClosed, nil
case "deleted":

View File

@ -7,7 +7,7 @@ handlers:
- url: /static
static_dir: static
secure: always
- url: /(|bug|text)
- url: /(|bug|search|text)
script: _go_app
login: required
secure: always

View File

@ -20,6 +20,7 @@
{{else}}
<td><select name="status">
<option value="new" {{if eq .Status "new"}}selected{{end}}>New</option>
<option value="claimed" {{if eq .Status "claimed"}}selected{{end}}>Claimed</option>
<option value="reported" {{if eq .Status "reported"}}selected{{end}}>Reported</option>
<option value="fixed" {{if eq .Status "fixed"}}selected{{end}}>Fixed</option>
<option value="unclear" {{if eq .Status "unclear"}}selected{{end}}>Unclear</option>
@ -149,27 +150,7 @@ new crashes will produce a new bug"/>
<br>
{{end}}
<table class="list_table">
<caption>Crashes:</caption>
<tr>
<th>Title</th>
<th>Manager</th>
<th>Time</th>
<th>Tag</th>
<th>Log</th>
<th>Report</th>
</tr>
{{range $c := $.Crashes}}
<tr>
<td class="title">{{$c.Title}}</td>
<td class="manager">{{$c.Manager}}</td>
<td class="time">{{formatTime $c.Time}}</td>
<td class="tag">{{$c.Tag}}</td>
<td class="log">{{if $c.Log}}<a href="/text?id={{$c.Log}}">log</a>{{end}}</td>
<td class="report">{{if $c.Report}}<a href="/text?id={{$c.Report}}">report</a>{{end}}</td>
</tr>
{{end}}
</table>
{{template "crash_list" $.Crashes}}
<br>
</body>
</html>

View File

@ -1,8 +1,42 @@
{{define "header"}}
<header id="topbar">
<h1><a href="/">syzkaller</a></h1>
{{.Found}} bugs found, {{.Fixed}} bugs fixed, {{.Crashed}} kernels crashed
<table class="position_table">
<tr>
<td>
<h1><a href="/">syzkaller</a></h1>
{{.Found}} bugs found, {{.Fixed}} bugs fixed, {{.Crashed}} kernels crashed
</td>
<td class="search">
<form action="/search">
<input name="query" type="text" size="30" maxlength="1000" value="{{.Query}}" required/>
<input type="submit" value="Search reports" class="button"/>
</form>
</td>
</tr>
</table>
</header>
{{end}}
{{define "crash_list"}}
<table class="list_table">
<caption>{{if .Title}}<a href="{{.Link}}">{{.Title}}</a>{{else}}Crashes:{{end}}</caption>
<tr>
<th>Title</th>
<th>Manager</th>
<th>Time</th>
<th>Tag</th>
<th>Log</th>
<th>Report</th>
</tr>
{{range $c := .List}}
<tr>
<td class="title">{{$c.Title}}</td>
<td class="managers" title="{{$c.Manager}}">{{$c.Manager}}</td>
<td class="time">{{formatTime $c.Time}}</td>
<td class="tag">{{$c.Tag}}</td>
<td class="log">{{if $c.Log}}<a href="/text?id={{$c.Log}}">log</a>{{end}}</td>
<td class="report">{{if $c.Report}}<a href="/text?id={{$c.Report}}">report</a>{{end}}</td>
</tr>
{{end}}
</table>
{{end}}

View File

@ -1,6 +1,6 @@
{{define "bug_table"}}
<table class="list_table">
<caption>{{.Name}}:</caption>
<caption>{{.Name}} ({{len $.Bugs}}):</caption>
<tr>
<th>Title</th>
<th>Count</th>
@ -10,7 +10,7 @@
</tr>
{{range $b := $.Bugs}}
<tr>
<td class="title"><a href="/bug?id={{$b.ID}}">{{$b.Title}}</a></td>
<td class="title"><a href="/bug?id={{$b.ID}}" title="{{$b.Comment}}">{{$b.Title}}</a></td>
<td class="count">{{$b.NumCrashes}}</td>
<td class="repro">{{$b.Repro}}</td>
<td class="time">{{formatTime $b.LastTime}}</td>

View File

@ -6,6 +6,7 @@
package dash
import (
"bytes"
"fmt"
"html/template"
"net/http"
@ -24,6 +25,7 @@ func init() {
http.Handle("/", handlerWrapper(handleAuth(handleDash)))
http.Handle("/bug", handlerWrapper(handleAuth(handleBug)))
http.Handle("/text", handlerWrapper(handleAuth(handleText)))
http.Handle("/search", handlerWrapper(handleAuth(handleSearch)))
http.Handle("/client", handlerWrapper(handleAuth(handleClient)))
}
@ -88,11 +90,18 @@ func handleDash(c appengine.Context, w http.ResponseWriter, r *http.Request) err
data := &dataDash{}
bugGroups := map[int]*uiBugGroup{
BugStatusNew: &uiBugGroup{Name: "New bugs"},
BugStatusClaimed: &uiBugGroup{Name: "Claimed bugs"},
BugStatusReported: &uiBugGroup{Name: "Reported bugs"},
BugStatusUnclear: &uiBugGroup{Name: "Unclear bugs"},
BugStatusFixed: &uiBugGroup{Name: "Fixed bugs"},
}
data.BugGroups = append(data.BugGroups, bugGroups[BugStatusNew], bugGroups[BugStatusReported], bugGroups[BugStatusUnclear], bugGroups[BugStatusFixed])
data.BugGroups = append(data.BugGroups,
bugGroups[BugStatusNew],
bugGroups[BugStatusClaimed],
bugGroups[BugStatusReported],
bugGroups[BugStatusUnclear],
bugGroups[BugStatusFixed],
)
all := r.FormValue("all") != ""
if all {
@ -104,7 +113,7 @@ func handleDash(c appengine.Context, w http.ResponseWriter, r *http.Request) err
var bugs []*Bug
var keys []*ds.Key
var err error
query := ds.NewQuery("Bug").Project("Title", "Status")
query := ds.NewQuery("Bug").Project("Title", "Status", "Comment")
if !all {
query = query.Filter("Status <", BugStatusClosed)
}
@ -116,9 +125,10 @@ func handleDash(c appengine.Context, w http.ResponseWriter, r *http.Request) err
for i, bug := range bugs {
id := keys[i].IntID()
ui := &uiBug{
ID: id,
Title: bug.Title,
Status: statusToString(bug.Status),
ID: id,
Title: bug.Title,
Status: statusToString(bug.Status),
Comment: bug.Comment,
}
bugMap[id] = ui
managers[id] = make(map[string]bool)
@ -211,9 +221,9 @@ func handleBug(c appengine.Context, w http.ResponseWriter, r *http.Request) erro
if reportLink == "" {
return fmt.Errorf("enter report link")
}
case BugStatusUnclear:
case BugStatusClaimed, BugStatusUnclear:
if comment == "" {
return fmt.Errorf("enter comment as to why it's unclear")
return fmt.Errorf("enter comment as to why it's unclear/who claimed it")
}
}
@ -228,7 +238,7 @@ func handleBug(c appengine.Context, w http.ResponseWriter, r *http.Request) erro
if status == BugStatusFixed && len(bug.Patches) == 0 {
return fmt.Errorf("add a patch for fixed bugs")
}
flushCached = bug.Status != status
flushCached = bug.Status != status || bug.Title != title
bug.Title = title
bug.Status = status
bug.ReportLink = reportLink
@ -314,7 +324,7 @@ func handleBug(c appengine.Context, w http.ResponseWriter, r *http.Request) erro
dstBug.Version++
dstBug.ReportLink = mergeStrings(dstBug.ReportLink, srcBug.ReportLink)
dstBug.Comment = mergeStrings(dstBug.Comment, srcBug.Comment)
dstBug.CVE = mergeStrings(dstBug.ReportLink, srcBug.CVE)
dstBug.CVE = mergeStrings(dstBug.CVE, srcBug.CVE)
var groupKeys []*ds.Key
var groups []*Group
for _, hash := range srcBug.Groups {
@ -517,7 +527,7 @@ func handleBug(c appengine.Context, w http.ResponseWriter, r *http.Request) erro
return fmt.Errorf("failed to fetch crashes: %v", err)
}
for _, crash := range crashes {
data.Crashes = append(data.Crashes, &uiCrash{
data.Crashes.List = append(data.Crashes.List, &uiCrash{
Title: group.DisplayTitle(),
Manager: crash.Manager,
Tag: crash.Tag,
@ -545,7 +555,7 @@ func handleBug(c appengine.Context, w http.ResponseWriter, r *http.Request) erro
}
}
sort.Sort(uiCrashArray(data.Crashes))
sort.Sort(uiCrashArray(data.Crashes.List))
sort.Sort(uiReproArray(data.Repros))
if len(data.Groups) == 1 {
@ -576,10 +586,75 @@ func handleText(c appengine.Context, w http.ResponseWriter, r *http.Request) err
return nil
}
func handleSearch(c appengine.Context, w http.ResponseWriter, r *http.Request) error {
cached, err := getCached(c)
if err != nil {
return err
}
data := &dataSearch{
Header: headerFromCached(cached),
}
data.Header.Query = r.FormValue("query")
query := []byte(data.Header.Query)
bugTitles := make(map[int64]string)
for _, b := range cached.Bugs {
bugTitles[b.ID] = b.Title
}
resMap := make(map[int64]*uiCrashGroup)
var groups []*Group
if _, err := ds.NewQuery("Group").GetAll(c, &groups); err != nil {
return fmt.Errorf("failed to fetch crash groups: %v", err)
}
for _, group := range groups {
var crashes []*Crash
if _, err := ds.NewQuery("Crash").Ancestor(group.Key(c)).GetAll(c, &crashes); err != nil {
return fmt.Errorf("failed to fetch crashes: %v", err)
}
for _, crash := range crashes {
if crash.Report == 0 {
continue
}
report, err := getText(c, crash.Report)
if err != nil {
return err
}
if !bytes.Contains(report, query) {
continue
}
cg := resMap[group.Bug]
if cg == nil {
cg = &uiCrashGroup{
Title: bugTitles[group.Bug],
Link: fmt.Sprintf("/bug?id=%v", group.Bug),
}
resMap[group.Bug] = cg
}
cg.List = append(cg.List, &uiCrash{
Title: group.DisplayTitle(),
Manager: crash.Manager,
Tag: crash.Tag,
Time: crash.Time,
Log: crash.Log,
Report: crash.Report,
})
}
}
for _, res := range resMap {
sort.Sort(uiCrashArray(res.List))
data.Results = append(data.Results, res)
}
sort.Sort(uiCrashGroupArray(data.Results))
return templates.ExecuteTemplate(w, "search.html", data)
}
type dataHeader struct {
Found int64
Fixed int64
Crashed int64
Query string
}
func headerFromCached(cached *Cached) *dataHeader {
@ -590,6 +665,11 @@ func headerFromCached(cached *Cached) *dataHeader {
}
}
type dataSearch struct {
Header *dataHeader
Results []*uiCrashGroup
}
type dataDash struct {
Header *dataHeader
BugGroups []*uiBugGroup
@ -598,12 +678,18 @@ type dataDash struct {
type dataBug struct {
Header *dataHeader
uiBug
Crashes []*uiCrash
Crashes uiCrashGroup
Repros []*uiRepro
Message string
AllBugs []*uiBug
}
type uiCrashGroup struct {
Title string
Link string
List []*uiCrash
}
type uiBugGroup struct {
Name string
Bugs []*uiBug
@ -694,6 +780,20 @@ func (a uiCrashArray) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
type uiCrashGroupArray []*uiCrashGroup
func (a uiCrashGroupArray) Len() int {
return len(a)
}
func (a uiCrashGroupArray) Less(i, j int) bool {
return a[i].Title < a[j].Title
}
func (a uiCrashGroupArray) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
type uiReproArray []*uiRepro
func (a uiReproArray) Len() int {

View File

@ -14,6 +14,7 @@ indexes:
properties:
- name: Status
- name: Title
- name: Comment
- kind: Crash
ancestor: yes

View File

@ -75,7 +75,7 @@ func buildCached(c appengine.Context) (*Cached, error) {
})
}
switch bug.Status {
case BugStatusNew, BugStatusReported, BugStatusUnclear:
case BugStatusNew, BugStatusReported, BugStatusUnclear, BugStatusClaimed:
cached.Found++
case BugStatusFixed, BugStatusClosed:
cached.Found++
@ -95,7 +95,7 @@ func buildCached(c appengine.Context) (*Cached, error) {
return nil, fmt.Errorf("failed to find bug for crash %v (%v)", group.Title, group.Seq)
}
switch status {
case BugStatusNew, BugStatusReported, BugStatusFixed, BugStatusUnclear, BugStatusClosed:
case BugStatusNew, BugStatusReported, BugStatusFixed, BugStatusUnclear, BugStatusClaimed, BugStatusClosed:
cached.Crashed += group.NumCrashes
case BugStatusDeleted:
default:

View File

@ -34,16 +34,7 @@ func parsePatch(text string) (title string, diff string, err error) {
continue
}
if strings.HasPrefix(ln, "Subject: ") {
ln = ln[len("Subject: "):]
if strings.Contains(strings.ToLower(ln), "[patch") {
pos := strings.IndexByte(ln, ']')
if pos == -1 {
err = fmt.Errorf("subject line does not contain ']'")
return
}
ln = ln[pos+1:]
}
title = ln
title = ln[len("Subject: "):]
continue
}
if ln == "" || title != "" || diffStarted {
@ -57,6 +48,14 @@ func parsePatch(text string) (title string, diff string, err error) {
if err = s.Err(); err != nil {
return
}
if strings.Contains(strings.ToLower(title), "[patch") {
pos := strings.IndexByte(title, ']')
if pos == -1 {
err = fmt.Errorf("title contains '[patch' but not ']'")
return
}
title = title[pos+1:]
}
title = strings.TrimSpace(title)
if title == "" {
err = fmt.Errorf("failed to extract title")

18
syz-dash/search.html Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<title>Syzkaller Dashboard</title>
<link rel="stylesheet" href="/static/style.css"/>
</head>
<body>
{{template "header" .Header}}
{{if .Results}}
{{range $r := .Results}}
{{template "crash_list" $r}}
{{end}}
{{else}}
<b>No matches found</b>
{{end}}
</body>
</html>

View File

@ -35,6 +35,22 @@ table td, table th {
overflow: hidden;
}
.position_table {
border: 0px;
margin: 0px;
width: 100%;
border-collapse: collapse;
}
.position_table td, .position_table tr {
vertical-align: center;
padding: 0px;
}
.position_table .search {
text-align: right;
}
.list_table td, .list_table th {
border-left: 1px solid #ccc;
}
@ -62,7 +78,9 @@ table td, table th {
.list_table .tag {
font-family: monospace;
font-size: 9pt;
font-size: 8pt;
width: 200pt;
max-width: 200pt;
}
.list_table .opts {