ddtelemetry 1.0.0a1 → 1.0.0a2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 410462bde4ad03647c35d4eb9706a513e8453e74d2218e9d60cc2f19b0a8f935
4
- data.tar.gz: 8392f095624dc46eb70c463fdbf9e004fad56c273670eb54e650ae044140d101
3
+ metadata.gz: 4fb967335678607cb86deb8c2b48446735a957230cb9349ce3858d21233d002b
4
+ data.tar.gz: eadc2b1b9a1737564dbd38abc68f4a59c320d5cdbeb13ed3d0df0fe7c5c3a3f3
5
5
  SHA512:
6
- metadata.gz: 8fc60025054933c5f5b07a0d72bb3c457c67016324ce9670d6eee63b952f8ab5cdc0fd4cd85bdc92dc5e82b0f44eff69272137c7b1087b5aba9e6321afeca423
7
- data.tar.gz: 34e45164626c7ec0b40382ac3c1fe4fd020a412c12b8903a93c34fac557210ef87045d69664de4a8198798d4be2d93433535ee3243da2613723efc28cf422663
6
+ metadata.gz: 1474fb4050d596b8f1fed72f80760e63e37f42a99327c9acdd57d7af5afe3139f096f8067437ca5d297c52ea3d4847117aeacb8ffc8d9b142f753f47495491e9
7
+ data.tar.gz: 6ac9b71a55dd640a652a762c6974a6260a7448cfc94d1a30cd4f23a340a75237b7fba521049228a5ebc0b3287837434da604224253f6df693d8162e2bba3262f
data/Gemfile CHANGED
@@ -10,6 +10,6 @@ group :devel do
10
10
  gem 'rake'
11
11
  gem 'rspec'
12
12
  gem 'rspec-its'
13
- gem 'rubocop', '~> 0.50'
13
+ gem 'rubocop', '~> 0.52'
14
14
  gem 'timecop', '~> 0.9'
15
15
  end
data/NEWS.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # DDTelemetry news
2
2
 
3
+ ## 1.0.0a2 (2017-12-18)
4
+
5
+ Changes:
6
+
7
+ * Many API changes to make usage simpler
8
+
3
9
  ## 1.0.0a1 (2017-12-02)
4
10
 
5
11
  Initial release.
data/README.md CHANGED
@@ -6,9 +6,7 @@
6
6
 
7
7
  # DDTelemetry
8
8
 
9
- _DDTelemetry_ provides in-process, non-timeseries telemetry for short-running Ruby processes.
10
-
11
- ⚠️ This project is **experimental** and should not be used in production yet.
9
+ _DDTelemetry_ is a Ruby library for recording and analysing measurements in short-running Ruby processes.
12
10
 
