vitess.io/vitess@v0.16.2/tools/statsd.go (about) 1 /* 2 * Copyright 2019 The Vitess Authors. 3 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 8 * http://www.apache.org/licenses/LICENSE-2.0 9 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // statsd is a simple server for hosting test.go remote stats. 18 package main 19 20 import ( 21 "encoding/json" 22 "log" 23 "net/http" 24 "os" 25 "strconv" 26 "sync" 27 "time" 28 ) 29 30 var mu sync.Mutex 31 32 const statsFileName = "stats.json" 33 34 func main() { 35 http.HandleFunc("/travis/stats", func(w http.ResponseWriter, r *http.Request) { 36 if r.Method == "POST" { 37 test := r.FormValue("test") 38 result := r.FormValue("result") 39 40 if test == "" || result == "" { 41 return 42 } 43 44 switch result { 45 case "pass": 46 duration := r.FormValue("duration") 47 if duration == "" { 48 return 49 } 50 dur, err := time.ParseDuration(duration) 51 if err != nil { 52 return 53 } 54 testPassed(test, dur) 55 case "fail": 56 testFailed(test) 57 case "flake": 58 try := r.FormValue("try") 59 if try == "" { 60 return 61 } 62 i, err := strconv.ParseInt(try, 10, 64) 63 if err != nil { 64 return 65 } 66 testFlaked(test, int(i)) 67 } 68 69 return 70 } 71 72 http.ServeFile(w, r, statsFileName) 73 }) 74 75 http.ListenAndServe(":15123", nil) 76 } 77 78 type Stats struct { 79 TestStats map[string]TestStats 80 } 81 82 type TestStats struct { 83 Pass, Fail, Flake int 84 PassTime time.Duration 85 } 86 87 func testPassed(name string, passTime time.Duration) { 88 updateTestStats(name, func(ts *TestStats) { 89 totalTime := int64(ts.PassTime)*int64(ts.Pass) + int64(passTime) 90 ts.Pass++ 91 ts.PassTime = time.Duration(totalTime / int64(ts.Pass)) 92 }) 93 } 94 95 func testFailed(name string) { 96 updateTestStats(name, func(ts *TestStats) { 97 ts.Fail++ 98 }) 99 } 100 101 func testFlaked(name string, try int) { 102 updateTestStats(name, func(ts *TestStats) { 103 ts.Flake += try - 1 104 }) 105 } 106 107 func updateTestStats(name string, update func(*TestStats)) { 108 var stats Stats 109 110 mu.Lock() 111 defer mu.Unlock() 112 113 data, err := os.ReadFile(statsFileName) 114 if err != nil { 115 log.Print("Can't read stats file, starting new one.") 116 } else { 117 if err := json.Unmarshal(data, &stats); err != nil { 118 log.Printf("Can't parse stats file: %v", err) 119 return 120 } 121 } 122 123 if stats.TestStats == nil { 124 stats.TestStats = make(map[string]TestStats) 125 } 126 ts := stats.TestStats[name] 127 update(&ts) 128 stats.TestStats[name] = ts 129 130 data, err = json.MarshalIndent(stats, "", "\t") 131 if err != nil { 132 log.Printf("Can't encode stats file: %v", err) 133 return 134 } 135 if err := os.WriteFile(statsFileName, data, 0644); err != nil { 136 log.Printf("Can't write stats file: %v", err) 137 } 138 }