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 +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
|