yabeda 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52ac87de3f6f2edd66e88d967b2e2e7231a87a27189e9d0a129cbafc6041813f
4
- data.tar.gz: 2096a9ea457d7aa10efdcb54bc29a6a6e520e15974bba1efafc53df2921b5116
3
+ metadata.gz: e8a48fc237f75ffdb834af3b14fe7d991cb1f65a37d528a0139d2cc2895d3e6b
4
+ data.tar.gz: f3b316975ff26a048e9a8361c93b164591f3b3add12bea20a01b0f47316519f2
5
5
  SHA512:
6
- metadata.gz: 3a75f40eb17ab3d35feef5449bfe07e369487c61609eca03da0f7eec17d1d7e32ad5b51b4a5079d3247b994df8e9730d681d501b7fa7b71d17a9b7d1ede06993
7
- data.tar.gz: bcf1c14781cb17a3621ed4f03a3431b2399ee9e4ec80bfa044455903d2d4e9ab25563b94a01207435b05e1898e681dda91c6f36d437e574c33060f0f7c1014ef
6
+ metadata.gz: e9f252ca373fbf287c6ce674180e7cb58c35177951e903767722e8ad396bbee7cbebff73c6e9fcb7e2e43e30c6599c0aea5a53a7f4f546be28382a173ce5c856
7
+ data.tar.gz: 43fff38b19ef0fcde369eb4f43f78049543949d54d9f23d130a7e895ed7e10d62a6612f9328d1e9128cf1354578423f6992d56ee354063614c47f350cd960682
@@ -3,7 +3,9 @@ name: Lint Ruby
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - master
6
+ - '**'
7
+ tags-ignore:
8
+ - 'v*'
7
9
  paths:
8
10
  - "gemfiles/*"
9
11
  - "Gemfile"
@@ -20,12 +22,15 @@ on:
20
22
 
21
23
  jobs:
22
24
  rubocop:
25
+ # Skip running tests for local pull requests (use push event instead), run only for foreign ones
26
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login != github.event.pull_request.base.repo.owner.login
27
+ name: RuboCop
23
28
  runs-on: ubuntu-latest
24
29
  steps:
25
- - uses: actions/checkout@v3
30
+ - uses: actions/checkout@v4
26
31
  - uses: ruby/setup-ruby@v1
27
32
  with:
28
- ruby-version: 3.2
33
+ ruby-version: "3.3"
29
34
  bundler-cache: true
30
35
  - name: Lint Ruby code with RuboCop
31
36
  run: |
@@ -1,4 +1,4 @@
1
- name: Build and release gem to RubyGems
1
+ name: Build and release gem
2
2
 
3
3
  on:
4
4
  push:
@@ -8,13 +8,17 @@ on:
8
8
  jobs:
9
9
  release:
10
10
  runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ id-token: write
14
+ packages: write
11
15
  steps:
12
- - uses: actions/checkout@v2
16
+ - uses: actions/checkout@v4
13
17
  with:
14
18
  fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290
15
19
  - uses: ruby/setup-ruby@v1
16
20
  with:
17
- ruby-version: 2.7
21
+ ruby-version: "3.3"
18
22
  - name: "Extract data from tag: version, message, body"
19
23
  id: tag
20
24
  run: |
@@ -75,8 +79,8 @@ jobs:
75
79
  GEM_HOST_API_KEY: Bearer ${{ secrets.GITHUB_TOKEN }}
76
80
  run: |
77
81
  gem push yabeda-${{ steps.tag.outputs.version }}.gem --host https://rubygems.pkg.github.com/${{ github.repository_owner }}
82
+ - name: Configure RubyGems Credentials
83
+ uses: rubygems/configure-rubygems-credentials@main
78
84
  - name: Publish to RubyGems
79
- env:
80
- GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_API_KEY }}"
81
85
  run: |
82
86
  gem push yabeda-${{ steps.tag.outputs.version }}.gem
@@ -11,32 +11,23 @@ on:
11
11
  jobs:
12
12
  test:
13
13
  name: "Ruby ${{ matrix.ruby }}"
14
+ # Skip running tests for local pull requests (use push event instead), run only for foreign ones
15
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login != github.event.pull_request.base.repo.owner.login
14
16
  runs-on: ubuntu-latest
15
17
  strategy:
16
18
  fail-fast: false
17
19
  matrix:
18
20
  include:
