instrument_all_the_things 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +14 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +95 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +7 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.lock +74 -0
  10. data/README.md +397 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +11 -0
  13. data/bin/setup +8 -0
  14. data/instrument_all_the_things.gemspec +42 -0
  15. data/lib/instrument_all_the_things.rb +70 -0
  16. data/lib/instrument_all_the_things/clients/stat_reporter/datadog.rb +12 -0
  17. data/lib/instrument_all_the_things/clients/tracer/blackhole.rb +22 -0
  18. data/lib/instrument_all_the_things/context.rb +23 -0
  19. data/lib/instrument_all_the_things/helpers.rb +55 -0
  20. data/lib/instrument_all_the_things/instrumentors/all.rb +6 -0
  21. data/lib/instrument_all_the_things/instrumentors/error_logging.rb +48 -0
  22. data/lib/instrument_all_the_things/instrumentors/execution_count_and_timing.rb +21 -0
  23. data/lib/instrument_all_the_things/instrumentors/gc_stats.rb +49 -0
  24. data/lib/instrument_all_the_things/instrumentors/tracing.rb +30 -0
  25. data/lib/instrument_all_the_things/method_instrumentor.rb +57 -0
  26. data/lib/instrument_all_the_things/method_proxy.rb +77 -0
  27. data/lib/instrument_all_the_things/testing/rspec_matchers.rb +97 -0
  28. data/lib/instrument_all_the_things/testing/stat_tracker.rb +43 -0
  29. data/lib/instrument_all_the_things/testing/trace_tracker.rb +25 -0
  30. data/lib/instrument_all_the_things/version.rb +5 -0
  31. data/logo.jpg +0 -0
  32. data/vendor/cache/ast-2.4.0.gem +0 -0
  33. data/vendor/cache/benchmark-ips-2.7.2.gem +0 -0
  34. data/vendor/cache/coderay-1.1.2.gem +0 -0
  35. data/vendor/cache/ddtrace-0.34.0.gem +0 -0
  36. data/vendor/cache/diff-lcs-1.3.gem +0 -0
  37. data/vendor/cache/docile-1.3.2.gem +0 -0
  38. data/vendor/cache/dogstatsd-ruby-4.7.0.gem +0 -0
  39. data/vendor/cache/jaro_winkler-1.5.4.gem +0 -0
  40. data/vendor/cache/method_source-0.9.2.gem +0 -0
  41. data/vendor/cache/msgpack-1.3.3.gem +0 -0
  42. data/vendor/cache/parallel-1.19.1.gem +0 -0
  43. data/vendor/cache/parser-2.7.0.2.gem +0 -0
  44. data/vendor/cache/pry-0.12.2.gem +0 -0
  45. data/vendor/cache/rainbow-3.0.0.gem +0 -0
  46. data/vendor/cache/rake-10.5.0.gem +0 -0
  47. data/vendor/cache/rexml-3.2.4.gem +0 -0
  48. data/vendor/cache/rspec-3.9.0.gem +0 -0
  49. data/vendor/cache/rspec-core-3.9.1.gem +0 -0
  50. data/vendor/cache/rspec-expectations-3.9.0.gem +0 -0
  51. data/vendor/cache/rspec-mocks-3.9.1.gem +0 -0
  52. data/vendor/cache/rspec-support-3.9.2.gem +0 -0
  53. data/vendor/cache/rubocop-0.80.0.gem +0 -0
  54. data/vendor/cache/ruby-progressbar-1.10.1.gem +0 -0
  55. data/vendor/cache/simplecov-0.18.1.gem +0 -0
  56. data/vendor/cache/simplecov-html-0.11.0.gem +0 -0
  57. data/vendor/cache/unicode-display_width-1.6.1.gem +0 -0
  58. metadata +227 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8233c84f173a53f91f896e019fa3ad70cbf0834cc43b3291c341c633099cc13b
