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
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
module DDTelemetry
|
4
4
|
class Stopwatch
|
5
|
-
attr_reader :duration
|
6
|
-
|
7
5
|
class AlreadyRunningError < StandardError
|
8
6
|
def message
|
9
7
|
'Cannot start, because stopwatch is already running'
|
@@ -16,6 +14,12 @@ module DDTelemetry
|
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
17
|
+
class StillRunningError < StandardError
|
18
|
+
def message
|
19
|
+
'Cannot get duration, because stopwatch is still running'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
19
23
|
def initialize
|
20
24
|
@duration = 0.0
|
21
25
|
@last_start = nil
|
@@ -32,6 +36,11 @@ module DDTelemetry
|
|
32
36
|
@last_start = nil
|
33
37
|
end
|
34
38
|
|
39
|
+
def duration
|
40
|
+
raise StillRunningError if running?
|
41
|
+
@duration
|
42
|
+
end
|
43
|
+
|
35
44
|
def running?
|
36
45
|
!@last_start.nil?
|
37
46
|
end
|
data/lib/ddtelemetry/summary.rb
CHANGED
@@ -1,55 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DDTelemetry
|
4
|
-
class Summary
|
5
|
-
|
6
|
-
|
7
|
-
'Cannot calculate quantile for empty summary'
|
8
|
-
end
|
4
|
+
class Summary < Metric
|
5
|
+
def observe(value, label)
|
6
|
+
basic_metric_for(label, BasicSummary).observe(value)
|
9
7
|
end
|
10
8
|
|
11
|
-
def
|
12
|
-
|
9
|
+
def get(label)
|
10
|
+
values = basic_metric_for(label, BasicSummary).values
|
11
|
+
DDTelemetry::Stats.new(values)
|
13
12
|
end
|
14
13
|
|
15
|
-
def
|
16
|
-
|
17
|
-
@sorted_values = nil
|
18
|
-
end
|
19
|
-
|
20
|
-
def count
|
21
|
-
@values.size
|
22
|
-
end
|
23
|
-
|
24
|
-
def sum
|
25
|
-
raise EmptySummaryError if @values.empty?
|
26
|
-
@values.reduce(:+)
|
27
|
-
end
|
28
|
-
|
29
|
-
def avg
|
30
|
-
sum / count
|
31
|
-
end
|
32
|
-
|
33
|
-
def min
|
34
|
-
quantile(0.0)
|
35
|
-
end
|
36
|
-
|
37
|
-
def max
|
38
|
-
quantile(1.0)
|
39
|
-
end
|
40
|
-
|
41
|
-
def quantile(fraction)
|
42
|
-
raise EmptySummaryError if @values.empty?
|
43
|
-
|
44
|
-
target = (@values.size - 1) * fraction.to_f
|
45
|
-
interp = target % 1.0
|
46
|
-
sorted_values[target.floor] * (1.0 - interp) + sorted_values[target.ceil] * interp
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def sorted_values
|
52
|
-
@sorted_values ||= @values.sort
|
14
|
+
def to_s
|
15
|
+
DDTelemetry::Printer.new.summary_to_s(self)
|
53
16
|
end
|
54
17
|
end
|
55
18
|
end
|
data/lib/ddtelemetry/version.rb
CHANGED
data/roadmap.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
## Roadmap
|
2
|
+
|
3
|
+
This document details the changes need to get to a stable 1.0.
|
4
|
+
|
5
|
+
* **Don’t use the term “telemetry”:** The term “telemetry” specifically refers to collecting data from remote systems. DDTelemetry does not at all collect from remote systems (in fact, that is explicitly out-of-scope). Suggestion: DDMetrics?
|
6
|
+
|
7
|
+
* **Key-value labels:** Labels can currently be any object. Stabilising on key-value labels, with keys being symbols and labels being strings, would make it easier to create tooling around transforming and analysing datasets that are produced by DDTelemetry.
|
data/samples/cache.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ddtelemetry'
|
4
|
+
|
5
|
+
class Cache
|
6
|
+
attr_reader :counter
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@map = {}
|
10
|
+
@counter = DDTelemetry::Counter.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
@counter.increment(:set)
|
15
|
+
|
16
|
+
@map[key] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
if @map.key?(key)
|
21
|
+
@counter.increment(:get_hit)
|
22
|
+
else
|
23
|
+
@counter.increment(:get_miss)
|
24
|
+
end
|
25
|
+
|
26
|
+
@map[key]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
cache = Cache.new
|
31
|
+
|
32
|
+
cache['greeting']
|
33
|
+
cache['greeting']
|
34
|
+
cache['greeting'] = 'Hi there!'
|
35
|
+
cache['greeting']
|
36
|
+
cache['greeting']
|
37
|
+
cache['greeting']
|
38
|
+
|
39
|
+
p cache.counter.get(:set)
|
40
|
+
# => 1
|
41
|
+
|
42
|
+
p cache.counter.get(:get_hit)
|
43
|
+
# => 3
|
44
|
+
|
45
|
+
p cache.counter.get(:get_miss)
|
46
|
+
# => 2
|
47
|
+
|
48
|
+
puts cache.counter
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe DDTelemetry::BasicCounter do
|
4
|
+
subject(:counter) { described_class.new }
|
5
|
+
|
6
|
+
it 'starts at 0' do
|
7
|
+
expect(counter.value).to eq(0)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#increment' do
|
11
|
+
subject { counter.increment }
|
12
|
+
|
13
|
+
it 'increments' do
|
14
|
+
expect { subject }
|
15
|
+
.to change { counter.value }
|
16
|
+
.from(0)
|
17
|
+
.to(1)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe DDTelemetry::BasicSummary do
|
4
|
+
subject(:summary) { described_class.new }
|
5
|
+
|
6
|
+
context 'no observations' do
|
7
|
+
its(:values) { is_expected.to be_empty }
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'one observation' do
|
11
|
+
before { subject.observe(2.1) }
|
12
|
+
its(:values) { is_expected.to eq([2.1]) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'two observations' do
|
16
|
+
before do
|
17
|
+
subject.observe(2.1)
|
18
|
+
subject.observe(4.1)
|
19
|
+
end
|
20
|
+
|
21
|
+
its(:values) { is_expected.to eq([2.1, 4.1]) }
|
22
|
+
end
|
23
|
+
end
|
@@ -3,18 +3,99 @@
|
|
3
3
|
describe DDTelemetry::Counter do
|
4
4
|
subject(:counter) { described_class.new }
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
describe 'new counter' do
|
7
|
+
it 'starts at 0' do
|
8
|
+
expect(subject.get(:erb)).to eq(0)
|
9
|
+
expect(subject.get(:haml)).to eq(0)
|
10
|
+
end
|
8
11
|
end
|
9
12
|
|
10
13
|
describe '#increment' do
|
11
|
-
subject { counter.increment }
|
14
|
+
subject { counter.increment(:erb) }
|
12
15
|
|
13
|
-
it 'increments' do
|
16
|
+
it 'increments the matching value' do
|
14
17
|
expect { subject }
|
15
|
-
.to change { counter.
|
18
|
+
.to change { counter.get(:erb) }
|
16
19
|
.from(0)
|
17
20
|
.to(1)
|
18
21
|
end
|
22
|
+
|
23
|
+
it 'does not increment any other value' do
|
24
|
+
expect(counter.get(:haml)).to eq(0)
|
25
|
+
expect { subject }
|
26
|
+
.not_to change { counter.get(:haml) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#get' do
|
31
|
+
subject { counter.get(:erb) }
|
32
|
+
|
33
|
+
context 'not incremented' do
|
34
|
+
it { is_expected.to eq(0) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'incremented' do
|
38
|
+
before { counter.increment(:erb) }
|
39
|
+
it { is_expected.to eq(1) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'other incremented' do
|
43
|
+
before { counter.increment(:haml) }
|
44
|
+
it { is_expected.to eq(0) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#labels' do
|
49
|
+
subject { counter.labels }
|
50
|
+
|
51
|
+
before do
|
52
|
+
counter.increment(:erb)
|
53
|
+
counter.increment(:erb)
|
54
|
+
counter.increment(:haml)
|
55
|
+
end
|
56
|
+
|
57
|
+
it { is_expected.to contain_exactly(:haml, :erb) }
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#each' do
|
61
|
+
subject do
|
62
|
+
{}.tap do |res|
|
63
|
+
counter.each { |label, count| res[label] = count }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
before do
|
68
|
+
counter.increment(:erb)
|
69
|
+
counter.increment(:erb)
|
70
|
+
counter.increment(:haml)
|
71
|
+
end
|
72
|
+
|
73
|
+
it { is_expected.to eq(haml: 1, erb: 2) }
|
74
|
+
|
75
|
+
it 'is enumerable' do
|
76
|
+
expect(counter.map { |_label, count| count }.sort)
|
77
|
+
.to eq([1, 2])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#to_s' do
|
82
|
+
subject { counter.to_s }
|
83
|
+
|
84
|
+
before do
|
85
|
+
counter.increment(:erb)
|
86
|
+
counter.increment(:erb)
|
87
|
+
counter.increment(:haml)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'returns table' do
|
91
|
+
expected = <<~TABLE
|
92
|
+
│ count
|
93
|
+
─────┼──────
|
94
|
+
erb │ 2
|
95
|
+
haml │ 1
|
96
|
+
TABLE
|
97
|
+
|
98
|
+
expect(subject.strip).to eq(expected.strip)
|
99
|
+
end
|
19
100
|
end
|
20
101
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe DDTelemetry::Stats do
|
4
|
+
subject(:stats) { described_class.new(values) }
|
5
|
+
|
6
|
+
context 'no values' do
|
7
|
+
let(:values) { [] }
|
8
|
+
|
9
|
+
it 'errors on #min' do
|
10
|
+
expect { subject.min }
|
11
|
+
.to raise_error(DDTelemetry::Stats::EmptyError)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'errors on #max' do
|
15
|
+
expect { subject.max }
|
16
|
+
.to raise_error(DDTelemetry::Stats::EmptyError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'errors on #avg' do
|
20
|
+
expect { subject.avg }
|
21
|
+
.to raise_error(DDTelemetry::Stats::EmptyError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'errors on #sum' do
|
25
|
+
expect { subject.sum }
|
26
|
+
.to raise_error(DDTelemetry::Stats::EmptyError)
|
27
|
+
end
|
28
|
+
|
29
|
+
its(:count) { is_expected.to eq(0) }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'one value' do
|
33
|
+
let(:values) { [2.1] }
|
34
|
+
|
35
|
+
its(:inspect) { is_expected.to eq('<DDTelemetry::Stats count=1>') }
|
36
|
+
its(:count) { is_expected.to eq(1) }
|
37
|
+
its(:sum) { is_expected.to eq(2.1) }
|
38
|
+
its(:avg) { is_expected.to eq(2.1) }
|
39
|
+
its(:min) { is_expected.to eq(2.1) }
|
40
|
+
its(:max) { is_expected.to eq(2.1) }
|
41
|
+
|
42
|
+
it 'has proper quantiles' do
|
43
|
+
expect(subject.quantile(0.00)).to eq(2.1)
|
44
|
+
expect(subject.quantile(0.25)).to eq(2.1)
|
45
|
+
expect(subject.quantile(0.50)).to eq(2.1)
|
46
|
+
expect(subject.quantile(0.90)).to eq(2.1)
|
47
|
+
expect(subject.quantile(0.99)).to eq(2.1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'two values' do
|
52
|
+
let(:values) { [2.1, 4.1] }
|
53
|
+
|
54
|
+
its(:inspect) { is_expected.to eq('<DDTelemetry::Stats count=2>') }
|
55
|
+
its(:count) { is_expected.to be_within(0.000001).of(2) }
|
56
|
+
its(:sum) { is_expected.to be_within(0.000001).of(6.2) }
|
57
|
+
its(:avg) { is_expected.to be_within(0.000001).of(3.1) }
|
58
|
+
its(:min) { is_expected.to be_within(0.000001).of(2.1) }
|
59
|
+
its(:max) { is_expected.to be_within(0.000001).of(4.1) }
|
60
|
+
|
61
|
+
it 'has proper quantiles' do
|
62
|
+
expect(subject.quantile(0.00)).to be_within(0.000001).of(2.1)
|
63
|
+
expect(subject.quantile(0.25)).to be_within(0.000001).of(2.6)
|
64
|
+
expect(subject.quantile(0.50)).to be_within(0.000001).of(3.1)
|
65
|
+
expect(subject.quantile(0.90)).to be_within(0.000001).of(3.9)
|
66
|
+
expect(subject.quantile(0.99)).to be_within(0.000001).of(4.08)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'integer values' do
|
71
|
+
let(:values) { [1, 2] }
|
72
|
+
|
73
|
+
its(:count) { is_expected.to be_within(0.000001).of(2) }
|
74
|
+
its(:sum) { is_expected.to be_within(0.000001).of(3) }
|
75
|
+
its(:avg) { is_expected.to be_within(0.000001).of(1.5) }
|
76
|
+
its(:min) { is_expected.to be_within(0.000001).of(1) }
|
77
|
+
its(:max) { is_expected.to be_within(0.000001).of(2) }
|
78
|
+
|
79
|
+
it 'has proper quantiles' do
|
80
|
+
expect(subject.quantile(0.00)).to be_within(0.000001).of(1.0)
|
81
|
+
expect(subject.quantile(0.25)).to be_within(0.000001).of(1.25)
|
82
|
+
expect(subject.quantile(0.50)).to be_within(0.000001).of(1.5)
|
83
|
+
expect(subject.quantile(0.90)).to be_within(0.000001).of(1.9)
|
84
|
+
expect(subject.quantile(0.99)).to be_within(0.000001).of(1.99)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -9,8 +9,6 @@ describe DDTelemetry::Stopwatch do
|
|
9
9
|
expect(stopwatch.duration).to eq(0.0)
|
10
10
|
end
|
11
11
|
|
12
|
-
# TODO: if running, raise error when asking for #duration
|
13
|
-
|
14
12
|
it 'records correct duration after start+stop' do
|
15
13
|
Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0))
|
16
14
|
stopwatch.start
|
@@ -38,12 +36,20 @@ describe DDTelemetry::Stopwatch do
|
|
38
36
|
end
|
39
37
|
|
40
38
|
it 'errors when stopping when not started' do
|
41
|
-
expect { stopwatch.stop }
|
39
|
+
expect { stopwatch.stop }
|
40
|
+
.to raise_error(
|
41
|
+
DDTelemetry::Stopwatch::NotRunningError,
|
42
|
+
'Cannot stop, because stopwatch is not running',
|
43
|
+
)
|
42
44
|
end
|
43
45
|
|
44
46
|
it 'errors when starting when already started' do
|
45
47
|
stopwatch.start
|
46
|
-
expect { stopwatch.start }
|
48
|
+
expect { stopwatch.start }
|
49
|
+
.to raise_error(
|
50
|
+
DDTelemetry::Stopwatch::AlreadyRunningError,
|
51
|
+
'Cannot start, because stopwatch is already running',
|
52
|
+
)
|
47
53
|
end
|
48
54
|
|
49
55
|
it 'reports running status' do
|
@@ -60,4 +66,13 @@ describe DDTelemetry::Stopwatch do
|
|
60
66
|
expect(stopwatch).not_to be_running
|
61
67
|
expect(stopwatch).to be_stopped
|
62
68
|
end
|
69
|
+
|
70
|
+
it 'errors when getting duration while running' do
|
71
|
+
stopwatch.start
|
72
|
+
expect { stopwatch.duration }
|
73
|
+
.to raise_error(
|
74
|
+
DDTelemetry::Stopwatch::StillRunningError,
|
75
|
+
'Cannot get duration, because stopwatch is still running',
|
76
|
+
)
|
77
|
+
end
|
63
78
|
end
|