21
+ - ruby: "3.3"
19
22
  - ruby: "3.2"
20
23
  - ruby: "3.1"
21
24
  - ruby: "3.0"
22
25
  - ruby: "2.7"
23
- container:
24
- image: ruby:${{ matrix.ruby }}
25
- env:
26
- CI: true
27
26
  steps:
28
- - uses: actions/checkout@v2
29
- - uses: actions/cache@v2
27
+ - uses: actions/checkout@v4
28
+ - uses: ruby/setup-ruby@v1
30
29
  with:
31
- path: vendor/bundle
32
- key: bundle-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles('**/Gemfile') }}
33
- restore-keys: |
34
- bundle-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles('**/Gemfile') }}
35
- bundle-${{ matrix.ruby }}-
36
- - name: Bundle install
37
- run: |
38
- bundle config path vendor/bundle
39
- bundle install
40
- bundle update
30
+ ruby-version: ${{ matrix.ruby }}
31
+ bundler-cache: true
41
32
  - name: Run RSpec
42
33
  run: bundle exec rspec
data/.rubocop.yml CHANGED
@@ -35,6 +35,9 @@ RSpec/NestedGroups:
35
35
  RSpec/MultipleMemoizedHelpers:
36
36
  Enabled: false
37
37
 
38
+ RSpec/ExampleLength:
39
+ Max: 10
40
+
38
41
  Bundler/OrderedGems:
39
42
  Enabled: false
40
43
 
