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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.rubocop.yml +59 -0
- data/.travis.yml +21 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +21 -0
- data/NEWS.md +23 -0
- data/README.md +260 -0
- data/Rakefile +18 -0
- data/ddmetrics.gemspec +19 -0
- data/lib/ddmetrics.rb +19 -0
- data/lib/ddmetrics/basic_counter.rb +15 -0
- data/lib/ddmetrics/basic_summary.rb +15 -0
- data/lib/ddmetrics/counter.rb +19 -0
- data/lib/ddmetrics/metric.rb +36 -0
- data/lib/ddmetrics/printer.rb +49 -0
- data/lib/ddmetrics/stats.rb +54 -0
- data/lib/ddmetrics/stopwatch.rb +52 -0
- data/lib/ddmetrics/summary.rb +20 -0
- data/lib/ddmetrics/table.rb +40 -0
- data/lib/ddmetrics/version.rb +5 -0
- data/samples/cache.rb +48 -0
- data/scripts/release +115 -0
- data/spec/ddmetrics/basic_counter_spec.rb +20 -0
- data/spec/ddmetrics/basic_summary_spec.rb +23 -0
- data/spec/ddmetrics/counter_spec.rb +109 -0
- data/spec/ddmetrics/stats_spec.rb +87 -0
- data/spec/ddmetrics/stopwatch_spec.rb +78 -0
- data/spec/ddmetrics/summary_spec.rb +84 -0
- data/spec/ddmetrics/table_spec.rb +47 -0
- data/spec/ddmetrics_spec.rb +7 -0
- data/spec/spec_helper.rb +19 -0
- metadata +77 -0
@@ -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
|