github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/scheduler_stats_test.go (about)

     1  package model
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/evergreen-ci/evergreen"
     9  	"github.com/evergreen-ci/evergreen/db"
    10  	"github.com/evergreen-ci/evergreen/model/distro"
    11  	"github.com/evergreen-ci/evergreen/model/host"
    12  	"github.com/evergreen-ci/evergreen/model/task"
    13  	"github.com/evergreen-ci/evergreen/testutil"
    14  	"github.com/evergreen-ci/evergreen/util"
    15  	. "github.com/smartystreets/goconvey/convey"
    16  )
    17  
    18  var projectTestConfig = testutil.TestConfig()
    19  
    20  func init() {
    21  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(projectTestConfig))
    22  }
    23  
    24  func TestBucketResource(t *testing.T) {
    25  	Convey("With a start time and a bucket size of 10 and 10 buckets", t, func() {
    26  		frameStart := time.Now()
    27  		// 10 buckets * 10 bucket size = 100
    28  		frameEnd := frameStart.Add(time.Duration(100))
    29  		bucketSize := time.Duration(10)
    30  		Convey("when resource start time is equal to end time should error", func() {
    31  			buckets := make([]Bucket, 10)
    32  			resourceStart := frameEnd
    33  			resourceEnd := frameEnd.Add(time.Duration(10))
    34  			resource := ResourceInfo{
    35  				Start: resourceStart,
    36  				End:   resourceEnd,
    37  			}
    38  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
    39  			So(err, ShouldNotBeNil)
    40  		})
    41  		Convey("when resource start time is greater than end time should error", func() {
    42  			buckets := make([]Bucket, 10)
    43  			resourceStart := frameEnd.Add(time.Duration(10))
    44  			resourceEnd := frameEnd.Add(time.Duration(20))
    45  			resource := ResourceInfo{
    46  				Start: resourceStart,
    47  				End:   resourceEnd,
    48  			}
    49  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
    50  			So(err, ShouldNotBeNil)
    51  		})
    52  		Convey("when resource end time is equal to start time should error", func() {
    53  			buckets := make([]Bucket, 10)
    54  			resourceStart := frameStart.Add(time.Duration(-10))
    55  			resourceEnd := frameStart
    56  			resource := ResourceInfo{
    57  				Start: resourceStart,
    58  				End:   resourceEnd,
    59  			}
    60  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
    61  			So(err, ShouldNotBeNil)
    62  		})
    63  		Convey("when resource end time is less than start time should error", func() {
    64  			buckets := make([]Bucket, 10)
    65  			resourceStart := frameStart.Add(time.Duration(-30))
    66  			resourceEnd := frameStart.Add(time.Duration(-10))
    67  			resource := ResourceInfo{
    68  				Start: resourceStart,
    69  				End:   resourceEnd,
    70  			}
    71  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
    72  			So(err, ShouldNotBeNil)
    73  		})
    74  		Convey("when resource end time is less than resource start time, should error", func() {
    75  			buckets := make([]Bucket, 10)
    76  			resourceStart := frameStart.Add(time.Duration(10))
    77  			resourceEnd := frameStart
    78  			resource := ResourceInfo{
    79  				Start: resourceStart,
    80  				End:   resourceEnd,
    81  			}
    82  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
    83  			So(err, ShouldNotBeNil)
    84  		})
    85  		Convey("when resource start is zero, errors out", func() {
    86  			buckets := make([]Bucket, 10)
    87  			resourceStart := time.Time{}
    88  			resourceEnd := frameStart.Add(time.Duration(1))
    89  			resource := ResourceInfo{
    90  				Start: resourceStart,
    91  				End:   resourceEnd,
    92  			}
    93  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
    94  			So(err, ShouldNotBeNil)
    95  		})
    96  		Convey("when the resource start and end time are in the same bucket, only one bucket has the difference", func() {
    97  			buckets := make([]Bucket, 10)
    98  			resourceStart := frameStart.Add(time.Duration(1))
    99  			resourceEnd := frameStart.Add(time.Duration(5))
   100  			resource := ResourceInfo{
   101  				Start: resourceStart,
   102  				End:   resourceEnd,
   103  			}
   104  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
   105  			So(err, ShouldBeNil)
   106  			So(buckets[0].TotalTime, ShouldEqual, time.Duration(4))
   107  			for i := 1; i < 10; i++ {
   108  				So(buckets[i].TotalTime, ShouldEqual, 0)
   109  			}
   110  		})
   111  		Convey("when the resourceEnd is zero, there is no error", func() {
   112  			buckets := make([]Bucket, 10)
   113  			resourceStart := frameStart.Add(time.Duration(10))
   114  			resourceEnd := util.ZeroTime
   115  			So(util.IsZeroTime(resourceEnd), ShouldBeTrue)
   116  			resource := ResourceInfo{
   117  				Start: resourceStart,
   118  				End:   resourceEnd,
   119  			}
   120  			_, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
   121  			So(err, ShouldBeNil)
   122  			So(buckets[0].TotalTime, ShouldEqual, 0)
   123  			for i := 1; i < 10; i++ {
   124  				So(buckets[i].TotalTime, ShouldEqual, 10)
   125  			}
   126  		})
   127  
   128  	})
   129  }
   130  
   131  func TestCreateHostBuckets(t *testing.T) {
   132  	testutil.HandleTestingErr(db.ClearCollections(host.Collection), t, "couldnt reset host")
   133  	Convey("With a starting time and a minute bucket size and inserting dynamic hosts with different time frames", t, func() {
   134  		now := time.Now()
   135  		bucketSize := time.Duration(10) * time.Second
   136  
   137  		// -20 -> 20
   138  		beforeStartHost := host.Host{Id: "beforeStartHost", CreationTime: now.Add(time.Duration(-20) * time.Second), TerminationTime: now.Add(time.Duration(20) * time.Second), Provider: "ec2"}
   139  		So(beforeStartHost.Insert(), ShouldBeNil)
   140  
   141  		// 80 -> 120
   142  		afterEndHost := host.Host{Id: "afterEndHost", CreationTime: now.Add(time.Duration(80) * time.Second), TerminationTime: now.Add(time.Duration(120) * time.Second), Provider: "ec2"}
   143  		So(afterEndHost.Insert(), ShouldBeNil)
   144  
   145  		// 20 -> 40
   146  		h1 := host.Host{Id: "h1", CreationTime: now.Add(time.Duration(20) * time.Second), TerminationTime: now.Add(time.Duration(40) * time.Second), Provider: "ec2"}
   147  		So(h1.Insert(), ShouldBeNil)
   148  
   149  		// 10 -> 80
   150  		h2 := host.Host{Id: "h2", CreationTime: now.Add(time.Duration(10) * time.Second), TerminationTime: now.Add(time.Duration(80) * time.Second), Provider: "ec2"}
   151  		So(h2.Insert(), ShouldBeNil)
   152  
   153  		// 20 ->
   154  		h3 := host.Host{Id: "h3", CreationTime: now.Add(time.Duration(20) * time.Second), TerminationTime: util.ZeroTime, Provider: "ec2", Status: evergreen.HostRunning}
   155  		So(h3.Insert(), ShouldBeNil)
   156  
   157  		// 5 -> 7
   158  		sameBucket := host.Host{Id: "sameBucket", CreationTime: now.Add(time.Duration(5) * time.Second), TerminationTime: now.Add(time.Duration(7) * time.Second), Provider: "ec2"}
   159  		So(sameBucket.Insert(), ShouldBeNil)
   160  
   161  		// 5 -> 30
   162  		h4 := host.Host{Id: "h4", CreationTime: now.Add(time.Duration(5) * time.Second), TerminationTime: now.Add(time.Duration(30) * time.Second), Provider: "ec2"}
   163  		So(h4.Insert(), ShouldBeNil)
   164  
   165  		Convey("for three buckets of 10 seconds, should only retrieve pertinent host docs", func() {
   166  
   167  			endTime := now.Add(time.Duration(30) * time.Second)
   168  			hosts, err := host.Find(host.ByDynamicWithinTime(now, endTime))
   169  			So(err, ShouldBeNil)
   170  			So(len(hosts), ShouldEqual, 6)
   171  			frameBounds := FrameBounds{
   172  				StartTime:     now,
   173  				EndTime:       endTime,
   174  				BucketSize:    bucketSize,
   175  				NumberBuckets: 3,
   176  			}
   177  			Convey("should create the correct buckets and bucket time accordingly", func() {
   178  				buckets, errors := CreateHostBuckets(hosts, frameBounds)
   179  				So(errors, ShouldBeEmpty)
   180  				So(len(buckets), ShouldEqual, 3)
   181  				So(int(buckets[0].TotalTime.Seconds()), ShouldEqual, 17)
   182  				So(int(buckets[1].TotalTime.Seconds()), ShouldEqual, 30)
   183  				So(int(math.Ceil(buckets[2].TotalTime.Seconds())), ShouldEqual, 40)
   184  			})
   185  		})
   186  
   187  	})
   188  }
   189  
   190  func TestCreateTaskBuckets(t *testing.T) {
   191  	testutil.HandleTestingErr(db.ClearCollections(task.Collection), t, "couldnt reset host")
   192  	Convey("With a starting time and a minute bucket size and inserting tasks with different start and finish", t, func() {
   193  		now := time.Now()
   194  		bucketSize := time.Duration(10) * time.Second
   195  
   196  		// -20 -> 20
   197  		beforeStartHost := task.Task{Id: "beforeStartTask", StartTime: now.Add(time.Duration(-20) * time.Second), FinishTime: now.Add(time.Duration(20) * time.Second), Status: evergreen.TaskSucceeded}
   198  		So(beforeStartHost.Insert(), ShouldBeNil)
   199  
   200  		// 80 -> 120
   201  		afterEndHost := task.Task{Id: "afterStartTask", StartTime: now.Add(time.Duration(80) * time.Second), FinishTime: now.Add(time.Duration(120) * time.Second), Status: evergreen.TaskFailed}
   202  		So(afterEndHost.Insert(), ShouldBeNil)
   203  
   204  		// 20 -> 40: shouldnt be added
   205  		h1 := task.Task{Id: "h1", StartTime: now.Add(time.Duration(20) * time.Second), FinishTime: now.Add(time.Duration(40) * time.Second), Status: evergreen.TaskUndispatched}
   206  		So(h1.Insert(), ShouldBeNil)
   207  
   208  		// 10 -> 80
   209  		h2 := task.Task{Id: "h2", StartTime: now.Add(time.Duration(10) * time.Second), FinishTime: now.Add(time.Duration(80) * time.Second), Status: evergreen.TaskSucceeded}
   210  		So(h2.Insert(), ShouldBeNil)
   211  
   212  		// 20 -> shouldnt be added
   213  		neverEnding := task.Task{Id: "neverEnding", StartTime: now.Add(time.Duration(20) * time.Second), Status: evergreen.TaskSucceeded}
   214  		So(neverEnding.Insert(), ShouldBeNil)
   215  
   216  		// 5 -> 7
   217  		sameBucket := task.Task{Id: "sameBucket", StartTime: now.Add(time.Duration(5) * time.Second), FinishTime: now.Add(time.Duration(7) * time.Second), Status: evergreen.TaskFailed}
   218  		So(sameBucket.Insert(), ShouldBeNil)
   219  
   220  		// 5 -> 30
   221  		h4 := task.Task{Id: "h4", StartTime: now.Add(time.Duration(5) * time.Second), FinishTime: now.Add(time.Duration(30) * time.Second), Status: evergreen.TaskFailed}
   222  		So(h4.Insert(), ShouldBeNil)
   223  
   224  		endTime := now.Add(time.Duration(40) * time.Second)
   225  		frameBounds := FrameBounds{
   226  			StartTime:     now,
   227  			EndTime:       endTime,
   228  			NumberBuckets: 4,
   229  			BucketSize:    bucketSize,
   230  		}
   231  		Convey("for four buckets of 10 seconds", func() {
   232  			tasks, err := task.Find(task.ByTimeRun(now, endTime))
   233  			So(err, ShouldBeNil)
   234  			So(len(tasks), ShouldEqual, 4)
   235  
   236  			buckets, errors := CreateTaskBuckets(tasks, []task.Task{}, frameBounds)
   237  			So(errors, ShouldBeEmpty)
   238  			So(len(buckets), ShouldEqual, 4)
   239  			So(int(buckets[0].TotalTime.Seconds()), ShouldEqual, 17)
   240  			So(int(math.Ceil(buckets[1].TotalTime.Seconds())), ShouldEqual, 30)
   241  			So(int(math.Ceil(buckets[2].TotalTime.Seconds())), ShouldEqual, 20)
   242  		})
   243  
   244  	})
   245  }
   246  
   247  func TestAverageStatistics(t *testing.T) {
   248  	testutil.HandleTestingErr(db.ClearCollections(task.Collection), t, "couldnt reset host")
   249  	Convey("With a distro sampleDistro inserted", t, func() {
   250  		d := distro.Distro{
   251  			Id: "sampleDistro",
   252  		}
   253  		err := d.Insert()
   254  		So(err, ShouldBeNil)
   255  		distroId := d.Id
   256  		Convey("With a set of tasks that have different scheduled -> start times over a given time period", func() {
   257  			now := time.Now()
   258  			bucketSize := 10 * time.Second
   259  			numberBuckets := 3
   260  
   261  			task1 := task.Task{Id: "task1", ScheduledTime: now,
   262  				StartTime: now.Add(time.Duration(5) * time.Second), Status: evergreen.TaskStarted, DistroId: distroId}
   263  
   264  			So(task1.Insert(), ShouldBeNil)
   265  
   266  			task2 := task.Task{Id: "task2", ScheduledTime: now,
   267  				StartTime: now.Add(time.Duration(20) * time.Second), Status: evergreen.TaskStarted, DistroId: distroId}
   268  
   269  			So(task2.Insert(), ShouldBeNil)
   270  
   271  			task3 := task.Task{Id: "task3", ScheduledTime: now.Add(time.Duration(10) * time.Second),
   272  				StartTime: now.Add(time.Duration(20) * time.Second), Status: evergreen.TaskStarted, DistroId: distroId}
   273  			So(task3.Insert(), ShouldBeNil)
   274  
   275  			frameBounds := FrameBounds{
   276  				StartTime:     now,
   277  				EndTime:       now.Add(time.Duration(numberBuckets) * bucketSize),
   278  				NumberBuckets: numberBuckets,
   279  				BucketSize:    bucketSize,
   280  			}
   281  			avgBuckets, err := AverageStatistics(distroId, frameBounds)
   282  			So(err, ShouldBeNil)
   283  
   284  			So(avgBuckets[0].AverageTime, ShouldEqual, 5*time.Second)
   285  			So(avgBuckets[1].AverageTime, ShouldEqual, 0)
   286  			So(avgBuckets[2].AverageTime, ShouldEqual, 15*time.Second)
   287  
   288  			Convey("if the distro id given does not exist, it shoud return an empty list", func() {
   289  				_, err := AverageStatistics("noId", frameBounds)
   290  				So(err, ShouldNotBeNil)
   291  			})
   292  		})
   293  	})
   294  
   295  }
   296  
   297  func TestFindPredictedMakespan(t *testing.T) {
   298  	Convey("With a simple set of tasks that are dependent on each other and different times taken", t, func() {
   299  
   300  		a := task.Task{Id: "a", TimeTaken: time.Duration(5) * time.Second, DependsOn: []task.Dependency{}}
   301  		b := task.Task{Id: "b", TimeTaken: time.Duration(3) * time.Second, DependsOn: []task.Dependency{{"a", evergreen.TaskFailed}}}
   302  		c := task.Task{Id: "c", TimeTaken: time.Duration(4) * time.Second, DependsOn: []task.Dependency{{"a", evergreen.TaskFailed}}}
   303  		f := task.Task{Id: "f", TimeTaken: time.Duration(40) * time.Second, DependsOn: []task.Dependency{{"b", evergreen.TaskFailed}}}
   304  
   305  		d := task.Task{Id: "d", TimeTaken: time.Duration(10) * time.Second}
   306  		e := task.Task{Id: "e", TimeTaken: time.Duration(5) * time.Second, DependsOn: []task.Dependency{{"d", evergreen.TaskFailed}}}
   307  
   308  		Convey("with one tree of dependencies", func() {
   309  			allTasks := []task.Task{a, b, c}
   310  			depPath := FindPredictedMakespan(allTasks)
   311  			So(depPath.TotalTime, ShouldEqual, time.Duration(9)*time.Second)
   312  			So(len(depPath.Tasks), ShouldEqual, 2)
   313  		})
   314  		Convey("with one tree and one singular longer task", func() {
   315  			allTasks := []task.Task{a, b, c, d}
   316  			depPath := FindPredictedMakespan(allTasks)
   317  			So(depPath.TotalTime, ShouldEqual, time.Duration(10)*time.Second)
   318  			So(len(depPath.Tasks), ShouldEqual, 1)
   319  			So(depPath.Tasks[0], ShouldEqual, "d")
   320  		})
   321  		Convey("with two trees", func() {
   322  			allTasks := []task.Task{a, b, c, d, e}
   323  			depPath := FindPredictedMakespan(allTasks)
   324  			So(depPath.TotalTime, ShouldEqual, time.Duration(15)*time.Second)
   325  			So(len(depPath.Tasks), ShouldEqual, 2)
   326  
   327  		})
   328  		Convey("with a tree with varying times taken", func() {
   329  			allTasks := []task.Task{a, b, c, f}
   330  			depPath := FindPredictedMakespan(allTasks)
   331  			So(depPath.TotalTime, ShouldEqual, time.Duration(48)*time.Second)
   332  		})
   333  
   334  	})
   335  }
   336  
   337  func TestCalculateActualMakespan(t *testing.T) {
   338  	Convey("With a simple set of tasks that are dependent on each other and different times taken", t, func() {
   339  		now := time.Now()
   340  		a := task.Task{Id: "a", StartTime: now.Add(time.Duration(-10) * time.Second), FinishTime: now.Add(10 * time.Second)}
   341  		b := task.Task{Id: "b", StartTime: now.Add(time.Duration(-20) * time.Second), FinishTime: now.Add(20 * time.Second)}
   342  		c := task.Task{Id: "c", StartTime: now, FinishTime: now.Add(10 * time.Second)}
   343  		d := task.Task{Id: "d", StartTime: now.Add(time.Duration(10) * time.Second), FinishTime: now.Add(40 * time.Second)}
   344  
   345  		Convey("with one tree of dependencies", func() {
   346  			allTasks := []task.Task{a, b, c, d}
   347  			makespan := CalculateActualMakespan(allTasks)
   348  			So(makespan, ShouldEqual, time.Duration(60)*time.Second)
   349  		})
   350  
   351  	})
   352  }