Repository: kentik/ktranslate
Pull request #588 (2023-06-16 20:49:08)

Merge Kentik Branch Into Main
Look at merging the Kentik branch into the main section. 


Patch:
diff --git a/.github/workflows/publish-kentik.yml b/.github/workflows/publish-kentik.yml
index 6529fa6..3665fe3 100644
--- a/.github/workflows/publish-kentik.yml
+++ b/.github/workflows/publish-kentik.yml
@@ -27,7 +27,7 @@ jobs:
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
-          go-version: '1.20.4'
+          go-version: '1.20.5'
 
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v1
diff --git a/go.mod b/go.mod
index c1dd266..2dc6aeb 100644
--- a/go.mod
+++ b/go.mod
@@ -60,7 +60,6 @@ require (
 	github.com/PuerkitoBio/purell v1.1.1 // indirect
 	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
-	github.com/aws/aws-sdk-go-v2 v1.17.6 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/pkg/inputs/snmp/metrics/device_metrics.go b/pkg/inputs/snmp/metrics/device_metrics.go
index db3c22e..d5d280a 100644
--- a/pkg/inputs/snmp/metrics/device_metrics.go
+++ b/pkg/inputs/snmp/metrics/device_metrics.go
@@ -28,6 +28,7 @@ type DeviceMetrics struct {
 	conf        *kt.SnmpDeviceConfig
 	gconf       *kt.SnmpGlobalConfig
 	metrics     *kt.SnmpDeviceMetric
+	profile     *mibs.Profile
 	profileName string
 	oids        map[string]*kt.Mib
 	missing     map[string]bool
@@ -56,6 +57,7 @@ func NewDeviceMetrics(gconf *kt.SnmpGlobalConfig, conf *kt.SnmpDeviceConfig, met
 		log:         log,
 		conf:        conf,
 		metrics:     metrics,
+		profile:     profile, // Danger, profile can be nil.
 		profileName: profile.GetProfileName(conf.InstrumentationName),
 		oids:        oidMap,
 		missing:     map[string]bool{},
@@ -152,7 +154,8 @@ func (dm *DeviceMetrics) pollFromConfig(ctx context.Context, server *gosnmp.GoSN
 	}
 
 	// Map back into types we know about.
-	metricsFound := map[string]kt.MetricInfo{"Uptime": kt.MetricInfo{Oid: sysUpTime, Profile: dm.profileName}}
+	mib, _ := dm.profile.GetMibAndOid()
+	metricsFound := map[string]kt.MetricInfo{"Uptime": kt.MetricInfo{Oid: sysUpTime, Mib: mib, Profile: dm.profileName}}
 	for _, wrapper := range results {
 		if wrapper.variable.Value == nil { // You can get nil w/out getting an error, though.
 			continue
@@ -352,6 +355,8 @@ func assureDeviceMetrics(m map[string]*deviceMetricRow, index string) *deviceMet
 
 // Return a flow with status of SNMP, reguardless of if the rest of the system is working.
 func (dm *DeviceMetrics) GetStatusFlows() []*kt.JCHF {
+	mib, oid := dm.profile.GetMibAndOid()
+
 	dst := kt.NewJCHF()
 	dst.CustomStr = map[string]string{}
 	dst.CustomInt = map[string]int32{}
@@ -361,13 +366,26 @@ func (dm *DeviceMetrics) GetStatusFlows() []*kt.JCHF {
 	dst.DeviceName = dm.conf.DeviceName
 	dst.SrcAddr = dm.conf.DeviceIP
 	dst.Timestamp = time.Now().Unix()
-	dst.CustomMetrics = map[string]kt.MetricInfo{"PollingHealth": kt.MetricInfo{Oid: "computed", Mib: "computed", Profile: dm.profileName}}
+	dst.CustomMetrics = map[string]kt.MetricInfo{
+		"PollingHealth": {Oid: oid, Mib: mib, Profile: dm.profileName},
+		"PollingStatus": {Oid: oid, Mib: mib, Profile: dm.profileName},
+	}
 	dm.conf.SetUserTags(dst.CustomStr)
 	if dst.Provider == kt.ProviderDefault { // Add this to trigger a UI element.
 		dst.CustomStr["profile_message"] = kt.DefaultProfileMessage
 	}
-	dst.CustomBigInt["PollingHealth"] = dm.metrics.Fail.Value()
-	reasonVal := kt.SNMP_STATUS_MAP[dst.CustomBigInt["PollingHealth"]]
+
+	pollingHealth := dm.metrics.Fail.Value()
+	pollingStatus := int64(0)
+
+	if pollingHealth == kt.SNMP_GOOD {
+		pollingStatus = 1
+	}
+
+	dst.CustomBigInt["PollingHealth"] = pollingHealth
+	dst.CustomBigInt["PollingStatus"] = pollingStatus
+
+	reasonVal := kt.SNMP_STATUS_MAP[pollingHealth]
 	pts := strings.Split(reasonVal, ": ")
 	if len(pts) == 2 {
 		dst.CustomStr[kt.StringPrefix+"PollingHealth"] = pts[0]
@@ -375,6 +393,7 @@ func (dm *DeviceMetrics) GetStatusFlows() []*kt.JCHF {
 	} else {
 		dst.CustomStr[kt.StringPrefix+"PollingHealth"] = reasonVal
 	}
+
 	return []*kt.JCHF{dst}
 }
 
@@ -387,6 +406,7 @@ func (dm *DeviceMetrics) GetPingStats(ctx context.Context, pinger *ping.Pinger)
 		return nil, nil
 	}
 
+	mib, oid := dm.profile.GetMibAndOid()
 	stats := pinger.Statistics()
 	dst := kt.NewJCHF()
 	dst.CustomStr = map[string]string{}
@@ -399,13 +419,13 @@ func (dm *DeviceMetrics) GetPingStats(ctx context.Context, pinger *ping.Pinger)
 	dst.Timestamp = time.Now().Unix()
 	dst.CustomMetrics = map[string]kt.MetricInfo{}
 	dst.CustomBigInt["MinRttMs"] = stats.MinRtt.Microseconds()
-	dst.CustomMetrics["MinRttMs"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Format: kt.FloatMS, Profile: "ping", Type: "ping"}
+	dst.CustomMetrics["MinRttMs"] = kt.MetricInfo{Oid: oid, Mib: mib, Format: kt.FloatMS, Profile: "ping", Type: "ping"}
 	dst.CustomBigInt["MaxRttMs"] = stats.MaxRtt.Microseconds()
-	dst.CustomMetrics["MaxRttMs"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Format: kt.FloatMS, Profile: "ping", Type: "ping"}
+	dst.CustomMetrics["MaxRttMs"] = kt.MetricInfo{Oid: oid, Mib: mib, Format: kt.FloatMS, Profile: "ping", Type: "ping"}
 	dst.CustomBigInt["AvgRttMs"] = stats.AvgRtt.Microseconds()
-	dst.CustomMetrics["AvgRttMs"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Format: kt.FloatMS, Profile: "ping", Type: "ping"}
+	dst.CustomMetrics["AvgRttMs"] = kt.MetricInfo{Oid: oid, Mib: mib, Format: kt.FloatMS, Profile: "ping", Type: "ping"}
 	dst.CustomBigInt["StdDevRtt"] = stats.StdDevRtt.Microseconds()
-	dst.CustomMetrics["StdDevRtt"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Format: kt.FloatMS, Profile: "ping", Type: "ping"}
+	dst.CustomMetrics["StdDevRtt"] = kt.MetricInfo{Oid: oid, Mib: mib, Format: kt.FloatMS, Profile: "ping", Type: "ping"}
 
 	// Calc these directly
 	sent := uint64(stats.PacketsSent)
@@ -422,12 +442,12 @@ func (dm *DeviceMetrics) GetPingStats(ctx context.Context, pinger *ping.Pinger)
 	}
 
 	dst.CustomBigInt["PacketsSent"] = int64(diffSent)
-	dst.CustomMetrics["PacketsSent"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Profile: "ping", Type: "ping"}
+	dst.CustomMetrics["PacketsSent"] = kt.MetricInfo{Oid: oid, Mib: mib, Profile: "ping", Type: "ping"}
 	dst.CustomBigInt["PacketsRecv"] = int64(diffRecv)
-	dst.CustomMetrics["PacketsRecv"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Profile: "ping", Type: "ping"}
+	dst.CustomMetrics["PacketsRecv"] = kt.MetricInfo{Oid: oid, Mib: mib, Profile: "ping", Type: "ping"}
 	if percnt >= 0.0 {
 		dst.CustomBigInt["PacketLossPct"] = int64(percnt * 1000.)
-		dst.CustomMetrics["PacketLossPct"] = kt.MetricInfo{Oid: "computed", Mib: "computed", Format: kt.FloatMS, Profile: "ping", Type: "ping"}
+		dst.CustomMetrics["PacketLossPct"] = kt.MetricInfo{Oid: oid, Mib: mib, Format: kt.FloatMS, Profile: "ping", Type: "ping"}
 
 		// If percent ~ 100, push rtt down to 0 to avoid bad readings.
 		if math.Abs(percnt-99.) <= 1 {
diff --git a/pkg/inputs/snmp/metrics/interface_metrics.go b/pkg/inputs/snmp/metrics/interface_metrics.go
index 0e7c4e3..f10d76e 100644
--- a/pkg/inputs/snmp/metrics/interface_metrics.go
+++ b/pkg/inputs/snmp/metrics/interface_metrics.go
@@ -149,11 +149,12 @@ func (im *InterfaceMetrics) Poll(ctx context.Context, server *gosnmp.GoSNMP, las
 			} else {
 				switch variable.Type {
 				case gosnmp.Integer:
-					// Since its just an int, keep it without computing a delta.
+					// Since it's just an int, keep it without computing a delta.
 					delta[varName] = value
 				default:
 					// Treat as a counter
-					// Calculate the different of this counter here.
+					// Calculate the difference of this counter here.
+					delta[varName+"_counter"] = value
 					delta[varName] = im.intValues[intId].SetValueAndReturnDelta(varName, value)
 				}
 			}
@@ -162,7 +163,7 @@ func (im *InterfaceMetrics) Poll(ctx context.Context, server *gosnmp.GoSNMP, las
 
 	im.log.Infof("SNMP interface metric poll - found metrics for %d interfaces.", len(deltas))
 
-	// See if we have a uptime delta to work with
+	// See if we have an uptime delta to work with
 	for _, dm := range lastDeviceMetrics {
 		intId := AllDeviceInterface
 		if _, ok := im.intValues[intId]; !ok {
@@ -214,9 +215,14 @@ func (im *InterfaceMetrics) convertToCHF(deltas map[string]map[string]uint64) []
 			dst.CustomStr["profile_message"] = kt.DefaultProfileMessage
 		}
 
+		baseMetricName := func(n string) string {
+			return strings.TrimSuffix(n, "_counter")
+		}
+
 		metrics := map[string]kt.MetricInfo{}
 		for k, v := range cs {
-			mib := im.oidMibMap[im.nameOidMap[k][1:]]
+			oid := im.nameOidMap[baseMetricName(k)]
+			mib := im.oidMibMap[oid[1:]]
 			if mib != nil && mib.EnumRev != nil {
 				if ev, ok := mib.EnumRev[int64(v)]; ok {
 					// If we know what the enum string is, set it here.
@@ -232,21 +238,21 @@ func (im *InterfaceMetrics) convertToCHF(deltas map[string]map[string]uint64) []
 				if mib.Format != "" {
 					format = mib.Format
 				}
-				metrics[k] = kt.MetricInfo{Oid: im.nameOidMap[k], Mib: mib.Mib, Profile: im.profileName, Table: mib.Table, Format: format, PollDur: mib.PollDur}
+				metrics[k] = kt.MetricInfo{Oid: oid, Mib: mib.Mib, Profile: im.profileName, Table: mib.Table, Format: format, PollDur: mib.PollDur}
 			} else {
-				metrics[k] = kt.MetricInfo{Oid: im.nameOidMap[k], Profile: im.profileName, Format: kt.CountMetric}
+				metrics[k] = kt.MetricInfo{Oid: oid, Profile: im.profileName, Format: kt.CountMetric}
 			}
 		}
 
-		// If there's a script attatched here, run it now.
-		for k, _ := range cs {
-			mib := im.oidMibMap[im.nameOidMap[k][1:]]
+		// If there's a script attached here, run it now.
+		for k := range cs {
+			mib := im.oidMibMap[im.nameOidMap[baseMetricName(k)][1:]]
 			if mib != nil && mib.Script != nil {
 				mib.Script.EnrichMetric(strint, k, dst.CustomBigInt, dst.CustomStr, metrics)
 			}
 		}
 
-		// Drop in Error %s here if appicable.
+		// Drop in Error %s here if applicable.
 		if dst.CustomBigInt[SNMP_ifHCInUcastPkts] > 0 {
 			if idi, ok := im.nameOidMap[SNMP_ifInErrors]; ok {
 				if mib, ok := im.oidMibMap[idi[1:]]; ok {
diff --git a/pkg/inputs/snmp/metrics/poll.go b/pkg/inputs/snmp/metrics/poll.go
index 9a7d8d2..6c9cb83 100644
--- a/pkg/inputs/snmp/metrics/poll.go
+++ b/pkg/inputs/snmp/metrics/poll.go
@@ -211,7 +211,9 @@ func (p *Poller) StartLoop(ctx context.Context) {
 					continue
 				}
 
-				flows, err := p.Poll(ctx)
+				pollCtx, pollCancel := context.WithTimeout(ctx, STATUS_CHECK_TIME)
+				flows, err := p.Poll(pollCtx)
+				pollCancel()
 				if err != nil {
 					p.log.Warnf("There was an error when polling the SNMP counter: %v.", err)
 
@@ -238,7 +240,7 @@ func (p *Poller) StartLoop(ctx context.Context) {
 				// Great!  We finished the poll in the same block we started it in!
 				p.jchfChan <- flows
 
-			case _ = <-statusCheck.C: // Send in on a seperate timer status about how this system is working.
+			case <-statusCheck.C: // Send in on a seperate timer status about how this system is working.
 				p.jchfChan <- p.deviceMetrics.GetStatusFlows()
 
 			case <-ctx.Done():
@@ -262,12 +264,12 @@ func (p *Poller) Poll(ctx context.Context) ([]*kt.JCHF, error) {
 	deviceFlows, err := p.deviceMetrics.Poll(ctx, p.server, p.pinger)
 	if err != nil {
 		p.log.Warnf("Cannot poll device metrics: %v", err)
-		p.metrics.Fail.Update(kt.SNMP_BAD_DEVICE_ERR)
+		p.metrics.Fail.Update(kt.SNMP_BAD_POLL_TIMEOUT)
 	}
 
 	flows, err := p.interfaceMetrics.Poll(ctx, p.server, deviceFlows)
 	if err != nil {
-		p.metrics.Fail.Update(kt.SNMP_BAD_INTERFACE_ERR)
+		p.metrics.Fail.Update(kt.SNMP_BAD_POLL_TIMEOUT)
 		return nil, err
 	}
 
@@ -278,7 +280,7 @@ func (p *Poller) Poll(ctx context.Context) ([]*kt.JCHF, error) {
 	if len(flows) > 0 {
 		p.metrics.Fail.Update(kt.SNMP_GOOD)
 	} else {
-		p.metrics.Fail.Update(kt.SNMP_BAD_NO_DATA) // Otherwise, set to bad because there's no data coming out of this device.
+		p.metrics.Fail.Update(kt.SNMP_BAD_POLL_TIMEOUT) // Otherwise, set to bad because there's no data coming out of this device.
 	}
 
 	return flows, nil
diff --git a/pkg/inputs/snmp/mibs/profile.go b/pkg/inputs/snmp/mibs/profile.go
index be85247..11d89cd 100644
--- a/pkg/inputs/snmp/mibs/profile.go
+++ b/pkg/inputs/snmp/mibs/profile.go
@@ -76,12 +76,40 @@ type Profile struct {
 	MatchesList      []Match           `yaml:"matches_list"`
 	SysMap           map[string]string
 	extended         bool
+	cMib             string
+	cOid             string
 }
 
 var (
 	commentRE = regexp.MustCompile(`\- (.*?)\#(.*)$`)
 )
 
+func (p *Profile) GetMibAndOid() (mib string, oid string) {
+	oid = "computed"
+	mib = "computed"
+	if p == nil {
+		return
+	}
+
+	if p.cMib != "" { // Cache if possible.
+		oid = p.cOid
+		mib = p.cMib
+		return
+	}
+
+	for _, m := range p.Metrics {
+		if m.Table.Oid == ".1.2.3.3" {
+			mib = m.Table.Name
+			oid = m.Table.Oid
+			p.cOid = oid
+			p.cMib = mib
+			return
+		}
+	}
+
+	return
+}
+
 func (p *Profile) GetProfileName(override string) string {
 	if override != "" { // This can override what is set as the profile name.
 		return override
diff --git a/pkg/inputs/snmp/util/util.go b/pkg/inputs/snmp/util/util.go
index ec9cfcc..37a97aa 100644
--- a/pkg/inputs/snmp/util/util.go
+++ b/pkg/inputs/snmp/util/util.go
@@ -113,6 +113,10 @@ func GetIndex(value, prefix string) string {
 // walk the OID subtree under a root, returning a slice of varbinds
 func WalkOID(ctx context.Context, device *kt.SnmpDeviceConfig, oid string, server *gosnmp.GoSNMP, log logger.ContextL, logName string) ([]gosnmp.SnmpPDU, error) {
 
+	if ctx.Err() != nil {
+		return nil, ctx.Err()
+	}
+
 	// New strategy -- for each varbind, we'll try three times:
 	//   first, with GetBulk
 	//   if that fails, try GetNext without sleeping in-between
@@ -150,7 +154,12 @@ func WalkOID(ctx context.Context, device *kt.SnmpDeviceConfig, oid string, serve
 	var err error
 	var results []gosnmp.SnmpPDU
 	for i, try := range tries {
-		time.Sleep(try.sleep)
+		select {
+		case <-ctx.Done():
+			log.Warnf("Context canceled in the %s walking OID %s after %d retries: %v.", logName, oid, i, err)
+			return nil, ctx.Err()
+		case <-time.After(try.sleep):
+		}
 
 		results, err = try.walk(oid)
 		if err == nil {


Test patch:
diff --git a/pkg/inputs/snmp/mibs/profile_test.go b/pkg/inputs/snmp/mibs/profile_test.go
index d17111b..0373ce3 100644
--- a/pkg/inputs/snmp/mibs/profile_test.go
+++ b/pkg/inputs/snmp/mibs/profile_test.go
@@ -129,6 +129,29 @@ func TestFindProfile(t *testing.T) {
 	}
 }
 
+func TestGetMibAndOid(t *testing.T) {
+	fooPro := Profile{Metrics: []MIB{MIB{Table: OID{Oid: ".1.2.3.3", Name: "foo"}}}}
+
+	tests := map[string][]*Profile{
+		"foo": []*Profile{
+			&Profile{Metrics: []MIB{MIB{Table: OID{Oid: ".1.2.3.3", Name: "foo"}}}},
+			&fooPro,
+			&fooPro, // Caching.
+			&fooPro,
+		},
+		"computed": []*Profile{
+			nil, // Nil case.
+			&Profile{Metrics: []MIB{MIB{Table: OID{Oid: ".1.2.3.3.1", Name: "foo"}}}}}, // Not found case.
+	}
+
+	for expected, profiles := range tests {
+		for _, profile := range profiles {
+			mib, _ := profile.GetMibAndOid()
+			assert.Equal(t, expected, mib)
+		}
+	}
+}
+
 func TestPrune(t *testing.T) {
 	mibs := map[string]*kt.Mib{
 		".1.3.6.1.4.1.9.9.48.1.1.1.5": &kt.Mib{Tag: "MemoryUsed"},