From 13ab4beeefd2c49666ce771753fbb3a28c9d2f2c Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 22 Nov 2018 14:27:37 +0100 Subject: [PATCH] syz-manager: modernize web UI 1. Use dashboard style. 2. Allow sorting of tables. 3. Show old crashes in grey. 4. Use tables instead of text output for more pages. 5. Show corpus inputs on a separate page to allow copy-pasting. 6. Use standard JS sorting instead of custom bubble sort (much faster). 7. Fix off-by one in table sorting. Fixes #694 --- Makefile | 2 +- dashboard/app/handler.go | 87 +-------- dashboard/app/main.go | 3 +- dashboard/app/reporting.go | 5 +- dashboard/app/static/common.js | 31 ++-- dashboard/app/static/style.css | 16 ++ pkg/html/generated.go | 221 +++++++++++++++++++++++ pkg/html/html.go | 110 +++++++++++ syz-manager/html.go | 321 ++++++++++++++++----------------- 9 files changed, 525 insertions(+), 271 deletions(-) create mode 100644 pkg/html/generated.go create mode 100644 pkg/html/html.go diff --git a/Makefile b/Makefile index 61865eb4..99f7eeb4 100644 --- a/Makefile +++ b/Makefile @@ -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 3bde1cab..167ca585 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 02dbfb8d..525cd1c0 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 0fd2d7aa..4982c8ad 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 5a146f3b..5c20f7df 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 cb9e6e37..536cd82a 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 00000000..7c5157aa --- /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 00000000..c7f85da8 --- /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 = `` + 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 f9f96933..777dcd39 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{ - Short: p.String(), - Full: string(inp.Prog), - Cover: len(inp.Cover), + data.Inputs = append(data.Inputs, &UIInput{ Sig: sig, + Short: p.String(), + Cover: len(inp.Cover), }) } - 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 UIInput struct { - Short string - Full string - Calls int - Cover int - Sig string +type UICorpus struct { + Call string + Inputs []*UIInput } -type UICallTypeArray []UICallType +type UIInput struct { + Sig string + Short string + Cover int +} -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(` {{.Name }} syzkaller - {{STYLE}} + {{HEAD}} {{.Name }} syzkaller
-
- +
{{range $s := $.Stats}} - - {{if $s.Link}} - - {{else}} - - {{end}} + + {{end}}
Stats:
{{$s.Name}}{{$s.Value}}{{$s.Value}}{{$s.Name}} + {{if $s.Link}} + {{$s.Value}} + {{else}} + {{$s.Value}} + {{end}} +
-
- +
- - - - + + + + {{range $c := $.Crashes}} - - - + + + {{end}}
Crashes:
DescriptionCountLast TimeReportDescriptionCountLast TimeReport
{{$c.Description}}{{$c.Count}}{{$c.LastTime}}{{$c.Description}}{{$c.Count}}{{formatTime $c.LastTime}} {{if $c.Triaged}} {{$c.Triaged}} @@ -651,11 +652,10 @@ var summaryTemplate = template.Must(template.New("").Parse(addStyle(`
-
Log:
- -`))) +`) -var syscallsTemplate = template.Must(template.New("").Parse(addStyle(` +var syscallsTemplate = html.CreatePage(` {{.Name }} syzkaller - {{STYLE}} + {{HEAD}} -Per-call coverage: -
-{{range $c := $.Calls}} - {{$c.Name}} - inputs:{{$c.Inputs}} - cover:{{$c.Cover}} - prio
-{{end}} - -`))) -var crashTemplate = template.Must(template.New("").Parse(addStyle(` + + + + + + + + + {{range $c := $.Calls}} + + + + + + + {{end}} +
Per-syscall coverage:
SyscallInputsCoveragePrio
{{$c.Name}}{{$c.Inputs}}{{$c.Cover}}prio
+ +`) + +var crashTemplate = html.CreatePage(` {{.Description}} - {{STYLE}} + {{HEAD}} {{.Description}} -

{{if .Triaged}} Report: {{.Triaged}} {{end}} -

- +
@@ -712,34 +720,43 @@ Report: {{.Triaged}} - {{if $c.Report}} - - {{else}} - - {{end}} - - + + {{end}} + + + {{end}}
# Log
{{$c.Index}} logreport{{$c.TimeStr}}{{$c.Tag}} + {{if $c.Report}} + report{{formatTime $c.Time}}{{formatShortHash $c.Tag}}
-`))) +`) -var corpusTemplate = template.Must(template.New("").Parse(addStyle(` +var corpusTemplate = html.CreatePage(` syzkaller corpus - {{STYLE}} + {{HEAD}} -{{range $c := $}} - {{$c.Short}} - cover:{{$c.Cover}} -
-{{end}} + + + + + + + + {{range $inp := $.Inputs}} + + + + + {{end}} +
Corpus{{if $.Call}} for {{$.Call}}{{end}}:
CoverageProgram
{{$inp.Cover}}{{$inp.Short}}
-`))) +`) 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(` syzkaller priorities - {{STYLE}} + {{HEAD}} -Priorities for {{$.Call}}

-{{range $p := $.Prios}} - {{printf "%.4f\t%s" $p.Prio $p.Call}}
-{{end}} + + + + + + + {{range $p := $.Prios}} + + + + + {{end}} +
Priorities for {{$.Call}}:
PrioCall
{{printf "%.4f" $p.Prio}}{{$p.Call}}
-`))) +`) 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(` syzkaller coverage - {{STYLE}} + {{HEAD}} - +
@@ -805,31 +825,4 @@ var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(` {{end}}
Call Successful
-`))) - -func addStyle(html string) string { - return strings.Replace(html, "{{STYLE}}", htmlStyle, -1) -} - -const htmlStyle = ` - -` +`)