github.com/cilium/cilium@v1.16.2/pkg/hubble/observer/local_observer.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package observer 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "strings" 12 "sync/atomic" 13 14 "github.com/sirupsen/logrus" 15 "google.golang.org/grpc/codes" 16 "google.golang.org/grpc/status" 17 "google.golang.org/protobuf/types/known/timestamppb" 18 19 flowpb "github.com/cilium/cilium/api/v1/flow" 20 observerpb "github.com/cilium/cilium/api/v1/observer" 21 v1 "github.com/cilium/cilium/pkg/hubble/api/v1" 22 "github.com/cilium/cilium/pkg/hubble/build" 23 "github.com/cilium/cilium/pkg/hubble/container" 24 "github.com/cilium/cilium/pkg/hubble/filters" 25 "github.com/cilium/cilium/pkg/hubble/observer/observeroption" 26 observerTypes "github.com/cilium/cilium/pkg/hubble/observer/types" 27 "github.com/cilium/cilium/pkg/hubble/parser" 28 parserErrors "github.com/cilium/cilium/pkg/hubble/parser/errors" 29 "github.com/cilium/cilium/pkg/hubble/parser/fieldmask" 30 nodeTypes "github.com/cilium/cilium/pkg/node/types" 31 "github.com/cilium/cilium/pkg/time" 32 ) 33 34 // DefaultOptions to include in the server. Other packages may extend this 35 // in their init() function. 36 var DefaultOptions []observeroption.Option 37 38 // LocalObserverServer is an implementation of the server.Observer interface 39 // that's meant to be run embedded inside the Cilium process. It ignores all 40 // the state change events since the state is available locally. 41 type LocalObserverServer struct { 42 // ring buffer that contains the references of all flows 43 ring *container.Ring 44 45 // events is the channel used by the writer(s) to send the flow data 46 // into the observer server. 47 events chan *observerTypes.MonitorEvent 48 49 // stopped is mostly used in unit tests to signalize when the events 50 // channel is empty, once it's closed. 51 stopped chan struct{} 52 53 log logrus.FieldLogger 54 55 // payloadParser decodes flowpb.Payload into flowpb.Flow 56 payloadParser parser.Decoder 57 58 opts observeroption.Options 59 60 // startTime is the time when this instance was started 61 startTime time.Time 62 63 // numObservedFlows counts how many flows have been observed 64 numObservedFlows atomic.Uint64 65 66 namespaceManager NamespaceManager 67 } 68 69 // NewLocalServer returns a new local observer server. 70 func NewLocalServer( 71 payloadParser parser.Decoder, 72 namespaceManager NamespaceManager, 73 logger logrus.FieldLogger, 74 options ...observeroption.Option, 75 ) (*LocalObserverServer, error) { 76 opts := observeroption.Default // start with defaults 77 options = append(options, DefaultOptions...) 78 for _, opt := range options { 79 if err := opt(&opts); err != nil { 80 return nil, fmt.Errorf("failed to apply option: %w", err) 81 } 82 } 83 84 logger.WithFields(logrus.Fields{ 85 "maxFlows": opts.MaxFlows, 86 "eventQueueSize": opts.MonitorBuffer, 87 }).Info("Configuring Hubble server") 88 89 s := &LocalObserverServer{ 90 log: logger, 91 ring: container.NewRing(opts.MaxFlows), 92 events: make(chan *observerTypes.MonitorEvent, opts.MonitorBuffer), 93 stopped: make(chan struct{}), 94 payloadParser: payloadParser, 95 startTime: time.Now(), 96 namespaceManager: namespaceManager, 97 opts: opts, 98 } 99 100 for _, f := range s.opts.OnServerInit { 101 err := f.OnServerInit(s) 102 if err != nil { 103 s.log.WithError(err).Error("failed in OnServerInit") 104 return nil, err 105 } 106 } 107 108 return s, nil 109 } 110 111 // Start implements GRPCServer.Start. 112 func (s *LocalObserverServer) Start() { 113 // We use a cancellation context here so that any goroutines spawned in the 114 // OnMonitorEvent/OnDecodedFlow/OnDecodedEvent hooks have a signal for cancellation. 115 // When Start() returns, the deferred cancel() will run and we expect hooks 116 // to stop any goroutines that may have spawned by listening to the 117 // ctx.Done() channel for the stop signal. 118 ctx, cancel := context.WithCancel(context.Background()) 119 defer cancel() 120 121 nextEvent: 122 for monitorEvent := range s.GetEventsChannel() { 123 for _, f := range s.opts.OnMonitorEvent { 124 stop, err := f.OnMonitorEvent(ctx, monitorEvent) 125 if err != nil { 126 s.log.WithError(err).WithField("event", monitorEvent).Info("failed in OnMonitorEvent") 127 } 128 if stop { 129 continue nextEvent 130 } 131 } 132 133 ev, err := s.payloadParser.Decode(monitorEvent) 134 if err != nil { 135 switch { 136 case 137 // silently ignore unknown or skipped events 138 errors.Is(err, parserErrors.ErrUnknownEventType), 139 errors.Is(err, parserErrors.ErrEventSkipped), 140 // silently ignore perf ring buffer events with unknown types, 141 // since they are not intended for us (e.g. MessageTypeRecCapture) 142 parserErrors.IsErrInvalidType(err): 143 default: 144 s.log.WithError(err).WithField("event", monitorEvent).Debug("failed to decode payload") 145 } 146 continue 147 } 148 149 if flow, ok := ev.Event.(*flowpb.Flow); ok { 150 // track namespaces seen. 151 s.trackNamespaces(flow) 152 for _, f := range s.opts.OnDecodedFlow { 153 stop, err := f.OnDecodedFlow(ctx, flow) 154 if err != nil { 155 s.log.WithError(err).WithField("event", monitorEvent).Info("failed in OnDecodedFlow") 156 } 157 if stop { 158 continue nextEvent 159 } 160 } 161 162 s.numObservedFlows.Add(1) 163 } 164 165 for _, f := range s.opts.OnDecodedEvent { 166 stop, err := f.OnDecodedEvent(ctx, ev) 167 if err != nil { 168 s.log.WithError(err).WithField("event", ev).Info("failed in OnDecodedEvent") 169 } 170 if stop { 171 continue nextEvent 172 } 173 } 174 s.GetRingBuffer().Write(ev) 175 } 176 close(s.GetStopped()) 177 } 178 179 // GetEventsChannel returns the event channel to receive flowpb.Payload events. 180 func (s *LocalObserverServer) GetEventsChannel() chan *observerTypes.MonitorEvent { 181 return s.events 182 } 183 184 // GetRingBuffer implements GRPCServer.GetRingBuffer. 185 func (s *LocalObserverServer) GetRingBuffer() *container.Ring { 186 return s.ring 187 } 188 189 // GetLogger implements GRPCServer.GetLogger. 190 func (s *LocalObserverServer) GetLogger() logrus.FieldLogger { 191 return s.log 192 } 193 194 // GetStopped implements GRPCServer.GetStopped. 195 func (s *LocalObserverServer) GetStopped() chan struct{} { 196 return s.stopped 197 } 198 199 // GetPayloadParser implements GRPCServer.GetPayloadParser. 200 func (s *LocalObserverServer) GetPayloadParser() parser.Decoder { 201 return s.payloadParser 202 } 203 204 // GetOptions implements serveroptions.Server.GetOptions. 205 func (s *LocalObserverServer) GetOptions() observeroption.Options { 206 return s.opts 207 } 208 209 // ServerStatus should have a comment, apparently. It returns the server status. 210 func (s *LocalObserverServer) ServerStatus( 211 ctx context.Context, req *observerpb.ServerStatusRequest, 212 ) (*observerpb.ServerStatusResponse, error) { 213 214 rate, err := getFlowRate(s.GetRingBuffer(), time.Now()) 215 if err != nil { 216 s.log.WithError(err).Warn("Failed to get flow rate") 217 } 218 219 return &observerpb.ServerStatusResponse{ 220 Version: build.ServerVersion.String(), 221 MaxFlows: s.GetRingBuffer().Cap(), 222 NumFlows: s.GetRingBuffer().Len(), 223 SeenFlows: s.numObservedFlows.Load(), 224 UptimeNs: uint64(time.Since(s.startTime).Nanoseconds()), 225 FlowsRate: rate, 226 }, nil 227 } 228 229 // GetNodes implements observerpb.ObserverClient.GetNodes. 230 func (s *LocalObserverServer) GetNodes(ctx context.Context, req *observerpb.GetNodesRequest) (*observerpb.GetNodesResponse, error) { 231 return nil, status.Errorf(codes.Unimplemented, "GetNodes not implemented") 232 } 233 234 // GetNamespaces implements observerpb.ObserverClient.GetNamespaces. 235 func (s *LocalObserverServer) GetNamespaces(ctx context.Context, req *observerpb.GetNamespacesRequest) (*observerpb.GetNamespacesResponse, error) { 236 return &observerpb.GetNamespacesResponse{Namespaces: s.namespaceManager.GetNamespaces()}, nil 237 } 238 239 // GetFlows implements the proto method for client requests. 240 func (s *LocalObserverServer) GetFlows( 241 req *observerpb.GetFlowsRequest, 242 server observerpb.Observer_GetFlowsServer, 243 ) (err error) { 244 if err := validateRequest(req); err != nil { 245 return err 246 } 247 // This context is used for goroutines spawned specifically to serve this 248 // request, meaning it must be cancelled once the request is done and this 249 // function returns. 250 ctx, cancel := context.WithCancel(server.Context()) 251 defer cancel() 252 253 for _, f := range s.opts.OnGetFlows { 254 ctx, err = f.OnGetFlows(ctx, req) 255 if err != nil { 256 return err 257 } 258 } 259 260 log := s.GetLogger() 261 filterList := append(filters.DefaultFilters(log), s.opts.OnBuildFilter...) 262 whitelist, err := filters.BuildFilterList(ctx, req.Whitelist, filterList) 263 if err != nil { 264 return err 265 } 266 blacklist, err := filters.BuildFilterList(ctx, req.Blacklist, filterList) 267 if err != nil { 268 return err 269 } 270 271 start := time.Now() 272 ring := s.GetRingBuffer() 273 274 i := uint64(0) 275 defer func() { 276 log.WithFields(logrus.Fields{ 277 "number_of_flows": i, 278 "buffer_size": ring.Cap(), 279 "whitelist": logFilters(req.Whitelist), 280 "blacklist": logFilters(req.Blacklist), 281 "took": time.Since(start), 282 }).Debug("GetFlows finished") 283 }() 284 285 ringReader, err := newRingReader(ring, req, whitelist, blacklist) 286 if err != nil { 287 if errors.Is(err, io.EOF) { 288 return nil 289 } 290 return err 291 } 292 293 eventsReader, err := newEventsReader(ringReader, req, log, whitelist, blacklist) 294 if err != nil { 295 return err 296 } 297 298 fm := req.GetFieldMask() 299 if len(fm.GetPaths()) == 0 { 300 // TODO: Remove req.Experimental.GetFieldMask after v1.17 301 fm = req.Experimental.GetFieldMask() 302 } 303 mask, err := fieldmask.New(fm) 304 if err != nil { 305 return err 306 } 307 308 var flow *flowpb.Flow 309 if mask.Active() { 310 flow = new(flowpb.Flow) 311 mask.Alloc(flow.ProtoReflect()) 312 } 313 314 nextEvent: 315 for ; ; i++ { 316 e, err := eventsReader.Next(ctx) 317 if err != nil { 318 if errors.Is(err, io.EOF) { 319 return nil 320 } 321 return err 322 } 323 324 var resp *observerpb.GetFlowsResponse 325 326 switch ev := e.Event.(type) { 327 case *flowpb.Flow: 328 eventsReader.eventCount++ 329 for _, f := range s.opts.OnFlowDelivery { 330 stop, err := f.OnFlowDelivery(ctx, ev) 331 switch { 332 case err != nil: 333 return err 334 case stop: 335 continue nextEvent 336 } 337 } 338 if mask.Active() { 339 // Copy only fields in the mask 340 mask.Copy(flow.ProtoReflect(), ev.ProtoReflect()) 341 ev = flow 342 } 343 resp = &observerpb.GetFlowsResponse{ 344 Time: ev.GetTime(), 345 NodeName: ev.GetNodeName(), 346 ResponseTypes: &observerpb.GetFlowsResponse_Flow{ 347 Flow: ev, 348 }, 349 } 350 case *flowpb.LostEvent: 351 // Don't increment eventsReader.eventCount as a LostEvent is an 352 // event type that is never explicitly requested by the user (e.g. 353 // when a query asks for 20 events, then lost events should not be 354 // accounted for as they are not events per se but an indication 355 // that some event was lost). 356 resp = &observerpb.GetFlowsResponse{ 357 Time: e.Timestamp, 358 NodeName: nodeTypes.GetAbsoluteNodeName(), 359 ResponseTypes: &observerpb.GetFlowsResponse_LostEvents{ 360 LostEvents: ev, 361 }, 362 } 363 } 364 365 if resp == nil { 366 continue 367 } 368 369 err = server.Send(resp) 370 if err != nil { 371 return err 372 } 373 } 374 } 375 376 // GetAgentEvents implements observerpb.ObserverClient.GetAgentEvents. 377 func (s *LocalObserverServer) GetAgentEvents( 378 req *observerpb.GetAgentEventsRequest, 379 server observerpb.Observer_GetAgentEventsServer, 380 ) (err error) { 381 if err := validateRequest(req); err != nil { 382 return err 383 } 384 385 ctx, cancel := context.WithCancel(server.Context()) 386 defer cancel() 387 388 var whitelist, blacklist filters.FilterFuncs 389 390 start := time.Now() 391 log := s.GetLogger() 392 ring := s.GetRingBuffer() 393 394 i := uint64(0) 395 defer func() { 396 log.WithFields(logrus.Fields{ 397 "number_of_agent_events": i, 398 "buffer_size": ring.Cap(), 399 "took": time.Since(start), 400 }).Debug("GetAgentEvents finished") 401 }() 402 403 ringReader, err := newRingReader(ring, req, whitelist, blacklist) 404 if err != nil { 405 if errors.Is(err, io.EOF) { 406 return nil 407 } 408 return err 409 } 410 411 eventsReader, err := newEventsReader(ringReader, req, log, whitelist, blacklist) 412 if err != nil { 413 return err 414 } 415 416 for ; ; i++ { 417 e, err := eventsReader.Next(ctx) 418 if err != nil { 419 if errors.Is(err, io.EOF) { 420 return nil 421 } 422 return err 423 } 424 425 switch ev := e.Event.(type) { 426 case *flowpb.AgentEvent: 427 eventsReader.eventCount++ 428 resp := &observerpb.GetAgentEventsResponse{ 429 Time: e.Timestamp, 430 NodeName: nodeTypes.GetAbsoluteNodeName(), 431 AgentEvent: ev, 432 } 433 err = server.Send(resp) 434 if err != nil { 435 return err 436 } 437 } 438 } 439 } 440 441 // GetDebugEvents implements observerpb.ObserverClient.GetDebugEvents. 442 func (s *LocalObserverServer) GetDebugEvents( 443 req *observerpb.GetDebugEventsRequest, 444 server observerpb.Observer_GetDebugEventsServer, 445 ) (err error) { 446 if err := validateRequest(req); err != nil { 447 return err 448 } 449 450 ctx, cancel := context.WithCancel(server.Context()) 451 defer cancel() 452 453 var whitelist, blacklist filters.FilterFuncs 454 455 start := time.Now() 456 log := s.GetLogger() 457 ring := s.GetRingBuffer() 458 459 i := uint64(0) 460 defer func() { 461 log.WithFields(logrus.Fields{ 462 "number_of_debug_events": i, 463 "buffer_size": ring.Cap(), 464 "took": time.Since(start), 465 }).Debug("GetDebugEvents finished") 466 }() 467 468 ringReader, err := newRingReader(ring, req, whitelist, blacklist) 469 if err != nil { 470 if errors.Is(err, io.EOF) { 471 return nil 472 } 473 return err 474 } 475 476 eventsReader, err := newEventsReader(ringReader, req, log, whitelist, blacklist) 477 if err != nil { 478 return err 479 } 480 481 for ; ; i++ { 482 e, err := eventsReader.Next(ctx) 483 if err != nil { 484 if errors.Is(err, io.EOF) { 485 return nil 486 } 487 return err 488 } 489 490 switch ev := e.Event.(type) { 491 case *flowpb.DebugEvent: 492 eventsReader.eventCount++ 493 resp := &observerpb.GetDebugEventsResponse{ 494 Time: e.Timestamp, 495 NodeName: nodeTypes.GetAbsoluteNodeName(), 496 DebugEvent: ev, 497 } 498 err = server.Send(resp) 499 if err != nil { 500 return err 501 } 502 } 503 } 504 } 505 506 func logFilters(filters []*flowpb.FlowFilter) string { 507 s := make([]string, 0, len(filters)) 508 for _, f := range filters { 509 s = append(s, f.String()) 510 } 511 return "{" + strings.Join(s, ",") + "}" 512 } 513 514 // genericRequest allows to abstract away generic request information for 515 // GetFlowsRequest, GetAgentEventsRequest and GetDebugEventsRequest. 516 type genericRequest interface { 517 GetNumber() uint64 518 GetFollow() bool 519 GetSince() *timestamppb.Timestamp 520 GetUntil() *timestamppb.Timestamp 521 GetFirst() bool 522 } 523 524 var ( 525 _ genericRequest = (*observerpb.GetFlowsRequest)(nil) 526 _ genericRequest = (*observerpb.GetAgentEventsRequest)(nil) 527 _ genericRequest = (*observerpb.GetDebugEventsRequest)(nil) 528 ) 529 530 // eventsReader reads flows using a RingReader. It applies the GetFlows request 531 // criteria (blacklist, whitelist, follow, ...) before returning events. 532 type eventsReader struct { 533 ringReader *container.RingReader 534 whitelist, blacklist filters.FilterFuncs 535 maxEvents uint64 536 follow, timeRange bool 537 eventCount uint64 538 since, until *time.Time 539 } 540 541 // newEventsReader creates a new eventsReader that uses the given RingReader to 542 // read through the ring buffer. Only events that match the request criteria 543 // are returned. 544 func newEventsReader(r *container.RingReader, req genericRequest, log logrus.FieldLogger, whitelist, blacklist filters.FilterFuncs) (*eventsReader, error) { 545 log.WithFields(logrus.Fields{ 546 "req": req, 547 "whitelist": whitelist, 548 "blacklist": blacklist, 549 }).Debug("creating a new eventsReader") 550 551 since, until := req.GetSince(), req.GetUntil() 552 reader := &eventsReader{ 553 ringReader: r, 554 whitelist: whitelist, 555 blacklist: blacklist, 556 maxEvents: req.GetNumber(), 557 follow: req.GetFollow(), 558 timeRange: since != nil || until != nil, 559 } 560 561 if since != nil { 562 if err := since.CheckValid(); err != nil { 563 return nil, err 564 } 565 sinceTime := since.AsTime() 566 reader.since = &sinceTime 567 } 568 569 if until != nil { 570 if err := until.CheckValid(); err != nil { 571 return nil, err 572 } 573 untilTime := until.AsTime() 574 reader.until = &untilTime 575 } 576 577 return reader, nil 578 } 579 580 // Next returns the next event that matches the request criteria. 581 func (r *eventsReader) Next(ctx context.Context) (*v1.Event, error) { 582 for { 583 select { 584 case <-ctx.Done(): 585 return nil, ctx.Err() 586 default: 587 } 588 var e *v1.Event 589 var err error 590 if r.follow { 591 e = r.ringReader.NextFollow(ctx) 592 } else { 593 if r.maxEvents > 0 && (r.eventCount >= r.maxEvents) { 594 return nil, io.EOF 595 } 596 e, err = r.ringReader.Next() 597 if err != nil { 598 return nil, err 599 } 600 } 601 if e == nil { 602 return nil, io.EOF 603 } 604 605 // Treat LostEvent as a special case as callers will never explicitly 606 // request them. This means that no regular filter nor time range 607 // filter should be applied. 608 // Note: lost events don't respect the assumption that "ring buffer 609 // timestamps are supposed to be monotonic" as their timestamp 610 // corresponds to when a LostEvent was detected. 611 _, isLostEvent := e.Event.(*flowpb.LostEvent) 612 if !isLostEvent { 613 if r.timeRange { 614 if err := e.Timestamp.CheckValid(); err != nil { 615 return nil, err 616 } 617 ts := e.Timestamp.AsTime() 618 619 if r.until != nil && ts.After(*r.until) { 620 return nil, io.EOF 621 } 622 623 if r.since != nil && ts.Before(*r.since) { 624 continue 625 } 626 } 627 628 if !filters.Apply(r.whitelist, r.blacklist, e) { 629 continue 630 } 631 } 632 633 return e, nil 634 } 635 } 636 637 func (s *LocalObserverServer) trackNamespaces(flow *flowpb.Flow) { 638 // track namespaces seen. 639 if srcNs := flow.GetSource().GetNamespace(); srcNs != "" { 640 s.namespaceManager.AddNamespace(&observerpb.Namespace{ 641 Namespace: srcNs, 642 Cluster: nodeTypes.GetClusterName(), 643 }) 644 } 645 if dstNs := flow.GetDestination().GetNamespace(); dstNs != "" { 646 s.namespaceManager.AddNamespace(&observerpb.Namespace{ 647 Namespace: dstNs, 648 Cluster: nodeTypes.GetClusterName(), 649 }) 650 } 651 } 652 653 func validateRequest(req genericRequest) error { 654 if req.GetFirst() && req.GetFollow() { 655 return status.Errorf(codes.InvalidArgument, "first cannot be specified with follow") 656 } 657 return nil 658 } 659 660 // newRingReader creates a new RingReader that starts at the correct ring 661 // offset to match the flow request. 662 func newRingReader(ring *container.Ring, req genericRequest, whitelist, blacklist filters.FilterFuncs) (*container.RingReader, error) { 663 since := req.GetSince() 664 665 // since takes precedence over Number (--first and --last) 666 if req.GetFirst() && since == nil { 667 // Start from the beginning of the ring. 668 return container.NewRingReader(ring, ring.OldestWrite()), nil 669 } 670 671 if req.GetFollow() && req.GetNumber() == 0 && since == nil { 672 // no need to rewind 673 return container.NewRingReader(ring, ring.LastWriteParallel()), nil 674 } 675 676 var sinceTime time.Time 677 if since != nil { 678 if err := since.CheckValid(); err != nil { 679 return nil, err 680 } 681 sinceTime = since.AsTime() 682 } 683 684 idx := ring.LastWriteParallel() 685 reader := container.NewRingReader(ring, idx) 686 687 var eventCount uint64 688 // We need to find out what the right index is; that is the index with the 689 // oldest entry that is within time range boundaries (if any is defined) 690 // or until we find enough events. 691 // In order to avoid buffering events, we have to rewind first to find the 692 // correct index, then create a new reader that starts from there 693 for i := ring.Len(); i > 0; i, idx = i-1, idx-1 { 694 e, err := reader.Previous() 695 lost := e.GetLostEvent() 696 if lost != nil && lost.Source == flowpb.LostEventSource_HUBBLE_RING_BUFFER { 697 idx++ // we went backward 1 too far 698 break 699 } else if err != nil { 700 return nil, err 701 } 702 // Note: LostEvent type is ignored here and this is expected as lost 703 // events will never be explicitly requested by the caller 704 _, isLostEvent := e.Event.(*flowpb.LostEvent) 705 if isLostEvent || !filters.Apply(whitelist, blacklist, e) { 706 continue 707 } 708 eventCount++ 709 if since != nil { 710 if err := e.Timestamp.CheckValid(); err != nil { 711 return nil, err 712 } 713 ts := e.Timestamp.AsTime() 714 if ts.Before(sinceTime) { 715 idx++ // we went backward 1 too far 716 break 717 } 718 } else if eventCount == req.GetNumber() { 719 break // we went backward far enough 720 } 721 } 722 return container.NewRingReader(ring, idx), nil 723 } 724 725 func getFlowRate(ring *container.Ring, at time.Time) (float64, error) { 726 reader := container.NewRingReader(ring, ring.LastWriteParallel()) 727 count := 0 728 since := at.Add(-1 * time.Minute) 729 var lastSeenEvent *v1.Event 730 for { 731 e, err := reader.Previous() 732 lost := e.GetLostEvent() 733 if lost != nil && lost.Source == flowpb.LostEventSource_HUBBLE_RING_BUFFER { 734 // a lost event means we read the complete ring buffer 735 // if we read at least one flow, update `since` to calculate the rate over the available time range 736 if lastSeenEvent != nil { 737 since = lastSeenEvent.Timestamp.AsTime() 738 } 739 break 740 } else if errors.Is(err, io.EOF) { 741 // an EOF error means the ring buffer is empty, ignore error and continue 742 break 743 } else if err != nil { 744 // unexpected error 745 return 0, err 746 } 747 if _, isFlowEvent := e.Event.(*flowpb.Flow); !isFlowEvent { 748 // ignore non flow events 749 continue 750 } 751 if err := e.Timestamp.CheckValid(); err != nil { 752 return 0, err 753 } 754 ts := e.Timestamp.AsTime() 755 if ts.Before(since) { 756 // scanned the last minute, exit loop 757 break 758 } 759 lastSeenEvent = e 760 count++ 761 } 762 return float64(count) / at.Sub(since).Seconds(), nil 763 }