ddmetrics 1.0.0rc1

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.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DDMetrics::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 DDMetrics::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
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DDMetrics::Counter do
4
+ subject(:counter) { described_class.new }
5
+
6
+ describe 'new counter' do
7
+ it 'starts at 0' do
8
+ expect(subject.get(filter: :erb)).to eq(0)
9
+ expect(subject.get(filter: :haml)).to eq(0)
10
+ end
11
+ end
12
+
13
+ describe '#increment' do
14
+ subject { counter.increment(filter: :erb) }
15
+
16
+ it 'increments the matching value' do
17
+ expect { subject }
18
+ .to change { counter.get(filter: :erb) }
19
+ .from(0)
20
+ .to(1)
21
+ end
22
+
23
+ it 'does not increment any other value' do
24
+ expect(counter.get(filter: :haml)).to eq(0)
25
+ expect { subject }
26
+ .not_to change { counter.get(filter: :haml) }
27
+ end
28
+
29
+ context 'non-hash label' do
30
+ subject { counter.increment('WRONG UGH') }
31
+
32
+ it 'errors' do
33
+ expect { subject }.to raise_error(ArgumentError, 'label argument must be a hash')
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#get' do
39
+ subject { counter.get(filter: :erb) }
40
+
41
+ context 'not incremented' do
42
+ it { is_expected.to eq(0) }
43
+ end
44
+
45
+ context 'incremented' do
46
+ before { counter.increment(filter: :erb) }
47
+ it { is_expected.to eq(1) }
48
+ end
49
+
50
+ context 'other incremented' do
51
+ before { counter.increment(filter: :haml) }
52
+ it { is_expected.to eq(0) }
53
+ end
54
+ end
55
+
56
+ describe '#labels' do
57
+ subject { counter.labels }
58
+
59
+ before do
60
+ counter.increment(filter: :erb)
61
+ counter.increment(filter: :erb)
62
+ counter.increment(filter: :haml)
63
+ end
64
+
65
+ it { is_expected.to match_array([{ filter: :haml }, { filter: :erb }]) }
66
+ end
67
+
68
+ describe '#each' do
69
+ subject do
70
+ {}.tap do |res|
71
+ counter.each { |label, count| res[label] = count }
72
+ end
73
+ end
74
+
75
+ before do
76
+ counter.increment(filter: :erb)
77
+ counter.increment(filter: :erb)
78
+ counter.increment(filter: :haml)
79
+ end
80
+
81
+ it { is_expected.to eq({ filter: :haml } => 1, { filter: :erb } => 2) }
82
+
83
+ it 'is enumerable' do
84
+ expect(counter.map { |_label, count| count }.sort)
85
+ .to eq([1, 2])
86
+ end
87
+ end
88
+
89
+ describe '#to_s' do
90
+ subject { counter.to_s }
91
+
92
+ before do
93
+ counter.increment(filter: :erb)
94
+ counter.increment(filter: :erb)
95
+ counter.increment(filter: :haml)
96
+ end
97
+
98
+ it 'returns table' do
99
+ expected = <<~TABLE
100
+ │ count
101
+ ────────────┼──────
102
+ filter=erb │ 2
103
+ filter=haml │ 1
104
+ TABLE
105
+
106
+ expect(subject.strip).to eq(expected.strip)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DDMetrics::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(DDMetrics::Stats::EmptyError)
12
+ end
13
+
14
+ it 'errors on #max' do
15
+ expect { subject.max }
16
+ .to raise_error(DDMetrics::Stats::EmptyError)
17
+ end
18
+
19
+ it 'errors on #avg' do
20
+ expect { subject.avg }
21
+ .to raise_error(DDMetrics::Stats::EmptyError)
22
+ end
23
+
24
+ it 'errors on #sum' do
25
+ expect { subject.sum }
26
+ .to raise_error(DDMetrics::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('<DDMetrics::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('<DDMetrics::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
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DDMetrics::Stopwatch do
4
+ subject(:stopwatch) { described_class.new }
5
+
6
+ after { Timecop.return }
7
+
8
+ it 'is zero by default' do
9
+ expect(stopwatch.duration).to eq(0.0)
10
+ end
11
+
12
+ it 'records correct duration after start+stop' do
13
+ Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0))
14
+ stopwatch.start
15
+
16
+ Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1))
17
+ stopwatch.stop
18
+
19
+ expect(stopwatch.duration).to eq(1.0)
20
+ end
21
+
22
+ it 'records correct duration after start+stop+start+stop' do
23
+ Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0))
24
+ stopwatch.start
25
+
26
+ Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1))
27
+ stopwatch.stop
28
+
29
+ Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3))
30
+ stopwatch.start
31
+
32
+ Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 6))
33
+ stopwatch.stop
34
+
35
+ expect(stopwatch.duration).to eq(1.0 + 3.0)
36
+ end
37
+
38
+ it 'errors when stopping when not started' do
39
+ expect { stopwatch.stop }
40
+ .to raise_error(
41
+ DDMetrics::Stopwatch::NotRunningError,
42
+ 'Cannot stop, because stopwatch is not running',
43
+ )
44
+ end
45
+
46
+ it 'errors when starting when already started' do
47
+ stopwatch.start
48
+ expect { stopwatch.start }
49
+ .to raise_error(
50
+ DDMetrics::Stopwatch::AlreadyRunningError,
51
+ 'Cannot start, because stopwatch is already running',
52
+ )
53
+ end
54
+
55
+ it 'reports running status' do
56
+ expect(stopwatch).not_to be_running
57
+ expect(stopwatch).to be_stopped
58
+
59
+ stopwatch.start
60
+
61
+ expect(stopwatch).to be_running
62
+ expect(stopwatch).not_to be_stopped
63
+
64
+ stopwatch.stop
65
+
66
+ expect(stopwatch).not_to be_running
67
+ expect(stopwatch).to be_stopped
68
+ end
69
+
70
+ it 'errors when getting duration while running' do
71
+ stopwatch.start
72
+ expect { stopwatch.duration }
73
+ .to raise_error(
74
+ DDMetrics::Stopwatch::StillRunningError,
75
+ 'Cannot get duration, because stopwatch is still running',
76
+ )
77
+ end
78
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DDMetrics::Summary do
4
+ subject(:summary) { described_class.new }
5
+
6
+ describe '#observe' do
7
+ context 'non-hash label' do
8
+ subject { summary.observe(1.23, 'WRONG UGH') }
9
+
10
+ it 'errors' do
11
+ expect { subject }.to raise_error(ArgumentError, 'label argument must be a hash')
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#get' do
17
+ subject { summary.get(filter: :erb) }
18
+
19
+ before do
20
+ summary.observe(2.1, filter: :erb)
21
+ summary.observe(4.1, filter: :erb)
22
+ summary.observe(5.3, filter: :haml)
23
+ end
24
+
25
+ it { is_expected.to be_a(DDMetrics::Stats) }
26
+
27
+ its(:sum) { is_expected.to eq(2.1 + 4.1) }
28
+ its(:count) { is_expected.to eq(2) }
29
+ end
30
+
31
+ describe '#labels' do
32
+ subject { summary.labels }
33
+
34
+ before do
35
+ summary.observe(2.1, filter: :erb)
36
+ summary.observe(4.1, filter: :erb)
37
+ summary.observe(5.3, filter: :haml)
38
+ end
39
+
40
+ it { is_expected.to match_array([{ filter: :haml }, { filter: :erb }]) }
41
+ end
42
+
43
+ describe '#each' do
44
+ subject do
45
+ {}.tap do |res|
46
+ summary.each { |label, stats| res[label] = stats.avg.round(2) }
47
+ end
48
+ end
49
+
50
+ before do
51
+ summary.observe(2.1, filter: :erb)
52
+ summary.observe(4.1, filter: :erb)
53
+ summary.observe(5.3, filter: :haml)
54
+ end
55
+
56
+ it { is_expected.to eq({ filter: :haml } => 5.3, { filter: :erb } => 3.1) }
57
+
58
+ it 'is enumerable' do
59
+ expect(summary.map { |_label, stats| stats.sum }.sort)
60
+ .to eq([5.3, 2.1 + 4.1])
61
+ end
62
+ end
63
+
64
+ describe '#to_s' do
65
+ subject { summary.to_s }
66
+
67
+ before do
68
+ summary.observe(2.1, filter: :erb)
69
+ summary.observe(4.1, filter: :erb)
70
+ summary.observe(5.3, filter: :haml)
71
+ end
72
+
73
+ it 'returns table' do
74
+ expected = <<~TABLE
75
+ │ count min .50 .90 .95 max tot
76
+ ────────────┼────────────────────────────────────────────────
77
+ filter=erb │ 2 2.10 3.10 3.90 4.00 4.10 6.20
78
+ filter=haml │ 1 5.30 5.30 5.30 5.30 5.30 5.30
79
+ TABLE
80
+
81
+ expect(subject.strip).to eq(expected.strip)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DDMetrics::Table do
4
+ let(:table) { described_class.new(rows) }
5
+
6
+ let(:rows) do
7
+ [
8
+ %w[name awesomeness],
9
+ %w[denis high],
10
+ %w[REDACTED low],
11
+ ]
12
+ end
13
+
14
+ example do
15
+ expect(table.to_s).to eq(<<~TABLE.rstrip)
16
+ name │ awesomeness
17
+ ─────────┼────────────
18
+ denis │ high
19
+ REDACTED │ low
20
+ TABLE
21
+ end
22
+
23
+ context 'unsorted data' do
24
+ let(:rows) do
25
+ [
26
+ %w[name awesomeness],
27
+ %w[ccc highc],
28
+ %w[bbb highb],
29
+ %w[ddd highd],
30
+ %w[eee highe],
31
+ %w[aaa higha],
32
+ ]
33
+ end
34
+
35
+ example do
36
+ expect(table.to_s).to eq(<<~TABLE.rstrip)
37
+ name │ awesomeness
38
+ ─────┼────────────
39
+ aaa │ higha
40
+ bbb │ highb
41
+ ccc │ highc
42
+ ddd │ highd
43
+ eee │ highe
44
+ TABLE
45
+ end
46
+ end
47
+ end