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

system/process metrics #1

Draft
wants to merge 4 commits into
base: base-metrics
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions instrumentation/base/Appraisals
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
appraise "base" do
remove_gem "opentelemetry-metrics-api"
end

appraise "metrics-api" do
end
2 changes: 2 additions & 0 deletions instrumentation/base/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@

source 'https://rubygems.org'

gem 'opentelemetry-metrics-api'

gemspec
176 changes: 167 additions & 9 deletions instrumentation/base/lib/opentelemetry/instrumentation/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
#
# SPDX-License-Identifier: Apache-2.0

begin
require 'opentelemetry-metrics-api'
rescue LoadError
end

Copy link
Owner Author

Choose a reason for hiding this comment

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

upstream into base-metrics?

module OpenTelemetry
module Instrumentation
# The Base class holds all metadata and configuration for an
Expand Down Expand Up @@ -69,8 +74,9 @@ class << self
integer: ->(v) { v.is_a?(Integer) },
string: ->(v) { v.is_a?(String) }
}.freeze
SINGLETON_MUTEX = Thread::Mutex.new

private_constant :NAME_REGEX, :VALIDATORS
private_constant :NAME_REGEX, :VALIDATORS, :SINGLETON_MUTEX

private :new

Expand Down Expand Up @@ -163,20 +169,62 @@ def option(name, default:, validate:)
end

def instance
@instance ||= new(instrumentation_name, instrumentation_version, install_blk,
present_blk, compatible_blk, options)
@instance || SINGLETON_MUTEX.synchronize do
@instance ||= new(instrumentation_name, instrumentation_version, install_blk,
present_blk, compatible_blk, options, instrument_configs)
end
end

if defined?(OpenTelemetry::Metrics)
%i[
counter
observable_counter
histogram
gauge
observable_gauge
up_down_counter
observable_up_down_counter
].each do |instrument_kind|
define_method(instrument_kind) do |name, **opts, &block|
opts[:callback] ||= block
register_instrument(instrument_kind, name, **opts)
end
end

def register_instrument(kind, name, **opts)
@instrument_configs ||= {}

key = [kind, name]
if @instrument_configs.key?(key)
warn("Duplicate instrument configured for #{self}: #{key.inspect}")
else
@instrument_configs[key] = opts
end
end
else
def counter(*, **); end
def observable_counter(*, **); end
def histogram(*, **); end
def gauge(*, **); end
def observable_gauge(*, **); end
def up_down_counter(*, **); end
def observable_up_down_counter(*, **); end
end

private

attr_reader :install_blk, :present_blk, :compatible_blk, :options
attr_reader :install_blk, :present_blk, :compatible_blk, :options, :instrument_configs

def infer_name
@inferred_name ||= if (md = name.match(NAME_REGEX)) # rubocop:disable Naming/MemoizedInstanceVariableName
md['namespace'] || md['classname']
end
end

def metrics_defined?
defined?(OpenTelemetry::Metrics)
end

def infer_version
return unless (inferred_name = infer_name)

Expand All @@ -189,13 +237,13 @@ def infer_version
end
end

attr_reader :name, :version, :config, :installed, :tracer
attr_reader :name, :version, :config, :installed, :tracer, :meter, :instrument_configs

alias installed? installed

