// addRecDeps recursively finds all dependencies of tasks and adds them to tasks and uiDeps. // done is a hashtable of task IDs whose dependencies we have found. // TODO EVG-614: delete this function once Task.DependsOn includes all recursive dependencies. func addRecDeps(tasks map[string]task.Task, uiDeps map[string]uiDep, done map[string]bool) error { curTask := make(map[string]bool) depIds := make([]string, 0) for _, t := range tasks { if _, ok := done[t.Id]; !ok { for _, dep := range t.DependsOn { depIds = append(depIds, dep.TaskId) } curTask[t.Id] = true } } if len(depIds) == 0 { return nil } deps, err := task.Find(task.ByIds(depIds).WithFields(task.DisplayNameKey, task.StatusKey, task.ActivatedKey, task.BuildVariantKey, task.DetailsKey, task.DependsOnKey)) if err != nil { return err } for _, dep := range deps { tasks[dep.Id] = dep } for _, t := range tasks { if _, ok := curTask[t.Id]; ok { for _, dep := range t.DependsOn { if uid, ok := uiDeps[dep.TaskId]; !ok || // only replace if the current uiDep is not strict and not recursive (uid.RequiredStatus == model.AllStatuses && !uid.Recursive) { depTask := tasks[dep.TaskId] uiDeps[depTask.Id] = uiDep{ Id: depTask.Id, Name: depTask.DisplayName, Status: depTask.Status, RequiredStatus: dep.Status, Activated: depTask.Activated, BuildVariant: depTask.BuildVariant, Details: depTask.Details, Recursive: true, } } } done[t.Id] = true } } return addRecDeps(tasks, uiDeps, done) }
// getTaskDependencies returns the uiDeps for the task and its status (either its original status, // "blocked", or "pending") func getTaskDependencies(t *task.Task) ([]uiDep, string, error) { depIds := []string{} for _, dep := range t.DependsOn { depIds = append(depIds, dep.TaskId) } dependencies, err := task.Find(task.ByIds(depIds).WithFields(task.DisplayNameKey, task.StatusKey, task.ActivatedKey, task.BuildVariantKey, task.DetailsKey, task.DependsOnKey)) if err != nil { return nil, "", err } idToUiDep := make(map[string]uiDep) // match each task with its dependency requirements for _, depTask := range dependencies { for _, dep := range t.DependsOn { if dep.TaskId == depTask.Id { idToUiDep[depTask.Id] = uiDep{ Id: depTask.Id, Name: depTask.DisplayName, Status: depTask.Status, RequiredStatus: dep.Status, Activated: depTask.Activated, BuildVariant: depTask.BuildVariant, Details: depTask.Details, //TODO EVG-614: add "Recursive: dep.Recursive," once Task.DependsOn includes all recursive dependencies } } } } idToDep := make(map[string]task.Task) for _, dep := range dependencies { idToDep[dep.Id] = dep } // TODO EVG 614: delete this section once Task.DependsOn includes all recursive dependencies err = addRecDeps(idToDep, idToUiDep, make(map[string]bool)) if err != nil { return nil, "", err } // set the status for each of the uiDeps as "blocked" or "pending" if appropriate // and get the status for task status := setBlockedOrPending(*t, idToDep, idToUiDep) uiDeps := make([]uiDep, 0, len(idToUiDep)) for _, dep := range idToUiDep { uiDeps = append(uiDeps, dep) } return uiDeps, status, nil }
// computeRunningTasksDuration returns the estimated time to completion of all // currently running tasks for a given distro given its hosts func computeRunningTasksDuration(existingDistroHosts []host.Host, taskDurations model.ProjectTaskDurations) (runningTasksDuration float64, err error) { runningTaskIds := []string{} for _, existingDistroHost := range existingDistroHosts { if existingDistroHost.RunningTask != "" { runningTaskIds = append(runningTaskIds, existingDistroHost.RunningTask) } } // if this distro's hosts are all free, return immediately if len(runningTaskIds) == 0 { return } runningTasksMap := make(map[string]task.Task) runningTasks, err := task.Find(task.ByIds(runningTaskIds)) if err != nil { return runningTasksDuration, err } // build a map of task id => task for _, runningTask := range runningTasks { runningTasksMap[runningTask.Id] = runningTask } // compute the total time to completion for running tasks for _, runningTaskId := range runningTaskIds { runningTask, ok := runningTasksMap[runningTaskId] if !ok { return runningTasksDuration, fmt.Errorf("Unable to find running "+ "task with _id %v", runningTaskId) } expectedDuration := model.GetTaskExpectedDuration(runningTask, taskDurations) elapsedTime := time.Now().Sub(runningTask.StartTime) if elapsedTime > expectedDuration { // probably an outlier; or an unknown data point continue } runningTasksDuration += expectedDuration.Seconds() - elapsedTime.Seconds() } return }
// GetFailedTests returns a mapping of task id to a slice of failed tasks // extracted from a pipeline of aggregated tasks func (self *taskHistoryIterator) GetFailedTests(aggregatedTasks *mgo.Pipe) (map[string][]task.TestResult, error) { // get the ids of the failed task var failedTaskIds []string var taskHistory TaskHistory iter := aggregatedTasks.Iter() for { if iter.Next(&taskHistory) { for _, task := range taskHistory.Tasks { if task.Status == evergreen.TaskFailed { failedTaskIds = append(failedTaskIds, task.Id) } } } else { break } } if err := iter.Err(); err != nil { return nil, err } // find all the relevant failed tests failedTestsMap := make(map[string][]task.TestResult) tasks, err := task.Find(task.ByIds(failedTaskIds).WithFields(task.IdKey, task.TestResultsKey)) if err != nil { return nil, err } // create the mapping of the task id to the list of failed tasks for _, task := range tasks { for _, test := range task.TestResults { if test.Status == evergreen.TestFailedStatus { failedTestsMap[task.Id] = append(failedTestsMap[task.Id], test) } } } return failedTestsMap, nil }
// Fetch versions until 'numVersionElements' elements are created, including // elements consisting of multiple versions rolled-up into one. // The skip value indicates how many versions back in time should be skipped // before starting to fetch versions, the project indicates which project the // returned versions should be a part of. func getVersionsAndVariants(skip, numVersionElements int, project *model.Project) (versionVariantData, error) { // the final array of versions to return finalVersions := []waterfallVersion{} // keep track of the build variants we see bvSet := map[string]bool{} waterfallRows := map[string]waterfallRow{} // build variant mappings - used so we can store the display name as // the build variant field of a build buildVariantMappings := project.GetVariantMappings() // keep track of the last rolled-up version, so inactive versions can // be added var lastRolledUpVersion *waterfallVersion = nil // loop until we have enough from the db for len(finalVersions) < numVersionElements { // fetch the versions and associated builds versionsFromDB, buildsByVersion, err := fetchVersionsAndAssociatedBuilds(project, skip, numVersionElements) if err != nil { return versionVariantData{}, fmt.Errorf("error fetching versions and builds:"+ " %v", err) } // if we've reached the beginning of all versions if len(versionsFromDB) == 0 { break } // to fetch started tasks and failed tests for providing additional context // in a tooltip failedAndStartedTaskIds := []string{} // update the amount skipped skip += len(versionsFromDB) // create the necessary versions, rolling up inactive ones for _, versionFromDB := range versionsFromDB { // if we have hit enough versions, break out if len(finalVersions) == numVersionElements { break } // the builds for the version buildsInVersion := buildsByVersion[versionFromDB.Id] // see if there are any active tasks in the version versionActive := anyActiveTasks(buildsInVersion) // add any represented build variants to the set and initialize rows for _, b := range buildsInVersion { bvSet[b.BuildVariant] = true buildVariant := waterfallBuildVariant{ Id: b.BuildVariant, DisplayName: buildVariantMappings[b.BuildVariant], } if buildVariant.DisplayName == "" { buildVariant.DisplayName = b.BuildVariant + " (removed)" } if _, ok := waterfallRows[b.BuildVariant]; !ok { waterfallRows[b.BuildVariant] = waterfallRow{ Builds: map[string]waterfallBuild{}, BuildVariant: buildVariant, } } } // if it is inactive, roll up the version and don't create any // builds for it if !versionActive { if lastRolledUpVersion == nil { lastRolledUpVersion = &waterfallVersion{RolledUp: true, RevisionOrderNumber: versionFromDB.RevisionOrderNumber} } // add the version metadata into the last rolled-up version lastRolledUpVersion.Ids = append(lastRolledUpVersion.Ids, versionFromDB.Id) lastRolledUpVersion.Authors = append(lastRolledUpVersion.Authors, versionFromDB.Author) lastRolledUpVersion.Errors = append( lastRolledUpVersion.Errors, waterfallVersionError{versionFromDB.Errors}) lastRolledUpVersion.Warnings = append( lastRolledUpVersion.Warnings, waterfallVersionError{versionFromDB.Warnings}) lastRolledUpVersion.Messages = append( lastRolledUpVersion.Messages, versionFromDB.Message) lastRolledUpVersion.Ignoreds = append( lastRolledUpVersion.Ignoreds, versionFromDB.Ignored) lastRolledUpVersion.CreateTimes = append( lastRolledUpVersion.CreateTimes, versionFromDB.CreateTime) lastRolledUpVersion.Revisions = append( lastRolledUpVersion.Revisions, versionFromDB.Revision) // move on to the next version continue } // add a pending rolled-up version, if it exists if lastRolledUpVersion != nil { finalVersions = append(finalVersions, *lastRolledUpVersion) lastRolledUpVersion = nil } // if we have hit enough versions, break out if len(finalVersions) == numVersionElements { break } // if the version can not be rolled up, create a fully fledged // version for it activeVersion := waterfallVersion{ Ids: []string{versionFromDB.Id}, Messages: []string{versionFromDB.Message}, Authors: []string{versionFromDB.Author}, CreateTimes: []time.Time{versionFromDB.CreateTime}, Revisions: []string{versionFromDB.Revision}, Errors: []waterfallVersionError{{versionFromDB.Errors}}, Warnings: []waterfallVersionError{{versionFromDB.Warnings}}, Ignoreds: []bool{versionFromDB.Ignored}, RevisionOrderNumber: versionFromDB.RevisionOrderNumber, } // add the builds to the waterfall row for _, b := range buildsInVersion { currentRow := waterfallRows[b.BuildVariant] buildForWaterfall := waterfallBuild{ Id: b.Id, Version: versionFromDB.Id, } tasks, statusCount := createWaterfallTasks(b.Tasks) buildForWaterfall.Tasks = tasks buildForWaterfall.TaskStatusCount = statusCount currentRow.Builds[versionFromDB.Id] = buildForWaterfall waterfallRows[b.BuildVariant] = currentRow for _, task := range buildForWaterfall.Tasks { if task.Status == evergreen.TaskFailed || task.Status == evergreen.TaskStarted { failedAndStartedTaskIds = append(failedAndStartedTaskIds, task.Id) } } } // add the version finalVersions = append(finalVersions, activeVersion) } failedAndStartedTasks, err := task.Find(task.ByIds(failedAndStartedTaskIds)) if err != nil { return versionVariantData{}, fmt.Errorf("error fetching failed tasks:"+ " %v", err) } addFailedAndStartedTests(waterfallRows, failedAndStartedTasks) } // if the last version was rolled-up, add it if lastRolledUpVersion != nil { finalVersions = append(finalVersions, *lastRolledUpVersion) } // create the list of display names for the build variants represented buildVariants := waterfallBuildVariants{} for name := range bvSet { displayName := buildVariantMappings[name] if displayName == "" { displayName = name + " (removed)" } buildVariants = append(buildVariants, waterfallBuildVariant{Id: name, DisplayName: displayName}) } return versionVariantData{ Rows: waterfallRows, Versions: finalVersions, BuildVariants: buildVariants, }, nil }
func (uis *UIServer) allTaskQueues(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) taskQueues, err := model.FindAllTaskQueues() if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error finding task queues: %v", err)) return } // cached map of version id to relevant patch cachedPatches := map[string]*patch.Patch{} // convert the task queues to the ui versions uiTaskQueues := []uiTaskQueue{} for _, tQ := range taskQueues { asUI := uiTaskQueue{ Distro: tQ.Distro, Queue: []uiTaskQueueItem{}, } if len(tQ.Queue) == 0 { uiTaskQueues = append(uiTaskQueues, asUI) continue } // convert the individual task queue items taskIds := []string{} for _, item := range tQ.Queue { // cache the ids, for fetching the tasks from the db taskIds = append(taskIds, item.Id) queueItemAsUI := uiTaskQueueItem{ Id: item.Id, DisplayName: item.DisplayName, BuildVariant: item.BuildVariant, RevisionOrderNumber: item.RevisionOrderNumber, Requester: item.Requester, Revision: item.Revision, Project: item.Project, } asUI.Queue = append(asUI.Queue, queueItemAsUI) } // find all the relevant tasks tasks, err := task.Find(task.ByIds(taskIds).WithFields(task.VersionKey, task.BuildIdKey)) if err != nil { msg := fmt.Sprintf("Error finding tasks: %v", err) evergreen.Logger.Errorf(slogger.ERROR, msg) http.Error(w, msg, http.StatusInternalServerError) return } // store all of the version and build ids in the relevant task queue // items for _, task := range tasks { // this sucks, but it's because we're not guaranteed the order out // of the db for idx, queueItemAsUI := range asUI.Queue { if queueItemAsUI.Id == task.Id { queueItemAsUI.Version = task.Version queueItemAsUI.Build = task.BuildId asUI.Queue[idx] = queueItemAsUI } } } // add all of the necessary patch info into the relevant task queue // items for idx, queueItemAsUI := range asUI.Queue { if queueItemAsUI.Requester == evergreen.PatchVersionRequester { // fetch the patch, if necessary var p *patch.Patch var ok bool if p, ok = cachedPatches[queueItemAsUI.Version]; ok { queueItemAsUI.User = p.Author asUI.Queue[idx] = queueItemAsUI } else { p, err = patch.FindOne( patch.ByVersion(queueItemAsUI.Version).WithFields(patch.AuthorKey), ) if err != nil { msg := fmt.Sprintf("Error finding patch: %v", err) evergreen.Logger.Errorf(slogger.ERROR, msg) http.Error(w, msg, http.StatusInternalServerError) return } if p == nil { msg := fmt.Sprintf("Couldn't find patch for version %v", queueItemAsUI.Version) evergreen.Logger.Errorf(slogger.ERROR, msg) http.Error(w, msg, http.StatusInternalServerError) return } cachedPatches[queueItemAsUI.Version] = p } queueItemAsUI.User = p.Author asUI.Queue[idx] = queueItemAsUI } } uiTaskQueues = append(uiTaskQueues, asUI) } // add other useful statistics to view alongside queue idleHosts, err := host.Find(host.IsIdle) if err != nil { msg := fmt.Sprintf("Error finding idle hosts: %v", err) evergreen.Logger.Errorf(slogger.ERROR, msg) http.Error(w, msg, http.StatusInternalServerError) return } activeHosts, err := host.Find(host.IsLive) if err != nil { msg := fmt.Sprintf("Error finding active hosts: %v", err) evergreen.Logger.Errorf(slogger.ERROR, msg) http.Error(w, msg, http.StatusInternalServerError) return } idleStaticHostsCount := 0 for _, host := range idleHosts { if host.Provider == evergreen.HostTypeStatic { idleStaticHostsCount++ } } activeStaticHostsCount := 0 for _, host := range activeHosts { if host.Provider == evergreen.HostTypeStatic { activeStaticHostsCount++ } } hostStats := uiHostStatistics{ ActiveHosts: len(activeHosts), ActiveStaticHosts: activeStaticHostsCount, IdleHosts: len(idleHosts), IdleStaticHosts: idleStaticHostsCount, } uis.WriteHTML(w, http.StatusOK, struct { ProjectData projectContext User *user.DBUser Flashes []interface{} Data uiResourceInfo }{projCtx, GetUser(r), []interface{}{}, uiResourceInfo{uiTaskQueues, hostStats}}, "base", "task_queues.html", "base_angular.html", "menu.html") }