Skip to content

Commit

Permalink
Ability to limit specific metrics to specific adapters (#37)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrey Novikov <[email protected]>
  • Loading branch information
Keallar and Envek authored Oct 1, 2024
1 parent 8d178bc commit 901e83f
Show file tree
Hide file tree
Showing 18 changed files with 341 additions and 19 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- Ability to limit some metrics to specific adapters. [#37](https://github.com/yabeda-rb/yabeda/pull/37) by [@Keallar] and [@Envek]

```ruby
Yabeda.configure do
group :cloud do
adapter :newrelic, :datadog

counter :foo
end

counter :bar, adapter: :prometheus
end
```

- Multiple expectations in RSpec matchers:

```ruby
Expand Down Expand Up @@ -155,3 +169,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
[@dsalahutdinov]: https://github.com/dsalahutdinov "Dmitry Salahutdinov"
[@asusikov]: https://github.com/asusikov "Alexander Susikov"
[@liaden]: https://github.com/liaden "Joel Johnson"
[@Keallar]: https://github.com/Keallar "Eugene Lysanskiy"
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,32 @@ expect { whatever }.to increment_yabeda_counter(:my_counter).with(
)
```
## Advanced usage
### Limiting metrics and groups to specific adapters
You can limit, which metrics and groups should be available for specific adapter:
```ruby
Yabeda.configure do
group :internal do
adapter :prometheus
counter :foo
gauge :bar
end
group :cloud do
adapter :newrelic
counter :baz
end
counter :qux, adapter: :prometheus
end
```
## Roadmap (aka TODO or Help wanted)
- Ability to change metric settings for individual adapters
Expand All @@ -233,16 +259,6 @@ expect { whatever }.to increment_yabeda_counter(:my_counter).with(
end
```
- Ability to route some metrics only for given adapter:
```rb
adapter :prometheus do
include_group :sidekiq
end
```
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
8 changes: 5 additions & 3 deletions lib/yabeda.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def groups
end
end

# @return [Hash<String, Yabeda::BaseAdapter>] All loaded adapters
# @return [Hash<Symbol, Yabeda::BaseAdapter>] All loaded adapters
def adapters
@adapters ||= Concurrent::Hash.new
end
Expand Down Expand Up @@ -68,6 +68,8 @@ def register_adapter(name, instance)
adapters[name] = instance
# NOTE: Pretty sure there is race condition
metrics.each_value do |metric|
next unless metric.adapters.key?(name)

instance.register!(metric)
end
end
Expand Down Expand Up @@ -99,8 +101,8 @@ def configure!

# Register metrics in adapters after evaluating all configuration blocks
# to ensure that all global settings (like default tags) will be applied.
adapters.each_value do |adapter|
metrics.each_value do |metric|
metrics.each_value do |metric|
metric.adapters.each_value do |adapter|
adapter.register!(metric)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/yabeda/counter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Counter < Metric
def increment(tags, by: 1)
all_tags = ::Yabeda::Tags.build(tags, group)
values[all_tags] += by
::Yabeda.adapters.each_value do |adapter|
adapters.each_value do |adapter|
adapter.perform_counter_increment!(self, all_tags, by)
end
values[all_tags]
Expand Down
12 changes: 11 additions & 1 deletion lib/yabeda/dsl/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,24 @@ def temporary_tags
Thread.current[:yabeda_temporary_tags] ||= {}
end

# Limit all group metrics to specific adapters only
#
# @param adapter_names [Array<Symbol>] Names of adapters to use
def adapter(*adapter_names, group: @group)
raise ConfigurationError, "Adapter limitation can't be defined outside of group" unless group

Yabeda.groups[group] ||= Yabeda::Group.new(group)
Yabeda.groups[group].adapter(*adapter_names)
end

private

def register_metric(metric)
name = [metric.group, metric.name].compact.join("_")
::Yabeda.define_singleton_method(name) { metric }
::Yabeda.metrics[name] = metric
register_group_for(metric) if metric.group
::Yabeda.adapters.each_value { |adapter| adapter.register!(metric) } if ::Yabeda.configured?
metric.adapters.each_value { |adapter| adapter.register!(metric) } if ::Yabeda.configured?
metric
end

Expand Down
2 changes: 1 addition & 1 deletion lib/yabeda/gauge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Gauge < Metric
def set(tags, value)
all_tags = ::Yabeda::Tags.build(tags, group)
values[all_tags] = value
::Yabeda.adapters.each_value do |adapter|
adapters.each_value do |adapter|
adapter.perform_gauge_set!(self, all_tags, value)
end
value
Expand Down
7 changes: 7 additions & 0 deletions lib/yabeda/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ def default_tag(key, value)
@default_tags[key] = value
end

def adapter(*adapter_names)
return @adapter if adapter_names.empty?

@adapter ||= Concurrent::Array.new
@adapter.push(*adapter_names)
end

def register_metric(metric)
define_singleton_method(metric.name) { metric }
end
Expand Down
2 changes: 1 addition & 1 deletion lib/yabeda/histogram.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def measure(tags, value = nil)

all_tags = ::Yabeda::Tags.build(tags, group)
values[all_tags] = value
::Yabeda.adapters.each_value do |adapter|
adapters.each_value do |adapter|
adapter.perform_histogram_measure!(self, all_tags, value)
end
value
Expand Down
31 changes: 30 additions & 1 deletion lib/yabeda/metric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Metric
option :per, optional: true, comment: "Per which unit is measured `unit`. E.g. `call` as in seconds per call"
option :group, optional: true, comment: "Category name for grouping metrics"
option :aggregation, optional: true, comment: "How adapters should aggregate values from different processes"
# rubocop:disable Layout/LineLength
option :adapter, optional: true, comment: "Monitoring system adapter to register metric in and report metric values to (other adapters won't be used)"
# rubocop:enable Layout/LineLength

# Returns the value for the given label set
def get(labels = {})
Expand All @@ -25,13 +28,39 @@ def values
end

# Returns allowed tags for metric (with account for global and group-level +default_tags+)
# @return Array<Symbol>
# @return [Array<Symbol>]
def tags
(Yabeda.groups[group].default_tags.keys + Array(super)).uniq
end

def inspect
"#<#{self.class.name}: #{[@group, @name].compact.join('.')}>"
end

# Returns the metric adapters
# @return [Hash<Symbol, Yabeda::BaseAdapter>]
def adapters
return ::Yabeda.adapters unless adapter

@adapters ||= begin
adapter_names = Array(adapter)
unknown_adapters = adapter_names - ::Yabeda.adapters.keys

if unknown_adapters.any?
raise ConfigurationError,
"invalid adapter option #{adapter.inspect} in metric #{inspect}"
end

::Yabeda.adapters.slice(*adapter_names)
end
end

# Redefined option reader to get group-level adapter if not set on metric level
# @api private
def adapter
return ::Yabeda.groups[group]&.adapter if @adapter == Dry::Initializer::UNDEFINED

super
end
end
end
2 changes: 1 addition & 1 deletion lib/yabeda/summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def observe(tags, value = nil)

all_tags = ::Yabeda::Tags.build(tags, group)
values[all_tags] = value
::Yabeda.adapters.each_value do |adapter|
adapters.each_value do |adapter|
adapter.perform_summary_observe!(self, all_tags, value)
end
value
Expand Down
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "bundler/setup"
require "yabeda"
require "yabeda/base_adapter"
require "pry"

RSpec.configure do |config|
Expand Down
22 changes: 22 additions & 0 deletions spec/yabeda/counter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,26 @@
increment_counter
expect(adapter).to have_received(:perform_counter_increment!).with(counter, built_tags, metric_value)
end

context "with adapter option" do
let(:counter) { Yabeda.counter_with_adapter }
let(:another_adapter) { instance_double(Yabeda::BaseAdapter, perform_counter_increment!: true, register!: true) }

before do
Yabeda.register_adapter(:another_adapter, another_adapter)
Yabeda.configure do
counter :counter_with_adapter, adapter: :test_adapter
end
Yabeda.configure! unless Yabeda.already_configured?
end

it "execute perform_counter_increment! method of adapter with name :test_adapter" do
increment_counter

aggregate_failures do
expect(adapter).to have_received(:perform_counter_increment!).with(counter, built_tags, metric_value)
expect(another_adapter).not_to have_received(:perform_counter_increment!)
end
end
end
end
55 changes: 55 additions & 0 deletions spec/yabeda/dsl/class_methods_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,59 @@
end
end
end

describe ".configure" do
subject(:configure) { Yabeda.configure(&block) }

let(:block) { proc { histogram :test_histogram, buckets: [42] } }

before do
Yabeda.register_adapter(:another_adapter, Yabeda::TestAdapter.instance)
Yabeda.configure! unless Yabeda.configured?
end

it "register metric" do
configure

expect(Yabeda.test_histogram).to be_a(Yabeda::Histogram)
end

context "when got metric with adapter option" do
let(:block) { proc { histogram :invalid_test, buckets: [42], adapter: :another_adapter } }

it { expect { configure }.not_to raise_error }

context "when option is invalid" do
let(:block) { proc { histogram :invalid_test, buckets: [42], adapter: :invalid } }

it { expect { configure }.to raise_error(Yabeda::ConfigurationError, /invalid adapter option/) }
end
end
end

describe ".adapter" do
context "when group is not defined" do
it "raises an error" do
expect do
Yabeda.configure { adapter :test }
Yabeda.configure! unless Yabeda.already_configured?
end.to raise_error(Yabeda::ConfigurationError, /can't be defined outside of group/)
end
end

context "with a specified group that does not exist" do
before do
Yabeda.configure { adapter :test, group: :adapter_group }
Yabeda.configure! unless Yabeda.already_configured?
end

it "creates the group" do
expect(Yabeda.groups[:adapter_group]).to be_a(Yabeda::Group)
end

it "defines the default tag" do
expect(Yabeda.groups[:adapter_group].adapter).to eq(%i[test])
end
end
end
end
22 changes: 22 additions & 0 deletions spec/yabeda/gauge_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,26 @@
end
end
end

context "with adapter option" do
let(:gauge) { Yabeda.gauge_with_adapter }
let(:another_adapter) { instance_double(Yabeda::BaseAdapter, perform_gauge_set!: true, register!: true) }

before do
Yabeda.register_adapter(:another_adapter, another_adapter)
Yabeda.configure do
gauge :gauge_with_adapter, adapter: :test_adapter
end
Yabeda.configure! unless Yabeda.already_configured?
end

it "execute perform_counter_increment! method of adapter with name :test_adapter" do
set_gauge

aggregate_failures do
expect(adapter).to have_received(:perform_gauge_set!).with(gauge, built_tags, metric_value)
expect(another_adapter).not_to have_received(:perform_gauge_set!)
end
end
end
end
22 changes: 22 additions & 0 deletions spec/yabeda/histogram_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,26 @@
expect { measure_histogram }.to raise_error(ArgumentError)
end
end

context "with adapter option" do
let(:histogram) { Yabeda.histogram_with_adapter }
let(:another_adapter) { instance_double(Yabeda::BaseAdapter, perform_histogram_measure!: true, register!: true) }

before do
Yabeda.register_adapter(:another_adapter, another_adapter)
Yabeda.configure do
histogram :histogram_with_adapter, adapter: :test_adapter, buckets: [1, 10, 100]
end
Yabeda.configure! unless Yabeda.already_configured?
end

it "execute perform_counter_increment! method of adapter with name :test_adapter" do
measure_histogram

aggregate_failures do
expect(adapter).to have_received(:perform_histogram_measure!).with(histogram, built_tags, metric_value)
expect(another_adapter).not_to have_received(:perform_histogram_measure!)
end
end
end
end
Loading

0 comments on commit 901e83f

Please sign in to comment.