Please take the OpenCensus user survey

OpenCensus Agent

Introduction

The OpenCensus Agent exporter aka “ocagent-exporter” enables Go applications to send the observability that they’ve collected using OpenCensus to the OpenCensus Agent

This exporter connects and sends observability signals via a single HTTP/2 stream and gRPC with Protocol Buffers to the OpenCensus Agent. If unspecified, this exporter tries to connect to the OpenCensus Agent on port 55678.

Purpose

It converts OpenCensus Stats and Traces into OpenCensus Proto Metrics and Traces which are then sent to the OpenCensus Agent. Therefore programs no longer have to enable the traditional backends’ exporters for every single application.

For example, some backends like Prometheus require opening a unique port per application. This port is what they’ll pull stats from. If you have 10,000 microservices that all each export to Prometheus, you already have to manually and uniquely create at least 10,000 unique ports. Uniquely creating a port will not only exhaust your file descriptors but it also becomes cumbersome and error prone to do.

With the ocagent-exporter, instead the stats are uploaded to the OpenCensus Agent and from there, the agent is singly scraped by Prometheus.

As you can see from above, this not only scales your application’s resource allocation but also scales your development speed and liberates you from complex batching deployments. It also ensures that your applications can be kept light, that the agent’s deployment can safely be restarted flexibly.

The same thing happens for traces.

Imports and initialization

The exporter’s import path is “contrib.go.opencensus.io/exporter/ocagent”.

An exporter can be started by invoking ocagent.New, whose signature is:

func NewExporter(opts ...ExporterOption) (*Exporter, error)

Below is a full example of the simplest end-to-end initialization:

package main

import (
	"log"

	"contrib.go.opencensus.io/exporter/ocagent"
)

func main() {
	oce, err := ocagent.NewExporter()
	if err != nil {
		log.Fatalf("Failed to create a new ocagent exporter: %v", err)
	}
	// Before the program stops, please
	// remember to stop  the exporter.
	defer oce.Stop()
}

Options

Options allow you to customize the exporter. The function signature for New allows optional functional options.

func NewExporter(opts ...ExporterOption) (*Exporter, error)

Custom address

This option allows one to talk to an OpenCensus Agent running on a customized address. The customized address could be anything that is resolvable by net.LookupAddr

        oce, err := ocagent.NewExporter(
                // WithAddress takes in any value that can be resolved
                // by invoking net.Lookup https://golang.org/pkg/net/#LookupAddr
                ocagent.WithAddress("<my_host>:<myport>"))

Insecure

This option allows one to talk to the OpenCensus Agent without mutual TLS.

WithInsecure is akin to grpc.WithInsecure

It can be enabled like this

        oce, err := ocagent.NewExporter(
                // WithInsecure is akin to  grpc.WithInsecure()
                ocagent.WithInsecure())

Service name

This option allows one to set the service name of the caller by using WithServiceName

It can be enabled like this

        oce, err := ocagent.NewExporter(ocagent.WithServiceName("with-service-name"))

Reconnection period

This option defines the amount of time for a failed connection, that the exporter takes before a reconnection attempt to the agent.

WithReconnectionPeriod is the option’s name.

Here is an example for how to tell it to attempt reconnecting on fail, after 10 seconds.

        oce, err := ocagent.NewExporter(ocagent.WithReconnectionPeriod(10 * time.Second))

Enabling stats exporting

This exporter implements OpenCensus-Go’s view.Exporter

This allows it to receive stats emitted by OpenCensus-Go and upload them to the agent. It can be enabled by

import "go.opencensus.io/stats/view"

func main() {
        view.RegisterExporter(oce)
}

Enabling trace exporting

This exporter implements OpenCensus-Go’s trace.Exporter

This allows it to receive stats emitted by OpenCensus-Go and upload them to the agent. It can be enabled by

import "go.opencensus.io/trace"

func main() {
        trace.RegisterExporter(oce)
}

End to end Example

This end to end example exports stats and traces to the agent. It will require you to deploy the OpenCensus-Agent in order to examine the stats and traces.

