@@ -6,9 +6,12 @@ package observe
6
6
import (
7
7
"bufio"
8
8
"context"
9
+ "fmt"
9
10
"io"
11
+ "math"
10
12
11
13
observerpb "github.com/cilium/cilium/api/v1/observer"
14
+ "github.com/cilium/cilium/pkg/container"
12
15
v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
13
16
"github.com/cilium/cilium/pkg/hubble/filters"
14
17
"github.com/cilium/hubble/pkg/logger"
@@ -59,11 +62,18 @@ func (o *IOReaderObserver) ServerStatus(_ context.Context, _ *observerpb.ServerS
59
62
60
63
// ioReaderClient implements Observer_GetFlowsClient.
61
64
type ioReaderClient struct {
65
+ grpc.ClientStream
66
+
62
67
scanner * bufio.Scanner
63
68
request * observerpb.GetFlowsRequest
64
69
allow filters.FilterFuncs
65
70
deny filters.FilterFuncs
66
- grpc.ClientStream
71
+
72
+ // Used for --last
73
+ buffer * container.RingBuffer
74
+ resps []* observerpb.GetFlowsResponse
75
+ // Used for --first/--last
76
+ flowsReturned uint64
67
77
}
68
78
69
79
func newIOReaderClient (ctx context.Context , scanner * bufio.Scanner , request * observerpb.GetFlowsRequest ) (* ioReaderClient , error ) {
@@ -75,36 +85,111 @@ func newIOReaderClient(ctx context.Context, scanner *bufio.Scanner, request *obs
75
85
if err != nil {
76
86
return nil , err
77
87
}
88
+
89
+ var buf * container.RingBuffer
90
+ // last
91
+ if n := request .GetNumber (); ! request .GetFirst () && n != 0 && n != math .MaxUint64 {
92
+ if n > 1_000_000 {
93
+ return nil , fmt .Errorf ("--last must be <= 1_000_000, got %d" , n )
94
+ }
95
+ buf = container .NewRingBuffer (int (n ))
96
+ }
78
97
return & ioReaderClient {
79
98
scanner : scanner ,
80
99
request : request ,
81
100
allow : allow ,
82
101
deny : deny ,
102
+ buffer : buf ,
83
103
}, nil
84
104
}
85
105
86
106
func (c * ioReaderClient ) Recv () (* observerpb.GetFlowsResponse , error ) {
107
+ if c .returnedEnoughFlows () {
108
+ return nil , io .EOF
109
+ }
110
+
87
111
for c .scanner .Scan () {
88
- line := c .scanner .Text ()
89
- var res observerpb.GetFlowsResponse
90
- err := protojson .Unmarshal (c .scanner .Bytes (), & res )
91
- if err != nil {
92
- logger .Logger .WithError (err ).WithField ("line" , line ).Warn ("Failed to unmarshal json to flow" )
112
+ res := c .unmarshalNext ()
113
+ if res == nil {
93
114
continue
94
115
}
95
- if c .request .Since != nil && c .request .Since .AsTime ().After (res .Time .AsTime ()) {
96
- continue
97
- }
98
- if c .request .Until != nil && c .request .Until .AsTime ().Before (res .Time .AsTime ()) {
99
- continue
100
- }
101
- if ! filters .Apply (c .allow , c .deny , & v1.Event {Timestamp : res .Time , Event : res .GetFlow ()}) {
102
- continue
116
+
117
+ switch {
118
+ case c .isLast ():
119
+ // store flows in a FIFO buffer, effectively keeping the last N flows
120
+ // until we finish reading from the stream
121
+ c .buffer .Add (res )
122
+ case c .isFirst ():
123
+ // track number of flows returned, so we can exit once we've given back N flows
124
+ c .flowsReturned ++
125
+ return res , nil
126
+ default : // --all
127
+ return res , nil
103
128
}
104
- return & res , nil
105
129
}
130
+
106
131
if err := c .scanner .Err (); err != nil {
107
132
return nil , err
108
133
}
134
+
135
+ if res := c .popFromLastBuffer (); res != nil {
136
+ return res , nil
137
+ }
138
+
109
139
return nil , io .EOF
110
140
}
141
+
142
+ func (c * ioReaderClient ) isFirst () bool {
143
+ return c .request .GetFirst () && c .request .GetNumber () != 0 && c .request .GetNumber () != math .MaxUint64
144
+ }
145
+
146
+ func (c * ioReaderClient ) isLast () bool {
147
+ return c .buffer != nil && c .request .GetNumber () != math .MaxUint64
148
+ }
149
+
150
+ func (c * ioReaderClient ) returnedEnoughFlows () bool {
151
+ return c .request .GetNumber () > 0 && c .flowsReturned >= c .request .GetNumber ()
152
+ }
153
+
154
+ func (c * ioReaderClient ) popFromLastBuffer () * observerpb.GetFlowsResponse {
155
+ // Handle --last by iterating over our FIFO and returning one item each time.
156
+ if c .isLast () {
157
+ if len (c .resps ) == 0 {
158
+ // Iterate over the buffer and store them in a slice, because we cannot
159
+ // index into the ring buffer itself
160
+ // TODO: Add the ability to index into the ring buffer and we could avoid
161
+ // this copy.
162
+ c .buffer .Iterate (func (i interface {}) {
163
+ c .resps = append (c .resps , i .(* observerpb.GetFlowsResponse ))
164
+ })
165
+ }
166
+
167
+ // return the next element from the buffered results
168
+ if len (c .resps ) > int (c .flowsReturned ) {
169
+ resp := c .resps [c .flowsReturned ]
170
+ c .flowsReturned ++
171
+ return resp
172
+ }
173
+ }
174
+ return nil
175
+ }
176
+
177
+ func (c * ioReaderClient ) unmarshalNext () * observerpb.GetFlowsResponse {
178
+ var res observerpb.GetFlowsResponse
179
+ err := protojson .Unmarshal (c .scanner .Bytes (), & res )
180
+ if err != nil {
181
+ line := c .scanner .Text ()
182
+ logger .Logger .WithError (err ).WithField ("line" , line ).Warn ("Failed to unmarshal json to flow" )
183
+ return nil
184
+ }
185
+ if c .request .Since != nil && c .request .Since .AsTime ().After (res .Time .AsTime ()) {
186
+ return nil
187
+ }
188
+ if c .request .Until != nil && c .request .Until .AsTime ().Before (res .Time .AsTime ()) {
189
+ return nil
190
+ }
191
+ if ! filters .Apply (c .allow , c .deny , & v1.Event {Timestamp : res .Time , Event : res .GetFlow ()}) {
192
+ return nil
193
+ }
194
+ return & res
195
+ }
0 commit comments