data/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.13.1 - 2024-10-11
11
+
12
+ ### Fixed
13
+
14
+ - Compatibility with Ruby 2.x, broken in 0.13.0 due to differences of keywords handling in Ruby 2.x for case when method has arguments with default values. [@Envek]
15
+
16
+ ## 0.13.0 - 2024-10-02
17
+
18
+ ### Added
19
+
20
+ - Ability to limit some metrics to specific adapters. [#37](https://github.com/yabeda-rb/yabeda/pull/37) by [@Keallar] and [@Envek]
21
+
22
+ ```ruby
23
+ Yabeda.configure do
24
+ group :cloud do
25
+ adapter :newrelic, :datadog
26
+
27
+ counter :foo
28
+ end
29
+
30
+ counter :bar, adapter: :prometheus
31
+ end
32
+ ```
33
+
34
+ - Multiple expectations in RSpec matchers. [@Envek]
35
+
36
+ ```ruby
37
+ expect { whatever }.to increment_yabeda_counter(:my_counter).with(
38
+ { tag: "foo" } => 1,
39
+ { tag: "bar" } => (be >= 42),
40
+ )
41
+ ```
42
+
43
+ ### Changed
44
+
45
+ - Don't require to provide tags for counters and histograms, use empty tags (`{}`) by default. See discussion at [#26](https://github.com/yabeda-rb/yabeda/issues/26). [@Envek]
46
+
47
+ ```ruby
48
+ Yabeda.foo.increment
49
+ # same as
50
+ Yabeda.foo.increment({}, by: 1)
51
+ ```
52
+
53
+ ### Fixed
54
+
55
+ - Railtie loading to prevent calling methods that have not yet been defined. [#38](https://github.com/yabeda-rb/yabeda/pull/38) by [@bibendi].
56
+
10
57
  ## 0.12.0 - 2023-07-28
11
58
 
12
59
  ### Added
@@ -37,7 +84,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
37
84
 
38
85
  ### Changed
39
86
 
40
- - Adapters now should use method `Yabeda.collect!` instead of manual calling of every collector block.
87
+ - Adapters now should use method `Yabeda.collect!` instead of manual calling of every collector block.
41
88
 
42
89
  ## 0.9.0 - 2021-05-07
43
90
 
@@ -140,3 +187,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
140
187
  [@dsalahutdinov]: https://github.com/dsalahutdinov "Dmitry Salahutdinov"
141
188
  [@asusikov]: https://github.com/asusikov "Alexander Susikov"
142
189
  [@liaden]: https://github.com/liaden "Joel Johnson"
190
+ [@bibendi]: https://github.com/bibendi "Misha Merkushin"
191
+ [@Keallar]: https://github.com/Keallar "Eugene Lysanskiy"
data/Gemfile CHANGED
@@ -16,6 +16,6 @@ group :development, :test do
16
16
  gem "pry"
17
17
  gem "pry-byebug", platform: :mri
18
18
 
19
- gem "rubocop", "~> 1.0"
20
- gem "rubocop-rspec"
19
+ gem "rubocop", "~> 1.0", require: false
20
+ gem "rubocop-rspec", require: false
21
21
  end
data/README.md CHANGED
@@ -130,6 +130,7 @@ These are developed and maintained by other awesome folks:
130
130
 
131
131
  - [Statsd](https://github.com/asusikov/yabeda-statsd)
132
132
  - [AWS CloudWatch](https://github.com/retsef/yabeda-cloudwatch)
133
+ - [Honeybadger Insights](https://github.com/honeybadger-io/yabeda-honeybadger_insights)
133
134
  - _…and more! You can write your own adapter and open a pull request to add it into this list._
134
135
 
135
136
  ## Available plugins to collect metrics
@@ -156,6 +157,7 @@ These are developed and maintained by other awesome folks:
156
157
  - [yabeda-activejob](https://github.com/Fullscript/yabeda-activejob) — backend-agnostic metrics for background jobs.
157
158
  - [yabeda-shoryuken](https://github.com/retsef/yabeda-shoryuken) — metrics for [Shoryuken](https://github.com/ruby-shoryuken/shoryuken) jobs execution message queues.
158
159
  - [yabeda-rack-ratelimit](https://github.com/basecamp/yabeda-rack-ratelimit) — metrics for [Rack::Ratelimit](https://github.com/jeremy/rack-ratelimit)
160
+ - [yabeda-hanami](https://github.com/mlibrary/yabeda-hanami) — metrics for [Hanami](https://hanamirb.org/) The web, with simplicity.
159
161
  - _…and more! You can write your own adapter and open a pull request to add it into this list._
160
162
 
161
163
  ## Configuration
@@ -210,6 +212,41 @@ expect { subject }.to \
210
212
  with(be_between(0.005, 0.05))
211
213
  ```
212
214
 
215
+ You also can specify multiple tags and their expected values in `with`:
216
+
217
+ ```ruby
218
+ expect { whatever }.to increment_yabeda_counter(:my_counter).with(
219
+ { tag: "foo" } => 1,
220
+ { tag: "bar" } => (be >= 42),
221
+ )
222
+ ```
223
+
224
+ ## Advanced usage
225
+
226
+ ### Limiting metrics and groups to specific adapters
227
+
228
+ You can limit, which metrics and groups should be available for specific adapter:
229
+
230
+ ```ruby
231
+ Yabeda.configure do
232
+ group :internal do
233
+ adapter :prometheus
234
+
235
+ counter :foo
236
+ gauge :bar
237
+ end
238
+
239
+ group :cloud do
240
+ adapter :newrelic
241
+
242
+ counter :baz
243
+ end
244
+
245
+ counter :qux, adapter: :prometheus
246
+ end
247
+ ```
248
+
249
+
213
250
  ## Roadmap (aka TODO or Help wanted)
214
251
 
215
252
  - Ability to change metric settings for individual adapters
@@ -222,16 +259,6 @@ expect { subject }.to \
222
259
  end
223
260
  ```
224
261
 
225
- - Ability to route some metrics only for given adapter:
226
-
227
- ```rb
228
- adapter :prometheus do
229
- include_group :sidekiq
230
- end
231
- ```
232
-
233
-
234
-
235
262
  ## Development
236
263
 
237
264
  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.
@@ -3,10 +3,15 @@
3
3
  module Yabeda
4
4
  # Growing-only counter
5
5
  class Counter < Metric
6
- def increment(tags, by: 1)
6
+ # @overload increment(tags = {}, by: 1)
7
+ # Increment the counter for given set of tags by the given increment value
8
+ # @param tags Hash{Symbol=>#to_s} tags to identify the counter
9
+ # @param by [Integer] strictly positive increment value
10
+ def increment(*args)
11
+ tags, by = self.class.parse_args(*args)
7
12
  all_tags = ::Yabeda::Tags.build(tags, group)
8
13
  values[all_tags] += by
9
- ::Yabeda.adapters.each do |_, adapter|
14
+ adapters.each_value do |adapter|
10
15
  adapter.perform_counter_increment!(self, all_tags, by)
11
16
  end
12
17
  values[all_tags]
@@ -15,5 +20,25 @@ module Yabeda
15
20
  def values
16
21
  @values ||= Concurrent::Hash.new(0)
17
22
  end
23
+
24
+ # @api private
25
+ # rubocop:disable Metrics/MethodLength
26
+ def self.parse_args(*args)
27
+ case args.size
28
+ when 0 # increment()
29
+ [EMPTY_TAGS, 1]
30
+ when 1 # increment(by: 5) or increment(tags)
31
+ if args[0].key?(:by)
32
+ [EMPTY_TAGS, args.fetch(:by)]
33
+ else
34
+ [args[0], 1]
35
+ end
36
+ when 2 # increment(tags, by: 5)
37
+ [args[0], args[1].fetch(:by, 1)]
38
+ else
39
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..2)"
40
+ end
41
+ end
42
+ # rubocop:enable Metrics/MethodLength
18
43
  end
19
44
  end
@@ -92,6 +92,16 @@ module Yabeda
92
92
  Thread.current[:yabeda_temporary_tags] ||= {}
93
93
  end
94
94
 
95
+ # Limit all group metrics to specific adapters only
96
+ #
97
+ # @param adapter_names [Array<Symbol>] Names of adapters to use
98
+ def adapter(*adapter_names, group: @group)
99
+ raise ConfigurationError, "Adapter limitation can't be defined outside of group" unless group
100
+
101
+ Yabeda.groups[group] ||= Yabeda::Group.new(group)
102
+ Yabeda.groups[group].adapter(*adapter_names)
103
+ end
104
+
95
105
  private
96
106
 
97
107
  def register_metric(metric)
@@ -99,7 +109,7 @@ module Yabeda
99
109
  ::Yabeda.define_singleton_method(name) { metric }
100
110
  ::Yabeda.metrics[name] = metric
101
111
  register_group_for(metric) if metric.group
102
- ::Yabeda.adapters.each_value { |adapter| adapter.register!(metric) } if ::Yabeda.configured?
112
+ metric.adapters.each_value { |adapter| adapter.register!(metric) } if ::Yabeda.configured?
103
113
  metric
104
114
  end
105
115
 
data/lib/yabeda/gauge.rb CHANGED
@@ -6,17 +6,27 @@ module Yabeda
6
6
  def set(tags, value)
7
7
  all_tags = ::Yabeda::Tags.build(tags, group)
8
8
  values[all_tags] = value
9
- ::Yabeda.adapters.each do |_, adapter|
9
+ adapters.each_value do |adapter|
10
10
  adapter.perform_gauge_set!(self, all_tags, value)
11
11
  end
12
12
  value
13
13
  end
14
14
 
15
- def increment(tags, by: 1)
15
+ # @overload increment(tags = {}, by: 1)
16
+ # Convenience method to increment current gauge value for given set of tags by the given increment value
17
+ # @param tags Hash{Symbol=>#to_s} tags to identify the gauge
18
+ # @param by [Integer] increment value
19
+ def increment(*args)
20
+ tags, by = Counter.parse_args(*args)
16
21
  set(tags, get(tags).to_i + by)
17
22
  end
18
23
 
19
- def decrement(tags, by: 1)
24
+ # @overload decrement(tags = {}, by: 1)
25
+ # Convenience method to decrement current gauge value for given set of tags by the given decrement value
26
+ # @param tags Hash{Symbol=>#to_s} tags to identify the gauge
27
+ # @param by [Integer] decrement value
28
+ def decrement(*args)
29
+ tags, by = Counter.parse_args(*args)
20
30
  set(tags, get(tags).to_i - by)
21
31
  end
22
32
  end
data/lib/yabeda/group.rb CHANGED
@@ -19,6 +19,13 @@ module Yabeda
19
19
  @default_tags[key] = value
20
20
  end
21
21
 
22
+ def adapter(*adapter_names)
23
+ return @adapter if adapter_names.empty?
24
+
25
+ @adapter ||= Concurrent::Array.new
26
+ @adapter.push(*adapter_names)
27
+ end
28
+
22
29
  def register_metric(metric)
23
30
  define_singleton_method(metric.name) { metric }
24
31
  end
@@ -7,7 +7,7 @@ module Yabeda
7
7
  option :buckets
8
8
 
9
9
  # rubocop: disable Metrics/MethodLength
10
- def measure(tags, value = nil)
10
+ def measure(tags = {}, value = nil)
11
11
  if value.nil? ^ block_given?
12
12
  raise ArgumentError, "You must provide either numeric value or block for Yabeda::Histogram#measure!"
13
13
  end
@@ -20,7 +20,7 @@ module Yabeda
20
20
 
21
21
  all_tags = ::Yabeda::Tags.build(tags, group)
22
22
  values[all_tags] = value
23
- ::Yabeda.adapters.each do |_, adapter|
23
+ adapters.each_value do |adapter|
24
24
  adapter.perform_histogram_measure!(self, all_tags, value)
25
25
  end
26
26
  value
data/lib/yabeda/metric.rb CHANGED
@@ -14,6 +14,9 @@ module Yabeda
14
14
  option :per, optional: true, comment: "Per which unit is measured `unit`. E.g. `call` as in seconds per call"
15
15
  option :group, optional: true, comment: "Category name for grouping metrics"
16
16
  option :aggregation, optional: true, comment: "How adapters should aggregate values from different processes"
17
+ # rubocop:disable Layout/LineLength
18
+ option :adapter, optional: true, comment: "Monitoring system adapter to register metric in and report metric values to (other adapters won't be used)"
19
+ # rubocop:enable Layout/LineLength
17
20
 
18
21
  # Returns the value for the given label set
19
22
  def get(labels = {})
@@ -25,7 +28,7 @@ module Yabeda
25
28
  end
26
29
 
27
30
  # Returns allowed tags for metric (with account for global and group-level +default_tags+)
28
- # @return Array<Symbol>
31
+ # @return [Array<Symbol>]
29
32
  def tags
30
33
  (Yabeda.groups[group].default_tags.keys + Array(super)).uniq
31
34
  end
@@ -33,5 +36,31 @@ module Yabeda
33
36
  def inspect
34
37
  "#<#{self.class.name}: #{[@group, @name].compact.join('.')}>"
35
38
  end
39
+
40
+ # Returns the metric adapters
41
+ # @return [Hash<Symbol, Yabeda::BaseAdapter>]
42
+ def adapters
43
+ return ::Yabeda.adapters unless adapter
44
+
45
+ @adapters ||= begin
46
+ adapter_names = Array(adapter)
47
+ unknown_adapters = adapter_names - ::Yabeda.adapters.keys
48
+
49
+ if unknown_adapters.any?
50
+ raise ConfigurationError,
51
+ "invalid adapter option #{adapter.inspect} in metric #{inspect}"
52
+ end
53
+
54
+ ::Yabeda.adapters.slice(*adapter_names)
55
+ end
56
+ end
57
+
58
+ # Redefined option reader to get group-level adapter if not set on metric level
59
+ # @api private
60
+ def adapter
61
+ return ::Yabeda.groups[group]&.adapter if @adapter == Dry::Initializer::UNDEFINED
62
+
63
+ super
64
+ end
36
65
  end
37
66
  end
@@ -8,21 +8,30 @@ module Yabeda
8
8
  # Example:
9
9
  # expect { anything }.to do_whatever_with_yabeda_metric(Yabeda.something)
10
10
  class BaseMatcher < ::RSpec::Matchers::BuiltIn::BaseMatcher
11
- attr_reader :tags, :metric
11
+ attr_reader :tags, :metric, :expectations
12
12
 
13
13
  # Specify a scope of labels (tags). Subset of tags can be specified.
14
14
  def with_tags(tags)
15
+ raise ArgumentError, "Can't use `with_tags` with expectations hash provided" if !@tags && @expectations&.any?
16
+
15
17
  @tags = tags
18
+ @expectations = { tags => nil }
19
+ self
20
+ end
21
+
22
+ def with(expectations)
23
+ @expectations = expectations || {}
16
24
  self
17
25
  end
18
26
 
19
- def initialize(expected)
27
+ def initialize(metric)
20
28
  super
21
- @expected = @metric = resolve_metric(expected)
29
+ @expected = @metric = resolve_metric(metric)
30
+ @expectations = {}
22
31
  rescue KeyError
23
32
  raise ArgumentError, <<~MSG
24
33
  Pass metric name or metric instance to matcher (e.g. `increment_yabeda_counter(Yabeda.metric_name)` or \
25
- increment_yabeda_counter('metric_name')). Got #{expected.inspect} instead
34
+ increment_yabeda_counter('metric_name')). Got #{metric.inspect} instead
26
35
  MSG
27
36
  end
28
37
 
@@ -56,9 +65,11 @@ module Yabeda
56
65
  # Filter metric changes by tags.
57
66
  # If tags specified, treat them as subset of real tags (to avoid bothering with default tags in tests)
58
67
  def filter_matching_changes(changes)
59
- return changes if tags.nil?
68
+ return changes.map { |tags, change| [tags, [nil, change]] }.to_h unless expectations&.any?
60
69
 
61
- changes.select { |t, _v| t >= tags }
70
+ expectations.map do |tags, expected|
71
+ [tags, [expected, changes.find { |t, _v| t >= tags }&.[](1)]]
72
+ end.to_h
62
73
  end
63
74
  end
64
75
  end
@@ -15,6 +15,7 @@ module Yabeda
15
15
  class IncrementYabedaCounter < BaseMatcher
16
16
  def by(increment)
17
17
  @expected_increment = increment
18
+ @expectations = { tags => increment } if tags
18
19
  self
19
20
  end
20
21
 
@@ -32,8 +33,12 @@ module Yabeda
32
33
 
33
34
  increments = filter_matching_changes(Yabeda::TestAdapter.instance.counters.fetch(metric))
34
35
 
35
- increments.values.any? do |actual_increment|
36
- expected_increment.nil? || values_match?(expected_increment, actual_increment)
36
+ return false if increments.empty?
37
+
38
+ increments.values.all? do |expected_increment, actual_increment|
39
+ next !actual_increment.nil? if expected_increment.nil?
40
+
41
+ values_match?(expected_increment, actual_increment)
37
42
  end
38
43
  end
39
44
 
@@ -49,13 +54,16 @@ module Yabeda
49
54
 
50
55
  increments = filter_matching_changes(Yabeda::TestAdapter.instance.counters.fetch(metric))
51
56
 
52
- increments.none?
57
+ increments.none? { |_tags, (_expected, actual)| !actual.nil? }
53
58
  end
54
59
 
55
60
  def failure_message
56
61
  "expected #{expected_formatted} " \
57
62
  "to be incremented #{"by #{description_of(expected_increment)} " unless expected_increment.nil?}" \
58
63
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
64
+ "#{if !tags && expectations
65
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
66
+ end}" \
59
67
  "but #{actual_increments_message}"
60
68
  end
61
69
 
@@ -63,6 +71,9 @@ module Yabeda
63
71
  "expected #{expected_formatted} " \
64
72
  "not to be incremented " \
65
73
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
74
+ "#{if !tags && expectations
75
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
76
+ end}" \
66
77
  "but #{actual_increments_message}"
67
78
  end
68
79
 
@@ -14,6 +14,8 @@ module Yabeda
14
14
  # Custom matcher class with implementation for +measure_yabeda_histogram+
15
15
  class MeasureYabedaHistogram < BaseMatcher
16
16
  def with(value)
17
+ return super if value.is_a?(Hash)
18
+
17
19
  @expected_value = value
18
20
  self
19
21
  end
@@ -32,7 +34,13 @@ module Yabeda
32
34
 
33
35
  measures = filter_matching_changes(Yabeda::TestAdapter.instance.histograms.fetch(metric))
34
36
 
35
- measures.values.any? { |measure| expected_value.nil? || values_match?(expected_value, measure) }
37
+ return false if measures.empty?
38
+
39
+ measures.values.all? do |expected_measure, actual_measure|
40
+ next !actual_measure.nil? if expected_measure.nil?
41
+
42
+ values_match?(expected_measure, actual_measure)
43
+ end
36
44
  end
37
45
 
38
46
  def match_when_negated(metric, block)
@@ -47,13 +55,16 @@ module Yabeda
47
55
 
48
56
  measures = filter_matching_changes(Yabeda::TestAdapter.instance.histograms.fetch(metric))
49
57
 
50
- measures.none?
58
+ measures.none? { |_tags, (_expected, actual)| !actual.nil? }
51
59
  end
52
60
 
53
61
  def failure_message
54
62
  "expected #{expected_formatted} " \
55
63
  "to be changed #{"to #{expected} " unless expected_value.nil?}" \
56
64
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
65
+ "#{if !tags && expectations
66
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
67
+ end}" \
57
68
  "but #{actual_changes_message}"
58
69
  end
59
70
 
@@ -61,6 +72,9 @@ module Yabeda
61
72
  "expected #{expected_formatted} " \
62
73
  "not to be incremented " \
63
74
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
75
+ "#{if !tags && expectations
76
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
77
+ end}" \
64
78
  "but #{actual_changes_message}"
65
79
  end
66
80
 
@@ -14,6 +14,8 @@ module Yabeda
14
14
  # Custom matcher class with implementation for +observe_yabeda_summary+
15
15
  class ObserveYabedaSummary < BaseMatcher
16
16
  def with(value)
17
+ return super if value.is_a?(Hash)
18
+
17
19
  @expected_value = value
18
20
  self
19
21
  end
@@ -32,7 +34,13 @@ module Yabeda
32
34
 
33
35
  observations = filter_matching_changes(Yabeda::TestAdapter.instance.summaries.fetch(metric))
34
36
 
35
- observations.values.any? { |observation| expected_value.nil? || values_match?(expected_value, observation) }
37
+ return false if observations.empty?
38
+
39
+ observations.values.all? do |expected_observation, actual_observation|
40
+ next !actual_observation.nil? if expected_observation.nil?
41
+
42
+ values_match?(expected_observation, actual_observation)
43
+ end
36
44
  end
37
45
 
38
46
  def match_when_negated(metric, block)
@@ -47,13 +55,16 @@ module Yabeda
47
55
 
48
56
  observations = filter_matching_changes(Yabeda::TestAdapter.instance.summaries.fetch(metric))
49
57
 
50
- observations.none?
58
+ observations.none? { |_tags, (_expected, actual)| !actual.nil? }
51
59
  end
52
60
 
53
61
  def failure_message
54
62
  "expected #{expected_formatted} " \
55
63
  "to be observed #{"with #{expected} " unless expected_value.nil?}" \
56
64
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
65
+ "#{if !tags && expectations
66
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
67
+ end}" \
57
68
  "but #{actual_changes_message}"
58
69
  end
59
70
 
@@ -61,6 +72,9 @@ module Yabeda
61
72
  "expected #{expected_formatted} " \
62
73
  "not to be observed " \
63
74
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
75
+ "#{if !tags && expectations
76
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
77
+ end}" \
64
78
  "but #{actual_changes_message}"
65
79
  end
66
80
 
@@ -14,6 +14,8 @@ module Yabeda
14
14
  # Custom matcher class with implementation for +update_yabeda_gauge+
15
15
  class UpdateYabedaGauge < BaseMatcher
16
16
  def with(value)
17
+ return super if value.is_a?(Hash)
18
+
17
19
  @expected_value = value
18
20
  self
19
21
  end
@@ -32,7 +34,13 @@ module Yabeda
32
34
 
33
35
  updates = filter_matching_changes(Yabeda::TestAdapter.instance.gauges.fetch(metric))
34
36
 
35
- updates.values.any? { |update| expected_value.nil? || values_match?(expected_value, update) }
37
+ return false if updates.empty?
38
+
39
+ updates.values.all? do |expected_update, actual_update|
40
+ next !actual_update.nil? if expected_update.nil?
41
+
42
+ expected_update.nil? || values_match?(expected_update, actual_update)
43
+ end
36
44
  end
37
45
 
38
46
  def match_when_negated(metric, block)
@@ -47,13 +55,16 @@ module Yabeda
47
55
 
48
56
  updates = filter_matching_changes(Yabeda::TestAdapter.instance.gauges.fetch(metric))
49
57
 
50
- updates.none?
58
+ updates.none? { |_tags, (_expected, actual)| !actual.nil? }
51
59
  end
52
60
 
53
61
  def failure_message
54
62
  "expected #{expected_formatted} " \
55
63
  "to be changed #{"to #{expected_value} " unless expected_value.nil?}" \
56
64
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
65
+ "#{if !tags && expectations
66
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
67
+ end}" \
57
68
  "but #{actual_changes_message}"
