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 +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
|