| /* |
| Handlers and types specific to Chromium perf tasks. |
| */ |
| |
| package chromium_perf |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "path" |
| "path/filepath" |
| "strconv" |
| "strings" |
| "text/template" |
| |
| "cloud.google.com/go/datastore" |
| "github.com/gorilla/mux" |
| "go.skia.org/infra/ct/go/ctfe/task_common" |
| ctfeutil "go.skia.org/infra/ct/go/ctfe/util" |
| ctutil "go.skia.org/infra/ct/go/util" |
| "go.skia.org/infra/go/ds" |
| "go.skia.org/infra/go/email" |
| skutil "go.skia.org/infra/go/util" |
| "google.golang.org/api/iterator" |
| ) |
| |
| var ( |
| addTaskTemplate *template.Template = nil |
| runsHistoryTemplate *template.Template = nil |
| ) |
| |
| func ReloadTemplates(resourcesDir string) { |
| addTaskTemplate = template.Must(template.ParseFiles( |
| filepath.Join(resourcesDir, "templates/chromium_perf.html"), |
| filepath.Join(resourcesDir, "templates/header.html"), |
| filepath.Join(resourcesDir, "templates/titlebar.html"), |
| )) |
| runsHistoryTemplate = template.Must(template.ParseFiles( |
| filepath.Join(resourcesDir, "templates/chromium_perf_runs_history.html"), |
| filepath.Join(resourcesDir, "templates/header.html"), |
| filepath.Join(resourcesDir, "templates/titlebar.html"), |
| )) |
| } |
| |
| type DatastoreTask struct { |
| task_common.CommonCols |
| |
| Benchmark string |
| Platform string |
| RunOnGCE bool |
| PageSets string |
| IsTestPageSet bool |
| RepeatRuns int64 |
| RunInParallel bool |
| BenchmarkArgs string |
| BrowserArgsNoPatch string |
| BrowserArgsWithPatch string |
| Description string |
| CustomWebpagesGSPath string |
| ChromiumPatchGSPath string |
| BlinkPatchGSPath string |
| SkiaPatchGSPath string |
| CatapultPatchGSPath string |
| BenchmarkPatchGSPath string |
| ChromiumPatchBaseBuildGSPath string |
| V8PatchGSPath string |
| Results string |
| NoPatchRawOutput string |
| WithPatchRawOutput string |
| ChromiumHash string |
| CCList []string |
| TaskPriority int |
| GroupName string |
| } |
| |
| func (task DatastoreTask) GetTaskName() string { |
| return "ChromiumPerf" |
| } |
| |
| func (task DatastoreTask) GetDescription() string { |
| return task.Description |
| } |
| |
| func (task DatastoreTask) GetPopulatedAddTaskVars() (task_common.AddTaskVars, error) { |
| taskVars := &AddTaskVars{} |
| taskVars.Username = task.Username |
| taskVars.TsAdded = ctutil.GetCurrentTs() |
| taskVars.RepeatAfterDays = strconv.FormatInt(task.RepeatAfterDays, 10) |
| taskVars.Benchmark = task.Benchmark |
| taskVars.Platform = task.Platform |
| taskVars.RunOnGCE = strconv.FormatBool(task.RunOnGCE) |
| taskVars.PageSets = task.PageSets |
| taskVars.RepeatRuns = strconv.FormatInt(task.RepeatRuns, 10) |
| taskVars.RunInParallel = strconv.FormatBool(task.RunInParallel) |
| taskVars.BenchmarkArgs = task.BenchmarkArgs |
| taskVars.BrowserArgsNoPatch = task.BrowserArgsNoPatch |
| taskVars.BrowserArgsWithPatch = task.BrowserArgsWithPatch |
| taskVars.Description = task.Description |
| taskVars.ChromiumHash = task.ChromiumHash |
| taskVars.CCList = task.CCList |
| taskVars.TaskPriority = strconv.Itoa(task.TaskPriority) |
| taskVars.GroupName = task.GroupName |
| |
| var err error |
| taskVars.CustomWebpages, err = ctutil.GetPatchFromStorage(task.CustomWebpagesGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.CustomWebpagesGSPath, err) |
| } |
| taskVars.ChromiumPatch, err = ctutil.GetPatchFromStorage(task.ChromiumPatchGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.ChromiumPatchGSPath, err) |
| } |
| taskVars.BlinkPatch, err = ctutil.GetPatchFromStorage(task.BlinkPatchGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.BlinkPatchGSPath, err) |
| } |
| taskVars.SkiaPatch, err = ctutil.GetPatchFromStorage(task.SkiaPatchGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.SkiaPatchGSPath, err) |
| } |
| taskVars.CatapultPatch, err = ctutil.GetPatchFromStorage(task.CatapultPatchGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.CatapultPatchGSPath, err) |
| } |
| taskVars.BenchmarkPatch, err = ctutil.GetPatchFromStorage(task.BenchmarkPatchGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.BenchmarkPatchGSPath, err) |
| } |
| taskVars.V8Patch, err = ctutil.GetPatchFromStorage(task.V8PatchGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.V8PatchGSPath, err) |
| } |
| taskVars.ChromiumPatchBaseBuild, err = ctutil.GetPatchFromStorage(task.ChromiumPatchBaseBuildGSPath) |
| if err != nil { |
| return nil, fmt.Errorf("Could not read from %s: %s", task.ChromiumPatchBaseBuildGSPath, err) |
| } |
| |
| return taskVars, nil |
| } |
| |
| func (task DatastoreTask) GetResultsLink() string { |
| return task.Results |
| } |
| |
| func (task DatastoreTask) RunsOnGCEWorkers() bool { |
| // Perf tasks should normally always run on bare-metal machines but we |
| // also have Windows GCE instances now. |
| return task.RunOnGCE |
| } |
| |
| func (task DatastoreTask) GetDatastoreKind() ds.Kind { |
| return ds.CHROMIUM_PERF_TASKS |
| } |
| |
| func (task DatastoreTask) Query(it *datastore.Iterator) (interface{}, error) { |
| tasks := []*DatastoreTask{} |
| for { |
| t := &DatastoreTask{} |
| _, err := it.Next(t) |
| if err == iterator.Done { |
| break |
| } else if err != nil { |
| return nil, fmt.Errorf("Failed to retrieve list of tasks: %s", err) |
| } |
| tasks = append(tasks, t) |
| } |
| |
| return tasks, nil |
| } |
| |
| func (task DatastoreTask) Get(c context.Context, key *datastore.Key) (task_common.Task, error) { |
| t := &DatastoreTask{} |
| if err := ds.DS.Get(c, key, t); err != nil { |
| return nil, err |
| } |
| return t, nil |
| } |
| |
| func (task DatastoreTask) TriggerSwarmingTaskAndMail(ctx context.Context) error { |
| runID := task_common.GetRunID(&task) |
| emails := task_common.GetEmailRecipients(task.Username, task.CCList) |
| isolateArgs := map[string]string{ |
| "RUN_REQUESTER": task.Username, |
| "DESCRIPTION": task.Description, |
| "PAGESET_TYPE": task.PageSets, |
| "BENCHMARK": task.Benchmark, |
| "BENCHMARK_ARGS": task.BenchmarkArgs, |
| "BROWSER_EXTRA_ARGS_NOPATCH": task.BrowserArgsNoPatch, |
| "BROWSER_EXTRA_ARGS_WITHPATCH": task.BrowserArgsWithPatch, |
| "REPEAT_BENCHMARK": strconv.FormatInt(task.RepeatRuns, 10), |
| "RUN_IN_PARALLEL": strconv.FormatBool(task.RunInParallel), |
| "TARGET_PLATFORM": task.Platform, |
| "RUN_ON_GCE": strconv.FormatBool(task.RunsOnGCEWorkers()), |
| "CHROMIUM_HASH": task.ChromiumHash, |
| "RUN_ID": runID, |
| "TASK_PRIORITY": strconv.Itoa(task.TaskPriority), |
| "GROUP_NAME": task.GroupName, |
| "CHROMIUM_PATCH_GS_PATH": task.ChromiumPatchGSPath, |
| "SKIA_PATCH_GS_PATH": task.SkiaPatchGSPath, |
| "V8_PATCH_GS_PATH": task.V8PatchGSPath, |
| "CATAPULT_PATCH_GS_PATH": task.CatapultPatchGSPath, |
| "CHROMIUM_BASE_BUILD_PATCH_GS_PATH": task.ChromiumPatchBaseBuildGSPath, |
| "CUSTOM_WEBPAGES_CSV_GS_PATH": task.CustomWebpagesGSPath, |
| } |
| sTaskID, err := ctutil.TriggerMasterScriptSwarmingTask(ctx, runID, "run_chromium_perf_on_workers", ctutil.CHROMIUM_PERF_MASTER_ISOLATE, task_common.ServiceAccountFile, task.Platform, false, isolateArgs) |
| if err != nil { |
| return fmt.Errorf("Could not trigger master script for run_chromium_perf_on_workers with isolate args %v: %s", isolateArgs, err) |
| } |
| // Mark task as started in datastore. |
| if err := task_common.UpdateTaskSetStarted(ctx, runID, sTaskID, &task); err != nil { |
| return fmt.Errorf("Could not mark task as started in datastore: %s", err) |
| } |
| // Send start email. |
| skutil.LogErr(ctfeutil.SendTaskStartEmail(task.DatastoreKey.ID, emails, "Chromium perf", runID, task.Description, fmt.Sprintf("Triggered %s benchmark on %s %s pageset.", task.Benchmark, task.Platform, task.PageSets))) |
| |
| return nil |
| } |
| |
| func (task DatastoreTask) SendCompletionEmail(ctx context.Context, completedSuccessfully bool) error { |
| runID := task_common.GetRunID(&task) |
| emails := task_common.GetEmailRecipients(task.Username, task.CCList) |
| emailSubject := fmt.Sprintf("Cluster telemetry chromium perf task has completed (#%d)", task.DatastoreKey.ID) |
| failureHtml := "" |
| viewActionMarkup := "" |
| ctPerfHtml := "" |
| var err error |
| |
| if completedSuccessfully { |
| if viewActionMarkup, err = email.GetViewActionMarkup(task.Results, "View Results", "Direct link to the HTML results"); err != nil { |
| return fmt.Errorf("Failed to get view action markup: %s", err) |
| } |
| ctPerfHtml = ctfeutil.GetCTPerfEmailHtml(task.GroupName) |
| } else { |
| emailSubject += " with failures" |
| failureHtml = ctfeutil.GetFailureEmailHtml(runID) |
| if viewActionMarkup, err = email.GetViewActionMarkup(fmt.Sprintf(ctutil.SWARMING_RUN_ID_ALL_TASKS_LINK_TEMPLATE, runID), "View Failure", "Direct link to the swarming logs"); err != nil { |
| return fmt.Errorf("Failed to get view action markup: %s", err) |
| } |
| } |
| bodyTemplate := ` |
| The chromium perf %s benchmark task on %s pageset has completed. %s.<br/> |
| Run description: %s<br/> |
| %s |
| %s |
| The HTML output with differences between the base run and the patch run is <a href='%s'>here</a>.<br/> |
| The patch(es) you specified are here: |
| <a href='%s'>chromium</a>/<a href='%s'>skia</a>/<a href='%s'>v8</a>/<a href='%s'>catapult</a>/<a href='%s'>chromium (base build)</a> |
| <br/> |
| Custom webpages (if specified) are <a href='%s'>here</a>. |
| <br/><br/> |
| You can schedule more runs <a href='%s'>here</a>. |
| <br/><br/> |
| Thanks! |
| ` |
| chromiumPatchLink := ctutil.GCS_HTTP_LINK + path.Join(ctutil.GCSBucketName, task.ChromiumPatchGSPath) |
| skiaPatchLink := ctutil.GCS_HTTP_LINK + path.Join(ctutil.GCSBucketName, task.SkiaPatchGSPath) |
| v8PatchLink := ctutil.GCS_HTTP_LINK + path.Join(ctutil.GCSBucketName, task.V8PatchGSPath) |
| catapultPatchLink := ctutil.GCS_HTTP_LINK + path.Join(ctutil.GCSBucketName, task.CatapultPatchGSPath) |
| chromiumPatchBaseBuildLink := ctutil.GCS_HTTP_LINK + path.Join(ctutil.GCSBucketName, task.ChromiumPatchBaseBuildGSPath) |
| customWebpagesLink := ctutil.GCS_HTTP_LINK + path.Join(ctutil.GCSBucketName, task.CustomWebpagesGSPath) |
| emailBody := fmt.Sprintf(bodyTemplate, task.Benchmark, task.PageSets, ctfeutil.GetSwarmingLogsLink(runID), task.Description, failureHtml, ctPerfHtml, task.Results, chromiumPatchLink, skiaPatchLink, v8PatchLink, catapultPatchLink, chromiumPatchBaseBuildLink, customWebpagesLink, path.Join(task_common.WebappURL, ctfeutil.CHROMIUM_PERF_URI)) |
| if err := ctfeutil.SendEmailWithMarkup(emails, emailSubject, emailBody, viewActionMarkup); err != nil { |
| return fmt.Errorf("Error while sending email: %s", err) |
| } |
| return nil |
| } |
| |
| func (task *DatastoreTask) SetCompleted(success bool) { |
| if success { |
| runID := task_common.GetRunID(task) |
| task.Results = ctutil.GetPerfOutputLink(runID) |
| task.NoPatchRawOutput = ctutil.GetPerfNoPatchOutputLink(runID) |
| task.WithPatchRawOutput = ctutil.GetPerfWithPatchOutputLink(runID) |
| } |
| task.TsCompleted = ctutil.GetCurrentTsInt64() |
| task.Failure = !success |
| task.TaskDone = true |
| } |
| |
| func addTaskView(w http.ResponseWriter, r *http.Request) { |
| ctfeutil.ExecuteSimpleTemplate(addTaskTemplate, w, r) |
| } |
| |
| type AddTaskVars struct { |
| task_common.AddTaskCommonVars |
| |
| Benchmark string `json:"benchmark"` |
| Platform string `json:"platform"` |
| RunOnGCE string `json:"run_on_gce"` |
| PageSets string `json:"page_sets"` |
| CustomWebpages string `json:"custom_webpages"` |
| RepeatRuns string `json:"repeat_runs"` |
| RunInParallel string `json:"run_in_parallel"` |
| BenchmarkArgs string `json:"benchmark_args"` |
| BrowserArgsNoPatch string `json:"browser_args_nopatch"` |
| BrowserArgsWithPatch string `json:"browser_args_withpatch"` |
| Description string `json:"desc"` |
| ChromiumHash string `json:"chromium_hash"` |
| CCList []string `json:"cc_list"` |
| TaskPriority string `json:"task_priority"` |
| GroupName string `json:"group_name"` |
| |
| ChromiumPatch string `json:"chromium_patch"` |
| BlinkPatch string `json:"blink_patch"` |
| SkiaPatch string `json:"skia_patch"` |
| CatapultPatch string `json:"catapult_patch"` |
| BenchmarkPatch string `json:"benchmark_patch"` |
| V8Patch string `json:"v8_patch"` |
| ChromiumPatchBaseBuild string `json:"chromium_patch_base_build"` |
| } |
| |
| func (task *AddTaskVars) GetDatastoreKind() ds.Kind { |
| return ds.CHROMIUM_PERF_TASKS |
| } |
| |
| func (task *AddTaskVars) GetPopulatedDatastoreTask(ctx context.Context) (task_common.Task, error) { |
| if task.Benchmark == "" || |
| task.Platform == "" || |
| task.PageSets == "" || |
| task.RepeatRuns == "" || |
| task.RunInParallel == "" || |
| task.Description == "" { |
| return nil, fmt.Errorf("Invalid parameters") |
| } |
| if task.GroupName != "" && len(task.GroupName) >= ctfeutil.MAX_GROUPNAME_LEN { |
| return nil, fmt.Errorf("Please limit group names to less than %d characters", ctfeutil.MAX_GROUPNAME_LEN) |
| } |
| |
| customWebpagesSlice, err := ctfeutil.GetQualifiedCustomWebpages(task.CustomWebpages, task.BenchmarkArgs) |
| if err != nil { |
| return nil, err |
| } |
| customWebpages := strings.Join(customWebpagesSlice, ",") |
| customWebpagesGSPath, err := ctutil.SavePatchToStorage(customWebpages) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save custom webpages to storage: %s", err) |
| } |
| |
| chromiumPatchGSPath, err := ctutil.SavePatchToStorage(task.ChromiumPatch) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save chromium patch to storage: %s", err) |
| } |
| blinkPatchGSPath, err := ctutil.SavePatchToStorage(task.BlinkPatch) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save blink patch to storage: %s", err) |
| } |
| skiaPatchGSPath, err := ctutil.SavePatchToStorage(task.SkiaPatch) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save skia patch to storage: %s", err) |
| } |
| catapultPatchGSPath, err := ctutil.SavePatchToStorage(task.CatapultPatch) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save catapult patch to storage: %s", err) |
| } |
| benchmarkPatchGSPath, err := ctutil.SavePatchToStorage(task.BenchmarkPatch) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save benchmark patch to storage: %s", err) |
| } |
| v8PatchGSPath, err := ctutil.SavePatchToStorage(task.V8Patch) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save v8 patch to storage: %s", err) |
| } |
| chromiumPatchBaseBuildGSPath, err := ctutil.SavePatchToStorage(task.ChromiumPatchBaseBuild) |
| if err != nil { |
| return nil, fmt.Errorf("Could not save chromium patch for base build to storage: %s", err) |
| } |
| |
| t := &DatastoreTask{ |
| Benchmark: task.Benchmark, |
| Platform: task.Platform, |
| PageSets: task.PageSets, |
| IsTestPageSet: task.PageSets == ctutil.PAGESET_TYPE_DUMMY_1k || task.PageSets == ctutil.PAGESET_TYPE_MOBILE_DUMMY_1k, |
| BenchmarkArgs: task.BenchmarkArgs, |
| BrowserArgsNoPatch: task.BrowserArgsNoPatch, |
| BrowserArgsWithPatch: task.BrowserArgsWithPatch, |
| Description: task.Description, |
| ChromiumHash: task.ChromiumHash, |
| CCList: task.CCList, |
| GroupName: task.GroupName, |
| |
| CustomWebpagesGSPath: customWebpagesGSPath, |
| ChromiumPatchGSPath: chromiumPatchGSPath, |
| BlinkPatchGSPath: blinkPatchGSPath, |
| SkiaPatchGSPath: skiaPatchGSPath, |
| CatapultPatchGSPath: catapultPatchGSPath, |
| BenchmarkPatchGSPath: benchmarkPatchGSPath, |
| V8PatchGSPath: v8PatchGSPath, |
| ChromiumPatchBaseBuildGSPath: chromiumPatchBaseBuildGSPath, |
| } |
| runOnGCE, err := strconv.ParseBool(task.RunOnGCE) |
| if err != nil { |
| return nil, fmt.Errorf("%s is not bool: %s", task.RunOnGCE, err) |
| } |
| t.RunOnGCE = runOnGCE |
| runInParallel, err := strconv.ParseBool(task.RunInParallel) |
| if err != nil { |
| return nil, fmt.Errorf("%s is not bool: %s", task.RunInParallel, err) |
| } |
| t.RunInParallel = runInParallel |
| repeatRuns, err := strconv.ParseInt(task.RepeatRuns, 10, 64) |
| if err != nil { |
| return nil, fmt.Errorf("%s is not int64: %s", task.RepeatRuns, err) |
| } |
| t.RepeatRuns = repeatRuns |
| taskPriority, err := strconv.Atoi(task.TaskPriority) |
| if err != nil { |
| return nil, fmt.Errorf("%s is not int: %s", task.TaskPriority, err) |
| } |
| if taskPriority == 0 { |
| // This should only happen for repeating tasks that were created before |
| // support for task priorities was added to CT. |
| // Triggering tasks with 0 priority fails in swarming with |
| // "priority 0 can only be used for terminate request" |
| // Override it to the medium priority. |
| taskPriority = ctutil.TASKS_PRIORITY_MEDIUM |
| } |
| t.TaskPriority = taskPriority |
| return t, nil |
| } |
| |
| func addTaskHandler(w http.ResponseWriter, r *http.Request) { |
| task_common.AddTaskHandler(w, r, &AddTaskVars{}) |
| } |
| |
| func getTasksHandler(w http.ResponseWriter, r *http.Request) { |
| task_common.GetTasksHandler(&DatastoreTask{}, w, r) |
| } |
| |
| func deleteTaskHandler(w http.ResponseWriter, r *http.Request) { |
| task_common.DeleteTaskHandler(&DatastoreTask{}, w, r) |
| } |
| |
| func redoTaskHandler(w http.ResponseWriter, r *http.Request) { |
| task_common.RedoTaskHandler(&DatastoreTask{}, w, r) |
| } |
| |
| func runsHistoryView(w http.ResponseWriter, r *http.Request) { |
| ctfeutil.ExecuteSimpleTemplate(runsHistoryTemplate, w, r) |
| } |
| |
| func AddHandlers(externalRouter *mux.Router) { |
| externalRouter.HandleFunc("/", addTaskView).Methods("GET") |
| externalRouter.HandleFunc("/"+ctfeutil.CHROMIUM_PERF_URI, addTaskView).Methods("GET") |
| externalRouter.HandleFunc("/"+ctfeutil.CHROMIUM_PERF_RUNS_URI, runsHistoryView).Methods("GET") |
| |
| externalRouter.HandleFunc("/"+ctfeutil.ADD_CHROMIUM_PERF_TASK_POST_URI, addTaskHandler).Methods("POST") |
| externalRouter.HandleFunc("/"+ctfeutil.GET_CHROMIUM_PERF_TASKS_POST_URI, getTasksHandler).Methods("POST") |
| externalRouter.HandleFunc("/"+ctfeutil.DELETE_CHROMIUM_PERF_TASK_POST_URI, deleteTaskHandler).Methods("POST") |
| externalRouter.HandleFunc("/"+ctfeutil.REDO_CHROMIUM_PERF_TASK_POST_URI, redoTaskHandler).Methods("POST") |
| } |