diff --git a/contrib/jmx-metrics/README.md b/contrib/jmx-metrics/README.md index 22fe7435..65c4cad3 100644 --- a/contrib/jmx-metrics/README.md +++ b/contrib/jmx-metrics/README.md @@ -101,7 +101,31 @@ via `otel.jmx.groovy.script`, it will then run the script on the specified - `otel.longValueRecorder(String name, String description, String unit, Map labels)` These methods will return a new or previously registered instance of the applicable metric -instruments. Each one provides three additional signatures where labels, unit, and description +instruments. Each one provides three additional signatures where labels, unit, and description +aren't desired upon invocation. + +- `otel.(String name, String description, String unit)` - `labels` are empty map. + +- `otel.(String name, String description)` - `unit` is "1" and `labels` are empty map. + +- `otel.(String name)` - `description` is empty string, `unit` is "1" and `labels` are empty map. + +### OpenTelemetry Asynchronous Instrument Helpers + +- `otel.doubleSumObserver(String name, String description, String unit, Map labels)` + +- `otel.longSumObserver(String name, String description, String unit, Map labels)` + +- `otel.doubleUpDownSumObserver(String name, String description, String unit, Map labels)` + +- `otel.longUpDownSumObserver(String name, String description, String unit, Map labels)` + +- `otel.doubleValueObserver(String name, String description, String unit, Map labels)` + +- `otel.longValueObserver(String name, String description, String unit, Map labels)` + +These methods will return a new or previously registered instance of the applicable metric +instruments. Each one provides three additional signatures where labels, unit, and description aren't desired upon invocation. - `otel.(String name, String description, String unit)` - `labels` are empty map. diff --git a/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index cb1d2a63..28441016 100644 --- a/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -17,9 +17,16 @@ package io.opentelemetry.contrib.jmxmetrics import io.opentelemetry.metrics.DoubleCounter +import io.opentelemetry.metrics.DoubleSumObserver import io.opentelemetry.metrics.DoubleUpDownCounter +import io.opentelemetry.metrics.DoubleUpDownSumObserver +import io.opentelemetry.metrics.DoubleValueObserver import io.opentelemetry.metrics.LongCounter +import io.opentelemetry.metrics.LongSumObserver import io.opentelemetry.metrics.LongUpDownCounter +import io.opentelemetry.metrics.LongUpDownSumObserver +import io.opentelemetry.metrics.LongValueObserver + import java.util.logging.Logger import javax.management.openmbean.CompositeData @@ -89,6 +96,11 @@ class InstrumentHelper { return } + // Observer instruments need to have a single callback set, so pool all update + // operations in a list of closures per instrument to be executed after all values + // are established, potentially as the callback. + def instToUpdates = [:] + [mbeans, values].transpose().each { mbean, value -> if (value instanceof CompositeData) { value.getCompositeType().keySet().each { key -> @@ -96,14 +108,36 @@ class InstrumentHelper { def updatedInstrumentName = "${instrumentName}.${key}" def labels = getLabels(mbean, labelFuncs) def inst = instrument(updatedInstrumentName, description, unit) + println "InstrumentHelper.update (composite) - ${inst}" logger.fine("Recording ${updatedInstrumentName} - ${inst} w/ ${val} - ${labels}") - updateInstrumentWithValue(inst, val, labels) + if (!instToUpdates.containsKey(inst)) { + instToUpdates[inst] = [] + } + instToUpdates[inst].add(prepareUpdateClosure(inst, val, labels)) } } else { def labels = getLabels(mbean, labelFuncs) def inst = instrument(instrumentName, description, unit) + println "InstrumentHelper.update - ${inst}" logger.fine("Recording ${instrumentName} - ${inst} w/ ${value} - ${labels}") - updateInstrumentWithValue(inst, value, labels) + if (!instToUpdates.containsKey(inst)) { + instToUpdates[inst] = [] + } + instToUpdates[inst].add(prepareUpdateClosure(inst, value, labels)) + } + } + + instToUpdates.each {inst, updateClosures -> + if (instrumentIsObserver(inst)) { + inst.setCallback({ result -> + updateClosures.each { update -> + update(result) + } + }) + } else { + updateClosures.each { + it(inst) + } } } } @@ -116,15 +150,32 @@ class InstrumentHelper { return labels } - private static void updateInstrumentWithValue(inst, value, labels) { + private static Closure prepareUpdateClosure(inst, value, labels) { def labelMap = GroovyMetricEnvironment.mapToLabels(labels) - if (inst instanceof DoubleCounter - || inst instanceof DoubleUpDownCounter - || inst instanceof LongCounter - || inst instanceof LongUpDownCounter) { - inst.add(value, labelMap) + if (instrumentIsObserver(inst)) { + return { result -> + result.observe(value, labelMap) + } + } else if (instrumentIsCounter(inst)) { + return { i -> i.add(value, labelMap) } } else { - inst.record(value, labelMap) + return { i -> i.record(value, labelMap) } } } + + private static boolean instrumentIsObserver(inst) { + return (inst instanceof DoubleSumObserver + || inst instanceof DoubleUpDownSumObserver + || inst instanceof LongSumObserver + || inst instanceof LongUpDownSumObserver + || inst instanceof DoubleValueObserver + || inst instanceof LongValueObserver) + } + + private static boolean instrumentIsCounter(inst) { + return (inst instanceof DoubleCounter + || inst instanceof DoubleUpDownCounter + || inst instanceof LongCounter + || inst instanceof LongUpDownCounter) + } } diff --git a/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy b/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy index 6e370c3f..b56ab457 100644 --- a/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy +++ b/contrib/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy @@ -17,10 +17,16 @@ package io.opentelemetry.contrib.jmxmetrics import io.opentelemetry.metrics.DoubleCounter +import io.opentelemetry.metrics.DoubleSumObserver import io.opentelemetry.metrics.DoubleUpDownCounter +import io.opentelemetry.metrics.DoubleUpDownSumObserver +import io.opentelemetry.metrics.DoubleValueObserver import io.opentelemetry.metrics.DoubleValueRecorder import io.opentelemetry.metrics.LongCounter +import io.opentelemetry.metrics.LongSumObserver import io.opentelemetry.metrics.LongUpDownCounter +import io.opentelemetry.metrics.LongUpDownSumObserver +import io.opentelemetry.metrics.LongValueObserver import io.opentelemetry.metrics.LongValueRecorder import javax.management.ObjectName @@ -196,4 +202,100 @@ class OtelHelper { LongValueRecorder longValueRecorder(String name) { return longValueRecorder(name, '') } + + DoubleSumObserver doubleSumObserver(String name, String description, String unit, Map labels) { + return groovyMetricEnvironment.getDoubleSumObserver(name, description, unit, labels) + } + + DoubleSumObserver doubleSumObserver(String name, String description, String unit) { + return doubleSumObserver(name, description, unit, null) + } + + DoubleSumObserver doubleSumObserver(String name, String description) { + return doubleSumObserver(name, description, SCALAR) + } + + DoubleSumObserver doubleSumObserver(String name) { + return doubleSumObserver(name, '') + } + + LongSumObserver longSumObserver(String name, String description, String unit, Map labels) { + return groovyMetricEnvironment.getLongSumObserver(name, description, unit, labels) + } + + LongSumObserver longSumObserver(String name, String description, String unit) { + return longSumObserver(name, description, unit, null) + } + + LongSumObserver longSumObserver(String name, String description) { + return longSumObserver(name, description, SCALAR) + } + + LongSumObserver longSumObserver(String name) { + return longSumObserver(name, '') + } + + DoubleUpDownSumObserver doubleUpDownSumObserver(String name, String description, String unit, Map labels) { + return groovyMetricEnvironment.getDoubleUpDownSumObserver(name, description, unit, labels) + } + + DoubleUpDownSumObserver doubleUpDownSumObserver(String name, String description, String unit) { + return doubleUpDownSumObserver(name, description, unit, null) + } + + DoubleUpDownSumObserver doubleUpDownSumObserver(String name, String description) { + return doubleUpDownSumObserver(name, description, SCALAR) + } + + DoubleUpDownSumObserver doubleUpDownSumObserver(String name) { + return doubleUpDownSumObserver(name, '') + } + + LongUpDownSumObserver longUpDownSumObserver(String name, String description, String unit, Map labels) { + return groovyMetricEnvironment.getLongUpDownSumObserver(name, description, unit, labels) + } + + LongUpDownSumObserver longUpDownSumObserver(String name, String description, String unit) { + return longUpDownSumObserver(name, description, unit, null) + } + + LongUpDownSumObserver longUpDownSumObserver(String name, String description) { + return longUpDownSumObserver(name, description, SCALAR) + } + + LongUpDownSumObserver longUpDownSumObserver(String name) { + return longUpDownSumObserver(name, '') + } + + DoubleValueObserver doubleValueObserver(String name, String description, String unit, Map labels) { + return groovyMetricEnvironment.getDoubleValueObserver(name, description, unit, labels) + } + + DoubleValueObserver doubleValueObserver(String name, String description, String unit) { + return doubleValueObserver(name, description, unit, null) + } + + DoubleValueObserver doubleValueObserver(String name, String description) { + return doubleValueObserver(name, description, SCALAR) + } + + DoubleValueObserver doubleValueObserver(String name) { + return doubleValueObserver(name, '') + } + + LongValueObserver longValueObserver(String name, String description, String unit, Map labels) { + return groovyMetricEnvironment.getLongValueObserver(name, description, unit, labels) + } + + LongValueObserver longValueObserver(String name, String description, String unit) { + return longValueObserver(name, description, unit, null) + } + + LongValueObserver longValueObserver(String name, String description) { + return longValueObserver(name, description, SCALAR) + } + + LongValueObserver longValueObserver(String name) { + return longValueObserver(name, '') + } } diff --git a/contrib/jmx-metrics/src/main/java/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/contrib/jmx-metrics/src/main/java/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index 6d62f17f..de217298 100644 --- a/contrib/jmx-metrics/src/main/java/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/contrib/jmx-metrics/src/main/java/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -23,10 +23,16 @@ import io.opentelemetry.exporters.logging.LoggingMetricExporter; import io.opentelemetry.exporters.otlp.OtlpGrpcMetricExporter; import io.opentelemetry.exporters.prometheus.PrometheusCollector; import io.opentelemetry.metrics.DoubleCounter; +import io.opentelemetry.metrics.DoubleSumObserver; import io.opentelemetry.metrics.DoubleUpDownCounter; +import io.opentelemetry.metrics.DoubleUpDownSumObserver; +import io.opentelemetry.metrics.DoubleValueObserver; import io.opentelemetry.metrics.DoubleValueRecorder; import io.opentelemetry.metrics.LongCounter; +import io.opentelemetry.metrics.LongSumObserver; import io.opentelemetry.metrics.LongUpDownCounter; +import io.opentelemetry.metrics.LongUpDownSumObserver; +import io.opentelemetry.metrics.LongValueObserver; import io.opentelemetry.metrics.LongValueRecorder; import io.opentelemetry.metrics.Meter; import io.opentelemetry.sdk.OpenTelemetrySdk; @@ -258,4 +264,142 @@ public class GroovyMetricEnvironment { .setConstantLabels(labels) .build(); } + + /** + * Build or retrieve previously registered {@link DoubleSumObserver}. + * + * @param name - metric name + * @param description metric description + * @param unit - metric unit + * @param constantLabels - metric descriptor's constant labels + * @return new or memoized {@link DoubleSumObserver} + */ + public DoubleSumObserver getDoubleSumObserver( + final String name, + final String description, + final String unit, + final Map constantLabels) { + Labels labels = mapToLabels(constantLabels); + return meter + .doubleSumObserverBuilder(name) + .setDescription(description) + .setUnit(unit) + .setConstantLabels(labels) + .build(); + } + + /** + * Build or retrieve previously registered {@link LongSumObserver}. + * + * @param name - metric name + * @param description metric description + * @param unit - metric unit + * @param constantLabels - metric descriptor's constant labels + * @return new or memoized {@link LongSumObserver} + */ + public LongSumObserver getLongSumObserver( + final String name, + final String description, + final String unit, + final Map constantLabels) { + Labels labels = mapToLabels(constantLabels); + return meter + .longSumObserverBuilder(name) + .setDescription(description) + .setUnit(unit) + .setConstantLabels(labels) + .build(); + } + + /** + * Build or retrieve previously registered {@link DoubleUpDownSumObserver}. + * + * @param name - metric name + * @param description metric description + * @param unit - metric unit + * @param constantLabels - metric descriptor's constant labels + * @return new or memoized {@link DoubleUpDownSumObserver} + */ + public DoubleUpDownSumObserver getDoubleUpDownSumObserver( + final String name, + final String description, + final String unit, + final Map constantLabels) { + Labels labels = mapToLabels(constantLabels); + return meter + .doubleUpDownSumObserverBuilder(name) + .setDescription(description) + .setUnit(unit) + .setConstantLabels(labels) + .build(); + } + + /** + * Build or retrieve previously registered {@link LongUpDownSumObserver}. + * + * @param name - metric name + * @param description metric description + * @param unit - metric unit + * @param constantLabels - metric descriptor's constant labels + * @return new or memoized {@link LongUpDownSumObserver} + */ + public LongUpDownSumObserver getLongUpDownSumObserver( + final String name, + final String description, + final String unit, + final Map constantLabels) { + Labels labels = mapToLabels(constantLabels); + return meter + .longUpDownSumObserverBuilder(name) + .setDescription(description) + .setUnit(unit) + .setConstantLabels(labels) + .build(); + } + + /** + * Build or retrieve previously registered {@link DoubleValueObserver}. + * + * @param name - metric name + * @param description metric description + * @param unit - metric unit + * @param constantLabels - metric descriptor's constant labels + * @return new or memoized {@link DoubleValueObserver} + */ + public DoubleValueObserver getDoubleValueObserver( + final String name, + final String description, + final String unit, + final Map constantLabels) { + Labels labels = mapToLabels(constantLabels); + return meter + .doubleValueObserverBuilder(name) + .setDescription(description) + .setUnit(unit) + .setConstantLabels(labels) + .build(); + } + + /** + * Build or retrieve previously registered {@link LongValueObserver}. + * + * @param name - metric name + * @param description metric description + * @param unit - metric unit + * @param constantLabels - metric descriptor's constant labels + * @return new or memoized {@link LongValueObserver} + */ + public LongValueObserver getLongValueObserver( + final String name, + final String description, + final String unit, + final Map constantLabels) { + Labels labels = mapToLabels(constantLabels); + return meter + .longValueObserverBuilder(name) + .setDescription(description) + .setUnit(unit) + .setConstantLabels(labels) + .build(); + } } diff --git a/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelperTest.groovy b/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelperTest.groovy index 580d2739..39d06bcb 100644 --- a/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelperTest.groovy +++ b/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelperTest.groovy @@ -175,5 +175,17 @@ class InstrumentHelperTest extends Specification { false | "multiple" | "Double" | "doubleValueRecorder" | SUMMARY | 123.456 true | "single" | "Long" | "longValueRecorder" | SUMMARY | 234 false | "multiple" | "Long" | "longValueRecorder" | SUMMARY | 234 + true | "single" | "Double" | "doubleSumObserver" | MONOTONIC_DOUBLE | 123.456 + false | "multiple" | "Double" | "doubleSumObserver" | MONOTONIC_DOUBLE | 123.456 + true | "single" | "Double" | "doubleUpDownSumObserver" | NON_MONOTONIC_DOUBLE | 123.456 + false | "multiple" | "Double" | "doubleUpDownSumObserver" | NON_MONOTONIC_DOUBLE | 123.456 + true | "single" | "Long" | "longSumObserver" | MONOTONIC_LONG | 234 + false | "multiple" | "Long" | "longSumObserver" | MONOTONIC_LONG | 234 + true | "single" | "Long" | "longUpDownSumObserver" | NON_MONOTONIC_LONG | 234 + false | "multiple" | "Long" | "longUpDownSumObserver" | NON_MONOTONIC_LONG | 234 + true | "single" | "Double" | "doubleValueObserver" | SUMMARY | 123.456 + false | "multiple" | "Double" | "doubleValueObserver" | SUMMARY | 123.456 + true | "single" | "Long" | "longValueObserver" | SUMMARY | 234 + false | "multiple" | "Long" | "longValueObserver" | SUMMARY | 234 } } diff --git a/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelperAsynchronousMetricTest.groovy b/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelperAsynchronousMetricTest.groovy new file mode 100644 index 00000000..4c0aa2ae --- /dev/null +++ b/contrib/jmx-metrics/src/test/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelperAsynchronousMetricTest.groovy @@ -0,0 +1,896 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.contrib.jmxmetrics + +import static io.opentelemetry.sdk.metrics.data.MetricData.Descriptor.Type.MONOTONIC_DOUBLE +import static io.opentelemetry.sdk.metrics.data.MetricData.Descriptor.Type.MONOTONIC_LONG +import static io.opentelemetry.sdk.metrics.data.MetricData.Descriptor.Type.NON_MONOTONIC_DOUBLE +import static io.opentelemetry.sdk.metrics.data.MetricData.Descriptor.Type.NON_MONOTONIC_LONG +import static io.opentelemetry.sdk.metrics.data.MetricData.Descriptor.Type.SUMMARY + +import io.opentelemetry.common.Labels +import io.opentelemetry.sdk.OpenTelemetrySdk +import org.junit.Rule +import org.junit.rules.TestName +import org.junit.rules.TestRule + +import spock.lang.Shared +import spock.lang.Specification + +class OtelHelperAsynchronousMetricTest extends Specification{ + + @Shared + GroovyMetricEnvironment gme + + @Shared + OtelHelper otel + + @Rule public final TestRule name = new TestName() + + def setup() { + // Set up a MeterSdk per test to be able to collect its metrics alone + gme = new GroovyMetricEnvironment( + new JmxConfig(new Properties().tap { + it.setProperty(JmxConfig.EXPORTER_TYPE, 'inmemory') + it.setProperty(JmxConfig.INTERVAL_MILLISECONDS, '100') + }), + name.methodName, '' + ) + otel = new OtelHelper(null, gme) + } + + def exportMetrics() { + def provider = OpenTelemetrySdk.meterProvider.get(name.methodName, '') + return provider.collectAll().sort { md1, md2 -> + def p1 = md1.points[0] + def p2 = md2.points[0] + def s1 = p1.startEpochNanos + def s2 = p2.startEpochNanos + if (s1 == s2) { + if (md1.descriptor.type == SUMMARY) { + return p1.percentileValues[0].value <=> p2.percentileValues[0].value + } + return p1.value <=> p2.value + } + s1 <=> s2 + } + } + + def "double sum observer"() { + when: + def dso = otel.doubleSumObserver( + 'double-sum', 'a double sum', + 'ms', [key1:'value1', key2:'value2'] + ) + dso.setCallback({doubleResult -> + doubleResult.observe(123.456, Labels.of('key', 'value')) + }) + + dso = otel.doubleSumObserver('my-double-sum', 'another double sum', 'µs') + dso.setCallback({ doubleResult -> + doubleResult.observe(234.567, Labels.of('myKey', 'myValue')) + } ) + + dso = otel.doubleSumObserver('another-double-sum', 'double sum') + dso.setCallback({ doubleResult -> + doubleResult.observe(345.678, Labels.of('anotherKey', 'anotherValue')) + }) + + dso = otel.doubleSumObserver('yet-another-double-sum') + dso.setCallback({ doubleResult -> + doubleResult.observe(456.789, Labels.of('yetAnotherKey', 'yetAnotherValue')) + }) + + def metrics = exportMetrics() + then: + assert metrics.size() == 4 + + def first = metrics[0] + def second = metrics[1] + def third = metrics[2] + def fourth = metrics[3] + + assert first.descriptor.name == 'double-sum' + assert first.descriptor.description == 'a double sum' + assert first.descriptor.unit == 'ms' + assert first.descriptor.constantLabels == Labels.of( + 'key1', 'value1', 'key2', 'value2' + ) + assert first.descriptor.type == MONOTONIC_DOUBLE + assert first.points.size() == 1 + assert first.points[0].value == 123.456 + assert first.points[0].labels == Labels.of('key', 'value') + + assert second.descriptor.name == 'my-double-sum' + assert second.descriptor.description == 'another double sum' + assert second.descriptor.unit == 'µs' + assert second.descriptor.constantLabels == Labels.empty() + assert second.descriptor.type == MONOTONIC_DOUBLE + assert second.points.size() == 1 + assert second.points[0].value == 234.567 + assert second.points[0].labels == Labels.of('myKey', 'myValue') + + assert third.descriptor.name == 'another-double-sum' + assert third.descriptor.description == 'double sum' + assert third.descriptor.unit == '1' + assert third.descriptor.constantLabels == Labels.empty() + assert third.descriptor.type == MONOTONIC_DOUBLE + assert third.points.size() == 1 + assert third.points[0].value == 345.678 + assert third.points[0].labels == Labels.of('anotherKey', 'anotherValue') + + assert fourth.descriptor.name == 'yet-another-double-sum' + assert fourth.descriptor.description == '' + assert fourth.descriptor.unit == '1' + assert fourth.descriptor.constantLabels == Labels.empty() + assert fourth.descriptor.type == MONOTONIC_DOUBLE + assert fourth.points.size() == 1 + assert fourth.points[0].value == 456.789 + assert fourth.points[0].labels == Labels.of('yetAnotherKey', 'yetAnotherValue') + } + + def "double sum observer memoization"() { + when: + def dcOne = otel.doubleSumObserver('dc', 'double') + dcOne.setCallback({ doubleResult -> + doubleResult.observe(10.1, Labels.of('key1', 'value1')) + }) + def dcTwo = otel.doubleSumObserver('dc', 'double') + dcTwo.setCallback({ doubleResult -> + doubleResult.observe(20.2, Labels.of('key2', 'value2')) + }) + def firstMetrics = exportMetrics() + + def dcThree = otel.doubleSumObserver('dc', 'double') + dcOne.setCallback({ doubleResult -> + doubleResult.observe(30.3, Labels.of('key3', 'value3')) + }) + def dcFour = otel.doubleSumObserver('dc', 'double') + dcTwo.setCallback({ doubleResult -> + doubleResult.observe(40.4, Labels.of('key4', 'value4')) + }) + def secondMetrics = exportMetrics() + + then: + assert dcOne.is(dcTwo) + assert dcTwo.is(dcThree) + assert dcTwo.is(dcFour) + + assert firstMetrics.size() == 1 + assert secondMetrics.size() == 1 + + def firstMetric = firstMetrics[0] + assert firstMetric.descriptor.name == 'dc' + assert firstMetric.descriptor.description == 'double' + assert firstMetric.descriptor.unit == '1' + assert firstMetric.descriptor.constantLabels == Labels.empty() + assert firstMetric.descriptor.type == MONOTONIC_DOUBLE + assert firstMetric.points.size() == 1 + assert firstMetric.points[0].value == 20.2 + assert firstMetric.points[0].labels == Labels.of('key2', 'value2') + + def secondMetric = secondMetrics[0] + assert secondMetric.descriptor.name == 'dc' + assert secondMetric.descriptor.description == 'double' + assert secondMetric.descriptor.unit == '1' + assert secondMetric.descriptor.constantLabels == Labels.empty() + assert secondMetric.descriptor.type == MONOTONIC_DOUBLE + assert secondMetric.points.size() == 2 + assert secondMetric.points[0].value == 20.2 + assert secondMetric.points[0].labels == Labels.of('key2', 'value2') + assert secondMetric.points[1].value == 40.4 + assert secondMetric.points[1].labels == Labels.of('key4', 'value4') + } + + def "long sum observer"() { + when: + def dso = otel.longSumObserver( + 'long-sum', 'a long sum', + 'ms', [key1:'value1', key2:'value2'] + ) + dso.setCallback({longResult -> + longResult.observe(123, Labels.of('key', 'value')) + }) + + dso = otel.longSumObserver('my-long-sum', 'another long sum', 'µs') + dso.setCallback({ longResult -> + longResult.observe(234, Labels.of('myKey', 'myValue')) + } ) + + dso = otel.longSumObserver('another-long-sum', 'long sum') + dso.setCallback({ longResult -> + longResult.observe(345, Labels.of('anotherKey', 'anotherValue')) + }) + + dso = otel.longSumObserver('yet-another-long-sum') + dso.setCallback({ longResult -> + longResult.observe(456, Labels.of('yetAnotherKey', 'yetAnotherValue')) + }) + + def metrics = exportMetrics() + then: + assert metrics.size() == 4 + + def first = metrics[0] + def second = metrics[1] + def third = metrics[2] + def fourth = metrics[3] + + assert first.descriptor.name == 'long-sum' + assert first.descriptor.description == 'a long sum' + assert first.descriptor.unit == 'ms' + assert first.descriptor.constantLabels == Labels.of( + 'key1', 'value1', 'key2', 'value2' + ) + assert first.descriptor.type == MONOTONIC_LONG + assert first.points.size() == 1 + assert first.points[0].value == 123 + assert first.points[0].labels == Labels.of('key', 'value') + + assert second.descriptor.name == 'my-long-sum' + assert second.descriptor.description == 'another long sum' + assert second.descriptor.unit == 'µs' + assert second.descriptor.constantLabels == Labels.empty() + assert second.descriptor.type == MONOTONIC_LONG + assert second.points.size() == 1 + assert second.points[0].value == 234 + assert second.points[0].labels == Labels.of('myKey', 'myValue') + + assert third.descriptor.name == 'another-long-sum' + assert third.descriptor.description == 'long sum' + assert third.descriptor.unit == '1' + assert third.descriptor.constantLabels == Labels.empty() + assert third.descriptor.type == MONOTONIC_LONG + assert third.points.size() == 1 + assert third.points[0].value == 345 + assert third.points[0].labels == Labels.of('anotherKey', 'anotherValue') + + assert fourth.descriptor.name == 'yet-another-long-sum' + assert fourth.descriptor.description == '' + assert fourth.descriptor.unit == '1' + assert fourth.descriptor.constantLabels == Labels.empty() + assert fourth.descriptor.type == MONOTONIC_LONG + assert fourth.points.size() == 1 + assert fourth.points[0].value == 456 + assert fourth.points[0].labels == Labels.of('yetAnotherKey', 'yetAnotherValue') + } + + def "long sum observer memoization"() { + when: + def dcOne = otel.longSumObserver('dc', 'long') + dcOne.setCallback({ longResult -> + longResult.observe(10, Labels.of('key1', 'value1')) + }) + def dcTwo = otel.longSumObserver('dc', 'long') + dcTwo.setCallback({ longResult -> + longResult.observe(20, Labels.of('key2', 'value2')) + }) + def firstMetrics = exportMetrics() + + def dcThree = otel.longSumObserver('dc', 'long') + dcOne.setCallback({ longResult -> + longResult.observe(30, Labels.of('key3', 'value3')) + }) + def dcFour = otel.longSumObserver('dc', 'long') + dcTwo.setCallback({ longResult -> + longResult.observe(40, Labels.of('key4', 'value4')) + }) + def secondMetrics = exportMetrics() + + then: + assert dcOne.is(dcTwo) + assert dcTwo.is(dcThree) + assert dcTwo.is(dcFour) + + assert firstMetrics.size() == 1 + assert secondMetrics.size() == 1 + + def firstMetric = firstMetrics[0] + assert firstMetric.descriptor.name == 'dc' + assert firstMetric.descriptor.description == 'long' + assert firstMetric.descriptor.unit == '1' + assert firstMetric.descriptor.constantLabels == Labels.empty() + assert firstMetric.descriptor.type == MONOTONIC_LONG + assert firstMetric.points.size() == 1 + assert firstMetric.points[0].value == 20 + assert firstMetric.points[0].labels == Labels.of('key2', 'value2') + + def secondMetric = secondMetrics[0] + assert secondMetric.descriptor.name == 'dc' + assert secondMetric.descriptor.description == 'long' + assert secondMetric.descriptor.unit == '1' + assert secondMetric.descriptor.constantLabels == Labels.empty() + assert secondMetric.descriptor.type == MONOTONIC_LONG + assert secondMetric.points.size() == 2 + assert secondMetric.points[0].value == 20 + assert secondMetric.points[0].labels == Labels.of('key2', 'value2') + assert secondMetric.points[1].value == 40 + assert secondMetric.points[1].labels == Labels.of('key4', 'value4') + } + + def "double up down sum observer"() { + when: + def dso = otel.doubleUpDownSumObserver( + 'double-up-down-sum', 'a double up down sum', + 'ms', [key1:'value1', key2:'value2'] + ) + dso.setCallback({doubleResult -> + doubleResult.observe(123.456, Labels.of('key', 'value')) + }) + + dso = otel.doubleUpDownSumObserver('my-double-up-down-sum', 'another double up down sum', 'µs') + dso.setCallback({ doubleResult -> + doubleResult.observe(234.567, Labels.of('myKey', 'myValue')) + } ) + + dso = otel.doubleUpDownSumObserver('another-double-up-down-sum', 'double up down sum') + dso.setCallback({ doubleResult -> + doubleResult.observe(345.678, Labels.of('anotherKey', 'anotherValue')) + }) + + dso = otel.doubleUpDownSumObserver('yet-another-double-up-down-sum') + dso.setCallback({ doubleResult -> + doubleResult.observe(456.789, Labels.of('yetAnotherKey', 'yetAnotherValue')) + }) + + def metrics = exportMetrics() + then: + assert metrics.size() == 4 + + def first = metrics[0] + def second = metrics[1] + def third = metrics[2] + def fourth = metrics[3] + + assert first.descriptor.name == 'double-up-down-sum' + assert first.descriptor.description == 'a double up down sum' + assert first.descriptor.unit == 'ms' + assert first.descriptor.constantLabels == Labels.of( + 'key1', 'value1', 'key2', 'value2' + ) + assert first.descriptor.type == NON_MONOTONIC_DOUBLE + assert first.points.size() == 1 + assert first.points[0].value == 123.456 + assert first.points[0].labels == Labels.of('key', 'value') + + assert second.descriptor.name == 'my-double-up-down-sum' + assert second.descriptor.description == 'another double up down sum' + assert second.descriptor.unit == 'µs' + assert second.descriptor.constantLabels == Labels.empty() + assert second.descriptor.type == NON_MONOTONIC_DOUBLE + assert second.points.size() == 1 + assert second.points[0].value == 234.567 + assert second.points[0].labels == Labels.of('myKey', 'myValue') + + assert third.descriptor.name == 'another-double-up-down-sum' + assert third.descriptor.description == 'double up down sum' + assert third.descriptor.unit == '1' + assert third.descriptor.constantLabels == Labels.empty() + assert third.descriptor.type == NON_MONOTONIC_DOUBLE + assert third.points.size() == 1 + assert third.points[0].value == 345.678 + assert third.points[0].labels == Labels.of('anotherKey', 'anotherValue') + + assert fourth.descriptor.name == 'yet-another-double-up-down-sum' + assert fourth.descriptor.description == '' + assert fourth.descriptor.unit == '1' + assert fourth.descriptor.constantLabels == Labels.empty() + assert fourth.descriptor.type == NON_MONOTONIC_DOUBLE + assert fourth.points.size() == 1 + assert fourth.points[0].value == 456.789 + assert fourth.points[0].labels == Labels.of('yetAnotherKey', 'yetAnotherValue') + } + + def "double up down sum observer memoization"() { + when: + def dcOne = otel.doubleUpDownSumObserver('dc', 'double') + dcOne.setCallback({ doubleResult -> + doubleResult.observe(10.1, Labels.of('key1', 'value1')) + }) + def dcTwo = otel.doubleUpDownSumObserver('dc', 'double') + dcTwo.setCallback({ doubleResult -> + doubleResult.observe(20.2, Labels.of('key2', 'value2')) + }) + def firstMetrics = exportMetrics() + + def dcThree = otel.doubleUpDownSumObserver('dc', 'double') + dcOne.setCallback({ doubleResult -> + doubleResult.observe(30.3, Labels.of('key3', 'value3')) + }) + def dcFour = otel.doubleUpDownSumObserver('dc', 'double') + dcTwo.setCallback({ doubleResult -> + doubleResult.observe(40.4, Labels.of('key4', 'value4')) + }) + def secondMetrics = exportMetrics() + + then: + assert dcOne.is(dcTwo) + assert dcTwo.is(dcThree) + assert dcTwo.is(dcFour) + + assert firstMetrics.size() == 1 + assert secondMetrics.size() == 1 + + def firstMetric = firstMetrics[0] + assert firstMetric.descriptor.name == 'dc' + assert firstMetric.descriptor.description == 'double' + assert firstMetric.descriptor.unit == '1' + assert firstMetric.descriptor.constantLabels == Labels.empty() + assert firstMetric.descriptor.type == NON_MONOTONIC_DOUBLE + assert firstMetric.points.size() == 1 + assert firstMetric.points[0].value == 20.2 + assert firstMetric.points[0].labels == Labels.of('key2', 'value2') + + def secondMetric = secondMetrics[0] + assert secondMetric.descriptor.name == 'dc' + assert secondMetric.descriptor.description == 'double' + assert secondMetric.descriptor.unit == '1' + assert secondMetric.descriptor.constantLabels == Labels.empty() + assert secondMetric.descriptor.type == NON_MONOTONIC_DOUBLE + assert secondMetric.points.size() == 2 + assert secondMetric.points[0].value == 20.2 + assert secondMetric.points[0].labels == Labels.of('key2', 'value2') + assert secondMetric.points[1].value == 40.4 + assert secondMetric.points[1].labels == Labels.of('key4', 'value4') + } + + def "long up down sum observer"() { + when: + def dso = otel.longUpDownSumObserver( + 'long-up-down-sum', 'a long up down sum', + 'ms', [key1:'value1', key2:'value2'] + ) + dso.setCallback({longResult -> + longResult.observe(123, Labels.of('key', 'value')) + }) + + dso = otel.longUpDownSumObserver('my-long-up-down-sum', 'another long up down sum', 'µs') + dso.setCallback({ longResult -> + longResult.observe(234, Labels.of('myKey', 'myValue')) + } ) + + dso = otel.longUpDownSumObserver('another-long-up-down-sum', 'long up down sum') + dso.setCallback({ longResult -> + longResult.observe(345, Labels.of('anotherKey', 'anotherValue')) + }) + + dso = otel.longUpDownSumObserver('yet-another-long-up-down-sum') + dso.setCallback({ longResult -> + longResult.observe(456, Labels.of('yetAnotherKey', 'yetAnotherValue')) + }) + + def metrics = exportMetrics() + then: + assert metrics.size() == 4 + + def first = metrics[0] + def second = metrics[1] + def third = metrics[2] + def fourth = metrics[3] + + assert first.descriptor.name == 'long-up-down-sum' + assert first.descriptor.description == 'a long up down sum' + assert first.descriptor.unit == 'ms' + assert first.descriptor.constantLabels == Labels.of( + 'key1', 'value1', 'key2', 'value2' + ) + assert first.descriptor.type == NON_MONOTONIC_LONG + assert first.points.size() == 1 + assert first.points[0].value == 123 + assert first.points[0].labels == Labels.of('key', 'value') + + assert second.descriptor.name == 'my-long-up-down-sum' + assert second.descriptor.description == 'another long up down sum' + assert second.descriptor.unit == 'µs' + assert second.descriptor.constantLabels == Labels.empty() + assert second.descriptor.type == NON_MONOTONIC_LONG + assert second.points.size() == 1 + assert second.points[0].value == 234 + assert second.points[0].labels == Labels.of('myKey', 'myValue') + + assert third.descriptor.name == 'another-long-up-down-sum' + assert third.descriptor.description == 'long up down sum' + assert third.descriptor.unit == '1' + assert third.descriptor.constantLabels == Labels.empty() + assert third.descriptor.type == NON_MONOTONIC_LONG + assert third.points.size() == 1 + assert third.points[0].value == 345 + assert third.points[0].labels == Labels.of('anotherKey', 'anotherValue') + + assert fourth.descriptor.name == 'yet-another-long-up-down-sum' + assert fourth.descriptor.description == '' + assert fourth.descriptor.unit == '1' + assert fourth.descriptor.constantLabels == Labels.empty() + assert fourth.descriptor.type == NON_MONOTONIC_LONG + assert fourth.points.size() == 1 + assert fourth.points[0].value == 456 + assert fourth.points[0].labels == Labels.of('yetAnotherKey', 'yetAnotherValue') + } + + def "long up down sum observer memoization"() { + when: + def dcOne = otel.longUpDownSumObserver('dc', 'long') + dcOne.setCallback({ longResult -> + longResult.observe(10, Labels.of('key1', 'value1')) + }) + def dcTwo = otel.longUpDownSumObserver('dc', 'long') + dcTwo.setCallback({ longResult -> + longResult.observe(20, Labels.of('key2', 'value2')) + }) + def firstMetrics = exportMetrics() + + def dcThree = otel.longUpDownSumObserver('dc', 'long') + dcOne.setCallback({ longResult -> + longResult.observe(30, Labels.of('key3', 'value3')) + }) + def dcFour = otel.longUpDownSumObserver('dc', 'long') + dcTwo.setCallback({ longResult -> + longResult.observe(40, Labels.of('key4', 'value4')) + }) + def secondMetrics = exportMetrics() + + then: + assert dcOne.is(dcTwo) + assert dcTwo.is(dcThree) + assert dcTwo.is(dcFour) + + assert firstMetrics.size() == 1 + assert secondMetrics.size() == 1 + + def firstMetric = firstMetrics[0] + assert firstMetric.descriptor.name == 'dc' + assert firstMetric.descriptor.description == 'long' + assert firstMetric.descriptor.unit == '1' + assert firstMetric.descriptor.constantLabels == Labels.empty() + assert firstMetric.descriptor.type == NON_MONOTONIC_LONG + assert firstMetric.points.size() == 1 + assert firstMetric.points[0].value == 20 + assert firstMetric.points[0].labels == Labels.of('key2', 'value2') + + def secondMetric = secondMetrics[0] + assert secondMetric.descriptor.name == 'dc' + assert secondMetric.descriptor.description == 'long' + assert secondMetric.descriptor.unit == '1' + assert secondMetric.descriptor.constantLabels == Labels.empty() + assert secondMetric.descriptor.type == NON_MONOTONIC_LONG + assert secondMetric.points.size() == 2 + assert secondMetric.points[0].value == 20 + assert secondMetric.points[0].labels == Labels.of('key2', 'value2') + assert secondMetric.points[1].value == 40 + assert secondMetric.points[1].labels == Labels.of('key4', 'value4') + } + + def "double value observer"() { + when: + def dso = otel.doubleValueObserver( + 'double-value', 'a double value', + 'ms', [key1:'value1', key2:'value2'] + ) + dso.setCallback({doubleResult -> + doubleResult.observe(123.456, Labels.of('key', 'value')) + }) + + dso = otel.doubleValueObserver('my-double-value', 'another double value', 'µs') + dso.setCallback({ doubleResult -> + doubleResult.observe(234.567, Labels.of('myKey', 'myValue')) + } ) + + dso = otel.doubleValueObserver('another-double-value', 'double value') + dso.setCallback({ doubleResult -> + doubleResult.observe(345.678, Labels.of('anotherKey', 'anotherValue')) + }) + + dso = otel.doubleValueObserver('yet-another-double-value') + dso.setCallback({ doubleResult -> + doubleResult.observe(456.789, Labels.of('yetAnotherKey', 'yetAnotherValue')) + }) + + def metrics = exportMetrics() + then: + assert metrics.size() == 4 + + def first = metrics[0] + def second = metrics[1] + def third = metrics[2] + def fourth = metrics[3] + + assert first.descriptor.name == 'double-value' + assert first.descriptor.description == 'a double value' + assert first.descriptor.unit == 'ms' + assert first.descriptor.constantLabels == Labels.of( + 'key1', 'value1', 'key2', 'value2' + ) + assert first.descriptor.type == SUMMARY + assert first.points.size() == 1 + assert first.points[0].count == 1 + assert first.points[0].sum == 123.456 + assert first.points[0].percentileValues[0].percentile == 0 + assert first.points[0].percentileValues[0].value == 123.456 + assert first.points[0].percentileValues[1].percentile == 100 + assert first.points[0].percentileValues[1].value == 123.456 + assert first.points[0].labels == Labels.of('key', 'value') + + assert second.descriptor.name == 'my-double-value' + assert second.descriptor.description == 'another double value' + assert second.descriptor.unit == 'µs' + assert second.descriptor.constantLabels == Labels.empty() + assert second.descriptor.type == SUMMARY + assert second.points[0].count == 1 + assert second.points[0].sum == 234.567 + assert second.points[0].percentileValues[0].percentile == 0 + assert second.points[0].percentileValues[0].value == 234.567 + assert second.points[0].percentileValues[1].percentile == 100 + assert second.points[0].percentileValues[1].value == 234.567 + assert second.points[0].labels == Labels.of('myKey', 'myValue') + + assert third.descriptor.name == 'another-double-value' + assert third.descriptor.description == 'double value' + assert third.descriptor.unit == '1' + assert third.descriptor.constantLabels == Labels.empty() + assert third.descriptor.type == SUMMARY + assert third.points.size() == 1 + assert third.points[0].count == 1 + assert third.points[0].sum == 345.678 + assert third.points[0].percentileValues[0].percentile == 0 + assert third.points[0].percentileValues[0].value == 345.678 + assert third.points[0].percentileValues[1].percentile == 100 + assert third.points[0].percentileValues[1].value == 345.678 + assert third.points[0].labels == Labels.of('anotherKey', 'anotherValue') + + assert fourth.descriptor.name == 'yet-another-double-value' + assert fourth.descriptor.description == '' + assert fourth.descriptor.unit == '1' + assert fourth.descriptor.constantLabels == Labels.empty() + assert fourth.descriptor.type == SUMMARY + assert fourth.points.size() == 1 + assert fourth.points[0].count == 1 + assert fourth.points[0].sum == 456.789 + assert fourth.points[0].percentileValues[0].percentile == 0 + assert fourth.points[0].percentileValues[0].value == 456.789 + assert fourth.points[0].percentileValues[1].percentile == 100 + assert fourth.points[0].percentileValues[1].value == 456.789 + assert fourth.points[0].labels == Labels.of('yetAnotherKey', 'yetAnotherValue') + } + + def "double value observer memoization"() { + when: + def dcOne = otel.doubleValueObserver('dc', 'double') + dcOne.setCallback({ doubleResult -> + doubleResult.observe(10.1, Labels.of('key1', 'value1')) + }) + def dcTwo = otel.doubleValueObserver('dc', 'double') + dcTwo.setCallback({ doubleResult -> + doubleResult.observe(20.2, Labels.of('key2', 'value2')) + }) + def firstMetrics = exportMetrics() + + def dcThree = otel.doubleValueObserver('dc', 'double') + dcOne.setCallback({ doubleResult -> + doubleResult.observe(30.3, Labels.of('key3', 'value3')) + }) + def dcFour = otel.doubleValueObserver('dc', 'double') + dcTwo.setCallback({ doubleResult -> + doubleResult.observe(40.4, Labels.of('key4', 'value4')) + doubleResult.observe(50.5, Labels.of('key2', 'value2')) + }) + def secondMetrics = exportMetrics() + + then: + assert dcOne.is(dcTwo) + assert dcTwo.is(dcThree) + assert dcTwo.is(dcFour) + + assert firstMetrics.size() == 1 + assert secondMetrics.size() == 1 + + def firstMetric = firstMetrics[0] + assert firstMetric.descriptor.name == 'dc' + assert firstMetric.descriptor.description == 'double' + assert firstMetric.descriptor.unit == '1' + assert firstMetric.descriptor.constantLabels == Labels.empty() + assert firstMetric.descriptor.type == SUMMARY + assert firstMetric.points.size() == 1 + assert firstMetric.points[0].count == 1 + assert firstMetric.points[0].sum == 20.2 + assert firstMetric.points[0].percentileValues[0].percentile == 0 + assert firstMetric.points[0].percentileValues[0].value == 20.2 + assert firstMetric.points[0].percentileValues[1].percentile == 100 + assert firstMetric.points[0].percentileValues[1].value == 20.2 + assert firstMetric.points[0].labels == Labels.of('key2', 'value2') + + def secondMetric = secondMetrics[0] + assert secondMetric.descriptor.name == 'dc' + assert secondMetric.descriptor.description == 'double' + assert secondMetric.descriptor.unit == '1' + assert secondMetric.descriptor.constantLabels == Labels.empty() + assert secondMetric.descriptor.type == SUMMARY + assert secondMetric.points.size() == 2 + assert secondMetric.points[0].count == 1 + assert secondMetric.points[0].sum == 40.4 + assert secondMetric.points[0].percentileValues[0].percentile == 0 + assert secondMetric.points[0].percentileValues[0].value == 40.4 + assert secondMetric.points[0].percentileValues[1].percentile == 100 + assert secondMetric.points[0].percentileValues[1].value == 40.4 + assert secondMetric.points[0].labels == Labels.of('key4', 'value4') + assert secondMetric.points[1].count == 1 + assert secondMetric.points[1].sum == 50.5 + assert secondMetric.points[1].percentileValues[0].percentile == 0 + assert secondMetric.points[1].percentileValues[0].value == 50.5 + assert secondMetric.points[1].percentileValues[1].percentile == 100 + assert secondMetric.points[1].percentileValues[1].value == 50.5 + assert secondMetric.points[1].labels == Labels.of('key2', 'value2') + } + + def "long value observer"() { + when: + def dso = otel.longValueObserver( + 'long-value', 'a long value', + 'ms', [key1:'value1', key2:'value2'] + ) + dso.setCallback({longResult -> + longResult.observe(123, Labels.of('key', 'value')) + }) + + dso = otel.longValueObserver('my-long-value', 'another long value', 'µs') + dso.setCallback({ longResult -> + longResult.observe(234, Labels.of('myKey', 'myValue')) + } ) + + dso = otel.longValueObserver('another-long-value', 'long value') + dso.setCallback({ longResult -> + longResult.observe(345, Labels.of('anotherKey', 'anotherValue')) + }) + + dso = otel.longValueObserver('yet-another-long-value') + dso.setCallback({ longResult -> + longResult.observe(456, Labels.of('yetAnotherKey', 'yetAnotherValue')) + }) + + def metrics = exportMetrics() + then: + assert metrics.size() == 4 + + def first = metrics[0] + def second = metrics[1] + def third = metrics[2] + def fourth = metrics[3] + + assert first.descriptor.name == 'long-value' + assert first.descriptor.description == 'a long value' + assert first.descriptor.unit == 'ms' + assert first.descriptor.constantLabels == Labels.of( + 'key1', 'value1', 'key2', 'value2' + ) + assert first.descriptor.type == SUMMARY + assert first.points.size() == 1 + assert first.points[0].count == 1 + assert first.points[0].sum == 123 + assert first.points[0].percentileValues[0].percentile == 0 + assert first.points[0].percentileValues[0].value == 123 + assert first.points[0].percentileValues[1].percentile == 100 + assert first.points[0].percentileValues[1].value == 123 + assert first.points[0].labels == Labels.of('key', 'value') + + assert second.descriptor.name == 'my-long-value' + assert second.descriptor.description == 'another long value' + assert second.descriptor.unit == 'µs' + assert second.descriptor.constantLabels == Labels.empty() + assert second.descriptor.type == SUMMARY + assert second.points.size() == 1 + assert second.points[0].count == 1 + assert second.points[0].sum == 234 + assert second.points[0].percentileValues[0].percentile == 0 + assert second.points[0].percentileValues[0].value == 234 + assert second.points[0].percentileValues[1].percentile == 100 + assert second.points[0].percentileValues[1].value == 234 + assert second.points[0].labels == Labels.of('myKey', 'myValue') + + assert third.descriptor.name == 'another-long-value' + assert third.descriptor.description == 'long value' + assert third.descriptor.unit == '1' + assert third.descriptor.constantLabels == Labels.empty() + assert third.descriptor.type == SUMMARY + assert third.points.size() == 1 + assert third.points[0].count == 1 + assert third.points[0].sum == 345 + assert third.points[0].percentileValues[0].percentile == 0 + assert third.points[0].percentileValues[0].value == 345 + assert third.points[0].percentileValues[1].percentile == 100 + assert third.points[0].percentileValues[1].value == 345 + assert third.points[0].labels == Labels.of('anotherKey', 'anotherValue') + + assert fourth.descriptor.name == 'yet-another-long-value' + assert fourth.descriptor.description == '' + assert fourth.descriptor.unit == '1' + assert fourth.descriptor.constantLabels == Labels.empty() + assert fourth.descriptor.type == SUMMARY + assert fourth.points.size() == 1 + assert fourth.points[0].count == 1 + assert fourth.points[0].sum == 456 + assert fourth.points[0].percentileValues[0].percentile == 0 + assert fourth.points[0].percentileValues[0].value == 456 + assert fourth.points[0].percentileValues[1].percentile == 100 + assert fourth.points[0].percentileValues[1].value == 456 + assert fourth.points[0].labels == Labels.of('yetAnotherKey', 'yetAnotherValue') + } + + def "long value observer memoization"() { + when: + def dcOne = otel.longValueObserver('dc', 'long') + dcOne.setCallback({ longResult -> + longResult.observe(10, Labels.of('key1', 'value1')) + }) + def dcTwo = otel.longValueObserver('dc', 'long') + dcTwo.setCallback({ longResult -> + longResult.observe(20, Labels.of('key2', 'value2')) + }) + def firstMetrics = exportMetrics() + + def dcThree = otel.longValueObserver('dc', 'long') + dcOne.setCallback({ longResult -> + longResult.observe(30, Labels.of('key3', 'value3')) + }) + def dcFour = otel.longValueObserver('dc', 'long') + dcTwo.setCallback({ longResult -> + longResult.observe(40, Labels.of('key4', 'value4')) + longResult.observe(50, Labels.of('key2', 'value2')) + }) + def secondMetrics = exportMetrics() + + then: + assert dcOne.is(dcTwo) + assert dcTwo.is(dcThree) + assert dcTwo.is(dcFour) + + assert firstMetrics.size() == 1 + assert secondMetrics.size() == 1 + + def firstMetric = firstMetrics[0] + assert firstMetric.descriptor.name == 'dc' + assert firstMetric.descriptor.description == 'long' + assert firstMetric.descriptor.unit == '1' + assert firstMetric.descriptor.constantLabels == Labels.empty() + assert firstMetric.descriptor.type == SUMMARY + assert firstMetric.points.size() == 1 + assert firstMetric.points[0].sum == 20 + assert firstMetric.points[0].percentileValues[0].percentile == 0 + assert firstMetric.points[0].percentileValues[0].value == 20 + assert firstMetric.points[0].percentileValues[1].percentile == 100 + assert firstMetric.points[0].percentileValues[1].value == 20 + assert firstMetric.points[0].labels == Labels.of('key2', 'value2') + + def secondMetric = secondMetrics[0] + assert secondMetric.descriptor.name == 'dc' + assert secondMetric.descriptor.description == 'long' + assert secondMetric.descriptor.unit == '1' + assert secondMetric.descriptor.constantLabels == Labels.empty() + assert secondMetric.descriptor.type == SUMMARY + assert secondMetric.points.size() == 2 + assert secondMetric.points[0].sum == 40 + assert secondMetric.points[0].percentileValues[0].percentile == 0 + assert secondMetric.points[0].percentileValues[0].value == 40 + assert secondMetric.points[0].percentileValues[1].percentile == 100 + assert secondMetric.points[0].percentileValues[1].value == 40 + assert secondMetric.points[0].labels == Labels.of('key4', 'value4') + assert secondMetric.points[1].sum == 50 + assert secondMetric.points[1].percentileValues[0].percentile == 0 + assert secondMetric.points[1].percentileValues[0].value == 50 + assert secondMetric.points[1].percentileValues[1].percentile == 100 + assert secondMetric.points[1].percentileValues[1].value == 50 + assert secondMetric.points[1].labels == Labels.of('key2', 'value2') + } +}