@@ -19,6 +19,7 @@ import (
19
19
"crypto/rand"
20
20
"crypto/sha1"
21
21
"crypto/tls"
22
+ "encoding/csv"
22
23
"errors"
23
24
"flag"
24
25
"fmt"
@@ -36,6 +37,7 @@ import (
36
37
"path"
37
38
"runtime"
38
39
"sort"
40
+ "strconv"
39
41
"strings"
40
42
"sync"
41
43
"sync/atomic"
@@ -2028,7 +2030,7 @@ func (st *buildStatus) newTestSet(names []string, benchmarks []*benchmarkItem) *
2028
2030
set .items = append (set .items , & testItem {
2029
2031
set : set ,
2030
2032
name : name ,
2031
- duration : testDuration (name ),
2033
+ duration : testDuration (st . builderRev . name , name ),
2032
2034
take : make (chan token , 1 ),
2033
2035
done : make (chan token ),
2034
2036
})
@@ -2039,15 +2041,15 @@ func (st *buildStatus) newTestSet(names []string, benchmarks []*benchmarkItem) *
2039
2041
set : set ,
2040
2042
name : name ,
2041
2043
bench : bench ,
2042
- duration : testDuration (name ),
2044
+ duration : testDuration (st . builderRev . name , name ),
2043
2045
take : make (chan token , 1 ),
2044
2046
done : make (chan token ),
2045
2047
})
2046
2048
}
2047
2049
return set
2048
2050
}
2049
2051
2050
- func partitionGoTests (tests []string ) (sets [][]string ) {
2052
+ func partitionGoTests (builderName string , tests []string ) (sets [][]string ) {
2051
2053
var srcTests []string
2052
2054
var cmdTests []string
2053
2055
for _ , name := range tests {
@@ -2073,19 +2075,99 @@ func partitionGoTests(tests []string) (sets [][]string) {
2073
2075
curDur = 0
2074
2076
}
2075
2077
}
2076
- for _ , name := range goTests {
2077
- d := testDuration (name ) - minGoTestSpeed // subtract 'go' tool overhead
2078
+ for _ , testName := range goTests {
2079
+ d := testDuration (builderName , testName )
2078
2080
if curDur + d > sizeThres {
2079
2081
flush () // no-op if empty
2080
2082
}
2081
- curSet = append (curSet , name )
2083
+ curSet = append (curSet , testName )
2082
2084
curDur += d
2083
2085
}
2084
2086
2085
2087
flush ()
2086
2088
return
2087
2089
}
2088
2090
2091
+ func secondsToDuration (sec float64 ) time.Duration {
2092
+ return time .Duration (float64 (sec ) * float64 (time .Second ))
2093
+ }
2094
+
2095
+ type testDurationMap map [string ]map [string ]time.Duration // builder name => test name => avg
2096
+
2097
+ var (
2098
+ testDurations atomic.Value // of testDurationMap
2099
+ testDurationsMu sync.Mutex // held while updating testDurations
2100
+ )
2101
+
2102
+ func getTestDurations () testDurationMap {
2103
+ if m , ok := testDurations .Load ().(testDurationMap ); ok {
2104
+ return m
2105
+ }
2106
+ testDurationsMu .Lock ()
2107
+ defer testDurationsMu .Unlock ()
2108
+ if m , ok := testDurations .Load ().(testDurationMap ); ok {
2109
+ return m
2110
+ }
2111
+ updateTestDurationsLocked ()
2112
+ return testDurations .Load ().(testDurationMap )
2113
+ }
2114
+
2115
+ func updateTestDurations () {
2116
+ testDurationsMu .Lock ()
2117
+ defer testDurationsMu .Unlock ()
2118
+ updateTestDurationsLocked ()
2119
+ }
2120
+
2121
+ func updateTestDurationsLocked () {
2122
+ defer time .AfterFunc (1 * time .Hour , updateTestDurations )
2123
+ m := loadTestDurations ()
2124
+ testDurations .Store (m )
2125
+ }
2126
+
2127
+ // The csv file on cloud storage looks like:
2128
+ // Builder,Event,MedianSeconds,count
2129
+ // linux-arm-arm5,run_test:runtime:cpu124,334.49922194,10
2130
+ // linux-arm,run_test:runtime:cpu124,284.609130993,26
2131
+ // linux-arm-arm5,run_test:go_test:cmd/compile/internal/gc,260.0241916,12
2132
+ // linux-arm,run_test:go_test:cmd/compile/internal/gc,224.425924681,26
2133
+ // solaris-amd64-smartosbuildlet,run_test:test:2_5,199.653975717,9
2134
+ // solaris-amd64-smartosbuildlet,run_test:test:1_5,169.89733442,9
2135
+ // solaris-amd64-smartosbuildlet,run_test:test:3_5,163.770453839,9
2136
+ // solaris-amd64-smartosbuildlet,run_test:test:0_5,158.250119402,9
2137
+ // openbsd-386-gce58,run_test:runtime:cpu124,146.494229388,12
2138
+ func loadTestDurations () (m testDurationMap ) {
2139
+ m = make (testDurationMap )
2140
+ r , err := storageClient .Bucket (buildEnv .BuildletBucket ).Object ("test-durations.csv" ).NewReader (context .Background ())
2141
+ if err != nil {
2142
+ log .Printf ("loading test durations object from GCS: %v" , err )
2143
+ return
2144
+ }
2145
+ defer r .Close ()
2146
+ recs , err := csv .NewReader (r ).ReadAll ()
2147
+ if err != nil {
2148
+ log .Printf ("reading test durations CSV: %v" , err )
2149
+ return
2150
+ }
2151
+ for _ , rec := range recs {
2152
+ if len (rec ) < 3 || rec [0 ] == "Builder" {
2153
+ continue
2154
+ }
2155
+ builder , testName , secondsStr := rec [0 ], rec [1 ], rec [2 ]
2156
+ secs , err := strconv .ParseFloat (secondsStr , 64 )
2157
+ if err != nil {
2158
+ log .Printf ("unexpected seconds value in test durations CSV: %v" , err )
2159
+ continue
2160
+ }
2161
+ mm := m [builder ]
2162
+ if mm == nil {
2163
+ mm = make (map [string ]time.Duration )
2164
+ m [builder ] = mm
2165
+ }
2166
+ mm [testName ] = secondsToDuration (secs )
2167
+ }
2168
+ return
2169
+ }
2170
+
2089
2171
var minGoTestSpeed = (func () time.Duration {
2090
2172
var min Seconds
2091
2173
for name , secs := range fixedTestDuration {
@@ -2325,11 +2407,18 @@ var fixedTestDuration = map[string]Seconds{
2325
2407
2326
2408
// testDuration predicts how long the dist test 'name' will take 'name' will take.
2327
2409
// It's only a scheduling guess.
2328
- func testDuration (name string ) time.Duration {
2329
- if secs , ok := fixedTestDuration [name ]; ok {
2410
+ func testDuration (builderName , testName string ) time.Duration {
2411
+ if false { // disabled for now. never tested. TODO: test, enable.
2412
+ durs := getTestDurations ()
2413
+ bdur := durs [builderName ]
2414
+ if d , ok := bdur [testName ]; ok {
2415
+ return d
2416
+ }
2417
+ }
2418
+ if secs , ok := fixedTestDuration [testName ]; ok {
2330
2419
return secs .Duration ()
2331
2420
}
2332
- if strings .HasPrefix (name , "bench:" ) {
2421
+ if strings .HasPrefix (testName , "bench:" ) {
2333
2422
// Assume benchmarks are roughly 20 seconds per run.
2334
2423
return 2 * benchRuns * 20 * time .Second
2335
2424
}
@@ -2845,7 +2934,7 @@ func (s *testSet) initInOrder() {
2845
2934
2846
2935
// First do the go_test:* ones. partitionGoTests
2847
2936
// only returns those, which are the ones we merge together.
2848
- stdSets := partitionGoTests (names )
2937
+ stdSets := partitionGoTests (s . st . builderRev . name , names )
2849
2938
for _ , set := range stdSets {
2850
2939
tis := make ([]* testItem , len (set ))
2851
2940
for i , name := range set {
0 commit comments