4
+ data.tar.gz: 71cb7736c90caa59f66e7666eb20d8b3c0a71510c0e0dd2b740ee6f3397174d0
5
+ SHA512:
6
+ metadata.gz: 6614358e22371b877b9b79f63807906dcc08560a8585dcaeb985599ef889ef900c60a911ef15a1554749da0d84f0bcc92c4cffa4bd38b7b8ef5c3d0e8dbd3704
7
+ data.tar.gz: 8a19bb27f82820e1bb8695355e6e8761c02e3237e0a33c441831a57969d21a7192c96e1ccf92c35fc008d03f075227d1306583ccdbaea352ea9e2dd6a365a063
@@ -0,0 +1,14 @@
1
+ kind: pipeline
2
+ name: default
3
+
4
+ steps:
5
+ - name: test-ruby
6
+ image: getterminus/ruby-ci-image:2.6-je-20190205
7
+ group: bundler
8
+ volumes:
9
+ - name: bundledir
10
+ path: /usr/local/bundle
11
+ commands:
12
+ - bundle check --path=/usr/local/bundle || bundle --local --path=/usr/local/bundle
13
+ - bundle exec rspec
14
+
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .DS_Store
13
+ .vscode
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,95 @@
1
+ AllCops:
2
+ Exclude:
3
+ - Makefile
4
+ - vendor/**/*
5
+ - bin/*
6
+ - Guardfile
7
+
8
+ Naming/AccessorMethodName:
9
+ Exclude:
10
+ - app/controllers/**/*
11
+
12
+ Layout/EndOfLine:
13
+ Enabled: false
14
+
15
+ Style/DateTime:
16
+ Enabled: false
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+
21
+ Lint/Debugger:
22
+ Enabled: true
23
+
24
+ Style/FrozenStringLiteralComment:
25
+ Enabled: true
26
+ EnforcedStyle: always
27
+
28
+ Style/TrailingCommaInHashLiteral:
29
+ Enabled: true
30
+ EnforcedStyleForMultiline: comma
31
+
32
+ Style/TrailingCommaInArrayLiteral:
33
+ Enabled: true
34
+ EnforcedStyleForMultiline: comma
35
+
36
+ Style/TrailingCommaInArguments:
37
+ Enabled: true
38
+ EnforcedStyleForMultiline: comma
39
+
40
+ Lint/UnusedMethodArgument:
41
+ AllowUnusedKeywordArguments: true
42
+
43
+ Layout/LineLength:
44
+ Enabled: true
45
+ Max: 280
46
+ IgnoreCopDirectives: true
47
+ IgnoredPatterns: ['\A#', '\A\s*sig { .* }\Z']
48
+ Exclude:
49
+ - '**/*_pb.rb'
50
+
51
+ Metrics/AbcSize:
52
+ Enabled: true
53
+ Max: 46
54
+
55
+ Metrics/CyclomaticComplexity:
56
+ Max: 7
57
+
58
+ Metrics/MethodLength:
59
+ Enabled: true
60
+ Max: 32
61
+
62
+ Layout/ParameterAlignment:
63
+ Enabled: true
64
+ EnforcedStyle: with_fixed_indentation
65
+
66
+ Naming/MethodParameterName:
67
+ Enabled: true
68
+ AllowedNames: ['io', 'id', 'to', 'by', 'on', 'in', 'at', '_'] # Defaults + _
69
+
70
+ Layout/MultilineMethodCallIndentation:
71
+ Enabled: true
72
+ EnforcedStyle: indented
73
+
74
+ Style/ParallelAssignment:
75
+ Enabled: true
76
+
77
+ Metrics/ClassLength:
78
+ Max: 240
79
+
80
+ Metrics/BlockLength:
81
+ Max: 30
82
+ Exclude:
83
+ - spec/**/*.rb
84
+ - '**/*_pb.rb'
85
+
86
+ Style/BlockDelimiters:
87
+ Enabled: true
88
+ BracesRequiredMethods: ['expect']
89
+
90
+ Metrics/ParameterLists:
91
+ Max: 6
92
+
93
+ Lint/AmbiguousBlockAssociation:
94
+ Exclude:
95
+ - spec/**/*.rb
@@ -0,0 +1 @@
1
+ 2.6.1
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.1
7
+ before_install: gem install bundler -v 2.0.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in instrument_all_the_things.gemspec
4
+ gemspec
@@ -0,0 +1,74 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ instrument_all_the_things (1.0.4)
5
+ ddtrace
6
+ dogstatsd-ruby
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.0)
12
+ benchmark-ips (2.7.2)
13
+ coderay (1.1.2)
14
+ ddtrace (0.34.0)
15
+ msgpack
16
+ diff-lcs (1.3)
17
+ docile (1.3.2)
18
+ dogstatsd-ruby (4.7.0)
19
+ jaro_winkler (1.5.4)
20
+ method_source (0.9.2)
21
+ msgpack (1.3.3)
22
+ parallel (1.19.1)
23
+ parser (2.7.0.2)
24
+ ast (~> 2.4.0)
25
+ pry (0.12.2)
26
+ coderay (~> 1.1.0)
27
+ method_source (~> 0.9.0)
28
+ rainbow (3.0.0)
29
+ rake (10.5.0)
30
+ rexml (3.2.4)
31
+ rspec (3.9.0)
32
+ rspec-core (~> 3.9.0)
33
+ rspec-expectations (~> 3.9.0)
34
+ rspec-mocks (~> 3.9.0)
35
+ rspec-core (3.9.1)
36
+ rspec-support (~> 3.9.1)
37
+ rspec-expectations (3.9.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.9.0)
40
+ rspec-mocks (3.9.1)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.9.0)
43
+ rspec-support (3.9.2)
44
+ rubocop (0.80.0)
45
+ jaro_winkler (~> 1.5.1)
46
+ parallel (~> 1.10)
47
+ parser (>= 2.7.0.1)
48
+ rainbow (>= 2.2.2, < 4.0)
49
+ rexml
50
+ ruby-progressbar (~> 1.7)
51
+ unicode-display_width (>= 1.4.0, < 1.7)
52
+ ruby-progressbar (1.10.1)
53
+ simplecov (0.18.1)
54
+ docile (~> 1.1)
55
+ simplecov-html (~> 0.11.0)
56
+ simplecov-html (0.11.0)
57
+ unicode-display_width (1.6.1)
58
+
59
+ PLATFORMS
60
+ ruby
61
+ x86_64-darwin-18
62
+
63
+ DEPENDENCIES
64
+ benchmark-ips
65
+ bundler (~> 2.0)
66
+ instrument_all_the_things!
67
+ pry
68
+ rake (~> 10.0)
69
+ rspec (~> 3.0)
70
+ rubocop
71
+ simplecov
72
+
73
+ BUNDLED WITH
74
+ 2.1.4
@@ -0,0 +1,397 @@
1
+ Visibility into your application is one of the most critical parts of software development. At best, visibility is typically an afterthought and this is a problem. So what do you do?
2
+
3
+ ![Instrument all the things](./logo.jpg?raw=true)
4
+
5
+ # InstrumentAllTheThings
6
+
7
+ At Terminus we use DataDog for our application visibility. InstrumentAllTheThings provides simple ways to quickly and unobtrusively add detailed instrumentation to Datadog metrics and Datadog APM.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'instrument_all_the_things'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install instrument_all_the_things
24
+
25
+ ## Usage
26
+ *Note:* For convenience the InstrumentAllTheThings constant is aliased to IATT unless the constant is already defined.
27
+
28
+
29
+ ## Method Instrumentation
30
+ Method instrumentation both in APM as well as in simple counts & timings is the bread and butter of visibility, and it
31
+ is trivial to implement with IATT.
32
+
33
+ Each measured metric may be individually disabled, and some may be provided additional configuration. All measurments
34
+ default to on, unless otherwise specified. You may disable the specified measurement by providing a falsy value to the
35
+ configuration key when calling `instrument`
36
+
37
+ *Example*
38
+ ```ruby
39
+ class Foo
40
+ include InstrumentAllTheThings
41
+
42
+ instrument config_key: {configuration_option: 123}
43
+ def foo
44
+ end
45
+ end
46
+ ```
47
+ ### Garbage Collection Stats
48
+ _Configuration Key `gc_stats`_
49
+
50
+ Collects the difference between the specified keys during the execution of the method.
51
+
52
+ Stat diffs are added to the active trace span as a tag, and a stat is emitted with the following format
53
+
54
+ `klass_name.(instance|class)_methods.(stat_name)_change`
55
+
56
+ #### Description of default stats
57
+ _GC Stats are not thread local, if your app is multi threaded other threads may be contributing to these stats_
58
+ | Option | Description
59
+ | ----- | ----
60
+ | total_allocated_pages | Total number of memory pages owned by this ruby process. Mature processes tend to see a slowdown in page allocations
61
+ | total_allocated_objects | Total number of objects which have not been garbage collected yet
62
+ | count | Total number of GC runs during this method's exuection
63
+
64
+ #### Options
65
+ | Option | Description | Default
66
+ | ----- | ---- | -----
67
+ | diffed_stats | Stats to diff and record | [:total_allocated_pages, :total_allocated_objects, :count]
68
+
69
+ ### Error Logging
70
+ _Configuration Key `log_errors`_
71
+
72
+ When set to a non falsy value all errors raised in a span will be logged to the configured IATT logger and re-emitted.
73
+ The first traces span where they are seen logs them only, they will not be re-logged by IATT at any future level
74
+
75
+ By default call stacks are logged without all gem paths (as defined by the `Bundler.bundle_path`)
76
+
77
+ | Option | Description | Default
78
+ | ----- | ---- | -----
79
+ | rescue_class | The parent error which should be caught and logged | StandardError
80
+ | exclude_bundle_path | If truthy, remove all entries from the error backtrace which are in the bundle path | true
81
+
82
+ ### Tracing
83
+ _Configuration Key `trace`_
84
+
85
+ When set to a non falsy value, a span for this method will be created. The defaults are listed below. This hash will
86
+ also be passed to the DataDog tracer, and their [options](https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#manual-instrumentation) should also be understood.
87
+
88
+ | Option | Description | Default
89
+ | ----- | ---- | -----
90
+ | service | This is the value which shows up on the [first page of the APM screen](https://app.datadoghq.com/apm/home) this should be set at the entry point of your application or process | `nil`
91
+ | resource | How this method will show up when viewing the service in APM. | For instance methods `ClassName.method_name`<br>For class methods `ClassName#method_name`
92
+ | span_name | You probably don't want to change this | `method.execution`
93
+ | span_type | See DD Docs. | `nil`
94
+ | tags | Set of tags to be added to the span, expected to be a hash | {}
95
+
96
+ #### Dynamic Tags
97
+
98
+ If a trace's tags need to reference either an instance variable or a parameter to the method being traced, you must pass in a proc for the tag value.
99
+
100
+ For instance variables, this will look like this:
101
+ ```ruby
102
+ instrument trace: { tags: [-> { "some_stat:#{some_instance_var}" }]}
103
+ def my_instance_method
104
+ ...
105
+ end
106
+ ```
107
+
108
+ For parameter references, your proc will need to to have a parameter of `args` or `kwargs` (or both), depending on if the parameter you need to reference is a normal parameter or a keyword parameter. Examples:
109
+
110
+ ```ruby
111
+ instrument trace: { tags: [->(args) { "log_args:#{args[0]}" }]}
112
+ def my_instance_method(my_var)
113
+ ...
114
+ end
115
+
116
+ instrument trace: { tags: [->(kwargs) { "log_args:#{kwargs[:my_arg]}" }]}
117
+ def my_instance_method(my_arg:)
118
+ ...
119
+ end
120
+ ```
121
+
122
+ ## Testing Support
123
+
124
+ You can setup your test environment by running the following setup:
125
+
126
+ ```ruby
127
+ require 'instrument_all_the_things/testing/stat_tracker'
128
+ require 'instrument_all_the_things/testing/trace_tracker'
129
+ require 'instrument_all_the_things/testing/rspec_matchers'
130
+
131
+ Datadog.configure do |c|
132
+ c.tracer transport_options: proc { |t|
133
+ t.adapter :test, IATT::Testing::TraceTracker.new
134
+ }
135
+ end
136
+
137
+ IATT.stat_reporter = IATT::Testing::StatTracker.new
138
+
139
+ RSpec.configure do |config|
140
+ config.include InstrumentAllTheThings::Testing::RSpecMatchers
141
+ config.before(:each) do
142
+ IATT::Testing::TraceTracker.tracker.reset!
143
+ IATT.stat_reporter.reset!
144
+ end
145
+ end
146
+ ```
147
+
148
+ This injects middleware and in the StatsD interface as well as in the Tracer output. By doing this you can start using
149
+ some awesome rspec helpers like so:
150
+
151
+ ```ruby
152
+ let(:klass) do
153
+ Class.new do
154
+ include InstrumentAllTheThings::Helpers
155
+
156
+ instrument
157
+ def foo
158
+ end
159
+
160
+ def self.inspect
161
+ 'KlassName'
162
+ end
163
+ end
164
+ end
165
+
166
+ it 'traces' do
167
+ expect {
168
+ klass.new.foo
169
+
170
+ # Datadog writes trace to the wire and to the test harness asynchronously
171
+ # This helper is provided to force the flush before expectations are stated
172
+ flush_traces
173
+ }.to change{
174
+ emitted_spans(
175
+ filtered_by: {resource: 'KlassName.foo'}
176
+ )
177
+ }.by(1)
178
+ end
179
+ end
180
+ ```
181
+
182
+ ## Stat Transmission
183
+ InstrumentAllTheThings provides no real functionality on top of the build in Datadog statsd utility. The primary goal
184
+ of the IATT library is to allow for easy testing. The following method are provided on the InstrumentAllTheThings module.
185
+
186
+ ### Increment
187
+ Examples:
188
+ ```ruby
189
+ expect{
190
+ InstrumentAllTheThings.increment('my.counter')
191
+ }.to change{ counter_value('my.counter') }.from(0).to(1)
192
+
193
+ expect{
194
+ InstrumentAllTheThings.increment('my.counter', by: 5)
195
+ }.to change { counter_value('my.counter') }.from(0).to(5)
196
+
197
+ # You can also filter by tags
198
+ expect {
199
+ InstrumentAllTheThings.increment('my.counter', by: 3, tags: ['a:b', 'foo:bar'])
200
+ InstrumentAllTheThings.increment('my.counter', by: 2, tags: ['a:b', 'foo:baz'])
201
+ }.to change { counter_value('my.counter') }.from(0).to(5)
202
+ .and change { counter_value('my.counter', with_tags: ['a:b']) }.from(0).to(5)
203
+ .and change { counter_value('my.counter', with_tags: ['foo:bar']) }.from(0).to(3)
204
+ .and change { counter_value('my.counter', with_tags: ['foo:baz']) }.from(0).to(2)
205
+ ```
206
+
207
+ ### Decrement
208
+ See Increment for all examples
209
+ ```ruby
210
+ expect{
211
+ InstrumentAllTheThings.increment('my.counter')
212
+ }.to change{ counter_value('my.counter') }.from(0).to(-1)
213
+ ```
214
+
215
+ ### Count
216
+ Count underlies both increment and decrement, and works in a similar way.
217
+
218
+ ```ruby
219
+ expect {
220
+ InstrumentAllTheThings.count('my.counter', 3, tags: ['a:b', 'foo:bar'])
221
+ InstrumentAllTheThings.count('my.counter', 2, tags: ['a:b', 'foo:baz'])
222
+ }.to change { counter_value('my.counter') }.from(0).to(5)
223
+ .and change { counter_value('my.counter', with_tags: ['a:b']) }.from(0).to(5)
224
+ .and change { counter_value('my.counter', with_tags: ['foo:bar']) }.from(0).to(3)
225
+ .and change { counter_value('my.counter', with_tags: ['foo:baz']) }.from(0).to(2)
226
+ ```
227
+
228
+ ### Gauge
229
+ ```ruby
230
+ expect {
231
+ InstrumentAllTheThings.gauge('my.gauge', 1)
232
+ InstrumentAllTheThings.gauge('my.gauge', 2)
233
+ }.to change { gauge_value('my.gauge') }.from(nil).to(2)
234
+
235
+ expect {
236
+ InstrumentAllTheThings.gauge('my.gauge', 3, tags: ['a:b', 'foo:bar'])
237
+ InstrumentAllTheThings.gauge('my.gauge', 1, tags: ['a:b', 'foo:bar'])
238
+ InstrumentAllTheThings.gauge('my.gauge', 2, tags: ['a:b', 'foo:baz'])
239
+ InstrumentAllTheThings.gauge('my.gauge', 7, tags: ['a:b'])
240
+ }.to change { gauge_value('my.gauge') }.to(7)
241
+ .and change { gauge_value('my.gauge', with_tags: ['a:b']) }.to(7)
242
+ .and change { gauge_value('my.gauge', with_tags: ['foo:bar']) }.to(1)
243
+ .and change { gauge_value('my.gauge', with_tags: ['foo:baz']) }.to(2)
244
+ ```
245
+
246
+ ### Set
247
+ ```ruby
248
+ expect {
249
+ InstrumentAllTheThings.set('my.set', 1)
250
+ InstrumentAllTheThings.set('my.set', 2)
251
+ }.to change { set_value('my.set') }.from(0).to(2)
252
+
253
+ expect {
254
+ InstrumentAllTheThings.set('my.set', 3, tags: ['a:b', 'foo:bar'])
255
+ InstrumentAllTheThings.set('my.set', 3, tags: ['a:b', 'foo:bar'])
256
+ InstrumentAllTheThings.set('my.set', 5, tags: ['a:b', 'foo:bar'])
257
+ InstrumentAllTheThings.set('my.set', 6, tags: ['a:b', 'foo:baz'])
258
+ InstrumentAllTheThings.set('my.set', 9, tags: ['a:b'])
259
+ }.to change { set_value('my.set') }.to(4)
260
+ .and change { set_value('my.set', with_tags: ['a:b']) }.to(4)
261
+ .and change { set_value('my.set', with_tags: ['foo:bar']) }.to(2)
262
+ .and change { set_value('my.set', with_tags: ['foo:baz']) }.to(1)
263
+ ```
264
+
265
+ ### Histogram
266
+ Histogram is a pesudo metric in Datadog based on the StatsD timing metric. InstrumentAllTheThings provides a way to
267
+ test the values emitted, not the statistical calculations derived.
268
+
269
+ ```ruby
270
+ expect {
271
+ InstrumentAllTheThings.histogram('my.histogram', 1)
272
+ InstrumentAllTheThings.histogram('my.histogram', 2)
273
+ }.to change { histogram_values('my.histogram') }.from([]).to([1, 2])
274
+
275
+ expect {
276
+ InstrumentAllTheThings.histogram('my.histogram', 3, tags: ['a:b', 'foo:bar'])
277
+ InstrumentAllTheThings.histogram('my.histogram', 5, tags: ['a:b', 'foo:bar'])
278
+ InstrumentAllTheThings.histogram('my.histogram', 6, tags: ['a:b', 'foo:baz'])
279
+ InstrumentAllTheThings.histogram('my.histogram', 9, tags: ['a:b'])
280
+ }.to change { histogram_values('my.histogram') }.to([3, 5, 6, 9])
281
+ .and change { histogram_values('my.histogram', with_tags: ['a:b']) }.to([3, 5, 6, 9])
282
+ .and change { histogram_values('my.histogram', with_tags: ['foo:bar']) }.to([3, 5])
283
+ .and change { histogram_values('my.histogram', with_tags: ['foo:baz']) }.to([6])
284
+ ```
285
+
286
+ ### Distribution
287
+ ```ruby
288
+ expect {
289
+ InstrumentAllTheThings.distribution('my.distribution', 1)
290
+ InstrumentAllTheThings.distribution('my.distribution', 2)
291
+ }.to change { distribution_values('my.distribution') }.from([]).to([1, 2])
292
+
293
+ expect {
294
+ InstrumentAllTheThings.distribution('my.distribution', 3, tags: ['a:b', 'foo:bar'])
295
+ InstrumentAllTheThings.distribution('my.distribution', 5, tags: ['a:b', 'foo:bar'])
296
+ InstrumentAllTheThings.distribution('my.distribution', 6, tags: ['a:b', 'foo:baz'])
297
+ InstrumentAllTheThings.distribution('my.distribution', 9, tags: ['a:b'])
298
+ }.to change { distribution_values('my.distribution') }.to([3, 5, 6, 9])
299
+ .and change { distribution_values('my.distribution', with_tags: ['a:b']) }.to([3, 5, 6, 9])
300
+ .and change { distribution_values('my.distribution', with_tags: ['foo:bar']) }.to([3, 5])
301
+ .and change { distribution_values('my.distribution', with_tags: ['foo:baz']) }.to([6])
302
+ ```
303
+
304
+ ### Timing
305
+ ```ruby
306
+ expect {
307
+ InstrumentAllTheThings.timing('my.timing', 1)
308
+ InstrumentAllTheThings.timing('my.timing', 2)
309
+ }.to change { timing_values('my.timing') }.from([]).to([1, 2])
310
+
311
+ expect {
312
+ InstrumentAllTheThings.timing('my.timing', 3, tags: ['a:b', 'foo:bar'])
313
+ InstrumentAllTheThings.timing('my.timing', 5, tags: ['a:b', 'foo:bar'])
314
+ InstrumentAllTheThings.timing('my.timing', 6, tags: ['a:b', 'foo:baz'])
315
+ InstrumentAllTheThings.timing('my.timing', 9, tags: ['a:b'])
316
+ }.to change { timing_values('my.timing') }.to([3, 5, 6, 9])
317
+ .and change { timing_values('my.timing', with_tags: ['a:b']) }.to([3, 5, 6, 9])
318
+ .and change { timing_values('my.timing', with_tags: ['foo:bar']) }.to([3, 5])
319
+ .and change { timing_values('my.timing', with_tags: ['foo:baz']) }.to([6])
320
+ ```
321
+
322
+ ### Time
323
+ The time helper wraps a block with reporting to the timing metric time. As such specs should leverage that spec helper.
324
+
325
+ ```ruby
326
+ expect {
327
+ InstrumentAllTheThings.time('my.time') {}
328
+ InstrumentAllTheThings.time('my.time') {}
329
+ }.to change { timing_values('my.time').length }.from(0).to(2)
330
+
331
+ expect {
332
+ InstrumentAllTheThings.time('my.time', tags: ['a:b', 'foo:bar']) {}
333
+ InstrumentAllTheThings.time('my.time', tags: ['a:b', 'foo:bar']) {}
334
+ InstrumentAllTheThings.time('my.time', tags: ['a:b', 'foo:baz']) {}
335
+ InstrumentAllTheThings.time('my.time', tags: ['a:b']) {}
336
+ }.to change { timing_values('my.time').length }.to(4)
337
+ .and change { timing_values('my.time', with_tags: ['a:b']).length }.to(4)
338
+ .and change { timing_values('my.time', with_tags: ['foo:bar']).length }.to(2)
339
+ .and change { timing_values('my.time', with_tags: ['foo:baz']).length }.to(1)
340
+ ```
341
+ ## Performance
342
+ Is it slow? That depends quite a bit on your perspective. We keep a benchmark as part of our specs and monitor the
343
+ overall cost of instrumentation. The current numbers look roughly like:
344
+
345
+ ```
346
+ Calculating -------------------------------------
347
+ uninstrumetned 17.038M (± 3.3%) i/s - 85.164M in 5.004312s
348
+ the_works 7.404k (±12.9%) i/s - 36.936k in 5.100630s
349
+ only_trace 27.968k (±12.7%) i/s - 139.209k in 5.061907s
350
+ only_error_logging 638.098k (± 4.6%) i/s - 3.231M in 5.075275s
351
+ only_gc_stats 12.930k (±13.2%) i/s - 63.865k in 5.070874s
352
+ only_execution_counts 9.847k (±11.1%) i/s - 49.088k in 5.073475s
353
+ ```
354
+
355
+ All of these calculations are performed against an empty method, which ruby executes in 0.000059ms. If we consider
356
+ this the baseline, running all of the included instrumentation in the non-error case has a cost of 0.135ms. If this
357
+ is an expensive calculation is directly proportional to the execution duration of the method. If you are tracing a 1ms
358
+ method, it represents a 13% overhead, but if you are tracing a 100ms method it is only 0.13%.
359
+
360
+ The moral of the story, don't blindly add the works to every method in your stack, instead choose methods who's output
361
+ will be diagnostically meaningful.
362
+
363
+ We are also always looking for patches with will reduce execution time and allocations of the instrumentation, so
364
+ please open some PRs!
365
+
366
+ ## Configuration
367
+ The configuration for IATT is available on the top level InstrumentAllTheThings module.
368
+
369
+ | Config Name | Description | Default
370
+ | ----------- | ----------- | -------
371
+ | stat_namespace | The string to add to all outbound stats (may not be changed after stat transmiter initialization) | `nil`
372
+ | logger | The logger used to report errors and info | If the constant `Rails` is set, use `Rails.logger`. <br>If `App` and it responds to `logger` use `App.logger`. Otherwise create a new `Logger` sent to STDOUT
373
+ | stat_reporter | The class which receives simple stats | If [Datadog::Statsd](https://github.com/DataDog/dogstatsd-ruby) is found, use that, otherwise the Blackhole client is used
374
+ | tracer | The instance of a tracer which will handle all traces | If `Datadog` is defined and responds to `tracer`, use the value returned by that. Otherwise use the Blackhole. [Gem](https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md)
375
+
376
+
377
+ ### Stats Reporters
378
+ #### Datadog
379
+ The default client if the constant `Datadog::Statsd` is found.
380
+
381
+ Initialized with environment variables
382
+ * `DATADOG_HOST` if set, otherwise `localhost`
383
+ * `DATADOG_POST` if set, otherwise `8125`
384
+
385
+ ### Tracers
386
+ #### Datadog
387
+ The default client if the constant `Datadog` is found and has a non-null value for `tracer`.
388
+
389
+ ## Development
390
+
391
+ 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.
392
+
393
+ 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).
394
+
395
+ ## Contributing
396
+
397
+ Bug reports and pull requests are welcome on GitHub at https://github.com/GetTerminus/instrument_all_the_things.