tabs 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -276,6 +276,20 @@ enumerable and most existing code utilizing tabs should continue to
276
276
  function. However, please review the docs for more information if you
277
277
  encounter issues when upgrading.
278
278
 
279
+ ### Breaking Changes in v0.8.2
280
+
281
+ In version 0.8.2 and higher the storage keys for value metrics have been
282
+ changed. Originally the various pieces (avg, sum, count, etc) were
283
+ stored in a JSON serialized string in a single key. The intent was that
284
+ this would comprise a poor-mans transaction of sorts. The downside
285
+ however was a major hit on performance when doing a lot of writes or
286
+ reading stats for a large date range. In v0.8.2 these component values
287
+ are stored in a real Redis hash and updated atomically when a value is
288
+ recorded. In future versions this will be changed to use a MULTI
289
+ statement to simulate a transaction. Value data that was recorded prior
290
+ to v0.8.2 will not be accessible in this or future versions so please
291
+ continue to use v0.8.1 or lower if that is an issue.
292
+
279
293
  ## Contributing
280
294
 
281
295
  1. Fork it
@@ -18,5 +18,10 @@ module Tabs
18
18
  period_end = Tabs::Resolution.normalize(resolution, period.last.utc)
19
19
  (period_start..period_end)
20
20
  end
21
+
22
+ def to_numeric(v)
23
+ ((float = Float(v)) && (float % 1.0 == 0) ? float.to_i : float) rescue v
24
+ end
25
+
21
26
  end
22
27
  end
@@ -23,11 +23,13 @@ module Tabs
23
23
  def stats(period, resolution)
24
24
  timestamps = timestamp_range period, resolution
25
25
  keys = timestamps.map do |ts|
26
- "stat:value:#{key}:data:#{Tabs::Resolution.serialize(resolution, ts)}"
26
+ formatted_time = Tabs::Resolution.serialize(resolution, ts)
27
+ "stat:value:#{key}:data:#{formatted_time}"
27
28
  end
28
29
 
29
30
  values = mget(*keys).map do |v|
30
- value = v.nil? ? default_value(0) : JSON.parse(v)
31
+ value = v.nil? ? default_value(0) : v
32
+ value = Hash[value.map { |k, i| [k, to_numeric(i)] }]
31
33
  value["timestamp"] = timestamps.shift
32
34
  value.with_indifferent_access
33
35
  end
@@ -42,35 +44,34 @@ module Tabs
42
44
  private
43
45
 
44
46
  def update_values(stat_key, value)
45
- hash = get_current_hash(stat_key)
46
- increment(hash, value)
47
- update_min(hash, value)
48
- update_max(hash, value)
49
- update_avg(hash)
50
- set(stat_key, JSON.generate(hash))
47
+ count = update_count(stat_key)
48
+ sum = update_sum(stat_key, value)
49
+ update_min(stat_key, value)
50
+ update_max(stat_key, value)
51
+ update_avg(stat_key, sum, count)
51
52
  end
52
53
 
53
- def get_current_hash(stat_key)
54
- hash = get(stat_key)
55
- return JSON.parse(hash) if hash
56
- default_value
54
+ def update_count(stat_key)
55
+ hincrby(stat_key, "count", 1)
57
56
  end
58
57
 
59
- def increment(hash, value)
60
- hash["count"] += 1
61
- hash["sum"] += value
58
+ def update_sum(stat_key, value)
59
+ hincrby(stat_key, "sum", value)
62
60
  end
63
61
 
64
- def update_min(hash, value)
65
- hash["min"] = value if hash["min"].nil? || value < hash["min"]
62
+ def update_min(stat_key, value)
63
+ min = (hget(stat_key, "min") || 0).to_i
64
+ hset(stat_key, "min", value) if value < min || min == 0
66
65
  end
67
66
 
68
- def update_max(hash, value)
69
- hash["max"] = value if hash["max"].nil? || value > hash["max"]
67
+ def update_max(stat_key, value)
68
+ max = (hget(stat_key, "max") || 0).to_i
69
+ hset(stat_key, "max", value) if value > max || max == 0
70
70
  end
