Warning
OTEPs have been moved to the Specification repository. This repository has been preserved for reference purposes. Please otherwise refer to the Specification.
Introduce a first-class LabelSet
API type as a handle on a pre-defined set of labels for the Metrics API.
Labels are the term for key-value pairs used in the OpenTelemetry Metrics API. Treatment of labels in the Metrics API is especially important for performance across a variety of export strategies.
Label serialization is often one of the most expensive tasks when processing metric events. Creating a LabelSet
once and re-using it many times can greatly reduce the overall cost of processing many events.
The Metrics API supports three calling conventions: the Handle convention, the Direct convention, and the Batch convention. Each of these conventions stands to benefit when a LabelSet
is re-used, as it allows the SDK to process the label set once instead of once per call. Whenever more than one handle will be created with the same labels, more than one instrument will be called directly with the same labels, or more than one batch of metric events will be recorded with the same labels, re-using a LabelSet
makes it possible for the SDK to improve performance.
Metric instrument APIs which presently take labels in the form { Key: Value, ... }
will be updated to take an explicit LabelSet
. The Meter.Labels()
API method supports getting a LabelSet
from the API, allowing the programmer to acquire a pre-defined label set. Here are several examples of LabelSet
re-use. Assume we have two instruments:
var (
cumulative = metric.NewFloat64Cumulative("my_counter")
gauge = metric.NewFloat64Gauge("my_gauge")
)
Use a LabelSet
to construct multiple Handles:
var (
labels = meter.Labels({ "required_key1": value1, "required_key2": value2 })
chandle = cumulative.GetHandle(labels)
ghandle = gauge.GetHandle(labels)
)
for ... {
// ...
chandle.Add(...)
ghandle.Set(...)
}
Use a LabelSet
to make multiple Direct calls:
labels := meter.Labels({ "required_key1": value1, "required_key2": value2 })
cumulative.Add(quantity, labels)
gauge.Set(quantity, labels)
Of course, repeated calls to Meter.RecordBatch()
could re-use a LabelSet
as well.
As a language-level decision, APIs may support ordered LabelSet
construction, in which a pre-defined set of ordered label keys is
defined such that values can be supplied in order. This allows a
faster code path to construct the LabelSet
. For example,
var rpcLabelKeys = meter.OrderedLabelKeys("a", "b", "c")
for _, input := range stream {
labels := rpcLabelKeys.Values(1, 2, 3) // a=1, b=2, c=3
// ...
}
This is specified as a language-optional feature because its safety,
and therefore its value as an input for monitoring, depends on the
availability of type-checking in the source language. Passing
unordered labels (i.e., a list of bound keys and values) to
Meter.Labels(...)
is considered the safer alternative.
LabelSet values may be used with any named Meter originating from the same Meter provider. That is, LabelSets acquired through a named Meter may be used by any Meter from the same Meter provider.
Metric SDKs that do not or cannot take advantage of the LabelSet optimizations are not especially burdened by having to support these APIs. It is trivial to supply an implementation of LabelSet
that simply stores a list of labels. This may not be acceptable in performance-critical applications, but this is the common case in many metrics and diagnostics APIs today.
In languages where overloading is a standard convenience, the metrics API may elect to offer alternate forms that elide the call to Meter.Labels()
, for example:
instrument.GetHandle({ Key: Value, ... })
as opposed to this:
instrument.GetHandle(meter.Labels({ Key: Value, ... }))
A key distinction between LabelSet
and similar concepts in existing metrics libraries is that it is a write-only structure. LabelSet
allows the developer to input metric labels without being able to read them back. This avoids forcing the SDK to retain a reference to memory that is not required.
Some existing metrics APIs support this concept. For example, see Scope
in the Tally metric API for Go.
Some libraries take LabelSet
one step further. In the future, we may add to the the LabelSet
API a method to extend the label set with additional labels. For example:
serviceLabels := meter.Labels({ "k1": "v1", "k2": "v2" })
// ...
requestLabels := serviceLabels.With({ "k3": "v3", "k4": "v4" })