# rubocop:disable Metrics/ParameterLists
def initialize(name, version, install_blk, present_blk,
compatible_blk, options)
compatible_blk, options, instrument_configs)
@name = name
@version = version
@install_blk = install_blk
Expand All @@ -204,7 +252,9 @@ def initialize(name, version, install_blk, present_blk,
@config = {}
@installed = false
@options = options
@tracer = OpenTelemetry::Trace::Tracer.new
@tracer = OpenTelemetry::Trace::Tracer.new # default no-op tracer
@meter = OpenTelemetry::Metrics::Meter.new if defined?(OpenTelemetry::Metrics::Meter) # default no-op meter
@instrument_configs = instrument_configs || {}
end
# rubocop:enable Metrics/ParameterLists

Expand All @@ -217,10 +267,21 @@ def install(config = {})
return true if installed?

@config = config_options(config)

@metrics_enabled = compute_metrics_enabled

if metrics_defined?
@metrics_instruments = {}
@instrument_mutex = Mutex.new
end

return false unless installable?(config)

instance_exec(@config, &@install_blk)
@tracer = OpenTelemetry.tracer_provider.tracer(name, version)
@meter = OpenTelemetry.meter_provider.meter(name, version: version) if metrics_enabled?

instance_exec(@config, &@install_blk)

@installed = true
end

Expand Down Expand Up @@ -261,8 +322,76 @@ def enabled?(config = nil)
true
end

# This is based on a variety of factors, and should be invalidated when @config changes.
# It should be explicitly set in `initialize` for now.
def metrics_enabled?
!!@metrics_enabled
end

# @api private
# ONLY yields if the meter is enabled.
def with_meter
yield @meter if metrics_enabled?
end

if defined?(OpenTelemetry::Metrics)
%i[
counter
observable_counter
histogram
gauge
observable_gauge
up_down_counter
observable_up_down_counter
].each do |kind|
define_method(kind) do |name|
get_metrics_instrument(kind, name)
end
end
end

private

def metrics_defined?
defined?(OpenTelemetry::Metrics)
end

def get_metrics_instrument(kind, name)
# FIXME: we should probably return *something*
# if metrics is not enabled, but if the api is undefined,
# it's unclear exactly what would be suitable.
# For now, there are no public methods that call this
# if metrics isn't defined.
return unless metrics_defined?

@metrics_instruments.fetch([kind, name]) do |key|
@instrument_mutex.synchronize do
@metrics_instruments[key] ||= create_configured_instrument(kind, name)
end
end
end

def create_configured_instrument(kind, name)
config = @instrument_configs[[kind, name]]

# FIXME: what is appropriate here?
if config.nil?
Kernel.warn("unconfigured instrument requested: #{kind} of '#{name}'")
return
end

# FIXME: some of these have different opts;
# should verify that they work before this point.
meter.public_send(:"create_#{kind}", name, **config)
end

def compute_metrics_enabled
return false unless defined?(OpenTelemetry::Metrics)
return false if metrics_disabled_by_env_var?

!!@config[:metrics] || metrics_enabled_by_env_var?
end

# The config_options method is responsible for validating that the user supplied
# config hash is valid.
# Unknown configuration keys are not included in the final config hash.
Expand Down Expand Up @@ -317,13 +446,42 @@ def config_options(user_config)
# will be OTEL_RUBY_INSTRUMENTATION_SINATRA_ENABLED. A value of 'false' will disable
# the instrumentation, all other values will enable it.
def enabled_by_env_var?
!disabled_by_env_var?
end

def disabled_by_env_var?
var_name = name.dup.tap do |n|
n.upcase!
n.gsub!('::', '_')
n.gsub!('OPENTELEMETRY_', 'OTEL_RUBY_')
n << '_ENABLED'
end
ENV[var_name] != 'false'
ENV[var_name] == 'false'
end

# Checks if this instrumentation's metrics are enabled by env var.
# This follows the conventions as outlined above, using `_METRICS_ENABLED` as a suffix.
# Unlike INSTRUMENTATION_*_ENABLED variables, these are explicitly opt-in (i.e.
# if the variable is unset, and `metrics: true` is not in the instrumentation's config,
# the metrics will not be enabled)
def metrics_enabled_by_env_var?
ENV.key?(metrics_env_var_name) && ENV[metrics_env_var_name] != 'false'
end

def metrics_disabled_by_env_var?
ENV[metrics_env_var_name] == 'false'
end

def metrics_env_var_name
@metrics_env_var_name ||=
begin
var_name = name.dup
var_name.upcase!
var_name.gsub!('::', '_')
var_name.gsub!('OPENTELEMETRY_', 'OTEL_RUBY_')
var_name << '_METRICS_ENABLED'
var_name
end
end

# Checks to see if the user has passed any environment variables that set options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
spec.add_dependency 'opentelemetry-common', '~> 0.21'
spec.add_dependency 'opentelemetry-registry', '~> 0.1'

spec.add_development_dependency 'appraisal', '~> 2.5'
spec.add_development_dependency 'bundler', '~> 2.4'
spec.add_development_dependency 'minitest', '~> 5.0'
spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3'
Expand Down
Loading