diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | dashboard/app/handler.go | 87 | ||||
| -rw-r--r-- | dashboard/app/main.go | 3 | ||||
| -rw-r--r-- | dashboard/app/reporting.go | 5 | ||||
| -rw-r--r-- | dashboard/app/static/common.js | 31 | ||||
| -rw-r--r-- | dashboard/app/static/style.css | 16 | ||||
| -rw-r--r-- | pkg/html/generated.go | 221 | ||||
| -rw-r--r-- | pkg/html/html.go | 110 | ||||
| -rw-r--r-- | syz-manager/html.go | 313 |
9 files changed, 521 insertions, 267 deletions
@@ -175,7 +175,7 @@ generate: generate_go generate_sys $(MAKE) format generate_go: bin/syz-sysgen format_cpp - $(GO) generate ./pkg/csource ./executor ./pkg/ifuzz ./pkg/build + $(GO) generate ./pkg/csource ./executor ./pkg/ifuzz ./pkg/build ./pkg/html generate_sys: bin/syz-sysgen bin/syz-sysgen diff --git a/dashboard/app/handler.go b/dashboard/app/handler.go index 3bde1cab5..167ca5857 100644 --- a/dashboard/app/handler.go +++ b/dashboard/app/handler.go @@ -5,12 +5,9 @@ package dash import ( "bytes" - "fmt" - "html/template" "net/http" - "time" - "github.com/google/syzkaller/dashboard/dashapi" + "github.com/google/syzkaller/pkg/html" "golang.org/x/net/context" "google.golang.org/appengine" "google.golang.org/appengine/log" @@ -90,84 +87,4 @@ func commonHeader(c context.Context, r *http.Request) *uiHeader { return h } -func formatTime(t time.Time) string { - if t.IsZero() { - return "" - } - return t.Format("2006/01/02 15:04") -} - -func formatClock(t time.Time) string { - if t.IsZero() { - return "" - } - return t.Format("15:04") -} - -func formatDuration(d time.Duration) string { - if d == 0 { - return "" - } - days := int(d / (24 * time.Hour)) - hours := int(d / time.Hour % 24) - mins := int(d / time.Minute % 60) - if days >= 10 { - return fmt.Sprintf("%vd", days) - } else if days != 0 { - return fmt.Sprintf("%vd%02vh", days, hours) - } else if hours != 0 { - return fmt.Sprintf("%vh%02vm", hours, mins) - } - return fmt.Sprintf("%vm", mins) -} - -func formatLateness(now, t time.Time) string { - if t.IsZero() { - return "never" - } - d := now.Sub(t) - if d < 5*time.Minute { - return "now" - } - return formatDuration(d) -} - -func formatReproLevel(l dashapi.ReproLevel) string { - switch l { - case ReproLevelSyz: - return "syz" - case ReproLevelC: - return "C" - default: - return "" - } -} - -func formatStat(v int64) string { - if v == 0 { - return "" - } - return fmt.Sprint(v) -} - -func formatShortHash(v string) string { - const hashLen = 8 - if len(v) <= hashLen { - return v - } - return v[:hashLen] -} - -var ( - templates = template.Must(template.New("").Funcs(templateFuncs).ParseGlob("*.html")) - - templateFuncs = template.FuncMap{ - "formatTime": formatTime, - "formatClock": formatClock, - "formatDuration": formatDuration, - "formatLateness": formatLateness, - "formatReproLevel": formatReproLevel, - "formatStat": formatStat, - "formatShortHash": formatShortHash, - } -) +var templates = html.CreateGlob("*.html") diff --git a/dashboard/app/main.go b/dashboard/app/main.go index 02dbfb8d9..525cd1c04 100644 --- a/dashboard/app/main.go +++ b/dashboard/app/main.go @@ -14,6 +14,7 @@ import ( "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" + "github.com/google/syzkaller/pkg/html" "golang.org/x/net/context" "google.golang.org/appengine/datastore" "google.golang.org/appengine/log" @@ -570,7 +571,7 @@ func createUIBug(c context.Context, bug *Bug, state *ReportingState, managers [] default: status = fmt.Sprintf("unknown (%v)", bug.Status) } - status = fmt.Sprintf("%v on %v", status, formatTime(bug.Closed)) + status = fmt.Sprintf("%v on %v", status, html.FormatTime(bug.Closed)) break } } diff --git a/dashboard/app/reporting.go b/dashboard/app/reporting.go index 0fd2d7aa3..4982c8ade 100644 --- a/dashboard/app/reporting.go +++ b/dashboard/app/reporting.go @@ -14,6 +14,7 @@ import ( "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" + "github.com/google/syzkaller/pkg/html" "golang.org/x/net/context" "google.golang.org/appengine/datastore" "google.golang.org/appengine/log" @@ -97,7 +98,7 @@ func needReport(c context.Context, typ string, state *ReportingState, bug *Bug) if !bugReporting.Reported.IsZero() && bugReporting.ReproLevel >= bug.ReproLevel { status = fmt.Sprintf("%v: reported%v on %v", reporting.DisplayTitle, reproStr(bugReporting.ReproLevel), - formatTime(bugReporting.Reported)) + html.FormatTime(bugReporting.Reported)) reporting, bugReporting = nil, nil return } @@ -149,7 +150,7 @@ func needReport(c context.Context, typ string, state *ReportingState, bug *Bug) status = fmt.Sprintf("%v: ready to report", reporting.DisplayTitle) if !bugReporting.Reported.IsZero() { status += fmt.Sprintf(" (reported%v on %v)", - reproStr(bugReporting.ReproLevel), formatTime(bugReporting.Reported)) + reproStr(bugReporting.ReproLevel), html.FormatTime(bugReporting.Reported)) } return } diff --git a/dashboard/app/static/common.js b/dashboard/app/static/common.js index 5a146f3bd..5c20f7df9 100644 --- a/dashboard/app/static/common.js +++ b/dashboard/app/static/common.js @@ -3,28 +3,22 @@ function sortTable(item, colName, conv, desc = false) { table = item.parentNode.parentNode.parentNode; - rows = table.getElementsByTagName("tr"); + rows = table.rows; col = findColumnByName(rows[0].getElementsByTagName("th"), colName); - values = new Array; + values = []; for (i = 1; i < rows.length; i++) - values[i] = conv(rows[i].getElementsByTagName("td")[col].textContent); + values.push([conv(rows[i].getElementsByTagName("td")[col].textContent), rows[i]]); if (desc) desc = !isSorted(values.slice().reverse()) else desc = isSorted(values); - do { - changed = false; - for (i = 1; i < values.length - 1; i++) { - v0 = values[i]; - v1 = values[i + 1]; - if (desc && v0 >= v1 || !desc && v0 <= v1) - continue; - changed = true; - values[i] = v1; - values[i + 1] = v0; - rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); - } - } while (changed); + values.sort(function(a, b) { + if (a[0] == b[0]) return 0; + if (desc && a[0] > b[0] || !desc && a[0] < b[0]) return -1; + return 1; + }); + for (i = 0; i < values.length; i++) + table.appendChild(values[i][1]); return false; } @@ -37,8 +31,8 @@ function findColumnByName(headers, colName) { } function isSorted(values) { - for (i = 1; i < rows.length - 1; i++) { - if (values[i] > values[i + 1]) + for (i = 0; i < values.length - 1; i++) { + if (values[i][0] > values[i + 1][0]) return false; } return true; @@ -46,6 +40,7 @@ function isSorted(values) { function textSort(v) { return v.toLowerCase(); } function numSort(v) { return -parseInt(v); } +function floatSort(v) { return -parseFloat(v); } function reproSort(v) { return v == "C" ? 0 : v == "syz" ? 1 : 2; } function patchedSort(v) { return v == "" ? -1 : parseInt(v); } diff --git a/dashboard/app/static/style.css b/dashboard/app/static/style.css index cb9e6e378..536cd82ab 100644 --- a/dashboard/app/static/style.css +++ b/dashboard/app/static/style.css @@ -122,11 +122,27 @@ table td, table th { text-align: right; } +.list_table .stat_name { + width: 150pt; + max-width: 150pt; + font-family: monospace; +} + +.list_table .stat_value { + width: 100pt; + max-width: 100pt; + font-family: monospace; +} + .bad { color: #f00; font-weight: bold; } +.inactive { + color: #888; +} + .plain { text-decoration: none; } diff --git a/pkg/html/generated.go b/pkg/html/generated.go new file mode 100644 index 000000000..7c5157aab --- /dev/null +++ b/pkg/html/generated.go @@ -0,0 +1,221 @@ +package html + +const style = ` +#topbar { + padding: 5px 10px; + background: #E0EBF5; +} + +#topbar a { + color: #375EAB; + text-decoration: none; +} + +h1, h2, h3, h4 { + margin: 0; + padding: 0; + color: #375EAB; + font-weight: bold; +} + +table { + border: 1px solid #ccc; + margin: 20px 5px; + border-collapse: collapse; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +table caption { + font-weight: bold; +} + +table td, table th { + vertical-align: top; + padding: 2px 8px; + text-overflow: ellipsis; + 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; +} + +.list_table th { + background: #F4F4F4; +} + +.list_table tr:nth-child(2n+1) { + background: #F4F4F4; +} + +.list_table tr:hover { + background: #ffff99; +} + +.list_table .namespace { + width: 100pt; + max-width: 100pt; +} + +.list_table .title { + width: 350pt; + max-width: 350pt; +} + +.list_table .tag { + font-family: monospace; + font-size: 8pt; + width: 40pt; + max-width: 40pt; +} + +.list_table .opts { + width: 40pt; + max-width: 40pt; +} + +.list_table .status { + width: 250pt; + max-width: 250pt; +} + +.list_table .patched { + width: 60pt; + max-width: 60pt; + text-align: center; +} + +.list_table .kernel { + width: 60pt; + max-width: 60pt; +} + +.list_table .maintainers { + width: 150pt; + max-width: 150pt; +} + +.list_table .result { + width: 60pt; + max-width: 60pt; +} + +.list_table .stat { + width: 50pt; + max-width: 50pt; + font-family: monospace; + text-align: right; +} + +.list_table .stat_name { + width: 150pt; + max-width: 150pt; + font-family: monospace; +} + +.list_table .stat_value { + width: 100pt; + max-width: 100pt; + font-family: monospace; +} + +.bad { + color: #f00; + font-weight: bold; +} + +.inactive { + color: #888; +} + +.plain { + text-decoration: none; +} + +textarea { + width:100%; + font-family: monospace; +} +` +const js = ` +// Copyright 2018 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. + +function sortTable(item, colName, conv, desc = false) { + table = item.parentNode.parentNode.parentNode; + rows = table.rows; + col = findColumnByName(rows[0].getElementsByTagName("th"), colName); + values = []; + for (i = 1; i < rows.length; i++) + values.push([conv(rows[i].getElementsByTagName("td")[col].textContent), rows[i]]); + if (desc) + desc = !isSorted(values.slice().reverse()) + else + desc = isSorted(values); + values.sort(function(a, b) { + if (a[0] == b[0]) return 0; + if (desc && a[0] > b[0] || !desc && a[0] < b[0]) return -1; + return 1; + }); + for (i = 0; i < values.length; i++) + table.appendChild(values[i][1]); + return false; +} + +function findColumnByName(headers, colName) { + for (i = 0; i < headers.length; i++) { + if (headers[i].textContent == colName) + return i; + } + return 0; +} + +function isSorted(values) { + for (i = 0; i < values.length - 1; i++) { + if (values[i][0] > values[i + 1][0]) + return false; + } + return true; +} + +function textSort(v) { return v.toLowerCase(); } +function numSort(v) { return -parseInt(v); } +function floatSort(v) { return -parseFloat(v); } +function reproSort(v) { return v == "C" ? 0 : v == "syz" ? 1 : 2; } +function patchedSort(v) { return v == "" ? -1 : parseInt(v); } + +function timeSort(v) { + if (v == "now") + return 0; + m = v.indexOf('m'); + h = v.indexOf('h'); + d = v.indexOf('d'); + if (m > 0 && h < 0) + return parseInt(v); + if (h > 0 && m > 0) + return parseInt(v) * 60 + parseInt(v.substring(h + 1)); + if (d > 0 && h > 0) + return parseInt(v) * 60 * 24 + parseInt(v.substring(d + 1)) * 60; + if (d > 0) + return parseInt(v) * 60 * 24; + return 1000000000; +} +` diff --git a/pkg/html/html.go b/pkg/html/html.go new file mode 100644 index 000000000..c7f85da88 --- /dev/null +++ b/pkg/html/html.go @@ -0,0 +1,110 @@ +// Copyright 2018 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. + +//go:generate bash -c "echo '// AUTOGENERATED FILE' > generated.go" +//go:generate bash -c "echo 'package html' > generated.go" +//go:generate bash -c "echo 'const style = `' >> generated.go" +//go:generate bash -c "cat ../../dashboard/app/static/style.css >> generated.go" +//go:generate bash -c "echo '`' >> generated.go" +//go:generate bash -c "echo 'const js = `' >> generated.go" +//go:generate bash -c "cat ../../dashboard/app/static/common.js >> generated.go" +//go:generate bash -c "echo '`' >> generated.go" + +package html + +import ( + "fmt" + "html/template" + "strings" + "time" + + "github.com/google/syzkaller/dashboard/dashapi" +) + +func CreatePage(page string) *template.Template { + const headTempl = `<style type="text/css" media="screen">%v</style><script>%v</script>` + page = strings.Replace(page, "{{HEAD}}", fmt.Sprintf(headTempl, style, js), 1) + return template.Must(template.New("").Funcs(funcs).Parse(page)) +} + +func CreateGlob(glob string) *template.Template { + return template.Must(template.New("").Funcs(funcs).ParseGlob(glob)) +} + +var funcs = template.FuncMap{ + "formatTime": FormatTime, + "formatClock": formatClock, + "formatDuration": formatDuration, + "formatLateness": formatLateness, + "formatReproLevel": formatReproLevel, + "formatStat": formatStat, + "formatShortHash": formatShortHash, +} + +func FormatTime(t time.Time) string { + if t.IsZero() { + return "" + } + return t.Format("2006/01/02 15:04") +} + +func formatClock(t time.Time) string { + if t.IsZero() { + return "" + } + return t.Format("15:04") +} + +func formatDuration(d time.Duration) string { + if d == 0 { + return "" + } + days := int(d / (24 * time.Hour)) + hours := int(d / time.Hour % 24) + mins := int(d / time.Minute % 60) + if days >= 10 { + return fmt.Sprintf("%vd", days) + } else if days != 0 { + return fmt.Sprintf("%vd%02vh", days, hours) + } else if hours != 0 { + return fmt.Sprintf("%vh%02vm", hours, mins) + } + return fmt.Sprintf("%vm", mins) +} + +func formatLateness(now, t time.Time) string { + if t.IsZero() { + return "never" + } + d := now.Sub(t) + if d < 5*time.Minute { + return "now" + } + return formatDuration(d) +} + +func formatReproLevel(l dashapi.ReproLevel) string { + switch l { + case dashapi.ReproLevelSyz: + return "syz" + case dashapi.ReproLevelC: + return "C" + default: + return "" + } +} + +func formatStat(v int64) string { + if v == 0 { + return "" + } + return fmt.Sprint(v) +} + +func formatShortHash(v string) string { + const hashLen = 8 + if len(v) <= hashLen { + return v + } + return v[:hashLen] +} diff --git a/syz-manager/html.go b/syz-manager/html.go index f9f96933a..777dcd39b 100644 --- a/syz-manager/html.go +++ b/syz-manager/html.go @@ -6,7 +6,6 @@ package main import ( "bufio" "fmt" - "html/template" "io" "io/ioutil" "net" @@ -21,13 +20,12 @@ import ( "time" "github.com/google/syzkaller/pkg/cover" + "github.com/google/syzkaller/pkg/html" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" ) -const dateFormat = "Jan 02 2006 15:04:05 MST" - func (mgr *Manager) initHTTP() { http.HandleFunc("/", mgr.httpSummary) http.HandleFunc("/syscalls", mgr.httpSyscalls) @@ -38,6 +36,7 @@ func (mgr *Manager) initHTTP() { http.HandleFunc("/file", mgr.httpFile) http.HandleFunc("/report", mgr.httpReport) http.HandleFunc("/rawcover", mgr.httpRawCover) + http.HandleFunc("/input", mgr.httpInput) // Browsers like to request this, without special handler this goes to / handler. http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) @@ -83,8 +82,9 @@ func (mgr *Manager) httpSyscalls(w http.ResponseWriter, r *http.Request) { Cover: len(cc.cov), }) } - sort.Sort(UICallTypeArray(data.Calls)) - + sort.Slice(data.Calls, func(i, j int) bool { + return data.Calls[i].Name < data.Calls[j].Name + }) if err := syscallsTemplate.Execute(w, data); err != nil { http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) @@ -104,7 +104,7 @@ func (mgr *Manager) collectStats() []UIStat { stats := []UIStat{ {Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)}, {Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)}, - {Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))}, + {Name: "corpus", Value: fmt.Sprint(len(mgr.corpus)), Link: "/corpus"}, {Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))}, {Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"}, {Name: "signal", Value: fmt.Sprint(mgr.corpusSignal.Len())}, @@ -124,7 +124,9 @@ func (mgr *Manager) collectStats() []UIStat { intStats := convertStats(mgr.stats.all(), secs) intStats = append(intStats, convertStats(mgr.fuzzerStats, secs)...) - sort.Sort(UIStatArray(intStats)) + sort.Slice(intStats, func(i, j int) bool { + return intStats[i].Name < intStats[j].Name + }) stats = append(stats, intStats...) return stats } @@ -164,7 +166,7 @@ func (mgr *Manager) collectSyscallInfo() map[string]*CallCov { func (mgr *Manager) httpCrash(w http.ResponseWriter, r *http.Request) { crashID := r.FormValue("id") - crash := readCrash(mgr.cfg.Workdir, crashID, nil, true) + crash := readCrash(mgr.cfg.Workdir, crashID, nil, mgr.startTime, true) if crash == nil { http.Error(w, fmt.Sprintf("failed to read crash info"), http.StatusInternalServerError) return @@ -179,10 +181,11 @@ func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) { mgr.mu.Lock() defer mgr.mu.Unlock() - var data []UIInput - call := r.FormValue("call") + data := UICorpus{ + Call: r.FormValue("call"), + } for sig, inp := range mgr.corpus { - if call != inp.Call { + if data.Call != "" && data.Call != inp.Call { continue } p, err := mgr.target.Deserialize(inp.Prog) @@ -190,14 +193,19 @@ func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("failed to deserialize program: %v", err), http.StatusInternalServerError) return } - data = append(data, UIInput{ + data.Inputs = append(data.Inputs, &UIInput{ + Sig: sig, Short: p.String(), - Full: string(inp.Prog), Cover: len(inp.Cover), - Sig: sig, }) } - sort.Sort(UIInputArray(data)) + sort.Slice(data.Inputs, func(i, j int) bool { + a, b := data.Inputs[i], data.Inputs[j] + if a.Cover != b.Cover { + return a.Cover > b.Cover + } + return a.Short < b.Short + }) if err := corpusTemplate.Execute(w, data); err != nil { http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) @@ -308,7 +316,9 @@ func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) { for i, p := range prios[idx] { data.Prios = append(data.Prios, UIPrio{mgr.target.Syscalls[i].Name, p}) } - sort.Sort(UIPrioArray(data.Prios)) + sort.Slice(data.Prios, func(i, j int) bool { + return data.Prios[i].Prio > data.Prios[j].Prio + }) if err := prioTemplate.Execute(w, data); err != nil { http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) @@ -333,6 +343,18 @@ func (mgr *Manager) httpFile(w http.ResponseWriter, r *http.Request) { io.Copy(w, f) } +func (mgr *Manager) httpInput(w http.ResponseWriter, r *http.Request) { + mgr.mu.Lock() + defer mgr.mu.Unlock() + inp, ok := mgr.corpus[r.FormValue("sig")] + if !ok { + http.Error(w, "can't find the input", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Write(inp.Prog) +} + func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) { mgr.mu.Lock() defer mgr.mu.Unlock() @@ -413,16 +435,18 @@ func (mgr *Manager) collectCrashes(workdir string) ([]*UICrashType, error) { } var crashTypes []*UICrashType for _, dir := range dirs { - crash := readCrash(workdir, dir, repros, false) + crash := readCrash(workdir, dir, repros, mgr.startTime, false) if crash != nil { crashTypes = append(crashTypes, crash) } } - sort.Sort(UICrashTypeArray(crashTypes)) + sort.Slice(crashTypes, func(i, j int) bool { + return strings.ToLower(crashTypes[i].Description) < strings.ToLower(crashTypes[j].Description) + }) return crashTypes, nil } -func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashType { +func readCrash(workdir, dir string, repros map[string]bool, start time.Time, full bool) *UICrashType { if len(dir) != 40 { return nil } @@ -478,7 +502,7 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT crash.Log = filepath.Join("crashes", dir, "log"+index) if stat, err := os.Stat(filepath.Join(workdir, crash.Log)); err == nil { crash.Time = stat.ModTime() - crash.TimeStr = crash.Time.Format(dateFormat) + crash.Active = crash.Time.After(start) } tag, _ := ioutil.ReadFile(filepath.Join(crashdir, dir, "tag"+index)) crash.Tag = string(tag) @@ -487,13 +511,16 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT crash.Report = reportFile } } - sort.Sort(UICrashArray(crashes)) + sort.Slice(crashes, func(i, j int) bool { + return crashes[i].Time.After(crashes[j].Time) + }) } triaged := reproStatus(hasRepro, hasCRepro, repros[desc], reproAttempts >= maxReproAttempts) return &UICrashType{ Description: desc, - LastTime: modTime.Format(dateFormat), + LastTime: modTime, + Active: modTime.After(start), ID: dir, Count: len(crashes), Triaged: triaged, @@ -537,7 +564,8 @@ type UISyscallsData struct { type UICrashType struct { Description string - LastTime string + LastTime time.Time + Active bool ID string Count int Triaged string @@ -545,12 +573,12 @@ type UICrashType struct { } type UICrash struct { - Index int - Time time.Time - TimeStr string - Log string - Report string - Tag string + Index int + Time time.Time + Active bool + Log string + Report string + Tag string } type UIStat struct { @@ -565,84 +593,57 @@ type UICallType struct { Cover int } +type UICorpus struct { + Call string + Inputs []*UIInput +} + type UIInput struct { + Sig string Short string - Full string - Calls int Cover int - Sig string } -type UICallTypeArray []UICallType - -func (a UICallTypeArray) Len() int { return len(a) } -func (a UICallTypeArray) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a UICallTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type UIInputArray []UIInput - -func (a UIInputArray) Len() int { return len(a) } -func (a UIInputArray) Less(i, j int) bool { return a[i].Cover > a[j].Cover } -func (a UIInputArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type UIStatArray []UIStat - -func (a UIStatArray) Len() int { return len(a) } -func (a UIStatArray) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a UIStatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type UICrashTypeArray []*UICrashType - -func (a UICrashTypeArray) Len() int { return len(a) } -func (a UICrashTypeArray) Less(i, j int) bool { return a[i].Description < a[j].Description } -func (a UICrashTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type UICrashArray []*UICrash - -func (a UICrashArray) Len() int { return len(a) } -func (a UICrashArray) Less(i, j int) bool { return a[i].Time.After(a[j].Time) } -func (a UICrashArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -var summaryTemplate = template.Must(template.New("").Parse(addStyle(` +var summaryTemplate = html.CreatePage(` <!doctype html> <html> <head> <title>{{.Name }} syzkaller</title> - {{STYLE}} + {{HEAD}} </head> <body> <b>{{.Name }} syzkaller</b> <br> -<br> -<table> +<table class="list_table"> <caption>Stats:</caption> {{range $s := $.Stats}} <tr> - <td>{{$s.Name}}</td> - {{if $s.Link}} - <td><a href="{{$s.Link}}">{{$s.Value}}</a></td> - {{else}} - <td>{{$s.Value}}</td> - {{end}} + <td class="stat_name">{{$s.Name}}</td> + <td class="stat_value"> + {{if $s.Link}} + <a href="{{$s.Link}}">{{$s.Value}}</a> + {{else}} + {{$s.Value}} + {{end}} + </td> </tr> {{end}} </table> -<br> -<table> +<table class="list_table"> <caption>Crashes:</caption> <tr> - <th>Description</th> - <th>Count</th> - <th>Last Time</th> - <th>Report</th> + <th><a onclick="return sortTable(this, 'Description', textSort)" href="#">Description</a></th> + <th><a onclick="return sortTable(this, 'Count', numSort)" href="#">Count</a></th> + <th><a onclick="return sortTable(this, 'Last Time', textSort, true)" href="#">Last Time</a></th> + <th><a onclick="return sortTable(this, 'Report', textSort)" href="#">Report</a></th> </tr> {{range $c := $.Crashes}} <tr> - <td><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td> - <td>{{$c.Count}}</td> - <td>{{$c.LastTime}}</td> + <td class="title"><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td> + <td class="stat {{if not $c.Active}}inactive{{end}}">{{$c.Count}}</td> + <td class="time {{if not $c.Active}}inactive{{end}}">{{formatTime $c.LastTime}}</td> <td> {{if $c.Triaged}} <a href="/report?id={{$c.ID}}">{{$c.Triaged}}</a> @@ -651,11 +652,10 @@ var summaryTemplate = template.Must(template.New("").Parse(addStyle(` </tr> {{end}} </table> -<br> <b>Log:</b> <br> -<textarea id="log_textarea" readonly rows="20"> +<textarea id="log_textarea" readonly rows="20" wrap=off> {{.Log}} </textarea> <script> @@ -663,44 +663,52 @@ var summaryTemplate = template.Must(template.New("").Parse(addStyle(` textarea.scrollTop = textarea.scrollHeight; </script> </body></html> -`))) +`) -var syscallsTemplate = template.Must(template.New("").Parse(addStyle(` +var syscallsTemplate = html.CreatePage(` <!doctype html> <html> <head> <title>{{.Name }} syzkaller</title> - {{STYLE}} + {{HEAD}} </head> <body> -<b>Per-call coverage:</b> -<br> -{{range $c := $.Calls}} - {{$c.Name}} - <a href='/corpus?call={{$c.Name}}'>inputs:{{$c.Inputs}}</a> - <a href='/cover?call={{$c.Name}}'>cover:{{$c.Cover}}</a> - <a href='/prio?call={{$c.Name}}'>prio</a> <br> -{{end}} + +<table class="list_table"> + <caption>Per-syscall coverage:</caption> + <tr> + <th><a onclick="return sortTable(this, 'Syscall', textSort)" href="#">Syscall</a></th> + <th><a onclick="return sortTable(this, 'Inputs', numSort)" href="#">Inputs</a></th> + <th><a onclick="return sortTable(this, 'Coverage', numSort)" href="#">Coverage</a></th> + <th>Prio</th> + </tr> + {{range $c := $.Calls}} + <tr> + <td>{{$c.Name}}</td> + <td><a href='/corpus?call={{$c.Name}}'>{{$c.Inputs}}</a></td> + <td><a href='/cover?call={{$c.Name}}'>{{$c.Cover}}</a></td> + <td><a href='/prio?call={{$c.Name}}'>prio</a></td> + </tr> + {{end}} +</table> </body></html> -`))) +`) -var crashTemplate = template.Must(template.New("").Parse(addStyle(` +var crashTemplate = html.CreatePage(` <!doctype html> <html> <head> <title>{{.Description}}</title> - {{STYLE}} + {{HEAD}} </head> <body> <b>{{.Description}}</b> -<br><br> {{if .Triaged}} Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a> {{end}} -<br><br> -<table> +<table class="list_table"> <tr> <th>#</th> <th>Log</th> @@ -712,34 +720,43 @@ Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a> <tr> <td>{{$c.Index}}</td> <td><a href="/file?name={{$c.Log}}">log</a></td> - {{if $c.Report}} - <td><a href="/file?name={{$c.Report}}">report</a></td> - {{else}} - <td></td> - {{end}} - <td>{{$c.TimeStr}}</td> - <td>{{$c.Tag}}</td> + <td> + {{if $c.Report}} + <a href="/file?name={{$c.Report}}">report</a></td> + {{end}} + </td> + <td class="time {{if not $c.Active}}inactive{{end}}">{{formatTime $c.Time}}</td> + <td class="tag {{if not $c.Active}}inactive{{end}}" title="{{$c.Tag}}">{{formatShortHash $c.Tag}}</td> </tr> {{end}} </table> </body></html> -`))) +`) -var corpusTemplate = template.Must(template.New("").Parse(addStyle(` +var corpusTemplate = html.CreatePage(` <!doctype html> <html> <head> <title>syzkaller corpus</title> - {{STYLE}} + {{HEAD}} </head> <body> -{{range $c := $}} - <span title="{{$c.Full}}">{{$c.Short}}</span> - <a href='/cover?input={{$c.Sig}}'>cover:{{$c.Cover}}</a> - <br> -{{end}} + +<table class="list_table"> + <caption>Corpus{{if $.Call}} for {{$.Call}}{{end}}:</caption> + <tr> + <th>Coverage</th> + <th>Program</th> + </tr> + {{range $inp := $.Inputs}} + <tr> + <td><a href='/cover?input={{$inp.Sig}}'>{{$inp.Cover}}</a></td> + <td><a href="/input?sig={{$inp.Sig}}">{{$inp.Short}}</a></td> + </tr> + {{end}} +</table> </body></html> -`))) +`) type UIPrioData struct { Call string @@ -751,26 +768,29 @@ type UIPrio struct { Prio float32 } -type UIPrioArray []UIPrio - -func (a UIPrioArray) Len() int { return len(a) } -func (a UIPrioArray) Less(i, j int) bool { return a[i].Prio > a[j].Prio } -func (a UIPrioArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -var prioTemplate = template.Must(template.New("").Parse(addStyle(` +var prioTemplate = html.CreatePage(` <!doctype html> <html> <head> <title>syzkaller priorities</title> - {{STYLE}} + {{HEAD}} </head> <body> -Priorities for {{$.Call}} <br> <br> -{{range $p := $.Prios}} - {{printf "%.4f\t%s" $p.Prio $p.Call}} <br> -{{end}} +<table class="list_table"> + <caption>Priorities for {{$.Call}}:</caption> + <tr> + <th><a onclick="return sortTable(this, 'Prio', floatSort)" href="#">Prio</a></th> + <th><a onclick="return sortTable(this, 'Call', textSort)" href="#">Call</a></th> + </tr> + {{range $p := $.Prios}} + <tr> + <td>{{printf "%.4f" $p.Prio}}</td> + <td><a href='/prio?call={{$p.Call}}'>{{$p.Call}}</a></td> + </tr> + {{end}} +</table> </body></html> -`))) +`) type UIFallbackCoverData struct { Calls []UIFallbackCall @@ -782,15 +802,15 @@ type UIFallbackCall struct { Errnos []int } -var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(` +var fallbackCoverTemplate = html.CreatePage(` <!doctype html> <html> <head> <title>syzkaller coverage</title> - {{STYLE}} + {{HEAD}} </head> <body> -<table> +<table class="list_table"> <tr> <th>Call</th> <th>Successful</th> @@ -805,31 +825,4 @@ var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(` {{end}} </table> </body></html> -`))) - -func addStyle(html string) string { - return strings.Replace(html, "{{STYLE}}", htmlStyle, -1) -} - -const htmlStyle = ` - <style type="text/css" media="screen"> - table { - border-collapse:collapse; - border:1px solid; - } - table caption { - font-weight: bold; - } - table td { - border:1px solid; - padding: 3px; - } - table th { - border:1px solid; - padding: 3px; - } - textarea { - width:100%; - } - </style> -` +`) |
