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 +4 -4
- data/Gemfile +1 -1
- data/NEWS.md +6 -0
- data/README.md +143 -20
- data/Rakefile +5 -1
- data/lib/ddtelemetry.rb +6 -7
- data/lib/ddtelemetry/basic_counter.rb +15 -0
- data/lib/ddtelemetry/basic_summary.rb +15 -0
- data/lib/ddtelemetry/counter.rb +8 -6
- data/lib/ddtelemetry/metric.rb +30 -0
- data/lib/ddtelemetry/printer.rb +45 -0
- data/lib/ddtelemetry/stats.rb +54 -0
- data/lib/ddtelemetry/stopwatch.rb +11 -2
- data/lib/ddtelemetry/summary.rb +8 -45
- data/lib/ddtelemetry/version.rb +1 -1
- data/roadmap.md +7 -0
- data/samples/cache.rb +48 -0
- data/spec/ddtelemetry/basic_counter_spec.rb +20 -0
- data/spec/ddtelemetry/basic_summary_spec.rb +23 -0
- data/spec/ddtelemetry/counter_spec.rb +86 -5
- data/spec/ddtelemetry/stats_spec.rb +87 -0
- data/spec/ddtelemetry/stopwatch_spec.rb +19 -4
- data/spec/ddtelemetry/summary_spec.rb +51 -45
- data/spec/ddtelemetry_spec.rb +0 -25
- data/spec/spec_helper.rb +1 -1
- metadata +12 -7
- data/lib/ddtelemetry/labelled_counter.rb +0 -35
- data/lib/ddtelemetry/labelled_summary.rb +0 -37
- data/lib/ddtelemetry/registry.rb +0 -18
- data/spec/ddtelemetry/labelled_counter_spec.rb +0 -94
- data/spec/ddtelemetry/labelled_summary_spec.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fb967335678607cb86deb8c2b48446735a957230cb9349ce3858d21233d002b
|
4
|
+
data.tar.gz: eadc2b1b9a1737564dbd38abc68f4a59c320d5cdbeb13ed3d0df0fe7c5c3a3f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1474fb4050d596b8f1fed72f80760e63e37f42a99327c9acdd57d7af5afe3139f096f8067437ca5d297c52ea3d4847117aeacb8ffc8d9b142f753f47495491e9
|
7
|
+
data.tar.gz: 6ac9b71a55dd640a652a762c6974a6260a7448cfc94d1a30cd4f23a340a75237b7fba521049228a5ebc0b3287837434da604224253f6df693d8162e2bba3262f
|
data/Gemfile
CHANGED
data/NEWS.md
CHANGED
data/README.md
CHANGED
@@ -6,9 +6,7 @@
|
|
6
6
|
|
7
7
|
# DDTelemetry
|
8
8
|
|
9
|
-
_DDTelemetry_
|
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`,
|
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
|
-
|
42
|
-
|
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
|
-
@
|
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
|
-
@
|
54
|
+
@counter.increment(:get_hit)
|
55
55
|
else
|
56
|
-
@
|
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
|
64
|
+
Let’s construct a cache and exercise it:
|
65
65
|
|
66
66
|
```ruby
|
67
|
-
|
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,
|
77
|
+
Finally, get the recorded telemetry values:
|
79
78
|
|
80
79
|
```ruby
|
81
|
-
|
82
|
-
# =>
|
80
|
+
cache.counter.get(:set) # => 1
|
81
|
+
cache.counter.get(:get_hit) # => 3
|
82
|
+
cache.counter.get(:get_miss) # => 2
|
83
|
+
```
|
83
84
|
|
84
|
-
|
85
|
-
# => 3
|
85
|
+
Or even print all stats:
|
86
86
|
|
87
|
-
|
88
|
-
|
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
|
-
|
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'
|
data/lib/ddtelemetry/counter.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DDTelemetry
|
4
|
-
class Counter
|
5
|
-
|
4
|
+
class Counter < Metric
|
5
|
+
def increment(label)
|
6
|
+
basic_metric_for(label, BasicCounter).increment
|
7
|
+
end
|
6
8
|
|
7
|
-
def
|
8
|
-
|
9
|
+
def get(label)
|
10
|
+
basic_metric_for(label, BasicCounter).value
|
9
11
|
end
|
10
12
|
|
11
|
-
def
|
12
|
-
|
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
|