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  }