58
69
  end
59
70
 
@@ -61,6 +72,9 @@ module Yabeda
61
72
  "expected #{expected_formatted} " \
62
73
  "not to be changed " \
63
74
  "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
75
+ "#{if !tags && expectations
76
+ "with following expectations: #{::RSpec::Support::ObjectFormatter.format(expectations)} "
77
+ end}" \
64
78
  "but #{actual_changes_message}"
65
79
  end
66
80
 
@@ -5,7 +5,7 @@ module Yabeda
5
5
  # calculate averages, percentiles, and so on.
6
6
  class Summary < Metric
7
7
  # rubocop: disable Metrics/MethodLength
8
- def observe(tags, value = nil)
8
+ def observe(tags = {}, value = nil)
9
9
  if value.nil? ^ block_given?
10
10
  raise ArgumentError, "You must provide either numeric value or block for Yabeda::Summary#observe!"
11
11
  end
@@ -18,7 +18,7 @@ module Yabeda
18
18
 
19
19
  all_tags = ::Yabeda::Tags.build(tags, group)
20
20
  values[all_tags] = value
21
- ::Yabeda.adapters.each do |_, adapter|
21
+ adapters.each_value do |adapter|
22
22
  adapter.perform_summary_observe!(self, all_tags, value)
23
23
  end
