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