Files
akvorado/inlet/flow/root.go
Vincent Bernat e352202631 inlet: make use of schema for inlet
This is a huge change to make the various subcomponents of the inlet use
the schema to generate the protobuf. For it to make sense, we also
modify the way we parse flows to directly serialize non-essential fields
to Protobuf.

The performance is mostly on par with the previous commit. We are a bit
less efficient because we don't have a fixed structure, but we avoid
loosing too much performance by not relying on reflection and keeping
the production of messages as code. We use less of Goflow2: raw flow
parsing is still done by Goflow2, but we don't use the producer part
anymore. This helps a bit with the performance as we parse less.
Overall, we are 20% than the previous commit and twice faster than the
1.6.4!

```
goos: linux
goarch: amd64
pkg: akvorado/inlet/flow
cpu: AMD Ryzen 5 5600X 6-Core Processor
BenchmarkDecodeEncodeNetflow
BenchmarkDecodeEncodeNetflow/with_encoding
BenchmarkDecodeEncodeNetflow/with_encoding-12             151484              7789 ns/op            8272 B/op        143 allocs/op
BenchmarkDecodeEncodeNetflow/without_encoding
BenchmarkDecodeEncodeNetflow/without_encoding-12          162550              7133 ns/op            8272 B/op        143 allocs/op
BenchmarkDecodeEncodeSflow
BenchmarkDecodeEncodeSflow/with_encoding
BenchmarkDecodeEncodeSflow/with_encoding-12                94844             13193 ns/op            9816 B/op        295 allocs/op
BenchmarkDecodeEncodeSflow/without_encoding
BenchmarkDecodeEncodeSflow/without_encoding-12             92569             12456 ns/op            9816 B/op        295 allocs/op
```

There was a tentative to parse sFlow packets with gopackets, but the
adhoc parser used here is more performant.
2023-01-17 20:53:00 +01:00

175 lines
4.0 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
// Package flow handle incoming flows (currently Netflow v9 and IPFIX).
package flow
import (
"errors"
"fmt"
netHTTP "net/http"
"net/netip"
"gopkg.in/tomb.v2"
"akvorado/common/daemon"
"akvorado/common/http"
"akvorado/common/reporter"
"akvorado/common/schema"
"akvorado/inlet/flow/decoder"
"akvorado/inlet/flow/input"
)
// Component represents the flow component.
type Component struct {
r *reporter.Reporter
d *Dependencies
t tomb.Tomb
config Configuration
metrics struct {
decoderStats *reporter.CounterVec
decoderErrors *reporter.CounterVec
decoderTime *reporter.SummaryVec
}
// Channel for sending flows out of the package.
outgoingFlows chan *schema.FlowMessage
// Per-exporter rate-limiters
limiters map[netip.Addr]*limiter
// Inputs
inputs []input.Input
}
// Dependencies are the dependencies of the flow component.
type Dependencies struct {
Daemon daemon.Component
HTTP *http.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,
outgoingFlows: make(chan *schema.FlowMessage),
limiters: make(map[netip.Addr]*limiter),
inputs: make([]input.Input, len(configuration.Inputs)),
}
// Initialize decoders (at most once each)
var alreadyInitialized = map[string]decoder.Decoder{}
decs := make([]decoder.Decoder, len(configuration.Inputs))
for idx, input := range c.config.Inputs {
dec, ok := alreadyInitialized[input.Decoder]
if ok {
decs[idx] = dec
continue
}
decoderfunc, ok := decoders[input.Decoder]
if !ok {
return nil, fmt.Errorf("unknown decoder %q", input.Decoder)
}
dec = decoderfunc(r)
alreadyInitialized[input.Decoder] = dec
decs[idx] = c.wrapDecoder(dec, input.UseSrcAddrForExporterAddr)
}
// Initialize inputs
for idx, input := range c.config.Inputs {
var err error
c.inputs[idx], err = input.Config.New(r, c.d.Daemon, decs[idx])
if err != nil {
return nil, err
}
}
// Metrics
c.metrics.decoderStats = c.r.CounterVec(
reporter.CounterOpts{
Name: "decoder_count",
Help: "Decoder processed count.",
},
[]string{"name"},
)
c.metrics.decoderErrors = c.r.CounterVec(
reporter.CounterOpts{
Name: "decoder_error_count",
Help: "Decoder processed error count.",
},
[]string{"name"},
)
c.metrics.decoderTime = c.r.SummaryVec(
reporter.SummaryOpts{
Name: "summary_decoding_time_seconds",
Help: "Decoding time summary.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"name"},
)
c.d.Daemon.Track(&c.t, "inlet/flow")
c.d.HTTP.AddHandler("/api/v0/inlet/flow/schema.proto",
netHTTP.HandlerFunc(func(w netHTTP.ResponseWriter, r *netHTTP.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(schema.Flows.ProtobufDefinition()))
}))
return &c, nil
}
// Flows returns a channel to receive flows.
func (c *Component) Flows() <-chan *schema.FlowMessage {
return c.outgoingFlows
}
// Start starts the flow component.
func (c *Component) Start() error {
for _, input := range c.inputs {
ch, err := input.Start()
stopper := input.Stop
if err != nil {
return err
}
c.t.Go(func() error {
defer stopper()
for {
select {
case <-c.t.Dying():
return nil
case fmsgs := <-ch:
if c.allowMessages(fmsgs) {
for _, fmsg := range fmsgs {
select {
case <-c.t.Dying():
return nil
case c.outgoingFlows <- fmsg:
}
}
}
}
}
})
}
return nil
}
// Stop stops the flow component
func (c *Component) Stop() error {
defer func() {
close(c.outgoingFlows)
c.r.Info().Msg("flow component stopped")
}()
c.r.Info().Msg("stopping flow component")
c.t.Kill(nil)
return c.t.Wait()
}