yabeda 0.10.1 → 0.12.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: f5aed70338bdfe24c4505a3cbe51cb4853c7c0e0c1842ef1cee93480ef517c02
4
- data.tar.gz: 95dc76b40980f227abe24c7bf70339d47d81eaa91e6bdfa354c8b6e6b93c8170
3
+ metadata.gz: 52ac87de3f6f2edd66e88d967b2e2e7231a87a27189e9d0a129cbafc6041813f
4
+ data.tar.gz: 2096a9ea457d7aa10efdcb54bc29a6a6e520e15974bba1efafc53df2921b5116
5
5
  SHA512:
6
- metadata.gz: 77e865d851f2e9f2a168426983df6693c9e655e0349107aafd25594eea12f9f83c14da519abbdfc5826f8c0e208d68b2a0616735771ba5ec82bde7acb3e71085
7
- data.tar.gz: aa7596a3de80b063845d552136c882cbdf46e772a1f53998058ba894dc8428290fdbae391ef241f2264dd5e76bd41be8cfab0b8afad654152b50d3b47fd0ab3b
6
+ metadata.gz: 3a75f40eb17ab3d35feef5449bfe07e369487c61609eca03da0f7eec17d1d7e32ad5b51b4a5079d3247b994df8e9730d681d501b7fa7b71d17a9b7d1ede06993
7
+ data.tar.gz: bcf1c14781cb17a3621ed4f03a3431b2399ee9e4ec80bfa044455903d2d4e9ab25563b94a01207435b05e1898e681dda91c6f36d437e574c33060f0f7c1014ef
@@ -0,0 +1,32 @@
1
+ name: Lint Ruby
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ paths:
8
+ - "gemfiles/*"
9
+ - "Gemfile"
10
+ - "**/*.rb"
11
+ - "**/*.gemspec"
12
+ - ".github/workflows/lint.yml"
13
+ pull_request:
14
+ paths:
15
+ - "gemfiles/*"
16
+ - "Gemfile"
17
+ - "**/*.rb"
18
+ - "**/*.gemspec"
19
+ - ".github/workflows/lint.yml"
20
+
21
+ jobs:
22
+ rubocop:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v3
26
+ - uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: 3.2
29
+ bundler-cache: true
30
+ - name: Lint Ruby code with RuboCop
31
+ run: |
32
+ bundle exec rubocop
@@ -16,12 +16,10 @@ jobs:
16
16
  fail-fast: false
17
17
  matrix:
18
18
  include:
19
+ - ruby: "3.2"
20
+ - ruby: "3.1"
19
21
  - ruby: "3.0"
20
22
  - ruby: "2.7"
21
- - ruby: "2.6"
22
- - ruby: "2.5"
23
- - ruby: "2.4"
24
- - ruby: "2.3"
25
23
  container:
26
24
  image: ruby:${{ matrix.ruby }}
27
25
  env:
@@ -35,14 +33,10 @@ jobs:
35
33
  restore-keys: |
36
34
  bundle-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles('**/Gemfile') }}
37
35
  bundle-${{ matrix.ruby }}-
38
- - name: Upgrade Bundler to 2.0 (for older Rubies)
39
- run: gem install bundler -v '~> 2.0'
40
36
  - name: Bundle install
41
37
  run: |
42
38
  bundle config path vendor/bundle
43
39
  bundle install
44
40
  bundle update
45
- - name: Run Rubocop
46
- run: bundle exec rubocop
47
41
  - name: Run RSpec
48
42
  run: bundle exec rspec
data/.rubocop.yml CHANGED
@@ -10,6 +10,9 @@ Metrics/BlockLength:
10
10
  - "Gemfile"
11
11
  - "spec/**/*"
12
12
 
13
+ Metrics/AbcSize:
14
+ Max: 25
15
+
13
16
  Layout/LineLength:
14
17
  Max: 120
15
18
 
@@ -23,6 +26,15 @@ RSpec/LetSetup:
23
26
  RSpec/MultipleExpectations:
24
27
  Enabled: false
25
28
 
29
+ RSpec/DescribeClass:
30
+ Enabled: false
31
+
32
+ RSpec/NestedGroups:
33
+ Max: 4
34
+
35
+ RSpec/MultipleMemoizedHelpers:
36
+ Enabled: false
37
+
26
38
  Bundler/OrderedGems:
27
39
  Enabled: false
28
40
 
@@ -52,3 +64,6 @@ Style/HashTransformKeys:
52
64
 
53
65
  Style/HashTransformValues:
54
66
  Enabled: true
