go.etcd.io/etcd@v3.3.27+incompatible/pkg/report/timeseries.go (about) 1 // Copyright 2016 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package report 16 17 import ( 18 "bytes" 19 "encoding/csv" 20 "fmt" 21 "log" 22 "math" 23 "sort" 24 "sync" 25 "time" 26 ) 27 28 type DataPoint struct { 29 Timestamp int64 30 MinLatency time.Duration 31 AvgLatency time.Duration 32 MaxLatency time.Duration 33 ThroughPut int64 34 } 35 36 type TimeSeries []DataPoint 37 38 func (t TimeSeries) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 39 func (t TimeSeries) Len() int { return len(t) } 40 func (t TimeSeries) Less(i, j int) bool { return t[i].Timestamp < t[j].Timestamp } 41 42 type secondPoint struct { 43 minLatency time.Duration 44 maxLatency time.Duration 45 totalLatency time.Duration 46 count int64 47 } 48 49 type secondPoints struct { 50 mu sync.Mutex 51 tm map[int64]secondPoint 52 } 53 54 func newSecondPoints() *secondPoints { 55 return &secondPoints{tm: make(map[int64]secondPoint)} 56 } 57 58 func (sp *secondPoints) Add(ts time.Time, lat time.Duration) { 59 sp.mu.Lock() 60 defer sp.mu.Unlock() 61 62 tk := ts.Unix() 63 if v, ok := sp.tm[tk]; !ok { 64 sp.tm[tk] = secondPoint{minLatency: lat, maxLatency: lat, totalLatency: lat, count: 1} 65 } else { 66 if lat != time.Duration(0) { 67 v.minLatency = minDuration(v.minLatency, lat) 68 } 69 v.maxLatency = maxDuration(v.maxLatency, lat) 70 v.totalLatency += lat 71 v.count++ 72 sp.tm[tk] = v 73 } 74 } 75 76 func (sp *secondPoints) getTimeSeries() TimeSeries { 77 sp.mu.Lock() 78 defer sp.mu.Unlock() 79 80 var ( 81 minTs int64 = math.MaxInt64 82 maxTs int64 = -1 83 ) 84 for k := range sp.tm { 85 if minTs > k { 86 minTs = k 87 } 88 if maxTs < k { 89 maxTs = k 90 } 91 } 92 for ti := minTs; ti < maxTs; ti++ { 93 if _, ok := sp.tm[ti]; !ok { // fill-in empties 94 sp.tm[ti] = secondPoint{totalLatency: 0, count: 0} 95 } 96 } 97 98 var ( 99 tslice = make(TimeSeries, len(sp.tm)) 100 i int 101 ) 102 for k, v := range sp.tm { 103 var lat time.Duration 104 if v.count > 0 { 105 lat = time.Duration(v.totalLatency) / time.Duration(v.count) 106 } 107 tslice[i] = DataPoint{ 108 Timestamp: k, 109 MinLatency: v.minLatency, 110 AvgLatency: lat, 111 MaxLatency: v.maxLatency, 112 ThroughPut: v.count, 113 } 114 i++ 115 } 116 117 sort.Sort(tslice) 118 return tslice 119 } 120 121 func (t TimeSeries) String() string { 122 buf := new(bytes.Buffer) 123 wr := csv.NewWriter(buf) 124 if err := wr.Write([]string{"UNIX-SECOND", "MIN-LATENCY-MS", "AVG-LATENCY-MS", "MAX-LATENCY-MS", "AVG-THROUGHPUT"}); err != nil { 125 log.Fatal(err) 126 } 127 rows := [][]string{} 128 for i := range t { 129 row := []string{ 130 fmt.Sprintf("%d", t[i].Timestamp), 131 t[i].MinLatency.String(), 132 t[i].AvgLatency.String(), 133 t[i].MaxLatency.String(), 134 fmt.Sprintf("%d", t[i].ThroughPut), 135 } 136 rows = append(rows, row) 137 } 138 if err := wr.WriteAll(rows); err != nil { 139 log.Fatal(err) 140 } 141 wr.Flush() 142 if err := wr.Error(); err != nil { 143 log.Fatal(err) 144 } 145 return fmt.Sprintf("\nSample in one second (unix latency throughput):\n%s", buf.String()) 146 } 147 148 func minDuration(a, b time.Duration) time.Duration { 149 if a < b { 150 return a 151 } 152 return b 153 } 154 155 func maxDuration(a, b time.Duration) time.Duration { 156 if a > b { 157 return a 158 } 159 return b 160 }