stats 0.1.0

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