67
+
68
+ Style/Documentation:
69
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.12.0 - 2023-07-28
11
+
12
+ ### Added
13
+
14
+ - Summary metric type (mostly for Prometheus adapter).
15
+
16
+ ## 0.11.0 - 2021-09-25
17
+
18
+ ### Added
19
+
20
+ - RSpec matchers `increment_yabeda_counter`, `update_yabeda_gauge`, and `measure_yabeda_histogram` for convenient testing. [#25](https://github.com/yabeda-rb/yabeda/pull/25) by [@Envek][]
21
+ - Automatic setup of RSpec on `require "yabeda/rspec"`
22
+ - Special test adapter that collects metric changes in memory
23
+
10
24
  ## 0.10.1 - 2021-08-30
11
25
 
12
26
  ### Fixed
data/Gemfile CHANGED
@@ -8,9 +8,14 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
8
8
  gemspec
9
9
 
10
10
  group :development, :test do
11
+ gem "rake", "~> 12.0"
12
+ gem "rspec", "~> 3.0"
13
+ gem "yard"
14
+ gem "yard-dry-initializer"
15
+
11
16
  gem "pry"
12
17
  gem "pry-byebug", platform: :mri
13
18
 
14
- gem "rubocop", "~> 0.80.0"
19
+ gem "rubocop", "~> 1.0"
15
20
  gem "rubocop-rspec"
16
21
  end
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # ![Yabeda](./yabeda-logo.png)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/yabeda.svg)](https://rubygems.org/gems/yabeda)
4
-
5
- **This software is Work in Progress: features will appear and disappear, API will be changed, your feedback is always welcome!**
4
+ [![Tests status](https://github.com/yabeda-rb/yabeda/actions/workflows/test.yml/badge.svg)](https://github.com/yabeda-rb/yabeda/actions/workflows/test.yml)
6
5
 
7
6
  Extendable solution for easy setup of monitoring in your Ruby apps.
8
7
 
@@ -18,6 +17,11 @@ Most of the time you don't need to add this gem to your Gemfile directly (unless
18
17
 
19
18
  ```ruby
20
19
  gem 'yabeda'
20
+
21
+ # Add some plugins to quickly start collecting some essential metrics:
22
+ # gem 'yabeda-rails'
23
+ # gem 'yabeda-sidekiq'
24
+
21
25
  # Then add monitoring system adapter, e.g.:
22
26
  # gem 'yabeda-prometheus'
23
27
  ```
@@ -39,6 +43,7 @@ And then execute:
39
43
  comment "How long whistles are being active"
40
44
  unit :seconds
41
45
  end
46
+ summary :bells_ringing_duration, unit: :seconds, comment: "How long bells are ringing"
42
47
  end
43
48
  end
44
49
  ```
@@ -124,6 +129,7 @@ And then execute:
124
129
  These are developed and maintained by other awesome folks:
125
130
 
126
131
  - [Statsd](https://github.com/asusikov/yabeda-statsd)
132
+ - [AWS CloudWatch](https://github.com/retsef/yabeda-cloudwatch)
127
133
  - _…and more! You can write your own adapter and open a pull request to add it into this list._
128
134
 
129
135
  ## Available plugins to collect metrics
@@ -131,6 +137,7 @@ These are developed and maintained by other awesome folks:
131
137
  ### Maintained by Yabeda
132
138
 
133
139
  - [yabeda-rails] — basic request metrics for [Ruby on Rails](https://rubyonrails.org/) applications.
140
+ - [yabeda-activerecord] — query performance and connection pool stats for apps using ActiveRecord to query databases.
134
141
  - [yabeda-sidekiq] — comprehensive set of metrics for monitoring [Sidekiq](https://sidekiq.org/) jobs execution and queues.
135
142
  - [yabeda-faktory] — metrics for monitoring jobs execution by Ruby workers of [Faktory](https://contribsys.com/faktory/).
136
143
  - [yabeda-graphql] — metrics to query and field-level monitoring for apps using [GraphQL-Ruby](https://graphql-ruby.org/).
@@ -146,6 +153,9 @@ These are developed and maintained by other awesome folks:
146
153
  - [yabeda-grape](https://github.com/efigence/yabeda-grape) — metrics for [Grape](https://github.com/ruby-grape/grape) framework.
147
154
  - [yabeda-gruf](https://github.com/Placewise/yabeda-gruf) — metrics for [gRPC Ruby Framework](https://github.com/bigcommerce/gruf)
148
155
  - [yabeda-gc](https://github.com/ianks/yabeda-gc) — metrics for Ruby garbage collection.
156
+ - [yabeda-activejob](https://github.com/Fullscript/yabeda-activejob) — backend-agnostic metrics for background jobs.
157
+ - [yabeda-shoryuken](https://github.com/retsef/yabeda-shoryuken) — metrics for [Shoryuken](https://github.com/ruby-shoryuken/shoryuken) jobs execution message queues.
158
+ - [yabeda-rack-ratelimit](https://github.com/basecamp/yabeda-rack-ratelimit) — metrics for [Rack::Ratelimit](https://github.com/jeremy/rack-ratelimit)
149
159
  - _…and more! You can write your own adapter and open a pull request to add it into this list._
150
160
 
151
161
  ## Configuration
@@ -162,6 +172,44 @@ Config key | Type | Default | Description |
162
172
 
163
173
  These are only enabled in debug mode. To enable it either set `debug` config key to `true` (e.g. by specifying `YABEDA_DEBUG=true` in your environment variables or executing `Yabeda.debug!` in your code).
164
174
 
175
+ ## Testing
176
+
177
+ ### RSpec
178
+
179
+ Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
180
+
181
+ ```ruby
182
+ require "yabeda/rspec"
183
+ ```
184
+
185
+ Now you can use `increment_yabeda_counter`, `update_yabeda_gauge`, `measure_yabeda_histogram`, and `observe_yabeda_summary` matchers:
186
+
187
+ ```ruby
188
+ it "increments counters" do
189
+ expect { subject }.to increment_yabeda_counter(Yabeda.myapp.foo_count).by(3)
190
+ end
191
+ ```
192
+
193
+ You can scope metrics by used tags with `with_tags`:
194
+
195
+ ```ruby
196
+ it "updates gauges" do
197
+ expect { subject }.to \
198
+ update_yabeda_gauge("some_gauge_name").
199
+ with_tags(method: "command", command: "subscribe")
200
+ end
201
+ ```
202
+
203
+ Note that tags you specified doesn't need to be exact, but can be a subset of tags used on metric update. In this example updates with following sets of tags `{ method: "command", command: "subscribe", status: "SUCCESS" }` and `{ method: "command", command: "subscribe", status: "FAILURE" }` will make test example to pass.
204
+
205
+ And check for values with `by` for counters, `to` for gauges, and `with` for histograms and summaries (and you [can use other matchers here](https://relishapp.com/rspec/rspec-expectations/v/3-10/docs/composing-matchers)):
206
+
207
+ ```ruby
208
+ expect { subject }.to \
209
+ measure_yabeda_histogram(Yabeda.something.anything_runtime).
210
+ with(be_between(0.005, 0.05))
211
+ ```
212
+
165
213
  ## Roadmap (aka TODO or Help wanted)
166
214
 
167
215
  - Ability to change metric settings for individual adapters
@@ -229,6 +277,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/yabeda
229
277
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
230
278
 
231
279
  [yabeda-rails]: https://github.com/yabeda-rb/yabeda-rails/ "Yabeda plugin for collecting and exporting basic metrics for Rails applications"
280
+ [yabeda-activerecord]: https://github.com/yabeda-rb/yabeda-activerecord/ "Yabeda plugin to collect query performance metrics and connection pool statistics"
232
281
  [yabeda-sidekiq]: https://github.com/yabeda-rb/yabeda-sidekiq/ "Yabeda plugin for complete monitoring of Sidekiq metrics"
233
282
  [yabeda-faktory]: https://github.com/yabeda-rb/yabeda-faktory/ "Yabeda plugin for complete monitoring of Faktory Ruby Workers"
234
283
  [yabeda-graphql]: https://github.com/yabeda-rb/yabeda-graphql/ "Measure and understand how good your GraphQL-Ruby application works"
@@ -8,6 +8,7 @@ module Yabeda
8
8
  when Counter then register_counter!(metric)
9
9
  when Gauge then register_gauge!(metric)
10
10
  when Histogram then register_histogram!(metric)
11
+ when Summary then register_summary!(metric)
11
12
  else raise "#{metric.class} is unknown metric type"
12
13
  end
13
14
  end
@@ -36,6 +37,14 @@ module Yabeda
36
37
  raise NotImplementedError, "#{self.class} doesn't support measuring histograms"
37
38
  end
38
39
 
40
+ def register_summary!(_metric)
41
+ raise NotImplementedError, "#{self.class} doesn't support summaries as metric type!"
42
+ end
43
+
44
+ def perform_summary_observe!(_metric, _tags, _value)
45
+ raise NotImplementedError, "#{self.class} doesn't support observing summaries"
46
+ end
47
+
39
48
  # Hook to enable debug mode in adapters when it is enabled in Yabeda itself
40
49
  def debug!; end
41
50
  end
@@ -4,6 +4,7 @@ require "yabeda/metric"
4
4
  require "yabeda/counter"
5
5
  require "yabeda/gauge"
6
6
  require "yabeda/histogram"
7
+ require "yabeda/summary"
7
8
  require "yabeda/group"
8
9
  require "yabeda/global_group"
9
10
  require "yabeda/dsl/metric_builder"
@@ -11,7 +12,6 @@ require "yabeda/dsl/metric_builder"
11
12
  module Yabeda
12
13
  # DSL for ease of work with Yabeda
13
14
  module DSL
14
- # rubocop: disable Style/Documentation
15
15
  module ClassMethods
16
16
  # Block for grouping and simplifying configuration of related metrics
17
17
  def configure(&block)
@@ -56,6 +56,12 @@ module Yabeda
56
56
  register_metric(metric)
57
57
  end
58
58
 
59
+ # Register a summary
60
+ def summary(*args, **kwargs, &block)
61
+ metric = MetricBuilder.new(Summary).build(args, kwargs, @group, &block)
62
+ register_metric(metric)
63
+ end
64
+
59
65
  # Add default tag for all metric
60
66
  #
61
67
  # @param name [Symbol] Name of default tag
@@ -110,6 +116,5 @@ module Yabeda
110
116
  group.register_metric(metric)
111
117
  end
112
118
  end
113
- # rubocop: enable Style/Documentation
114
119
  end
115
120
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
- require_relative "./group"
4
+ require_relative "group"
5
5
 
6
6
  module Yabeda
7
7
  # Represents implicit global group
data/lib/yabeda/metric.rb CHANGED
@@ -29,5 +29,9 @@ module Yabeda
29
29
  def tags
30
30
  (Yabeda.groups[group].default_tags.keys + Array(super)).uniq
31
31
  end
32
+
33
+ def inspect
34
+ "#<#{self.class.name}: #{[@group, @name].compact.join('.')}>"
35
+ end
32
36
  end
33
37
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yabeda
4
+ module RSpec
5
+ # Notes:
6
+ # +expected+ is always a metric instance
7
+ # +actual+ is always a block of code
8
+ # Example:
9
+ # expect { anything }.to do_whatever_with_yabeda_metric(Yabeda.something)
10
+ class BaseMatcher < ::RSpec::Matchers::BuiltIn::BaseMatcher
11
+ attr_reader :tags, :metric
12
+
13
+ # Specify a scope of labels (tags). Subset of tags can be specified.
14
+ def with_tags(tags)
15
+ @tags = tags
16
+ self
17
+ end
18
+
19
+ def initialize(expected)
20
+ super
21
+ @expected = @metric = resolve_metric(expected)
22
+ rescue KeyError
23
+ raise ArgumentError, <<~MSG
24
+ 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
26
+ MSG
27
+ end
28
+
29
+ # RSpec doesn't define this method, but it is more convenient to rely on +match_when_negated+ method presence
30
+ def does_not_match?(actual)
31
+ @actual = actual
32
+ if respond_to?(:match_when_negated)
33
+ match_when_negated(expected, actual)
34
+ else
35
+ !match(expected, actual)
36
+ end
37
+ end
38
+
39
+ def supports_block_expectations?
40
+ true
41
+ end
42
+
43
+ # Pretty print metric name (expected is expected to always be a Yabeda metric instance)
44
+ def expected_formatted
45
+ "Yabeda.#{[metric.group, metric.name].compact.join('.')}"
46
+ end
47
+
48
+ private
49
+
50
+ def resolve_metric(instance_or_name)
51
+ return instance_or_name if instance_or_name.is_a? Yabeda::Metric
52
+
53
+ Yabeda.metrics.fetch(instance_or_name.to_s)
54
+ end
55
+
56
+ # Filter metric changes by tags.
57
+ # If tags specified, treat them as subset of real tags (to avoid bothering with default tags in tests)
58
+ def filter_matching_changes(changes)
59
+ return changes if tags.nil?
60
+
61
+ changes.select { |t, _v| t >= tags }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_matcher"
4
+
5
+ module Yabeda
6
+ module RSpec
7
+ # Checks whether Yabeda counter was incremented during test run or not
8
+ # @param metric [Yabeda::Counter,String,Symbol] metric instance or name
9
+ # @return [Yabeda::RSpec::IncrementYabedaCounter]
10
+ def increment_yabeda_counter(metric)
11
+ IncrementYabedaCounter.new(metric)
12
+ end
13
+
14
+ # Custom matcher class with implementation for +increment_yabeda_counter+
15
+ class IncrementYabedaCounter < BaseMatcher
16
+ def by(increment)
17
+ @expected_increment = increment
18
+ self
19
+ end
20
+
21
+ attr_reader :expected_increment
22
+
23
+ def initialize(*)
24
+ super
25
+ return if metric.is_a? Yabeda::Counter
26
+
27
+ raise ArgumentError, "Pass counter instance/name to `increment_yabeda_counter`. Got #{metric.inspect} instead"
28
+ end
29
+
30
+ def match(metric, block)
31
+ block.call
32
+
33
+ increments = filter_matching_changes(Yabeda::TestAdapter.instance.counters.fetch(metric))
34
+
35
+ increments.values.any? do |actual_increment|
36
+ expected_increment.nil? || values_match?(expected_increment, actual_increment)
37
+ end
38
+ end
39
+
40
+ def match_when_negated(metric, block)
41
+ unless expected_increment.nil?
42
+ raise NotImplementedError, <<~MSG
43
+ `expect(Yabeda.metric_name).not_to increment_yabeda_counter` doesn't support specifying increment
44
+ with `.by` as it can lead to false positives.
45
+ MSG
46
+ end
47
+
48
+ block.call
49
+
50
+ increments = filter_matching_changes(Yabeda::TestAdapter.instance.counters.fetch(metric))
51
+
52
+ increments.none?
53
+ end
54
+
55
+ def failure_message
56
+ "expected #{expected_formatted} " \
57
+ "to be incremented #{"by #{description_of(expected_increment)} " unless expected_increment.nil?}" \
58
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
59
+ "but #{actual_increments_message}"
60
+ end
61
+
62
+ def failure_message_when_negated
63
+ "expected #{expected_formatted} " \
64
+ "not to be incremented " \
65
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
66
+ "but #{actual_increments_message}"
67
+ end
68
+
69
+ def actual_increments_message
70
+ counter_increments = Yabeda::TestAdapter.instance.counters.fetch(metric)
71
+ if counter_increments.empty?
72
+ "no increments of this counter have been made"
73
+ elsif tags && counter_increments.key?(tags)
74
+ "has been incremented by #{counter_increments.fetch(tags)}"
75
+ else
76
+ "following increments have been made: #{::RSpec::Support::ObjectFormatter.format(counter_increments)}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_matcher"
4
+
5
+ module Yabeda
6
+ module RSpec
7
+ # Checks whether Yabeda histogram was measured during test run or not
8
+ # @param metric [Yabeda::Histogram,String,Symbol] metric instance or name
9
+ # @return [Yabeda::RSpec::MeasureYabedaHistogram]
10
+ def measure_yabeda_histogram(metric)
11
+ MeasureYabedaHistogram.new(metric)
12
+ end
13
+
14
+ # Custom matcher class with implementation for +measure_yabeda_histogram+
15
+ class MeasureYabedaHistogram < BaseMatcher
16
+ def with(value)
17
+ @expected_value = value
18
+ self
19
+ end
20
+
21
+ attr_reader :expected_value
22
+
23
+ def initialize(*)
24
+ super
25
+ return if metric.is_a? Yabeda::Histogram
26
+
27
+ raise ArgumentError, "Pass histogram instance/name to `measure_yabeda_histogram`. Got #{metric.inspect} instead"
28
+ end
29
+
30
+ def match(metric, block)
31
+ block.call
32
+
33
+ measures = filter_matching_changes(Yabeda::TestAdapter.instance.histograms.fetch(metric))
34
+
35
+ measures.values.any? { |measure| expected_value.nil? || values_match?(expected_value, measure) }
36
+ end
37
+
38
+ def match_when_negated(metric, block)
39
+ unless expected_value.nil?
40
+ raise NotImplementedError, <<~MSG
41
+ `expect {}.not_to measure_yabeda_histogram` doesn't support specifying values with `.with`
42
+ as it can lead to false positives.
43
+ MSG
44
+ end
45
+
46
+ block.call
47
+
48
+ measures = filter_matching_changes(Yabeda::TestAdapter.instance.histograms.fetch(metric))
49
+
50
+ measures.none?
51
+ end
52
+
53
+ def failure_message
54
+ "expected #{expected_formatted} " \
55
+ "to be changed #{"to #{expected} " unless expected_value.nil?}" \
56
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
57
+ "but #{actual_changes_message}"
58
+ end
59
+
60
+ def failure_message_when_negated
61
+ "expected #{expected_formatted} " \
62
+ "not to be incremented " \
63
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
64
+ "but #{actual_changes_message}"
65
+ end
66
+
67
+ def actual_changes_message
68
+ measures = Yabeda::TestAdapter.instance.histograms.fetch(metric)
69
+ if measures.empty?
70
+ "no changes of this gauge have been made"
71
+ elsif tags && measures.key?(tags)
72
+ "has been changed to #{measures.fetch(tags)} with tags #{::RSpec::Support::ObjectFormatter.format(tags)}"
73
+ else
74
+ "following changes have been made: #{::RSpec::Support::ObjectFormatter.format(measures)}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_matcher"
4
+
5
+ module Yabeda
6
+ module RSpec
7
+ # Checks whether Yabeda summary was observed during test run or not
8
+ # @param metric [Yabeda::Summary,String,Symbol] metric instance or name
9
+ # @return [Yabeda::RSpec::ObserveYabedaSummary]
10
+ def observe_yabeda_summary(metric)
11
+ ObserveYabedaSummary.new(metric)
12
+ end
13
+
14
+ # Custom matcher class with implementation for +observe_yabeda_summary+
15
+ class ObserveYabedaSummary < BaseMatcher
16
+ def with(value)
17
+ @expected_value = value
18
+ self
19
+ end
20
+
21
+ attr_reader :expected_value
22
+
23
+ def initialize(*)
24
+ super
25
+ return if metric.is_a? Yabeda::Summary
26
+
27
+ raise ArgumentError, "Pass summary instance/name to `observe_yabeda_summary`. Got #{metric.inspect} instead"
28
+ end
29
+
30
+ def match(metric, block)
31
+ block.call
32
+
33
+ observations = filter_matching_changes(Yabeda::TestAdapter.instance.summaries.fetch(metric))
34
+
35
+ observations.values.any? { |observation| expected_value.nil? || values_match?(expected_value, observation) }
36
+ end
37
+
38
+ def match_when_negated(metric, block)
39
+ unless expected_value.nil?
40
+ raise NotImplementedError, <<~MSG
41
+ `expect {}.not_to observe_yabeda_summary` doesn't support specifying values with `.with`
42
+ as it can lead to false positives.
43
+ MSG
44
+ end
45
+
46
+ block.call
47
+
48
+ observations = filter_matching_changes(Yabeda::TestAdapter.instance.summaries.fetch(metric))
49
+
50
+ observations.none?
51
+ end
52
+
53
+ def failure_message
54
+ "expected #{expected_formatted} " \
55
+ "to be observed #{"with #{expected} " unless expected_value.nil?}" \
56
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
57
+ "but #{actual_changes_message}"
58
+ end
59
+
60
+ def failure_message_when_negated
61
+ "expected #{expected_formatted} " \
62
+ "not to be observed " \
63
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
64
+ "but #{actual_changes_message}"
65
+ end
66
+
67
+ def actual_changes_message
68
+ observations = Yabeda::TestAdapter.instance.summaries.fetch(metric)
69
+ if observations.empty?
70
+ "no observations of this summary have been made"
71
+ elsif tags && observations.key?(tags)
72
+ formatted_tags = ::RSpec::Support::ObjectFormatter.format(tags)
73
+ "has been observed with #{observations.fetch(tags)} with tags #{formatted_tags}"
74
+ else
75
+ "following observations have been made: #{::RSpec::Support::ObjectFormatter.format(observations)}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_matcher"
4
+
5
+ module Yabeda
6
+ module RSpec
7
+ # Checks whether Yabeda gauge was set to some value during test run or not
8
+ # @param metric [Yabeda::Gauge,String,Symbol] metric instance or name
9
+ # @return [Yabeda::RSpec::UpdateYabedaGauge]
10
+ def update_yabeda_gauge(metric)
11
+ UpdateYabedaGauge.new(metric)
12
+ end
13
+
14
+ # Custom matcher class with implementation for +update_yabeda_gauge+
15
+ class UpdateYabedaGauge < BaseMatcher
16
+ def with(value)
17
+ @expected_value = value
18
+ self
19
+ end
20
+
21
+ attr_reader :expected_value
22
+
23
+ def initialize(*)
24
+ super
25
+ return if metric.is_a? Yabeda::Gauge
26
+
27
+ raise ArgumentError, "Pass gauge instance/name to `update_yabeda_gauge`. Got #{metric.inspect} instead"
28
+ end
29
+
30
+ def match(metric, block)
31
+ block.call
32
+
33
+ updates = filter_matching_changes(Yabeda::TestAdapter.instance.gauges.fetch(metric))
34
+
35
+ updates.values.any? { |update| expected_value.nil? || values_match?(expected_value, update) }
36
+ end
37
+
38
+ def match_when_negated(metric, block)
39
+ unless expected_value.nil?
40
+ raise NotImplementedError, <<~MSG
41
+ `expect(Yabeda.metric_name).not_to update_yabeda_gauge` doesn't support specifying values with `.with`
42
+ as it can lead to false positives.
43
+ MSG
44
+ end
45
+
46
+ block.call
47
+
48
+ updates = filter_matching_changes(Yabeda::TestAdapter.instance.gauges.fetch(metric))
49
+
50
+ updates.none?
51
+ end
52
+
53
+ def failure_message
54
+ "expected #{expected_formatted} " \
55
+ "to be changed #{"to #{expected_value} " unless expected_value.nil?}" \
56
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
57
+ "but #{actual_changes_message}"
58
+ end
59
+
60
+ def failure_message_when_negated
61
+ "expected #{expected_formatted} " \
62
+ "not to be changed " \
63
+ "#{"with tags #{::RSpec::Support::ObjectFormatter.format(tags)} " if tags}" \
64
+ "but #{actual_changes_message}"
65
+ end
66
+
67
+ def actual_changes_message
68
+ updates = Yabeda::TestAdapter.instance.gauges.fetch(metric)
69
+ if updates.empty?
70
+ "no changes of this gauge have been made"
71
+ elsif tags && updates.key?(tags)
72
+ "has been changed to #{updates.fetch(tags)} with tags #{::RSpec::Support::ObjectFormatter.format(tags)}"
73
+ else
74
+ "following changes have been made: #{::RSpec::Support::ObjectFormatter.format(updates)}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "testing"
4
+
5
+ module Yabeda
6
+ # RSpec integration for Yabeda: custom matchers, etc
7
+ module RSpec
8
+ end
9
+ end
10
+
11
+ require_relative "rspec/increment_yabeda_counter"
12
+ require_relative "rspec/update_yabeda_gauge"
13
+ require_relative "rspec/measure_yabeda_histogram"
14
+ require_relative "rspec/observe_yabeda_summary"
15
+
16
+ RSpec.configure do |config|
17
+ config.before(:suite) do
18
+ Yabeda.configure! unless Yabeda.already_configured?
19
+ end
20
+
21
+ config.after(:each) do
22
+ Yabeda::TestAdapter.instance.reset!
23
+ end
24
+
25
+ config.include(Yabeda::RSpec)
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yabeda
4
+ # Base class for complex metric for measuring time values that allow to
5
+ # calculate averages, percentiles, and so on.
6
+ class Summary < Metric
7
+ # rubocop: disable Metrics/MethodLength
8
+ def observe(tags, value = nil)
9
+ if value.nil? ^ block_given?
10
+ raise ArgumentError, "You must provide either numeric value or block for Yabeda::Summary#observe!"
11
+ end
12
+
13
+ if block_given?
14
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
15
+ yield
16
+ value = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - starting)
17
+ end
18
+
19
+ all_tags = ::Yabeda::Tags.build(tags, group)
20
+ values[all_tags] = value
21
+ ::Yabeda.adapters.each do |_, adapter|
22
+ adapter.perform_summary_observe!(self, all_tags, value)
23
+ end
24
+ value
25
+ end
26
+ # rubocop: enable Metrics/MethodLength
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ require_relative "base_adapter"
6
+
7
+ module Yabeda
8
+ # Fake monitoring system adapter that collects latest metric values for later inspection
9
+ class TestAdapter < BaseAdapter
10
+ include Singleton
11
+
12
+ attr_reader :counters, :gauges, :histograms, :summaries
13
+
14
+ # rubocop:disable Metrics/AbcSize
15
+ def initialize
16
+ super
17
+ @counters = Hash.new { |ch, ck| ch[ck] = Hash.new { |th, tk| th[tk] = 0 } }
18
+ @gauges = Hash.new { |gh, gk| gh[gk] = Hash.new { |th, tk| th[tk] = nil } }
19
+ @histograms = Hash.new { |hh, hk| hh[hk] = Hash.new { |th, tk| th[tk] = nil } }
20
+ @summaries = Hash.new { |sh, sk| sh[sk] = Hash.new { |th, tk| th[tk] = nil } }
21
+ end
22
+ # rubocop:enable Metrics/AbcSize
23
+
24
+ # Call this method after every test example to quickly get blank state for the next test example
25
+ def reset!
26
+ [@counters, @gauges, @histograms, @summaries].each do |collection|
27
+ collection.each_value(&:clear) # Reset tag-values hash to be empty
28
+ end
29
+ end
30
+
31
+ def register_counter!(metric)
32
+ @counters[metric]
33
+ end
34
+
35
+ def register_gauge!(metric)
36
+ @gauges[metric]
37
+ end
38
+
39
+ def register_histogram!(metric)
40
+ @histograms[metric]
41
+ end
42
+
43
+ def register_summary!(metric)
44
+ @summaries[metric]
45
+ end
46
+
47
+ def perform_counter_increment!(counter, tags, increment)
48
+ @counters[counter][tags] += increment
49
+ end
50
+
51
+ def perform_gauge_set!(gauge, tags, value)
52
+ @gauges[gauge][tags] = value
53
+ end
54
+
55
+ def perform_histogram_measure!(histogram, tags, value)
56
+ @histograms[histogram][tags] = value
57
+ end
58
+
59
+ def perform_summary_observe!(summary, tags, value)
60
+ @summaries[summary][tags] = value
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Include this file to get things prepared for testing
4
+
5
+ require_relative "test_adapter"
6
+
7
+ Yabeda.register_adapter(:test, Yabeda::TestAdapter.instance)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yabeda
4
- VERSION = "0.10.1"
4
+ VERSION = "0.12.0"
5
5
  end
data/lib/yabeda.rb CHANGED
@@ -86,7 +86,7 @@ module Yabeda
86
86
 
87
87
  # Perform configuration: registration of metrics and collector blocks
88
88
  # @return [void]
89
- # rubocop: disable Metrics/MethodLength, Metrics/AbcSize
89
+ # rubocop: disable Metrics/MethodLength
90
90
  def configure!
91
91
  raise(AlreadyConfiguredError, @configured_by) if already_configured?
92
92
 
@@ -129,12 +129,11 @@ module Yabeda
129
129
 
130
130
  true
131
131
  end
132
- # rubocop: enable Metrics/MethodLength, Metrics/AbcSize
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.
136
136
  # @api private
137
- # rubocop: disable Metrics/AbcSize
138
137
  def reset!
139
138
  default_tags.clear
140
139
  adapters.clear
@@ -147,6 +146,5 @@ module Yabeda
147
146
  instance_variable_set(:@configured_by, nil)
148
147
  instance_variable_set(:@debug_was_enabled_by, nil)
149
148
  end
150
- # rubocop: enable Metrics/AbcSize
151
149
  end
152
150
  end
data/yabeda.gemspec CHANGED
@@ -29,9 +29,5 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency "concurrent-ruby"
30
30
  spec.add_dependency "dry-initializer"
31
31
 
32
- spec.add_development_dependency "bundler", "~> 2.0"
33
- spec.add_development_dependency "rake", "~> 12.0"
34
- spec.add_development_dependency "rspec", "~> 3.0"
35
- spec.add_development_dependency "yard"
36
- spec.add_development_dependency "yard-dry-initializer"
32
+ spec.required_ruby_version = ">= 2.3"
37
33
  end
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.10.1
4
+ version: 0.12.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: 2021-08-30 00:00:00.000000000 Z
11
+ date: 2023-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anyway_config
@@ -58,76 +58,6 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
- - !ruby/object:Gem::Dependency
62
- name: bundler
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '2.0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '2.0'
75
- - !ruby/object:Gem::Dependency
76
- name: rake
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '12.0'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '12.0'
89
- - !ruby/object:Gem::Dependency
90
- name: rspec
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '3.0'
96
- type: :development
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '3.0'
103
- - !ruby/object:Gem::Dependency
104
- name: yard
105
- requirement: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- type: :development
111
- prerelease: false
112
- version_requirements: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- version: '0'
117
- - !ruby/object:Gem::Dependency
118
- name: yard-dry-initializer
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- version: '0'
124
- type: :development
125
- prerelease: false
126
- version_requirements: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - ">="
129
- - !ruby/object:Gem::Version
130
- version: '0'
131
61
  description: 'Collect statistics about how your application is performing with ease.
132
62
  Export metrics to various monitoring systems.
133
63
 
@@ -139,6 +69,7 @@ extensions: []
139
69
  extra_rdoc_files: []
140
70
  files:
141
71
  - ".github/workflows/build-release.yml"
72
+ - ".github/workflows/lint.yml"
142
73
  - ".github/workflows/test.yml"
143
74
  - ".gitignore"
144
75
  - ".rspec"
@@ -166,7 +97,16 @@ files:
166
97
  - lib/yabeda/histogram.rb
167
98
  - lib/yabeda/metric.rb
168
99
  - lib/yabeda/railtie.rb
100
+ - lib/yabeda/rspec.rb
101
+ - lib/yabeda/rspec/base_matcher.rb
102
+ - lib/yabeda/rspec/increment_yabeda_counter.rb
103
+ - lib/yabeda/rspec/measure_yabeda_histogram.rb
104
+ - lib/yabeda/rspec/observe_yabeda_summary.rb
105
+ - lib/yabeda/rspec/update_yabeda_gauge.rb
106
+ - lib/yabeda/summary.rb
169
107
  - lib/yabeda/tags.rb
108
+ - lib/yabeda/test_adapter.rb
109
+ - lib/yabeda/testing.rb
170
110
  - lib/yabeda/version.rb
171
111
  - yabeda-logo.png
172
112
  - yabeda.gemspec
@@ -182,7 +122,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
182
122
  requirements:
183
123
  - - ">="
184
124
  - !ruby/object:Gem::Version
185
- version: '0'
125
+ version: '2.3'
186
126
  required_rubygems_version: !ruby/object:Gem::Requirement
187
127
  requirements:
188
128
  - - ">="