measurometer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +103 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/measurometer/statsd_driver.rb +38 -0
- data/lib/measurometer/version.rb +3 -0
- data/lib/measurometer.rb +89 -0
- data/measurometer.gemspec +27 -0
- data/spec/measurometer/statsd_driver_spec.rb +28 -0
- data/spec/measurometer_spec.rb +111 -0
- data/spec/spec_helper.rb +11 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ec73a73f9d9ffac70cebe59196acea5fe394003d
|
4
|
+
data.tar.gz: c75bd18585251af9ce93b54046c950b35d5202a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8a2cafb15f8d6328b91f615c5662147798baaa20f08c7148708aa4a8c6599caf8d58a9f89cf075d51e5962e0fdea6b4081a771626be558fae17d336b530874e7
|
7
|
+
data.tar.gz: 0b946d3fcfb239b93e873a0da293f5febb1d3f8e3fa0dbd700352a4efd830594859eb38d226d8c65fe037251f25203442e86d6a54009b2bfe6af38f9d64270ef
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
## Our Standards
|
8
|
+
|
9
|
+
Examples of behavior that contributes to creating a positive environment include:
|
10
|
+
|
11
|
+
* Using welcoming and inclusive language
|
12
|
+
* Being respectful of differing viewpoints and experiences
|
13
|
+
* Gracefully accepting constructive criticism
|
14
|
+
* Focusing on what is best for the community
|
15
|
+
* Showing empathy towards other community members
|
16
|
+
|
17
|
+
Examples of unacceptable behavior by participants include:
|
18
|
+
|
19
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
20
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
21
|
+
* Public or private harassment
|
22
|
+
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
23
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
24
|
+
|
25
|
+
## Our Responsibilities
|
26
|
+
|
27
|
+
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
28
|
+
|
29
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
30
|
+
|
31
|
+
## Scope
|
32
|
+
|
33
|
+
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
34
|
+
|
35
|
+
## Enforcement
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at julik@wetransfer.com and/or noah@wetransfer.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
38
|
+
|
39
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
40
|
+
|
41
|
+
## Attribution
|
42
|
+
|
43
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
44
|
+
|
45
|
+
[homepage]: http://contributor-covenant.org
|
46
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2018- WeTransfer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Measurometer
|
2
|
+
|
3
|
+
Minimum viable API for instrumentation _in libraries._ A lightweight "hub" module that
|
4
|
+
will source simple instrumentation measurements/block contexts to different destinations. To illustrate the usefulness,
|
5
|
+
imagine you are using a library called `promulgator` in your code, as well as a library called `profligator`
|
6
|
+
|
7
|
+
You start measuring how long tasks in your application take, and you notice that an action suddenly takes 30 seconds.
|
8
|
+
You for a fact know that this action consists of the following code:
|
9
|
+
|
10
|
+
```
|
11
|
+
heavy_model = ModelTree.find(user_id)
|
12
|
+
prpfligation_outcome = Profilgator.profligate(heavy_model)
|
13
|
+
Promulgator.promulgate(profligation_outcome)
|
14
|
+
```
|
15
|
+
|
16
|
+
You can see how long `ModelTree.find` takes, because your metrics and error tracing solution latches onto the ActiveSupport
|
17
|
+
instrumentation hooks and records how long things take. But the Promulgator and the Profligator... who knows
|
18
|
+
what they do and for how long? Writing of benchmark scripts ensues.
|
19
|
+
|
20
|
+
Imagine, on the other hand, that you had the `Meaasurometer` supported in both the Promulgator and the Profligator.
|
21
|
+
Then you could say, for example, if you use [Appsignal](https://appsignal.com)
|
22
|
+
|
23
|
+
```
|
24
|
+
Measurometer.drivers << Appsignal
|
25
|
+
```
|
26
|
+
|
27
|
+
and metrics from both the `promulgator` gem and the `profligator` gem calls would be sourced to Appsignal automatically.
|
28
|
+
The good part of it is that neither the `promulgator` or the `profligator` would have to depend on something
|
29
|
+
heavy (or Rails version dependent!) as `ActiveSupport::Notifications`
|
30
|
+
|
31
|
+
## Visualising the benefit
|
32
|
+
|
33
|
+
This is an action from one of our applications, where we parse the image format and then perform image processing.
|
34
|
+
Both tasks (format detection and image transformations) are handled by separate libraries - `format_parser` and
|
35
|
+
`image_vise` respectively. Even though the graph comes from Appsignal, neither of the libraries has knowledge
|
36
|
+
of Appsignal's existence in the system.
|
37
|
+
|
38
|
+
![Appsignal action with Measurometer sources](measurometer_in_practice.png)
|
39
|
+
|
40
|
+
## Usage with statsd-ruby
|
41
|
+
|
42
|
+
For [statsd-ruby](https://github.com/reinh/statsd) we provide a builtin adapter. Pass it your client object:
|
43
|
+
|
44
|
+
```
|
45
|
+
$statsd = Statsd.new 'localhost', 8125
|
46
|
+
Measurometer.drivers << Measurometer::StatsdDriver.new($statsd)
|
47
|
+
```
|
48
|
+
|
49
|
+
## Installation for libraries
|
50
|
+
|
51
|
+
If you want to supply Measurometer metrics, add this line to your **library's** Gemfile:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
s.add_dependency 'measurometer', '~> 1'
|
55
|
+
```
|
56
|
+
|
57
|
+
That's right, we _promise_ to guarantee version 1 compatibility for as long as possible, for as long
|
58
|
+
as practical, and if there are to be breaking changes to the API semantic versioning will be followed.
|
59
|
+
|
60
|
+
Should it so happen that you do not trust us that API version compatibility will be maintained,
|
61
|
+
you can **copy** the entire Measurometer into your library, just let the user know that the driver
|
62
|
+
will have to be installed for your library separately. **Seriously though:** – we do realize
|
63
|
+
that Measurometer is meant to be a diamond dependency and will therefore commit to maintaining semver.
|
64
|
+
|
65
|
+
## Usage in libraries
|
66
|
+
|
67
|
+
When executing a semantically meaningful block of code, name the block using your library's name as a prefix,
|
68
|
+
and run the block wrapped with `Measurometer.instrument()`
|
69
|
+
|
70
|
+
```
|
71
|
+
Measurometer.instrument('profligator.ideate') do
|
72
|
+
options_offered_to_client = Thinkfluencer.ideate(the_creative)
|
73
|
+
Measurometer.increment_counter('profligator.num_ideations', 1)x
|
74
|
+
Measurometer.add_distribution_value('profligator.options_offered_to_client_per_ideation', options_offered_to_client.length)
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
Note that it is prudent to make your library provide **either** Measurometer instrumentation **or** ActiveSupport::Notifications,
|
79
|
+
but **not both** - unless your metrics collection driver/system can deal with that cleanly.
|
80
|
+
|
81
|
+
## Usage in applications
|
82
|
+
|
83
|
+
If one or more of the libraries you are using are Measurometer-instrumented, all you have to do is
|
84
|
+
connect a driver. The driver must respond to the same methods as the Measurometer module, and has to
|
85
|
+
be explicitly added to the set of Measurometer drivers to source instrumentation, like so:
|
86
|
+
|
87
|
+
```
|
88
|
+
Measurometer.drivers << Appsignal
|
89
|
+
```
|
90
|
+
|
91
|
+
Appsignal, since Measurometer's API copies it's instrumentaiton API, can be used as-is,
|
92
|
+
without any adapters.
|
93
|
+
|
94
|
+
## Development
|
95
|
+
|
96
|
+
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.
|
97
|
+
|
98
|
+
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).
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/WeTransfer/measurometer.
|
103
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'measurometer'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Measurometer
|
2
|
+
class StatsdDriver
|
3
|
+
attr_accessor :statsd_client
|
4
|
+
|
5
|
+
MONOTONIC_AVAILABLE = defined?(Process::CLOCK_MONOTONIC)
|
6
|
+
|
7
|
+
def initialize(ruby_statsd_client)
|
8
|
+
@statsd_client = ruby_statsd_client
|
9
|
+
end
|
10
|
+
|
11
|
+
def instrument(action_name)
|
12
|
+
s = gettime
|
13
|
+
yield.tap do
|
14
|
+
delta_fractional_s = gettime - s
|
15
|
+
millis = (delta_fractional_s * 1000).to_i
|
16
|
+
@statsd_client.timing(action_name, millis)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def increment_counter(counter_name, by)
|
21
|
+
@statsd_client.increment(counter_name, by)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_distribution_value(key_path, value)
|
25
|
+
@statsd_client.count(key_path, value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_gauge(gauge_name, value)
|
29
|
+
@statsd_client.gauge(gauge_name, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def gettime
|
35
|
+
MONOTONIC_AVAILABLE ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/measurometer.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'measurometer/version'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Measurometer
|
5
|
+
@drivers = Set.new
|
6
|
+
autoload :StatsdDriver, 'measurometer/statsd_driver'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Permits adding instrumentation drivers. Measurometer is 1-1 API
|
10
|
+
# compatible with Appsignal, which we use a lot. So to magically
|
11
|
+
# obtain all Appsignal instrumentation, add the Appsignal module
|
12
|
+
# as a driver.
|
13
|
+
#
|
14
|
+
# Measurometer.drivers << Appsignal
|
15
|
+
#
|
16
|
+
# A driver must be reentrant and thread-safe - it should be possible
|
17
|
+
# to have multiple `instrument` calls open from different threads at the
|
18
|
+
# same time.
|
19
|
+
#
|
20
|
+
# The driver must support the same interface as the Measurometer class
|
21
|
+
# itself, minus the `drivers` method.
|
22
|
+
#
|
23
|
+
# Note that this method does not return a copy of the drivers, it returns
|
24
|
+
# the mutable Set itself
|
25
|
+
#
|
26
|
+
# @return [Set]
|
27
|
+
def drivers
|
28
|
+
@drivers
|
29
|
+
end
|
30
|
+
|
31
|
+
# Runs a given block within a cascade of `instrument` blocks of all the
|
32
|
+
# added drivers.
|
33
|
+
#
|
34
|
+
# Measurometer.instrument('do_foo') { compute! }
|
35
|
+
#
|
36
|
+
# unfolds to
|
37
|
+
#
|
38
|
+
# Appsignal.instrument('do_foo') do
|
39
|
+
# StatsdDriver#instrument('do_foo') do
|
40
|
+
# compute!
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param block_name[String] under which path to push the metric
|
45
|
+
# @param blk[#call] the block to instrument
|
46
|
+
# @return [Object] the return value of &blk
|
47
|
+
def instrument(block_name, &blk)
|
48
|
+
return yield if @drivers.empty? # The block wrapping business is not free
|
49
|
+
blk_return_value = nil
|
50
|
+
blk_with_capture = -> { blk_return_value = blk.call }
|
51
|
+
@drivers.inject(blk_with_capture) { |outer_block, driver|
|
52
|
+
-> {
|
53
|
+
driver.instrument(block_name, &outer_block)
|
54
|
+
}
|
55
|
+
}.call
|
56
|
+
blk_return_value
|
57
|
+
end
|
58
|
+
|
59
|
+
# Adds a distribution value (sample) under a given path
|
60
|
+
#
|
61
|
+
# @param value_path[String] under which path to push the metric
|
62
|
+
# @param value[Numeric] distribution value
|
63
|
+
# @return nil
|
64
|
+
def add_distribution_value(value_path, value)
|
65
|
+
@drivers.each { |d| d.add_distribution_value(value_path, value) }
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# Increment a named counter under a given path
|
70
|
+
#
|
71
|
+
# @param counter_path[String] under which path to push the metric
|
72
|
+
# @param by[Integer] the counter increment to apply
|
73
|
+
# @return nil
|
74
|
+
def increment_counter(counter_path, by)
|
75
|
+
@drivers.each { |d| d.increment_counter(counter_path, by) }
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set a global single named value (gauge)
|
80
|
+
#
|
81
|
+
# @param gauge_name[String] under which path to push the metric
|
82
|
+
# @param value[Integer] the absolute value of the gauge
|
83
|
+
# @return nil
|
84
|
+
def set_gauge(gauge_name, value)
|
85
|
+
@drivers.each { |d| d.set_gauge(gauge_name, value) }
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'measurometer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'measurometer'
|
8
|
+
spec.version = Measurometer::VERSION
|
9
|
+
spec.authors = ['Julik Tarkhanov']
|
10
|
+
spec.email = ['me@julik.nl']
|
11
|
+
|
12
|
+
spec.summary = 'Minimum viable API for instrumentation in libraries'
|
13
|
+
spec.description = 'Minimum viable API for instrumentation in libraries. Source metrics from your libraries to Measurometer, pick them up on the other end in the application, centrally.'
|
14
|
+
spec.homepage = 'https://github.com/WeTransfer/measurometer'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(/\.png$/)
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
spec.add_development_dependency 'wetransfer_style', '0.5.0'
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Measurometer::StatsdDriver' do
|
4
|
+
after(:each) do
|
5
|
+
Measurometer.drivers.clear
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'passes metrics to the contained Statsd client' do
|
9
|
+
statsd = spy('Statsd')
|
10
|
+
Measurometer.drivers << Measurometer::StatsdDriver.new(statsd)
|
11
|
+
|
12
|
+
Measurometer.instrument('some_block.x') do
|
13
|
+
sleep 0.21
|
14
|
+
end
|
15
|
+
|
16
|
+
Measurometer.set_gauge('app.some_gauge', 42)
|
17
|
+
Measurometer.increment_counter('app.some_counter', 2)
|
18
|
+
Measurometer.add_distribution_value('app.some_sample', 42)
|
19
|
+
|
20
|
+
expect(statsd).to have_received(:timing) {|block_name, timing_millis|
|
21
|
+
expect(block_name).to eq('some_block.x')
|
22
|
+
expect(timing_millis).to be_within(20).of(200)
|
23
|
+
}
|
24
|
+
|
25
|
+
expect(statsd).to have_received(:increment).with('app.some_counter', 2)
|
26
|
+
expect(statsd).to have_received(:count).with('app.some_sample', 42)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Measurometer do
|
4
|
+
RSpec::Matchers.define :include_counter_or_measurement_named do |named|
|
5
|
+
match do |actual|
|
6
|
+
actual.any? do |e|
|
7
|
+
e[0] == named && e[1] > 0
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has a version number' do
|
13
|
+
expect(Measurometer::VERSION).not_to be nil
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.drivers' do
|
17
|
+
before(:each) { Measurometer.drivers.clear }
|
18
|
+
after(:each) { Measurometer.drivers.clear }
|
19
|
+
let(:driver) { Object.new }
|
20
|
+
|
21
|
+
it 'allows adding and removing a driver' do
|
22
|
+
expect(Measurometer.drivers).not_to include(driver)
|
23
|
+
|
24
|
+
Measurometer.drivers << driver
|
25
|
+
expect(Measurometer.drivers).to include(driver)
|
26
|
+
|
27
|
+
Measurometer.drivers.delete(driver)
|
28
|
+
expect(Measurometer.drivers).not_to include(driver)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not add the same driver twice' do
|
32
|
+
Measurometer.drivers.clear
|
33
|
+
3.times { Measurometer.drivers << driver }
|
34
|
+
expect(Measurometer.drivers.length).to eq(1)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.instrument' do
|
39
|
+
it 'preserves the return value of the block even if one of the drivers swallows it' do
|
40
|
+
bad_driver = Object.new
|
41
|
+
def bad_driver.instrument(_blk)
|
42
|
+
yield
|
43
|
+
nil # Be nasty
|
44
|
+
end
|
45
|
+
|
46
|
+
Measurometer.drivers << bad_driver
|
47
|
+
instrument_result = Measurometer.instrument('foo') do
|
48
|
+
:block_result
|
49
|
+
end
|
50
|
+
Measurometer.drivers.delete(bad_driver)
|
51
|
+
|
52
|
+
expect(instrument_result).to eq(:block_result)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'sources instrumentation to a driver' do
|
57
|
+
driver_class = Class.new do
|
58
|
+
attr_accessor :timings, :counters, :distributions, :gauges
|
59
|
+
def instrument(block_name)
|
60
|
+
s = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
61
|
+
yield.tap do
|
62
|
+
delta = Process.clock_gettime(Process::CLOCK_MONOTONIC) - s
|
63
|
+
@timings ||= []
|
64
|
+
@timings << [block_name, delta * 1000]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_distribution_value(value_path, value)
|
69
|
+
@distributions ||= []
|
70
|
+
@distributions << [value_path, value]
|
71
|
+
end
|
72
|
+
|
73
|
+
def increment_counter(value_path, value)
|
74
|
+
@counters ||= []
|
75
|
+
@counters << [value_path, value]
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_gauge(value_path, value)
|
79
|
+
@gauges ||= []
|
80
|
+
@gauges << [value_path, value]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
instrumenter = driver_class.new
|
85
|
+
Measurometer.drivers << instrumenter
|
86
|
+
|
87
|
+
Measurometer.instrument('something_amazing.foo') do
|
88
|
+
sleep(rand / 4)
|
89
|
+
Measurometer.instrument('something_amazing.subtask') do
|
90
|
+
sleep(rand / 9)
|
91
|
+
Measurometer.increment_counter('something_amazing.subtasks_performed', 1)
|
92
|
+
end
|
93
|
+
Measurometer.instrument('something_amazing.another_subtask') do
|
94
|
+
sd = rand / 9
|
95
|
+
sleep(sd)
|
96
|
+
Measurometer.add_distribution_value('something_amazing.another_subtask.sleep_durations', sd)
|
97
|
+
end
|
98
|
+
Measurometer.set_gauge('some.gauge', 42)
|
99
|
+
:task_finished
|
100
|
+
end
|
101
|
+
|
102
|
+
Measurometer.drivers.delete(instrumenter)
|
103
|
+
|
104
|
+
expect(instrumenter.counters).to include_counter_or_measurement_named('something_amazing.subtasks_performed')
|
105
|
+
expect(instrumenter.distributions).to include_counter_or_measurement_named('something_amazing.another_subtask.sleep_durations')
|
106
|
+
expect(instrumenter.timings).to include_counter_or_measurement_named('something_amazing.subtask')
|
107
|
+
expect(instrumenter.timings).to include_counter_or_measurement_named('something_amazing.another_subtask')
|
108
|
+
expect(instrumenter.timings).to include_counter_or_measurement_named('something_amazing.foo')
|
109
|
+
expect(instrumenter.gauges).to include_counter_or_measurement_named('some.gauge')
|
110
|
+
end
|
111
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'measurometer'
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
# Enable flags like --only-failures and --next-failure
|
6
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
7
|
+
|
8
|
+
config.expect_with :rspec do |c|
|
9
|
+
c.syntax = :expect
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: measurometer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julik Tarkhanov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: wetransfer_style
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.5.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.5.0
|
69
|
+
description: Minimum viable API for instrumentation in libraries. Source metrics from
|
70
|
+
your libraries to Measurometer, pick them up on the other end in the application,
|
71
|
+
centrally.
|
72
|
+
email:
|
73
|
+
- me@julik.nl
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".gitignore"
|
79
|
+
- ".rspec"
|
80
|
+
- ".rubocop.yml"
|
81
|
+
- ".travis.yml"
|
82
|
+
- CHANGELOG.md
|
83
|
+
- CODE_OF_CONDUCT.md
|
84
|
+
- Gemfile
|
85
|
+
- LICENSE.txt
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- bin/console
|
89
|
+
- bin/setup
|
90
|
+
- lib/measurometer.rb
|
91
|
+
- lib/measurometer/statsd_driver.rb
|
92
|
+
- lib/measurometer/version.rb
|
93
|
+
- measurometer.gemspec
|
94
|
+
- spec/measurometer/statsd_driver_spec.rb
|
95
|
+
- spec/measurometer_spec.rb
|
96
|
+
- spec/spec_helper.rb
|
97
|
+
homepage: https://github.com/WeTransfer/measurometer
|
98
|
+
licenses: []
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.5.2
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Minimum viable API for instrumentation in libraries
|
120
|
+
test_files: []
|