Repository: prometheus/snmp_exporter (go)

Issue:
Handle BITS textual conversion
The [ServerTech Sentry 4](https://cdn10.servertech.com/assets/documents/documents/815/original/Sentry4.mib) uses a textual conversion of a `BITS` type. This looks like it's used as an enum of sort.

For example:
```
    EventNotificationMethods ::= TEXTUAL-CONVENTION
        STATUS      current
        DESCRIPTION
            "Bits to enable event notification methods."
        SYNTAX      BITS {
                        snmpTrap(0),     -- enables notifications by SNMP traps
                        email(1)         -- enables notifications by Email
                    }

    st4UnitNotifications OBJECT-TYPE
        SYNTAX      EventNotificationMethods
        MAX-ACCESS  read-write
        STATUS      current
        DESCRIPTION
            "The notification methods enabled for unit events."
        ::= { st4UnitEventConfigEntry 1 }
```

Another `BITS` example is status enum:

```
    st4UnitCapabilities OBJECT-TYPE
        SYNTAX      BITS {
                        dc(0),         -- unit is DC
                        phase3(1),     -- unit is 3 phase
                        wye(2),        -- unit has Wye circuits
                        delta(3)       -- unit has Delta circuits
                    }
        MAX-ACCESS  read-only
        STATUS      current
        DESCRIPTION
            "The capabilities of the unit."
        ::= { st4UnitConfigEntry 8 }
```

It looks like part of the problem is with `BITS`, we turn them into OctetStrings by default, and they turn into invalid UTF8.

```yaml
modules:
  servertech_sentry4:
    walk:
      - st4UnitCapabilities
```

```yaml
servertech_sentry4:
  walk:
  - 1.3.6.1.4.1.1718.4.1.2.2.1.8
  metrics:
  - name: st4UnitCapabilities
    oid: 1.3.6.1.4.1.1718.4.1.2.2.1.8
    type: OctetString
    help: The capabilities of the unit. - 1.3.6.1.4.1.1718.4.1.2.2.1.8
    indexes:
    - labelname: st4UnitIndex
      type: gauge
    enum_values:
      0: dc
      1: phase3
      2: wye
      3: delta
```

Pull request description:
Support BITS values.
Fixes #443

@SuperQ 

Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>

Patch:
diff --git a/collector.go b/collector.go
index 22e4f9b..3a41979 100644
--- a/collector.go
+++ b/collector.go
@@ -353,6 +353,8 @@ func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, o
 		return enumAsInfo(metric, int(value), labelnames, labelvalues)
 	case "EnumAsStateSet":
 		return enumAsStateSet(metric, int(value), labelnames, labelvalues)
+	case "Bits":
+		return bits(metric, pdu.Value, labelnames, labelvalues)
 	default:
 		// It's some form of string.
 		t = prometheus.GaugeValue
@@ -478,6 +480,34 @@ func enumAsStateSet(metric *config.Metric, value int, labelnames, labelvalues []
 	return results
 }
 
+func bits(metric *config.Metric, value interface{}, labelnames, labelvalues []string) []prometheus.Metric {
+	bytes, ok := value.([]byte)
+	if !ok {
+		return []prometheus.Metric{prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "BITS type was not a BISTRING on the wire.", nil, nil),
+			fmt.Errorf("error for metric %s with labels %v: %T", metric.Name, labelvalues, value))}
+	}
+	labelnames = append(labelnames, metric.Name)
+	results := []prometheus.Metric{}
+
+	for k, v := range metric.EnumValues {
+		bit := 0.0
+		// Most significant byte most significant bit, then most significant byte 2nd most significant bit etc.
+		if k < len(bytes)*8 {
+			if (bytes[k/8] & (128 >> (k % 8))) != 0 {
+				bit = 1.0
+			}
+		}
+		newMetric, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name, metric.Help+" (Bits)", labelnames, nil),
+			prometheus.GaugeValue, bit, append(labelvalues, v)...)
+		if err != nil {
+			newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for Bits", nil, nil),
+				fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err))
+		}
+		results = append(results, newMetric)
+	}
+	return results
+}
+
 // Right pad oid with zeros, and split at the given point.
 // Some routers exclude trailing 0s in responses.
 func splitOid(oid []int, count int) ([]int, []int) {
diff --git a/generator/README.md b/generator/README.md
index 2ef6b97..b51665e 100644
--- a/generator/README.md
+++ b/generator/README.md
@@ -127,6 +127,7 @@ modules:
                              #       not having the size in the index. Must be preceded by an InetAddressType.
                              #   EnumAsInfo: An enum for which a single timeseries is created. Good for constant values.
                              #   EnumAsStateSet: An enum with a time series per state. Good for variable low-cardinality enums.
+                             #   Bits: An RFC 2578 BITS construct, which produces a StateSet with a time series per bit.
 ```
 
 ### EnumAsInfo and EnumAsStateSet
diff --git a/generator/tree.go b/generator/tree.go
index 63c7f54..0f62490 100644
--- a/generator/tree.go
+++ b/generator/tree.go
@@ -157,8 +157,10 @@ func metricType(t string) (string, bool) {
 		return "gauge", true
 	case "counter", "COUNTER", "COUNTER64":
 		return "counter", true
-	case "OctetString", "OCTETSTR", "BITSTRING":
+	case "OctetString", "OCTETSTR":
 		return "OctetString", true
+	case "BITSTRING":
+		return "Bits", true
 	case "InetAddressIPv4", "IpAddr", "IPADDR", "NETADDR":
 		return "InetAddressIPv4", true
 	case "PhysAddress48", "DisplayString", "Float", "Double", "InetAddressIPv6":
diff --git a/snmp.yml b/snmp.yml
index 24de9d3..b2e8999 100644
--- a/snmp.yml
+++ b/snmp.yml
@@ -11669,7 +11669,7 @@ servertech_sentry3:
       type: gauge
   - name: infeedCapabilities
     oid: 1.3.6.1.4.1.1718.3.2.2.1.4
-    type: OctetString
+    type: Bits
     help: The capabilities of the input feed. - 1.3.6.1.4.1.1718.3.2.2.1.4
     indexes:
     - labelname: towerIndex
@@ -11918,7 +11918,7 @@ servertech_sentry3:
       type: gauge
   - name: outletCapabilities
     oid: 1.3.6.1.4.1.1718.3.2.3.1.4
-    type: OctetString
+    type: Bits
     help: The capabilities of the outlet. - 1.3.6.1.4.1.1718.3.2.3.1.4
     indexes:
     - labelname: towerIndex


Test patch:
diff --git a/collector_test.go b/collector_test.go
index aed876c..6130342 100644
--- a/collector_test.go
+++ b/collector_test.go
@@ -34,7 +34,7 @@ func TestPduToSample(t *testing.T) {
 		indexOids       []int
 		metric          *config.Metric
 		oidToPdu        map[string]gosnmp.SnmpPDU
-		expectedMetrics map[string]string
+		expectedMetrics []string
 		shouldErr       bool
 	}{
 		{
@@ -59,8 +59,8 @@ func TestPduToSample(t *testing.T) {
 				},
 			},
 			oidToPdu: make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{
-				`gauge:<value:5 > `: `Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
+			expectedMetrics: []string{
+				`Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge:<value:5 > `,
 			},
 		},
 		{
@@ -84,7 +84,7 @@ func TestPduToSample(t *testing.T) {
 					},
 				},
 			},