71
71
 
72
- def update_avg(hash)
73
- hash["avg"] = hash["sum"] / hash["count"]
72
+ def update_avg(stat_key, sum, count)
73
+ avg = sum.to_f / count.to_f
74
+ hset(stat_key, "avg", avg)
74
75
  end
75
76
 
76
77
  def default_value(nil_value=nil)
@@ -71,5 +71,13 @@ module Tabs
71
71
  redis.hkeys("tabs:#{key}")
72
72
  end
73
73
 
74
+ def hincrby(key, field, value)
75
+ redis.hincrby("tabs:#{key}", field, value)
76
+ end
77
+
78
+ def hgetall(key)
79
+ redis.hgetall("tabs:#{key}")
80
+ end
81
+
74
82
  end
75
83
  end
@@ -1,3 +1,3 @@
1
1
  module Tabs
2
- VERSION = "0.8.1"
2
+ VERSION = "0.8.2"
3
3
  end
@@ -16,7 +16,7 @@ describe Tabs::Metrics::Value do
16
16
  metric.record(42)
17
17
  time = Time.utc(now.year, now.month, now.day, now.hour)
18
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})
19
+ expect(stats).to include({ "timestamp"=>time, "count"=>2, "min"=>17, "max"=>42, "sum"=>59, "avg"=>29.5})
20
20
  end
21
21
 
22
22
  it "applys the value to the specified timestamp if one is supplied" do
@@ -58,42 +58,42 @@ describe Tabs::Metrics::Value do
58
58
  create_span(:minutes)
59
59
  stats = metric.stats(now..(now + 7.minutes), :minute)
