tabstabs 2.0.0

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