package main

import (
	"context"
	"fmt"
	"log"
	"math/rand"
	"time"

	"contrib.go.opencensus.io/exporter/ocagent"
	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
	"go.opencensus.io/tag"
	"go.opencensus.io/trace"
)

func main() {
	oce, err := ocagent.NewExporter(
		ocagent.WithInsecure(),
		ocagent.WithReconnectionPeriod(5 * time.Second),
		ocagent.WithAddress("localhost:55678"), // Only included here for demo purposes.
		ocagent.WithServiceName("ocagent-go-example"))
	if err != nil {
		log.Fatalf("Failed to create ocagent-exporter: %v", err)
	}
	trace.RegisterExporter(oce)
	view.RegisterExporter(oce)

	// Some configurations to get observability signals out.
	view.SetReportingPeriod(7 * time.Second)
	trace.ApplyConfig(trace.Config{
		DefaultSampler: trace.AlwaysSample(),
	})

	// Some stats
	keyClient, _ := tag.NewKey("client")
	keyMethod, _ := tag.NewKey("method")

	mLatencyMs := stats.Float64("latency", "The latency in milliseconds", "ms")
	mLineLengths := stats.Int64("line_lengths", "The length of each line", "By")

	views := []*view.View{
		{
			Name:        "opdemo/latency",
			Description: "The various latencies of the methods",
			Measure:     mLatencyMs,
			Aggregation: view.Distribution(0, 10, 50, 100, 200, 400, 800, 1000, 1400, 2000, 5000, 10000, 15000),
			TagKeys:     []tag.Key{keyClient, keyMethod},
		},
		{
			Name:        "opdemo/process_counts",
			Description: "The various counts",
			Measure:     mLatencyMs,
			Aggregation: view.Count(),
			TagKeys:     []tag.Key{keyClient, keyMethod},
		},
		{
			Name:        "opdemo/line_lengths",
			Description: "The lengths of the various lines in",
			Measure:     mLineLengths,
			Aggregation: view.Distribution(0, 10, 20, 50, 100, 150, 200, 500, 800),
			TagKeys:     []tag.Key{keyClient, keyMethod},
		},
		{
			Name:        "opdemo/line_counts",
			Description: "The counts of the lines in",
			Measure:     mLineLengths,
			Aggregation: view.Count(),
			TagKeys:     []tag.Key{keyClient, keyMethod},
		},
	}

	if err := view.Register(views...); err != nil {
		log.Fatalf("Failed to register views for metrics: %v", err)
	}

	ctx, _ := tag.New(context.Background(), tag.Insert(keyMethod, "repl"), tag.Insert(keyClient, "cli"))
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	for {
		startTime := time.Now()
		_, span := trace.StartSpan(context.Background(), "Foo")
		var sleep int64
		switch modulus := time.Now().Unix() % 5; modulus {
		case 0:
			sleep = rng.Int63n(17001)
		case 1:
			sleep = rng.Int63n(8007)
		case 2:
			sleep = rng.Int63n(917)
		case 3:
			sleep = rng.Int63n(87)
		case 4:
			sleep = rng.Int63n(1173)
		}

		time.Sleep(time.Duration(sleep) * time.Millisecond)

		span.End()
		latencyMs := float64(time.Since(startTime)) / 1e6
		nr := int(rng.Int31n(7))
		for i := 0; i < nr; i++ {
			randLineLength := rng.Int63n(999)
			stats.Record(ctx, mLineLengths.M(randLineLength))
			fmt.Printf("#%d: LineLength: %dBy\n", i, randLineLength)
		}
		stats.Record(ctx, mLatencyMs.M(latencyMs))
		fmt.Printf("Latency: %.3fms\n", latencyMs)
	}
}

References

Resource URL
ocagent Godoc contrib.go.opencensus.io/exporter/ocagent
Source code ocagent-exporter on Github
OpenCensus Agent OpenCensus Agent on Github
net.LookupAddr net.LookupAddr
grpc Go grpc GoDoc