OpenTelemetry Metrics

What are metrics?

Metrics are used to measure, monitor, and compare performance, for example, you can measure server response time, memory utilization, error rate, and more.

OpenTelemetry Metrics

OpenTelemetry defines a standardopen in new window on how to collect, aggregate, and send metrics to backend platforms.

While defining a new standard, OpenTelemetry also aims to work with existing metrics instrumentation protocols such as Prometheus and Statsd. Furthermore, OpenTelemetry Collector supports even more protocols like AWS Metrics, InfluxDB, Chrony, etc.

OpenTelemetry also allows you to correlate metrics and traces via exemplars which should show you a broader picture of the state of your system.


You capture measurements by creating instruments that have:

  • An unique name, for example, http.server.duration.
  • An instrument kind, for example, Histogram.
  • An optional unit of measure, for example, milliseconds or bytes.
  • An optional description.


A single instrument can produce multiple timeseries. A timeseries is a metric with an unique set of attributes, for example, each host has a separate timeseries for the same metric name.

Additive instruments

Additive or summable instruments produce timeseries that, when added up together, produce another meaningful and accurate timeseries. Additive instruments that measure non-decreasing numbers are also called monotonic.

For example, http.server.requests is an additive timeseries, because you can sum the number of requests from different hosts to get the total number of requests.

But system.memory.utilization (percents) is not additive, because the sum of memory utilization from different hosts is not meaningful (90% + 90% = 180%).

Synchronous instruments

Synchronous instruments are invoked together with operations they are measuring. For example, to measure the number of requests, you can call counter.Add(ctx, 1) whenever there is a new request. Synchronous measurements can have an associated trace context.

For synchronous instruments the difference between additive and grouping instruments is that additive instruments produce summable timeseries and grouping instruments produce a histogram.

Countermonotonicsum -> deltanumber of requests, request size
UpDownCounteradditivelast value -> sumnumber of connections
Histogramgroupinghistogramrequest duration, request size

Asynchronous instruments

Asynchronous instruments (observers) periodically invoke a callback function to collect measurements. For example, you can use observers to periodically measure memory or CPU usage. Asynchronous measurements can't have an associated trace context.

When choosing between UpDownCounterObserver (additive) and GaugeObserver (grouping), choose UpDownCounterObserver for summable timeseries and GaugeObserver otherwise. For example, to measure system.memory.usage (bytes), you should use UpDownCounterObserver. But to measure system.memory.utilization (percents), you should use GaugeObserver.

Instrument NamePropertiesAggregationExample
CounterObservermonotonicsum -> deltaCPU time
UpDownCounterObserveradditivelast value -> sumMemory usage (bytes)
GaugeObservergroupinglast value -> none/avgMemory utilization (%)

Choosing instruments

  1. If you need a histogram, a heatmap, or percentiles, use Histogram.

  2. If you want to count something by recording a delta value:

  3. If you want to measure something by recording an absolute value:


synchronous monotonic

Counter is a synchronous instrument that measures additive non-decreasing values, for example, the number of:

  • processed requests
  • received bytes
  • disk reads

For Counter timeseries, backends usually compute deltas and display rate values, for example, per_min(http.server.requests) returns the number of processed requests per minute.


asynchronous monotonic

CounterObserver is the asynchronous version of the Counter instrument.


synchronous additive

UpDownCounter is a synchronous instrument which measures additive values that increase or decrease with time, for example, the number of:

  • active requests
  • open connections
  • memory in use (megabytes)

For additive non-decreasing values you should use Counter or CounterObserver.

For UpDownCounter timeseries, backends usually display the last value, but different timeseries can be added up together, for example, go.sql.connections_open returns the total number of open connections and go.sql.connections_open{ = myservice} returns the number of open connections for one service.


asynchronous additive

UpDownCounterOserver is the asynchronous version of the UpDownCounter instrument.


synchronous grouping

Histogram is a synchronous instrument that produces a histogram from recorded values, for example:

  • request latency
  • request size

For Histogram timeseries, backends usually display percentiles, or a histogram, or a heatmap.


asynchronous grouping

GaugeObserver is an asynchronous instrument that measures non-additive values for which sum does not produce a meaningful correct result, for example:

  • error rate
  • memory utilization
  • cache hit rate

For GaugeObserver timeseries, backends usually display the last value and don't allow to sum different timeseries together.

Metrics examples

Number of emails

To measure the number of sent emails, you can create a Counter instrument and increment it whenever an email is sent:

import ""

var emailCounter = meter.NewInt64Counter(
	metric.WithDescription("Number of sent emails"),

emailCounter.Add(ctx, 1)

Later, you can add more attributes to gather detailed statistics, for example:

  • kind = welcome and kind = reset_password to measure different emails.
  • state = sent and state = bounced to measure bounced emails.

Operation latency

To measure the latency of operations, you can create a Histogram instrument and update it synchronously with the operation:

import ""

var opHistogram = meter.NewInt64Histogram(
	metric.WithDescription("Duration of some operation"),

t1 := time.Now()
dur := time.Since(t1)

opHistogram.Record(ctx, dur.Microseconds())

Cache hit rate

To measure the cache hit rate, you can create an CounterObserver and observe the cache statistics:

import ""

var counter metric.Int64CounterObserver

// Arbitrary key/value labels.
hits := []attribute.KeyValue{attribute.String("type", "hits")}
misses := []attribute.KeyValue{attribute.String("type", "misses")}
errors := []attribute.KeyValue{attribute.String("type", "errors")}

batchObserver := meter.NewBatchObserver(
	func(ctx context.Context, result metric.BatchObserverResult) {
		stats := cache.Stats()

		result.Observe(hits, counter.Observation(int64(stats.Hits)))
		result.Observe(misses, counter.Observation(int64(stats.Misses)))
		result.Observe(errors, counter.Observation(int64(stats.Errors)))

counter = batchObserver.NewInt64CounterObserver("some.prefix.cache_operations")

See Monitoring cache stats using OpenTelemetry Metricsopen in new window for details.

Error rate

To directly measure the error rate, you can create a GaugeObserver and observe the value without worrying how it is calculated:

import ""

_ = meter.NewFloat64GaugeObserver("some.prefix.error_rate",
	func(ctx context.Context, result metric.Float64ObserverResult) {
	metric.WithDescription("Error rate as reported by some other system"),

How to start using OpenTelemetry Metrics?

The easiest way to get started with metrics is to pick a vendor and follow the documentation. Most vendors provide pre-configured OpenTelemetry distros that allow you to skip some steps and can significantly improve your experience.

Uptrace is an open source backend for OpenTelemetry that uses ClickHouse database to store traces, metrics, and logs. It helps you monitor, understand, and optimize complex distributed systems.

Uptrace provides a modern UI with an intuitive query builder, rich dashboards, alerting rules, and integrations for most languages and frameworks. It can process billions of spans and metrics on a single server and allows to monitor your applications at 10x lower cost.

You can get startedopen in new window with Uptrace by downloading a DEB/RPM package or a pre-compiled Go binary.

What's next?

Next, learn about OpenTelemetry Metrics API for your programming language:

Last Updated: