Index: fuzzer/go/frontend/data/report.go |
diff --git a/fuzzer/go/frontend/data/report.go b/fuzzer/go/frontend/data/report.go |
deleted file mode 100644 |
index 1d280cfaebe902545b1fee71a844f461513f6432..0000000000000000000000000000000000000000 |
--- a/fuzzer/go/frontend/data/report.go |
+++ /dev/null |
@@ -1,465 +0,0 @@ |
-package data |
- |
-import ( |
- "bytes" |
- "encoding/gob" |
- "fmt" |
- "sort" |
- "sync" |
- |
- "github.com/skia-dev/glog" |
- "go.skia.org/infra/fuzzer/go/common" |
-) |
- |
-type FuzzReportTree []FileFuzzReport |
- |
-type FileFuzzReport struct { |
- FileName string `json:"fileName"` |
- Count int `json:"count"` |
- Functions []FunctionFuzzReport `json:"byFunction"` |
-} |
- |
-type FunctionFuzzReport struct { |
- FunctionName string `json:"functionName"` |
- Count int `json:"count"` |
- LineNumbers []LineFuzzReport `json:"byLineNumber"` |
-} |
- |
-type LineFuzzReport struct { |
- LineNumber int `json:"lineNumber"` |
- Count int `json:"count"` |
- Details SortedFuzzReports `json:"reports"` |
-} |
- |
-type FuzzReport struct { |
- DebugStackTrace StackTrace `json:"debugStackTrace"` |
- ReleaseStackTrace StackTrace `json:"releaseStackTrace"` |
- DebugFlags []string `json:"debugFlags"` |
- ReleaseFlags []string `json:"releaseFlags"` |
- |
- FuzzName string `json:"fuzzName"` |
- FuzzCategory string `json:"category"` |
-} |
- |
-type SortedFuzzReports []FuzzReport |
- |
-// ParseReport creates a report given the raw materials passed in. |
-func ParseReport(g GCSPackage) FuzzReport { |
- result := ParseGCSPackage(g) |
- return FuzzReport{ |
- DebugStackTrace: result.Debug.StackTrace, |
- ReleaseStackTrace: result.Release.StackTrace, |
- DebugFlags: result.Debug.Flags.ToHumanReadableFlags(), |
- ReleaseFlags: result.Release.Flags.ToHumanReadableFlags(), |
- FuzzName: g.Name, |
- FuzzCategory: g.FuzzCategory, |
- } |
-} |
- |
-// treeReportBuilder is an in-memory structure that allows easy creation of a tree of reports |
-// for use on the frontend. It has a fuzzReportCache for every fuzz type (e.g. skpicture, skcodec, etc) |
-type treeReportBuilder struct { |
- caches map[string]*fuzzReportCache |
- mutex sync.Mutex |
-} |
- |
-// newBuilder creates an initialized treeReportBuilder |
-func newBuilder() *treeReportBuilder { |
- return &treeReportBuilder{ |
- caches: map[string]*fuzzReportCache{}, |
- } |
-} |
- |
-// A fuzzReportCache holds three FuzzReportTrees - one for the raw data, a sorted version with |
-// all of the reports and an empty tree that holds no reports. These are used to procure data |
-// for the frontend. |
-type fuzzReportCache struct { |
- // All the data goes in here, in no particular order |
- rawData FuzzReportTree |
- // Generated, sorted cache |
- FullReport FuzzReportTree |
- |
- // If data is in rawData, but not in FullReport, the trees should be |
- // rebuilt |
- isDirty bool |
-} |
- |
-// currentData is the object that holds the cache of fuzz results. It is used by the frontend. |
-var currentData = newBuilder() |
- |
-// stagingData is the object that processes can write to to queue up new data |
-// without disturbing the data shown to users. |
-var stagingData = newBuilder() |
- |
-// FindFuzzDetails returns the detailed fuzz reports for a file name, function name, and line number. |
-// If functionName is "" or lineNumber is -1, all reports are shown. |
-func FindFuzzDetails(category, fileName, functionName string, lineNumber int) (FuzzReportTree, error) { |
- cache, found := currentData.caches[category] |
- if found { |
- if fileName == "" { |
- return cache.FullReport, nil |
- } |
- for _, file := range cache.FullReport { |
- if file.FileName == fileName { |
- if functionName == "" { |
- return FuzzReportTree{file}, nil |
- } |
- file.filterByFunctionName(functionName) |
- if lineNumber == common.UNKNOWN_LINE { |
- return FuzzReportTree{file}, nil |
- } |
- file.Functions[0].filterByLineNumber(lineNumber) |
- return FuzzReportTree{file}, nil |
- } |
- } |
- } |
- |
- return nil, fmt.Errorf("File %q not found", fileName) |
-} |
- |
-// filterByFunctionName removes all FuzzReportFunction except that which matches functionName |
-func (file *FileFuzzReport) filterByFunctionName(functionName string) { |
- for _, function := range file.Functions { |
- if functionName == function.FunctionName { |
- file.Functions = []FunctionFuzzReport{function} |
- break |
- } |
- } |
-} |
- |
-// filterByLineNumber removes all FuzzReportLineNumber except that which matches lineNumber |
-func (function *FunctionFuzzReport) filterByLineNumber(lineNumber int) { |
- for _, line := range function.LineNumbers { |
- if lineNumber == line.LineNumber { |
- function.LineNumbers = []LineFuzzReport{line} |
- } |
- } |
-} |
- |
-// FindFuzzDetailForFuzz returns a tree containing the single |
-// report with the given name, or an error, it it doesn't exist. |
-func FindFuzzDetailForFuzz(category, name string) (FuzzReportTree, error) { |
- if cache, found := currentData.caches[category]; found { |
- for _, file := range cache.FullReport { |
- if file.filterByFuzzName(name) { |
- return FuzzReportTree{file}, nil |
- } |
- } |
- } |
- return nil, fmt.Errorf("Fuzz with name %q not found", name) |
-} |
- |
-// filterByFuzzName filters out all functions that do not contain a fuzz with the given |
-// name and returns true. If such a fuzz does not exist, it returns false. |
-func (file *FileFuzzReport) filterByFuzzName(name string) bool { |
- for _, function := range file.Functions { |
- if function.filterByFuzzName(name) { |
- file.Functions = []FunctionFuzzReport{function} |
- return true |
- } |
- } |
- return false |
-} |
- |
-// filterByFuzzName filters out all lines that do not contain a fuzz with the given |
-// name and returns true. If such a fuzz does not exist, it returns false. |
-func (function *FunctionFuzzReport) filterByFuzzName(name string) bool { |
- for _, line := range function.LineNumbers { |
- if line.filterByFuzzName(name) { |
- function.LineNumbers = []LineFuzzReport{line} |
- return true |
- } |
- } |
- return false |
-} |
- |
-// filterByFuzzName filters out all fuzzes that do not have the given |
-// name and returns true. If such a fuzz does not exist, it returns false. |
-func (line *LineFuzzReport) filterByFuzzName(name string) bool { |
- if b, hasIt := line.Details.containsName(name); hasIt { |
- line.Details = SortedFuzzReports{b} |
- return true |
- } |
- return false |
-} |
- |
-func NewFuzzFound(category string, b FuzzReport) { |
- // set the category if it has not already been set |
- b.FuzzCategory = category |
- stagingData.addFuzzReport(category, b) |
-} |
- |
-// ClearStaging clears the staging representation. |
-func ClearStaging() { |
- stagingData.mutex.Lock() |
- defer stagingData.mutex.Unlock() |
- stagingData.caches = map[string]*fuzzReportCache{} |
-} |
- |
-// SetStaging replaces the staging representation with the given FuzzReport. |
-func SetStaging(category string, r FuzzReportTree) { |
- stagingData.mutex.Lock() |
- defer stagingData.mutex.Unlock() |
- cache, found := stagingData.caches[category] |
- if !found { |
- cache = &fuzzReportCache{} |
- stagingData.caches[category] = cache |
- } |
- cache.rawData = r |
- cache.rebuildSortedReports() |
-} |
- |
-// StagingToCurrent moves a copy of the staging data to the currentData. |
-func StagingToCurrent() { |
- currentData.mutex.Lock() |
- defer currentData.mutex.Unlock() |
- stagingData.mutex.Lock() |
- defer stagingData.mutex.Unlock() |
- |
- currentData.caches = map[string]*fuzzReportCache{} |
- for k, v := range stagingData.caches { |
- cache := fuzzReportCache{} |
- cache.rawData = cloneReport(v.rawData) |
- cache.rebuildSortedReports() |
- currentData.caches[k] = &cache |
- } |
-} |
- |
-// StagingToCurrent moves a copy of the current data to the staging data. |
-func StagingFromCurrent() { |
- currentData.mutex.Lock() |
- defer currentData.mutex.Unlock() |
- stagingData.mutex.Lock() |
- defer stagingData.mutex.Unlock() |
- |
- stagingData.caches = map[string]*fuzzReportCache{} |
- for k, v := range currentData.caches { |
- cache := fuzzReportCache{} |
- cache.rawData = cloneReport(v.rawData) |
- cache.rebuildSortedReports() |
- stagingData.caches[k] = &cache |
- } |
-} |
- |
-// StagingCopy returns a fresh copy of the underlying staging data. |
-func StagingCopy(category string) FuzzReportTree { |
- stagingData.mutex.Lock() |
- defer stagingData.mutex.Unlock() |
- cache, found := stagingData.caches[category] |
- if !found { |
- return FuzzReportTree{} |
- } |
- return cloneReport(cache.rawData) |
-} |
- |
-// addFuzzReport adds a FuzzReport to a treeReportBuilder's data member |
-func (r *treeReportBuilder) addFuzzReport(category string, b FuzzReport) { |
- reportFileName, reportFunctionName, reportLineNumber := extractStacktraceInfo(b.DebugStackTrace, b.ReleaseStackTrace) |
- |
- cache, found := r.caches[category] |
- if !found { |
- cache = &fuzzReportCache{} |
- r.caches[category] = cache |
- } |
- r.mutex.Lock() |
- defer r.mutex.Unlock() |
- foundFile, foundFunction, foundLine := cache.makeOrFindRecords(reportFileName, reportFunctionName, reportLineNumber) |
- |
- foundFile.Count++ |
- foundFunction.Count++ |
- foundLine.Count++ |
- foundLine.Details = foundLine.Details.append(b) |
- cache.isDirty = true |
- |
-} |
- |
-// extractStacktraceInfo returns the file name, function name and line number that |
-// a report with the given debug and release stacktrace should be sorted by. |
-// this tries to read the release stacktrace first, falling back to the debug stacktrace, |
-// failling back to Unknown. |
-func extractStacktraceInfo(debug, release StackTrace) (reportFileName, reportFunctionName string, reportLineNumber int) { |
- reportFileName, reportFunctionName, reportLineNumber = common.UNKNOWN_FILE, common.UNKNOWN_FUNCTION, common.UNKNOWN_LINE |
- |
- stacktrace := release |
- if stacktrace.IsEmpty() { |
- stacktrace = debug |
- } |
- if !stacktrace.IsEmpty() { |
- frame := stacktrace.Frames[0] |
- reportFileName = frame.PackageName + frame.FileName |
- reportFunctionName, reportLineNumber = frame.FunctionName, frame.LineNumber |
- } |
- return |
-} |
- |
-// makeOrFindRecords finds the FuzzReportFile, FuzzReportFunction and FuzzReportLineNumber |
-// associated with the inputs, creating the structures if needed. |
-func (c *fuzzReportCache) makeOrFindRecords(reportFileName, reportFunctionName string, reportLineNumber int) (*FileFuzzReport, *FunctionFuzzReport, *LineFuzzReport) { |
- var foundFile *FileFuzzReport |
- for i, file := range c.rawData { |
- if file.FileName == reportFileName { |
- foundFile = &c.rawData[i] |
- break |
- } |
- } |
- if foundFile == nil { |
- c.rawData = append(c.rawData, FileFuzzReport{reportFileName, 0, nil}) |
- foundFile = &c.rawData[len(c.rawData)-1] |
- } |
- |
- var foundFunction *FunctionFuzzReport |
- for i, function := range foundFile.Functions { |
- if function.FunctionName == reportFunctionName { |
- foundFunction = &foundFile.Functions[i] |
- break |
- } |
- } |
- if foundFunction == nil { |
- foundFile.Functions = append(foundFile.Functions, FunctionFuzzReport{reportFunctionName, 0, nil}) |
- foundFunction = &foundFile.Functions[len(foundFile.Functions)-1] |
- } |
- |
- var foundLine *LineFuzzReport |
- for i, line := range foundFunction.LineNumbers { |
- if line.LineNumber == reportLineNumber { |
- foundLine = &foundFunction.LineNumbers[i] |
- } |
- } |
- if foundLine == nil { |
- foundFunction.LineNumbers = append(foundFunction.LineNumbers, LineFuzzReport{reportLineNumber, 0, nil}) |
- foundLine = &foundFunction.LineNumbers[len(foundFunction.LineNumbers)-1] |
- } |
- return foundFile, foundFunction, foundLine |
-} |
- |
-// getTreeSortedByTotal gets the detailed FuzzReport for a fuzz category |
-// sorted by total number of fuzzes. |
-func (r *treeReportBuilder) getTreeSortedByTotal(category string) FuzzReportTree { |
- cache, found := r.caches[category] |
- if !found { |
- glog.Warningf("Could not find report tree for category %s", category) |
- return FuzzReportTree{} |
- } |
- if cache.isDirty { |
- r.mutex.Lock() |
- defer r.mutex.Unlock() |
- cache.rebuildSortedReports() |
- } |
- return cache.FullReport |
-} |
- |
-// rebuildSortedReports creates the sorted reports for a given cache. |
-func (c *fuzzReportCache) rebuildSortedReports() { |
- c.FullReport = c.getClonedSortedReport(true) |
- c.isDirty = false |
-} |
- |
-// getClonedSortedReport makes a newly allocated FuzzReport after running the passed in function |
-// on all FuzzReportLineNumber objects in the report. |
-func (c *fuzzReportCache) getClonedSortedReport(keepDetails bool) FuzzReportTree { |
- report := cloneReport(c.rawData) |
- sort.Sort(filesTotalSort(report)) |
- for i := range report { |
- file := &report[i] |
- sort.Sort(functionsTotalSort(file.Functions)) |
- for j := range file.Functions { |
- function := &file.Functions[j] |
- sort.Sort(linesTotalSort(function.LineNumbers)) |
- for k := range function.LineNumbers { |
- line := &function.LineNumbers[k] |
- if !keepDetails { |
- line.Details = nil |
- } |
- } |
- } |
- } |
- return report |
-} |
- |
-// cloneReport makes a copy of the input using the gob library. |
-func cloneReport(data []FileFuzzReport) FuzzReportTree { |
- var temp bytes.Buffer |
- enc := gob.NewEncoder(&temp) |
- dec := gob.NewDecoder(&temp) |
- |
- if err := enc.Encode(data); err != nil { |
- // This should never happen, but log it if it does |
- glog.Errorf("Error while cloning report: %v", err) |
- } |
- var clone FuzzReportTree |
- if err := dec.Decode(&clone); err != nil { |
- // This should never happen, but log it if it does |
- glog.Errorf("Error while cloning report: %v", err) |
- } |
- return clone |
-} |
- |
-// Total sort methods - sorts files, functions and lines by Count |
-type filesTotalSort []FileFuzzReport |
- |
-func (r filesTotalSort) Len() int { return len(r) } |
-func (r filesTotalSort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } |
- |
-func (r filesTotalSort) Less(i, j int) bool { |
- if r[i].Count != r[j].Count { |
- return r[i].Count > r[j].Count |
- } |
- // If they have the same total, sort by name |
- return r[i].FileName < r[j].FileName |
-} |
- |
-type functionsTotalSort []FunctionFuzzReport |
- |
-func (r functionsTotalSort) Len() int { return len(r) } |
-func (r functionsTotalSort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } |
- |
-func (r functionsTotalSort) Less(i, j int) bool { |
- if r[i].Count != r[j].Count { |
- return r[i].Count > r[j].Count |
- } |
- // If they have the same total, sort by name |
- return r[i].FunctionName < r[j].FunctionName |
-} |
- |
-type linesTotalSort []LineFuzzReport |
- |
-func (r linesTotalSort) Len() int { return len(r) } |
-func (r linesTotalSort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } |
- |
-func (r linesTotalSort) Less(i, j int) bool { |
- if r[i].Count != r[j].Count { |
- return r[i].Count > r[j].Count |
- } |
- // If they have the same total, sort by line number |
- return r[i].LineNumber < r[j].LineNumber |
-} |
- |
-func (p SortedFuzzReports) Len() int { return len(p) } |
-func (p SortedFuzzReports) Less(i, j int) bool { return p[i].FuzzName < p[j].FuzzName } |
-func (p SortedFuzzReports) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
- |
-// append adds b to the already sorted caller, and returns the sorted result. |
-// Precondition: Caller must be nil or sorted |
-func (p SortedFuzzReports) append(b FuzzReport) SortedFuzzReports { |
- s := append(p, b) |
- |
- // Google Storage gives us the fuzzes in alphabetical order. Thus, we can short circuit |
- // if the fuzz goes on the end (which is usually does). |
- // However, we can't always do this because when we load a second batch of fuzzes, |
- // those are in alphabetical order, but starting over from 0. |
- // We want to avoid [a,c,x,z,b,d] where b,d were added from the second batch. |
- if len(s) <= 1 || s.Less(len(s)-2, len(s)-1) { |
- return s |
- } |
- sort.Sort(s) |
- return s |
-} |
- |
-// containsName returns the FuzzReport and true if a fuzz with the given name is in the list. |
-func (p SortedFuzzReports) containsName(fuzzName string) (FuzzReport, bool) { |
- i := sort.Search(len(p), func(i int) bool { return p[i].FuzzName >= fuzzName }) |
- if i < len(p) && p[i].FuzzName == fuzzName { |
- return p[i], true |
- } |
- return FuzzReport{}, false |
-} |