From 86a9a44c7e741073f0348104dca8d3f508450bf3 Mon Sep 17 00:00:00 2001 From: Cameron Sparr Date: Mon, 1 Aug 2016 17:49:45 +0100 Subject: [PATCH] Completely refactor snmp plugin - use snmpget/snmpwalk terminology - use snmptranslate to get MIBs --- etc/telegraf.conf | 70 ++-- plugins/inputs/snmp/gosnmp.go | 66 ++++ plugins/inputs/snmp/snmp.go | 681 +++++++++++++--------------------- 3 files changed, 360 insertions(+), 457 deletions(-) create mode 100644 plugins/inputs/snmp/gosnmp.go diff --git a/etc/telegraf.conf b/etc/telegraf.conf index 04ac78927..f03a7f1eb 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -1397,35 +1397,49 @@ # #priv_protocol = "" # Values: "DES", "AES", "" # #priv_password = "" # -# ## measurement name -# name = "system" -# ## SNMP fields are gotten by using an "snmpget" request. If a name is not -# ## specified, we attempt to use snmptranslate on the OID to get the MIB name. -# [[inputs.snmp.field]] -# name = "hostname" -# oid = ".1.2.3.0.1.1" -# [[inputs.snmp.field]] -# name = "uptime" -# oid = ".1.2.3.0.1.200" -# [[inputs.snmp.field]] -# oid = ".1.2.3.0.1.201" +# ## Each 'tag' is an "snmpget" request. Tags are inherited by snmp 'walk' +# ## and 'get' requests specified below. If a name for the tag is not provided, +# ## we will attempt to use snmptranslate on the OID to get the MIB name. +# [[inputs.snmp.tag]] +# name = "sys_name" # optional, tag name +# oid = ".1.3.6.1.2.1.1.5.0" +# [[inputs.snmp.tag]] +# name = "sys_location" +# oid = ".1.3.6.1.2.1.1.6.0" # -# [[inputs.snmp.table]] -# ## measurement name -# name = "remote_servers" -# inherit_tags = ["hostname"] -# ## SNMP table fields must be specified individually. If the table field has -# ## multiple rows, they will all be gotten. -# [[inputs.snmp.table.field]] -# name = "server" -# oid = ".1.2.3.0.0.0" -# is_tag = true -# [[inputs.snmp.table.field]] -# name = "connections" -# oid = ".1.2.3.0.0.1" -# [[inputs.snmp.table.field]] -# name = "latency" -# oid = ".1.2.3.0.0.2" +# ## optional, name of the measurement that each 'get' field will be under. +# name = "snmp" +# ## Each 'get' is an "snmpget" request. If a name for the field is not provided, +# ## we will attempt to use snmptranslate on the OID to get the MIB name. +# [[inputs.snmp.get]] +# name = "snmp_in_packets" # optional, field name +# oid = ".1.3.6.1.2.1.11.1.0" +# [[inputs.snmp.get]] +# oid = ".1.3.6.1.2.1.11.2.0" +# +# ## An SNMP walk will do an "snmpwalk" from the given root OID. +# ## Each OID it encounters will be converted into a field on the measurement. +# ## We will attempt to use snmptranslate on the OID to get the MIB names for +# ## each field. +# [[inputs.snmp.walk]] +# ## optional, inherit_tags specifies which top-level tags to inherit. +# ## Globs are supported. +# inherit_tags = ["sys_*"] +# name = "snmp_metrics" # measurement name +# root_oid = ".1.3.6.1.2.1.11" +# +# [[inputs.snmp.walk]] +# ## optional, 'include' specifies MIB names to include in the walk. +# ## 'exclude' is also available, although they're mutually-exclusive. +# ## Globs are supported. +# include = ["if*"] +# name = "ifTable" +# root_oid = ".1.3.6.1.2.1.2.2" +# +# [[inputs.snmp.walk]] +# exclude = ["ifAlias"] +# name = "ifXTable" +# root_oid = ".1.3.6.1.2.1.31.1.1" # # DEPRECATED: This will be removed in a future release. diff --git a/plugins/inputs/snmp/gosnmp.go b/plugins/inputs/snmp/gosnmp.go new file mode 100644 index 000000000..6c39fbd20 --- /dev/null +++ b/plugins/inputs/snmp/gosnmp.go @@ -0,0 +1,66 @@ +package snmp + +import ( + "fmt" + + "github.com/soniah/gosnmp" +) + +// snmpConnection is an interface which wraps a *gosnmp.GoSNMP object. +// We interact through an interface so we can mock it out in tests. +type snmpConnection interface { + Host() string + //BulkWalkAll(string) ([]gosnmp.SnmpPDU, error) + Walk(string, gosnmp.WalkFunc) error + Get(oids []string) (*gosnmp.SnmpPacket, error) +} + +// gosnmpWrapper wraps a *gosnmp.GoSNMP object so we can use it as a snmpConnection. +type gosnmpWrapper struct { + *gosnmp.GoSNMP +} + +// Host returns the value of GoSNMP.Target. +func (gsw gosnmpWrapper) Host() string { + return gsw.Target +} + +// Walk wraps GoSNMP.Walk() or GoSNMP.BulkWalk(), depending on whether the +// connection is using SNMPv1 or newer. +// Also, if any error is encountered, it will just once reconnect and try again. +func (gsw gosnmpWrapper) Walk(oid string, fn gosnmp.WalkFunc) error { + var err error + // On error, retry once. + // Unfortunately we can't distinguish between an error returned by gosnmp, and one returned by the walk function. + for i := 0; i < 2; i++ { + if gsw.Version == gosnmp.Version1 { + err = gsw.GoSNMP.Walk(oid, fn) + } else { + err = gsw.GoSNMP.BulkWalk(oid, fn) + } + if err == nil { + return nil + } + if err := gsw.GoSNMP.Connect(); err != nil { + return fmt.Errorf("reconnecting %s", err) + } + } + return err +} + +// Get wraps GoSNMP.GET(). +// If any error is encountered, it will just once reconnect and try again. +func (gsw gosnmpWrapper) Get(oids []string) (*gosnmp.SnmpPacket, error) { + var err error + var pkt *gosnmp.SnmpPacket + for i := 0; i < 2; i++ { + pkt, err = gsw.GoSNMP.Get(oids) + if err == nil { + return pkt, nil + } + if err := gsw.GoSNMP.Connect(); err != nil { + return nil, fmt.Errorf("reconnecting %s", err) + } + } + return nil, err +} diff --git a/plugins/inputs/snmp/snmp.go b/plugins/inputs/snmp/snmp.go index 6d83deae6..642ee9b67 100644 --- a/plugins/inputs/snmp/snmp.go +++ b/plugins/inputs/snmp/snmp.go @@ -2,7 +2,6 @@ package snmp import ( "fmt" - "math" "net" "os/exec" "strconv" @@ -10,6 +9,7 @@ import ( "time" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/filter" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/inputs" @@ -36,35 +36,48 @@ const sampleConfig = ` #priv_protocol = "" # Values: "DES", "AES", "" #priv_password = "" - ## measurement name - name = "system" - ## SNMP fields are gotten by using an "snmpget" request. If a name is not - ## specified, we attempt to use snmptranslate on the OID to get the MIB name. - [[inputs.snmp.field]] - name = "hostname" - oid = ".1.2.3.0.1.1" - [[inputs.snmp.field]] - name = "uptime" - oid = ".1.2.3.0.1.200" - [[inputs.snmp.field]] - oid = ".1.2.3.0.1.201" + ## Each 'tag' is an "snmpget" request. Tags are inherited by snmp 'walk' + ## and 'get' requests specified below. If a name for the tag is not provided, + ## we will attempt to get the MIB name. + [[inputs.snmp.tag]] + name = "sys_name" # optional, tag name + oid = ".1.3.6.1.2.1.1.5.0" + [[inputs.snmp.tag]] + name = "sys_location" + oid = ".1.3.6.1.2.1.1.6.0" - [[inputs.snmp.table]] - ## measurement name - name = "remote_servers" - inherit_tags = ["hostname"] - ## SNMP table fields must be specified individually. If the table field has - ## multiple rows, they will all be gotten. - [[inputs.snmp.table.field]] - name = "server" - oid = ".1.2.3.0.0.0" - is_tag = true - [[inputs.snmp.table.field]] - name = "connections" - oid = ".1.2.3.0.0.1" - [[inputs.snmp.table.field]] - name = "latency" - oid = ".1.2.3.0.0.2" + ## optional, name of the measurement that each 'get' field will be under. + name = "snmp" + ## Each 'get' is an "snmpget" request. If a name for the field is not provided, + ## we will attempt to get the MIB name. + [[inputs.snmp.get]] + name = "snmp_in_packets" # optional, field name + oid = ".1.3.6.1.2.1.11.1.0" + [[inputs.snmp.get]] + oid = ".1.3.6.1.2.1.11.2.0" + + ## An SNMP walk will do an "snmpwalk" from the given root OID. + ## Each OID it encounters will be converted into a field on the measurement. + ## We will attempt to get the MIB names for each field. + [[inputs.snmp.walk]] + ## optional, inherit_tags specifies which top-level tags to inherit. + ## Globs are supported. + inherit_tags = ["sys_*"] + name = "snmp_metrics" # measurement name + root_oid = ".1.3.6.1.2.1.11" + + [[inputs.snmp.walk]] + ## optional, 'include' specifies MIB names to include in the walk. + ## 'exclude' is also available, although they're mutually-exclusive. + ## Globs are supported. + include = ["if*"] + name = "ifTable" + root_oid = ".1.3.6.1.2.1.2.2" + + [[inputs.snmp.walk]] + exclude = ["ifAlias"] + name = "ifXTable" + root_oid = ".1.3.6.1.2.1.31.1.1" ` // Snmp holds the configuration for the plugin. @@ -94,106 +107,59 @@ type Snmp struct { // Values: "DES", "AES", "". Default: "" PrivProtocol string PrivPassword string - EngineID string - EngineBoots uint32 - EngineTime uint32 - - Tables []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 - Fields []Field `toml:"field"` + // 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 + Gets []Get `toml:"get"` + Walks []Walk `toml:"walk"` + Tags []Tag `toml:"tag"` + + // oidToMib is a map of OIDs to MIBs. + oidToMib map[string]string + // translateBin is the location of the 'snmptranslate' binary. + translateBin string connectionCache map[string]snmpConnection - - inited bool } -// Table holds the configuration for a SNMP table. -type Table struct { - // Name will be the name of the measurement. - Name string - - // Which tags to inherit from the top-level config. - InheritTags []string - - // Fields is the tags and values to look up. - Fields []Field `toml:"field"` -} - -// Field holds the configuration for a Field to look up. -type Field struct { +// Get holds the configuration for a Get to look up. +type Get struct { // Name will be the name of the field. Name string + // OID is prefix for this field. + Oid string +} + +// Walker holds the configuration for a Walker to look up. +type Walk struct { + // Name will be the name of the measurement. + Name string // OID is prefix for this field. The plugin will perform a walk through all // OIDs with this as their parent. For each value found, the plugin will strip // off the OID prefix, and use the remainder as the index. For multiple fields // to show up in the same row, they must share the same index. - Oid string - // IsTag controls whether this OID is output as a tag or a value. - IsTag bool - // Conversion controls any type conversion that is done on the value. - // "float"/"float(0)" will convert the value into a float. - // "float(X)" will convert the value into a float, and then move the decimal before Xth right-most digit. - // "int" will conver the value into an integer. - Conversion string + RootOid string + // Include is a list of glob filters, only OID/MIBs that match these will + // be included in the resulting output metrics. + Include []string + include filter.Filter + // Exclude is the exact opposite of Include + Exclude []string + exclude filter.Filter + // InheritTags is a list of tags to inherit from the top-level snmp.tag + // configuration. + InheritTags []string + inheritTags filter.Filter } -// RTable is the resulting table built from a Table. -type RTable struct { - // Name is the name of the field, copied from Table.Name. +// Tag holds the config for a tag. +type Tag struct { + // Name will be the name of the tag. Name string - // Time is the time the table was built. - Time time.Time - // Rows are the rows that were found, one row for each table OID index found. - Rows []RTableRow -} - -// RTableRow is the resulting row containing all the OID values which shared -// the same index. -type RTableRow struct { - // Tags are all the Field values which had IsTag=true. - Tags map[string]string - // Fields are all the Field values which had IsTag=false. - Fields map[string]interface{} -} - -// Errors is a list of errors accumulated during an interval. -type Errors []error - -func (errs Errors) Error() string { - s := "" - for _, err := range errs { - if s == "" { - s = err.Error() - } else { - s = s + ". " + err.Error() - } - } - return s -} - -// NestedError wraps an error returned from deeper in the code. -type NestedError struct { - // Err is the error from where the NestedError was constructed. - Err error - // NestedError is the error that was passed back from the called function. - NestedErr error -} - -// Error returns a concatenated string of all the nested errors. -func (ne NestedError) Error() string { - return ne.Err.Error() + ": " + ne.NestedErr.Error() -} - -// Errorf is a convenience function for constructing a NestedError. -func Errorf(err error, msg string, format ...interface{}) error { - return NestedError{ - NestedErr: err, - Err: fmt.Errorf(msg, format...), - } + // OID is prefix for this field. + Oid string } // SampleConfig returns the default configuration of the input. @@ -210,258 +176,150 @@ func (s *Snmp) Description() string { // Any error encountered does not halt the process. The errors are accumulated // and returned at the end. func (s *Snmp) Gather(acc telegraf.Accumulator) error { - if !s.inited { - s.initOidNames() - } - s.inited = true - var errs Errors for _, agent := range s.Agents { gs, err := s.getConnection(agent) if err != nil { - errs = append(errs, Errorf(err, "agent %s", agent)) + acc.AddError(fmt.Errorf("Agent %s, err: %s", agent, err)) continue } - // First is the top-level fields. We treat the fields as table prefixes with an empty index. - t := Table{ - Name: s.Name, - Fields: s.Fields, - } topTags := map[string]string{} - if err := s.gatherTable(acc, gs, t, topTags, false); err != nil { - errs = append(errs, Errorf(err, "agent %s", agent)) - } - - // Now is the real tables. - for _, t := range s.Tables { - if err := s.gatherTable(acc, gs, t, topTags, true); err != nil { - errs = append(errs, Errorf(err, "agent %s", agent)) - } - } - } - - if errs == nil { - return nil - } - return errs -} - -// initOidNames loops through each [[inputs.snmp.field]] defined. -// If the field doesn't have a 'name' defined, it will attempt to use -// snmptranslate to get a name for the OID. If snmptranslate doesn't return a -// name, or snmptranslate is not available, then use the OID as the name. -func (s *Snmp) initOidNames() { - bin, _ := exec.LookPath("snmptranslate") - - // Lookup names for each OID defined as a "field" - for i, field := range s.Fields { - if field.Name != "" { - continue - } - s.Fields[i].Name = lookupOidName(bin, field.Oid) - } - - // Lookup names for each OID defined as a "table.field" - for i, table := range s.Tables { - for j, field := range table.Fields { - if field.Name != "" { + // Gather all snmp tags + for _, t := range s.Tags { + tagval, err := get(gs, t.Oid) + if err != nil { + acc.AddError(fmt.Errorf("Agent %s, err: %s", agent, err)) continue } - s.Tables[i].Fields[j].Name = lookupOidName(bin, field.Oid) - } - } -} - -func lookupOidName(bin, oid string) string { - name := oid - if bin != "" { - out, err := internal.CombinedOutputTimeout( - exec.Command(bin, "-Os", oid), - time.Millisecond*250) - if err == nil && len(out) > 0 { - name = strings.TrimSpace(string(out)) - } - } - return name -} - -func (s *Snmp) gatherTable( - acc telegraf.Accumulator, - gs snmpConnection, - t 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 + if tagval == nil { + continue } - } else { - // real table. Inherit any specified tags. - for _, k := range t.InheritTags { - if v, ok := topTags[k]; ok { - tr.Tags[k] = v - } + name := t.Name + if name == "" { + name, _ = s.getMibName(t.Oid) + } + if s, ok := tagval.(string); ok { + topTags[name] = s } } - if _, ok := tr.Tags["agent_host"]; !ok { - tr.Tags["agent_host"] = gs.Host() + + // Gather all snmp gets + fields := map[string]interface{}{} + for _, g := range s.Gets { + val, err := get(gs, g.Oid) + if err != nil { + acc.AddError(fmt.Errorf("Agent %s, err: %s", agent, err)) + continue + } + if val == nil { + continue + } + name := g.Name + if name == "" { + name, _ = s.getMibName(g.Oid) + } + fields[name] = val + } + if len(fields) > 0 { + acc.AddFields(s.Name, fields, topTags, time.Now()) + } + + // Gather all snmp walks + for _, w := range s.Walks { + w.compileWalkFilters(acc) + allfields := map[string]map[string]interface{}{} + now := time.Now() + s.walk(gs, allfields, w.RootOid, w.include, w.exclude) + for index, wfields := range allfields { + tags := copyTags(topTags, w.inheritTags) + tags["index"] = index + acc.AddFields(w.Name, wfields, tags, now) + } } - acc.AddFields(rt.Name, tr.Fields, tr.Tags, rt.Time) } return nil } -// Build retrieves all the fields specified in the table and constructs the RTable. -func (t Table) Build(gs snmpConnection, walk bool) (*RTable, error) { - rows := map[string]RTableRow{} - - tagCount := 0 - for _, f := range t.Fields { - if f.IsTag { - tagCount++ - } - - if len(f.Oid) == 0 { - return nil, fmt.Errorf("cannot have empty OID") - } - var oid string - if f.Oid[0] == '.' { - oid = f.Oid - } else { - // make sure OID has "." because the BulkWalkAll results do, and the prefix needs to match - oid = "." + f.Oid - } - - // ifv contains a mapping of table OID index to field value - ifv := map[string]interface{}{} - if !walk { - // This is used when fetching non-table fields. Fields configured a the top - // scope of the plugin. - // We fetch the fields directly, and add them to ifv as if the index were an - // empty string. This results in all the non-table fields sharing the same - // index, and being added on the same row. - if pkt, err := gs.Get([]string{oid}); err != nil { - return nil, Errorf(err, "performing get") - } else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject { - ent := pkt.Variables[0] - ifv[ent.Name[len(oid):]] = fieldConvert(f.Conversion, ent.Value) - } - } else { - err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error { - if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." { - return NestedError{} // break the walk - } - ifv[ent.Name[len(oid):]] = fieldConvert(f.Conversion, ent.Value) +// walk does a walk and populates the given 'fields' map with whatever it finds. +// as it goes, it attempts to lookup the MIB name of each OID it encounters. +func (s *Snmp) walk( + gs snmpConnection, + fields map[string]map[string]interface{}, + rootOid string, + include filter.Filter, + exclude filter.Filter, +) { + gs.Walk(rootOid, func(ent gosnmp.SnmpPDU) error { + name, index := s.getMibName(ent.Name) + if include != nil { + if !include.Match(name) { + return nil + } + } else if exclude != nil { + if exclude.Match(name) { return nil - }) - if err != nil { - if _, ok := err.(NestedError); !ok { - return nil, Errorf(err, "performing bulk walk") - } } } - - for i, v := range ifv { - rtr, ok := rows[i] - if !ok { - rtr = RTableRow{} - rtr.Tags = map[string]string{} - rtr.Fields = map[string]interface{}{} - rows[i] = rtr - } - if f.IsTag { - if vs, ok := v.(string); ok { - rtr.Tags[f.Name] = vs - } else { - rtr.Tags[f.Name] = fmt.Sprintf("%v", v) - } - } else { - rtr.Fields[f.Name] = v - } - } - } - - rt := RTable{ - Name: t.Name, - Time: time.Now(), //TODO record time at start - Rows: make([]RTableRow, 0, len(rows)), - } - for _, r := range rows { - if len(r.Tags) < tagCount { - // don't add rows which are missing tags, as without tags you can't filter - continue - } - rt.Rows = append(rt.Rows, r) - } - return &rt, nil -} - -// snmpConnection is an interface which wraps a *gosnmp.GoSNMP object. -// We interact through an interface so we can mock it out in tests. -type snmpConnection interface { - Host() string - //BulkWalkAll(string) ([]gosnmp.SnmpPDU, error) - Walk(string, gosnmp.WalkFunc) error - Get(oids []string) (*gosnmp.SnmpPacket, error) -} - -// gosnmpWrapper wraps a *gosnmp.GoSNMP object so we can use it as a snmpConnection. -type gosnmpWrapper struct { - *gosnmp.GoSNMP -} - -// Host returns the value of GoSNMP.Target. -func (gsw gosnmpWrapper) Host() string { - return gsw.Target -} - -// Walk wraps GoSNMP.Walk() or GoSNMP.BulkWalk(), depending on whether the -// connection is using SNMPv1 or newer. -// Also, if any error is encountered, it will just once reconnect and try again. -func (gsw gosnmpWrapper) Walk(oid string, fn gosnmp.WalkFunc) error { - var err error - // On error, retry once. - // Unfortunately we can't distinguish between an error returned by gosnmp, and one returned by the walk function. - for i := 0; i < 2; i++ { - if gsw.Version == gosnmp.Version1 { - err = gsw.GoSNMP.Walk(oid, fn) + if _, ok := fields[index]; ok { + fields[index][name] = normalize(ent.Value) } else { - err = gsw.GoSNMP.BulkWalk(oid, fn) + fields[index] = map[string]interface{}{ + name: normalize(ent.Value), + } } - if err == nil { - return nil - } - if err := gsw.GoSNMP.Connect(); err != nil { - return Errorf(err, "reconnecting") - } - } - return err + return nil + }) } -// Get wraps GoSNMP.GET(). -// If any error is encountered, it will just once reconnect and try again. -func (gsw gosnmpWrapper) Get(oids []string) (*gosnmp.SnmpPacket, error) { - var err error - var pkt *gosnmp.SnmpPacket - for i := 0; i < 2; i++ { - pkt, err = gsw.GoSNMP.Get(oids) - if err == nil { - return pkt, nil - } - if err := gsw.GoSNMP.Connect(); err != nil { - return nil, Errorf(err, "reconnecting") +// get simply gets the given OID and converts it to the given type. +func get(gs snmpConnection, oid string) (interface{}, error) { + pkt, err := gs.Get([]string{oid}) + if err != nil { + return nil, fmt.Errorf("Error performing get: %s", err) + } + if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject { + ent := pkt.Variables[0] + return normalize(ent.Value), nil + } + return nil, nil +} + +func (s *Snmp) getMibName(oid string) (string, string) { + name, ok := s.oidToMib[oid] + if !ok { + // lookup the mib using snmptranslate + name = lookupOidName(s.translateBin, oid) + s.oidToMib[oid] = name + } + return separateIndex(name) +} + +func (w *Walk) compileWalkFilters(acc telegraf.Accumulator) { + // if it's the first gather, compile any inherit_tags filter: + if len(w.InheritTags) > 0 && w.inheritTags == nil { + var err error + w.inheritTags, err = filter.CompileFilter(w.InheritTags) + if err != nil { + acc.AddError(err) + } + } + // if it's the first gather, compile any include filter: + if len(w.Include) > 0 && w.include == nil { + var err error + w.include, err = filter.CompileFilter(w.Include) + if err != nil { + acc.AddError(err) + } + } + // if it's the first gather, compile any exclude filter: + if len(w.Exclude) > 0 && w.exclude == nil { + var err error + w.exclude, err = filter.CompileFilter(w.Exclude) + if err != nil { + acc.AddError(err) } } - return nil, err } // getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the @@ -479,7 +337,7 @@ func (s *Snmp) getConnection(agent string) (snmpConnection, error) { host, portStr, err := net.SplitHostPort(agent) if err != nil { if err, ok := err.(*net.AddrError); !ok || err.Err != "missing port in address" { - return nil, Errorf(err, "parsing host") + return nil, fmt.Errorf("reconnecting %s", err) } host = agent portStr = "161" @@ -488,13 +346,13 @@ func (s *Snmp) getConnection(agent string) (snmpConnection, error) { port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - return nil, Errorf(err, "parsing port") + return nil, fmt.Errorf("reconnecting %s", err) } gs.Port = uint16(port) if s.Timeout != "" { if gs.Timeout, err = time.ParseDuration(s.Timeout); err != nil { - return nil, Errorf(err, "parsing timeout") + return nil, fmt.Errorf("reconnecting %s", err) } } else { gs.Timeout = time.Second * 1 @@ -568,112 +426,77 @@ func (s *Snmp) getConnection(agent string) (snmpConnection, error) { } sp.PrivacyPassphrase = s.PrivPassword - - sp.AuthoritativeEngineID = s.EngineID - - sp.AuthoritativeEngineBoots = s.EngineBoots - - sp.AuthoritativeEngineTime = s.EngineTime } if err := gs.Connect(); err != nil { - return nil, Errorf(err, "setting up connection") + return nil, fmt.Errorf("setting up connection %s", err) } s.connectionCache[agent] = gs return gs, nil } -// fieldConvert converts from any type according to the conv specification -// "float"/"float(0)" will convert the value into a float. -// "float(X)" will convert the value into a float, and then move the decimal before Xth right-most digit. -// "int" will convert the value into an integer. -// "" will convert a byte slice into a string. -// Any other conv will return the input value unchanged. -func fieldConvert(conv string, v interface{}) interface{} { - if conv == "" { - if bs, ok := v.([]byte); ok { - return string(bs) - } - return v - } - - var d int - if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" { - switch vt := v.(type) { - case float32: - v = float64(vt) / math.Pow10(d) - case float64: - v = float64(vt) / math.Pow10(d) - case int: - v = float64(vt) / math.Pow10(d) - case int8: - v = float64(vt) / math.Pow10(d) - case int16: - v = float64(vt) / math.Pow10(d) - case int32: - v = float64(vt) / math.Pow10(d) - case int64: - v = float64(vt) / math.Pow10(d) - case uint: - v = float64(vt) / math.Pow10(d) - case uint8: - v = float64(vt) / math.Pow10(d) - case uint16: - v = float64(vt) / math.Pow10(d) - case uint32: - v = float64(vt) / math.Pow10(d) - case uint64: - v = float64(vt) / math.Pow10(d) - case []byte: - vf, _ := strconv.ParseFloat(string(vt), 64) - v = vf / math.Pow10(d) - case string: - vf, _ := strconv.ParseFloat(vt, 64) - v = vf / math.Pow10(d) - } - } - if conv == "int" { - switch vt := v.(type) { - case float32: - v = int64(vt) - case float64: - v = int64(vt) - case int: - v = int64(vt) - case int8: - v = int64(vt) - case int16: - v = int64(vt) - case int32: - v = int64(vt) - case int64: - v = int64(vt) - case uint: - v = int64(vt) - case uint8: - v = int64(vt) - case uint16: - v = int64(vt) - case uint32: - v = int64(vt) - case uint64: - v = int64(vt) - case []byte: - v, _ = strconv.Atoi(string(vt)) - case string: - v, _ = strconv.Atoi(vt) - } +// normalize normalizes the given interface for metric storage. +func normalize(v interface{}) interface{} { + switch vt := v.(type) { + case []byte: + v = string(vt) } return v } +func copyTags(in map[string]string, inheritTags filter.Filter) map[string]string { + out := map[string]string{} + for k, v := range in { + if inheritTags != nil { + if !inheritTags.Match(k) { + continue + } + } + out[k] = v + } + return out +} + +// lookupOidName looks up the MIB name of the given OID using the provided +// snmptranslate binary. If a name is not found, then we just return the OID. +func lookupOidName(bin, oid string) string { + name := oid + if bin != "" { + out, err := internal.CombinedOutputTimeout( + exec.Command(bin, "-Os", oid), + time.Millisecond*250) + if err == nil && len(out) > 0 { + name = strings.TrimSpace(string(out)) + } + } + return name +} + +// separateIndex takes an input string (either a MIB or an OID) and separates +// out the index from it, ie: +// ifName.1 -> (ifName, 1) +// snmpInPkts.0 -> (snmpInPkts, 0) +// .1.3.6.4.2.0 -> (.1.3.6.4.2, 0) +func separateIndex(in string) (string, string) { + items := strings.Split(in, ".") + if len(items) == 1 { + return in, "0" + } + index := items[len(items)-1] + return strings.Join(items[0:len(items)-1], "."), index +} + func init() { + bin, _ := exec.LookPath("snmptranslate") inputs.Add("snmp", func() telegraf.Input { return &Snmp{ + Name: "snmp", Retries: 5, MaxRepetitions: 50, + translateBin: bin, + oidToMib: make(map[string]string), } }) }