-			expectedMetrics: map[string]string{},
+			expectedMetrics: []string{},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -107,7 +107,7 @@ func TestPduToSample(t *testing.T) {
 					},
 				},
 			},
-			expectedMetrics: map[string]string{},
+			expectedMetrics: []string{},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -130,8 +130,8 @@ func TestPduToSample(t *testing.T) {
 					},
 				},
 			},
-			expectedMetrics: map[string]string{
-				`gauge:<value:5 > `: `Desc{fqName: "TestMetricNameStatus", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
+			expectedMetrics: []string{
+				`Desc{fqName: "TestMetricNameStatus", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge:<value:5 > `,
 			},
 		},
 		{
@@ -192,10 +192,10 @@ func TestPduToSample(t *testing.T) {
 				},
 			},
 			oidToPdu: make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{
-				`gauge:<value:5 > `:    `Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
-				`gauge:<value:999 > `:  `Desc{fqName: "TestMetricNameMultipleRegexes", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
-				`gauge:<value:4.42 > `: `Desc{fqName: "TestMetricNameTemplate", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []}`,
+			expectedMetrics: []string{
+				`Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge:<value:5 > `,
+				`Desc{fqName: "TestMetricNameMultipleRegexes", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge:<value:999 > `,
+				`Desc{fqName: "TestMetricNameTemplate", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge:<value:4.42 > `,
 			},
 		},
 		{
@@ -212,7 +212,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{"counter:<value:2 > ": `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} counter:<value:2 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -228,7 +228,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{"gauge:<value:2 > ": `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} gauge:<value:2 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -243,7 +243,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"-2" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"-2" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -259,7 +259,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{"gauge:<value:3 > ": `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} gauge:<value:3 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -275,7 +275,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{"gauge:<value:3 > ": `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} gauge:<value:3 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -292,7 +292,7 @@ func TestPduToSample(t *testing.T) {
 				Indexes: []*config.Index{{Labelname: "foo", Type: "DisplayString"}},
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{`label:<name:"foo" value:"AA" > gauge:<value:3 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [foo]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [foo]} label:<name:"foo" value:"AA" > gauge:<value:3 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -344,7 +344,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 1}},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"4.5.6.7" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"4.5.6.7" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -359,7 +359,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 1}},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"4.5.6.7" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"4.5.6.7" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -374,7 +374,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 2}},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"0405:0607:0809:0A0B:0C0D:0E0F:1011:1213" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"0405:0607:0809:0A0B:0C0D:0E0F:1011:1213" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -389,7 +389,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 3}},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"0x0405060708" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"0x0405060708" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -404,7 +404,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        make(map[string]gosnmp.SnmpPDU),
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"0x04050607" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"0x04050607" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -419,7 +419,7 @@ func TestPduToSample(t *testing.T) {
 				Help: "Help string",
 			},
 			oidToPdu:        map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 3}},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"04:05:06:07:08:09" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"04:05:06:07:08:09" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -434,7 +434,7 @@ func TestPduToSample(t *testing.T) {
 				Help:       "Help string",
 				EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"},
 			},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"baz" > gauge:<value:1 > `: `Desc{fqName: "test_metric_info", help: "Help string (EnumAsInfo)", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric_info", help: "Help string (EnumAsInfo)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"baz" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -449,7 +449,7 @@ func TestPduToSample(t *testing.T) {
 				Help:       "Help string",
 				EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"},
 			},
-			expectedMetrics: map[string]string{`label:<name:"test_metric" value:"3" > gauge:<value:1 > `: `Desc{fqName: "test_metric_info", help: "Help string (EnumAsInfo)", constLabels: {}, variableLabels: [test_metric]}`},
+			expectedMetrics: []string{`Desc{fqName: "test_metric_info", help: "Help string (EnumAsInfo)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"3" > gauge:<value:1 > `},
 		},
 		{
 			pdu: &gosnmp.SnmpPDU{
@@ -464,10 +464,10 @@ func TestPduToSample(t *testing.T) {
 				Help:       "Help string",
 				EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"},
 			},
-			expectedMetrics: map[string]string{
-				`label:<name:"test_metric" value:"foo" > gauge:<value:0 > `: `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
-				`label:<name:"test_metric" value:"bar" > gauge:<value:0 > `: `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
-				`label:<name:"test_metric" value:"baz" > gauge:<value:1 > `: `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
+			expectedMetrics: []string{
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"foo" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"bar" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"baz" > gauge:<value:1 > `,
 			},
 		},
 		{
@@ -483,21 +483,46 @@ func TestPduToSample(t *testing.T) {
 				Help:       "Help string",
 				EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"},
 			},
-			expectedMetrics: map[string]string{
-				`label:<name:"test_metric" value:"foo" > gauge:<value:0 > `: `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
-				`label:<name:"test_metric" value:"bar" > gauge:<value:0 > `: `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
-				`label:<name:"test_metric" value:"baz" > gauge:<value:0 > `: `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
-				`label:<name:"test_metric" value:"3" > gauge:<value:1 > `:   `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]}`,
+			expectedMetrics: []string{
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"foo" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"bar" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"baz" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"3" > gauge:<value:1 > `,
+			},
+		},
+		{
+			pdu: &gosnmp.SnmpPDU{
+				Name:  "1.1",
+				Type:  gosnmp.Integer,
+				Value: []byte{1<<7 + 1<<6, 1 << 7, 1 << 0},
+			},
+			metric: &config.Metric{
+				Name:       "test_metric",
+				Oid:        "1.1",
+				Type:       "Bits",
+				Help:       "Help string",
+				EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz", 8: "byte2msb", 15: "byte2lsb", 16: "byte3msb", 23: "byte3lsb", 24: "missing"},
+			},
+			expectedMetrics: []string{
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"foo" > gauge:<value:1 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"bar" > gauge:<value:1 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"baz" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"byte2msb" > gauge:<value:1 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"byte2lsb" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"byte3msb" > gauge:<value:0 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"byte3lsb" > gauge:<value:1 > `,
+				`Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label:<name:"test_metric" value:"missing" > gauge:<value:0 > `,
 			},
 		},
 	}
 
-	for i, c := range cases {
+	for _, c := range cases {
 		metrics := pduToSamples(c.indexOids, c.pdu, c.metric, c.oidToPdu, log.NewNopLogger())
-		if len(metrics) != len(c.expectedMetrics) && !c.shouldErr {
-			t.Fatalf("Unexpected number of metrics returned for case %v: want %v, got %v", i, len(c.expectedMetrics), len(metrics))
-		}
 		metric := &io_prometheus_client.Metric{}
+		expected := map[string]struct{}{}
+		for _, e := range c.expectedMetrics {
+			expected[e] = struct{}{}
+		}
 		errHappened := false
 		for _, m := range metrics {
 			err := m.Write(metric)
@@ -509,13 +534,16 @@ func TestPduToSample(t *testing.T) {
 					t.Fatalf("Error writing metric: %v", err)
 				}
 			}
-			if _, ok := c.expectedMetrics[metric.String()]; !ok {
-				t.Fatalf("Unexpected metric: got %v", metric.String())
-			}
-			if c.expectedMetrics[metric.String()] != m.Desc().String() {
-				t.Fatalf("Unexpected metric: got %v , want %v", m.Desc().String(), c.expectedMetrics[metric.String()])
+			got := m.Desc().String() + " " + metric.String()
+			if _, ok := expected[got]; !ok {
+				t.Errorf("Unexpected metric: got %v", got)
+			} else {
+				delete(expected, got)
 			}
 		}
+		for e := range expected {
+			t.Errorf("Expected metric %v, but was not returned.", e)
+		}
 		if !errHappened && c.shouldErr {
 			t.Fatalf("Was expecting error, but none returned.")
 		}
diff --git a/generator/tree_test.go b/generator/tree_test.go
index c392301..72ece87 100644
--- a/generator/tree_test.go
+++ b/generator/tree_test.go
@@ -397,7 +397,7 @@ func TestGenerateConfigModule(t *testing.T) {
 					{
 						Name: "BITSTRING",
 						Oid:  "1.12",
-						Type: "OctetString",
+						Type: "Bits",
 						Help: " - 1.12",
 					},
 					{
@@ -780,11 +780,11 @@ func TestGenerateConfigModule(t *testing.T) {
 						Name: "bitstringIndex",
 						Oid:  "1.2.1.1",
 						Help: " - 1.2.1.1",
-						Type: "OctetString",
+						Type: "Bits",
 						Indexes: []*config.Index{
 							{
 								Labelname: "bitstringIndex",
-								Type:      "OctetString",
+								Type:      "Bits",
 							},
 						},
 					},
@@ -796,7 +796,7 @@ func TestGenerateConfigModule(t *testing.T) {
 						Indexes: []*config.Index{
 							{
 								Labelname: "bitstringIndex",
-								Type:      "OctetString",
+								Type:      "Bits",
 							},
 						},
 					},