From d8a94924cc12de7b5d66c6f3da5f40a678289ab6 Mon Sep 17 00:00:00 2001 From: jmacd Date: Fri, 20 Sep 2019 11:31:30 -0700 Subject: [PATCH 01/17] New Metrics specification draft --- specification/api-metrics.md | 578 +++++++++++++++++++++++++---------- 1 file changed, 415 insertions(+), 163 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 27212107ac7..7be1596a122 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -1,211 +1,463 @@ # Metrics API -
- -Table of Contents - +## Overview + +The _Metrics API_ supports reporting diagnostic measurements using +three basic kinds of instrument, commonly known as Counters, Gauges, +and Measures. Instruments are code-level objects, handled by +programmers, used to gain visibility into operational metrics about +the application, its components, and the system as a whole. + +Monitoring and alerting are the common use-case for the data provided +through metric instruments, after various collection and aggregation +strategies are applied to the data. We find there are many other uses +for the _metric events_ that stream into these instruments. We +imagine metric data being aggregated and recorded as events in tracing +and logging systems too, and for this reason OpenTelemetry requires a +separation of the API from the SDK. + +### Meter + +The OpenTelemetry API that provides the metrics SDK is named `Meter`. +According to the specification, the `Meter` implementation ultimately +determines how metrics events are handled. The specification's task +is to define the semantics of the event and describe standard +interpretation in high-level terms. How the `Meter` accomplishes its +goals and the export capabilities it supports are not specified. + +The standard interpretation for `Meter` implementations to follow is +specified so that users understand the intended use for each kind of +metric. For example, a monotonic Counter instrument supports +`Add()` events, so the standard interpretation is to compute a sum; +the sum may be exported as an absolute value or as the change in +value, but either way the purpose of using a Counter with `Add()` is +to monitor a sum. + +## Metric kinds and inputs + +The API distinguishes metric instruments by semantic meaning, not by +the type of value produced in an exporter. This is a departure from +convention, compared with a number of common metric libraries, and +stems from the separation of the API and the SDK. The SDK ultimately +determines how to handle metric events and could potentially implement +non-standard behavior. + +This explains why the metric API does not have metric instrument kinds +for exporting "Histogram" and "Summary" distribution explicitly, for +example. These are both semantically `Measure` instruments and an SDK +can be configured to produce histograms or distribution summaries from +Measure events. It is out of scope for the Metrics API to specify how +these alternatives are configured in a particular SDK. + +We believe the three metric kinds Counter, Gauge, and Measure form a +sufficient basis for expression of a wide variety of metric data. +Programmers write and read these as `Add()`, `Set()`, and `Record()` +method calls, signifying the semantics and standard interpretation, +and we believe these three methods are all that are needed. + +Nevertheless, it is common to apply restrictions on metric values, the +inputs to `Add()`, `Set()`, and `Record()`, in order to refine their +standard interpretation. Generally, there is a question of whether +the instrument can be used to compute a rate, because that is usually +a desireable analysis. Each metric instrument offers an optional +declaration, specifying restrictions on values input to the metric. +For example, Measures are declared as non-negative by default, +appropriate for reporting sizes and durations; a Measure option is +provided to record positive or negative values, but it does not change +the kind of instrument or the method name used, as the semantics are +unchanged. + +### Metric selection + +To guide the user in selecting the right kind of metric for an +application, we'll consider the following questions about the primary +intent of reporting given data. We use "of primary interest" here to +mean information that is almost certainly useful in understanding +system behavior. Consider these questions: + +- Does the measurement represent a quantity of something? Is it also non-negative? +- Is the sum a matter of primary interest? +- Is the event count a matter of primary interest? +- Is the distribution (p50, p99, etc.) a matter of primary interest? + +With answers to these questions, a user should be able to select the +kind of metric instrument based on its primary purpose. + +### Counter + +Counters support `Add(value)`. Choose this kind of metric when the +value is a quantity, the sum is of primary interest, and the event +count and value distribution are not of primary interest. + +Counters are defined as monotonic by default, meaning that positive +values are expected. Monotonic counters are typically used because +they can automatically be interpreted as a rate. + +As an option, counters can be declared as `NonMonotonic`, in which +case they support positive and negative increments. Non-monotonic +counters are useful to report changes in an accounting scheme, such as +the number of bytes allocated and deallocated. + +### Gauge + +Gauges support `Set(value)`. Gauge metrics express a pre-calculated +value that is either Set() by explicit instrumentation or observed +through a callback. Generally, this kind of metric should be used +when the metric cannot be expressed as a sum or because the +measurement interval is arbitrary. Use this kind of metric when the +measurement is not a quantity, and the sum and event count are not of +interest. + +Gauges are defined as non-monotonic by default, meaning that any value +(positive or negative) is allowed. + +As an option, gauges can be declared as `Monotonic`, in which case +successive values are expected to rise monotonically. Monotonic +gauges are useful in reporting computed cumulative sums, allowing an +application to compute a current value and report it, without +remembering the last-reported value in order to report an increment. + +A special case of gauge is supported, called an `Observer` metric +instrument, which is semantically equivalent to a gauge but uses a +callback to report the current value. Observer instruments are +defined by a callback, instead of supporting `Set()`, but the +semantics are the same. The only difference between `Observer` and +ordinary gauges is that their events do not have an associated +OpenTelemetry context. Observer instruments are non-monotonic by +default and monotonic as an option, like ordinary gauges. + +### Measure + +Measures support `Record(value)`, signifying that events report +individual measurements. This kind of metric should be used when the +count or rate of events is meaningful and either: + +- The sum is of interest in addition to the count (rate) +- Quantile information is of interest. + +Measures are defined as `NonNegative` by default, meaning that +negative values are invalid. Non-negative measures are typically used +to record absolute values such as durations and sizes. + +As an option, measures can be declared as `Signed` to indicate support +for positive and negative values. + +## Detailed Description + +### Structure + +Metric instruments are named. Regardless of the instrument kind, +metric events include the instrument name, a numerical value, and an +optional set of labels. Labels are key:value pairs associated with +events describing various dimensions or categories that describe thee +event. The Metrics API supports applying explicit labels through the +API itself, while labels can also be applied to metric events +implicitly, through the current OpenTelemetry context and resources. + +A metric `Descriptor` is a structural description of the instrument +itself, including its name and various options. Instruments may be +annotated with with specific `Units` and a description, for the +purpose of self-documentation. Instruments support an option to be +disabled by default, which implies to the SDK that a particular metric +should be explicitly enabled, otherwise these instruments are turned +"off" by the default configuration). + +Metric instruments are constructed independently of the SDK. They are +"pure" API objects, in this sense, able to be used by more than one +active `Meter` implementation. + +### Handles and LabelSets + +Metric instruments support a _Handle_ interface. Metric handles are a +pair consisting of an instrument and a specific set of pre-defined +labels, allowing for efficient repeated measurements. The use of +pre-defined labels is so important for performance that we make it a +first-class concept in the API. + +A `LabelSet` is an API object, returned by the SDK through +`Meter.DefineLabels(labels)`, that represents a set of "witnessed" +labels. Applications cannot read the labels belonging to a `LabelSet` +object, they are simply a reference to a specific +`Meter.DefineLabels()` event. + +Handles and LabelSets support different ways to achieve the same kind +of optimization. Generally, there is a high cost associated with +computing a canonicalized form of the label set that can be used as a +map key, in order to look up a corresponding group entry for +aggregation. Handles offer the most optimization potential, but +require the programmer to allocate and store one handle per metric. +LabelSets offer most of the optimization potential without managing +one handle per metric. + +Handles and LabelSets, unlike metric instruments themselves, are +SDK-dependent objects. LabelSets should only be used with the SDK +that provided them, an unrecognized LabelSet error will result if used +with a different SDK. + +### Export pipeline + +A metrics export pipeline consists of an SDK-provided `Meter` +implementation that processes metrics events (somehow), paired with a +metrics data exporter that sends the data (somehow). + +### Required label keys + +Metric instruments list an optional set of _required label keys_ to +support potential optimizations in the metric export pipeline. There +is an important relationship between metric Instruments, their +required label keys, and LabelSets. To support efficient +pre-aggregation in the metrics export pipeline, we must be able to +infer the value of the required label keys from the `LabelSet`, which +implies that they are not dependent on dynamic context. All this means +that the value of a required label key is explicitly unspecified +unless it is provided in the `LabelSet`, by definition, and not to be +taken from other context. + +### Input methods + +The API surface supports three ways to produce metrics events, after +obtaining a re-usable `LabelSet` via `Meter.DefineLabels(labels)`: + +1. Through the Instrument directly. Use `Counter.Add(LabelSet, +value)`, `Gauge.Set(LabelSet, value)`, or `Measure.Record(LabelSet, +value)` to produce an event (with no Handle). + +2. Through an instrument Handle. Use the +`Instrument.GetHandle(LabelSet)` API to construct a new handle that +implements the respective `Add(value)`, `Set(value)`, or +`Record(value)` method. + +3. Through a `Meter.RecordBatch` call. Use the batch API to enter +simultaneous measurements, where a measurement is a tuple consisting +of the `Instrument`, a `LabelSet` and the value for the appropriate +`Add()`, `Set()`, or `Record()` method. + +For instrument `Monotonic` (Counter: default), `Monotonic` (Gauge: +option), and `NonNegative` options (Measure: default), certain inputs +are invalid. These invalid inputs must not panic or throw unhandled +exceptions, however the SDK chooses to handle them. The SDK should +reject these invalid inputs, but it is not required to. + +# Method summary + +Programming APIs should follow idiomatic naming and style conventions +in each language. The names given here are suggestions aimed at +giving a consistent interface across languages in OpenTelemetry, but +these decisions will be made by individual language SIGs. + +Languages with strong typing and/or a distinction between signed and +unsigned types may choose to implement variations on these interfaces +with the correct signatures for operating on integer vs. floating +point and signed vs. unsigned values. The specification only requires +the three fundamental instrument types, whether or not they are +specialized for strong typing. + +## `Meter` interface + +Meter is an interface consisting of methods to define a label set, to +create or delete instrument handles, and to record a batch of measurements. + +### `DefineLabels(labels) -> LabelSet` + +`DefineLabels` allows the SDK to process an ordered collection of +labels and return an opaque handle, allowing the caller to amortize +the cost by re-using the returned `LabelSet`. The `LabelSet` returned +has indefinite lifetime, allowing the application to hold on to it as +long as it remains useful. The `LabelSet` cannot be explicitly +deleted. + +LabelSets have map semantics. When multiple labels are present in +`DefineLabels()`, the last value is taken. The SDK is not required to +return a unique `LabelSet` object when called for equivalent sets of +labels. + +There is no limit on label set size imposed by the API, although SDKs +may choose to. Label sets may contain arbitrary labels. At the point +of use, a label set may contain more or fewer than the required set of +labels, in which case the additional labels may be used (e.g., +sampled) or reported by the SDK. + +### `NewRecorder(Instrument, LabelSet) -> Recorder` + +`RecorderFor` returns a `Recorder` interface, which supports a +`Record(value)` method to support instrument handles. The API wraps +`Recorder` objects and exposes them via `Add(value)`, `Set(value)`, +and `Record(value)` method on handles for the respective instruments. + +The `Recorder` interface is embedded in the instrument handle and its +lifetime is controlled by the application. Applications should delete +handles when they are are no longer in use, to release the underlying +`Recorder` reference. -- [Meter](#meter) - - [Meter Creation](#meter-creation) - - [Create Metric](#create-metric) - - [Create Measure](#create-measure) - - [Record](#record) -- [Measure](#measure) - - [CreateDoubleMeasurement](#createdoublemeasurement) - - [CreateLongMeasurement](#createlongmeasurement) -- [Measurement](#measurement) -- [Metric](#metric) - - [GetOrCreateTimeSeries](#getorcreatetimeseries) - - [GetDefaultTimeSeries](#getdefaulttimeseries) - - [SetCallback](#setcallback) - - [RemoveTimeSeries](#removetimeseries) - - [Clear](#clear) - - [Type: Counter](#type-counter) - - [Type: Gauge](#type-gauge) +### `DeleteRecorder(Recorder)` -
+After the application is finished with an instrument handle, deleting +the handle will result in `DeleteRecorder()` on the underlying +recorder interface. The SDK may release any pre-aggregation state +associated with the handle after exporting a final update. -Metrics API allows to report raw measurements as well as metrics with the known -aggregation and labels. +### `RecordBatch(LabelSet, measurements...)` -Main class that is used to work with Metrics API is called `Meter`. It is used -to construct [`Measure`s](overview.md#measure) to record raw measurements -and `Metric`s to record metrics with [predefined -aggregation](overview.md#recording-metrics-with-predefined-aggregation). +`RecordBatch` is a primitive way to enter multiple simultaneous +measurements. The `Measurement` struct contains: + +- *Instrument*: the instrument `Descriptor` +- *Value*: the numerical value of the measurement event. + +Every measurement in the `RecordBatch` is associated with the +`LabelSet` argument (explicitly) and the current context and +resources (implicitly). -## Meter +### `RegisterObserver(Observer, callback)` -### Meter creation +Register an `Observer` instrument with the `Meter` to include it in +the metrics export pipeline. The SDK provides its own logic for when +to call the observer. When finished with an observer, use +`UnregisterObserver(Observer)`. -TODO: follow the spec for the Tracer. See work in progress: -https://github.com/open-telemetry/opentelemetry-specification/issues/39 +## Metric `Descriptor` -### Create Metric +A metric instrument is completely described by its descriptor, through +arguments and optional parameters passed to the constructor. The +complete contents of a metric `Descriptor` are: -`Meter` MUST expose the APIs to create a `Metric` of every supported type. -Depending on the language - builder pattern (C#, Java) or options (Go) SHOULD be -used. +- **Name** The unique name of this metric. Naming conventions are not discussed here. +- **Kind** An enumeration, one of `CounterKind`, `GaugeKind`, `ObserverKind`, or `MeasureKind` +- **Keys** The required label keys. +- **ID** A unique identifier associated with new instrument object. +- **Description** A string describing the meaning and use of this instrument. +- **Unit** The unit of measurement, not required. +- **Disabled** True value tells the SDK not to report by default. +- _Kind-specific options_ + - **NonMonotonic** (Counter): add positive and negative values + - **Monotonic** (Gauge): set a monotonic counter value + - **Signed** (Measure): record positive and negative values -`Metric` creation API requires the following argument. +## `Instrument` classes -Required arguments: +`Counter`, `Gauge`, and `Measure` instruments have consistent method +names and behavior, with the exception of their respective action +verb, `Add()`, `Set()`, or `Record()`. These three are covered here. -- name of the `Metric`. -- type of the `Metric`. This argument may be implicit from the API method name - on `Meter` was called to create this `Metric`. +### `New` -Optional arguments: +To construct a new instrument, the specific `Kind` and thus the return +value depend on the choice of (`Counter`, `Gauge`, or `Measure`). +`Name` is the only required parameter. `ID` is automatically assigned +by the API. The other fields (Description, Keys, Unit, Disabled, +Kind-specific) are options to the various constructors in a +language-appropriate style. -- description of the `Metric`. -- unit of the `Metric` values. -- list of keys for the labels with dynamic values. Order of the list is - important as the same order MUST be used on recording when supplying values - for these labels. -- set of name/value pairs for the labels with the constant values. -- component name that reports this `Metric`. See [semantic - convention](data-semantic-conventions.md) for the examples of well-known - components. -- resource this `Metric` is associated with. +Metric instruments are not associated with an SDK. They can be used +with multiple `Meter` instances in the same process. -### Create Measure +For example, -`Meter` MUST expose the API to create a `Measure` that will be used for -recording raw `Measurements`. +``` +requestKeys = ["path", "host"] +requestBytes = NewIntCounter("request.bytes", + WithKeys(requestKeys), + WithUnit(unit.Bytes)) +requestLatency = NewFloatMeasure("request.latency", + WithKeys(requestKeys), + WithUnit(unit.Second)) +loadAvg60s = NewFloatGauge("load.avg.60s") +``` -Depending on the language - builder pattern (C#, Java) or options (Go) SHOULD be -used. When multiple `Measure`s with the same arguments were created, -implementation may decide to return the same or distinct object. Users of API -MUST NOT set any expectations about `Measure`s being unique objects. +### `Add`, `Set`, `Record` -`Measure` creation exposes the following arguments. +Using a `LabelSet` obtained via `Meter.DefineLabels()` and an `Instrument` object directly, call the appropriate `Instrument.Action(LabelSet, value)` method, where `Action` is one of `Add`, `Set`, or `Record`. -Required arguments: +Continuing the example above, -- name of the `Measure`. +``` +labels = meter.DefineLabels({ "path": "/doc/{id}", "host": "h123", "port": 123, "frag": "#abc" }) -Optional arguments: +requestBytes.Add(labels, req.bytes) +requestLatency.Record(labels, req.latency) +``` -- description of the `Measure`. -- unit of the `Measure` values. -- type of the `Measure`. Can be one of two values - `long` and `double`. Default - type is `double`. +Operating on the instrument directly is expected to be relatively +fast, compared with defining a new label set, but not as fast as +operating through handles. -### Record +Use `meter.DefineLabels()` as the label set for label-free metric instruments. -`Meter` provides an API to record `Measurement`s. API built with the idea that -`Measurements`s aggregation will happen asynchronously. Typical library records -multiple `Measurement`s at once. Thus API accepts the collection of -`Measurement` so library can batch all the `Measurement`s that needs to be -recorded. +### `Handle` operation -Required argument: +Using a `LabelSet` obtained via `Meter.DefineLabels()` and an +`Instrument` object, call `GetHandle` to obtain a handle. The handle +should be stored for re-use. -- Set of `Measurement` to record. +For example, using `labels` as shown above: -Optional parameters: +``` +requestBytesHandle = requestBytes.GetHandle(labels) +requestLatencyHandle = requestLatency.GetHandle(labels) -- Explicit `DistributedContext` to use instead of the current context. Context - is used to add dimensions for the resulting `Metric` calculated out of - `Measurements`. -- Exemplar of the measurement in a form of `SpanContext`. This exemplar may be - used to provide an example of Spans in specific buckets when histogram - aggregation is used. +for req in requestBatch { + ... + requestsBytesHandle.Add(req.bytes) + requestsLatencyHandle.Record(req.latency) +} +``` -## Measure +Operating on handles should be faster than operating directly on the +instrument in the standard OpenTelemetry SDK. -`Measure` is a contract between the library exposing the raw measurement and SDK -aggregating these values into the `Metric`. `Measure` is constructed from the -`Meter` class, see [Create Measure](#create-measure) section, by providing set -of `Measure` identifiers. +After the application finishes with the handle, it should be +explicitly deleted in a language-appropriate way, which must call +`Meter.DeleteRecorder`. -### CreateDoubleMeasurement +### `RecordBatch` operation -Creates a `Measurement` with the value passes as an argument. It MUST only be -called on `Measure` of a type `double`. Implementation may return no-op -`Measurement` when there are no subscribers on this `Measure`. So `Measurement` -cannot be reused and MUST be re-created. +Using a `LabelSet` obtained via `Meter.DefineLabels()` call +`RecordBatch(LabelSet, measurements)`, where `measurements` is a list +of (`Instrument, `Value`) pairs. `RecordBatch` applies to the `Meter` +bound to the `LabelSet`. -Arguments: +For example, to record two metrics simultaneously: -Double value representing the `Measurement`. +``` +RecordBatch(labels, + Measurement(requestBytes, req.bytes), + Measurement(requestLatency, req.latency)) +``` -### CreateLongMeasurement +## `Observer` instrument -Creates a `Measurement` with the value passes as an argument. It MUST only be -called on `Measure` of a type `long`. Implementation may return no-op -`Measurement` when there are no subscribers on this `Measure`. So `Measurement` -cannot be reused and MUST be re-created. +`Observer` instruments are like `Gauge` instruments, but they are +registered directly with the `Meter` instance, allowing the +application to provide a `Meter`-specific callback. Use +`Meter.RegisterObserver(Observer, Callback)` to register an observer +and begin exporting data. -Arguments: +### `New` -Long value representing the `Measurement`. +A new `Observer` is constructed similar to a new `Gauge`, supporting +the same options. The `Observer` instrument does not support `Set()` +or `GetHandle()`. -## Measurement +### `Callback` interface -`Measurement` is an empty interface that represents a single value recorded for -the `Measure`. `Measurement` MUST be treated as immutable short lived object. -Instrumentation logic MUST NOT hold on to the object and MUST only record it -once. +The callback takes a (`Meter`, `Observer`) argument. The `Meter` and +`Observer` passed to a `Callback` match an individual registration. -## Metric +The callback returns a map from `LabelSet` to the current gauge value. +The return `LabelSets` must have been defined by the corresponding +`Meter` of the `Observer` registration. -`Metric` is a base class for various types of metrics. `Metric` is specialized -with the type of a time series that `Metric` holds. `Metric` is constructed from -the `Meter` class, see [Create Metric](#create-metric) section, by providing set -of `Metric` identifiers like name and set of label keys. +### `Callback` requirements -### GetOrCreateTimeSeries +Callbacks should avoid blocking. The implementation may be required to +cancel computation if the callback blocks for too long. -Creates a `TimeSeries` and returns a `TimeSeries` if the specified label values -is not already associated with this gauge, else returns an existing -`TimeSeries`. +Callbacks must not be called synchronously with application code via +any OpenTelemetry API. Implementations that cannot provide this +guarantee should prefer not to implement observer callbacks. -It is recommended to keep a reference to the `TimeSeries` instead of always -calling this method for every operations. +Callbacks may be called synchronously in the SDK on behalf of an +exporter. -Arguments: - -- List of label values. The order and number of labels MUST match the order and - number of label keys used when `Metric` was created. - -### GetDefaultTimeSeries - -Returns a `TimeSeries` for a metric with all labels not set (default label -value). - -Method takes no arguments. - -### SetCallback - -Sets a callback that gets executed every time before exporting this metric. It -MUST be used to provide polling of a `Metric`. Callback implementation MUST set -the value of a `Metric` to the value that will be exported. - -### RemoveTimeSeries - -Removes the `TimeSeries` from the `Metric`, if it is present. - -### Clear - -Removes all `TimeSeries` from the `Metric`. - -### Type: Counter - -`Counter` metric aggregates instantaneous values. Cumulative values can go up or -stay the same, but can never go down. Cumulative values cannot be negative. -`TimeSeries` for the `Counter` has two methods - `add` and `set`. - -- `add` adds the given value to the current value. The values cannot be - negative. -- `set` sets the given value. The value must be larger than the current recorded - value. In general should be used in combination with `SetCallback` where the - recorded value is guaranteed to be monotonically increasing. - -### Type: Gauge - -`Gauge` metric aggregates instantaneous values. Cummulative value can go both up -and down. `Gauge` values can be negative. `TimeSeries` for the `Gauge` has two -methods - `add` and `set`. - -- `add` adds the given value to the current value. The values can be negative. -- `set` sets the given value. +Callbacks should avoid calling OpenTelemetry APIs, but we recognize +this may be impossible to enforce. From 9a5f3ce7c8c163890ffc88e97581da8ca66daa9e Mon Sep 17 00:00:00 2001 From: Joshua MacDonald Date: Fri, 20 Sep 2019 15:42:48 -0700 Subject: [PATCH 02/17] Update specification/api-metrics.md Co-Authored-By: Allan Feldman <6374032+a-feld@users.noreply.github.com> --- specification/api-metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 7be1596a122..1eb4b654b41 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -413,7 +413,7 @@ explicitly deleted in a language-appropriate way, which must call Using a `LabelSet` obtained via `Meter.DefineLabels()` call `RecordBatch(LabelSet, measurements)`, where `measurements` is a list -of (`Instrument, `Value`) pairs. `RecordBatch` applies to the `Meter` +of (`Instrument`, `Value`) pairs. `RecordBatch` applies to the `Meter` bound to the `LabelSet`. For example, to record two metrics simultaneously: From 25f90539423e943da68e27bf2120cde9c2af747c Mon Sep 17 00:00:00 2001 From: jmacd Date: Mon, 23 Sep 2019 14:48:09 -0700 Subject: [PATCH 03/17] Replace Recorder w/ language-specific as it is not user-facing --- specification/api-metrics.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 7be1596a122..393ce2447d1 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -277,24 +277,27 @@ of use, a label set may contain more or fewer than the required set of labels, in which case the additional labels may be used (e.g., sampled) or reported by the SDK. -### `NewRecorder(Instrument, LabelSet) -> Recorder` +### `NewHandle(Instrument, LabelSet) -> language-specific` -`RecorderFor` returns a `Recorder` interface, which supports a -`Record(value)` method to support instrument handles. The API wraps -`Recorder` objects and exposes them via `Add(value)`, `Set(value)`, -and `Record(value)` method on handles for the respective instruments. +`NewHandle` returns a language-specific interface to support +instrument handles. The user calls `Instrument.GetHandle(LabelSet)` +directly, which calls through to `NewHandle(Instrument, LabelSet)`. +The `NewHandle` method is not a user-facing API and the return type +will vary depending on the language. -The `Recorder` interface is embedded in the instrument handle and its -lifetime is controlled by the application. Applications should delete -handles when they are are no longer in use, to release the underlying -`Recorder` reference. +User-level method calls on the handle, `Add(value)`, `Set(value)`, and +`Record(value)`, interact with the result of `NewHandle` in a +language- and API-specific way. SDKs are expected to hold resources +corresponding to each handle, so users are encouraged to delete +handles when they are no longer in use. -### `DeleteRecorder(Recorder)` +### `DeleteHandle(language-speciric)` After the application is finished with an instrument handle, deleting -the handle will result in `DeleteRecorder()` on the underlying -recorder interface. The SDK may release any pre-aggregation state -associated with the handle after exporting a final update. +the handle will result in `DeleteHandle()` on the underlying, +language- and API-specific type. The SDK may release any +pre-aggregation state associated with the handle after exporting a +final update. ### `RecordBatch(LabelSet, measurements...)` @@ -407,7 +410,7 @@ instrument in the standard OpenTelemetry SDK. After the application finishes with the handle, it should be explicitly deleted in a language-appropriate way, which must call -`Meter.DeleteRecorder`. +`Meter.DeleteHandle`. ### `RecordBatch` operation From 388dac1e13f3551d0c69be52b42929ce5d3d46cd Mon Sep 17 00:00:00 2001 From: jmacd Date: Mon, 23 Sep 2019 16:25:42 -0700 Subject: [PATCH 04/17] Edit description of required label keys --- specification/api-metrics.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 88ae0f7aea4..c6538b94f12 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -157,15 +157,22 @@ implicitly, through the current OpenTelemetry context and resources. A metric `Descriptor` is a structural description of the instrument itself, including its name and various options. Instruments may be annotated with with specific `Units` and a description, for the -purpose of self-documentation. Instruments support an option to be -disabled by default, which implies to the SDK that a particular metric -should be explicitly enabled, otherwise these instruments are turned -"off" by the default configuration). +purpose of self-documentation. Metric instruments are constructed independently of the SDK. They are "pure" API objects, in this sense, able to be used by more than one active `Meter` implementation. +#### Disabled option + +Instruments support a `Disabled` option. Metric instruments +configured with `Disabled: True` are considered "off" unless +explicitly requested via SDK configuration. Unless the SDK is +otherwise configured to enable a disabled metric, operations on these +instruments will be treated as no-ops. + +Metric instruments are enabled by default (i.e., have `Disabled: False`). + ### Handles and LabelSets Metric instruments support a _Handle_ interface. Metric handles are a @@ -203,15 +210,18 @@ metrics data exporter that sends the data (somehow). ### Required label keys Metric instruments list an optional set of _required label keys_ to -support potential optimizations in the metric export pipeline. There -is an important relationship between metric Instruments, their -required label keys, and LabelSets. To support efficient -pre-aggregation in the metrics export pipeline, we must be able to -infer the value of the required label keys from the `LabelSet`, which -implies that they are not dependent on dynamic context. All this means -that the value of a required label key is explicitly unspecified -unless it is provided in the `LabelSet`, by definition, and not to be -taken from other context. +support potential optimizations in the metric export pipeline. This +is an option in the sense that the empty set (of required label keys) +is a valid setting. If required label keys are provided, it implies +that the SDK can infer a value for the required label from the +`LabelSet`. + +To support efficient pre-aggregation in the metrics export pipeline, +we must be able to infer the label value from the `LabelSet`, which +implies that they are not dependent on dynamic context. Required +label keys address this relationship. The value of a required label +key is explicitly unspecified unless it is provided in the `LabelSet`, +by definition, and not to be taken from other context. ### Input methods From 20265e994d51ca9f3c27b8a5049751a3232b4985 Mon Sep 17 00:00:00 2001 From: Joshua MacDonald Date: Mon, 23 Sep 2019 22:29:32 -0700 Subject: [PATCH 05/17] Update specification/api-metrics.md Co-Authored-By: Chris Kleinknecht --- specification/api-metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index c6538b94f12..8da9e5cfddd 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -149,7 +149,7 @@ for positive and negative values. Metric instruments are named. Regardless of the instrument kind, metric events include the instrument name, a numerical value, and an optional set of labels. Labels are key:value pairs associated with -events describing various dimensions or categories that describe thee +events describing various dimensions or categories that describe the event. The Metrics API supports applying explicit labels through the API itself, while labels can also be applied to metric events implicitly, through the current OpenTelemetry context and resources. From 4a1ebff83ac806bf7877cfbc2d18e0f54b1a40e8 Mon Sep 17 00:00:00 2001 From: jmacd Date: Tue, 24 Sep 2019 15:07:31 -0700 Subject: [PATCH 06/17] 1st paragraph --- specification/api-metrics.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index c6538b94f12..6eb5da94ff2 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -3,10 +3,14 @@ ## Overview The _Metrics API_ supports reporting diagnostic measurements using -three basic kinds of instrument, commonly known as Counters, Gauges, -and Measures. Instruments are code-level objects, handled by -programmers, used to gain visibility into operational metrics about -the application, its components, and the system as a whole. +three basic kinds of instrument. "Metrics" are the thing being +produced--mathematical, statistical summaries of certain observable +behavior in the program. "Instruments" are the devices used by the +program to record observations about their behavior. Therefore, we +use "metric instrument" to refer to a program object, allocated +through the API, used for recording metrics. There are three distinct +instruments in the Metrics API, commonly known as Counters, Gauges, +and Measures. Monitoring and alerting are the common use-case for the data provided through metric instruments, after various collection and aggregation From 25872c458fcf2a1c513123c51c57ecc0ab05214c Mon Sep 17 00:00:00 2001 From: jmacd Date: Tue, 24 Sep 2019 16:49:43 -0700 Subject: [PATCH 07/17] Updates --- specification/api-metrics.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 3695fbd8d2a..4e1fc407ba8 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -22,12 +22,14 @@ separation of the API from the SDK. ### Meter -The OpenTelemetry API that provides the metrics SDK is named `Meter`. -According to the specification, the `Meter` implementation ultimately -determines how metrics events are handled. The specification's task -is to define the semantics of the event and describe standard -interpretation in high-level terms. How the `Meter` accomplishes its -goals and the export capabilities it supports are not specified. +The user-facing OpenTelemetry API consists of an SDK-independent part +for defining metric instruments and a part named `Meter` that is +implemented by the SDK. According to the specification, the `Meter` +implementation ultimately determines how metrics events are handled. +The specification's task is to define the semantics of the event and +describe standard interpretation in high-level terms. How the `Meter` +accomplishes its goals and the export capabilities it supports are not +specified. The standard interpretation for `Meter` implementations to follow is specified so that users understand the intended use for each kind of @@ -305,7 +307,7 @@ language- and API-specific way. SDKs are expected to hold resources corresponding to each handle, so users are encouraged to delete handles when they are no longer in use. -### `DeleteHandle(language-speciric)` +### `DeleteHandle(language-specific)` After the application is finished with an instrument handle, deleting the handle will result in `DeleteHandle()` on the underlying, @@ -315,8 +317,8 @@ final update. ### `RecordBatch(LabelSet, measurements...)` -`RecordBatch` is a primitive way to enter multiple simultaneous -measurements. The `Measurement` struct contains: +`RecordBatch` offers primitive support for entering multiple +simultaneous measurements. The `Measurement` struct contains: - *Instrument*: the instrument `Descriptor` - *Value*: the numerical value of the measurement event. @@ -343,7 +345,7 @@ complete contents of a metric `Descriptor` are: - **Keys** The required label keys. - **ID** A unique identifier associated with new instrument object. - **Description** A string describing the meaning and use of this instrument. -- **Unit** The unit of measurement, not required. +- **Unit** The unit of measurement, optional. - **Disabled** True value tells the SDK not to report by default. - _Kind-specific options_ - **NonMonotonic** (Counter): add positive and negative values @@ -365,6 +367,9 @@ by the API. The other fields (Description, Keys, Unit, Disabled, Kind-specific) are options to the various constructors in a language-appropriate style. +Metric units are defined according to the +[UCUM](http://unitsofmeasure.org/ucum.html) specification. + Metric instruments are not associated with an SDK. They can be used with multiple `Meter` instances in the same process. From 57714f7547fe4dcb342ad0ad10a80d86118431c7 Mon Sep 17 00:00:00 2001 From: jmacd Date: Tue, 24 Sep 2019 16:56:58 -0700 Subject: [PATCH 08/17] More detail on labels, keys, values --- specification/api-metrics.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 4e1fc407ba8..54f24b6fc9c 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -156,7 +156,11 @@ Metric instruments are named. Regardless of the instrument kind, metric events include the instrument name, a numerical value, and an optional set of labels. Labels are key:value pairs associated with events describing various dimensions or categories that describe the -event. The Metrics API supports applying explicit labels through the +event. A "label key" refers to the key component while "label value" +refers to the correlated value component of a label. Label refers to +the pair of label key and value. + +The Metrics API supports applying explicit labels through the API itself, while labels can also be applied to metric events implicitly, through the current OpenTelemetry context and resources. @@ -187,11 +191,11 @@ labels, allowing for efficient repeated measurements. The use of pre-defined labels is so important for performance that we make it a first-class concept in the API. -A `LabelSet` is an API object, returned by the SDK through -`Meter.DefineLabels(labels)`, that represents a set of "witnessed" -labels. Applications cannot read the labels belonging to a `LabelSet` -object, they are simply a reference to a specific -`Meter.DefineLabels()` event. +A `LabelSet` represents a set of labels, i.e., a set of key:value +assignments. `LabelSets` are returned by the SDK through a call to +`Meter.DefineLabels(labels)`. Applications cannot read the labels +belonging to a `LabelSet` object, they are simply a reference to a +specific `Meter.DefineLabels()` event. Handles and LabelSets support different ways to achieve the same kind of optimization. Generally, there is a high cost associated with From 9c4847d616dfca733688563fd0bef255f4ff318b Mon Sep 17 00:00:00 2001 From: jmacd Date: Fri, 27 Sep 2019 21:58:20 -0700 Subject: [PATCH 09/17] Remove references to aggregation; rename required label keys 'recommended' --- specification/api-metrics.md | 39 ++++++++++-------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 54f24b6fc9c..79a4569f518 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -200,38 +200,23 @@ specific `Meter.DefineLabels()` event. Handles and LabelSets support different ways to achieve the same kind of optimization. Generally, there is a high cost associated with computing a canonicalized form of the label set that can be used as a -map key, in order to look up a corresponding group entry for -aggregation. Handles offer the most optimization potential, but -require the programmer to allocate and store one handle per metric. -LabelSets offer most of the optimization potential without managing -one handle per metric. +map key, to group the entry for export. Handles offer the most +optimization potential, but require the programmer to allocate and +store one handle per metric. LabelSets offer most of the optimization +potential without managing one handle per metric. Handles and LabelSets, unlike metric instruments themselves, are SDK-dependent objects. LabelSets should only be used with the SDK that provided them, an unrecognized LabelSet error will result if used with a different SDK. -### Export pipeline +### Recommended label keys -A metrics export pipeline consists of an SDK-provided `Meter` -implementation that processes metrics events (somehow), paired with a -metrics data exporter that sends the data (somehow). - -### Required label keys - -Metric instruments list an optional set of _required label keys_ to -support potential optimizations in the metric export pipeline. This -is an option in the sense that the empty set (of required label keys) -is a valid setting. If required label keys are provided, it implies -that the SDK can infer a value for the required label from the -`LabelSet`. - -To support efficient pre-aggregation in the metrics export pipeline, -we must be able to infer the label value from the `LabelSet`, which -implies that they are not dependent on dynamic context. Required -label keys address this relationship. The value of a required label -key is explicitly unspecified unless it is provided in the `LabelSet`, -by definition, and not to be taken from other context. +Metric instruments list an optional set of _recommended label keys_, +which are the default dimensions used for grouping metric data for +export. Unless the SDK is configured otherwise, the standard behavior +will be to group metric events by the values of their recommended +label keys. ### Input methods @@ -315,9 +300,7 @@ handles when they are no longer in use. After the application is finished with an instrument handle, deleting the handle will result in `DeleteHandle()` on the underlying, -language- and API-specific type. The SDK may release any -pre-aggregation state associated with the handle after exporting a -final update. +language- and API-specific type. ### `RecordBatch(LabelSet, measurements...)` From 3d2b8ecf410f62172a22a9fbff88304724d4cc78 Mon Sep 17 00:00:00 2001 From: jmacd Date: Mon, 30 Sep 2019 23:56:06 -0700 Subject: [PATCH 10/17] Feedback --- specification/api-metrics.md | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 79a4569f518..10946fcf949 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -65,7 +65,7 @@ Nevertheless, it is common to apply restrictions on metric values, the inputs to `Add()`, `Set()`, and `Record()`, in order to refine their standard interpretation. Generally, there is a question of whether the instrument can be used to compute a rate, because that is usually -a desireable analysis. Each metric instrument offers an optional +a desirable analysis. Each metric instrument offers an optional declaration, specifying restrictions on values input to the metric. For example, Measures are declared as non-negative by default, appropriate for reporting sizes and durations; a Measure option is @@ -160,29 +160,22 @@ event. A "label key" refers to the key component while "label value" refers to the correlated value component of a label. Label refers to the pair of label key and value. -The Metrics API supports applying explicit labels through the -API itself, while labels can also be applied to metric events -implicitly, through the current OpenTelemetry context and resources. +The Metrics API supports applying explicit labels through the API +itself using `LabelSet` objects, while labels can also be applied to +metric events implicitly, through the current OpenTelemetry context. +System-level properties (a.k.a. "resources") are not detailed in the +API, but are also implicitly associated with metric events via the +SDK. A metric `Descriptor` is a structural description of the instrument itself, including its name and various options. Instruments may be -annotated with with specific `Units` and a description, for the +annotated with specific `Units` and a description, for the purpose of self-documentation. Metric instruments are constructed independently of the SDK. They are "pure" API objects, in this sense, able to be used by more than one active `Meter` implementation. -#### Disabled option - -Instruments support a `Disabled` option. Metric instruments -configured with `Disabled: True` are considered "off" unless -explicitly requested via SDK configuration. Unless the SDK is -otherwise configured to enable a disabled metric, operations on these -instruments will be treated as no-ops. - -Metric instruments are enabled by default (i.e., have `Disabled: False`). - ### Handles and LabelSets Metric instruments support a _Handle_ interface. Metric handles are a @@ -333,7 +326,6 @@ complete contents of a metric `Descriptor` are: - **ID** A unique identifier associated with new instrument object. - **Description** A string describing the meaning and use of this instrument. - **Unit** The unit of measurement, optional. -- **Disabled** True value tells the SDK not to report by default. - _Kind-specific options_ - **NonMonotonic** (Counter): add positive and negative values - **Monotonic** (Gauge): set a monotonic counter value @@ -350,7 +342,7 @@ verb, `Add()`, `Set()`, or `Record()`. These three are covered here. To construct a new instrument, the specific `Kind` and thus the return value depend on the choice of (`Counter`, `Gauge`, or `Measure`). `Name` is the only required parameter. `ID` is automatically assigned -by the API. The other fields (Description, Keys, Unit, Disabled, +by the API. The other fields (Description, Keys, Unit, Kind-specific) are options to the various constructors in a language-appropriate style. From b042a04abf8b08aedd207e7167625751ed29576a Mon Sep 17 00:00:00 2001 From: jmacd Date: Tue, 8 Oct 2019 14:24:05 -0700 Subject: [PATCH 11/17] Move contentous stuff out --- README.md | 2 + specification/api-metrics-meter.md | 6 + specification/api-metrics-user.md | 6 + specification/api-metrics.md | 353 +++-------------------------- 4 files changed, 43 insertions(+), 324 deletions(-) create mode 100644 specification/api-metrics-meter.md create mode 100644 specification/api-metrics-user.md diff --git a/README.md b/README.md index cc0268da227..091d41cadb1 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ The OpenTelemetry specification describes the cross-language requirements and ex - [Tracing](specification/api-tracing.md) - [Sampling](specification/api-sampling.md) - [Metrics](specification/api-metrics.md) + - [User-Facing API](specification/api-metrics-user.md) + - [Meter API](specification/api-metrics-meter.md) - Data Specification - [Semantic Conventions](specification/data-semantic-conventions.md) - [Protocol](specification/protocol.md) diff --git a/specification/api-metrics-meter.md b/specification/api-metrics-meter.md new file mode 100644 index 00000000000..f43b780a557 --- /dev/null +++ b/specification/api-metrics-meter.md @@ -0,0 +1,6 @@ +# Metric SDK-facing API + +This document is a placeholder pending active discussion. It will +eventually contain portions that were removed from +https://github.com/open-telemetry/opentelemetry-specification/blob/3d2b8ecf410f62172a22a9fbff88304724d4cc78/specification/api-metrics.md +after further discussion. diff --git a/specification/api-metrics-user.md b/specification/api-metrics-user.md new file mode 100644 index 00000000000..4f33f742f58 --- /dev/null +++ b/specification/api-metrics-user.md @@ -0,0 +1,6 @@ +# Metric User-facing API + +This document is a placeholder pending active discussion. It will +eventually contain portions that were removed from +https://github.com/open-telemetry/opentelemetry-specification/blob/3d2b8ecf410f62172a22a9fbff88304724d4cc78/specification/api-metrics.md +after further discussion. diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 10946fcf949..f7748fd3e79 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -2,8 +2,8 @@ ## Overview -The _Metrics API_ supports reporting diagnostic measurements using -three basic kinds of instrument. "Metrics" are the thing being +The user-facing metrics API supports reporting diagnostic measurements +using three basic kinds of instrument. "Metrics" are the thing being produced--mathematical, statistical summaries of certain observable behavior in the program. "Instruments" are the devices used by the program to record observations about their behavior. Therefore, we @@ -20,16 +20,28 @@ imagine metric data being aggregated and recorded as events in tracing and logging systems too, and for this reason OpenTelemetry requires a separation of the API from the SDK. -### Meter +### User-facing API The user-facing OpenTelemetry API consists of an SDK-independent part -for defining metric instruments and a part named `Meter` that is -implemented by the SDK. According to the specification, the `Meter` -implementation ultimately determines how metrics events are handled. -The specification's task is to define the semantics of the event and -describe standard interpretation in high-level terms. How the `Meter` -accomplishes its goals and the export capabilities it supports are not -specified. +for defining metric instruments. Review the [user-facing +OpenTelementry API specification](api-metrics-user.md) for more detail +about the variety of methods, options, and optimizations available for +users of the instrumentation API. + +To capture measurements using an instruement, you need an SDK that +implements the `Meter` API. + +### Meter API + +`Meter` is an interface with the SDK used to capture measurements in +various ways. Review the [SDK-facing OpenTelemetry API +specification](api-metrics-meter.md) known as `Meter`. + +Because of API-SDK separation, the `Meter` implementation ultimately +determines how metrics events are handled, . The specification's task +is to define the semantics of the event and describe standard +interpretation in high-level terms. How the `Meter` accomplishes its +goals and the export capabilities it supports are not specified. The standard interpretation for `Meter` implementations to follow is specified so that users understand the intended use for each kind of @@ -39,6 +51,13 @@ the sum may be exported as an absolute value or as the change in value, but either way the purpose of using a Counter with `Add()` is to monitor a sum. +### Purpose of this document + +This document gives an overview of the specification, introduces the +the three kinds of instrument, and discuss how end-users should think +about various options at a high-level, without getting into detail +about specific method calls. + ## Metric kinds and inputs The API distinguishes metric instruments by semantic meaning, not by @@ -148,317 +167,3 @@ to record absolute values such as durations and sizes. As an option, measures can be declared as `Signed` to indicate support for positive and negative values. -## Detailed Description - -### Structure - -Metric instruments are named. Regardless of the instrument kind, -metric events include the instrument name, a numerical value, and an -optional set of labels. Labels are key:value pairs associated with -events describing various dimensions or categories that describe the -event. A "label key" refers to the key component while "label value" -refers to the correlated value component of a label. Label refers to -the pair of label key and value. - -The Metrics API supports applying explicit labels through the API -itself using `LabelSet` objects, while labels can also be applied to -metric events implicitly, through the current OpenTelemetry context. -System-level properties (a.k.a. "resources") are not detailed in the -API, but are also implicitly associated with metric events via the -SDK. - -A metric `Descriptor` is a structural description of the instrument -itself, including its name and various options. Instruments may be -annotated with specific `Units` and a description, for the -purpose of self-documentation. - -Metric instruments are constructed independently of the SDK. They are -"pure" API objects, in this sense, able to be used by more than one -active `Meter` implementation. - -### Handles and LabelSets - -Metric instruments support a _Handle_ interface. Metric handles are a -pair consisting of an instrument and a specific set of pre-defined -labels, allowing for efficient repeated measurements. The use of -pre-defined labels is so important for performance that we make it a -first-class concept in the API. - -A `LabelSet` represents a set of labels, i.e., a set of key:value -assignments. `LabelSets` are returned by the SDK through a call to -`Meter.DefineLabels(labels)`. Applications cannot read the labels -belonging to a `LabelSet` object, they are simply a reference to a -specific `Meter.DefineLabels()` event. - -Handles and LabelSets support different ways to achieve the same kind -of optimization. Generally, there is a high cost associated with -computing a canonicalized form of the label set that can be used as a -map key, to group the entry for export. Handles offer the most -optimization potential, but require the programmer to allocate and -store one handle per metric. LabelSets offer most of the optimization -potential without managing one handle per metric. - -Handles and LabelSets, unlike metric instruments themselves, are -SDK-dependent objects. LabelSets should only be used with the SDK -that provided them, an unrecognized LabelSet error will result if used -with a different SDK. - -### Recommended label keys - -Metric instruments list an optional set of _recommended label keys_, -which are the default dimensions used for grouping metric data for -export. Unless the SDK is configured otherwise, the standard behavior -will be to group metric events by the values of their recommended -label keys. - -### Input methods - -The API surface supports three ways to produce metrics events, after -obtaining a re-usable `LabelSet` via `Meter.DefineLabels(labels)`: - -1. Through the Instrument directly. Use `Counter.Add(LabelSet, -value)`, `Gauge.Set(LabelSet, value)`, or `Measure.Record(LabelSet, -value)` to produce an event (with no Handle). - -2. Through an instrument Handle. Use the -`Instrument.GetHandle(LabelSet)` API to construct a new handle that -implements the respective `Add(value)`, `Set(value)`, or -`Record(value)` method. - -3. Through a `Meter.RecordBatch` call. Use the batch API to enter -simultaneous measurements, where a measurement is a tuple consisting -of the `Instrument`, a `LabelSet` and the value for the appropriate -`Add()`, `Set()`, or `Record()` method. - -For instrument `Monotonic` (Counter: default), `Monotonic` (Gauge: -option), and `NonNegative` options (Measure: default), certain inputs -are invalid. These invalid inputs must not panic or throw unhandled -exceptions, however the SDK chooses to handle them. The SDK should -reject these invalid inputs, but it is not required to. - -# Method summary - -Programming APIs should follow idiomatic naming and style conventions -in each language. The names given here are suggestions aimed at -giving a consistent interface across languages in OpenTelemetry, but -these decisions will be made by individual language SIGs. - -Languages with strong typing and/or a distinction between signed and -unsigned types may choose to implement variations on these interfaces -with the correct signatures for operating on integer vs. floating -point and signed vs. unsigned values. The specification only requires -the three fundamental instrument types, whether or not they are -specialized for strong typing. - -## `Meter` interface - -Meter is an interface consisting of methods to define a label set, to -create or delete instrument handles, and to record a batch of measurements. - -### `DefineLabels(labels) -> LabelSet` - -`DefineLabels` allows the SDK to process an ordered collection of -labels and return an opaque handle, allowing the caller to amortize -the cost by re-using the returned `LabelSet`. The `LabelSet` returned -has indefinite lifetime, allowing the application to hold on to it as -long as it remains useful. The `LabelSet` cannot be explicitly -deleted. - -LabelSets have map semantics. When multiple labels are present in -`DefineLabels()`, the last value is taken. The SDK is not required to -return a unique `LabelSet` object when called for equivalent sets of -labels. - -There is no limit on label set size imposed by the API, although SDKs -may choose to. Label sets may contain arbitrary labels. At the point -of use, a label set may contain more or fewer than the required set of -labels, in which case the additional labels may be used (e.g., -sampled) or reported by the SDK. - -### `NewHandle(Instrument, LabelSet) -> language-specific` - -`NewHandle` returns a language-specific interface to support -instrument handles. The user calls `Instrument.GetHandle(LabelSet)` -directly, which calls through to `NewHandle(Instrument, LabelSet)`. -The `NewHandle` method is not a user-facing API and the return type -will vary depending on the language. - -User-level method calls on the handle, `Add(value)`, `Set(value)`, and -`Record(value)`, interact with the result of `NewHandle` in a -language- and API-specific way. SDKs are expected to hold resources -corresponding to each handle, so users are encouraged to delete -handles when they are no longer in use. - -### `DeleteHandle(language-specific)` - -After the application is finished with an instrument handle, deleting -the handle will result in `DeleteHandle()` on the underlying, -language- and API-specific type. - -### `RecordBatch(LabelSet, measurements...)` - -`RecordBatch` offers primitive support for entering multiple -simultaneous measurements. The `Measurement` struct contains: - -- *Instrument*: the instrument `Descriptor` -- *Value*: the numerical value of the measurement event. - -Every measurement in the `RecordBatch` is associated with the -`LabelSet` argument (explicitly) and the current context and -resources (implicitly). - -### `RegisterObserver(Observer, callback)` - -Register an `Observer` instrument with the `Meter` to include it in -the metrics export pipeline. The SDK provides its own logic for when -to call the observer. When finished with an observer, use -`UnregisterObserver(Observer)`. - -## Metric `Descriptor` - -A metric instrument is completely described by its descriptor, through -arguments and optional parameters passed to the constructor. The -complete contents of a metric `Descriptor` are: - -- **Name** The unique name of this metric. Naming conventions are not discussed here. -- **Kind** An enumeration, one of `CounterKind`, `GaugeKind`, `ObserverKind`, or `MeasureKind` -- **Keys** The required label keys. -- **ID** A unique identifier associated with new instrument object. -- **Description** A string describing the meaning and use of this instrument. -- **Unit** The unit of measurement, optional. -- _Kind-specific options_ - - **NonMonotonic** (Counter): add positive and negative values - - **Monotonic** (Gauge): set a monotonic counter value - - **Signed** (Measure): record positive and negative values - -## `Instrument` classes - -`Counter`, `Gauge`, and `Measure` instruments have consistent method -names and behavior, with the exception of their respective action -verb, `Add()`, `Set()`, or `Record()`. These three are covered here. - -### `New` - -To construct a new instrument, the specific `Kind` and thus the return -value depend on the choice of (`Counter`, `Gauge`, or `Measure`). -`Name` is the only required parameter. `ID` is automatically assigned -by the API. The other fields (Description, Keys, Unit, -Kind-specific) are options to the various constructors in a -language-appropriate style. - -Metric units are defined according to the -[UCUM](http://unitsofmeasure.org/ucum.html) specification. - -Metric instruments are not associated with an SDK. They can be used -with multiple `Meter` instances in the same process. - -For example, - -``` -requestKeys = ["path", "host"] -requestBytes = NewIntCounter("request.bytes", - WithKeys(requestKeys), - WithUnit(unit.Bytes)) -requestLatency = NewFloatMeasure("request.latency", - WithKeys(requestKeys), - WithUnit(unit.Second)) -loadAvg60s = NewFloatGauge("load.avg.60s") -``` - -### `Add`, `Set`, `Record` - -Using a `LabelSet` obtained via `Meter.DefineLabels()` and an `Instrument` object directly, call the appropriate `Instrument.Action(LabelSet, value)` method, where `Action` is one of `Add`, `Set`, or `Record`. - -Continuing the example above, - -``` -labels = meter.DefineLabels({ "path": "/doc/{id}", "host": "h123", "port": 123, "frag": "#abc" }) - -requestBytes.Add(labels, req.bytes) -requestLatency.Record(labels, req.latency) -``` - -Operating on the instrument directly is expected to be relatively -fast, compared with defining a new label set, but not as fast as -operating through handles. - -Use `meter.DefineLabels()` as the label set for label-free metric instruments. - -### `Handle` operation - -Using a `LabelSet` obtained via `Meter.DefineLabels()` and an -`Instrument` object, call `GetHandle` to obtain a handle. The handle -should be stored for re-use. - -For example, using `labels` as shown above: - -``` -requestBytesHandle = requestBytes.GetHandle(labels) -requestLatencyHandle = requestLatency.GetHandle(labels) - -for req in requestBatch { - ... - requestsBytesHandle.Add(req.bytes) - requestsLatencyHandle.Record(req.latency) -} -``` - -Operating on handles should be faster than operating directly on the -instrument in the standard OpenTelemetry SDK. - -After the application finishes with the handle, it should be -explicitly deleted in a language-appropriate way, which must call -`Meter.DeleteHandle`. - -### `RecordBatch` operation - -Using a `LabelSet` obtained via `Meter.DefineLabels()` call -`RecordBatch(LabelSet, measurements)`, where `measurements` is a list -of (`Instrument`, `Value`) pairs. `RecordBatch` applies to the `Meter` -bound to the `LabelSet`. - -For example, to record two metrics simultaneously: - -``` -RecordBatch(labels, - Measurement(requestBytes, req.bytes), - Measurement(requestLatency, req.latency)) -``` - -## `Observer` instrument - -`Observer` instruments are like `Gauge` instruments, but they are -registered directly with the `Meter` instance, allowing the -application to provide a `Meter`-specific callback. Use -`Meter.RegisterObserver(Observer, Callback)` to register an observer -and begin exporting data. - -### `New` - -A new `Observer` is constructed similar to a new `Gauge`, supporting -the same options. The `Observer` instrument does not support `Set()` -or `GetHandle()`. - -### `Callback` interface - -The callback takes a (`Meter`, `Observer`) argument. The `Meter` and -`Observer` passed to a `Callback` match an individual registration. - -The callback returns a map from `LabelSet` to the current gauge value. -The return `LabelSets` must have been defined by the corresponding -`Meter` of the `Observer` registration. - -### `Callback` requirements - -Callbacks should avoid blocking. The implementation may be required to -cancel computation if the callback blocks for too long. - -Callbacks must not be called synchronously with application code via -any OpenTelemetry API. Implementations that cannot provide this -guarantee should prefer not to implement observer callbacks. - -Callbacks may be called synchronously in the SDK on behalf of an -exporter. - -Callbacks should avoid calling OpenTelemetry APIs, but we recognize -this may be impossible to enforce. From 74a18ae737575b1a02d014a493ca28a51ca7a4cb Mon Sep 17 00:00:00 2001 From: jmacd Date: Wed, 9 Oct 2019 17:06:33 -0700 Subject: [PATCH 12/17] Add detail on instrument calling conven tions --- specification/api-metrics-user.md | 342 +++++++++++++++++++++++++++++- 1 file changed, 338 insertions(+), 4 deletions(-) diff --git a/specification/api-metrics-user.md b/specification/api-metrics-user.md index 4f33f742f58..949caf26744 100644 --- a/specification/api-metrics-user.md +++ b/specification/api-metrics-user.md @@ -1,6 +1,340 @@ # Metric User-facing API -This document is a placeholder pending active discussion. It will -eventually contain portions that were removed from -https://github.com/open-telemetry/opentelemetry-specification/blob/3d2b8ecf410f62172a22a9fbff88304724d4cc78/specification/api-metrics.md -after further discussion. +TODO Table of contents. + +## Overview + +Metric instruments are the entry point for application and framework +developers to instrument their code using counters, gauges, and +measures. + +### Metric names + +Metric instruments have names, which are how we refer to them in +external systems. Metric names conform to the following syntax: + +1 They are non-empty strings +1 They are case-insensitive +1 The first character must be non-numeric, non-space, non-punctuation +1 Subsequent characters must be belong to the alphanumeric characters, '_', '.', and '-'. + +Metrics names belong to a namespace by virtue of a "Named" `Meter` +instance. A "Named" `Meter` refers to the requirement that every +`Meter` instance must have an associated `component` label, determined +statically in the code. The `component` label value of the associated +`Meter` serves as its namespace, allowing the same metric name to be +used in multiple libraries of code, unambiguously, within the same +application. + +Metric instruments are defined using a `Meter` instance, using a +variety of `New` methods. The Meter will return an error when a +metric name is registered with a different kind with the same +component name. Metric systems are expected to automatically prefix +exported metrics by the `component` namespace in a manner consistent +with the target system. For example, a Prometheus exporter SHOULD use +the component followed by `_` as the [application +prefix](https://prometheus.io/docs/practices/naming/#metric-names). + +### Format of a metric event + +Regardless of the instrument kind or method, metric events include the +instrument descriptor, a numerical value, and an optional set of +labels. The descriptor, discussed in detail below, contains the +metric name and various optional settiungs. Labels are key:value +pairs associated with events describing various dimensions or +categories that describe the event. A "label key" refers to the key +component while "label value" refers to the correlated value component +of a label. Label refers to the pair of label key and value. + +Metric events always have an associated `component` label, by virtue +of the named `Meter` used in their definition. Other labels are +passed in to the metric event in the form of a `LabelSet` argument, +using several input methods discussed below. + +### New constructors + +The `Meter` interface allows creating of a registered metric +instrument using methods specific to each kind of metric. There are +six constructors representing the three kinds of instrument taking +either floating point or integer inputs. + +| `Meter` method | Kind of instrument | +|-------|------| +| `NewIntCounter` | An integer counter | +| `NewFloatCounter` | A floating point counter | +| `NewIntGauge` | An integer gauge | +| `NewFloatGauge` | A floating point gauge | +| `NewIntMeasure` | An integer measure | +| `NewFloatMeasure` | A floating point measure | + +As in all OpenTelemetry specifications, these names are examples. +Each language committee will decide on the appropriate names based on +conventions in that language. + +Binding instruments to a single `Meter` instance has two benefits: + +1 The instruments are exported even in the zero state, prior to first use +1 The namespace provided by the named `Meter` satisfies a namespace requirement +1 There is no explicit `Register` call + +The recommended practice is to define structures to contain the +instruments in use and keep references only to the instruments that +are specifically needed. + +We recognize that many existing metric systems support allocating +metric instruments statically and providing the `Meter` interface at +the time-of-use. In this example, typical of statsd clients, existing +code may not be structured with a convenient place to store new metric +instruments. Where this becomes a burden, it may be acceptable to use +the global `Meter` as a workaround. + +The situation is similar for users of Prometheus clients, where +instruments are allocated statically and there is an implicit global. +Such code may not have access to the appropriate `Meter` where +instruments are defined. Where this becomes a burden, it may be +acceptable to use the global `Meter` as a workaround. + +#### Metric instrument descriptors + +An instrument `Descriptor` is a structure describing all the +configurable aspects of an instrument that the user can elect. +Although the API provides a common `Descriptor` type--not the +SDK--users do not construct these directly. Users pass all common +configuration options to the appropriate `Meter.New` method, which +itself will use a helper method provided by the API to build the new +`Descriptor`. Users can access the descriptor of the built +instrument, in any case. + +Given descripors and a `Meter` instance you can construct new +instruments that are ready to use. Applications are expected to +construct long-lived instruments. Instruments are considered +permanent, there is no method to delete them forget the metrics they +produce in the SDK. + +The structure of the descriptor and various options are given in +detail below. + +#### Metric instrument constructor example code + +In this Golang example, a struct holding four instruments is built +given the provided `Meter`. These calls give examples of the kind of +configuration options available for descriptors as well. + +```golang +type instruments struct { + counter1 metric.Int64Counter + counter2 metric.Float64Counter + gauge3 metric.Int64Gauge + measure4 metric.Float64Measure +} + +func newInstruments(metric.Meter meter) *instruments { + return &instruments{ + counter1: meter.NewCounter("counter1", metric.WithKeys("client.service")) + counter2: meter.NewCounter("counter2", metric.WithNonMonotonic(true)) + gauge3: meter.NewGauge("gauge3", metric.WithUnit(unit.Bytes)). + measure4: meter.NewMeasure("measure4", metric.WithDescription("Measure of ...")). + } +}``` + +Code will be structured to call `newInstruments` somewhere in a +constructor and keep the `instruments` reference for use at runtime. +Here's an example of building a server with configured instruments and +a single metric operation. + +```golang +type server struct { + meter metric.Meter + instruments *instruments + + // ... other fields +} + +func newServer(meter metric.Meter) *server { + return &server{ + meter: meter, + instruments: newInstruments(meter), + + // ... other fields + } +} + +// ... + +func (s *server) operate(ctx context.Context) { + // ... other work + + s.counter1.Add(ctx, 1, s.meter.Labels( + label1.String("..."), + label2.String("..."))) +} +``` + +### Metric calling conventions + +This API is factored into three core concepts: instruments, handles, +and label sets. In doing so, we provide several ways of capturing +measurements that are semantically equivalent and generate equivalent +metric events, but offer varying degrees of performance and +convenience. + +#### Metric handle calling convention + +As described above, metric events consist of an instrument, a set of +labels, and a numerical value. The performance of a metric API +depends on the steps taken to enter a new measurement. One approach +to reduce cost is to pre-aggregate results, so that subsequent events +in the same collection period combine into the same working memory. + +This approach requires locating an entry for the instrument and label +set in a table of some kind, finding the place where a group of metric +events are being aggregated. This lookup can be successfully +precomputed, giving rise to the Handle calling convention. + +In situations where performance is a requirement and a metric is +repeatedly used with the same set of labels, the developer may elect +to use Handles as an optimization. For handles to be a benefit, it +requires that a specific instrument will be re-used with specific +labels. + +To obtain a handle given an instrument and label set, use the +`GetHandle()` method to return an interface that supports the `Add()`, +`Set()`, or `Record()` method of the instrument in question. + +A high-performace metrics SDK will take steps to ensure that +operations on handles are very fast. Application developers are +required to delete handles when they are no-longer in use. + +```golang +func (s *server) processStream(ctx context.Context) { + + streamLabels = []core.KeyValue{ + labelA.String("..."), + labelB.String("..."), + } + counter2Handle := s.instruments.counter2.GetHandle(streamLabels) + + for _, item := <-s.channel { + // ... other work + + // High-performance metric calling convention: use of handles. + counter2Handle.Add(ctx, item.size()) + } +} +``` + +#### Direct metric calling convention + +When convenience is more important than performance, or there is no +re-use to potentially optimize, users may elect to operate directly on +metric instruments, supplying a label set at the call site. + +For example, to update a single counter: + +```golang +func (s *server) method(ctx context.Context) { + // ... other work + + s.instruments.counter1.Add(ctx, 1, s.meter.Labels(...)) +} +``` + +This method offers the greatest convenience possible. If performance +becomes a problem, one option is to use handles as described above. +Another performance option, in some cases, is to just re-use the +labels. In the example here, `meter.Labels(...)` constructs a +re-usable label set which may be a useful performance optimization, as +discussed next. + +#### Label set calling convention + +A significant factor in the cost of metrics export is that labels, +which arrive as an unordered list of keys and values, must be +canonicalized in some way before they can be used for lookup. +Canonicalizing labels can be an expensive operation as it may require +sorting or de-duplicating by some other means, possibly even +serializing, the set of labels to produce a valid map key. + +The operation of converting an unordered set of labels into a +canonicalized set of labels, useful for pre-aggregation, is expensive +enough that we give it first-class treatment in the API. The +`meter.Labels(...)` API canonicalizes labels, returning an opaque +`LabelSet` object, another form of pre-computation available to the +user. + +Re-usable `LabelSet` objects provide a potential optimization for +scenarios where handles might not be effective. For example, if the +label set will be re-used but only used once per metric, handles do +not offer any optimization. It may be best to pre-compute a +canonicalized `LabelSet` once and re-use it with the direct calling +convention. + +```golang +func (s *server) method(ctx context.Context) { + // ... other work + + labelSet := s.meter.Labels(...) + + s.instruments.counter1.Add(ctx, 1, labelSet) + + // ... more work + + s.instruments.gauge1.Set(ctx, 10, labelSet) + + // ... more work + + s.instruments.measure1.Record(ctx, 100, labelSet) +} +``` + +#### RecordBatch calling convention + +There is one final API for entering measurements, which is like the +direct access calling convention but supports multiple simultaneous +measurements. The use of a RecordBatch API supports entering multiple +measurements, implying a semantically atomic update to several +instruments. Using the RecordBatch calling convention is otherwise +like the direct access calling convention. + +The preceding example could be rewritten: + +```golang +func (s *server) method(ctx context.Context) { + // ... other work + + labelSet := s.meter.Labels(...) + + // ... more work + + s.meter.RecordBatch(ctx, labelSet, []metric.Measurement{ + { s.instruments.counter1, 1 }, + { s.instruments.gauge1, 10 }, + { s.instruments.measure1, 100 }, + }) +} +``` + +## Detailed specification + +See the [SDK-facing Metrics API](api-metrics-meter.md) specification +for an in-depth summary of each method in the Metrics API. + +TODO more specifics on constructing metrics and the meaning of "recommended keys". + +### Metric Descriptor + +A metric instrument is completely described by its descriptor, through +arguments and optional parameters passed to the constructor. The +complete contents of a metric `Descriptor` are: + +- **Name** The unique name of this metric. Naming conventions are not discussed here. +- **Kind** An enumeration, one of `CounterKind`, `GaugeKind`, `ObserverKind`, or `MeasureKind` +- **Keys** The recommended label keys. +- **ID** A unique identifier associated with new instrument object. +- **Description** A string describing the meaning and use of this instrument. +- **Unit** The unit of measurement, optional. +- _Kind-specific options_ + - **NonMonotonic** (Counter): add positive and negative values + - **Monotonic** (Gauge): set a monotonic counter value + - **Signed** (Measure): record positive and negative values + From bed6a8166a83af7d1253f8b31252baf8ad78c30e Mon Sep 17 00:00:00 2001 From: jmacd Date: Wed, 9 Oct 2019 17:13:09 -0700 Subject: [PATCH 13/17] Format --- specification/api-metrics-user.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specification/api-metrics-user.md b/specification/api-metrics-user.md index 949caf26744..f176ee6d4fb 100644 --- a/specification/api-metrics-user.md +++ b/specification/api-metrics-user.md @@ -13,10 +13,10 @@ measures. Metric instruments have names, which are how we refer to them in external systems. Metric names conform to the following syntax: -1 They are non-empty strings -1 They are case-insensitive -1 The first character must be non-numeric, non-space, non-punctuation -1 Subsequent characters must be belong to the alphanumeric characters, '_', '.', and '-'. +1. They are non-empty strings +1. They are case-insensitive +1. The first character must be non-numeric, non-space, non-punctuation +1. Subsequent characters must be belong to the alphanumeric characters, '_', '.', and '-'. Metrics names belong to a namespace by virtue of a "Named" `Meter` instance. A "Named" `Meter` refers to the requirement that every @@ -73,9 +73,9 @@ conventions in that language. Binding instruments to a single `Meter` instance has two benefits: -1 The instruments are exported even in the zero state, prior to first use -1 The namespace provided by the named `Meter` satisfies a namespace requirement -1 There is no explicit `Register` call +1. The instruments are exported even in the zero state, prior to first use +1. The namespace provided by the named `Meter` satisfies a namespace requirement +1. There is no explicit `Register` call The recommended practice is to define structures to contain the instruments in use and keep references only to the instruments that From 699c04a26025c7e76bafebaef34694a3a4936b19 Mon Sep 17 00:00:00 2001 From: jmacd Date: Wed, 9 Oct 2019 17:16:21 -0700 Subject: [PATCH 14/17] Format --- specification/api-metrics-user.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specification/api-metrics-user.md b/specification/api-metrics-user.md index f176ee6d4fb..5fca8540aac 100644 --- a/specification/api-metrics-user.md +++ b/specification/api-metrics-user.md @@ -135,7 +135,8 @@ func newInstruments(metric.Meter meter) *instruments { gauge3: meter.NewGauge("gauge3", metric.WithUnit(unit.Bytes)). measure4: meter.NewMeasure("measure4", metric.WithDescription("Measure of ...")). } -}``` +} +``` Code will be structured to call `newInstruments` somewhere in a constructor and keep the `instruments` reference for use at runtime. From 967cd653ec87f6cda913e05acad066473d03585d Mon Sep 17 00:00:00 2001 From: jmacd Date: Thu, 10 Oct 2019 11:13:40 -0700 Subject: [PATCH 15/17] Revert new stuff --- specification/api-metrics-user.md | 345 +----------------------------- 1 file changed, 5 insertions(+), 340 deletions(-) diff --git a/specification/api-metrics-user.md b/specification/api-metrics-user.md index 5fca8540aac..f43b780a557 100644 --- a/specification/api-metrics-user.md +++ b/specification/api-metrics-user.md @@ -1,341 +1,6 @@ -# Metric User-facing API - -TODO Table of contents. - -## Overview - -Metric instruments are the entry point for application and framework -developers to instrument their code using counters, gauges, and -measures. - -### Metric names - -Metric instruments have names, which are how we refer to them in -external systems. Metric names conform to the following syntax: - -1. They are non-empty strings -1. They are case-insensitive -1. The first character must be non-numeric, non-space, non-punctuation -1. Subsequent characters must be belong to the alphanumeric characters, '_', '.', and '-'. - -Metrics names belong to a namespace by virtue of a "Named" `Meter` -instance. A "Named" `Meter` refers to the requirement that every -`Meter` instance must have an associated `component` label, determined -statically in the code. The `component` label value of the associated -`Meter` serves as its namespace, allowing the same metric name to be -used in multiple libraries of code, unambiguously, within the same -application. - -Metric instruments are defined using a `Meter` instance, using a -variety of `New` methods. The Meter will return an error when a -metric name is registered with a different kind with the same -component name. Metric systems are expected to automatically prefix -exported metrics by the `component` namespace in a manner consistent -with the target system. For example, a Prometheus exporter SHOULD use -the component followed by `_` as the [application -prefix](https://prometheus.io/docs/practices/naming/#metric-names). - -### Format of a metric event - -Regardless of the instrument kind or method, metric events include the -instrument descriptor, a numerical value, and an optional set of -labels. The descriptor, discussed in detail below, contains the -metric name and various optional settiungs. Labels are key:value -pairs associated with events describing various dimensions or -categories that describe the event. A "label key" refers to the key -component while "label value" refers to the correlated value component -of a label. Label refers to the pair of label key and value. - -Metric events always have an associated `component` label, by virtue -of the named `Meter` used in their definition. Other labels are -passed in to the metric event in the form of a `LabelSet` argument, -using several input methods discussed below. - -### New constructors - -The `Meter` interface allows creating of a registered metric -instrument using methods specific to each kind of metric. There are -six constructors representing the three kinds of instrument taking -either floating point or integer inputs. - -| `Meter` method | Kind of instrument | -|-------|------| -| `NewIntCounter` | An integer counter | -| `NewFloatCounter` | A floating point counter | -| `NewIntGauge` | An integer gauge | -| `NewFloatGauge` | A floating point gauge | -| `NewIntMeasure` | An integer measure | -| `NewFloatMeasure` | A floating point measure | - -As in all OpenTelemetry specifications, these names are examples. -Each language committee will decide on the appropriate names based on -conventions in that language. - -Binding instruments to a single `Meter` instance has two benefits: - -1. The instruments are exported even in the zero state, prior to first use -1. The namespace provided by the named `Meter` satisfies a namespace requirement -1. There is no explicit `Register` call - -The recommended practice is to define structures to contain the -instruments in use and keep references only to the instruments that -are specifically needed. - -We recognize that many existing metric systems support allocating -metric instruments statically and providing the `Meter` interface at -the time-of-use. In this example, typical of statsd clients, existing -code may not be structured with a convenient place to store new metric -instruments. Where this becomes a burden, it may be acceptable to use -the global `Meter` as a workaround. - -The situation is similar for users of Prometheus clients, where -instruments are allocated statically and there is an implicit global. -Such code may not have access to the appropriate `Meter` where -instruments are defined. Where this becomes a burden, it may be -acceptable to use the global `Meter` as a workaround. - -#### Metric instrument descriptors - -An instrument `Descriptor` is a structure describing all the -configurable aspects of an instrument that the user can elect. -Although the API provides a common `Descriptor` type--not the -SDK--users do not construct these directly. Users pass all common -configuration options to the appropriate `Meter.New` method, which -itself will use a helper method provided by the API to build the new -`Descriptor`. Users can access the descriptor of the built -instrument, in any case. - -Given descripors and a `Meter` instance you can construct new -instruments that are ready to use. Applications are expected to -construct long-lived instruments. Instruments are considered -permanent, there is no method to delete them forget the metrics they -produce in the SDK. - -The structure of the descriptor and various options are given in -detail below. - -#### Metric instrument constructor example code - -In this Golang example, a struct holding four instruments is built -given the provided `Meter`. These calls give examples of the kind of -configuration options available for descriptors as well. - -```golang -type instruments struct { - counter1 metric.Int64Counter - counter2 metric.Float64Counter - gauge3 metric.Int64Gauge - measure4 metric.Float64Measure -} - -func newInstruments(metric.Meter meter) *instruments { - return &instruments{ - counter1: meter.NewCounter("counter1", metric.WithKeys("client.service")) - counter2: meter.NewCounter("counter2", metric.WithNonMonotonic(true)) - gauge3: meter.NewGauge("gauge3", metric.WithUnit(unit.Bytes)). - measure4: meter.NewMeasure("measure4", metric.WithDescription("Measure of ...")). - } -} -``` - -Code will be structured to call `newInstruments` somewhere in a -constructor and keep the `instruments` reference for use at runtime. -Here's an example of building a server with configured instruments and -a single metric operation. - -```golang -type server struct { - meter metric.Meter - instruments *instruments - - // ... other fields -} - -func newServer(meter metric.Meter) *server { - return &server{ - meter: meter, - instruments: newInstruments(meter), - - // ... other fields - } -} - -// ... - -func (s *server) operate(ctx context.Context) { - // ... other work - - s.counter1.Add(ctx, 1, s.meter.Labels( - label1.String("..."), - label2.String("..."))) -} -``` - -### Metric calling conventions - -This API is factored into three core concepts: instruments, handles, -and label sets. In doing so, we provide several ways of capturing -measurements that are semantically equivalent and generate equivalent -metric events, but offer varying degrees of performance and -convenience. - -#### Metric handle calling convention - -As described above, metric events consist of an instrument, a set of -labels, and a numerical value. The performance of a metric API -depends on the steps taken to enter a new measurement. One approach -to reduce cost is to pre-aggregate results, so that subsequent events -in the same collection period combine into the same working memory. - -This approach requires locating an entry for the instrument and label -set in a table of some kind, finding the place where a group of metric -events are being aggregated. This lookup can be successfully -precomputed, giving rise to the Handle calling convention. - -In situations where performance is a requirement and a metric is -repeatedly used with the same set of labels, the developer may elect -to use Handles as an optimization. For handles to be a benefit, it -requires that a specific instrument will be re-used with specific -labels. - -To obtain a handle given an instrument and label set, use the -`GetHandle()` method to return an interface that supports the `Add()`, -`Set()`, or `Record()` method of the instrument in question. - -A high-performace metrics SDK will take steps to ensure that -operations on handles are very fast. Application developers are -required to delete handles when they are no-longer in use. - -```golang -func (s *server) processStream(ctx context.Context) { - - streamLabels = []core.KeyValue{ - labelA.String("..."), - labelB.String("..."), - } - counter2Handle := s.instruments.counter2.GetHandle(streamLabels) - - for _, item := <-s.channel { - // ... other work - - // High-performance metric calling convention: use of handles. - counter2Handle.Add(ctx, item.size()) - } -} -``` - -#### Direct metric calling convention - -When convenience is more important than performance, or there is no -re-use to potentially optimize, users may elect to operate directly on -metric instruments, supplying a label set at the call site. - -For example, to update a single counter: - -```golang -func (s *server) method(ctx context.Context) { - // ... other work - - s.instruments.counter1.Add(ctx, 1, s.meter.Labels(...)) -} -``` - -This method offers the greatest convenience possible. If performance -becomes a problem, one option is to use handles as described above. -Another performance option, in some cases, is to just re-use the -labels. In the example here, `meter.Labels(...)` constructs a -re-usable label set which may be a useful performance optimization, as -discussed next. - -#### Label set calling convention - -A significant factor in the cost of metrics export is that labels, -which arrive as an unordered list of keys and values, must be -canonicalized in some way before they can be used for lookup. -Canonicalizing labels can be an expensive operation as it may require -sorting or de-duplicating by some other means, possibly even -serializing, the set of labels to produce a valid map key. - -The operation of converting an unordered set of labels into a -canonicalized set of labels, useful for pre-aggregation, is expensive -enough that we give it first-class treatment in the API. The -`meter.Labels(...)` API canonicalizes labels, returning an opaque -`LabelSet` object, another form of pre-computation available to the -user. - -Re-usable `LabelSet` objects provide a potential optimization for -scenarios where handles might not be effective. For example, if the -label set will be re-used but only used once per metric, handles do -not offer any optimization. It may be best to pre-compute a -canonicalized `LabelSet` once and re-use it with the direct calling -convention. - -```golang -func (s *server) method(ctx context.Context) { - // ... other work - - labelSet := s.meter.Labels(...) - - s.instruments.counter1.Add(ctx, 1, labelSet) - - // ... more work - - s.instruments.gauge1.Set(ctx, 10, labelSet) - - // ... more work - - s.instruments.measure1.Record(ctx, 100, labelSet) -} -``` - -#### RecordBatch calling convention - -There is one final API for entering measurements, which is like the -direct access calling convention but supports multiple simultaneous -measurements. The use of a RecordBatch API supports entering multiple -measurements, implying a semantically atomic update to several -instruments. Using the RecordBatch calling convention is otherwise -like the direct access calling convention. - -The preceding example could be rewritten: - -```golang -func (s *server) method(ctx context.Context) { - // ... other work - - labelSet := s.meter.Labels(...) - - // ... more work - - s.meter.RecordBatch(ctx, labelSet, []metric.Measurement{ - { s.instruments.counter1, 1 }, - { s.instruments.gauge1, 10 }, - { s.instruments.measure1, 100 }, - }) -} -``` - -## Detailed specification - -See the [SDK-facing Metrics API](api-metrics-meter.md) specification -for an in-depth summary of each method in the Metrics API. - -TODO more specifics on constructing metrics and the meaning of "recommended keys". - -### Metric Descriptor - -A metric instrument is completely described by its descriptor, through -arguments and optional parameters passed to the constructor. The -complete contents of a metric `Descriptor` are: - -- **Name** The unique name of this metric. Naming conventions are not discussed here. -- **Kind** An enumeration, one of `CounterKind`, `GaugeKind`, `ObserverKind`, or `MeasureKind` -- **Keys** The recommended label keys. -- **ID** A unique identifier associated with new instrument object. -- **Description** A string describing the meaning and use of this instrument. -- **Unit** The unit of measurement, optional. -- _Kind-specific options_ - - **NonMonotonic** (Counter): add positive and negative values - - **Monotonic** (Gauge): set a monotonic counter value - - **Signed** (Measure): record positive and negative values +# Metric SDK-facing API +This document is a placeholder pending active discussion. It will +eventually contain portions that were removed from +https://github.com/open-telemetry/opentelemetry-specification/blob/3d2b8ecf410f62172a22a9fbff88304724d4cc78/specification/api-metrics.md +after further discussion. From f90a61a6f58c5b22b1d7a9f92e3facd128d0ec46 Mon Sep 17 00:00:00 2001 From: jmacd Date: Thu, 10 Oct 2019 11:18:52 -0700 Subject: [PATCH 16/17] Add clarification on gauge inputs --- specification/api-metrics.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specification/api-metrics.md b/specification/api-metrics.md index 58a6d338d20..c7e9326051b 100644 --- a/specification/api-metrics.md +++ b/specification/api-metrics.md @@ -135,8 +135,9 @@ measurement interval is arbitrary. Use this kind of metric when the measurement is not a quantity, and the sum and event count are not of interest. -Gauges are defined as non-monotonic by default, meaning that any value -(positive or negative) is allowed. +Gauges are defined as non-monotonic by default, meaning that new +values are permitted to make positive or negative changes to the +gauge. There is no restriction on the sign of the input for gauges. As an option, gauges can be declared as `Monotonic`, in which case successive values are expected to rise monotonically. Monotonic From b23d47816bd522a5d0bdf191c93a52b7c14db39d Mon Sep 17 00:00:00 2001 From: jmacd Date: Thu, 10 Oct 2019 11:19:43 -0700 Subject: [PATCH 17/17] Revert new stuff --- specification/api-metrics-user.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/api-metrics-user.md b/specification/api-metrics-user.md index f43b780a557..4f33f742f58 100644 --- a/specification/api-metrics-user.md +++ b/specification/api-metrics-user.md @@ -1,4 +1,4 @@ -# Metric SDK-facing API +# Metric User-facing API This document is a placeholder pending active discussion. It will eventually contain portions that were removed from