github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/bookkeeping/task_bookkeeping.go (about) 1 package bookkeeping 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/evergreen-ci/evergreen/db" 8 "github.com/evergreen-ci/evergreen/model/task" 9 "github.com/mongodb/grip" 10 "gopkg.in/mgo.v2" 11 "gopkg.in/mgo.v2/bson" 12 ) 13 14 var _ fmt.Stringer = nil 15 16 const ( 17 DefaultGuessDur = time.Minute * 20 18 TaskBookkeepingCollection = "task_bk" 19 ) 20 21 type TaskBookkeeping struct { 22 // standard object id 23 Id bson.ObjectId `bson:"_id"` 24 25 // info that tasks with the same guessed duration will share 26 Name string `bson:"name"` 27 BuildVariant string `bson:"build_variant"` 28 HostType string `bson:"host_type"` // may change to an enum once Host.HostType changes 29 Project string `bson:"branch"` 30 31 // the duration we expect the task to take 32 ExpectedDuration time.Duration `bson:"expected_duration"` 33 34 // the number of times this task - as defined by the 35 // buildvariant and task name - has been started 36 NumStarted int64 `bson:"num_started"` 37 } 38 39 /************************************************************ 40 Helper functions to reduce boilerplate 41 ************************************************************/ 42 43 // finds a bookkeeping entry matching the specified interface 44 func findOneTaskBk(matcher interface{}, selector interface{}) (*TaskBookkeeping, error) { 45 46 // establish a database connection 47 session, db, err := db.GetGlobalSessionFactory().GetSession() 48 if err != nil { 49 grip.Errorf("Error establishing database connection: %+v", err) 50 return nil, err 51 } 52 53 // make sure the function is closed when the function exits 54 defer session.Close() 55 56 // query for the bookkeeping entry 57 taskBk := &TaskBookkeeping{} 58 err = db.C(TaskBookkeepingCollection).Find(matcher).Select(selector).One(taskBk) 59 60 // no entry was found 61 if err == mgo.ErrNotFound { 62 return nil, nil 63 } 64 65 // failure 66 if err != nil { 67 grip.Errorf("Unexpected error retrieving task bookkeeping entry from database: %+v", 68 err) 69 return nil, err 70 } 71 72 // success 73 return taskBk, nil 74 } 75 76 // upsert a single bookkeeping entry 77 func upsertOneTaskBk(matcher interface{}, update interface{}) error { 78 79 // establish a database connection 80 session, db, err := db.GetGlobalSessionFactory().GetSession() 81 if err != nil { 82 grip.Errorf("Error establishing database connection: %+v", err) 83 return err 84 } 85 86 // make sure the session is closed when the function exits 87 defer session.Close() 88 89 // update the bookkeeping entry 90 _, err = db.C(TaskBookkeepingCollection).Upsert(matcher, update) 91 return err 92 } 93 94 // update the expected duration that we expect the given task to take when run on the 95 // given host 96 func UpdateExpectedDuration(t *task.Task, timeTaken time.Duration) error { 97 matcher := bson.M{ 98 "name": t.DisplayName, 99 "build_variant": t.BuildVariant, 100 "branch": t.Project, 101 } 102 taskBk, err := findOneTaskBk(matcher, bson.M{}) 103 if err != nil { 104 return err 105 } 106 var averageTaskDuration time.Duration 107 108 if taskBk == nil { 109 averageTaskDuration = timeTaken 110 } else { 111 averageTime := ((taskBk.ExpectedDuration.Nanoseconds() * taskBk.NumStarted) + timeTaken.Nanoseconds()) / (taskBk.NumStarted + 1) 112 averageTaskDuration = time.Duration(averageTime) 113 } 114 115 // for now, we are just using the duration of the last comparable task ran as the 116 // guess for upcoming tasks 117 118 update := bson.M{ 119 "$set": bson.M{"expected_duration": averageTaskDuration}, 120 "$inc": bson.M{"num_started": 1}, 121 } 122 123 return upsertOneTaskBk(matcher, update) 124 }