Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add option to select translator #10802

Merged
merged 14 commits into from
Mar 18, 2022
5 changes: 5 additions & 0 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/models"
"github.com/influxdata/telegraf/plugins/serializers/influx"
)
Expand Down Expand Up @@ -186,6 +187,10 @@ func (a *Agent) Run(ctx context.Context) error {
// initPlugins runs the Init function on plugins.
func (a *Agent) initPlugins() error {
for _, input := range a.Config.Inputs {
// Share the snmp translator setting with plugins that need it.
if tp, ok := input.Input.(snmp.TranslatorPlugin); ok {
tp.SetTranslator(a.Config.Agent.Translator)
}
err := input.Init()
if err != nil {
return fmt.Errorf("could not initialize input %s: %v",
Expand Down
12 changes: 12 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ type AgentConfig struct {

Hostname string
OmitHostname bool

Translator string `toml:"translator"`
}

// InputNames returns a list of strings of the configured inputs.
Expand Down Expand Up @@ -418,6 +420,11 @@ var agentConfig = `
hostname = ""
## If set to true, do no set the "host" tag in the telegraf agent.
omit_hostname = false

## Translator for SNMP OIDs
## Valid values are "netsnmp" which runs snmptranslate and snmptable,
## and "gosmi" which uses the gosmi library.
Hipska marked this conversation as resolved.
Show resolved Hide resolved
# translator = "netsnmp"
`

var outputHeader = `
Expand Down Expand Up @@ -855,6 +862,11 @@ func (c *Config) LoadConfigData(data []byte) error {
c.Tags["host"] = c.Agent.Hostname
}

// Set snmp agent translator default
if c.Agent.Translator == "" {
c.Agent.Translator = "netsnmp"
}

if len(c.UnusedFields) > 0 {
return fmt.Errorf("line %d: configuration specified the fields %q, but they weren't used", tbl.Line, keys(c.UnusedFields))
}
Expand Down
2 changes: 2 additions & 0 deletions internal/snmp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type ClientConfig struct {
Version uint8 `toml:"version"`
// Path to mib files
Path []string `toml:"path"`
// Translator implementation
Translator string `toml:"-"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed to be exported.

Suggested change
Translator string `toml:"-"`
translatorType string


// Parameters for Version 1 & 2
Community string `toml:"community"`
Expand Down
2 changes: 2 additions & 0 deletions plugins/inputs/snmp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ path onto the global path variable
# version = 2

## Path to mib files
## Used by the gosmi translator.
## To add paths when translating with netsnmp, use the MIBDIRS environment variable
# path = ["/usr/share/snmp/mibs"]

## SNMP community string.
Expand Down
123 changes: 123 additions & 0 deletions plugins/inputs/snmp/gosmi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package snmp

import (
"fmt"
"sync"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/sleepinggenius2/gosmi"
)

type gosmiTranslator struct {
}

func NewGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) {
err := snmp.LoadMibsFromPath(paths, log, &snmp.GosmiMibLoader{})
if err == nil {
return &gosmiTranslator{}, nil
}
return nil, err
}

type gosmiSnmpTranslateCache struct {
mibName string
oidNum string
oidText string
conversion string
node gosmi.SmiNode
err error
}

var gosmiSnmpTranslateCachesLock sync.Mutex
var gosmiSnmpTranslateCaches map[string]gosmiSnmpTranslateCache

//nolint:revive
func (g *gosmiTranslator) SnmpTranslate(oid string) (string, string, string, string, error) {
a, b, c, d, _, e := g.SnmpTranslateFull(oid)
return a, b, c, d, e
}

//nolint:revive
func (g *gosmiTranslator) SnmpTranslateFull(oid string) (
mibName string, oidNum string, oidText string,
conversion string,
node gosmi.SmiNode,
err error) {
gosmiSnmpTranslateCachesLock.Lock()
if gosmiSnmpTranslateCaches == nil {
gosmiSnmpTranslateCaches = map[string]gosmiSnmpTranslateCache{}
}

var stc gosmiSnmpTranslateCache
var ok bool
if stc, ok = gosmiSnmpTranslateCaches[oid]; !ok {
// This will result in only one call to snmptranslate running at a time.
// We could speed it up by putting a lock in snmpTranslateCache and then
// returning it immediately, and multiple callers would then release the
// snmpTranslateCachesLock and instead wait on the individual
// snmpTranslation.Lock to release. But I don't know that the extra complexity
// is worth it. Especially when it would slam the system pretty hard if lots
// of lookups are being performed.

stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.node, stc.err = snmp.SnmpTranslateCall(oid)
gosmiSnmpTranslateCaches[oid] = stc
}

gosmiSnmpTranslateCachesLock.Unlock()

return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.node, stc.err
}

type gosmiSnmpTableCache struct {
mibName string
oidNum string
oidText string
fields []Field
err error
}

var gosmiSnmpTableCaches map[string]gosmiSnmpTableCache
var gosmiSnmpTableCachesLock sync.Mutex

// snmpTable resolves the given OID as a table, providing information about the
// table and fields within.
//nolint:revive //Too many return variable but necessary
func (g *gosmiTranslator) SnmpTable(oid string) (
mibName string, oidNum string, oidText string,
fields []Field,
err error) {
gosmiSnmpTableCachesLock.Lock()
if gosmiSnmpTableCaches == nil {
gosmiSnmpTableCaches = map[string]gosmiSnmpTableCache{}
}

var stc gosmiSnmpTableCache
var ok bool
if stc, ok = gosmiSnmpTableCaches[oid]; !ok {
stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err = g.SnmpTableCall(oid)
gosmiSnmpTableCaches[oid] = stc
}

gosmiSnmpTableCachesLock.Unlock()
return stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err
}

//nolint:revive //Too many return variable but necessary
func (g *gosmiTranslator) SnmpTableCall(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
mibName, oidNum, oidText, _, node, err := g.SnmpTranslateFull(oid)
if err != nil {
return "", "", "", nil, fmt.Errorf("translating: %w", err)
}

mibPrefix := mibName + "::"

col, tagOids, err := snmp.GetIndex(oidNum, mibPrefix, node)

for _, c := range col {
_, isTag := tagOids[mibPrefix+c]
fields = append(fields, Field{Name: c, Oid: mibPrefix + c, IsTag: isTag})
}

return mibName, oidNum, oidText, fields, err
}
Loading