60
60
  expect(stats).to include({ "timestamp" => (now + 3.minutes), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
61
- expect(stats).to include({ "timestamp" => (now + 6.minutes), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17})
61
+ expect(stats).to include({ "timestamp" => (now + 6.minutes), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
62
62
  end
63
63
 
64
64
  it "returns the expected results for an hourly metric" do
65
65
  create_span(:hours)
66
66
  stats = metric.stats(now..(now + 7.hours), :hour)
67
67
  expect(stats).to include({ "timestamp" => (now + 3.hours), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
68
- expect(stats).to include({ "timestamp" => (now + 6.hours), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17})
68
+ expect(stats).to include({ "timestamp" => (now + 6.hours), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
69
69
  end
70
70
 
71
71
  it "returns the expected results for a daily metric" do
72
72
  create_span(:days)
73
73
  stats = metric.stats(now..(now + 7.days), :day)
74
74
  expect(stats).to include({ "timestamp" => (now + 3.days), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
75
- expect(stats).to include({ "timestamp" => (now + 6.days), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17})
75
+ expect(stats).to include({ "timestamp" => (now + 6.days), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
76
76
  end
77
77
 
78
78
  it "returns the expected results for a weekly metric" do
79
79
  create_span(:weeks)
80
80
  stats = metric.stats(now..(now + 7.weeks), :week)
81
81
  expect(stats).to include({ "timestamp" => (now + 3.weeks).beginning_of_week, "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
82
- expect(stats).to include({ "timestamp" => (now + 6.weeks).beginning_of_week, "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17})
82
+ expect(stats).to include({ "timestamp" => (now + 6.weeks).beginning_of_week, "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
83
83
  end
84
84
 
85
85
  it "returns the expected results for a monthly metric" do
86
86
  create_span(:months)
87
87
  stats = metric.stats(now..(now + 7.months), :month)
88
88
  expect(stats).to include({ "timestamp" => (now + 3.months), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
89
- expect(stats).to include({ "timestamp" => (now + 6.months), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17})
89
+ expect(stats).to include({ "timestamp" => (now + 6.months), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
90
90
  end
91
91
 
92
92
  it "returns the expected results for a yearly metric" do
93
93
  create_span(:years)
94
94
  stats = metric.stats(now..(now + 7.years), :year)
95
95
  expect(stats).to include({ "timestamp" => (now + 3.years), "count"=>1, "min"=>10, "max"=>10, "sum"=>10, "avg"=>10})
96
- expect(stats).to include({ "timestamp" => (now + 6.years), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17})
96
+ expect(stats).to include({ "timestamp" => (now + 6.years), "count"=>2, "min"=>15, "max"=>20, "sum"=>35, "avg"=>17.5})
97
97
  end
98
98
  end
99
99
 
@@ -20,11 +20,25 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.post_install_message = <<EOS
22
22
  Tabs v0.8.0 - BREAKING CHANGES:
23
- The get_stats method now returns a more robust object instead of just
24
- an array of hashes. Existing data will continue to work (no changes were
25
- made to the underlying Redis keys). However, application code using
26
- tabs may need to be changed. Please review the README after installing
23
+ In version 0.8.0 and higher the get_stats method returns a more robust
24
+ object instead of just an array of hashes. These stats objects are
25
+ enumerable and most existing code utilizing tabs should continue to
26
+ function. However, please review the docs for more information if you
27
+ encounter issues when upgrading. Please review the README if installing
27
28
  v0.8.0 or higher.
29
+
30
+ Tabs v0.8.2 - BREAKING CHANGES:
31
+ In version 0.8.2 and higher the storage keys for value metrics have been
32
+ changed. Originally the various pieces (avg, sum, count, etc) were
33
+ stored in a JSON serialized string in a single key. The intent was that
34
+ this would comprise a poor-mans transaction of sorts. The downside
35
+ however was a major hit on performance when doing a lot of writes or
36
+ reading stats for a large date range. In v0.8.2 these component values
37
+ are stored in a real Redis hash and updated atomically when a value is
38
+ recorded. In future versions this will be changed to use a MULTI
39
+ statement to simulate a transaction. Value data that was recorded prior
40
+ to v0.8.2 will not be accessible in this or future versions so please
41
+ continue to use v0.8.1 or lower if that is an issue.
28
42
  EOS
29
43
 
30
44
  gem.add_dependency "activesupport", ">= 3.2"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-22 00:00:00.000000000 Z
12
+ date: 2013-08-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -186,16 +186,43 @@ homepage: https://github.com/thegrubbsian/tabs
186
186
  licenses: []
187
187
  post_install_message: ! 'Tabs v0.8.0 - BREAKING CHANGES:
188
188
 
189
- The get_stats method now returns a more robust object instead of just
189
+ In version 0.8.0 and higher the get_stats method returns a more robust
190
190
 
191
- an array of hashes. Existing data will continue to work (no changes were
191
+ object instead of just an array of hashes. These stats objects are
192
192
 
193
- made to the underlying Redis keys). However, application code using
193
+ enumerable and most existing code utilizing tabs should continue to
194
194
 
195
- tabs may need to be changed. Please review the README after installing
195
+ function. However, please review the docs for more information if you
196
+
197
+ encounter issues when upgrading. Please review the README if installing
196
198
 
197
199
  v0.8.0 or higher.
198
200
 
201
+
202
+ Tabs v0.8.2 - BREAKING CHANGES:
203
+
204
+ In version 0.8.2 and higher the storage keys for value metrics have been
205
+
206
+ changed. Originally the various pieces (avg, sum, count, etc) were
207
+
208
+ stored in a JSON serialized string in a single key. The intent was that
209
+
210
+ this would comprise a poor-mans transaction of sorts. The downside
211
+
212
+ however was a major hit on performance when doing a lot of writes or
213
+
214
+ reading stats for a large date range. In v0.8.2 these component values
215
+
216
+ are stored in a real Redis hash and updated atomically when a value is
217
+
218
+ recorded. In future versions this will be changed to use a MULTI
219
+
220
+ statement to simulate a transaction. Value data that was recorded prior
221
+
222
+ to v0.8.2 will not be accessible in this or future versions so please
223
+
224
+ continue to use v0.8.1 or lower if that is an issue.
225
+
199
226
  '
200
227
  rdoc_options: []
201
228
  require_paths: