vitess.io/vitess@v0.16.2/go/vt/throttler/interval_history.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 package throttler 18 19 import ( 20 "fmt" 21 "time" 22 ) 23 24 // intervalHistory stores a value per interval over time. 25 // For example, thread_trottler.go stores the number of requests per 1 second 26 // interval in an intervalHistory instance. 27 // This data is used by the MaxReplicationLagModule to determine the historic 28 // average value between two arbitrary points in time e.g. to find out the 29 // average actual throttler rate between two replication lag measurements. 30 // In general, the history should reflect only a short period of time (on the 31 // order of minutes) and is therefore bounded. 32 type intervalHistory struct { 33 records []record 34 interval time.Duration 35 nextIntervalStart time.Time 36 } 37 38 func newIntervalHistory(capacity int64, interval time.Duration) *intervalHistory { 39 return &intervalHistory{ 40 records: make([]record, 0, capacity), 41 interval: interval, 42 } 43 } 44 45 // add 46 // It is up to the programmer to ensure that two add() calls do not cover the 47 // same interval. 48 func (h *intervalHistory) add(record record) { 49 if record.time.Before(h.nextIntervalStart) { 50 panic(fmt.Sprintf("BUG: cannot add record because it is already covered by a previous entry. record: %v next expected interval start: %v", record, h.nextIntervalStart)) 51 } 52 if !record.time.Truncate(h.interval).Equal(record.time) { 53 panic(fmt.Sprintf("BUG: cannot add record because it does not start at the beginning of the interval. record: %v", record)) 54 } 55 // TODO(mberlin): Bound the list. 56 h.records = append(h.records, record) 57 h.nextIntervalStart = record.time.Add(h.interval) 58 } 59 60 // average returns the average value across all observations which span 61 // the range [from, to). 62 // Partially included observations are accounted by their included fraction. 63 // Missing observations are assumed with the value zero. 64 func (h *intervalHistory) average(from, to time.Time) float64 { 65 // Search only entries whose time of observation is in [start, end). 66 // Example: [from, to) = [1.5s, 2.5s) => [start, end) = [1s, 2s) 67 start := from.Truncate(h.interval) 68 end := to.Truncate(h.interval) 69 70 sum := 0.0 71 count := 0.0 72 var nextIntervalStart time.Time 73 for i := len(h.records) - 1; i >= 0; i-- { 74 t := h.records[i].time 75 76 if t.After(end) { 77 continue 78 } 79 if t.Before(start) { 80 break 81 } 82 83 // Account for intervals which were not recorded. 84 if !nextIntervalStart.IsZero() { 85 uncoveredRange := nextIntervalStart.Sub(t) 86 count += float64(uncoveredRange / h.interval) 87 } 88 89 // If an interval is only partially included, count only that fraction. 90 durationAfterTo := t.Add(h.interval).Sub(to) 91 if durationAfterTo < 0 { 92 durationAfterTo = 0 93 } 94 durationBeforeFrom := from.Sub(t) 95 if durationBeforeFrom < 0 { 96 durationBeforeFrom = 0 97 } 98 weight := float64((h.interval - durationBeforeFrom - durationAfterTo).Nanoseconds()) / float64(h.interval.Nanoseconds()) 99 100 sum += weight * float64(h.records[i].value) 101 count += weight 102 nextIntervalStart = t.Add(-1 * h.interval) 103 } 104 105 return float64(sum) / count 106 }