tabstabs 2.0.0

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +421 -0
  8. data/Rakefile +5 -0
  9. data/lib/tabs_tabs.rb +26 -0
  10. data/lib/tabs_tabs/config.rb +65 -0
  11. data/lib/tabs_tabs/helpers.rb +27 -0
  12. data/lib/tabs_tabs/metrics/counter.rb +69 -0
  13. data/lib/tabs_tabs/metrics/counter/stats.rb +51 -0
  14. data/lib/tabs_tabs/metrics/task.rb +72 -0
  15. data/lib/tabs_tabs/metrics/task/token.rb +89 -0
  16. data/lib/tabs_tabs/metrics/value.rb +91 -0
  17. data/lib/tabs_tabs/metrics/value/stats.rb +55 -0
  18. data/lib/tabs_tabs/resolution.rb +65 -0
  19. data/lib/tabs_tabs/resolutionable.rb +48 -0
  20. data/lib/tabs_tabs/resolutions/day.rb +40 -0
  21. data/lib/tabs_tabs/resolutions/hour.rb +40 -0
  22. data/lib/tabs_tabs/resolutions/minute.rb +40 -0
  23. data/lib/tabs_tabs/resolutions/month.rb +40 -0
  24. data/lib/tabs_tabs/resolutions/week.rb +40 -0
  25. data/lib/tabs_tabs/resolutions/year.rb +40 -0
  26. data/lib/tabs_tabs/storage.rb +105 -0
  27. data/lib/tabs_tabs/tabs_tabs.rb +117 -0
  28. data/lib/tabs_tabs/version.rb +3 -0
  29. data/spec/lib/tabs_tabs/config_spec.rb +60 -0
  30. data/spec/lib/tabs_tabs/metrics/counter/stats_spec.rb +42 -0
  31. data/spec/lib/tabs_tabs/metrics/counter_spec.rb +196 -0
  32. data/spec/lib/tabs_tabs/metrics/task/token_spec.rb +18 -0
  33. data/spec/lib/tabs_tabs/metrics/task_spec.rb +103 -0
  34. data/spec/lib/tabs_tabs/metrics/value/stats_spec.rb +61 -0
  35. data/spec/lib/tabs_tabs/metrics/value_spec.rb +160 -0
  36. data/spec/lib/tabs_tabs/resolution_spec.rb +52 -0
  37. data/spec/lib/tabs_tabs/resolutionable_spec.rb +53 -0
  38. data/spec/lib/tabs_tabs/resolutions/day_spec.rb +23 -0
  39. data/spec/lib/tabs_tabs/resolutions/hour_spec.rb +23 -0
  40. data/spec/lib/tabs_tabs/resolutions/minute_spec.rb +23 -0
  41. data/spec/lib/tabs_tabs/resolutions/month_spec.rb +23 -0
  42. data/spec/lib/tabs_tabs/resolutions/week_spec.rb +24 -0
  43. data/spec/lib/tabs_tabs/resolutions/year_spec.rb +23 -0
  44. data/spec/lib/tabs_tabs/storage_spec.rb +138 -0
  45. data/spec/lib/tabs_tabs_spec.rb +223 -0
  46. data/spec/spec_helper.rb +17 -0
  47. data/spec/support/custom_resolutions.rb +40 -0
  48. data/tabs_tabs.gemspec +31 -0
  49. metadata +213 -0
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Metrics::Value::Stats do
4
+
5
+ let(:period) { (Time.now - 2.days..Time.now) }
6
+ let(:resolution) { :hour }
7
+ let(:values) do
8
+ [
9
+ { "timestamp" => Time.now - 30.hours, "count" => 10, "sum" => 145, "min" => 11, "max" => 204, "avg" => 14.5 },
10
+ { "timestamp" => Time.now - 20.hours, "count" => 15, "sum" => 288, "min" => 10, "max" => 199, "avg" => 19.2 },
11
+ { "timestamp" => Time.now - 10.hours, "count" => 25, "sum" => 405, "min" => 12, "max" => 210, "avg" => 16.2 }
12
+ ]
13
+ end
14
+ let(:stats) { TabsTabs::Metrics::Value::Stats.new(period, resolution, values) }
15
+
16
+ it "is enumerable" do
17
+ expect(stats).to respond_to :each
18
+ expect(TabsTabs::Metrics::Value::Stats.ancestors).to include Enumerable
19
+ end
20
+
21
+ it "#count returns the total count for the entire set" do
22
+ expect(stats.count).to eq 50
23
+ end
24
+
25
+ it "sum returns the sum for the entire set" do
26
+ expect(stats.sum).to eq 838
27
+ end
28
+
29
+ it "min returns the min for the entire set" do
30
+ expect(stats.min).to eq 10
31
+ end
32
+
33
+ it "max returns the max for the entire set" do
34
+ expect(stats.max).to eq 210
35
+ end
36
+
37
+ it "avg returns the average for the entire set" do
38
+ expect(stats.avg).to eq 16.76
39
+ end
40
+
41
+ it "avg returns 0 if set is empty" do
42
+ stats = TabsTabs::Metrics::Value::Stats.new(period, resolution, [])
43
+ expect(stats.avg).to be_zero
44
+ end
45
+
46
+ context "override decimal precision" do
47
+ before do
48
+ @precision = TabsTabs.config.decimal_precision
49
+ TabsTabs.config.decimal_precision = 1
50
+ end
51
+
52
+ after do
53
+ TabsTabs.config.decimal_precision = @precision
54
+ end
55
+
56
+ it "allows you to override decimal precision" do
57
+ expect(stats.avg).to eq 16.8
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,160 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Metrics::Value do
4
+
5
+ include TabsTabs::Storage
6
+
7
+ let(:metric) { TabsTabs.create_metric("foo", "value") }
8
+ let(:now) { Time.utc(2000, 1, 1, 0, 0) }
9
+
10
+ describe ".record" do
11
+
12
+ before { Timecop.freeze(now) }
13
+
14
+ it "sets the expected values for the period" do
15
+ metric.record(17)
16
+ metric.record(42)
17
+ time = Time.utc(now.year, now.month, now.day, now.hour)
18
+ stats = metric.stats(((now - 2.hours)..(now + 4.hours)), :hour)
19
+ expect(stats).to include({ "timestamp"=>time, "count"=>2, "min"=>17, "max"=>42, "sum"=>59, "avg"=>29.5})
20
+ end
21
+
22
+ it "applys the value to the specified timestamp if one is supplied" do
23
+ time = Time.utc(now.year, now.month, now.day, now.hour) - 2.hours
24
+ metric.record(42, time)
25
+ stats = metric.stats(((now - 3.hours)..now), :hour)
26
+ expect(stats).to include({ "timestamp"=>time, "count"=>1, "min"=>42, "max"=>42, "sum"=>42, "avg"=>42})
27
+ end
28
+
29
+ end
30
+
31
+ describe ".stats" do
32
+
33
+ before do
34
+ Timecop.freeze(now)
35
+ end
36
+
37
+ after do
38
+ Timecop.return
39
+ end
40
+
41
+ def create_span(time_unit)
42
+ metric.record(5)
43
+ Timecop.freeze(now + 1.send(time_unit))
44
+ metric.record(25)
45
+ Timecop.freeze(now + 3.send(time_unit))
46
+ metric.record(10)
47
+ Timecop.freeze(now + 6.send(time_unit))
48
+ metric.record(15)
49
+ metric.record(20)
50
+ Timecop.freeze(now)
51
+ end
52
+
53
+ it "returns an instance of Tabs::Metrics::Value::Stats" do
54
+ create_span(:minutes)
55
+ stats = metric.stats(now..(now + 7.minutes), :minute)
56
+ expect(stats).to be_a_kind_of TabsTabs::Metrics::Value::Stats
57
+ end
58
+
59
+ it "returns the expected results for an minutely metric" do
60
+ create_span(:minutes)
61
+ stats = metric.stats(now..(now + 7.minutes), :minute)
62
+ expect(stats).to include({ "timestamp" => (now + 3.minutes), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
63
+ expect(stats).to include({ "timestamp" => (now + 6.minutes), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
64
+ end
65
+
66
+ it "returns the expected results for an hourly metric" do
67
+ create_span(:hours)
68
+ stats = metric.stats(now..(now + 7.hours), :hour)
69
+ expect(stats).to include({ "timestamp" => (now + 3.hours), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
70
+ expect(stats).to include({ "timestamp" => (now + 6.hours), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
71
+ end
72
+
73
+ it "returns the expected results for a daily metric" do
74
+ create_span(:days)
75
+ stats = metric.stats(now..(now + 7.days), :day)
76
+ expect(stats).to include({ "timestamp" => (now + 3.days), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
77
+ expect(stats).to include({ "timestamp" => (now + 6.days), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
78
+ end
79
+
80
+ it "returns the expected results for a weekly metric" do
81
+ create_span(:weeks)
82
+ stats = metric.stats(now..(now + 7.weeks), :week)
83
+ second_week_stats = stats.detect{|s| s["timestamp"] == (now + 1.week).beginning_of_week }
84
+ expect(second_week_stats["count"]).to eq(1)
85
+ expect(stats).to include({ "timestamp" => (now + 3.weeks).beginning_of_week, "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
86
+ expect(stats).to include({ "timestamp" => (now + 6.weeks).beginning_of_week, "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
87
+ end
88
+
89
+ it "returns the expected results for a monthly metric" do
90
+ create_span(:months)
91
+ stats = metric.stats(now..(now + 7.months), :month)
92
+ expect(stats).to include({ "timestamp" => (now + 3.months), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
93
+ expect(stats).to include({ "timestamp" => (now + 6.months), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
94
+ end
95
+
96
+ it "returns the expected results for a yearly metric" do
97
+ create_span(:years)
98
+ stats = metric.stats(now..(now + 7.years), :year)
99
+ expect(stats).to include({ "timestamp" => (now + 3.years), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
100
+ expect(stats).to include({ "timestamp" => (now + 6.years), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
101
+ end
102
+ end
103
+
104
+ describe ".drop!" do
105
+
106
+ before do
107
+ 3.times { metric.record(rand(30)) }
108
+ @count_keys = (TabsTabs::Resolution.all.map do |res|
109
+ smembers("stat:value:foo:keys:#{res}")
110
+ end).flatten
111
+ metric.drop!
112
+ end
113
+
114
+ it "deletes all resolution count keys" do
115
+ @count_keys.each do |key|
116
+ expect(exists(key)).to be_false
117
+ end
118
+ end
119
+
120
+ it "deletes all resolution key collection keys" do
121
+ TabsTabs::Resolution.all.each do |res|
122
+ expect(exists("stat:value:foo:keys:#{res}")).to be_falsey
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ describe ".drop_by_resolution!" do
129
+ before do
130
+ Timecop.freeze(now)
131
+ 2.times { metric.record(rand(30)) }
132
+ metric.drop_by_resolution!(:minute)
133
+ end
134
+
135
+ it "deletes all metrics for a resolution" do
136
+ stats = metric.stats((now - 1.minute)..(now + 1.minute), :minute)
137
+ expect(stats.sum).to eq(0)
138
+ end
139
+ end
140
+
141
+ describe "expiration of value metrics" do
142
+ let(:expires_setting){ 6.hours }
143
+ let(:now){ Time.utc(2050, 1, 1, 0, 0) }
144
+
145
+ before do
146
+ TabsTabs::Config.set_expirations({minute: expires_setting })
147
+ end
148
+
149
+ after do
150
+ TabsTabs::Config.reset_expirations
151
+ end
152
+
153
+ it "sets an expiration when recording a value" do
154
+ metric.record(17, now)
155
+ redis_expire_date = Time.now + TabsTabs::Storage.ttl(metric.storage_key(:minute, now))
156
+ expire_date = now + expires_setting + TabsTabs::Resolutions::Minute.to_seconds
157
+ expect(redis_expire_date).to be_within(2.seconds).of(expire_date)
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+ require File.expand_path("../../../support/custom_resolutions", __FILE__)
3
+
4
+ describe TabsTabs::Resolution do
5
+
6
+ describe "#register" do
7
+ it "registers a new resolution" do
8
+ TabsTabs::Resolution.register(WellFormedResolution)
9
+ expect(TabsTabs::Resolution.all).to include WellFormedResolution.name
10
+ end
11
+
12
+ context "with a custom resolution" do
13
+ it "does not return nil" do
14
+ expect(WellFormedResolution.serialize(Time.now)).to_not be_nil
15
+ end
16
+
17
+ it "gets stats for custom resolution" do
18
+ TabsTabs::Resolution.register(WellFormedResolution)
19
+ Timecop.freeze(Time.now)
20
+
21
+ TabsTabs.increment_counter("foo")
22
+ expect(TabsTabs.get_stats("foo", (Time.now - 5.seconds..Time.now), :seconds).values.size).to eq(6)
23
+ end
24
+
25
+ it "raises an error when method not implemented" do
26
+ expect{BadlyFormedResolution.normalize}.to raise_error(RuntimeError)
27
+ end
28
+
29
+ it "disregards already registered resolutions" do
30
+ expect { TabsTabs::Resolution.register(TabsTabs::Resolutions::Minute) }.to_not raise_error
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#unregister" do
36
+ it "unregisters a single resolution" do
37
+ TabsTabs::Resolution.unregister(:minute)
38
+ expect(TabsTabs::Resolution.all).to_not include(:minute)
39
+ end
40
+
41
+ it "unregisters an array of resolutions" do
42
+ TabsTabs::Resolution.unregister([:minute, :hour])
43
+ expect(TabsTabs::Resolution.all).to_not include(:hour)
44
+ expect(TabsTabs::Resolution.all).to_not include(:minute)
45
+ end
46
+
47
+ it "disregards passing in an unrecognized resolution" do
48
+ expect { TabsTabs::Resolution.unregister(:invalid_resolution) }.to_not raise_error
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,53 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Resolutionable do
4
+
5
+ module TestResolution
6
+ include TabsTabs::Resolutionable
7
+ extend self
8
+
9
+ def name
10
+ :test
11
+ end
12
+
13
+ def to_seconds
14
+ 1000
15
+ end
16
+
17
+ end
18
+
19
+ describe "interface exceptions" do
20
+
21
+ ["serialize", "deserialize", "from_seconds", "add", "normalize"].each do |method|
22
+ it "are raised when the #{method} method is not implemented" do
23
+ expect { TestResolution.send(method) }.to raise_error(RuntimeError)
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ describe "#expire" do
30
+ let(:expires_setting){ 1.day }
31
+
32
+ before do
33
+ TabsTabs::Config.register_resolution(TestResolution)
34
+ TabsTabs::Config.set_expirations(test: expires_setting)
35
+ end
36
+
37
+ after do
38
+ TabsTabs::Config.reset_expirations
39
+ TabsTabs::Config.unregister_resolutions(:test)
40
+ end
41
+
42
+ it "sets the expiration for the given key" do
43
+ now = Time.utc(2050, 1, 1, 0, 0, 0)
44
+ TabsTabs::Storage.set("foo", "bar")
45
+ TestResolution.expire("foo", now)
46
+ redis_expire_date = Time.now + TabsTabs::Storage.ttl("foo")
47
+ expire_date = now + expires_setting + TestResolution.to_seconds
48
+ expect(redis_expire_date).to be_within(2.seconds).of(expire_date)
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Resolutions::Day do
4
+ let(:timestamp){ Time.new(2000, 1, 1, 12, 15) }
5
+
6
+ context "#normalize" do
7
+ it "should normalize the date to year, month, day" do
8
+ expect(subject.normalize(timestamp)).to eq(timestamp.utc.change(hour: 0))
9
+ end
10
+ end
11
+
12
+ context "#serialize" do
13
+ it "should return YYYY-MM-DD" do
14
+ expect(subject.serialize(timestamp)).to eq("2000-01-01")
15
+ end
16
+ end
17
+
18
+ context "#deserialize" do
19
+ it "should convert string into date" do
20
+ expect(subject.deserialize("2000-01-01")).to eq(timestamp.utc.change(hour: 0))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Resolutions::Hour do
4
+ let(:timestamp){ Time.utc(2000, 1, 1, 14, 12) }
5
+
6
+ context "#normalize" do
7
+ it "should normalize the date to year, month, day, hour" do
8
+ expect(subject.normalize(timestamp)).to eq(timestamp.change(min: 0))
9
+ end
10
+ end
11
+
12
+ context "#serialize" do
13
+ it "should return YYYY-MM-DD-HH" do
14
+ expect(subject.serialize(timestamp)).to eq("2000-01-01-14")
15
+ end
16
+ end
17
+
18
+ context "#deserialize" do
19
+ it "should convert string into date" do
20
+ expect(subject.deserialize("2000-01-01-14")).to eq(timestamp.change(min: 0))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Resolutions::Minute do
4
+ let(:timestamp){ Time.utc(2000, 1, 1, 14, 12, 44) }
5
+
6
+ context "#normalize" do
7
+ it "should normalize the date to year, month, day, hour, minute" do
8
+ expect(subject.normalize(timestamp)).to eq(timestamp.change(sec: 0))
9
+ end
10
+ end
11
+
12
+ context "#serialize" do
13
+ it "should return YYYY-MM-DD-HH-MM" do
14
+ expect(subject.serialize(timestamp)).to eq("2000-01-01-14-12")
15
+ end
16
+ end
17
+
18
+ context "#deserialize" do
19
+ it "should convert string into date" do
20
+ expect(subject.deserialize("2000-01-01-14-12")).to eq(timestamp.change(sec: 0))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Resolutions::Month do
4
+ let(:timestamp){ Time.utc(2000, 1, 15) }
5
+
6
+ context "#normalize" do
7
+ it "should normalize the date to year, month" do
8
+ expect(subject.normalize(timestamp)).to eq(timestamp.change(day: 1))
9
+ end
10
+ end
11
+
12
+ context "#serialize" do
13
+ it "should return YYYY-MM" do
14
+ expect(subject.serialize(timestamp)).to eq("2000-01")
15
+ end
16
+ end
17
+
18
+ context "#deserialize" do
19
+ it "should convert string into date" do
20
+ expect(subject.deserialize("2000-01")).to eq(timestamp.change(day: 1))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe TabsTabs::Resolutions::Week do
4
+ let(:timestamp){ Time.utc(2000, 1, 1) }
5
+ let(:beginning_of_week_timestamp){ Time.utc(1999, 12, 27) }
6
+
7
+ context "#normalize" do
8
+ it "should roll to the beginning of the week" do
9
+ expect(subject.normalize(timestamp)).to eq(beginning_of_week_timestamp)
10
+ end
11
+ end
12
+
13
+ context "#serialize" do
14
+ it "should return YYYY-MM-DD based on beginning of the week" do
15
+ expect(subject.serialize(timestamp)).to eq("1999-12-27")
16
+ end
17
+ end
18
+
19
+ context "#deserialize" do
20
+ it "should convert beginning of week string into date" do
21
+ expect(subject.deserialize("1999-12-27")).to eq(beginning_of_week_timestamp)
22
+ end
23
+ end
24
+ end