yabeda 0.12.0 → 0.13.0

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: 8d2581bd55cdd41639a1c6f01d456c5e09e1ad7636253e92720d35242fde6fab
4
+ data.tar.gz: 02f88db1ee3d892e387129c8775ab1b31dbafe2ec97ea8a67c34ff6abf67dc1d
5
5
  SHA512:
6
- metadata.gz: 3a75f40eb17ab3d35feef5449bfe07e369487c61609eca03da0f7eec17d1d7e32ad5b51b4a5079d3247b994df8e9730d681d501b7fa7b71d17a9b7d1ede06993
7
- data.tar.gz: bcf1c14781cb17a3621ed4f03a3431b2399ee9e4ec80bfa044455903d2d4e9ab25563b94a01207435b05e1898e681dda91c6f36d437e574c33060f0f7c1014ef
6
+ metadata.gz: 57c4e7b716b077e37b7191cf4bfcc4f7090943eab1922439d485e02bc925408f21db656e239545bd7836413fc219282e71553b4cbd88e02b80f272dc236bacfb
7
+ data.tar.gz: 63707a843e531b01e685e17f7772dcf718ab730d401396d936ade23a5f8e0a7699ddbf7799caca797afcb4ef0fb5c07cacfd362ff98f46949846f166de636b97
@@ -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,47 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.13.0 - 2024-10-02
11
+
12
+ ### Added
13
+
14
+ - Ability to limit some metrics to specific adapters. [#37](https://github.com/yabeda-rb/yabeda/pull/37) by [@Keallar] and [@Envek]
15
+
16
+ ```ruby
17
+ Yabeda.configure do
18
+ group :cloud do
19
+ adapter :newrelic, :datadog
20
+
21
+ counter :foo
22
+ end
23
+
24
+ counter :bar, adapter: :prometheus
25
+ end
26
+ ```
27
+
28
+ - Multiple expectations in RSpec matchers. [@Envek]
29
+
30
+ ```ruby
31
+ expect { whatever }.to increment_yabeda_counter(:my_counter).with(
32
+ { tag: "foo" } => 1,
33
+ { tag: "bar" } => (be >= 42),
34
+ )
35
+ ```
36
+
37
+ ### Changed
38
+
39
+ - 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]
40
+
41
+ ```ruby
42
+ Yabeda.foo.increment
43
+ # same as
44
+ Yabeda.foo.increment({}, by: 1)
45
+ ```
46
+
47
+ ### Fixed
48
+
49
+ - Railtie loading to prevent calling methods that have not yet been defined. [#38](https://github.com/yabeda-rb/yabeda/pull/38) by [@bibendi].
50
+
10
51
  ## 0.12.0 - 2023-07-28
11
52
 
12
53
  ### Added
@@ -37,7 +78,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
37
78
 
38
79
  ### Changed
39
80
 
40
- - Adapters now should use method `Yabeda.collect!` instead of manual calling of every collector block.
81
+ - Adapters now should use method `Yabeda.collect!` instead of manual calling of every collector block.
41
82
 
42
83
  ## 0.9.0 - 2021-05-07
43
84
 
@@ -140,3 +181,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
140
181
  [@dsalahutdinov]: https://github.com/dsalahutdinov "Dmitry Salahutdinov"
141
182
  [@asusikov]: https://github.com/asusikov "Alexander Susikov"
142
183
  [@liaden]: https://github.com/liaden "Joel Johnson"
184
+ [@bibendi]: https://github.com/bibendi "Misha Merkushin"
185
+ [@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,10 @@
3
3
  module Yabeda
4
4
  # Growing-only counter
5
5
  class Counter < Metric
6
- def increment(tags, by: 1)
6
+ def increment(tags = {}, by: 1)
7
7
  all_tags = ::Yabeda::Tags.build(tags, group)
8
8
  values[all_tags] += by
9
- ::Yabeda.adapters.each do |_, adapter|
9
+ adapters.each_value do |adapter|
10
10
  adapter.perform_counter_increment!(self, all_tags, by)
11
11
  end
12
12
  values[all_tags]
@@ -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,17 @@ 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
+ def increment(tags = {}, by: 1)
16
16
  set(tags, get(tags).to_i + by)
17
17
  end
18
18
 
19
- def decrement(tags, by: 1)
19
+ def decrement(tags = {}, by: 1)
20
20
  set(tags, get(tags).to_i - by)
21
21
  end
22
22
  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.0"
5
5
  end
data/lib/yabeda.rb CHANGED
@@ -8,7 +8,6 @@ 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
@@ -29,7 +28,7 @@ module Yabeda
29
28
  end
30
29
  end
31
30
 
32
- # @return [Hash<String, Yabeda::BaseAdapter>] All loaded adapters
31
+ # @return [Hash<Symbol, Yabeda::BaseAdapter>] All loaded adapters
33
32
  def adapters
34
33
  @adapters ||= Concurrent::Hash.new
35
34
  end
@@ -68,7 +67,9 @@ module Yabeda
68
67
  def register_adapter(name, instance)
69
68
  adapters[name] = instance
70
69
  # NOTE: Pretty sure there is race condition
71
- metrics.each do |_, metric|
70
+ metrics.each_value do |metric|
71
+ next unless metric.adapters.key?(name)
72
+
72
73
  instance.register!(metric)
73
74
  end
74
75
  end
@@ -100,8 +101,8 @@ module Yabeda
100
101
 
101
102
  # Register metrics in adapters after evaluating all configuration blocks
102
103
  # to ensure that all global settings (like default tags) will be applied.
103
- adapters.each_value do |adapter|
104
- metrics.each_value do |metric|
104
+ metrics.each_value do |metric|
105
+ metric.adapters.each_value do |adapter|
105
106
  adapter.register!(metric)
106
107
  end
107
108
  end
@@ -129,7 +130,6 @@ module Yabeda
129
130
 
130
131
  true
131
132
  end
132
- # rubocop: enable Metrics/MethodLength
133
133
 
134
134
  # Forget all the configuration.
135
135
  # For testing purposes as it doesn't rollback changes in adapters.
@@ -143,8 +143,12 @@ module Yabeda
143
143
  @metrics = nil
144
144
  collectors.clear
145
145
  configurators.clear
146
+ @config = Config.new
146
147
  instance_variable_set(:@configured_by, nil)
147
148
  instance_variable_set(:@debug_was_enabled_by, nil)
148
149
  end
150
+ # rubocop: enable Metrics/MethodLength
149
151
  end
150
152
  end
153
+
154
+ 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.0
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-02 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