stats 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.gemspec
2
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Matt Duncan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ Stats
2
+ =====
3
+
4
+ Requires the redis gem.
5
+
6
+ Easily keep track of vital stats using Redis.
7
+
8
+ There are currently three types of statistics that Stats captures:
9
+
10
+ 1. Simple
11
+ ---------
12
+ Simple stats are meant to be updated periodically. Only the latest value is
13
+ kept.
14
+
15
+ Stats.set("load", System.load)
16
+
17
+ 2. Counts
18
+ ---------
19
+ Counts can be incremented and decremented. Only the latest value is kept.
20
+
21
+ Stats.incr("downloads")
22
+
23
+ 3. Timings
24
+ ----------
25
+ Timings are used to keep track of the time it takes to run a block. All values
26
+ are kept so that they can be graphed/analyzed.
27
+
28
+ Stats.time("archive") { file.archive }
29
+
30
+
31
+ Installation
32
+ ------------
33
+
34
+ $ gem install stats
35
+
36
+
37
+ Author
38
+ ------
39
+
40
+ Matt Duncan
41
+ matt@mattduncan.org
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ desc "Runs all tests"
6
+ task :test => ['test:unit', 'test:integration']
7
+
8
+ namespace :test do
9
+ desc "Runs unit tests"
10
+ Rake::TestTask.new(:unit) do |t|
11
+ t.libs << 'test'
12
+ t.test_files = FileList['test/unit/test_*.rb']
13
+ end
14
+
15
+ desc "Runs integration tests"
16
+ Rake::TestTask.new(:integration) do |t|
17
+ t.libs << 'test'
18
+ t.test_files = FileList['test/integration/test_*.rb']
19
+ end
20
+ end
21
+
22
+ desc "Build a gem"
23
+ task :gem => [:gemspec, :build]
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "stats"
29
+ gemspec.summary = "Simple stats collection using Redis."
30
+ gemspec.email = "matt@mattduncan.org"
31
+ gemspec.homepage = "http://github.com/mrduncan/stats"
32
+ gemspec.authors = ["Matt Duncan"]
33
+ gemspec.version = '0.1.0'
34
+ gemspec.add_dependency 'redis'
35
+ gemspec.description = <<description
36
+ The stats gem is a simple way to keep track of different statistics using
37
+ Redis.
38
+ description
39
+ end
40
+ rescue LoadError
41
+ warn "Jeweler not available. Install it with:"
42
+ warn "gem install jeweler"
43
+ end
@@ -0,0 +1,23 @@
1
+ module Stats
2
+ module Helpers
3
+ # Sets the Redis server with the specified 'hostname:port[:db]' string or
4
+ # Redis object.
5
+ def redis=(server)
6
+ case server
7
+ when String
8
+ host, port, db = server.split(':')
9
+ @redis = Redis.new(:host => host, :port => port, :db => db, :thread_safe => true)
10
+ else
11
+ @redis = server
12
+ end
13
+ end
14
+
15
+ # Returns the current Redis connection. A default connection will be made
16
+ # if one doesn't yet exist.
17
+ def redis
18
+ return @redis if @redis
19
+ self.redis = 'localhost:6379'
20
+ self.redis
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ module Stats
2
+ module Times
3
+ # Returns the timing data for the stat with the specified name.
4
+ def get_times(name)
5
+ times = Stats.redis.lrange(name, 0, -1)
6
+ TimingStat.new(times.map { |t| t.to_f })
7
+ end
8
+
9
+ # Adds a new time for the stat with the specified name.
10
+ def time(name, &block)
11
+ raise ArgumentError.new("Block required to time.") unless block
12
+ realtime = Benchmark.realtime { block.call }
13
+ Stats.redis.rpush(name, realtime)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Stats
2
+ class TimingStat < Array
3
+ # Returns the average value.
4
+ def average
5
+ sum / length.to_f
6
+ end
7
+
8
+ # Returns the sample variance of the values.
9
+ def population_variance
10
+ avg = average
11
+ 1 / length.to_f * inject(0) { |acc, i| acc + (i - avg) ** 2 }
12
+ end
13
+
14
+ # Returns the standard deviation of the values.
15
+ def standard_deviation
16
+ Math.sqrt(population_variance)
17
+ end
18
+
19
+ private
20
+ # Returns the sum of all values.
21
+ def sum
22
+ inject(0) { |acc, i| acc + i }
23
+ end
24
+ end
25
+ end
data/lib/stats.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'benchmark'
2
+ require 'redis'
3
+ require 'stats/helpers'
4
+ require 'stats/times'
5
+ require 'stats/timing_stat'
6
+
7
+ module Stats
8
+ extend self
9
+ extend Helpers
10
+ extend Times
11
+
12
+ # Sets the stat with the specified name.
13
+ def set(name, value)
14
+ Stats.redis.set(name, value)
15
+ end
16
+
17
+ # Returns the value with the specified name.
18
+ def get(name)
19
+ Stats.redis.get(name)
20
+ end
21
+
22
+ # Increments the stat with the specified name.
23
+ def incr(name, by = 1)
24
+ Stats.redis.incrby(name, by)
25
+ end
26
+
27
+ # Decrements the stat with the specified name.
28
+ def decr(name, by = 1)
29
+ Stats.redis.decrby(name, by)
30
+ end
31
+
32
+ # Clears the stat with the specified name.
33
+ def clear(name)
34
+ Stats.redis.del(name)
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ require 'test/unit'
2
+ require 'mocha'
3
+ require 'stats'
4
+
5
+ class TestStats < Test::Unit::TestCase
6
+ def setup
7
+ Stats.redis = 'localhost:6379:stats_test'
8
+ Stats.redis.flush_db
9
+ end
10
+
11
+ def test_should_set
12
+ Stats.set("load", 1.2)
13
+ assert_equal 1.2, Stats.get("load").to_f
14
+ Stats.set("load", 1.7)
15
+ assert_equal 1.7, Stats.get("load").to_f
16
+ end
17
+
18
+ def test_should_increment
19
+ assert_equal 0, Stats.get("downloads").to_i
20
+ Stats.incr("downloads")
21
+ assert_equal 1, Stats.get("downloads").to_i
22
+ Stats.incr("downloads", 2)
23
+ assert_equal 3, Stats.get("downloads").to_i
24
+ end
25
+
26
+ def test_should_decrement
27
+ Stats.set("invitations", 100)
28
+ Stats.decr("invitations")
29
+ assert_equal 99, Stats.get("invitations").to_i
30
+ Stats.decr("invitations", 4)
31
+ assert_equal 95, Stats.get("invitations").to_i
32
+ end
33
+
34
+ def test_should_clear
35
+ Stats.set("space_left", 100)
36
+ Stats.clear("space_left")
37
+ assert_nil Stats.get("space_left")
38
+ end
39
+
40
+ def test_should_time
41
+ Benchmark.expects(:realtime).returns(2)
42
+ Stats.time("archive") { "takes 2" }
43
+ Benchmark.expects(:realtime).returns(3)
44
+ Stats.time("archive") { "takes 3" }
45
+ assert_equal [2, 3], Stats.get_times("archive")
46
+ end
47
+ end
@@ -0,0 +1,75 @@
1
+ require 'test/unit'
2
+ require 'mocha'
3
+ require 'stats'
4
+
5
+ class TestStats < Test::Unit::TestCase
6
+ def setup
7
+ Stats.instance_variable_set(:@redis, nil)
8
+ end
9
+
10
+ def test_should_initialize_redis_with_host_port
11
+ Redis.expects(:new).with(:host => 'localhost',
12
+ :port => '6379',
13
+ :db => nil,
14
+ :thread_safe => true).returns(mock('redis'))
15
+ Stats.redis = 'localhost:6379'
16
+ end
17
+
18
+ def test_should_initialize_redis_with_host_port_db
19
+ Redis.expects(:new).with(:host => 'localhost',
20
+ :port => '6379',
21
+ :db => 'stats',
22
+ :thread_safe => true).returns(mock('redis'))
23
+ Stats.redis = 'localhost:6379:stats'
24
+ end
25
+
26
+ def test_should_initialize_redis_if_not_set
27
+ Redis.expects(:new).with(:host => 'localhost',
28
+ :port => '6379',
29
+ :db => nil,
30
+ :thread_safe => true).returns(mock('redis'))
31
+ Stats.redis
32
+ end
33
+
34
+ def test_should_set
35
+ Stats.redis = mock('redis')
36
+ Stats.redis.expects(:set).with("load", 2.1)
37
+ Stats.set("load", 2.1)
38
+ end
39
+
40
+ def test_should_get
41
+ Stats.redis = mock('redis')
42
+ Stats.redis.expects(:get).with("load").returns(1.1)
43
+ assert_equal 1.1, Stats.get("load")
44
+ end
45
+
46
+ def test_should_incr_by_one
47
+ Stats.redis = mock('redis')
48
+ Stats.redis.expects(:incrby).with("downloads", 1)
49
+ Stats.incr("downloads")
50
+ end
51
+
52
+ def test_should_incr_by_many
53
+ Stats.redis = mock('redis')
54
+ Stats.redis.expects(:incrby).with("purchased_items", 5)
55
+ Stats.incr("purchased_items", 5)
56
+ end
57
+
58
+ def test_should_decr_by_one
59
+ Stats.redis = mock('redis')
60
+ Stats.redis.expects(:decrby).with("invitations", 1)
61
+ Stats.decr("invitations")
62
+ end
63
+
64
+ def test_should_decr_by_many
65
+ Stats.redis = mock('redis')
66
+ Stats.redis.expects(:decrby).with("tickets_left", 4)
67
+ Stats.decr("tickets_left", 4)
68
+ end
69
+
70
+ def test_should_clear
71
+ Stats.redis = mock('redis')
72
+ Stats.redis.expects(:del).with("downloads")
73
+ Stats.clear("downloads")
74
+ end
75
+ end
@@ -0,0 +1,24 @@
1
+ require 'test/unit'
2
+ require 'mocha'
3
+ require 'stats'
4
+
5
+ class TestStats < Test::Unit::TestCase
6
+ def test_should_add_timing
7
+ Stats.redis = mock('redis')
8
+ Stats.redis.expects(:rpush).with("archive", 1)
9
+ Benchmark.expects(:realtime).returns(1)
10
+ Stats.time("archive") { "noop" }
11
+ end
12
+
13
+ def test_should_raise_arguement_error_when_timing_without_block
14
+ assert_raise(ArgumentError) do
15
+ Stats.time("archive")
16
+ end
17
+ end
18
+
19
+ def test_should_get_times
20
+ Stats.redis = mock('redis')
21
+ Stats.redis.expects(:lrange).with("archive", 0, -1).returns(["1", "2"])
22
+ assert_equal [1, 2], Stats.get_times("archive")
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/unit'
2
+ require 'mocha'
3
+ require 'stats'
4
+
5
+ class TestStats < Test::Unit::TestCase
6
+ def test_should_average
7
+ assert_equal 2.0, Stats::TimingStat.new([1, 2, 2, 3]).average
8
+ end
9
+
10
+ def test_should_get_population_variance
11
+ assert_equal 0.25, Stats::TimingStat.new([1, 1, 2, 2]).population_variance
12
+ end
13
+
14
+ def test_should_get_standard_deviation
15
+ assert_equal 0.5, Stats::TimingStat.new([1, 1, 2, 2]).standard_deviation
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stats
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Matt Duncan
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-17 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: redis
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ description: |
33
+ The stats gem is a simple way to keep track of different statistics using
34
+ Redis.
35
+
36
+ email: matt@mattduncan.org
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.md
44
+ files:
45
+ - .gitignore
46
+ - LICENSE
47
+ - README.md
48
+ - Rakefile
49
+ - lib/stats.rb
50
+ - lib/stats/helpers.rb
51
+ - lib/stats/times.rb
52
+ - lib/stats/timing_stat.rb
53
+ - test/integration/test_stats.rb
54
+ - test/unit/test_stats.rb
55
+ - test/unit/test_times.rb
56
+ - test/unit/test_timing_stat.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/mrduncan/stats
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --charset=UTF-8
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.6
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Simple stats collection using Redis.
87
+ test_files:
88
+ - test/integration/test_stats.rb
89
+ - test/unit/test_stats.rb
90
+ - test/unit/test_times.rb
91
+ - test/unit/test_timing_stat.rb