-
Notifications
You must be signed in to change notification settings - Fork 231
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: Rework Meter and MeterProvider API #1442
Changes from 4 commits
58ba96a
c419345
ad6fdd1
b2c0e2c
a678314
d666a90
11906b1
b9db252
d79e525
7cce415
b0ba487
bda5259
e4cd5c2
443d35e
9d9ad67
f96741b
6af99b7
671b5c0
f12cc09
787790b
703faf3
d919961
dde3ee8
1f4641c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ AllCops: | |
|
||
Lint/UnusedMethodArgument: | ||
Enabled: false | ||
|
||
Metrics/AbcSize: | ||
Enabled: false | ||
Metrics/LineLength: | ||
|
@@ -15,8 +16,14 @@ Metrics/CyclomaticComplexity: | |
Max: 20 | ||
Metrics/ParameterLists: | ||
Enabled: false | ||
|
||
Naming/VariableNumber: | ||
Enabled: false | ||
Naming/FileName: | ||
Exclude: | ||
- "lib/opentelemetry-metrics-api.rb" | ||
|
||
Style/IfUnlessModifier: | ||
Enabled: false | ||
Comment on lines
+27
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: When this is enabled, we can be forced into writing long lines with |
||
Style/ModuleFunction: | ||
Enabled: false |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,44 @@ | ||||||
# frozen_string_literal: true | ||||||
|
||||||
# Copyright The OpenTelemetry Authors | ||||||
# | ||||||
# SPDX-License-Identifier: Apache-2.0 | ||||||
|
||||||
module OpenTelemetry | ||||||
module Metrics | ||||||
module Instrument | ||||||
# https://opentelemetry.io/docs/reference/specification/metrics/api/#asynchronous-instrument-api | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This link didn't work for me. I found the doc at:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! I think they changed it. https://opentelemetry.io/docs/reference/specification/metrics/api/#asynchronous-instrument-api is redirecting to https://opentelemetry.io/docs/specs/otel/metrics/api/#asynchronous-instrument-api with status 301. I'll update them! Updated in e4cd5c2 |
||||||
class AsynchronousInstrument | ||||||
attr_reader :name, :unit, :description, :callback | ||||||
|
||||||
# @api private | ||||||
def initialize(name, unit: nil, description: nil, callback: nil) | ||||||
@name = name | ||||||
@unit = unit || '' | ||||||
@description = description || '' | ||||||
@callback = callback ? Array(callback) : [] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: since this is an array of functions, can we pluralize the name as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I can pluralize them! I think the reason I initially didn't do that was to keep the same "symbol same" from the specification although this But in Updated in 443d35e |
||||||
end | ||||||
|
||||||
# @param callback [Proc, Array<Proc>] | ||||||
# Callback functions should: | ||||||
# - be reentrant safe; | ||||||
# - not take an indefinite amount of time; | ||||||
# - not make duplicate observations (more than one Measurement with the same attributes) | ||||||
# across all registered callbacks; | ||||||
def register_callback(callback) | ||||||
@callback.concat(Array(callback)) | ||||||
end | ||||||
|
||||||
# @param callback [Proc, Array<Proc>] | ||||||
# Callback functions should: | ||||||
# - be reentrant safe; | ||||||
# - not take an indefinite amount of time; | ||||||
# - not make duplicate observations (more than one Measurement with the same attributes) | ||||||
# across all registered callbacks; | ||||||
def unregister_callback(callback) | ||||||
@callback -= Array(callback) | ||||||
end | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is how I imagine register/unregister callback functions would look like in Ruby. Feel free to suggest any changes https://opentelemetry.io/docs/specs/otel/metrics/api/#asynchronous-instrument-api There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just realized that, with this, people are able to register callbacks to No-Op instances of Asynchronous Instruments. I think I should delete these methods' bodies and leave them to be implemented by the SDK Updated in b0ba487 |
||||||
end | ||||||
end | ||||||
end | ||||||
end |
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 | ||
# https://opentelemetry.io/docs/reference/specification/metrics/api/#synchronous-instrument-api | ||
class SynchronousInstrument | ||
attr_reader :name, :unit, :description | ||
|
||
# @api private | ||
def initialize(name, unit: nil, description: nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Did you want to work this in as a follow up PR? https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#synchronous-instrument-api There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in 7cce415 I'm not sure how |
||
@name = name | ||
@unit = unit || '' | ||
@description = description || '' | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,71 +8,156 @@ module OpenTelemetry | |
module Metrics | ||
# No-op implementation of Meter. | ||
class Meter | ||
COUNTER = Instrument::Counter.new | ||
OBSERVABLE_COUNTER = Instrument::ObservableCounter.new | ||
HISTOGRAM = Instrument::Histogram.new | ||
OBSERVABLE_GAUGE = Instrument::ObservableGauge.new | ||
UP_DOWN_COUNTER = Instrument::UpDownCounter.new | ||
OBSERVABLE_UP_DOWN_COUNTER = Instrument::ObservableUpDownCounter.new | ||
NOOP_COUNTER = Instrument::Counter.new('np-op') | ||
NOOP_HISTOGRAM = Instrument::Histogram.new('np-op') | ||
NOOP_UP_DOWN_COUNTER = Instrument::UpDownCounter.new('np-op') | ||
NOOP_OBSERVABLE_COUNTER = Instrument::ObservableCounter.new('np-op') | ||
NOOP_OBSERVABLE_GAUGE = Instrument::ObservableGauge.new('np-op') | ||
NOOP_OBSERVABLE_UP_DOWN_COUNTER = Instrument::ObservableUpDownCounter.new('np-op') | ||
elias19r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private_constant( | ||
:NOOP_COUNTER, | ||
:NOOP_HISTOGRAM, | ||
:NOOP_UP_DOWN_COUNTER, | ||
:NOOP_OBSERVABLE_COUNTER, | ||
:NOOP_OBSERVABLE_GAUGE, | ||
:NOOP_OBSERVABLE_UP_DOWN_COUNTER | ||
) | ||
|
||
attr_reader :name, :version, :schema_url, :attributes | ||
|
||
# @api private | ||
def initialize(name, version: nil, schema_url: nil, attributes: nil) | ||
@name = name | ||
@version = version || '' | ||
@schema_url = schema_url || '' | ||
@attributes = attributes || {} | ||
|
||
NAME_REGEX = /^[a-zA-Z][-.\w]{0,62}$/.freeze | ||
|
||
private_constant(:COUNTER, :OBSERVABLE_COUNTER, :HISTOGRAM, :OBSERVABLE_GAUGE, :UP_DOWN_COUNTER, :OBSERVABLE_UP_DOWN_COUNTER) | ||
|
||
DuplicateInstrumentError = Class.new(OpenTelemetry::Error) | ||
InstrumentNameError = Class.new(OpenTelemetry::Error) | ||
InstrumentUnitError = Class.new(OpenTelemetry::Error) | ||
InstrumentDescriptionError = Class.new(OpenTelemetry::Error) | ||
|
||
def initialize | ||
@mutex = Mutex.new | ||
@instrument_registry = {} | ||
end | ||
|
||
# @param name [String] | ||
# Must conform to the instrument name syntax: | ||
# not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` | ||
# @param unit [optional String] | ||
# Must conform to the instrument unit rule: | ||
# case-sensitive ASCII string with maximum length of 63 characters | ||
# @param description [optional String] | ||
# Must conform to the instrument description rule: | ||
# UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters | ||
# | ||
# @return [Instrument::Counter] | ||
def create_counter(name, unit: nil, description: nil) | ||
create_instrument(:counter, name, unit, description, nil) { COUNTER } | ||
create_instrument(:counter, name, unit, description, nil) { NOOP_COUNTER } | ||
end | ||
|
||
# @param name [String] | ||
# Must conform to the instrument name syntax: | ||
# not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` | ||
# @param unit [optional String] | ||
# Must conform to the instrument unit rule: | ||
# case-sensitive ASCII string with maximum length of 63 characters | ||
# @param description [optional String] | ||
# Must conform to the instrument description rule: | ||
# UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters | ||
# | ||
# @return [Instrument::Histogram] | ||
def create_histogram(name, unit: nil, description: nil) | ||
create_instrument(:histogram, name, unit, description, nil) { HISTOGRAM } | ||
create_instrument(:histogram, name, unit, description, nil) { NOOP_HISTOGRAM } | ||
end | ||
|
||
# @param name [String] | ||
# Must conform to the instrument name syntax: | ||
# not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` | ||
# @param unit [optional String] | ||
# Must conform to the instrument unit rule: | ||
# case-sensitive ASCII string with maximum length of 63 characters | ||
# @param description [optional String] | ||
# Must conform to the instrument description rule: | ||
# UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters | ||
# | ||
# @return [Instrument::UpDownCounter] | ||
def create_up_down_counter(name, unit: nil, description: nil) | ||
create_instrument(:up_down_counter, name, unit, description, nil) { UP_DOWN_COUNTER } | ||
create_instrument(:up_down_counter, name, unit, description, nil) { NOOP_UP_DOWN_COUNTER } | ||
end | ||
|
||
def create_observable_counter(name, unit: nil, description: nil, callback:) | ||
create_instrument(:observable_counter, name, unit, description, callback) { OBSERVABLE_COUNTER } | ||
# @param name [String] | ||
# Must conform to the instrument name syntax: | ||
# not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` | ||
# @param unit [optional String] | ||
# Must conform to the instrument unit rule: | ||
# case-sensitive ASCII string with maximum length of 63 characters | ||
# @param description [optional String] | ||
# Must conform to the instrument description rule: | ||
# UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters | ||
# @param callback [optional Proc, Array<Proc>] | ||
# Callback functions should: | ||
# - be reentrant safe; | ||
# - not take an indefinite amount of time; | ||
# - not make duplicate observations (more than one Measurement with the same attributes) | ||
# across all registered callbacks; | ||
# | ||
# @return [Instrument::ObservableCounter] | ||
def create_observable_counter(name, unit: nil, description: nil, callback: nil) | ||
create_instrument(:observable_counter, name, unit, description, callback) { NOOP_OBSERVABLE_COUNTER } | ||
end | ||
|
||
def create_observable_gauge(name, unit: nil, description: nil, callback:) | ||
create_instrument(:observable_gauge, name, unit, description, callback) { OBSERVABLE_GAUGE } | ||
# @param name [String] | ||
# Must conform to the instrument name syntax: | ||
# not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` | ||
# @param unit [optional String] | ||
# Must conform to the instrument unit rule: | ||
# case-sensitive ASCII string with maximum length of 63 characters | ||
# @param description [optional String] | ||
# Must conform to the instrument description rule: | ||
# UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters | ||
# @param callback [optional Proc, Array<Proc>] | ||
# Callback functions should: | ||
# - be reentrant safe; | ||
# - not take an indefinite amount of time; | ||
# - not make duplicate observations (more than one Measurement with the same attributes) | ||
# across all registered callbacks; | ||
# | ||
# @return [Instrument::ObservableGauge] | ||
def create_observable_gauge(name, unit: nil, description: nil, callback: nil) | ||
create_instrument(:observable_gauge, name, unit, description, callback) { NOOP_OBSERVABLE_GAUGE } | ||
end | ||
|
||
def create_observable_up_down_counter(name, unit: nil, description: nil, callback:) | ||
create_instrument(:observable_up_down_counter, name, unit, description, callback) { OBSERVABLE_UP_DOWN_COUNTER } | ||
# @param name [String] | ||
# Must conform to the instrument name syntax: | ||
# not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` | ||
# @param unit [optional String] | ||
# Must conform to the instrument unit rule: | ||
# case-sensitive ASCII string with maximum length of 63 characters | ||
# @param description [optional String] | ||
# Must conform to the instrument description rule: | ||
# UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters | ||
# @param callback [optional Proc, Array<Proc>] | ||
# Callback functions should: | ||
# - be reentrant safe; | ||
# - not take an indefinite amount of time; | ||
# - not make duplicate observations (more than one Measurement with the same attributes) | ||
# across all registered callbacks; | ||
# | ||
# @return [Instrument::ObservableUpDownCounter] | ||
def create_observable_up_down_counter(name, unit: nil, description: nil, callback: nil) | ||
create_instrument(:observable_up_down_counter, name, unit, description, callback) { NOOP_OBSERVABLE_UP_DOWN_COUNTER } | ||
end | ||
|
||
private | ||
|
||
def create_instrument(kind, name, unit, description, callback) | ||
raise InstrumentNameError if name.nil? | ||
raise InstrumentNameError if name.empty? | ||
raise InstrumentNameError unless NAME_REGEX.match?(name) | ||
raise InstrumentUnitError if unit && (!unit.ascii_only? || unit.size > 63) | ||
raise InstrumentDescriptionError if description && (description.size > 1023 || !utf8mb3_encoding?(description.dup)) | ||
Comment on lines
-59
to
-63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After reading Synchronous Instrument API I noticed the following
and
Similar for So, I think we should remove these validations from here and add yard doc comments instead. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think that's reasonable, the API spec is explicit in that it should perform any validations against the name field, so that will be the responsibility of the SDK implementation. Yard docs seem like a good solution for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also in the "Metrics No-Op API Implementation" docs it makes clear No-Op Meters must not validate. For example:
https://opentelemetry.io/docs/specs/otel/metrics/noop/#counter-creation |
||
name = name.downcase | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is new. From the Metrics' Meter API specification for the Instrument Name Syntax it says:
So here I'm normalizing the name with |
||
|
||
@mutex.synchronize do | ||
OpenTelemetry.logger.warn("duplicate instrument registration occurred for instrument #{name}") if @instrument_registry.include? name | ||
if @instrument_registry.include?(name) | ||
OpenTelemetry.logger.warn("duplicate instrument registration occurred for #{name}") | ||
end | ||
|
||
@instrument_registry[name] = yield | ||
end | ||
end | ||
|
||
def utf8mb3_encoding?(string) | ||
string.force_encoding('UTF-8').valid_encoding? && | ||
string.each_char { |c| return false if c.bytesize >= 4 } | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to revert any of these style changes, just let me know
nit: For this one, I think that naming variables with
something_1
andsomething_2
in tests is easier to read