stats 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/LICENSE +20 -0
- data/README.md +41 -0
- data/Rakefile +43 -0
- data/lib/stats/helpers.rb +23 -0
- data/lib/stats/times.rb +16 -0
- data/lib/stats/timing_stat.rb +25 -0
- data/lib/stats.rb +36 -0
- data/test/integration/test_stats.rb +47 -0
- data/test/unit/test_stats.rb +75 -0
- data/test/unit/test_times.rb +24 -0
- data/test/unit/test_timing_stat.rb +17 -0
- metadata +91 -0
data/.gitignore
ADDED
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
|
data/lib/stats/times.rb
ADDED
@@ -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
|