Index: fuzzer/go/storage/download_reports.go |
diff --git a/fuzzer/go/storage/download_reports.go b/fuzzer/go/storage/download_reports.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..98070194228ed6b64685320160b43f1c19027fc2 |
--- /dev/null |
+++ b/fuzzer/go/storage/download_reports.go |
@@ -0,0 +1,137 @@ |
+package storage |
+ |
+import ( |
+ "fmt" |
+ "sort" |
+ "sync" |
+ "sync/atomic" |
+ |
+ "github.com/skia-dev/glog" |
+ "go.skia.org/infra/fuzzer/go/common" |
+ "go.skia.org/infra/fuzzer/go/config" |
+ "go.skia.org/infra/fuzzer/go/data" |
+ "go.skia.org/infra/go/gs" |
+ "google.golang.org/cloud/storage" |
+) |
+ |
+// GetReportsFromGS fetches all fuzz reports in the baseFolder from Google Storage. It returns a |
+// channel through which all reports will be sent. The channel will be closed when finished. An |
+// optional whitelist can be included, in which case only the fuzzes whose names are on the list |
+// will be downloaded. The category is needed to properly parse the downloaded files to make |
+// the FuzzReports. The downloading will use as many processes as specified, to speed things up. |
+func GetReportsFromGS(s *storage.Client, baseFolder, category string, whitelist []string, processes int) (<-chan data.FuzzReport, error) { |
+ reports := make(chan data.FuzzReport, 10000) |
+ |
+ fuzzPackages, err := fetchFuzzPackages(s, baseFolder, category) |
+ if err != nil { |
+ close(reports) |
+ return reports, err |
+ } |
+ |
+ toDownload := make(chan fuzzPackage, len(fuzzPackages)) |
+ completedCounter := int32(0) |
+ |
+ var wg sync.WaitGroup |
+ for i := 0; i < processes; i++ { |
+ wg.Add(1) |
+ go download(s, toDownload, reports, &completedCounter, &wg) |
+ } |
+ |
+ for _, d := range fuzzPackages { |
+ if whitelist != nil { |
+ name := d.FuzzName |
+ if i := sort.SearchStrings(whitelist, name); i < len(whitelist) && whitelist[i] == name { |
+ // is on the whitelist |
+ toDownload <- d |
+ } |
+ } else { |
+ // no white list |
+ toDownload <- d |
+ } |
+ } |
+ close(toDownload) |
+ |
+ // Wait until all are done downloading to close the reports channel, but don't block |
+ go func() { |
+ wg.Wait() |
+ close(reports) |
+ }() |
+ |
+ return reports, nil |
+} |
+ |
+// A fuzzPackage contains all the information about a fuzz, mostly the paths to the files that |
+// need to be downloaded. The use of this struct decouples the names of the files that need to be |
+// downloaded with the download logic. |
+type fuzzPackage struct { |
+ FuzzName string |
+ FuzzCategory string |
+ DebugASANName string |
+ DebugDumpName string |
+ DebugErrName string |
+ ReleaseASANName string |
+ ReleaseDumpName string |
+ ReleaseErrName string |
+} |
+ |
+// fetchFuzzPackages scans for all fuzzes in the given folder and returns a slice of all of the |
+// metadata for each fuzz, as a fuzz package. It returns error if it cannot access Google Storage. |
+func fetchFuzzPackages(s *storage.Client, baseFolder, category string) (fuzzPackages []fuzzPackage, err error) { |
+ fuzzNames, err := common.GetAllFuzzNamesInFolder(s, baseFolder) |
+ if err != nil { |
+ return nil, fmt.Errorf("Problem getting fuzz packages from %s: %s", baseFolder, err) |
+ } |
+ for _, fuzzName := range fuzzNames { |
+ prefix := fmt.Sprintf("%s/%s/%s", baseFolder, fuzzName, fuzzName) |
+ fuzzPackages = append(fuzzPackages, fuzzPackage{ |
+ FuzzName: fuzzName, |
+ FuzzCategory: category, |
+ DebugASANName: fmt.Sprintf("%s_debug.asan", prefix), |
+ DebugDumpName: fmt.Sprintf("%s_debug.dump", prefix), |
+ DebugErrName: fmt.Sprintf("%s_debug.err", prefix), |
+ ReleaseASANName: fmt.Sprintf("%s_release.asan", prefix), |
+ ReleaseDumpName: fmt.Sprintf("%s_release.dump", prefix), |
+ ReleaseErrName: fmt.Sprintf("%s_release.err", prefix), |
+ }) |
+ } |
+ return fuzzPackages, nil |
+} |
+ |
+// emptyStringOnError returns a string of the passed in bytes or empty string if err is nil. |
+func emptyStringOnError(b []byte, err error) string { |
+ if err != nil { |
+ glog.Warningf("Ignoring error when fetching file contents: %v", err) |
+ return "" |
+ } |
+ return string(b) |
+} |
+ |
+// download waits for fuzzPackages to appear on the toDownload channel and then downloads |
+// the four pieces of the package. It then parses them into a BinaryFuzzReport and sends |
+// the binary to the passed in channel. When there is no more work to be done, this function. |
+// returns and writes out true to the done channel. |
+func download(s *storage.Client, toDownload <-chan fuzzPackage, reports chan<- data.FuzzReport, completedCounter *int32, wg *sync.WaitGroup) { |
+ defer wg.Done() |
+ for job := range toDownload { |
+ p := data.GCSPackage{ |
+ Name: job.FuzzName, |
+ FuzzCategory: job.FuzzCategory, |
+ Debug: data.OutputFiles{ |
+ Asan: emptyStringOnError(gs.FileContentsFromGS(s, config.GS.Bucket, job.DebugASANName)), |
+ Dump: emptyStringOnError(gs.FileContentsFromGS(s, config.GS.Bucket, job.DebugDumpName)), |
+ StdErr: emptyStringOnError(gs.FileContentsFromGS(s, config.GS.Bucket, job.DebugErrName)), |
+ }, |
+ Release: data.OutputFiles{ |
+ Asan: emptyStringOnError(gs.FileContentsFromGS(s, config.GS.Bucket, job.ReleaseASANName)), |
+ Dump: emptyStringOnError(gs.FileContentsFromGS(s, config.GS.Bucket, job.ReleaseDumpName)), |
+ StdErr: emptyStringOnError(gs.FileContentsFromGS(s, config.GS.Bucket, job.ReleaseErrName)), |
+ }, |
+ } |
+ |
+ reports <- data.ParseReport(p) |
+ atomic.AddInt32(completedCounter, 1) |
+ if *completedCounter%100 == 0 { |
+ glog.Infof("%d fuzzes downloaded", *completedCounter) |
+ } |
+ } |
+} |