github.com/cilium/cilium@v1.16.2/pkg/hubble/recorder/sink/sink.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package sink
     5  
     6  import (
     7  	"context"
     8  
     9  	"github.com/cilium/cilium/pkg/hubble/recorder/pcap"
    10  	"github.com/cilium/cilium/pkg/lock"
    11  	"github.com/cilium/cilium/pkg/time"
    12  )
    13  
    14  // sink wraps a pcap.RecordWriter by adding a queue and managing its statistics
    15  // regarding written and dropped packets and bytes.
    16  type sink struct {
    17  	// these channels are initialized in startSink and never reassigned, thus
    18  	// they can be accessed without locking mutex
    19  	queue    chan record
    20  	shutdown chan struct{}
    21  	done     chan struct{}
    22  	trigger  chan struct{}
    23  	// mutex protects writes to stats and lastError
    24  	mutex     lock.Mutex
    25  	stats     Statistics
    26  	lastError error
    27  }
    28  
    29  // startSink creates a queue and goroutine for the sink. The spawned go
    30  // routine will run until one of the following happens:
    31  //   - sink.stop is called
    32  //   - a p.StopCondition is reached
    33  //   - ctx is cancelled
    34  //   - an error occurred
    35  func startSink(ctx context.Context, p PcapSink, queueSize int) *sink {
    36  	s := &sink{
    37  		queue:     make(chan record, queueSize),
    38  		shutdown:  make(chan struct{}),
    39  		done:      make(chan struct{}),
    40  		trigger:   make(chan struct{}, 1),
    41  		mutex:     lock.Mutex{},
    42  		stats:     Statistics{},
    43  		lastError: nil,
    44  	}
    45  
    46  	go func() {
    47  		// this defer executes p.Writer.Close(), but also makes sure to set
    48  		// lastError and close the s.done channel when exiting.
    49  		var err error
    50  		defer func() {
    51  			closeErr := p.Writer.Close()
    52  
    53  			s.mutex.Lock()
    54  			if err == nil {
    55  				s.lastError = closeErr
    56  			} else {
    57  				s.lastError = err
    58  			}
    59  			s.mutex.Unlock()
    60  			close(s.done)
    61  		}()
    62  
    63  		stop := p.StopCondition
    64  		var stopAfter <-chan time.Time
    65  		if stop.DurationElapsed != 0 {
    66  			stopTimer := time.NewTimer(stop.DurationElapsed)
    67  			defer func() {
    68  				stopTimer.Stop()
    69  			}()
    70  			stopAfter = stopTimer.C
    71  		}
    72  
    73  		if err = p.Writer.WriteHeader(p.Header); err != nil {
    74  			return
    75  		}
    76  
    77  		for {
    78  			select {
    79  			// s.queue will be closed when the sink is unregistered
    80  			case rec := <-s.queue:
    81  				pcapRecord := pcap.Record{
    82  					Timestamp:      rec.timestamp,
    83  					CaptureLength:  rec.inclLen,
    84  					OriginalLength: rec.origLen,
    85  				}
    86  
    87  				if err = p.Writer.WriteRecord(pcapRecord, rec.data); err != nil {
    88  					return
    89  				}
    90  
    91  				stats := s.addToStatistics(Statistics{
    92  					PacketsWritten: 1,
    93  					BytesWritten:   uint64(rec.inclLen),
    94  				})
    95  				if (stop.PacketsCaptured > 0 && stats.PacketsWritten >= stop.PacketsCaptured) ||
    96  					(stop.BytesCaptured > 0 && stats.BytesWritten >= stop.BytesCaptured) {
    97  					return
    98  				}
    99  			case <-s.shutdown:
   100  				return
   101  			case <-stopAfter:
   102  				// duration of stop condition has been reached
   103  				return
   104  			case <-ctx.Done():
   105  				err = ctx.Err()
   106  				return
   107  			}
   108  		}
   109  	}()
   110  
   111  	return s
   112  }
   113  
   114  // stop requests the sink to stop recording
   115  func (s *sink) stop() {
   116  	close(s.shutdown)
   117  }
   118  
   119  // addToStatistics adds add to the current statistics and returns the resulting
   120  // value.
   121  func (s *sink) addToStatistics(add Statistics) (result Statistics) {
   122  	s.mutex.Lock()
   123  	s.stats.BytesWritten += add.BytesWritten
   124  	s.stats.PacketsWritten += add.PacketsWritten
   125  	s.stats.BytesLost += add.BytesLost
   126  	s.stats.PacketsLost += add.PacketsLost
   127  	result = s.stats
   128  	s.mutex.Unlock()
   129  
   130  	// non-blocking send
   131  	select {
   132  	case s.trigger <- struct{}{}:
   133  	default:
   134  	}
   135  
   136  	return result
   137  }
   138  
   139  // enqueue submits a new record to this sink. If the sink is not keeping up,
   140  // the record is dropped and the sink statistics are updated accordingly
   141  func (s *sink) enqueue(rec record) {
   142  	select {
   143  	case <-s.shutdown:
   144  		// early return if shutting down
   145  		return
   146  	default:
   147  	}
   148  
   149  	select {
   150  	case s.queue <- rec:
   151  		// successfully enqueued rec in sink
   152  		return
   153  	default:
   154  	}
   155  
   156  	// sink queue was full, update statistics
   157  	s.addToStatistics(Statistics{
   158  		PacketsLost: 1,
   159  		BytesLost:   uint64(rec.inclLen),
   160  	})
   161  }
   162  
   163  // copyStats creates a snapshot of the current statistics
   164  func (s *sink) copyStats() Statistics {
   165  	s.mutex.Lock()
   166  	stats := s.stats
   167  	s.mutex.Unlock()
   168  
   169  	return stats
   170  }
   171  
   172  // err returns the last error which occurred in the sink.
   173  // This will always return nil before sink.done has signalled.
   174  func (s *sink) err() error {
   175  	s.mutex.Lock()
   176  	err := s.lastError
   177  	s.mutex.Unlock()
   178  
   179  	return err
   180  }