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 }