vitess.io/vitess@v0.16.2/go/vt/throttler/result.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 "bytes" 21 "fmt" 22 "sync" 23 "text/template" 24 "time" 25 26 "vitess.io/vitess/go/vt/topo/topoproto" 27 ) 28 29 type rateChange string 30 31 const ( 32 increasedRate rateChange = "increased" 33 decreasedRate rateChange = "decreased" 34 unchangedRate rateChange = "not changed" 35 ) 36 37 type goodOrBadRate string 38 39 const ( 40 goodRate = "good" 41 badRate = "bad" 42 ignoredRate = "ignored" 43 ) 44 45 var resultStringTemplate = template.Must(template.New("result.String()").Parse( 46 `rate was: {{.RateChange}} from: {{.OldRate}} to: {{.NewRate}} 47 alias: {{.Alias}} lag: {{.LagRecordNow.Stats.ReplicationLagSeconds}}s 48 last change: {{.TimeSinceLastRateChange}} rate: {{.CurrentRate}} good/bad? {{.GoodOrBad}} skipped b/c: {{.MemorySkipReason}} good/bad: {{.HighestGood}}/{{.LowestBad}} 49 state (old/tested/new): {{.OldState}}/{{.TestedState}}/{{.NewState}} 50 lag before: {{.LagBefore}} ({{.AgeOfBeforeLag}} ago) rates (primary/replica): {{.PrimaryRate}}/{{.GuessedReplicationRate}} backlog (old/new): {{.GuessedReplicationBacklogOld}}/{{.GuessedReplicationBacklogNew}} 51 reason: {{.Reason}}`)) 52 53 // result is generated by the MaxReplicationLag module for each processed 54 // "replicationLagRecord". 55 // It captures the details and the decision of the processing. 56 type result struct { 57 Now time.Time 58 RateChange rateChange 59 lastRateChange time.Time 60 61 OldState state 62 TestedState state 63 NewState state 64 65 OldRate int64 66 NewRate int64 67 Reason string 68 69 CurrentRate int64 70 GoodOrBad goodOrBadRate 71 MemorySkipReason string 72 HighestGood int64 73 LowestBad int64 74 75 LagRecordNow replicationLagRecord 76 LagRecordBefore replicationLagRecord 77 PrimaryRate int64 78 GuessedReplicationRate int64 79 GuessedReplicationBacklogOld int 80 GuessedReplicationBacklogNew int 81 } 82 83 func (r result) String() string { 84 var b bytes.Buffer 85 if err := resultStringTemplate.Execute(&b, r); err != nil { 86 panic(fmt.Sprintf("failed to Execute() template: %v", err)) 87 } 88 return b.String() 89 } 90 91 func (r result) Alias() string { 92 return topoproto.TabletAliasString(r.LagRecordNow.Tablet.Alias) 93 } 94 95 func (r result) TimeSinceLastRateChange() string { 96 if r.lastRateChange.IsZero() { 97 return "n/a" 98 } 99 return fmt.Sprintf("%.1fs", r.Now.Sub(r.lastRateChange).Seconds()) 100 } 101 102 func (r result) LagBefore() string { 103 if r.LagRecordBefore.isZero() { 104 return "n/a" 105 } 106 return fmt.Sprintf("%ds", r.LagRecordBefore.Stats.ReplicationLagSeconds) 107 } 108 109 func (r result) AgeOfBeforeLag() string { 110 if r.LagRecordBefore.isZero() { 111 return "n/a" 112 } 113 return fmt.Sprintf("%.1fs", r.LagRecordNow.time.Sub(r.LagRecordBefore.time).Seconds()) 114 } 115 116 // resultRing implements a ring buffer for "result" instances. 117 type resultRing struct { 118 // mu guards the fields below. 119 mu sync.Mutex 120 // position holds the index of the *next* result in the ring. 121 position int 122 // wrapped becomes true when the ring buffer "wrapped" at least once and we 123 // started reusing entries. 124 wrapped bool 125 // values is the underlying ring buffer. 126 values []result 127 } 128 129 // newResultRing creates a new resultRing. 130 func newResultRing(capacity int) *resultRing { 131 return &resultRing{ 132 values: make([]result, capacity), 133 } 134 } 135 136 // add inserts a new result into the ring buffer. 137 func (rr *resultRing) add(r result) { 138 rr.mu.Lock() 139 defer rr.mu.Unlock() 140 141 rr.values[rr.position] = r 142 rr.position++ 143 if rr.position == len(rr.values) { 144 rr.position = 0 145 rr.wrapped = true 146 } 147 } 148 149 // latestValues returns all values of the buffer. Entries are sorted in reverse 150 // chronological order i.e. newer items come first. 151 func (rr *resultRing) latestValues() []result { 152 rr.mu.Lock() 153 defer rr.mu.Unlock() 154 155 start := rr.position - 1 156 if start == -1 { 157 // Current position is at the end. 158 start = len(rr.values) - 1 159 } 160 count := len(rr.values) 161 if !rr.wrapped { 162 count = rr.position 163 } 164 165 results := make([]result, count) 166 for i := 0; i < count; i++ { 167 pos := start - i 168 if pos < 0 { 169 // We started in the middle of the array and need to wrap around at the 170 // beginning of it. 171 pos += count 172 } 173 results[i] = rr.values[pos%count] 174 } 175 return results 176 }