Files
akvorado/inlet/flow/root.go
Vincent Bernat bde9cb3b64 chore: Netflow → NetFlow
But like for ClickHouse/clickhouse, we keep using netflow when not
capitalized.
2025-07-31 09:14:02 +02:00

132 lines
3.0 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
// Package flow handle incoming NetFlow/IPFIX/sflow flows.
package flow
import (
"errors"
"sync"
"gopkg.in/tomb.v2"
"akvorado/common/daemon"
"akvorado/common/helpers"
"akvorado/common/httpserver"
"akvorado/common/pb"
"akvorado/common/reporter"
"akvorado/inlet/flow/input"
"akvorado/inlet/kafka"
)
// Component represents the flow component.
type Component struct {
r *reporter.Reporter
d *Dependencies
t tomb.Tomb
config Configuration
inputs []input.Input
payloadPool sync.Pool
}
// Dependencies are the dependencies of the flow component.
type Dependencies struct {
Daemon daemon.Component
HTTP *httpserver.Component
Kafka *kafka.Component
}
// New creates a new flow component.
func New(r *reporter.Reporter, configuration Configuration, dependencies Dependencies) (*Component, error) {
if len(configuration.Inputs) == 0 {
return nil, errors.New("no input configured")
}
c := Component{
r: r,
d: &dependencies,
config: configuration,
inputs: make([]input.Input, len(configuration.Inputs)),
payloadPool: sync.Pool{
New: func() any {
minPayload := 2000
if helpers.Testing() {
// Ensure we test the extension case.
minPayload = 5
}
s := make([]byte, minPayload)
return &s
},
},
}
// Initialize inputs
for idx, input := range c.config.Inputs {
var err error
c.inputs[idx], err = input.Config.New(r, c.d.Daemon, c.Send(input))
if err != nil {
return nil, err
}
}
c.d.Daemon.Track(&c.t, "inlet/flow")
return &c, nil
}
// Send sends a raw flow to Kafka.
func (c *Component) Send(config InputConfiguration) input.SendFunc {
return func(exporter string, flow *pb.RawFlow) {
flow.TimestampSource = config.TimestampSource
flow.Decoder = config.Decoder
flow.UseSourceAddress = config.UseSrcAddrForExporterAddr
// Get a payload from the pool and extend it if needed. We use a pool of
// pointers to slice as we may have to extend the capacity of the slice.
// We keep the original pointer to avoid an extra allocation on the heap
// when returning the slice to the pool.
ptr := c.payloadPool.Get().(*[]byte)
bytes := *ptr
n := flow.SizeVT()
if cap(bytes) < n {
bytes = make([]byte, n+100)
*ptr = bytes
}
// Marshal to it, send it to Kafka and return it when done
if n, err := flow.MarshalToSizedBufferVT(bytes[:n]); err == nil {
c.d.Kafka.Send(exporter, bytes[:n], func() {
c.payloadPool.Put(ptr)
})
} else {
c.payloadPool.Put(ptr)
}
}
}
// Start starts the flow component.
func (c *Component) Start() error {
for _, input := range c.inputs {
err := input.Start()
stopper := input.Stop
if err != nil {
return err
}
c.t.Go(func() error {
<-c.t.Dying()
stopper()
return nil
})
}
return nil
}
// Stop stops the flow component
func (c *Component) Stop() error {
defer c.r.Info().Msg("flow component stopped")
c.r.Info().Msg("stopping flow component")
c.t.Kill(nil)
return c.t.Wait()
}