13
11
  If you are looking for a full-featured timeseries monitoring system, look no further than [Prometheus](https://prometheus.io/).
14
12
 
@@ -32,28 +30,30 @@ class Cache
32
30
  end
33
31
  ```
34
32
 
35
- To start instrumenting this code, require `ddtelemetry`, pass a telemetry instance into the constructor, and record some metrics:
33
+ To start instrumenting this code, require `ddtelemetry`, create a counter, and record some metrics:
36
34
 
37
35
  ```ruby
38
36
  require 'ddtelemetry'
39
37
 
40
38
  class Cache
41
- def initialize(telemetry)
42
- @telemetry = telemetry
39
+ attr_reader :counter
40
+
41
+ def initialize
43
42
  @map = {}
43
+ @counter = DDTelemetry::Counter.new
44
44
  end
45
45
 
46
46
  def []=(key, value)
47
- @telemetry.counter(:cache).increment(:set)
47
+ @counter.increment(:set)
48
48
 
49
49
  @map[key] = value
50
50
  end
51
51
 
52
52
  def [](key)
53
53
  if @map.key?(key)
54
- @telemetry.counter(:cache).increment([:get, :hit])
54
+ @counter.increment(:get_hit)
55
55
  else
56
- @telemetry.counter(:cache).increment([:get, :miss])
56
+ @counter.increment(:get_miss)
57
57
  end
58
58
 
59
59
  @map[key]
@@ -61,11 +61,10 @@ class Cache
61
61
  end
62
62
  ```
63
63
 
64
- Let’s construct a cache (with telemetry) and exercise it:
64
+ Let’s construct a cache and exercise it:
65
65
 
66
66
  ```ruby
67
- telemetry = DDTelemetry::Registry.new
68
- cache = Cache.new(telemetry)
67
+ cache = Cache.new
69
68
 
70
69
  cache['greeting']
71
70
  cache['greeting']
@@ -75,17 +74,26 @@ cache['greeting']
75
74
  cache['greeting']
76
75
  ```
77
76
 
78
- Finally, print the recorded telemetry values:
77
+ Finally, get the recorded telemetry values:
79
78
 
80
79
  ```ruby
81
- p telemetry.counter(:cache).value(:set)
82
- # => 1
80
+ cache.counter.get(:set) # => 1
81
+ cache.counter.get(:get_hit) # => 3
82
+ cache.counter.get(:get_miss) # => 2
83
+ ```
83
84
 
84
- p telemetry.counter(:cache).value([:get, :hit])
85
- # => 3
85
+ Or even print all stats:
86
86
 
87
- p telemetry.counter(:cache).value([:get, :miss])
88
- # => 2
87
+ ```ruby
88
+ puts cache.counter
89
+ ```
90
+
91
+ ```
92
+ │ count
93
+ ─────────┼──────
94
+ get_miss │ 2
95
+ set │ 1
96
+ get_hit │ 3
89
97
  ```
90
98
 
91
99
  ## Installation
@@ -106,7 +114,122 @@ Or install it yourself as:
106
114
 
107
115
  ## Usage
108
116
 
109
- TODO
117
+ _DDTelemetry_ provides two metric types:
118
+
119
+ * A **counter** is an integer metric that only ever increases. Examples: cache hits, number of files written, …
120
+
121
+ * A **summary** records observations, and provides functionality for describing the distribution of the observations through quantiles. Examples: outgoing request durations, size of written files, …
122
+
123
+ Each metric is recorded with a label, which is a free-form object that is useful to further refine the kind of data that is being recorded. For example:
124
+
125
+ ```ruby
126
+ cache_hits_counter.increment(:file_cache)
127
+ request_durations_summary.observe(:weather_api, 1.07)
128
+ ```
129
+
130
+ NOTE: Labels will likely change to become key-value pairs in a future version of DDTelemetry.
131
+
132
+ ### Counters
133
+
134
+ To create a counter, instantiate `DDTelemetry::Counter`:
135
+
136
+ ```ruby
137
+ counter = DDTelemetry::Counter.new
138
+ ```
139
+
140
+ To increment a counter, call `#increment` with a label:
141
+
142
+ ```ruby
143
+ counter.increment(:file_cache)
144
+ ```
145
+
146
+ To get the value for a certain label, use `#get`:
147
+
148
+ ```ruby
149
+ counter.get(:file_cache)
150
+ # => 1
151
+ ```
152
+
153
+ ### Summaries
154
+
155
+ To create a summary, instantiate `DDTelemetry::Summary`:
156
+
157
+ ```ruby
158
+ summary = DDTelemetry::Summary.new
159
+ ```
160
+
161
+ To observe a value, call `#observe` with a label, along with the value to observe:
162
+
163
+ ```ruby
164
+ summary.observe(:weather_api, 0.88)
165
+ summary.observe(:weather_api, 1.07)
166
+ summary.observe(:weather_api, 0.91)
167
+ ```
168
+
169
+ To get the list of observations for a certain label, use `#get`, which will return a `DDTelemetry::Stats` instance:
170
+
171
+ ```ruby
172
+ summary.get(:weather_api)
173
+ # => <DDTelemetry::Stats>
174
+ ```
175
+
176
+ The following methods are available on `DDTelemetry::Stats`:
177
+
178
+ * `#count`: returns the number of values
179
+ * `#sum`: returns the sum of all values
180
+ * `#avg`: returns the average of all values
181
+ * `#min`: returns the minimum value
182
+ * `#max`: returns the maximum value
183
+ * `#quantile(fraction)`: returns the quantile at the given fraction (0.0 – 1.0)
184
+
185
+ ### Printing metrics
186
+
187
+ To print a metric, use `#to_s`. For example:
188
+
189
+ ```ruby
190
+ summary = DDTelemetry::Summary.new
191
+
192
+ summary.observe(2.1, :erb)
193
+ summary.observe(4.1, :erb)
194
+ summary.observe(5.3, :haml)
195
+
196
+ puts summary
197
+ ```
198
+
199
+ Output:
200
+
201
+ ```
202
+ │ count min .50 .90 .95 max tot
203
+ ─────┼────────────────────────────────────────────────
204
+ erb │ 2 2.10 3.10 3.90 4.00 4.10 6.20
205
+ haml │ 1 5.30 5.30 5.30 5.30 5.30 5.30
206
+ ```
207
+
208
+ ### Stopwatch
209
+
210
+ The `DDTelemetry::Stopwatch` class can be used to measure durations. Use `#start` and `#stop` to start and stop the stopwatch, respectively, and `#duration` to read the value of the stopwatch:
211
+
212
+ ```ruby
213
+ stopwatch = DDTelemetry::Stopwatch.new
214
+
215
+ stopwatch.start
216
+ sleep 1
217
+ stopwatch.stop
218
+ puts "That took #{stopwatch.duration}s."
219
+ # Output: That took 1.006831s.
220
+ ```
221
+
222
+ A stopwatch, once created, will never reset its duration. Running the stopwatch again will add to the existing duration:
223
+
224
+ ```ruby
225
+ stopwatch.start
226
+ sleep 1
227
+ stopwatch.stop
228
+ puts "That took #{stopwatch.duration}s."
229
+ # Output: That took 2.012879s.
230
+ ```
231
+
232
+ You can query whether or not a stopwatch is running using `#running?`; `#stopped?` is the opposite of `#running?`.
110
233
 
111
234
  ## Development
112
235
 
data/Rakefile CHANGED
@@ -7,8 +7,12 @@ RSpec::Core::RakeTask.new(:spec) do |t|
7
7
  t.verbose = false
8
8
  end
9
9
 
10
+ task :test_samples do
11
+ sh 'bundle exec ruby samples/cache.rb > /dev/null'
12
+ end
13
+
10
14
  RuboCop::RakeTask.new(:rubocop)
11
15
 
12
16
  task default: :test
13
17
 
14
- task test: %i[spec rubocop]
18
+ task test: %i[spec rubocop test_samples]
data/lib/ddtelemetry.rb CHANGED
@@ -3,18 +3,17 @@
3
3
  require_relative 'ddtelemetry/version'
4
4
 
5
5
  module DDTelemetry
6
- def self.new
7
- Registry.new
8
- end
9
6
  end
10
7
 
8
+ require_relative 'ddtelemetry/basic_counter'
9
+ require_relative 'ddtelemetry/basic_summary'
10
+
11
+ require_relative 'ddtelemetry/metric'
11
12
  require_relative 'ddtelemetry/counter'
12
13
  require_relative 'ddtelemetry/summary'
13
14
 
14
- require_relative 'ddtelemetry/labelled_counter'
15
- require_relative 'ddtelemetry/labelled_summary'
16
-
17
- require_relative 'ddtelemetry/registry'
18
15
  require_relative 'ddtelemetry/stopwatch'
19
16
 
20
17
  require_relative 'ddtelemetry/table'
18
+ require_relative 'ddtelemetry/printer'
19
+ require_relative 'ddtelemetry/stats'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDTelemetry
4
+ class BasicCounter
5
+ attr_reader :value
6
+
7
+ def initialize
8
+ @value = 0
9
+ end
10
+
11
+ def increment
12
+ @value += 1
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDTelemetry
4
+ class BasicSummary
5
+ attr_reader :values
6
+
7
+ def initialize
8
+ @values = []
9
+ end
10
+
11
+ def observe(value)
12
+ @values << value
13
+ end
14
+ end
15
+ end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DDTelemetry
4
- class Counter
5
- attr_reader :value
4
+ class Counter < Metric
5
+ def increment(label)
6
+ basic_metric_for(label, BasicCounter).increment
7
+ end
6
8
 
7
- def initialize
8
- @value = 0
9
+ def get(label)
10
+ basic_metric_for(label, BasicCounter).value
9
11
  end
10
12
 
11
- def increment
12
- @value += 1
13
+ def to_s
14
+ DDTelemetry::Printer.new.counter_to_s(self)
13
15
  end
14
16
  end
15
17
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDTelemetry
4
+ class Metric
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @basic_metrics = {}
9
+ end
10
+
11
+ def get(label)
12
+ basic_metric_for(label, BasicCounter)
13
+ end
14
+
15
+ def labels
16
+ @basic_metrics.keys
17
+ end
18
+
19
+ def each
20
+ @basic_metrics.each_key do |label|
21
+ yield(label, get(label))
22
+ end
23
+ end
24
+
25
+ # @api private
26
+ def basic_metric_for(label, basic_class)
27
+ @basic_metrics.fetch(label) { @basic_metrics[label] = basic_class.new }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDTelemetry
4
+ class Printer
5
+ def summary_to_s(summary)
6
+ DDTelemetry::Table.new(table_for_summary(summary)).to_s
7
+ end
8
+
9
+ def counter_to_s(counter)
10
+ DDTelemetry::Table.new(table_for_counter(counter)).to_s
11
+ end
12
+
13
+ private
14
+
15
+ def table_for_summary(summary)
16
+ headers = ['', 'count', 'min', '.50', '.90', '.95', 'max', 'tot']
17
+
18
+ rows = summary.labels.map do |label|
19
+ stats = summary.get(label)
20
+
21
+ count = stats.count
22
+ min = stats.min
23
+ p50 = stats.quantile(0.50)
24
+ p90 = stats.quantile(0.90)
25
+ p95 = stats.quantile(0.95)
26
+ tot = stats.sum
27
+ max = stats.max
28
+
29
+ [label.to_s, count.to_s] + [min, p50, p90, p95, max, tot].map { |r| format('%4.2f', r) }
30
+ end
31
+
32
+ [headers] + rows
33
+ end
34
+
35
+ def table_for_counter(counter)
36
+ headers = ['', 'count']
37
+
38
+ rows = counter.labels.map do |label|
39
+ [label.to_s, counter.get(label).to_s]
40
+ end
41
+
42
+ [headers] + rows
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDTelemetry
4
+ class Stats
5
+ class EmptyError < StandardError
6
+ def message
7
+ 'Not enough data to perform calculation'
8
+ end
9
+ end
10
+
11
+ def initialize(values)
12
+ @values = values
13
+ end
14
+
15
+ def inspect
16
+ "<#{self.class} count=#{count}>"
17
+ end
18
+
19
+ def count
20
+ @values.size
21
+ end
22
+
23
+ def sum
24
+ raise EmptyError if @values.empty?
25
+ @values.reduce(:+)
26
+ end
27
+
28
+ def avg
29
+ sum.to_f / count
30
+ end
31
+
32
+ def min
33
+ quantile(0.0)
34
+ end
35
+
36
+ def max
37
+ quantile(1.0)
38
+ end
39
+
40
+ def quantile(fraction)
41
+ raise EmptyError if @values.empty?
42
+
43
+ target = (@values.size - 1) * fraction.to_f
44
+ interp = target % 1.0
45
+ sorted_values[target.floor] * (1.0 - interp) + sorted_values[target.ceil] * interp
46
+ end
47
+
48
+ private
49
+
50
+ def sorted_values
51
+ @sorted_values ||= @values.sort
52
+ end
53
+ end
54
+ end