24
24
  value
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yabeda
4
- VERSION = "0.12.0"
4
+ VERSION = "0.13.1"
5
5
  end
data/lib/yabeda.rb CHANGED
@@ -8,12 +8,13 @@ require "yabeda/config"
8
8
  require "yabeda/dsl"
9
9
  require "yabeda/tags"
10
10
  require "yabeda/errors"
11
- require "yabeda/railtie" if defined?(Rails)
12
11
 
13
12
  # Extendable framework for collecting and exporting metrics from Ruby apps
14
13
  module Yabeda
15
14
  include DSL
16
15
 
16
+ EMPTY_TAGS = {}.freeze
17
+
17
18
  class << self
18
19
  extend Forwardable
19
20
 
@@ -29,7 +30,7 @@ module Yabeda
29
30
  end
30
31
  end
31
32
 
32
- # @return [Hash<String, Yabeda::BaseAdapter>] All loaded adapters
33
+ # @return [Hash<Symbol, Yabeda::BaseAdapter>] All loaded adapters
33
34
  def adapters
34
35
  @adapters ||= Concurrent::Hash.new
35
36
  end
@@ -68,7 +69,9 @@ module Yabeda
68
69
  def register_adapter(name, instance)
69
70
  adapters[name] = instance
70
71
  # NOTE: Pretty sure there is race condition
