github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-testbed/html.go (about) 1 // Copyright 2021 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import ( 7 "bytes" 8 "embed" 9 "fmt" 10 "html/template" 11 "log" 12 "net" 13 "net/http" 14 "net/url" 15 "os" 16 "time" 17 18 "github.com/google/syzkaller/pkg/html/pages" 19 "github.com/google/syzkaller/pkg/osutil" 20 "github.com/gorilla/handlers" 21 ) 22 23 func (ctx *TestbedContext) setupHTTPServer() { 24 mux := http.NewServeMux() 25 26 mux.HandleFunc("/", ctx.httpMain) 27 mux.HandleFunc("/graph", ctx.httpGraph) 28 mux.HandleFunc("/favicon.ico", ctx.httpFavicon) 29 30 listener, err := net.Listen("tcp", ctx.Config.HTTP) 31 if err != nil { 32 log.Fatalf("failed to listen on %s", ctx.Config.HTTP) 33 } 34 35 log.Printf("handling HTTP on %s", listener.Addr()) 36 go func() { 37 err := http.Serve(listener, handlers.CompressHandler(mux)) 38 if err != nil { 39 log.Fatalf("failed to listen on %v: %v", ctx.Config.HTTP, err) 40 } 41 }() 42 } 43 44 func (ctx *TestbedContext) httpFavicon(w http.ResponseWriter, r *http.Request) { 45 http.Error(w, "Not Found", http.StatusNotFound) 46 } 47 48 func (ctx *TestbedContext) getCurrentStatView(r *http.Request) (*StatView, error) { 49 views, err := ctx.GetStatViews() 50 if err != nil { 51 return nil, err 52 } 53 if len(views) == 0 { 54 return nil, fmt.Errorf("no stat views available") 55 } 56 viewName := r.FormValue("view") 57 if viewName != "" { 58 var targetView *StatView 59 for _, view := range views { 60 if view.Name == viewName { 61 targetView = &view 62 break 63 } 64 } 65 if targetView == nil { 66 return nil, fmt.Errorf("the requested view is not found") 67 } 68 return targetView, nil 69 } 70 // No specific view is requested. 71 // First try to find the first non-empty one. 72 for _, view := range views { 73 if !view.IsEmpty() { 74 return &view, nil 75 } 76 } 77 return &views[0], nil 78 } 79 80 func (ctx *TestbedContext) httpGraph(w http.ResponseWriter, r *http.Request) { 81 over := r.FormValue("over") 82 83 if ctx.Config.BenchCmp == "" { 84 http.Error(w, "the path to the benchcmp tool is not specified", http.StatusInternalServerError) 85 return 86 } 87 88 targetView, err := ctx.getCurrentStatView(r) 89 if err != nil { 90 http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError) 91 return 92 } 93 94 // TODO: move syz-benchcmp functionality to pkg/ and just import it? 95 dir, err := os.MkdirTemp("", "") 96 if err != nil { 97 http.Error(w, "failed to create temp folder", http.StatusInternalServerError) 98 return 99 } 100 defer os.RemoveAll(dir) 101 102 file, err := osutil.TempFile("") 103 if err != nil { 104 http.Error(w, "failed to create temp file", http.StatusInternalServerError) 105 return 106 } 107 defer os.Remove(file) 108 109 benches, err := targetView.SaveAvgBenches(dir) 110 if err != nil { 111 http.Error(w, "failed to save avg benches", http.StatusInternalServerError) 112 return 113 } 114 115 args := append([]string{"-all", "-over", over, "-out", file}, benches...) 116 if out, err := osutil.RunCmd(time.Hour, "", ctx.Config.BenchCmp, args...); err != nil { 117 http.Error(w, "syz-benchcmp failed\n"+string(out), http.StatusInternalServerError) 118 return 119 } 120 121 data, err := os.ReadFile(file) 122 if err != nil { 123 http.Error(w, "failed to read the temporary file", http.StatusInternalServerError) 124 return 125 } 126 w.Write(data) 127 } 128 129 type uiTable struct { 130 Table *Table 131 ColumnURL func(string) string 132 RowURL func(string) string 133 Extra bool 134 HasFooter bool 135 AlignedBy string 136 } 137 138 const ( 139 HTMLStatsTable = "stats" 140 HTMLBugsTable = "bugs" 141 HTMLBugCountsTable = "bug_counts" 142 HTMLReprosTable = "repros" 143 HTMLCReprosTable = "crepros" 144 HTMLReproAttemptsTable = "repro_attempts" 145 HTMLReproDurationTable = "repro_duration" 146 ) 147 148 type uiTableGenerator = func(urlPrefix string, view *StatView, r *http.Request) (*uiTable, error) 149 150 type uiTableType struct { 151 Key string 152 Title string 153 Generator uiTableGenerator 154 } 155 156 type uiStatView struct { 157 Name string 158 TableTypes map[string]uiTableType 159 ActiveTableType string 160 ActiveTable *uiTable 161 GenTableURL func(uiTableType) string 162 } 163 164 type uiMainPage struct { 165 Name string 166 Summary uiTable 167 Views []StatView 168 ActiveView uiStatView 169 } 170 171 func (ctx *TestbedContext) getTableTypes() []uiTableType { 172 allTypeList := []uiTableType{ 173 {HTMLStatsTable, "Statistics", ctx.httpMainStatsTable}, 174 {HTMLBugsTable, "Bugs", ctx.genSimpleTableController((*StatView).GenerateBugTable, true)}, 175 {HTMLBugCountsTable, "Bug Counts", ctx.genSimpleTableController((*StatView).GenerateBugCountsTable, false)}, 176 {HTMLReprosTable, "Repros", ctx.genSimpleTableController((*StatView).GenerateReproSuccessTable, true)}, 177 {HTMLCReprosTable, "C Repros", ctx.genSimpleTableController((*StatView).GenerateCReproSuccessTable, true)}, 178 {HTMLReproAttemptsTable, "All Repros", ctx.genSimpleTableController((*StatView).GenerateReproAttemptsTable, false)}, 179 {HTMLReproDurationTable, "Duration", ctx.genSimpleTableController((*StatView).GenerateReproDurationTable, true)}, 180 } 181 typeList := []uiTableType{} 182 for _, t := range allTypeList { 183 if ctx.Target.SupportsHTMLView(t.Key) { 184 typeList = append(typeList, t) 185 } 186 } 187 return typeList 188 } 189 190 func (ctx *TestbedContext) genSimpleTableController(method func(view *StatView) (*Table, error), 191 hasFooter bool) uiTableGenerator { 192 return func(urlPrefix string, view *StatView, r *http.Request) (*uiTable, error) { 193 table, err := method(view) 194 if err != nil { 195 return nil, fmt.Errorf("table generation failed: %w", err) 196 } 197 return &uiTable{ 198 Table: table, 199 HasFooter: hasFooter, 200 }, nil 201 } 202 } 203 204 func (ctx *TestbedContext) httpMainStatsTable(urlPrefix string, view *StatView, r *http.Request) (*uiTable, error) { 205 alignBy := r.FormValue("align") 206 table, err := view.AlignedStatsTable(alignBy) 207 if err != nil { 208 return nil, fmt.Errorf("stat table generation failed: %w", err) 209 } 210 baseColumn := r.FormValue("base_column") 211 if baseColumn != "" { 212 err := table.SetRelativeValues(baseColumn) 213 if err != nil { 214 log.Printf("failed to execute SetRelativeValues: %s", err) 215 } 216 } 217 218 return &uiTable{ 219 Table: table, 220 Extra: baseColumn != "", 221 ColumnURL: func(column string) string { 222 if column == baseColumn { 223 return "" 224 } 225 v := url.Values{} 226 v.Set("base_column", column) 227 v.Set("align", alignBy) 228 return urlPrefix + v.Encode() 229 }, 230 RowURL: func(row string) string { 231 if row == alignBy { 232 return "" 233 } 234 v := url.Values{} 235 v.Set("base_column", baseColumn) 236 v.Set("align", row) 237 return urlPrefix + v.Encode() 238 }, 239 AlignedBy: alignBy, 240 }, nil 241 } 242 243 func (ctx *TestbedContext) httpMain(w http.ResponseWriter, r *http.Request) { 244 activeView, err := ctx.getCurrentStatView(r) 245 if err != nil { 246 http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError) 247 return 248 } 249 views, err := ctx.GetStatViews() 250 if err != nil { 251 http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError) 252 return 253 } 254 uiView := uiStatView{Name: activeView.Name} 255 tableTypes := ctx.getTableTypes() 256 if len(tableTypes) == 0 { 257 http.Error(w, "No tables are available", http.StatusInternalServerError) 258 return 259 } 260 uiView.TableTypes = map[string]uiTableType{} 261 for _, table := range tableTypes { 262 uiView.TableTypes[table.Key] = table 263 } 264 uiView.ActiveTableType = r.FormValue("table") 265 if uiView.ActiveTableType == "" { 266 uiView.ActiveTableType = tableTypes[0].Key 267 } 268 tableType, found := uiView.TableTypes[uiView.ActiveTableType] 269 if !found { 270 http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError) 271 return 272 } 273 uiView.GenTableURL = func(t uiTableType) string { 274 v := url.Values{} 275 v.Set("view", activeView.Name) 276 v.Set("table", t.Key) 277 return "/?" + v.Encode() 278 } 279 uiView.ActiveTable, err = tableType.Generator(uiView.GenTableURL(tableType)+"&", activeView, r) 280 if err != nil { 281 http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError) 282 return 283 } 284 data := &uiMainPage{ 285 Name: ctx.Config.Name, 286 Summary: uiTable{Table: ctx.TestbedStatsTable()}, 287 Views: views, 288 ActiveView: uiView, 289 } 290 291 executeTemplate(w, mainTemplate, "testbed.html", data) 292 } 293 294 func executeTemplate(w http.ResponseWriter, templ *template.Template, name string, data interface{}) { 295 buf := new(bytes.Buffer) 296 if err := templ.ExecuteTemplate(buf, name, data); err != nil { 297 log.Printf("failed to execute template: %v", err) 298 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 299 return 300 } 301 w.Write(buf.Bytes()) 302 } 303 304 //go:embed templates 305 var testbedTemplates embed.FS 306 var mainTemplate = pages.CreateFromFS(testbedTemplates, "templates/*.html")