aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--dashboard/app/handler.go87
-rw-r--r--dashboard/app/main.go3
-rw-r--r--dashboard/app/reporting.go5
-rw-r--r--dashboard/app/static/common.js31
-rw-r--r--dashboard/app/static/style.css16
-rw-r--r--pkg/html/generated.go221
-rw-r--r--pkg/html/html.go110
-rw-r--r--syz-manager/html.go313
9 files changed, 521 insertions, 267 deletions
diff --git a/Makefile b/Makefile
index 61865eb42..99f7eeb43 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 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>
-`
+`)