Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTEP 0049: LabelSet specification (to match current Metrics spec) #49

Merged
merged 22 commits into from
Nov 16, 2019
Merged
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions text/0049-metric-label-set.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Metric `LabelSet` specification

**Status:** `proposed`

Introduce a first-class `LabelSet` API type as a handle on a pre-defined set of labels for the Metrics API.

## Motivation

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.
jmacd marked this conversation as resolved.
Show resolved Hide resolved

Whether pushing or pulling metrics, whether aggregating metric events in the process or not, it is an expensive computation to translate a set of labels into either a serialized representation or a unique lookup key. Users of the metrics API stand to benefit by re-using label sets whenever possible, because manipulating label sets often dominates the cost of processing metric events.
jmacd marked this conversation as resolved.
Show resolved Hide resolved

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, whenever more than one instrument will be called directly with the same labels, and whenever 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.
jmacd marked this conversation as resolved.
Show resolved Hide resolved

## Explanation

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 SDK, allowing the programmer to acquire a pre-define label set. Here are several examples of `LabelSet` re-use. Assume we have two instruments:
jmacd marked this conversation as resolved.
Show resolved Hide resolved

```golang
var (
cumulative = metric.NewFloat64Cumulative("my_counter")
gauge = metric.NewFloat64Gauge("my_gauge")
)
```

Use a `LabelSet` to construct multiple Handles:

```golang
var (
labels = meter.Labels({ "required_key1": value1, "required_key2": value2 })
bogdandrutu marked this conversation as resolved.
Show resolved Hide resolved
chandle = cumulative.GetHandle(labels)
ghandle = gauge.GetHandle(labels)
)
for ... {
// ...
chandle.Add(...)
ghandle.Set(...)
}
```

Use a `LabelSet` to for multiple Direct calls:
jmacd marked this conversation as resolved.
Show resolved Hide resolved

```golang
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.

### Ordered `LabelSet` option

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,

```golang

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.

## Internal details

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.

## Trade-offs and mitigations

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(meter, { Key: Value, ... })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be updated because instrument is now generated by the meter so no need to pass that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

```

as opposed to this:

```
instrument.GetHandle(meter.Labels({ Key: Value, ... }))
bogdandrutu marked this conversation as resolved.
Show resolved Hide resolved
```

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.

## Prior art and alternatives

Some existing metrics APIs support this concept. For example, see `Scope` in the [Tally metric API for Go](https://godoc.org/github.com/uber-go/tally#Scope).

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" })
```