71
- metrics.each do |_, metric|
72
+ metrics.each_value do |metric|
73
+ next unless metric.adapters.key?(name)
74
+
72
75
  instance.register!(metric)
73
76
  end
74
77
  end
@@ -100,8 +103,8 @@ module Yabeda
100
103
 
101
104
  # Register metrics in adapters after evaluating all configuration blocks
102
105
  # to ensure that all global settings (like default tags) will be applied.
103
- adapters.each_value do |adapter|
104
- metrics.each_value do |metric|
106
+ metrics.each_value do |metric|
107
+ metric.adapters.each_value do |adapter|
105
108
  adapter.register!(metric)
106
109
  end
107
110
  end
@@ -129,7 +132,6 @@ module Yabeda
129
132
 
130
133
  true
131
134
  end
132
- # rubocop: enable Metrics/MethodLength
133
135
 
134
136
  # Forget all the configuration.
135
137
  # For testing purposes as it doesn't rollback changes in adapters.
@@ -143,8 +145,12 @@ module Yabeda
143
145
  @metrics = nil
144
146
  collectors.clear
145
147
  configurators.clear
148
+ @config = Config.new
146
149
  instance_variable_set(:@configured_by, nil)
147
150
  instance_variable_set(:@debug_was_enabled_by, nil)
148
151
  end
152
+ # rubocop: enable Metrics/MethodLength
149
153
  end
150
154
  end
155
+
156
+ require "yabeda/railtie" if defined?(Rails)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yabeda
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Novikov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-28 00:00:00.000000000 Z
11
+ date: 2024-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anyway_config
@@ -68,8 +68,8 @@ executables: []
68
68
  extensions: []
69
69
  extra_rdoc_files: []
70
70
  files:
71
- - ".github/workflows/build-release.yml"
72
71
  - ".github/workflows/lint.yml"
72
+ - ".github/workflows/release.yml"
73
73
  - ".github/workflows/test.yml"
74
74
  - ".gitignore"
75
75
  - ".rspec"
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubygems_version: 3.1.6
132
+ rubygems_version: 3.5.16
133
133
  signing_key:
134
134
  specification_version: 4
135
135
  summary: Extensible framework for collecting metric for your Ruby application