ddtelemetry 1.0.0a1 → 1.0.0a2

Sign up to get free protection for your applications and to get access to all the features.
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