ddmetrics 1.0.0rc1

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