1
0
Fork 0
telegraf/plugins/inputs/snmp/snmp.go

207 lines
4.9 KiB
Go
Raw Permalink Normal View History

//go:generate ../../../tools/readme_config_includer/generator
package snmp
import (
_ "embed"
"errors"
"fmt"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type Snmp struct {
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
// udp://1.2.3.4:161). If the scheme is not specified then "udp" is used.
Agents []string `toml:"agents"`
// The tag used to name the agent host
AgentHostTag string `toml:"agent_host_tag"`
snmp.ClientConfig
Tables []snmp.Table `toml:"table"`
// Name & Fields are the elements of a Table.
// Telegraf chokes if we try to embed a Table. So instead we have to embed the
// fields of a Table, and construct a Table during runtime.
Name string `toml:"name"`
Fields []snmp.Field `toml:"field"`
Log telegraf.Logger `toml:"-"`
connectionCache []snmp.Connection
translator snmp.Translator
}
func (*Snmp) SampleConfig() string {
return sampleConfig
}
func (s *Snmp) SetTranslator(name string) {
s.Translator = name
}
func (s *Snmp) Init() error {
var err error
switch s.Translator {
case "gosmi":
s.translator, err = snmp.NewGosmiTranslator(s.Path, s.Log)
if err != nil {
return err
}
case "netsnmp":
s.translator = snmp.NewNetsnmpTranslator(s.Log)
default:
return errors.New("invalid translator value")
}
s.connectionCache = make([]snmp.Connection, len(s.Agents))
for i := range s.Tables {
if err := s.Tables[i].Init(s.translator); err != nil {
return fmt.Errorf("initializing table %s: %w", s.Tables[i].Name, err)
}
}
for i := range s.Fields {
if err := s.Fields[i].Init(s.translator); err != nil {
return fmt.Errorf("initializing field %s: %w", s.Fields[i].Name, err)
}
}
if len(s.AgentHostTag) == 0 {
s.AgentHostTag = "agent_host"
}
if s.AgentHostTag != "source" {
config.PrintOptionValueDeprecationNotice("inputs.snmp", "agent_host_tag", s.AgentHostTag, telegraf.DeprecationInfo{
Since: "1.29.0",
Notice: `set to "source" for consistent usage across plugins or safely ignore this message and continue to use the current value`,
})
}
return nil
}
func (s *Snmp) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for i, agent := range s.Agents {
wg.Add(1)
go func(i int, agent string) {
defer wg.Done()
gs, err := s.getConnection(i)
if err != nil {
acc.AddError(fmt.Errorf("agent %s: %w", agent, err))
return
}
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
t := snmp.Table{
Name: s.Name,
Fields: s.Fields,
}
topTags := make(map[string]string)
if err := s.gatherTable(acc, gs, t, topTags, false); err != nil {
acc.AddError(fmt.Errorf("agent %s: %w", agent, err))
}
// Now is the real tables.
for _, t := range s.Tables {
if err := s.gatherTable(acc, gs, t, topTags, true); err != nil {
acc.AddError(fmt.Errorf("agent %s: gathering table %s: %w", agent, t.Name, err))
}
}
}(i, agent)
}
wg.Wait()
return nil
}
func (s *Snmp) gatherTable(acc telegraf.Accumulator, gs snmp.Connection, t snmp.Table, topTags map[string]string, walk bool) error {
rt, err := t.Build(gs, walk)
if err != nil {
return err
}
for _, tr := range rt.Rows {
if !walk {
// top-level table. Add tags to topTags.
for k, v := range tr.Tags {
topTags[k] = v
}
} else {
// real table. Inherit any specified tags.
for _, k := range t.InheritTags {
if v, ok := topTags[k]; ok {
tr.Tags[k] = v
}
}
}
if _, ok := tr.Tags[s.AgentHostTag]; !ok {
tr.Tags[s.AgentHostTag] = gs.Host()
}
acc.AddFields(rt.Name, tr.Fields, tr.Tags, rt.Time)
}
return nil
}
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
// result using `agentIndex` as the cache key. This is done to allow multiple
// connections to a single address. It is an error to use a connection in
// more than one goroutine.
func (s *Snmp) getConnection(idx int) (snmp.Connection, error) {
if gs := s.connectionCache[idx]; gs != nil {
if err := gs.Reconnect(); err != nil {
return gs, fmt.Errorf("reconnecting: %w", err)
}
return gs, nil
}
agent := s.Agents[idx]
gs, err := snmp.NewWrapper(s.ClientConfig)
if err != nil {
return nil, err
}
err = gs.SetAgent(agent)
if err != nil {
return nil, err
}
s.connectionCache[idx] = gs
if err := gs.Connect(); err != nil {
return nil, fmt.Errorf("setting up connection: %w", err)
}
return gs, nil
}
func init() {
inputs.Add("snmp", func() telegraf.Input {
return &Snmp{
Name: "snmp",
ClientConfig: snmp.ClientConfig{
Retries: 3,
MaxRepetitions: 10,
Timeout: config.Duration(5 * time.Second),
Version: 2,
Path: []string{"/usr/share/snmp/mibs"},
Community: "public",
},
}
})
}