github.phpd.cn/cilium/cilium@v1.6.12/pkg/trigger/trigger.go (about) 1 // Copyright 2018 Authors of Cilium 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 trigger 16 17 import ( 18 "fmt" 19 "time" 20 21 "github.com/cilium/cilium/pkg/lock" 22 ) 23 24 const ( 25 metricLabelReason = "reason" 26 ) 27 28 // MetricsObserver is the interface a metrics collector has to implement in 29 // order to collect trigger metrics 30 type MetricsObserver interface { 31 // PostRun is called after a trigger run with the call duration, the 32 // latency between 1st queue request and the call run and the number of 33 // queued events folded into the last run 34 PostRun(callDuration, latency time.Duration, folds int) 35 36 // QueueEvent is called when Trigger() is called to schedule a trigger 37 // run 38 QueueEvent(reason string) 39 } 40 41 // Parameters are the user specified parameters 42 type Parameters struct { 43 // MinInterval is the minimum required interval between invocations of 44 // TriggerFunc 45 MinInterval time.Duration 46 47 // TriggerFunc is the function to be called when Trigger() is called 48 // while respecting MinInterval and serialization 49 TriggerFunc func(reasons []string) 50 51 MetricsObserver MetricsObserver 52 53 // Name is the unique name of the trigger. It must be provided in a 54 // format compatible to be used as prometheus name string. 55 Name string 56 57 // sleepInterval controls the waiter sleep duration. This parameter is 58 // only exposed to tests 59 sleepInterval time.Duration 60 } 61 62 type reasonStack map[string]struct{} 63 64 func newReasonStack() reasonStack { 65 return map[string]struct{}{} 66 } 67 68 func (r reasonStack) add(reason string) { 69 r[reason] = struct{}{} 70 } 71 72 func (r reasonStack) slice() []string { 73 result := make([]string, len(r)) 74 i := 0 75 for reason := range r { 76 result[i] = reason 77 i++ 78 } 79 return result 80 } 81 82 // Trigger represents an active trigger logic. Use NewTrigger() to create a 83 // trigger 84 type Trigger struct { 85 // protect mutual access of 'trigger' between Trigger() and waiter() 86 mutex lock.Mutex 87 trigger bool 88 89 // params are the user specified parameters 90 params Parameters 91 92 // lastTrigger is the timestamp of the last invoked trigger 93 lastTrigger time.Time 94 95 // wakeupCan is used to wake up the background trigger routine 96 wakeupChan chan bool 97 98 // closeChan is used to stop the background trigger routine 99 closeChan chan struct{} 100 101 // numFolds is the current count of folds that happened into the 102 // currently scheduled trigger 103 numFolds int 104 105 // foldedReasons is the sum of all unique reasons folded together. 106 foldedReasons reasonStack 107 108 waitStart time.Time 109 } 110 111 // NewTrigger returns a new trigger based on the provided parameters 112 func NewTrigger(p Parameters) (*Trigger, error) { 113 if p.sleepInterval == 0 { 114 p.sleepInterval = time.Second 115 } 116 117 if p.TriggerFunc == nil { 118 return nil, fmt.Errorf("trigger function is nil") 119 } 120 121 t := &Trigger{ 122 params: p, 123 wakeupChan: make(chan bool, 1), 124 closeChan: make(chan struct{}, 1), 125 foldedReasons: newReasonStack(), 126 } 127 128 // Guarantee that initial trigger has no delay 129 if p.MinInterval > time.Duration(0) { 130 t.lastTrigger = time.Now().Add(-1 * p.MinInterval) 131 } 132 133 go t.waiter() 134 135 return t, nil 136 } 137 138 // needsDelay returns whether and how long of a delay is required to fullfil 139 // MinInterval 140 func (t *Trigger) needsDelay() (bool, time.Duration) { 141 if t.params.MinInterval == time.Duration(0) { 142 return false, 0 143 } 144 145 sleepTime := time.Since(t.lastTrigger.Add(t.params.MinInterval)) 146 return sleepTime < 0, sleepTime * -1 147 } 148 149 // Trigger triggers the call to TriggerFunc as specified in the parameters 150 // provided to NewTrigger(). It respects MinInterval and ensures that calls to 151 // TriggerFunc are serialized. This function is non-blocking and will return 152 // immediately before TriggerFunc is potentially triggered and has completed. 153 func (t *Trigger) TriggerWithReason(reason string) { 154 t.mutex.Lock() 155 t.trigger = true 156 if t.numFolds == 0 { 157 t.waitStart = time.Now() 158 } 159 t.numFolds++ 160 t.foldedReasons.add(reason) 161 t.mutex.Unlock() 162 163 if t.params.MetricsObserver != nil { 164 t.params.MetricsObserver.QueueEvent(reason) 165 } 166 167 select { 168 case t.wakeupChan <- true: 169 default: 170 } 171 } 172 173 // Trigger triggers the call to TriggerFunc as specified in the parameters 174 // provided to NewTrigger(). It respects MinInterval and ensures that calls to 175 // TriggerFunc are serialized. This function is non-blocking and will return 176 // immediately before TriggerFunc is potentially triggered and has completed. 177 func (t *Trigger) Trigger() { 178 t.TriggerWithReason("") 179 } 180 181 // Shutdown stops the trigger mechanism 182 func (t *Trigger) Shutdown() { 183 close(t.closeChan) 184 } 185 186 func (t *Trigger) waiter() { 187 for { 188 // keep critical section as small as possible 189 t.mutex.Lock() 190 triggerEnabled := t.trigger 191 t.trigger = false 192 t.mutex.Unlock() 193 194 // run the trigger function 195 if triggerEnabled { 196 if delayNeeded, delay := t.needsDelay(); delayNeeded { 197 time.Sleep(delay) 198 } 199 200 t.mutex.Lock() 201 t.lastTrigger = time.Now() 202 numFolds := t.numFolds 203 t.numFolds = 0 204 reasons := t.foldedReasons.slice() 205 t.foldedReasons = newReasonStack() 206 callLatency := time.Since(t.waitStart) 207 t.mutex.Unlock() 208 209 beforeTrigger := time.Now() 210 t.params.TriggerFunc(reasons) 211 212 if t.params.MetricsObserver != nil { 213 callDuration := time.Since(beforeTrigger) 214 t.params.MetricsObserver.PostRun(callDuration, callLatency, numFolds) 215 } 216 } 217 218 select { 219 case <-t.wakeupChan: 220 case <-time.After(t.params.sleepInterval): 221 222 case <-t.closeChan: 223 return 224 } 225 } 226 }