title: Prometheus Exporter - Direct Instrumentation vs Custom Collector
body: <p>I'm currently writing a Prometheus exporter for a telemetry network application.</p>
  <p>I've read the doc here <a href="https://prometheus.io/docs/instrumenting/writing_exporters/#collectors" rel="nofollow noreferrer">Writing Exporters</a> and while I understand the use case for implementing a custom collector to avoid race condition, I'm not sure whether my use case could fit with direct instrumentation.</p>
  <p>Basically, the network metrics are streamed via gRPC by the network devices so my exporter just receives them and doesn't have to effectively <strong>scrape</strong> them.</p>
  <p>I've used direct instrumentation with below code:</p>
  <ul>
  <li>I declare my metrics using promauto package to keep code compact:</li>
  </ul>
  <pre class="lang-golang prettyprint-override"><code>package metrics
  
  import (
      &quot;github.com/lucabrasi83/prom-high-obs/proto/telemetry&quot;
      &quot;github.com/prometheus/client_golang/prometheus&quot;
      &quot;github.com/prometheus/client_golang/prometheus/promauto&quot;
  )
  
  var (
      cpu5Sec = promauto.NewGaugeVec(
  
          prometheus.GaugeOpts{
              Name: &quot;cisco_iosxe_iosd_cpu_busy_5_sec_percentage&quot;,
              Help: &quot;The IOSd daemon CPU busy percentage over the last 5 seconds&quot;,
          },
          []string{&quot;node&quot;},
      )
  </code></pre>
  <ul>
  <li>Below is how I simply set the metric value from the gRPC protocol buffer decoded message:</li>
  </ul>
  <pre class="lang-golang prettyprint-override"><code>cpu5Sec.WithLabelValues(msg.GetNodeIdStr()).Set(float64(val))
  </code></pre>
  <ul>
  <li>Finally, here is my main loop which basically handles the telemetry gRPC streams for metrics I'm interested in:</li>
  </ul>
  <pre class="lang-golang prettyprint-override"><code>for {
  
          req, err := stream.Recv()
          if err == io.EOF {
              return nil
          }
          if err != nil {
              logging.PeppaMonLog(
                  &quot;error&quot;,
                  fmt.Sprintf(&quot;Error while reading client %v stream: %v&quot;, clientIPSocket, err))
  
              return err
          }
  
          data := req.GetData()
  
          msg := &amp;telemetry.Telemetry{}
  
          err = proto.Unmarshal(data, msg)
  
          if err != nil {
              log.Fatalln(err)
          }
  
          if !logFlag {
              logging.PeppaMonLog(
                  &quot;info&quot;,
                  fmt.Sprintf(
                      &quot;Telemetry Subscription Request Received - Client %v - Node %v - YANG Model Path %v&quot;,
                      clientIPSocket, msg.GetNodeIdStr(), msg.GetEncodingPath(),
                  ),
              )
          }
          logFlag = true
  
          // Flag to determine whether the Telemetry device streams accepted YANG Node path
          yangPathSupported := false
  
          for _, m := range metrics.CiscoMetricRegistrar {
              if msg.EncodingPath == m.EncodingPath {
  
                  yangPathSupported = true
                  go m.RecordMetricFunc(msg)
              }
          }
  }
  </code></pre>
  <ul>
  <li>For each metric I'm interested in, I register it with a record metric function (m.RecordMetricFunc ) that takes the protocol buffer message as argument as per below.</li>
  </ul>
  <pre class="lang-golang prettyprint-override"><code>package metrics
  
  import &quot;github.com/lucabrasi83/prom-high-obs/proto/telemetry&quot;
  
  var CiscoMetricRegistrar []CiscoTelemetryMetric
  
  type CiscoTelemetryMetric struct {
      EncodingPath     string
      RecordMetricFunc func(msg *telemetry.Telemetry)
  }
  
  </code></pre>
  <ul>
  <li>I then use an init function for the actual registration:</li>
  </ul>
  <pre class="lang-golang prettyprint-override"><code>
  
  func init() {
      CiscoMetricRegistrar = append(CiscoMetricRegistrar, CiscoTelemetryMetric{
          EncodingPath:     CpuYANGEncodingPath,
          RecordMetricFunc: ParsePBMsgCpuBusyPercent,
      })
  }
  </code></pre>
  <p>I'm using Grafana as the frontend and so far haven't seen any particular discrepancy while correlating the Prometheus exposed metrics VS Checking metrics directly on the device.</p>
  <p>So I would like to understand whether this is following Prometheus best practices or I should still go through the custom collector route.</p>
  <p>Thanks in advance.</p>