func main() { defer common.LogPanic() worker_common.Init() if !*worker_common.Local { defer util.CleanTmpDir() } defer util.TimeTrack(time.Now(), "Running Chromium Perf") defer glog.Flush() // Validate required arguments. if *chromiumBuildNoPatch == "" { glog.Error("Must specify --chromium_build_nopatch") return } if *chromiumBuildWithPatch == "" { glog.Error("Must specify --chromium_build_withpatch") return } if *runID == "" { glog.Error("Must specify --run_id") return } if *runIDNoPatch == "" { glog.Error("Must specify --run_id_nopatch") return } if *runIDWithPatch == "" { glog.Error("Must specify --run_id_withpatch") return } if *benchmarkName == "" { glog.Error("Must specify --benchmark_name") return } // Reset the local chromium checkout. if err := util.ResetCheckout(util.ChromiumSrcDir); err != nil { glog.Errorf("Could not reset %s: %s", util.ChromiumSrcDir, err) return } // Sync the local chromium checkout. if err := util.SyncDir(util.ChromiumSrcDir); err != nil { glog.Errorf("Could not gclient sync %s: %s", util.ChromiumSrcDir, err) return } // Create the task file so that the master knows this worker is still busy. skutil.LogErr(util.CreateTaskFile(util.ACTIVITY_RUNNING_CHROMIUM_PERF)) defer util.DeleteTaskFile(util.ACTIVITY_RUNNING_CHROMIUM_PERF) if *targetPlatform == util.PLATFORM_ANDROID { if err := adb.VerifyLocalDevice(); err != nil { // Android device missing or offline. glog.Errorf("Could not find Android device: %s", err) return } // Kill adb server to make sure we start from a clean slate. skutil.LogErr(util.ExecuteCmd(util.BINARY_ADB, []string{"kill-server"}, []string{}, util.ADB_ROOT_TIMEOUT, nil, nil)) // Make sure adb shell is running as root. skutil.LogErr(util.ExecuteCmd(util.BINARY_ADB, []string{"root"}, []string{}, util.ADB_ROOT_TIMEOUT, nil, nil)) } // Instantiate GsUtil object. gs, err := util.NewGsUtil(nil) if err != nil { glog.Error(err) return } // Download the benchmark patch for this run from Google storage. benchmarkPatchName := *runID + ".benchmark.patch" benchmarkPatchLocalPath := filepath.Join(os.TempDir(), benchmarkPatchName) remoteDir := filepath.Join(util.ChromiumPerfRunsDir, *runID) benchmarkPatchRemotePath := filepath.Join(remoteDir, benchmarkPatchName) respBody, err := gs.GetRemoteFileContents(benchmarkPatchRemotePath) if err != nil { glog.Errorf("Could not fetch %s: %s", benchmarkPatchRemotePath, err) return } defer skutil.Close(respBody) buf := new(bytes.Buffer) if _, err := buf.ReadFrom(respBody); err != nil { glog.Errorf("Could not read from %s: %s", benchmarkPatchRemotePath, err) return } if err := ioutil.WriteFile(benchmarkPatchLocalPath, buf.Bytes(), 0666); err != nil { glog.Errorf("Unable to create file %s: %s", benchmarkPatchLocalPath, err) return } defer skutil.Remove(benchmarkPatchLocalPath) // Apply benchmark patch to the local chromium checkout. if buf.Len() > 10 { if err := util.ApplyPatch(benchmarkPatchLocalPath, util.ChromiumSrcDir); err != nil { glog.Errorf("Could not apply Telemetry's patch in %s: %s", util.ChromiumSrcDir, err) return } } // Download the specified chromium builds. for _, chromiumBuild := range []string{*chromiumBuildNoPatch, *chromiumBuildWithPatch} { if err := gs.DownloadChromiumBuild(chromiumBuild); err != nil { glog.Error(err) return } //Delete the chromium build to save space when we are done. defer skutil.RemoveAll(filepath.Join(util.ChromiumBuildsDir, chromiumBuild)) } chromiumBinaryNoPatch := filepath.Join(util.ChromiumBuildsDir, *chromiumBuildNoPatch, util.BINARY_CHROME) chromiumBinaryWithPatch := filepath.Join(util.ChromiumBuildsDir, *chromiumBuildWithPatch, util.BINARY_CHROME) // Download pagesets if they do not exist locally. if err := gs.DownloadWorkerArtifacts(util.PAGESETS_DIR_NAME, *pagesetType, *workerNum); err != nil { glog.Error(err) return } pathToPagesets := filepath.Join(util.PagesetsDir, *pagesetType) // Download archives if they do not exist locally. if err := gs.DownloadWorkerArtifacts(util.WEB_ARCHIVES_DIR_NAME, *pagesetType, *workerNum); err != nil { glog.Error(err) return } // Establish nopatch output paths. localOutputDirNoPatch := filepath.Join(util.StorageDir, util.BenchmarkRunsDir, *runIDNoPatch) skutil.RemoveAll(localOutputDirNoPatch) skutil.MkdirAll(localOutputDirNoPatch, 0700) defer skutil.RemoveAll(localOutputDirNoPatch) remoteDirNoPatch := filepath.Join(util.BenchmarkRunsDir, *runIDNoPatch) // Establish withpatch output paths. localOutputDirWithPatch := filepath.Join(util.StorageDir, util.BenchmarkRunsDir, *runIDWithPatch) skutil.RemoveAll(localOutputDirWithPatch) skutil.MkdirAll(localOutputDirWithPatch, 0700) defer skutil.RemoveAll(localOutputDirWithPatch) remoteDirWithPatch := filepath.Join(util.BenchmarkRunsDir, *runIDWithPatch) // Construct path to the ct_run_benchmark python script. _, currentFile, _, _ := runtime.Caller(0) pathToPyFiles := filepath.Join( filepath.Dir((filepath.Dir(filepath.Dir(filepath.Dir(currentFile))))), "py") fileInfos, err := ioutil.ReadDir(pathToPagesets) if err != nil { glog.Errorf("Unable to read the pagesets dir %s: %s", pathToPagesets, err) return } numWorkers := WORKER_POOL_SIZE if *targetPlatform == util.PLATFORM_ANDROID || !*runInParallel { // Do not run page sets in parallel if the target platform is Android. // This is because the nopatch/withpatch APK needs to be installed prior to // each run and this will interfere with the parallel runs. Instead of trying // to find a complicated solution to this, it makes sense for Android to // continue to be serial because it will help guard against // crashes/flakiness/inconsistencies which are more prevalent in mobile runs. numWorkers = 1 glog.Infoln("===== Going to run the task serially =====") } else { glog.Infoln("===== Going to run the task with parallel chrome processes =====") } // Create channel that contains all pageset file names. This channel will // be consumed by the worker pool. pagesetRequests := util.GetClosedChannelOfPagesets(fileInfos) var wg sync.WaitGroup // Use a RWMutex for the chromeProcessesCleaner goroutine to communicate to // the workers (acting as "readers") when it wants to be the "writer" and // kill all zombie chrome processes. var mutex sync.RWMutex // Loop through workers in the worker pool. for i := 0; i < numWorkers; i++ { // Increment the WaitGroup counter. wg.Add(1) // Create and run a goroutine closure that captures SKPs. go func() { // Decrement the WaitGroup counter when the goroutine completes. defer wg.Done() for pagesetName := range pagesetRequests { mutex.RLock() if err := runBenchmark(pagesetName, pathToPagesets, pathToPyFiles, localOutputDirNoPatch, *chromiumBuildNoPatch, chromiumBinaryNoPatch, *runIDNoPatch, *browserExtraArgsNoPatch); err != nil { glog.Errorf("Error while running nopatch benchmark: %s", err) return } if err := runBenchmark(pagesetName, pathToPagesets, pathToPyFiles, localOutputDirWithPatch, *chromiumBuildWithPatch, chromiumBinaryWithPatch, *runIDWithPatch, *browserExtraArgsWithPatch); err != nil { glog.Errorf("Error while running withpatch benchmark: %s", err) return } mutex.RUnlock() } }() } if !*worker_common.Local { // Start the cleaner. go util.ChromeProcessesCleaner(&mutex, *chromeCleanerTimer) } // Wait for all spawned goroutines to complete. wg.Wait() // If "--output-format=csv-pivot-table" was specified then merge all CSV files and upload. if strings.Contains(*benchmarkExtraArgs, "--output-format=csv-pivot-table") { if err := mergeUploadCSVFiles(localOutputDirNoPatch, pathToPyFiles, *runIDNoPatch, remoteDirNoPatch, gs); err != nil { glog.Errorf("Error while processing nopatch CSV files: %s", err) return } if err := mergeUploadCSVFiles(localOutputDirWithPatch, pathToPyFiles, *runIDWithPatch, remoteDirWithPatch, gs); err != nil { glog.Errorf("Error while processing withpatch CSV files: %s", err) return } } }
func main() { common.Init() defer util.TimeTrack(time.Now(), "Running Skia Correctness") defer glog.Flush() if *chromiumBuild == "" { glog.Error("Must specify --chromium_build") return } if *runID == "" { glog.Error("Must specify --run_id") return } // Create the task file so that the master knows this worker is still busy. skutil.LogErr(util.CreateTaskFile(util.ACTIVITY_RUNNING_SKIA_CORRECTNESS)) defer util.DeleteTaskFile(util.ACTIVITY_RUNNING_SKIA_CORRECTNESS) // Establish output paths. localOutputDir := filepath.Join(util.StorageDir, util.SkiaCorrectnessRunsDir, *runID) skutil.RemoveAll(filepath.Join(util.StorageDir, util.SkiaCorrectnessRunsDir)) skutil.MkdirAll(localOutputDir, 0700) defer skutil.RemoveAll(localOutputDir) remoteOutputDir := filepath.Join(util.SkiaCorrectnessRunsDir, *runID, fmt.Sprintf("slave%d", *workerNum)) // Instantiate GsUtil object. gs, err := util.NewGsUtil(nil) if err != nil { glog.Error(err) return } // Download SKPs if they do not exist locally. if err := gs.DownloadWorkerArtifacts(util.SKPS_DIR_NAME, filepath.Join(*pagesetType, *chromiumBuild), *workerNum); err != nil { glog.Error(err) return } localSkpsDir := filepath.Join(util.SkpsDir, *pagesetType, *chromiumBuild) // Download the Skia patch for this run from Google storage. patchName := *runID + ".patch" patchLocalPath := filepath.Join(os.TempDir(), patchName) remoteDir := filepath.Join(util.SkiaCorrectnessRunsDir, *runID) patchRemotePath := filepath.Join(remoteDir, "patches", patchName) respBody, err := gs.GetRemoteFileContents(patchRemotePath) if err != nil { glog.Errorf("Could not fetch %s: %s", patchRemotePath, err) return } defer skutil.Close(respBody) out, err := os.Create(patchLocalPath) if err != nil { glog.Errorf("Unable to create file %s: %s", patchLocalPath, err) return } defer skutil.Close(out) defer skutil.Remove(patchLocalPath) if _, err = io.Copy(out, respBody); err != nil { glog.Error(err) return } // Apply the patch to a clean checkout and run render_pictures. // Reset Skia tree. skutil.LogErr(util.ResetCheckout(util.SkiaTreeDir)) // Sync Skia tree. skutil.LogErr(util.SyncDir(util.SkiaTreeDir)) // Apply Skia patch. file, _ := os.Open(patchLocalPath) fileInfo, _ := file.Stat() // It is a valid patch only if it is more than 10 bytes. if fileInfo.Size() > 10 { glog.Info("Attempting to apply %s to %s", patchLocalPath, util.SkiaTreeDir) if err := util.ApplyPatch(patchLocalPath, util.SkiaTreeDir); err != nil { glog.Errorf("Could not apply patch %s to %s: %s", patchLocalPath, util.SkiaTreeDir, err) return } glog.Info("Patch successfully applied") } else { glog.Info("Patch is empty or invalid. Skipping the patch.") } // Build tools. skutil.LogErr(util.BuildSkiaTools()) // Run render_pictures. if err := runRenderPictures(localSkpsDir, filepath.Join(localOutputDir, "withpatch"), filepath.Join(remoteOutputDir, "withpatch"), *gpuWithPatchRun); err != nil { glog.Errorf("Error while running withpatch render_pictures: %s", err) return } // Remove the patch and run render_pictures. // Reset Skia tree. skutil.LogErr(util.ResetCheckout(util.SkiaTreeDir)) // Build tools. skutil.LogErr(util.BuildSkiaTools()) // Run render_pictures. if err := runRenderPictures(localSkpsDir, filepath.Join(localOutputDir, "nopatch"), filepath.Join(remoteOutputDir, "nopatch"), *gpuNoPatchRun); err != nil { glog.Errorf("Error while running nopatch render_pictures: %s", err) return } // Comparing pictures and saving differences in JSON output file. jsonSummaryDir := filepath.Join(localOutputDir, "json_summary") skutil.MkdirAll(jsonSummaryDir, 0700) jsonSummaryPath := filepath.Join(jsonSummaryDir, fmt.Sprintf("slave%d", *workerNum)+".json") // Construct path to the write_json_summary python script. _, currentFile, _, _ := runtime.Caller(0) pathToPyFiles := filepath.Join( filepath.Dir((filepath.Dir(filepath.Dir(filepath.Dir(currentFile))))), "py") summaryArgs := []string{ filepath.Join(pathToPyFiles, "write_json_summary.py"), "--img_root=" + localOutputDir, "--withpatch_json=" + filepath.Join(localOutputDir, "withpatch", "summary.json"), "--withpatch_images_base_url=file://" + filepath.Join(localOutputDir, "withpatch"), "--nopatch_json=" + filepath.Join(localOutputDir, "nopatch", "summary.json"), "--nopatch_images_base_url=file://" + filepath.Join(localOutputDir, "nopatch"), "--output_file_path=" + jsonSummaryPath, "--gs_output_dir=gs://" + filepath.Join(util.GS_BUCKET_NAME, remoteDir), "--gs_skp_dir=gs://" + filepath.Join(util.GS_BUCKET_NAME, util.SKPS_DIR_NAME, *pagesetType, *chromiumBuild, fmt.Sprintf("slave%d", *workerNum)), "--slave_num=" + strconv.Itoa(*workerNum), } if err := util.ExecuteCmd("python", summaryArgs, []string{}, 15*time.Minute, nil, nil); err != nil { glog.Error(err) return } // Upload artifacts to Google Storage. // Get list of failed file names and upload only those to Google Storage. // Read the JSON file. summaryFile, err := ioutil.ReadFile(jsonSummaryPath) if err != nil { glog.Errorf("Unable to read %s: %s", jsonSummaryPath, err) } var jsontype map[string]interface{} skutil.LogErr(json.Unmarshal(summaryFile, &jsontype)) if jsontype[fmt.Sprintf("slave%d", *workerNum)] != nil { failedFiles := jsontype[fmt.Sprintf("slave%d", *workerNum)].(map[string]interface{})["failedFiles"].([]interface{}) for i := range failedFiles { failedFile := failedFiles[i].(map[string]interface{})["fileName"].(string) // TODO(rmistry): Use goroutines to do the below in parallel. skutil.LogErr( gs.UploadFile(failedFile, filepath.Join(localOutputDir, "withpatch"), filepath.Join(remoteDir, fmt.Sprintf("slave%d", *workerNum), "withpatch-images"))) skutil.LogErr( gs.UploadFile(failedFile, filepath.Join(localOutputDir, "nopatch"), filepath.Join(remoteDir, fmt.Sprintf("slave%d", *workerNum), "nopatch-images"))) } // Copy the diffs and whitediffs to Google Storage. skutil.LogErr( gs.UploadDir(filepath.Join(localOutputDir, "diffs"), filepath.Join(remoteDir, fmt.Sprintf("slave%d", *workerNum), "diffs"), true)) skutil.LogErr( gs.UploadDir(filepath.Join(localOutputDir, "whitediffs"), filepath.Join(remoteDir, fmt.Sprintf("slave%d", *workerNum), "whitediffs"), true)) } // Upload the summary file. skutil.LogErr( gs.UploadFile(fmt.Sprintf("slave%d", *workerNum)+".json", jsonSummaryDir, filepath.Join(remoteDir, fmt.Sprintf("slave%d", *workerNum)))) }