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  }