github.com/XiaoMi/Gaea@v1.2.5/stats/rates.go (about) 1 /* 2 Copyright 2017 Google Inc. 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 package stats 18 19 import ( 20 "encoding/json" 21 "math" 22 "sync" 23 "time" 24 ) 25 26 var timeNow = time.Now 27 28 // CountTracker defines the interface that needs to 29 // be supported by a variable for being tracked by 30 // Rates. 31 type CountTracker interface { 32 // Counts returns a map which maps each category to a count. 33 // Subsequent calls must return a monotonously increasing count for the same 34 // category. 35 // Optionally, an implementation may include the "All" category which has 36 // the total count across all categories (e.g. timing.go does this). 37 Counts() map[string]int64 38 } 39 40 // wrappedCountTracker implements the CountTracker interface. 41 // It is used in multidimensional.go to publish specific, one-dimensional 42 // counters. 43 type wrappedCountTracker struct { 44 f func() map[string]int64 45 } 46 47 func (t wrappedCountTracker) Counts() map[string]int64 { return t.f() } 48 49 // Rates is capable of reporting the rate (typically QPS) 50 // for any variable that satisfies the CountTracker interface. 51 type Rates struct { 52 // mu guards all fields. 53 mu sync.Mutex 54 timeStamps *RingInt64 55 counts map[string]*RingInt64 56 countTracker CountTracker 57 samples int 58 interval time.Duration 59 // previousTotalCount is the total number of counts (across all categories) 60 // seen in the last sampling interval. 61 // It's used to calculate the latest total rate. 62 previousTotalCount int64 63 // timestampLastSampling is the time the periodic sampling was run last. 64 timestampLastSampling time.Time 65 // totalRate is the rate of total counts per second seen in the latest 66 // sampling interval e.g. 100 queries / 5 seconds sampling interval = 20 QPS. 67 totalRate float64 68 } 69 70 // NewRates reports rolling rate information for countTracker. samples specifies 71 // the number of samples to report, and interval specifies the time interval 72 // between samples. The minimum interval is 1 second. 73 // If passing the special value of -1s as interval, we don't snapshot. 74 // (use this for tests). 75 func NewRates(name string, countTracker CountTracker, samples int, interval time.Duration) *Rates { 76 if interval < 1*time.Second && interval != -1*time.Second { 77 panic("interval too small") 78 } 79 rt := &Rates{ 80 timeStamps: NewRingInt64(samples + 1), 81 counts: make(map[string]*RingInt64), 82 countTracker: countTracker, 83 samples: samples + 1, 84 interval: interval, 85 timestampLastSampling: timeNow(), 86 } 87 if name != "" { 88 publish(name, rt) 89 } 90 if interval > 0 { 91 go rt.track() 92 } 93 return rt 94 } 95 96 func (rt *Rates) track() { 97 for { 98 rt.snapshot() 99 <-time.After(rt.interval) 100 } 101 } 102 103 func (rt *Rates) snapshot() { 104 rt.mu.Lock() 105 defer rt.mu.Unlock() 106 107 now := timeNow() 108 rt.timeStamps.Add(now.UnixNano()) 109 110 // Record current count for each category. 111 var totalCount int64 112 for k, v := range rt.countTracker.Counts() { 113 if k != "All" { 114 // Include call categories except "All" (which is returned by the 115 // "Timer.Counts()" implementation) to avoid double counting. 116 totalCount += v 117 } 118 if values, ok := rt.counts[k]; ok { 119 values.Add(v) 120 } else { 121 rt.counts[k] = NewRingInt64(rt.samples) 122 rt.counts[k].Add(0) 123 rt.counts[k].Add(v) 124 } 125 } 126 127 // Calculate current total rate. 128 // NOTE: We assume that every category with a non-zero value, which was 129 // tracked in "rt.previousTotalCount" in a previous sampling interval, is 130 // tracked in the current sampling interval in "totalCount" as well. 131 // (I.e. categories and their count must not "disappear" in 132 // "rt.countTracker.Counts()".) 133 durationSeconds := now.Sub(rt.timestampLastSampling).Seconds() 134 rate := float64(totalCount-rt.previousTotalCount) / durationSeconds 135 // Round rate with a precision of 0.1. 136 rt.totalRate = math.Floor(rate*10+0.5) / 10 137 rt.previousTotalCount = totalCount 138 rt.timestampLastSampling = now 139 } 140 141 // Get returns for each category (string) its latest rates (up to X values 142 // where X is the configured number of samples of the Rates struct). 143 // Rates are ordered from least recent (index 0) to most recent (end of slice). 144 func (rt *Rates) Get() (rateMap map[string][]float64) { 145 rt.mu.Lock() 146 defer rt.mu.Unlock() 147 148 rateMap = make(map[string][]float64) 149 timeStamps := rt.timeStamps.Values() 150 if len(timeStamps) <= 1 { 151 return 152 } 153 for k, v := range rt.counts { 154 rateMap[k] = make([]float64, len(timeStamps)-1) 155 values := v.Values() 156 valueIndex := len(values) - 1 157 for i := len(timeStamps) - 1; i > 0; i-- { 158 if valueIndex <= 0 { 159 rateMap[k][i-1] = 0 160 continue 161 } 162 elapsed := float64((timeStamps[i] - timeStamps[i-1]) / 1e9) 163 rateMap[k][i-1] = float64(values[valueIndex]-values[valueIndex-1]) / elapsed 164 valueIndex-- 165 } 166 } 167 return 168 } 169 170 // GetCount get direct count without devided by duration 171 func (rt *Rates) GetCount() (rateMap map[string][]int64) { 172 rt.mu.Lock() 173 defer rt.mu.Unlock() 174 175 rateMap = make(map[string][]int64) 176 timeStamps := rt.timeStamps.Values() 177 if len(timeStamps) <= 1 { 178 return 179 } 180 for k, v := range rt.counts { 181 rateMap[k] = make([]int64, len(timeStamps)-1) 182 values := v.Values() 183 valueIndex := len(values) - 1 184 for i := len(timeStamps) - 1; i > 0; i-- { 185 if valueIndex <= 0 { 186 rateMap[k][i-1] = 0 187 continue 188 } 189 rateMap[k][i-1] = values[valueIndex] - values[valueIndex-1] 190 valueIndex-- 191 } 192 } 193 return 194 } 195 196 // TotalRate returns the current total rate (counted across categories). 197 func (rt *Rates) TotalRate() float64 { 198 rt.mu.Lock() 199 defer rt.mu.Unlock() 200 201 return rt.totalRate 202 } 203 204 func (rt *Rates) String() string { 205 data, err := json.Marshal(rt.Get()) 206 if err != nil { 207 data, _ = json.Marshal(err.Error()) 208 } 209 return string(data) 210 }