Repository: kentik/ktranslate
Pull request #271 (2022-02-16 22:22:09)

Basic conversion to handle string valued metrics: #252
Is this too basic to handle this case? What I've done is added a conversion called `to_one`. For any metric, this will convert the metric to a gaudge with a value of 1. The string value will also be captured as an attribute. It will get polled every $TICK instead of every 3 hours. For example, consider a metric with 

```
      - symbols:
          OID: 1.0.8802.1.1.2.1.4.1.1.7
          name: lldpRemPortId
          conversion: to_one 
```

This will turn into a metric `snmp.lldpRemPortId` which will always equal one. It will also have an attribute called `lldpRemPortId` with the last polled value. 

Thoughts? 



Patch:
diff --git a/pkg/formats/elasticsearch/elasticsearch.go b/pkg/formats/elasticsearch/elasticsearch.go
index 312033c..2a9279d 100644
--- a/pkg/formats/elasticsearch/elasticsearch.go
+++ b/pkg/formats/elasticsearch/elasticsearch.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 	"strings"
+	"time"
 
 	"github.com/kentik/ktranslate/pkg/formats/util"
 	"github.com/kentik/ktranslate/pkg/kt"
@@ -142,8 +143,8 @@ func (f *ElasticsearchFormat) Rollup(rolls []rollup.Rollup) (*kt.Output, error)
 func (f *ElasticsearchFormat) handleSynth(in *kt.JCHF) map[string]interface{} {
 	metrics := util.GetSynMetricNameSet(in.CustomInt["result_type"])
 	attr := in.Flatten()
-	strip(attr)
 
+	// Map metrics to better names.
 	for m, name := range metrics {
 		if _, ok := attr[m]; ok {
 			attr[name.Name] = attr[m]
@@ -160,6 +161,8 @@ func (f *ElasticsearchFormat) handleSynth(in *kt.JCHF) map[string]interface{} {
 		}
 	}
 
+	strip(attr) // Take out the rest here.
+
 	return attr
 }
 
@@ -187,12 +190,28 @@ var (
 		"trf_termination":     true,
 		"simple_trf_prof":     true,
 	}
+
+	KeepAttrs = map[string]bool{
+		"lost": true,
+		"sent": true,
+	}
 )
 
 func strip(in map[string]interface{}) {
 	for k, v := range in {
 		if DroppedAttrs[k] {
 			delete(in, k) // Skip.
+			continue
+		}
+		if KeepAttrs[k] {
+			continue // Always pass along even if 0.
+		}
+		if k == "timestamp" { // Convert to the ES native timestamp format.
+			if ints, ok := v.(int64); ok {
+				ts := time.Unix(ints, 0)
+				in["timestamp_str"] = ts.Format(time.RFC3339)
+			}
+			continue
 		}
 		switch tv := v.(type) {
 		case string:
diff --git a/pkg/inputs/snmp/util/util.go b/pkg/inputs/snmp/util/util.go
index a7a288b..d388da6 100644
--- a/pkg/inputs/snmp/util/util.go
+++ b/pkg/inputs/snmp/util/util.go
@@ -26,6 +26,7 @@ const (
 	CONV_HEXTOIP   = "hextoip"
 	CONV_ENGINE_ID = "engine_id"
 	CONV_REGEXP    = "regexp"
+	CONV_ONE       = "to_one"
 )
 
 var (
@@ -250,6 +251,8 @@ func GetFromConv(pdu gosnmp.SnmpPDU, conv string, log logger.ContextL) (int64, s
 		return hexToIP(bv)
 	case CONV_ENGINE_ID:
 		return engineID(bv)
+	case CONV_ONE:
+		return toOne(bv)
 	default:
 		// Otherwise, try out some custom conversions.
 		split := strings.Split(conv, ":")
@@ -360,3 +363,8 @@ func fromRegexp(bv []byte, reg string) (int64, string) {
 	}
 	return int64(ival), string(res[1]) // Parsed as int but return both just in case.
 }
+
+// This one is used for certain string valued oids which we want to poll as metrics. Just converts to 1.
+func toOne(bv []byte) (int64, string) {
+	return 1, string(bv)
+}


Test patch:
diff --git a/pkg/inputs/snmp/util/util_test.go b/pkg/inputs/snmp/util/util_test.go
index f104c28..88fc7eb 100644
--- a/pkg/inputs/snmp/util/util_test.go
+++ b/pkg/inputs/snmp/util/util_test.go
@@ -191,3 +191,19 @@ func TestRegex(t *testing.T) {
 		assert.Equal(expt, ival, "%s", in)
 	}
 }
+
+func TestToOne(t *testing.T) {
+	assert := assert.New(t)
+	l := lt.NewTestContextL(logger.NilContext, t)
+
+	tests := map[string]int64{
+		"lalalalaal": 1,
+		"foofoffofo": 1,
+	}
+
+	for in, expt := range tests {
+		pdu := gosnmp.SnmpPDU{Value: []byte(in)}
+		ival, _ := GetFromConv(pdu, CONV_ONE, l)
+		assert.Equal(expt, ival, "%s", in)
+	}
+}