Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add synchronous gauge #1718

Merged
merged 17 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions metrics_api/lib/opentelemetry/internal/proxy_meter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions metrics_api/lib/opentelemetry/metrics/instrument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
23 changes: 23 additions & 0 deletions metrics_api/lib/opentelemetry/metrics/instrument/gauge.rb
Original file line number Diff line number Diff line change
@@ -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<String, Numeric, Boolean>}] 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
23 changes: 22 additions & 1 deletion metrics_api/lib/opentelemetry/metrics/meter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved

def create_up_down_counter(name, unit: nil, description: nil)
create_instrument(:up_down_counter, name, unit, description, nil) { UP_DOWN_COUNTER }
end
Expand Down
1 change: 1 addition & 0 deletions metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
47 changes: 47 additions & 0 deletions metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/gauge.rb
Original file line number Diff line number Diff line change
@@ -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<String, Numeric, Boolean>}] 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
Comment on lines +28 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we turn this TODO into an issue, or reference it on an existing issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I will create an issue to track it (e.g. something like integrate metrics_sdk to sdk?)

Copy link
Contributor

Choose a reason for hiding this comment

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

Just seeing this! Yes, I think something like that would work and add a link to this code for reference. This is a good example for a similar TODO for logs: #1739

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
1 change: 1 addition & 0 deletions metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Loading