Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
102
plugins/processors/ifname/README.md
Normal file
102
plugins/processors/ifname/README.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
# Network Interface Name Processor Plugin
|
||||
|
||||
The `ifname` plugin looks up network interface names using SNMP.
|
||||
|
||||
Telegraf minimum version: Telegraf 1.15.0
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Secret-store support
|
||||
|
||||
This plugin supports secrets from secret-stores for the `auth_password` and
|
||||
`priv_password` option.
|
||||
See the [secret-store documentation][SECRETSTORE] for more details on how
|
||||
to use them.
|
||||
|
||||
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Add a tag of the network interface name looked up over SNMP by interface number
|
||||
[[processors.ifname]]
|
||||
## Name of tag holding the interface number
|
||||
# tag = "ifIndex"
|
||||
|
||||
## Name of output tag where service name will be added
|
||||
# dest = "ifName"
|
||||
|
||||
## Name of tag of the SNMP agent to request the interface name from
|
||||
## example: agent = "source"
|
||||
# agent = "agent"
|
||||
|
||||
## Timeout for each request.
|
||||
# timeout = "5s"
|
||||
|
||||
## SNMP version; can be 1, 2, or 3.
|
||||
# version = 2
|
||||
|
||||
## SNMP community string.
|
||||
# community = "public"
|
||||
|
||||
## Number of retries to attempt.
|
||||
# retries = 3
|
||||
|
||||
## The GETBULK max-repetitions parameter.
|
||||
# max_repetitions = 10
|
||||
|
||||
## SNMPv3 authentication and encryption options.
|
||||
##
|
||||
## Security Name.
|
||||
# sec_name = "myuser"
|
||||
## Authentication protocol; one of "MD5", "SHA", or "".
|
||||
# auth_protocol = "MD5"
|
||||
## Authentication password.
|
||||
# auth_password = "pass"
|
||||
## Security Level; one of "noAuthNoPriv", "authNoPriv", or "authPriv".
|
||||
# sec_level = "authNoPriv"
|
||||
## Context Name.
|
||||
# context_name = ""
|
||||
## Privacy protocol used for encrypted messages; one of "DES", "AES" or "".
|
||||
# priv_protocol = ""
|
||||
## Privacy password used for encrypted messages.
|
||||
# priv_password = ""
|
||||
|
||||
## max_parallel_lookups is the maximum number of SNMP requests to
|
||||
## make at the same time.
|
||||
# max_parallel_lookups = 100
|
||||
|
||||
## ordered controls whether or not the metrics need to stay in the
|
||||
## same order this plugin received them in. If false, this plugin
|
||||
## may change the order when data is cached. If you need metrics to
|
||||
## stay in order set this to true. keeping the metrics ordered may
|
||||
## be slightly slower
|
||||
# ordered = false
|
||||
|
||||
## cache_ttl is the amount of time interface names are cached for a
|
||||
## given agent. After this period elapses if names are needed they
|
||||
## will be retrieved again.
|
||||
# cache_ttl = "8h"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Example config:
|
||||
|
||||
```toml
|
||||
[[processors.ifname]]
|
||||
tag = "ifIndex"
|
||||
dest = "ifName"
|
||||
```
|
||||
|
||||
```diff
|
||||
- foo,ifIndex=2,agent=127.0.0.1 field=123 1502489900000000000
|
||||
+ foo,ifIndex=2,agent=127.0.0.1,ifName=eth0 field=123 1502489900000000000
|
||||
```
|
83
plugins/processors/ifname/cache.go
Normal file
83
plugins/processors/ifname/cache.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package ifname
|
||||
|
||||
// See https://girai.dev/blog/lru-cache-implementation-in-go/
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
type LRUValType = TTLValType
|
||||
|
||||
type hashType map[keyType]*list.Element
|
||||
|
||||
type LRUCache struct {
|
||||
cap uint // capacity
|
||||
l *list.List // doubly linked list
|
||||
m hashType // hash table for checking if list node exists
|
||||
}
|
||||
|
||||
// Pair is the value of a list node.
|
||||
type Pair struct {
|
||||
key keyType
|
||||
value LRUValType
|
||||
}
|
||||
|
||||
// initializes a new LRUCache.
|
||||
func NewLRUCache(capacity uint) LRUCache {
|
||||
return LRUCache{
|
||||
cap: capacity,
|
||||
l: new(list.List),
|
||||
m: make(hashType, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list node from the hash map.
|
||||
func (c *LRUCache) Get(key keyType) (LRUValType, bool) {
|
||||
// check if list node exists
|
||||
if node, ok := c.m[key]; ok {
|
||||
val := node.Value.(*list.Element).Value.(Pair).value
|
||||
// move node to front
|
||||
c.l.MoveToFront(node)
|
||||
return val, true
|
||||
}
|
||||
return LRUValType{}, false
|
||||
}
|
||||
|
||||
// Put key and value in the LRUCache
|
||||
func (c *LRUCache) Put(key keyType, value LRUValType) {
|
||||
// check if list node exists
|
||||
if node, ok := c.m[key]; ok {
|
||||
// move the node to front
|
||||
c.l.MoveToFront(node)
|
||||
// update the value of a list node
|
||||
node.Value.(*list.Element).Value = Pair{key: key, value: value}
|
||||
} else {
|
||||
// delete the last list node if the list is full
|
||||
if uint(c.l.Len()) == c.cap {
|
||||
// get the key that we want to delete
|
||||
idx := c.l.Back().Value.(*list.Element).Value.(Pair).key
|
||||
// delete the node pointer in the hash map by key
|
||||
delete(c.m, idx)
|
||||
// remove the last list node
|
||||
c.l.Remove(c.l.Back())
|
||||
}
|
||||
// initialize a list node
|
||||
node := &list.Element{
|
||||
Value: Pair{
|
||||
key: key,
|
||||
value: value,
|
||||
},
|
||||
}
|
||||
// push the new list node into the list
|
||||
ptr := c.l.PushFront(node)
|
||||
// save the node pointer in the hash map
|
||||
c.m[key] = ptr
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LRUCache) Delete(key keyType) {
|
||||
if node, ok := c.m[key]; ok {
|
||||
c.l.Remove(node)
|
||||
delete(c.m, key)
|
||||
}
|
||||
}
|
23
plugins/processors/ifname/cache_test.go
Normal file
23
plugins/processors/ifname/cache_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package ifname
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
c := NewLRUCache(2)
|
||||
|
||||
c.Put("ones", LRUValType{val: nameMap{1: "one"}})
|
||||
twoMap := LRUValType{val: nameMap{2: "two"}}
|
||||
c.Put("twos", twoMap)
|
||||
c.Put("threes", LRUValType{val: nameMap{3: "three"}})
|
||||
|
||||
_, ok := c.Get("ones")
|
||||
require.False(t, ok)
|
||||
|
||||
v, ok := c.Get("twos")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, twoMap, v)
|
||||
}
|
330
plugins/processors/ifname/ifname.go
Normal file
330
plugins/processors/ifname/ifname.go
Normal file
|
@ -0,0 +1,330 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package ifname
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/snmp"
|
||||
"github.com/influxdata/telegraf/plugins/common/parallel"
|
||||
"github.com/influxdata/telegraf/plugins/processors"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
type nameMap map[uint64]string
|
||||
type keyType = string
|
||||
type valType = nameMap
|
||||
|
||||
type mapFunc func(agent string) (nameMap, error)
|
||||
|
||||
type sigMap map[string]chan struct{}
|
||||
|
||||
type IfName struct {
|
||||
SourceTag string `toml:"tag"`
|
||||
DestTag string `toml:"dest"`
|
||||
AgentTag string `toml:"agent"`
|
||||
|
||||
snmp.ClientConfig
|
||||
|
||||
CacheSize uint `toml:"max_cache_entries"`
|
||||
MaxParallelLookups int `toml:"max_parallel_lookups"`
|
||||
Ordered bool `toml:"ordered"`
|
||||
CacheTTL config.Duration `toml:"cache_ttl"`
|
||||
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
ifTable *snmp.Table
|
||||
ifXTable *snmp.Table
|
||||
|
||||
cache *TTLCache
|
||||
lock sync.Mutex
|
||||
parallel parallel.Parallel
|
||||
sigs sigMap
|
||||
|
||||
getMapRemote mapFunc
|
||||
}
|
||||
|
||||
const minRetry = 5 * time.Minute
|
||||
|
||||
func (*IfName) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (d *IfName) Init() error {
|
||||
d.getMapRemote = d.getMapRemoteNoMock
|
||||
|
||||
c := NewTTLCache(time.Duration(d.CacheTTL), d.CacheSize)
|
||||
d.cache = &c
|
||||
|
||||
d.sigs = make(sigMap)
|
||||
|
||||
if _, err := snmp.NewWrapper(d.ClientConfig); err != nil {
|
||||
return fmt.Errorf("parsing SNMP client config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *IfName) addTag(metric telegraf.Metric) error {
|
||||
agent, ok := metric.GetTag(d.AgentTag)
|
||||
if !ok {
|
||||
d.Log.Warn("Agent tag missing.")
|
||||
return nil
|
||||
}
|
||||
|
||||
numS, ok := metric.GetTag(d.SourceTag)
|
||||
if !ok {
|
||||
d.Log.Warn("Source tag missing.")
|
||||
return nil
|
||||
}
|
||||
|
||||
num, err := strconv.ParseUint(numS, 10, 64)
|
||||
if err != nil {
|
||||
return errors.New("couldn't parse source tag as uint")
|
||||
}
|
||||
|
||||
firstTime := true
|
||||
for {
|
||||
m, age, err := d.getMap(agent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't retrieve the table of interface names for %s: %w", agent, err)
|
||||
}
|
||||
|
||||
name, found := m[num]
|
||||
if found {
|
||||
// success
|
||||
metric.AddTag(d.DestTag, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have the agent's interface map but it doesn't contain
|
||||
// the interface we're interested in. If the entry is old
|
||||
// enough, retrieve it from the agent once more.
|
||||
if age < minRetry {
|
||||
return fmt.Errorf("interface number %d isn't in the table of interface names on %s", num, agent)
|
||||
}
|
||||
|
||||
if firstTime {
|
||||
d.invalidate(agent)
|
||||
firstTime = false
|
||||
continue
|
||||
}
|
||||
|
||||
// not found, cache hit, retrying
|
||||
return fmt.Errorf("missing interface but couldn't retrieve table for %v", agent)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *IfName) invalidate(agent string) {
|
||||
d.lock.Lock()
|
||||
d.cache.Delete(agent)
|
||||
d.lock.Unlock()
|
||||
}
|
||||
|
||||
func (d *IfName) Start(acc telegraf.Accumulator) error {
|
||||
var err error
|
||||
|
||||
d.ifTable, err = makeTable("1.3.6.1.2.1.2.2.1.2")
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing ifTable: %w", err)
|
||||
}
|
||||
d.ifXTable, err = makeTable("1.3.6.1.2.1.31.1.1.1.1")
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing ifXTable: %w", err)
|
||||
}
|
||||
|
||||
fn := func(m telegraf.Metric) []telegraf.Metric {
|
||||
err := d.addTag(m)
|
||||
if err != nil {
|
||||
d.Log.Debugf("Error adding tag: %v", err)
|
||||
}
|
||||
return []telegraf.Metric{m}
|
||||
}
|
||||
|
||||
if d.Ordered {
|
||||
d.parallel = parallel.NewOrdered(acc, fn, 10000, d.MaxParallelLookups)
|
||||
} else {
|
||||
d.parallel = parallel.NewUnordered(acc, fn, d.MaxParallelLookups)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *IfName) Add(metric telegraf.Metric, _ telegraf.Accumulator) error {
|
||||
d.parallel.Enqueue(metric)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *IfName) Stop() {
|
||||
d.parallel.Stop()
|
||||
}
|
||||
|
||||
// getMap gets the interface names map either from cache or from the SNMP
|
||||
// agent
|
||||
func (d *IfName) getMap(agent string) (entry nameMap, age time.Duration, err error) {
|
||||
var sig chan struct{}
|
||||
|
||||
d.lock.Lock()
|
||||
|
||||
// Check cache
|
||||
m, ok, age := d.cache.Get(agent)
|
||||
if ok {
|
||||
d.lock.Unlock()
|
||||
return m, age, nil
|
||||
}
|
||||
|
||||
// cache miss. Is this the first request for this agent?
|
||||
sig, found := d.sigs[agent]
|
||||
if !found {
|
||||
// This is the first request. Make signal for subsequent requests to wait on
|
||||
s := make(chan struct{})
|
||||
d.sigs[agent] = s
|
||||
sig = s
|
||||
}
|
||||
|
||||
d.lock.Unlock()
|
||||
|
||||
if found {
|
||||
// This is not the first request. Wait for first to finish.
|
||||
<-sig
|
||||
|
||||
// Check cache again
|
||||
d.lock.Lock()
|
||||
m, ok, age := d.cache.Get(agent)
|
||||
d.lock.Unlock()
|
||||
if ok {
|
||||
return m, age, nil
|
||||
}
|
||||
return nil, 0, errors.New("getting remote table from cache")
|
||||
}
|
||||
|
||||
// The cache missed and this is the first request for this
|
||||
// agent. Make the SNMP request
|
||||
m, err = d.getMapRemote(agent)
|
||||
|
||||
d.lock.Lock()
|
||||
if err != nil {
|
||||
// snmp failure. signal without saving to cache
|
||||
close(sig)
|
||||
delete(d.sigs, agent)
|
||||
|
||||
d.lock.Unlock()
|
||||
return nil, 0, fmt.Errorf("getting remote table: %w", err)
|
||||
}
|
||||
|
||||
// snmp success. Cache response, then signal any other waiting
|
||||
// requests for this agent and clean up
|
||||
d.cache.Put(agent, m)
|
||||
close(sig)
|
||||
delete(d.sigs, agent)
|
||||
|
||||
d.lock.Unlock()
|
||||
return m, 0, nil
|
||||
}
|
||||
|
||||
func (d *IfName) getMapRemoteNoMock(agent string) (nameMap, error) {
|
||||
gs, err := snmp.NewWrapper(d.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing SNMP client config: %w", err)
|
||||
}
|
||||
|
||||
if err = gs.SetAgent(agent); err != nil {
|
||||
return nil, fmt.Errorf("parsing agent tag: %w", err)
|
||||
}
|
||||
|
||||
if err = gs.Connect(); err != nil {
|
||||
return nil, fmt.Errorf("connecting when fetching interface names: %w", err)
|
||||
}
|
||||
|
||||
// try ifXtable and ifName first. if that fails, fall back to
|
||||
// ifTable and ifDescr
|
||||
var m nameMap
|
||||
if m, err = buildMap(gs, d.ifXTable); err == nil {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if m, err = buildMap(gs, d.ifTable); err == nil {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("fetching interface names: %w", err)
|
||||
}
|
||||
|
||||
func init() {
|
||||
processors.AddStreaming("ifname", func() telegraf.StreamingProcessor {
|
||||
return &IfName{
|
||||
SourceTag: "ifIndex",
|
||||
DestTag: "ifName",
|
||||
AgentTag: "agent",
|
||||
CacheSize: 100,
|
||||
MaxParallelLookups: 100,
|
||||
ClientConfig: *snmp.DefaultClientConfig(),
|
||||
CacheTTL: config.Duration(8 * time.Hour),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func makeTable(oid string) (*snmp.Table, error) {
|
||||
var err error
|
||||
tab := snmp.Table{
|
||||
Name: "ifTable",
|
||||
IndexAsTag: true,
|
||||
Fields: []snmp.Field{
|
||||
{Oid: oid, Name: "ifName"},
|
||||
},
|
||||
}
|
||||
|
||||
err = tab.Init(nil)
|
||||
if err != nil {
|
||||
// Init already wraps
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tab, nil
|
||||
}
|
||||
|
||||
func buildMap(gs snmp.GosnmpWrapper, tab *snmp.Table) (nameMap, error) {
|
||||
var err error
|
||||
|
||||
rtab, err := tab.Build(gs, true)
|
||||
if err != nil {
|
||||
// Build already wraps
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(rtab.Rows) == 0 {
|
||||
return nil, errors.New("empty table")
|
||||
}
|
||||
|
||||
t := make(nameMap)
|
||||
for _, v := range rtab.Rows {
|
||||
iStr, ok := v.Tags["index"]
|
||||
if !ok {
|
||||
// should always have an index tag because the table should
|
||||
// always have IndexAsTag true
|
||||
return nil, errors.New("no index tag")
|
||||
}
|
||||
i, err := strconv.ParseUint(iStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.New("index tag isn't a uint")
|
||||
}
|
||||
nameIf, ok := v.Fields["ifName"]
|
||||
if !ok {
|
||||
return nil, errors.New("ifName field is missing")
|
||||
}
|
||||
name, ok := nameIf.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("ifName field isn't a string")
|
||||
}
|
||||
|
||||
t[i] = name
|
||||
}
|
||||
return t, nil
|
||||
}
|
232
plugins/processors/ifname/ifname_test.go
Normal file
232
plugins/processors/ifname/ifname_test.go
Normal file
|
@ -0,0 +1,232 @@
|
|||
package ifname
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/snmp"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
type item struct {
|
||||
entry nameMap
|
||||
age time.Duration
|
||||
err error
|
||||
}
|
||||
|
||||
func TestTableIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
t.Skip("Skipping test due to connect failures")
|
||||
|
||||
d := IfName{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
tab, err := makeTable("1.3.6.1.2.1.2.2.1.2")
|
||||
require.NoError(t, err)
|
||||
|
||||
gs, err := snmp.NewWrapper(*snmp.DefaultClientConfig())
|
||||
require.NoError(t, err)
|
||||
err = gs.SetAgent("127.0.0.1")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = gs.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Could use ifIndex but oid index is always the same
|
||||
m, err := buildMap(gs, tab)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, m)
|
||||
}
|
||||
|
||||
func TestIfNameIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
t.Skip("Skipping test due to connect failures")
|
||||
|
||||
d := IfName{
|
||||
SourceTag: "ifIndex",
|
||||
DestTag: "ifName",
|
||||
AgentTag: "agent",
|
||||
CacheSize: 1000,
|
||||
ClientConfig: *snmp.DefaultClientConfig(),
|
||||
}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
acc := testutil.Accumulator{}
|
||||
err = d.Start(&acc)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
m := testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"ifIndex": "1",
|
||||
"agent": "127.0.0.1",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
time.Unix(0, 0),
|
||||
)
|
||||
|
||||
expected := testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"ifIndex": "1",
|
||||
"agent": "127.0.0.1",
|
||||
"ifName": "lo",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
time.Unix(0, 0),
|
||||
)
|
||||
|
||||
err = d.addTag(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.RequireMetricEqual(t, expected, m)
|
||||
}
|
||||
|
||||
func TestGetMap(t *testing.T) {
|
||||
d := IfName{
|
||||
CacheSize: 1000,
|
||||
CacheTTL: config.Duration(10 * time.Second),
|
||||
}
|
||||
|
||||
require.NoError(t, d.Init())
|
||||
|
||||
expected := nameMap{
|
||||
1: "ifname1",
|
||||
2: "ifname2",
|
||||
}
|
||||
|
||||
var remoteCalls int32
|
||||
|
||||
// Mock the snmp transaction
|
||||
d.getMapRemote = func(string) (nameMap, error) {
|
||||
atomic.AddInt32(&remoteCalls, 1)
|
||||
return expected, nil
|
||||
}
|
||||
m, age, err := d.getMap("agent")
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, age) // Age is zero when map comes from getMapRemote
|
||||
require.Equal(t, expected, m)
|
||||
|
||||
// Remote call should happen the first time getMap runs
|
||||
require.Equal(t, int32(1), remoteCalls)
|
||||
|
||||
const thMax = 3
|
||||
ch := make(chan item, thMax)
|
||||
var wg sync.WaitGroup
|
||||
for th := 0; th < thMax; th++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m, age, err := d.getMap("agent")
|
||||
ch <- item{entry: m, age: age, err: err}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
|
||||
for entry := range ch {
|
||||
require.NoError(t, entry.err)
|
||||
require.NotZero(t, entry.age) // Age is nonzero when map comes from cache
|
||||
require.Equal(t, expected, entry.entry)
|
||||
}
|
||||
|
||||
// Remote call should not happen subsequent times getMap runs
|
||||
require.Equal(t, int32(1), remoteCalls)
|
||||
}
|
||||
|
||||
func TestTracking(t *testing.T) {
|
||||
// Setup raw input and expected output
|
||||
inputRaw := []telegraf.Metric{
|
||||
metric.New(
|
||||
"test",
|
||||
map[string]string{"ifIndex": "1", "agent": "127.0.0.1"},
|
||||
map[string]interface{}{"value": 42},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
expected := []telegraf.Metric{
|
||||
metric.New(
|
||||
"test",
|
||||
map[string]string{
|
||||
"ifIndex": "1",
|
||||
"agent": "127.0.0.1",
|
||||
"ifName": "lo",
|
||||
},
|
||||
map[string]interface{}{"value": 42},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
// Create fake notification for testing
|
||||
var mu sync.Mutex
|
||||
delivered := make([]telegraf.DeliveryInfo, 0, len(inputRaw))
|
||||
notify := func(di telegraf.DeliveryInfo) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
delivered = append(delivered, di)
|
||||
}
|
||||
|
||||
// Convert raw input to tracking metric
|
||||
input := make([]telegraf.Metric, 0, len(inputRaw))
|
||||
for _, m := range inputRaw {
|
||||
tm, _ := metric.WithTracking(m, notify)
|
||||
input = append(input, tm)
|
||||
}
|
||||
|
||||
// Prepare and start the plugin
|
||||
plugin := &IfName{
|
||||
SourceTag: "ifIndex",
|
||||
DestTag: "ifName",
|
||||
AgentTag: "agent",
|
||||
CacheSize: 1000,
|
||||
CacheTTL: config.Duration(10 * time.Second),
|
||||
MaxParallelLookups: 100,
|
||||
}
|
||||
require.NoError(t, plugin.Init())
|
||||
plugin.cache.Put("127.0.0.1", nameMap{1: "lo"})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Start(&acc))
|
||||
defer plugin.Stop()
|
||||
|
||||
// Process expected metrics and compare with resulting metrics
|
||||
for _, in := range input {
|
||||
require.NoError(t, plugin.Add(in, &acc))
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return int(acc.NMetrics()) >= len(expected)
|
||||
}, 3*time.Second, 100*time.Microsecond)
|
||||
|
||||
actual := acc.GetTelegrafMetrics()
|
||||
testutil.RequireMetricsEqual(t, expected, actual)
|
||||
|
||||
// Simulate output acknowledging delivery
|
||||
for _, m := range actual {
|
||||
m.Accept()
|
||||
}
|
||||
|
||||
// Check delivery
|
||||
require.Eventuallyf(t, func() bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return len(input) == len(delivered)
|
||||
}, time.Second, 100*time.Millisecond, "%d delivered but %d expected", len(delivered), len(expected))
|
||||
}
|
59
plugins/processors/ifname/sample.conf
Normal file
59
plugins/processors/ifname/sample.conf
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Add a tag of the network interface name looked up over SNMP by interface number
|
||||
[[processors.ifname]]
|
||||
## Name of tag holding the interface number
|
||||
# tag = "ifIndex"
|
||||
|
||||
## Name of output tag where service name will be added
|
||||
# dest = "ifName"
|
||||
|
||||
## Name of tag of the SNMP agent to request the interface name from
|
||||
## example: agent = "source"
|
||||
# agent = "agent"
|
||||
|
||||
## Timeout for each request.
|
||||
# timeout = "5s"
|
||||
|
||||
## SNMP version; can be 1, 2, or 3.
|
||||
# version = 2
|
||||
|
||||
## SNMP community string.
|
||||
# community = "public"
|
||||
|
||||
## Number of retries to attempt.
|
||||
# retries = 3
|
||||
|
||||
## The GETBULK max-repetitions parameter.
|
||||
# max_repetitions = 10
|
||||
|
||||
## SNMPv3 authentication and encryption options.
|
||||
##
|
||||
## Security Name.
|
||||
# sec_name = "myuser"
|
||||
## Authentication protocol; one of "MD5", "SHA", or "".
|
||||
# auth_protocol = "MD5"
|
||||
## Authentication password.
|
||||
# auth_password = "pass"
|
||||
## Security Level; one of "noAuthNoPriv", "authNoPriv", or "authPriv".
|
||||
# sec_level = "authNoPriv"
|
||||
## Context Name.
|
||||
# context_name = ""
|
||||
## Privacy protocol used for encrypted messages; one of "DES", "AES" or "".
|
||||
# priv_protocol = ""
|
||||
## Privacy password used for encrypted messages.
|
||||
# priv_password = ""
|
||||
|
||||
## max_parallel_lookups is the maximum number of SNMP requests to
|
||||
## make at the same time.
|
||||
# max_parallel_lookups = 100
|
||||
|
||||
## ordered controls whether or not the metrics need to stay in the
|
||||
## same order this plugin received them in. If false, this plugin
|
||||
## may change the order when data is cached. If you need metrics to
|
||||
## stay in order set this to true. keeping the metrics ordered may
|
||||
## be slightly slower
|
||||
# ordered = false
|
||||
|
||||
## cache_ttl is the amount of time interface names are cached for a
|
||||
## given agent. After this period elapses if names are needed they
|
||||
## will be retrieved again.
|
||||
# cache_ttl = "8h"
|
62
plugins/processors/ifname/ttl_cache.go
Normal file
62
plugins/processors/ifname/ttl_cache.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package ifname
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TTLValType struct {
|
||||
time time.Time // when entry was added
|
||||
val valType
|
||||
}
|
||||
|
||||
type timeFunc func() time.Time
|
||||
|
||||
type TTLCache struct {
|
||||
validDuration time.Duration
|
||||
lru LRUCache
|
||||
now timeFunc
|
||||
}
|
||||
|
||||
func NewTTLCache(valid time.Duration, capacity uint) TTLCache {
|
||||
return TTLCache{
|
||||
lru: NewLRUCache(capacity),
|
||||
validDuration: valid,
|
||||
now: time.Now,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TTLCache) Get(key keyType) (valType, bool, time.Duration) {
|
||||
v, ok := c.lru.Get(key)
|
||||
if !ok {
|
||||
return valType{}, false, 0
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Sometimes on Windows `c.now().Sub(v.time) == 0` due to clock resolution issues:
|
||||
// https://github.com/golang/go/issues/17696
|
||||
// https://github.com/golang/go/issues/29485
|
||||
// Force clock to refresh:
|
||||
time.Sleep(time.Nanosecond)
|
||||
}
|
||||
|
||||
age := c.now().Sub(v.time)
|
||||
if age < c.validDuration {
|
||||
return v.val, ok, age
|
||||
}
|
||||
|
||||
c.lru.Delete(key)
|
||||
return valType{}, false, 0
|
||||
}
|
||||
|
||||
func (c *TTLCache) Put(key keyType, value valType) {
|
||||
v := TTLValType{
|
||||
val: value,
|
||||
time: c.now(),
|
||||
}
|
||||
c.lru.Put(key, v)
|
||||
}
|
||||
|
||||
func (c *TTLCache) Delete(key keyType) {
|
||||
c.lru.Delete(key)
|
||||
}
|
43
plugins/processors/ifname/ttl_cache_test.go
Normal file
43
plugins/processors/ifname/ttl_cache_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package ifname
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTTLCacheExpire(t *testing.T) {
|
||||
c := NewTTLCache(1*time.Second, 100)
|
||||
|
||||
c.now = func() time.Time {
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
|
||||
c.Put("ones", nameMap{1: "one"})
|
||||
require.Len(t, c.lru.m, 1)
|
||||
|
||||
c.now = func() time.Time {
|
||||
return time.Unix(1, 0)
|
||||
}
|
||||
|
||||
_, ok, _ := c.Get("ones")
|
||||
require.False(t, ok)
|
||||
require.Empty(t, c.lru.m)
|
||||
require.Equal(t, 0, c.lru.l.Len())
|
||||
}
|
||||
|
||||
func TestTTLCache(t *testing.T) {
|
||||
c := NewTTLCache(1*time.Second, 100)
|
||||
|
||||
c.now = func() time.Time {
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
|
||||
expected := nameMap{1: "one"}
|
||||
c.Put("ones", expected)
|
||||
|
||||
actual, ok, _ := c.Get("ones")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue