yabeda 0.8.0 → 0.11.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 +4 -4
- data/.github/workflows/build-release.yml +82 -0
- data/.github/workflows/test.yml +48 -0
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +35 -0
- data/README.md +142 -6
- data/lib/yabeda/base_adapter.rb +3 -0
- data/lib/yabeda/config.rb +16 -0
- data/lib/yabeda/counter.rb +1 -1
- data/lib/yabeda/dsl/class_methods.rb +15 -8
- data/lib/yabeda/gauge.rb +1 -1
- data/lib/yabeda/global_group.rb +13 -0
- data/lib/yabeda/group.rb +10 -0
- data/lib/yabeda/histogram.rb +14 -2
- data/lib/yabeda/metric.rb +8 -2
- data/lib/yabeda/rspec/base_matcher.rb +64 -0
- data/lib/yabeda/rspec/increment_yabeda_counter.rb +81 -0
- data/lib/yabeda/rspec/measure_yabeda_histogram.rb +79 -0
- data/lib/yabeda/rspec/update_yabeda_gauge.rb +79 -0
- data/lib/yabeda/rspec.rb +25 -0
- data/lib/yabeda/tags.rb +6 -2
- data/lib/yabeda/test_adapter.rb +51 -0
- data/lib/yabeda/testing.rb +7 -0
- data/lib/yabeda/version.rb +1 -1
- data/lib/yabeda.rb +57 -4
- data/yabeda-logo.png +0 -0
- data/yabeda.gemspec +1 -0
- metadata +36 -5
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 262fe8c41ef493e3792e67b3e569f3820f8fc8de11a5795807d8d81efe8e0d9f
|
4
|
+
data.tar.gz: d86b4a968f1894ce65af4976873135957a122672cd58453023d38792b60c4db7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22c41d84fb0efbb8ca1802e02c1c4e6636fc8a2ae05c22775d4390ddc5ddfd0926b97464d6b2702e6b82a1b8709d66bf391bcb7aa6ed05df9a640a0143817889
|
7
|
+
data.tar.gz: ad5e58fc8b678c0764b6d8f3677c56a08d087dcbc9407b3a6864fa0e85226a773dce2efaa365d8bed0d0c25db300829811688819a407dee86bcefbea439e2232
|
@@ -0,0 +1,82 @@
|
|
1
|
+
name: Build and release gem to RubyGems
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
tags:
|
6
|
+
- v*
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
release:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
with:
|
14
|
+
fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290
|
15
|
+
- uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: 2.7
|
18
|
+
- name: "Extract data from tag: version, message, body"
|
19
|
+
id: tag
|
20
|
+
run: |
|
21
|
+
git fetch --tags --force # Really fetch annotated tag. See https://github.com/actions/checkout/issues/290#issuecomment-680260080
|
22
|
+
echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
|
23
|
+
echo ::set-output name=subject::$(git for-each-ref $GITHUB_REF --format='%(contents:subject)')
|
24
|
+
BODY="$(git for-each-ref $GITHUB_REF --format='%(contents:body)')"
|
25
|
+
# Extract changelog entries between this and previous version headers
|
26
|
+
escaped_version=$(echo ${GITHUB_REF#refs/tags/v} | sed -e 's/[]\/$*.^[]/\\&/g')
|
27
|
+
changelog=$(awk "BEGIN{inrelease=0} /## ${escaped_version}/{inrelease=1;next} /## [0-9]+\.[0-9]+\.[0-9]+/{inrelease=0;exit} {if (inrelease) print}" CHANGELOG.md)
|
28
|
+
# Multiline body for release. See https://github.community/t/set-output-truncates-multiline-strings/16852/5
|
29
|
+
BODY="${BODY}"$'\n'"${changelog}"
|
30
|
+
BODY="${BODY//'%'/'%25'}"
|
31
|
+
BODY="${BODY//$'\n'/'%0A'}"
|
32
|
+
BODY="${BODY//$'\r'/'%0D'}"
|
33
|
+
echo "::set-output name=body::$BODY"
|
34
|
+
# Add pre-release option if tag name has any suffix after vMAJOR.MINOR.PATCH
|
35
|
+
if [[ ${GITHUB_REF#refs/tags/} =~ ^v[0-9]+\.[0-9]+\.[0-9]+.+ ]]; then
|
36
|
+
echo ::set-output name=prerelease::true
|
37
|
+
fi
|
38
|
+
- name: Build gem
|
39
|
+
run: gem build
|
40
|
+
- name: Calculate checksums
|
41
|
+
run: sha256sum yabeda-${{ steps.tag.outputs.version }}.gem > SHA256SUM
|
42
|
+
- name: Check version
|
43
|
+
run: ls -l yabeda-${{ steps.tag.outputs.version }}.gem
|
44
|
+
- name: Create Release
|
45
|
+
id: create_release
|
46
|
+
uses: actions/create-release@v1
|
47
|
+
env:
|
48
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
49
|
+
with:
|
50
|
+
tag_name: ${{ github.ref }}
|
51
|
+
release_name: ${{ steps.tag.outputs.subject }}
|
52
|
+
body: ${{ steps.tag.outputs.body }}
|
53
|
+
draft: false
|
54
|
+
prerelease: ${{ steps.tag.outputs.prerelease }}
|
55
|
+
- name: Upload built gem as release asset
|
56
|
+
uses: actions/upload-release-asset@v1
|
57
|
+
env:
|
58
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
59
|
+
with:
|
60
|
+
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
61
|
+
asset_path: yabeda-${{ steps.tag.outputs.version }}.gem
|
62
|
+
asset_name: yabeda-${{ steps.tag.outputs.version }}.gem
|
63
|
+
asset_content_type: application/x-tar
|
64
|
+
- name: Upload checksums as release asset
|
65
|
+
uses: actions/upload-release-asset@v1
|
66
|
+
env:
|
67
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
68
|
+
with:
|
69
|
+
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
70
|
+
asset_path: SHA256SUM
|
71
|
+
asset_name: SHA256SUM
|
72
|
+
asset_content_type: text/plain
|
73
|
+
- name: Publish to GitHub packages
|
74
|
+
env:
|
75
|
+
GEM_HOST_API_KEY: Bearer ${{ secrets.GITHUB_TOKEN }}
|
76
|
+
run: |
|
77
|
+
gem push yabeda-${{ steps.tag.outputs.version }}.gem --host https://rubygems.pkg.github.com/${{ github.repository_owner }}
|
78
|
+
- name: Publish to RubyGems
|
79
|
+
env:
|
80
|
+
GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_API_KEY }}"
|
81
|
+
run: |
|
82
|
+
gem push yabeda-${{ steps.tag.outputs.version }}.gem
|
@@ -0,0 +1,48 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- '**'
|
8
|
+
tags-ignore:
|
9
|
+
- 'v*'
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
test:
|
13
|
+
name: "Ruby ${{ matrix.ruby }}"
|
14
|
+
runs-on: ubuntu-latest
|
15
|
+
strategy:
|
16
|
+
fail-fast: false
|
17
|
+
matrix:
|
18
|
+
include:
|
19
|
+
- ruby: "3.0"
|
20
|
+
- ruby: "2.7"
|
21
|
+
- ruby: "2.6"
|
22
|
+
- ruby: "2.5"
|
23
|
+
- ruby: "2.4"
|
24
|
+
- ruby: "2.3"
|
25
|
+
container:
|
26
|
+
image: ruby:${{ matrix.ruby }}
|
27
|
+
env:
|
28
|
+
CI: true
|
29
|
+
steps:
|
30
|
+
- uses: actions/checkout@v2
|
31
|
+
- uses: actions/cache@v2
|
32
|
+
with:
|
33
|
+
path: vendor/bundle
|
34
|
+
key: bundle-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles('**/Gemfile') }}
|
35
|
+
restore-keys: |
|
36
|
+
bundle-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles('**/Gemfile') }}
|
37
|
+
bundle-${{ matrix.ruby }}-
|
38
|
+
- name: Upgrade Bundler to 2.0 (for older Rubies)
|
39
|
+
run: gem install bundler -v '~> 2.0'
|
40
|
+
- name: Bundle install
|
41
|
+
run: |
|
42
|
+
bundle config path vendor/bundle
|
43
|
+
bundle install
|
44
|
+
bundle update
|
45
|
+
- name: Run Rubocop
|
46
|
+
run: bundle exec rubocop
|
47
|
+
- name: Run RSpec
|
48
|
+
run: bundle exec rspec
|
data/.rubocop.yml
CHANGED
@@ -23,6 +23,12 @@ RSpec/LetSetup:
|
|
23
23
|
RSpec/MultipleExpectations:
|
24
24
|
Enabled: false
|
25
25
|
|
26
|
+
RSpec/DescribeClass:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
RSpec/NestedGroups:
|
30
|
+
Max: 4
|
31
|
+
|
26
32
|
Bundler/OrderedGems:
|
27
33
|
Enabled: false
|
28
34
|
|
@@ -52,3 +58,6 @@ Style/HashTransformKeys:
|
|
52
58
|
|
53
59
|
Style/HashTransformValues:
|
54
60
|
Enabled: true
|
61
|
+
|
62
|
+
Style/Documentation:
|
63
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## Unreleased
|
9
|
+
|
10
|
+
## 0.11.0 - 2021-09-25
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- 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][]
|
15
|
+
- Automatic setup of RSpec on `require "yabeda/rspec"`
|
16
|
+
- Special test adapter that collects metric changes in memory
|
17
|
+
|
18
|
+
## 0.10.1 - 2021-08-30
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
- Compatibility with anyway_config 1.x gem (which is automatically used on older Rubies, older then minimal Ruby 2.5 for anyway_config 2.x)
|
23
|
+
|
24
|
+
## 0.10.0 - 2021-07-21
|
25
|
+
|
26
|
+
### Added
|
27
|
+
|
28
|
+
- Ability to pass a block to `Yabeda::Histogram#measure` to automatically measure its runtime in seconds using [monotonic time](https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/).
|
29
|
+
- Debug mode that will enable some additional metrics to help debug performance issues with your usage of Yabeda (or Yabeda itself). Use environment variable `YABEDA_DEBUG` to enable it or call `Yabeda.debug!`.
|
30
|
+
- Debugging histogram `yabeda_collect_duration` that measures duration of every collect block, as they are used for collecting metrics of application state and usually makes some potentially slow queries to databases, network requests, etc.
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
|
34
|
+
- Adapters now should use method `Yabeda.collect!` instead of manual calling of every collector block.
|
35
|
+
|
36
|
+
## 0.9.0 - 2021-05-07
|
37
|
+
|
38
|
+
### Added
|
39
|
+
|
40
|
+
- Ability to set global metric tags only for a specific group [#19](https://github.com/yabeda-rb/yabeda/pull/19) by [@liaden]
|
41
|
+
|
8
42
|
## 0.8.0 - 2020-08-21
|
9
43
|
|
10
44
|
### Added
|
@@ -99,3 +133,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
99
133
|
[@Envek]: https://github.com/Envek "Andrey Novikov"
|
100
134
|
[@dsalahutdinov]: https://github.com/dsalahutdinov "Dmitry Salahutdinov"
|
101
135
|
[@asusikov]: https://github.com/asusikov "Alexander Susikov"
|
136
|
+
[@liaden]: https://github.com/liaden "Joel Johnson"
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# Yabeda
|
1
|
+
# 
|
2
2
|
|
3
|
-
[](https://rubygems.org/gems/yabeda)
|
3
|
+
[](https://rubygems.org/gems/yabeda)
|
4
4
|
|
5
5
|
**This software is Work in Progress: features will appear and disappear, API will be changed, your feedback is always welcome!**
|
6
6
|
|
@@ -79,10 +79,14 @@ And then execute:
|
|
79
79
|
end
|
80
80
|
```
|
81
81
|
|
82
|
-
5. _Optionally_ setup default tags
|
82
|
+
5. _Optionally_ setup default tags for all appropriate metrics
|
83
83
|
```ruby
|
84
84
|
Yabeda.configure do
|
85
|
+
# matches all metrics in all groups
|
85
86
|
default_tag :rails_environment, 'production'
|
87
|
+
|
88
|
+
# matches all metrics in the :your_app group
|
89
|
+
default_tag :tag_name, 'override', group: :your_app
|
86
90
|
end
|
87
91
|
|
88
92
|
# You can redefine them for limited amount of time
|
@@ -91,17 +95,111 @@ And then execute:
|
|
91
95
|
end
|
92
96
|
```
|
93
97
|
|
94
|
-
|
95
|
-
|
98
|
+
**Note**: any usage of `with_tags` **must** have all those tags defined on all metrics that are generated in the block.
|
99
|
+
|
100
|
+
6. _Optionally_ override default tags using precedence:
|
101
|
+
|
102
|
+
The tag precedence from high to low is:
|
103
|
+
|
104
|
+
* Manually specified tags
|
105
|
+
* Thread local tags (specified by `Yabeda.with_tags`)
|
106
|
+
* Group specific tags
|
107
|
+
* Global tags
|
108
|
+
|
109
|
+
7. See the docs for the adapter you're using
|
110
|
+
8. Enjoy!
|
96
111
|
|
97
112
|
## Available monitoring system adapters
|
98
113
|
|
99
|
-
|
114
|
+
### Maintained by Yabeda
|
115
|
+
|
116
|
+
- Prometheus:
|
117
|
+
- [yabeda-prometheus](https://github.com/yabeda-rb/yabeda-prometheus) — wraps [official Ruby client for Prometheus](https://github.com/prometheus/client_ruby).
|
118
|
+
- [yabeda-prometheus-mmap](https://github.com/yabeda-rb/yabeda-prometheus-mmap) — wraps [GitLab's fork of Prometheus Ruby client](https://gitlab.com/gitlab-org/prometheus-client-mmap) which may work better for multi-process application servers.
|
100
119
|
- [Datadog](https://github.com/yabeda-rb/yabeda-datadog)
|
101
120
|
- [NewRelic](https://github.com/yabeda-rb/yabeda-newrelic)
|
121
|
+
|
122
|
+
### Third-party adapters
|
123
|
+
|
124
|
+
These are developed and maintained by other awesome folks:
|
125
|
+
|
102
126
|
- [Statsd](https://github.com/asusikov/yabeda-statsd)
|
103
127
|
- _…and more! You can write your own adapter and open a pull request to add it into this list._
|
104
128
|
|
129
|
+
## Available plugins to collect metrics
|
130
|
+
|
131
|
+
### Maintained by Yabeda
|
132
|
+
|
133
|
+
- [yabeda-rails] — basic request metrics for [Ruby on Rails](https://rubyonrails.org/) applications.
|
134
|
+
- [yabeda-sidekiq] — comprehensive set of metrics for monitoring [Sidekiq](https://sidekiq.org/) jobs execution and queues.
|
135
|
+
- [yabeda-faktory] — metrics for monitoring jobs execution by Ruby workers of [Faktory](https://contribsys.com/faktory/).
|
136
|
+
- [yabeda-graphql] — metrics to query and field-level monitoring for apps using [GraphQL-Ruby](https://graphql-ruby.org/).
|
137
|
+
- [yabeda-puma-plugin] — metrics for internal state and performance of [Puma](https://puma.io/) application server.
|
138
|
+
- [yabeda-http_requests] — monitor how many outgoing HTTP calls your application does (uses [Sniffer](https://github.com/aderyabin/sniffer)).
|
139
|
+
- [yabeda-schked] — monitor number and duration of Cron jobs executed by [Schked](https://github.com/bibendi/schked).
|
140
|
+
- [yabeda-anycable] — monitor number, duration, and status of [AnyCable](https://anycable.io/) RPC calls.
|
141
|
+
|
142
|
+
### Third-party plugins
|
143
|
+
|
144
|
+
These are developed and maintained by other awesome folks:
|
145
|
+
|
146
|
+
- [yabeda-grape](https://github.com/efigence/yabeda-grape) — metrics for [Grape](https://github.com/ruby-grape/grape) framework.
|
147
|
+
- [yabeda-gruf](https://github.com/Placewise/yabeda-gruf) — metrics for [gRPC Ruby Framework](https://github.com/bigcommerce/gruf)
|
148
|
+
- [yabeda-gc](https://github.com/ianks/yabeda-gc) — metrics for Ruby garbage collection.
|
149
|
+
- _…and more! You can write your own adapter and open a pull request to add it into this list._
|
150
|
+
|
151
|
+
## Configuration
|
152
|
+
|
153
|
+
Configuration is handled by [anyway_config] gem. With it you can load settings from environment variables (which names are constructed from config key upcased and prefixed with `YABEDA_`), YAML files, and other sources. See [anyway_config] docs for details.
|
154
|
+
|
155
|
+
Config key | Type | Default | Description |
|
156
|
+
---------- | -------- | ------- | ----------- |
|
157
|
+
`debug` | boolean | `false` | Collects metrics measuring Yabeda performance |
|
158
|
+
|
159
|
+
## Debugging metrics
|
160
|
+
|
161
|
+
- Time of collector block run: `yabeda_collect_duration` (segmented by block source location). Collector blocks are used for collecting metrics of application state and usually makes some potentially slow queries to databases, network requests, etc.
|
162
|
+
|
163
|
+
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
|
+
|
165
|
+
## Testing
|
166
|
+
|
167
|
+
### RSpec
|
168
|
+
|
169
|
+
Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
require "yabeda/rspec"
|
173
|
+
```
|
174
|
+
|
175
|
+
Now you can use `increment_yabeda_counter`, `update_yabeda_gauge`, and `measure_yabeda_histogram` matchers:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
it "increments counters" do
|
179
|
+
expect { subject }.to increment_yabeda_counter(Yabeda.myapp.foo_count).by(3)
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
You can scope metrics by used tags with `with_tags`:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
it "updates gauges" do
|
187
|
+
expect { subject }.to \
|
188
|
+
update_yabeda_gauge("some_gauge_name").
|
189
|
+
with_tags(method: "command", command: "subscribe")
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
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.
|
194
|
+
|
195
|
+
And check for values with `by` for counters, `to` for gauges, and `with` for gauges and histograms (and you [can use other matchers here](https://relishapp.com/rspec/rspec-expectations/v/3-10/docs/composing-matchers)):
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
expect { subject }.to \
|
199
|
+
measure_yabeda_histogram(Yabeda.something.anything_runtime).
|
200
|
+
with(be_between(0.005, 0.05))
|
201
|
+
```
|
202
|
+
|
105
203
|
## Roadmap (aka TODO or Help wanted)
|
106
204
|
|
107
205
|
- Ability to change metric settings for individual adapters
|
@@ -130,6 +228,36 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
130
228
|
|
131
229
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
132
230
|
|
231
|
+
### Releasing
|
232
|
+
|
233
|
+
1. Bump version number in `lib/yabeda/version.rb`
|
234
|
+
|
235
|
+
In case of pre-releases keep in mind [rubygems/rubygems#3086](https://github.com/rubygems/rubygems/issues/3086) and check version with command like `Gem::Version.new(Yabeda::VERSION).to_s`
|
236
|
+
|
237
|
+
2. Fill `CHANGELOG.md` with missing changes, add header with version and date.
|
238
|
+
|
239
|
+
3. Make a commit:
|
240
|
+
|
241
|
+
```sh
|
242
|
+
git add lib/yabeda/version.rb CHANGELOG.md
|
243
|
+
version=$(ruby -r ./lib/yabeda/version.rb -e "puts Gem::Version.new(Yabeda::VERSION)")
|
244
|
+
git commit --message="${version}: " --edit
|
245
|
+
```
|
246
|
+
|
247
|
+
3. Create annotated tag:
|
248
|
+
|
249
|
+
```sh
|
250
|
+
git tag v${version} --annotate --message="${version}: " --edit --sign
|
251
|
+
```
|
252
|
+
|
253
|
+
4. Fill version name into subject line and (optionally) some description (changes will be taken from changelog and appended automatically)
|
254
|
+
|
255
|
+
5. Push it:
|
256
|
+
|
257
|
+
```sh
|
258
|
+
git push --follow-tags
|
259
|
+
```
|
260
|
+
|
133
261
|
## Contributing
|
134
262
|
|
135
263
|
Bug reports and pull requests are welcome on GitHub at https://github.com/yabeda-rb/yabeda.
|
@@ -139,3 +267,11 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/yabeda
|
|
139
267
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
140
268
|
|
141
269
|
[yabeda-rails]: https://github.com/yabeda-rb/yabeda-rails/ "Yabeda plugin for collecting and exporting basic metrics for Rails applications"
|
270
|
+
[yabeda-sidekiq]: https://github.com/yabeda-rb/yabeda-sidekiq/ "Yabeda plugin for complete monitoring of Sidekiq metrics"
|
271
|
+
[yabeda-faktory]: https://github.com/yabeda-rb/yabeda-faktory/ "Yabeda plugin for complete monitoring of Faktory Ruby Workers"
|
272
|
+
[yabeda-graphql]: https://github.com/yabeda-rb/yabeda-graphql/ "Measure and understand how good your GraphQL-Ruby application works"
|
273
|
+
[yabeda-puma-plugin]: https://github.com/yabeda-rb/yabeda-puma-plugin/ "Collects Puma web-server metrics from puma control application"
|
274
|
+
[yabeda-http_requests]: https://github.com/yabeda-rb/yabeda-http_requests/ "Builtin metrics to monitor external HTTP requests"
|
275
|
+
[yabeda-schked]: https://github.com/yabeda-rb/yabeda-schked/ "Built-in metrics for monitoring Schked recurring jobs out of the box"
|
276
|
+
[yabeda-anycable]: https://github.com/yabeda-rb/yabeda-anycable "Collect performance metrics for AnyCable RPC server"
|
277
|
+
[anyway_config]: https://github.com/palkan/anyway_config "Configuration library for Ruby gems and applications"
|
data/lib/yabeda/base_adapter.rb
CHANGED
@@ -35,5 +35,8 @@ module Yabeda
|
|
35
35
|
def perform_histogram_measure!(_metric, _tags, _value)
|
36
36
|
raise NotImplementedError, "#{self.class} doesn't support measuring histograms"
|
37
37
|
end
|
38
|
+
|
39
|
+
# Hook to enable debug mode in adapters when it is enabled in Yabeda itself
|
40
|
+
def debug!; end
|
38
41
|
end
|
39
42
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anyway"
|
4
|
+
|
5
|
+
module Yabeda
|
6
|
+
# Runtime configuration for the main yabeda gem
|
7
|
+
class Config < ::Anyway::Config
|
8
|
+
config_name :yabeda
|
9
|
+
|
10
|
+
# Declare and collect metrics about Yabeda performance
|
11
|
+
attr_config debug: false
|
12
|
+
|
13
|
+
# Implement predicate method from AnywayConfig 2.x to support AnywayConfig 1.x users
|
14
|
+
alias debug? debug unless instance_methods.include?(:debug?)
|
15
|
+
end
|
16
|
+
end
|
data/lib/yabeda/counter.rb
CHANGED
@@ -4,7 +4,7 @@ module Yabeda
|
|
4
4
|
# Growing-only counter
|
5
5
|
class Counter < Metric
|
6
6
|
def increment(tags, by: 1)
|
7
|
-
all_tags = ::Yabeda::Tags.build(tags)
|
7
|
+
all_tags = ::Yabeda::Tags.build(tags, group)
|
8
8
|
values[all_tags] += by
|
9
9
|
::Yabeda.adapters.each do |_, adapter|
|
10
10
|
adapter.perform_counter_increment!(self, all_tags, by)
|
@@ -5,12 +5,12 @@ require "yabeda/counter"
|
|
5
5
|
require "yabeda/gauge"
|
6
6
|
require "yabeda/histogram"
|
7
7
|
require "yabeda/group"
|
8
|
+
require "yabeda/global_group"
|
8
9
|
require "yabeda/dsl/metric_builder"
|
9
10
|
|
10
11
|
module Yabeda
|
11
12
|
# DSL for ease of work with Yabeda
|
12
13
|
module DSL
|
13
|
-
# rubocop: disable Style/Documentation
|
14
14
|
module ClassMethods
|
15
15
|
# Block for grouping and simplifying configuration of related metrics
|
16
16
|
def configure(&block)
|
@@ -30,6 +30,7 @@ module Yabeda
|
|
30
30
|
# (like NewRelic) it is treated individually and has a special meaning.
|
31
31
|
def group(group_name)
|
32
32
|
@group = group_name
|
33
|
+
Yabeda.groups[@group] ||= Yabeda::Group.new(@group)
|
33
34
|
return unless block_given?
|
34
35
|
|
35
36
|
yield
|
@@ -58,24 +59,30 @@ module Yabeda
|
|
58
59
|
#
|
59
60
|
# @param name [Symbol] Name of default tag
|
60
61
|
# @param value [String] Value of default tag
|
61
|
-
def default_tag(name, value)
|
62
|
-
|
62
|
+
def default_tag(name, value, group: @group)
|
63
|
+
if group
|
64
|
+
Yabeda.groups[group] ||= Yabeda::Group.new(group)
|
65
|
+
Yabeda.groups[group].default_tag(name, value)
|
66
|
+
else
|
67
|
+
Yabeda.default_tags[name] = value
|
68
|
+
end
|
63
69
|
end
|
64
70
|
|
65
71
|
# Redefine default tags for a limited amount of time
|
66
72
|
# @param tags Hash{Symbol=>#to_s}
|
67
73
|
def with_tags(**tags)
|
68
|
-
|
74
|
+
previous_temp_tags = temporary_tags
|
75
|
+
Thread.current[:yabeda_temporary_tags] = Thread.current[:yabeda_temporary_tags].merge(tags)
|
69
76
|
yield
|
70
77
|
ensure
|
71
|
-
Thread.current[:yabeda_temporary_tags] =
|
78
|
+
Thread.current[:yabeda_temporary_tags] = previous_temp_tags
|
72
79
|
end
|
73
80
|
|
74
81
|
# Get tags set by +with_tags+
|
75
82
|
# @api private
|
76
83
|
# @return Hash
|
77
84
|
def temporary_tags
|
78
|
-
Thread.current[:yabeda_temporary_tags]
|
85
|
+
Thread.current[:yabeda_temporary_tags] ||= {}
|
79
86
|
end
|
80
87
|
|
81
88
|
private
|
@@ -95,12 +102,12 @@ module Yabeda
|
|
95
102
|
if group.nil?
|
96
103
|
group = Group.new(metric.group)
|
97
104
|
::Yabeda.groups[metric.group] = group
|
98
|
-
::Yabeda.define_singleton_method(metric.group) { group }
|
99
105
|
end
|
100
106
|
|
107
|
+
::Yabeda.define_singleton_method(metric.group) { group } unless ::Yabeda.respond_to?(metric.group)
|
108
|
+
|
101
109
|
group.register_metric(metric)
|
102
110
|
end
|
103
111
|
end
|
104
|
-
# rubocop: enable Style/Documentation
|
105
112
|
end
|
106
113
|
end
|
data/lib/yabeda/gauge.rb
CHANGED
@@ -4,7 +4,7 @@ module Yabeda
|
|
4
4
|
# Arbitrary value, can be changed in both sides
|
5
5
|
class Gauge < Metric
|
6
6
|
def set(tags, value)
|
7
|
-
all_tags = ::Yabeda::Tags.build(tags)
|
7
|
+
all_tags = ::Yabeda::Tags.build(tags, group)
|
8
8
|
values[all_tags] = value
|
9
9
|
::Yabeda.adapters.each do |_, adapter|
|
10
10
|
adapter.perform_gauge_set!(self, all_tags, value)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
require_relative "./group"
|
5
|
+
|
6
|
+
module Yabeda
|
7
|
+
# Represents implicit global group
|
8
|
+
class GlobalGroup < Group
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators ::Yabeda, :default_tags, :default_tag
|
12
|
+
end
|
13
|
+
end
|
data/lib/yabeda/group.rb
CHANGED
@@ -9,6 +9,16 @@ module Yabeda
|
|
9
9
|
|
10
10
|
param :name
|
11
11
|
|
12
|
+
def default_tags
|
13
|
+
@default_tags ||= Concurrent::Hash.new
|
14
|
+
::Yabeda.default_tags.merge(@default_tags)
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_tag(key, value)
|
18
|
+
@default_tags ||= Concurrent::Hash.new
|
19
|
+
@default_tags[key] = value
|
20
|
+
end
|
21
|
+
|
12
22
|
def register_metric(metric)
|
13
23
|
define_singleton_method(metric.name) { metric }
|
14
24
|
end
|
data/lib/yabeda/histogram.rb
CHANGED
@@ -6,13 +6,25 @@ module Yabeda
|
|
6
6
|
class Histogram < Metric
|
7
7
|
option :buckets
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
# rubocop: disable Metrics/MethodLength
|
10
|
+
def measure(tags, value = nil)
|
11
|
+
if value.nil? ^ block_given?
|
12
|
+
raise ArgumentError, "You must provide either numeric value or block for Yabeda::Histogram#measure!"
|
13
|
+
end
|
14
|
+
|
15
|
+
if block_given?
|
16
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
17
|
+
yield
|
18
|
+
value = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - starting)
|
19
|
+
end
|
20
|
+
|
21
|
+
all_tags = ::Yabeda::Tags.build(tags, group)
|
11
22
|
values[all_tags] = value
|
12
23
|
::Yabeda.adapters.each do |_, adapter|
|
13
24
|
adapter.perform_histogram_measure!(self, all_tags, value)
|
14
25
|
end
|
15
26
|
value
|
16
27
|
end
|
28
|
+
# rubocop: enable Metrics/MethodLength
|
17
29
|
end
|
18
30
|
end
|
data/lib/yabeda/metric.rb
CHANGED
@@ -17,15 +17,21 @@ module Yabeda
|
|
17
17
|
|
18
18
|
# Returns the value for the given label set
|
19
19
|
def get(labels = {})
|
20
|
-
values[::Yabeda::Tags.build(labels)]
|
20
|
+
values[::Yabeda::Tags.build(labels, group)]
|
21
21
|
end
|
22
22
|
|
23
23
|
def values
|
24
24
|
@values ||= Concurrent::Hash.new
|
25
25
|
end
|
26
26
|
|
27
|
+
# Returns allowed tags for metric (with account for global and group-level +default_tags+)
|
28
|
+
# @return Array<Symbol>
|
27
29
|
def tags
|
28
|
-
(Yabeda.default_tags.keys + Array(super)).uniq
|
30
|
+
(Yabeda.groups[group].default_tags.keys + Array(super)).uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"#<#{self.class.name}: #{[@group, @name].compact.join('.')}>"
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -0,0 +1,64 @@
|
|
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
|
+
@expected = @metric = resolve_metric(expected)
|
21
|
+
rescue KeyError
|
22
|
+
raise ArgumentError, <<~MSG
|
23
|
+
Pass metric name or metric instance to matcher (e.g. `increment_yabeda_counter(Yabeda.metric_name)` or \
|
24
|
+
increment_yabeda_counter('metric_name')). Got #{expected.inspect} instead
|
25
|
+
MSG
|
26
|
+
end
|
27
|
+
|
28
|
+
# RSpec doesn't define this method, but it is more convenient to rely on +match_when_negated+ method presence
|
29
|
+
def does_not_match?(actual)
|
30
|
+
@actual = actual
|
31
|
+
if respond_to?(:match_when_negated)
|
32
|
+
match_when_negated(expected, actual)
|
33
|
+
else
|
34
|
+
!match(expected, actual)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def supports_block_expectations?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Pretty print metric name (expected is expected to always be a Yabeda metric instance)
|
43
|
+
def expected_formatted
|
44
|
+
"Yabeda.#{[metric.group, metric.name].compact.join('.')}"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def resolve_metric(instance_or_name)
|
50
|
+
return instance_or_name if instance_or_name.is_a? Yabeda::Metric
|
51
|
+
|
52
|
+
Yabeda.metrics.fetch(instance_or_name.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Filter metric changes by tags.
|
56
|
+
# If tags specified, treat them as subset of real tags (to avoid bothering with default tags in tests)
|
57
|
+
def filter_matching_changes(changes)
|
58
|
+
return changes if tags.nil?
|
59
|
+
|
60
|
+
changes.select { |t, _v| t >= tags }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
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,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
|
data/lib/yabeda/rspec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
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
|
+
|
15
|
+
::RSpec.configure do |config|
|
16
|
+
config.before(:suite) do
|
17
|
+
Yabeda.configure! unless Yabeda.already_configured?
|
18
|
+
end
|
19
|
+
|
20
|
+
config.after(:each) do
|
21
|
+
Yabeda::TestAdapter.instance.reset!
|
22
|
+
end
|
23
|
+
|
24
|
+
config.include(Yabeda::RSpec)
|
25
|
+
end
|
data/lib/yabeda/tags.rb
CHANGED
@@ -3,8 +3,12 @@
|
|
3
3
|
module Yabeda
|
4
4
|
# Class to merge tags
|
5
5
|
class Tags
|
6
|
-
def self.build(tags)
|
7
|
-
|
6
|
+
def self.build(tags, group_name = nil)
|
7
|
+
Yabeda.default_tags.dup.tap do |result|
|
8
|
+
result.merge!(Yabeda.groups[group_name].default_tags) if group_name
|
9
|
+
result.merge!(Yabeda.temporary_tags)
|
10
|
+
result.merge!(tags)
|
11
|
+
end
|
8
12
|
end
|
9
13
|
end
|
10
14
|
end
|
@@ -0,0 +1,51 @@
|
|
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
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@counters = Hash.new { |ch, ck| ch[ck] = Hash.new { |th, tk| th[tk] = 0 } }
|
16
|
+
@gauges = Hash.new { |gh, gk| gh[gk] = Hash.new { |th, tk| th[tk] = nil } }
|
17
|
+
@histograms = Hash.new { |hh, hk| hh[hk] = Hash.new { |th, tk| th[tk] = nil } }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Call this method after every test example to quickly get blank state for the next test example
|
21
|
+
def reset!
|
22
|
+
[@counters, @gauges, @histograms].each do |collection|
|
23
|
+
collection.each_value(&:clear) # Reset tag-values hash to be empty
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_counter!(metric)
|
28
|
+
@counters[metric]
|
29
|
+
end
|
30
|
+
|
31
|
+
def register_gauge!(metric)
|
32
|
+
@gauges[metric]
|
33
|
+
end
|
34
|
+
|
35
|
+
def register_histogram!(metric)
|
36
|
+
@histograms[metric]
|
37
|
+
end
|
38
|
+
|
39
|
+
def perform_counter_increment!(counter, tags, increment)
|
40
|
+
@counters[counter][tags] += increment
|
41
|
+
end
|
42
|
+
|
43
|
+
def perform_gauge_set!(gauge, tags, value)
|
44
|
+
@gauges[gauge][tags] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def perform_histogram_measure!(histogram, tags, value)
|
48
|
+
@histograms[histogram][tags] = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/yabeda/version.rb
CHANGED
data/lib/yabeda.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "concurrent"
|
4
|
+
require "forwardable"
|
4
5
|
|
5
6
|
require "yabeda/version"
|
7
|
+
require "yabeda/config"
|
6
8
|
require "yabeda/dsl"
|
7
9
|
require "yabeda/tags"
|
8
10
|
require "yabeda/errors"
|
@@ -13,6 +15,8 @@ module Yabeda
|
|
13
15
|
include DSL
|
14
16
|
|
15
17
|
class << self
|
18
|
+
extend Forwardable
|
19
|
+
|
16
20
|
# @return [Hash<String, Yabeda::Metric>] All registered metrics
|
17
21
|
def metrics
|
18
22
|
@metrics ||= Concurrent::Hash.new
|
@@ -20,7 +24,9 @@ module Yabeda
|
|
20
24
|
|
21
25
|
# @return [Hash<String, Yabeda::Group>] All registered metrics
|
22
26
|
def groups
|
23
|
-
@groups ||= Concurrent::Hash.new
|
27
|
+
@groups ||= Concurrent::Hash.new.tap do |hash|
|
28
|
+
hash[nil] = Yabeda::GlobalGroup.new(nil)
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
# @return [Hash<String, Yabeda::BaseAdapter>] All loaded adapters
|
@@ -33,7 +39,26 @@ module Yabeda
|
|
33
39
|
@collectors ||= Concurrent::Array.new
|
34
40
|
end
|
35
41
|
|
36
|
-
|
42
|
+
def config
|
43
|
+
@config ||= Config.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def_delegators :config, :debug?
|
47
|
+
|
48
|
+
# Execute all collector blocks for periodical retrieval of metrics
|
49
|
+
#
|
50
|
+
# This method is intended to be used by monitoring systems adapters
|
51
|
+
def collect!
|
52
|
+
collectors.each do |collector|
|
53
|
+
if config.debug?
|
54
|
+
yabeda.collect_duration.measure({ location: collector.source_location.join(":") }, &collector)
|
55
|
+
else
|
56
|
+
collector.call
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Hash<Symbol, Symbol>] All added global default tags
|
37
62
|
def default_tags
|
38
63
|
@default_tags ||= Concurrent::Hash.new
|
39
64
|
end
|
@@ -65,6 +90,8 @@ module Yabeda
|
|
65
90
|
def configure!
|
66
91
|
raise(AlreadyConfiguredError, @configured_by) if already_configured?
|
67
92
|
|
93
|
+
debug! if config.debug?
|
94
|
+
|
68
95
|
configurators.each do |(group, block)|
|
69
96
|
group group
|
70
97
|
class_eval(&block)
|
@@ -81,19 +108,45 @@ module Yabeda
|
|
81
108
|
|
82
109
|
@configured_by = caller_locations(1, 1)[0].to_s
|
83
110
|
end
|
111
|
+
|
112
|
+
# Enable and setup service metrics to monitor yabeda performance
|
113
|
+
def debug!
|
114
|
+
return false if @debug_was_enabled_by # Prevent multiple calls
|
115
|
+
|
116
|
+
config.debug ||= true # Enable debug mode in config if it wasn't enabled from other sources
|
117
|
+
@debug_was_enabled_by = caller_locations(1, 1)[0].to_s
|
118
|
+
|
119
|
+
configure do
|
120
|
+
group :yabeda
|
121
|
+
|
122
|
+
histogram :collect_duration,
|
123
|
+
tags: %i[location], unit: :seconds,
|
124
|
+
buckets: [0.0001, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60].freeze,
|
125
|
+
comment: "A histogram for the time required to evaluate collect blocks"
|
126
|
+
end
|
127
|
+
|
128
|
+
adapters.each_value(&:debug!)
|
129
|
+
|
130
|
+
true
|
131
|
+
end
|
84
132
|
# rubocop: enable Metrics/MethodLength, Metrics/AbcSize
|
85
133
|
|
86
134
|
# Forget all the configuration.
|
87
135
|
# For testing purposes as it doesn't rollback changes in adapters.
|
88
136
|
# @api private
|
137
|
+
# rubocop: disable Metrics/AbcSize
|
89
138
|
def reset!
|
90
139
|
default_tags.clear
|
91
140
|
adapters.clear
|
92
|
-
groups.
|
93
|
-
|
141
|
+
groups.each_key { |group| singleton_class.send(:remove_method, group) if group && respond_to?(group) }
|
142
|
+
@groups = nil
|
143
|
+
metrics.each_key { |metric| singleton_class.send(:remove_method, metric) if respond_to?(metric) }
|
144
|
+
@metrics = nil
|
94
145
|
collectors.clear
|
95
146
|
configurators.clear
|
96
147
|
instance_variable_set(:@configured_by, nil)
|
148
|
+
instance_variable_set(:@debug_was_enabled_by, nil)
|
97
149
|
end
|
150
|
+
# rubocop: enable Metrics/AbcSize
|
98
151
|
end
|
99
152
|
end
|
data/yabeda-logo.png
ADDED
Binary file
|
data/yabeda.gemspec
CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
26
|
spec.require_paths = ["lib"]
|
27
27
|
|
28
|
+
spec.add_dependency "anyway_config", ">= 1.0", "< 3"
|
28
29
|
spec.add_dependency "concurrent-ruby"
|
29
30
|
spec.add_dependency "dry-initializer"
|
30
31
|
|
metadata
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yabeda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.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:
|
11
|
+
date: 2021-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: anyway_config
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3'
|
13
33
|
- !ruby/object:Gem::Dependency
|
14
34
|
name: concurrent-ruby
|
15
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,17 +131,18 @@ dependencies:
|
|
111
131
|
description: 'Collect statistics about how your application is performing with ease.
|
112
132
|
Export metrics to various monitoring systems.
|
113
133
|
|
114
|
-
'
|
134
|
+
'
|
115
135
|
email:
|
116
136
|
- envek@envek.name
|
117
137
|
executables: []
|
118
138
|
extensions: []
|
119
139
|
extra_rdoc_files: []
|
120
140
|
files:
|
141
|
+
- ".github/workflows/build-release.yml"
|
142
|
+
- ".github/workflows/test.yml"
|
121
143
|
- ".gitignore"
|
122
144
|
- ".rspec"
|
123
145
|
- ".rubocop.yml"
|
124
|
-
- ".travis.yml"
|
125
146
|
- ".yardopts"
|
126
147
|
- CHANGELOG.md
|
127
148
|
- Gemfile
|
@@ -132,6 +153,7 @@ files:
|
|
132
153
|
- bin/setup
|
133
154
|
- lib/yabeda.rb
|
134
155
|
- lib/yabeda/base_adapter.rb
|
156
|
+
- lib/yabeda/config.rb
|
135
157
|
- lib/yabeda/counter.rb
|
136
158
|
- lib/yabeda/dsl.rb
|
137
159
|
- lib/yabeda/dsl/class_methods.rb
|
@@ -139,12 +161,21 @@ files:
|
|
139
161
|
- lib/yabeda/dsl/option_builder.rb
|
140
162
|
- lib/yabeda/errors.rb
|
141
163
|
- lib/yabeda/gauge.rb
|
164
|
+
- lib/yabeda/global_group.rb
|
142
165
|
- lib/yabeda/group.rb
|
143
166
|
- lib/yabeda/histogram.rb
|
144
167
|
- lib/yabeda/metric.rb
|
145
168
|
- lib/yabeda/railtie.rb
|
169
|
+
- lib/yabeda/rspec.rb
|
170
|
+
- lib/yabeda/rspec/base_matcher.rb
|
171
|
+
- lib/yabeda/rspec/increment_yabeda_counter.rb
|
172
|
+
- lib/yabeda/rspec/measure_yabeda_histogram.rb
|
173
|
+
- lib/yabeda/rspec/update_yabeda_gauge.rb
|
146
174
|
- lib/yabeda/tags.rb
|
175
|
+
- lib/yabeda/test_adapter.rb
|
176
|
+
- lib/yabeda/testing.rb
|
147
177
|
- lib/yabeda/version.rb
|
178
|
+
- yabeda-logo.png
|
148
179
|
- yabeda.gemspec
|
149
180
|
homepage: https://github.com/yabeda-rb/yabeda
|
150
181
|
licenses:
|
@@ -165,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
196
|
- !ruby/object:Gem::Version
|
166
197
|
version: '0'
|
167
198
|
requirements: []
|
168
|
-
rubygems_version: 3.1.
|
199
|
+
rubygems_version: 3.1.6
|
169
200
|
signing_key:
|
170
201
|
specification_version: 4
|
171
202
|
summary: Extensible framework for collecting metric for your Ruby application
|