diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb index 117a400778..124b434f06 100644 --- a/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb @@ -19,7 +19,7 @@ def initialize(kind, name, unit, desc, callable) def upgrade_with(meter) @delegate = case @kind - when :counter, :histogram, :up_down_counter + when :counter, :histogram, :up_down_counter, :gauge meter.send("create_#{@kind}", @name, unit: @unit, description: @desc) when :observable_counter, :observable_gauge, :observable_up_down_counter meter.send("create_#{@kind}", @name, unit: @unit, description: @desc, callback: @callback) diff --git a/metrics_api/lib/opentelemetry/internal/proxy_meter.rb b/metrics_api/lib/opentelemetry/internal/proxy_meter.rb index 74d497c684..9f44b4bb67 100644 --- a/metrics_api/lib/opentelemetry/internal/proxy_meter.rb +++ b/metrics_api/lib/opentelemetry/internal/proxy_meter.rb @@ -45,6 +45,7 @@ def create_instrument(kind, name, unit, description, callback) case kind when :counter then @delegate.create_counter(name, unit: unit, description: description) when :histogram then @delegate.create_histogram(name, unit: unit, description: description) + when :gauge then @delegate.create_gauge(name, unit: unit, description: description) when :up_down_counter then @delegate.create_up_down_counter(name, unit: unit, description: description) when :observable_counter then @delegate.create_observable_counter(name, unit: unit, description: description, callback: callback) when :observable_gauge then @delegate.create_observable_gauge(name, unit: unit, description: description, callback: callback) diff --git a/metrics_api/lib/opentelemetry/metrics/instrument.rb b/metrics_api/lib/opentelemetry/metrics/instrument.rb index 3782a8420c..b40f31bc2d 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument.rb @@ -6,6 +6,7 @@ require 'opentelemetry/metrics/instrument/counter' require 'opentelemetry/metrics/instrument/histogram' +require 'opentelemetry/metrics/instrument/gauge' require 'opentelemetry/metrics/instrument/observable_counter' require 'opentelemetry/metrics/instrument/observable_gauge' require 'opentelemetry/metrics/instrument/observable_up_down_counter' diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/gauge.rb b/metrics_api/lib/opentelemetry/metrics/instrument/gauge.rb new file mode 100644 index 0000000000..6e65f94946 --- /dev/null +++ b/metrics_api/lib/opentelemetry/metrics/instrument/gauge.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Metrics + module Instrument + # No-op implementation of Gauge. + class Gauge + # Record the current value for the Gauge + # + # @param [Numeric] amount The current absolute value. + # @param [Hash{String => String, Numeric, Boolean, Array}] attributes + # Values must be non-nil and (array of) string, boolean or numeric type. + # Array values must not contain nil elements and all elements must be of + # the same basic type (string, numeric, boolean). + def record(amount, attributes: {}); end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/metrics/meter.rb b/metrics_api/lib/opentelemetry/metrics/meter.rb index f44b586110..7bfc1e498a 100644 --- a/metrics_api/lib/opentelemetry/metrics/meter.rb +++ b/metrics_api/lib/opentelemetry/metrics/meter.rb @@ -11,11 +11,14 @@ class Meter COUNTER = Instrument::Counter.new OBSERVABLE_COUNTER = Instrument::ObservableCounter.new HISTOGRAM = Instrument::Histogram.new + GAUGE = Instrument::Gauge.new OBSERVABLE_GAUGE = Instrument::ObservableGauge.new UP_DOWN_COUNTER = Instrument::UpDownCounter.new OBSERVABLE_UP_DOWN_COUNTER = Instrument::ObservableUpDownCounter.new - private_constant(:COUNTER, :OBSERVABLE_COUNTER, :HISTOGRAM, :OBSERVABLE_GAUGE, :UP_DOWN_COUNTER, :OBSERVABLE_UP_DOWN_COUNTER) + NAME_REGEX = /\A[a-zA-Z][-.\w]{0,62}\z/ + + private_constant(:COUNTER, :OBSERVABLE_COUNTER, :HISTOGRAM, :GAUGE, :OBSERVABLE_GAUGE, :UP_DOWN_COUNTER, :OBSERVABLE_UP_DOWN_COUNTER) DuplicateInstrumentError = Class.new(OpenTelemetry::Error) InstrumentNameError = Class.new(OpenTelemetry::Error) @@ -35,6 +38,24 @@ def create_histogram(name, unit: nil, description: nil) create_instrument(:histogram, name, unit, description, nil) { HISTOGRAM } end + # Gauge is an synchronous Instrument which reports non-additive value(s) + # + # With this api call: + # + # meter.create_gauge("cpu.frequency", + # description: "the real-time CPU clock speed", + # unit: "ms") + # + # + # @param name [String] the name of the gauge. + # @param unit [optional String] an optional string provided by user. + # @param description [optional String] an optional free-form text provided by user. + # + # @return [nil] after creation of gauge, it will be stored in instrument_registry + def create_gauge(name, unit: nil, description: nil) + create_instrument(:gauge, name, unit, description, nil) { GAUGE } + end + def create_up_down_counter(name, unit: nil, description: nil) create_instrument(:up_down_counter, name, unit, description, nil) { UP_DOWN_COUNTER } end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb index c440634c8c..d137cfbdd4 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb @@ -20,3 +20,4 @@ module Instrument require 'opentelemetry/sdk/metrics/instrument/observable_gauge' require 'opentelemetry/sdk/metrics/instrument/observable_up_down_counter' require 'opentelemetry/sdk/metrics/instrument/up_down_counter' +require 'opentelemetry/sdk/metrics/instrument/gauge' diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/gauge.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/gauge.rb new file mode 100644 index 0000000000..1c0e8f2663 --- /dev/null +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/gauge.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module SDK + module Metrics + module Instrument + # {Gauge} is the SDK implementation of {OpenTelemetry::Metrics::Gauge}. + class Gauge < OpenTelemetry::SDK::Metrics::Instrument::SynchronousInstrument + # Returns the instrument kind as a Symbol + # + # @return [Symbol] + def instrument_kind + :gauge + end + + # Record the absolute value of the Gauge. + # + # @param [numeric] value The current absolute value. + # @param [Hash{String => String, Numeric, Boolean, Array}] attributes + # Values must be non-nil and (array of) string, boolean or numeric type. + # Array values must not contain nil elements and all elements must be of + # the same basic type (string, numeric, boolean). + def record(value, attributes: {}) + # TODO: When the metrics SDK stabilizes and is merged into the main SDK, + # we can leverage the SDK Internal validation classes to enforce this: + # https://github.com/open-telemetry/opentelemetry-ruby/blob/6bec625ef49004f364457c26263df421526b60d6/sdk/lib/opentelemetry/sdk/internal.rb#L47 + update(value, attributes) + nil + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + nil + end + + private + + def default_aggregation + OpenTelemetry::SDK::Metrics::Aggregation::LastValue.new + end + end + end + end + end +end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb index 1307817ec6..dee8fea8d2 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb @@ -46,6 +46,7 @@ def create_instrument(kind, name, unit, description, callback) case kind when :counter then OpenTelemetry::SDK::Metrics::Instrument::Counter.new(name, unit, description, @instrumentation_scope, @meter_provider) when :observable_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider) + when :gauge then OpenTelemetry::SDK::Metrics::Instrument::Gauge.new(name, unit, description, @instrumentation_scope, @meter_provider) when :histogram then OpenTelemetry::SDK::Metrics::Instrument::Histogram.new(name, unit, description, @instrumentation_scope, @meter_provider) when :observable_gauge then OpenTelemetry::SDK::Metrics::Instrument::ObservableGauge.new(name, unit, description, callback, @instrumentation_scope, @meter_provider) when :up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::UpDownCounter.new(name, unit, description, @instrumentation_scope, @meter_provider) diff --git a/metrics_sdk/test/opentelemetry/sdk/metrics/instrument/gauge_test.rb b/metrics_sdk/test/opentelemetry/sdk/metrics/instrument/gauge_test.rb new file mode 100644 index 0000000000..f3fc5509c6 --- /dev/null +++ b/metrics_sdk/test/opentelemetry/sdk/metrics/instrument/gauge_test.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::SDK::Metrics::Instrument::Gauge do + let(:metric_exporter) { OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new } + let(:meter) { OpenTelemetry.meter_provider.meter('test') } + let(:gauge) { meter.create_gauge('gauge', unit: 'smidgen', description: 'a small amount of something') } + + before do + reset_metrics_sdk + OpenTelemetry::SDK.configure + OpenTelemetry.meter_provider.add_metric_reader(metric_exporter) + end + + it 'gauge should count -2' do + gauge.record(-2, attributes: { 'foo' => 'bar' }) + metric_exporter.pull + last_snapshot = metric_exporter.metric_snapshots + + _(last_snapshot[0].name).must_equal('gauge') + _(last_snapshot[0].unit).must_equal('smidgen') + _(last_snapshot[0].description).must_equal('a small amount of something') + _(last_snapshot[0].instrumentation_scope.name).must_equal('test') + _(last_snapshot[0].data_points[0].attributes).must_equal('foo' => 'bar') + _(last_snapshot[0].data_points[0].value).must_equal(-2) + _(last_snapshot[0].aggregation_temporality).must_equal(:delta) + end + + it 'gauge should count 1 for last recording' do + gauge.record(-2, attributes: { 'foo' => 'bar' }) + gauge.record(1, attributes: { 'foo' => 'bar' }) + metric_exporter.pull + last_snapshot = metric_exporter.metric_snapshots + + _(last_snapshot.size).must_equal(1) + _(last_snapshot[0].data_points.size).must_equal(1) + _(last_snapshot[0].data_points[0].value).must_equal(1) + end + + it 'separate gauge should record their own last value' do + gauge.record(-2, attributes: { 'foo' => 'bar' }) + gauge.record(1, attributes: { 'foo' => 'bar' }) + gauge2 = meter.create_gauge('gauge2', unit: 'smidgen', description: 'a small amount of something') + gauge2.record(10, attributes: {}) + + metric_exporter.pull + last_snapshot = metric_exporter.metric_snapshots + + _(last_snapshot.size).must_equal(2) + _(last_snapshot[0].data_points[0].value).must_equal(1) + _(last_snapshot[1].data_points[0].value).must_equal(10) + end +end