abaci 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/CHANGELOG.md +3 -0
- data/README.md +75 -0
- data/lib/abaci.rb +28 -0
- data/lib/abaci/counter.rb +100 -0
- data/lib/abaci/store.rb +48 -0
- data/lib/abaci/version.rb +3 -0
- metadata +69 -0
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Abaci
|
2
|
+
|
3
|
+
[](http://travis-ci.org/jdtornow/abaci) [](https://gemnasium.com/jdtornow/abaci)
|
4
|
+
|
5
|
+
Abaci (pronounced abba-sigh) is a simple structure for the collection and reporting on numerical counters. Counters can be used to track time-based statistics for a wide array of functions within your application. Abaci uses a Redis backend to store all counters for lightning quick read and write access.
|
6
|
+
|
7
|
+
What does *abaci* mean? Its the plural form of [Abacus](http://en.wikipedia.org/wiki/Abacus), a common arithmetic assistance device from before we had computers. Imagine that.
|
8
|
+
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
* Ruby 1.9.2+
|
12
|
+
* Redis
|
13
|
+
|
14
|
+
## Configuration
|
15
|
+
|
16
|
+
Abaci will use the default Redis instance by default, assumed to be available via `Redis.current`. To use with a specific Redis instance, pass a `Redis` object to `Abaci.options[:redis]`. You can use it this way with [Redis Namespace](https://github.com/defunkt/redis-namespace) if necessary.
|
17
|
+
|
18
|
+
By default, the prefix `ab` is used before all stats stored in Redis. If you are using running multiple apps using the same Redis instance (likely in development mode), you can change the prefix by setting `Abaci.options[:prefix]`.
|
19
|
+
|
20
|
+
Here's a sample configuration that we use often:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
connection = Redis.new
|
24
|
+
REDIS = Redis::Namespace.new(:ns, :redis => connection)
|
25
|
+
|
26
|
+
Abaci.options[:redis] = REDIS
|
27
|
+
Abaci.options[:prefix] = 'stats'
|
28
|
+
```
|
29
|
+
|
30
|
+
If you are using Rails, put that in an initializer and you're good to go.
|
31
|
+
|
32
|
+
## Basic Usage
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# Log page views
|
36
|
+
Abaci['views'].increment # => 1
|
37
|
+
Abaci['views'].increment # => 2
|
38
|
+
|
39
|
+
# Increment (and decrement) all take a parameter or default to incrementing by 1
|
40
|
+
Abaci['views'].increment(3) # => 5
|
41
|
+
|
42
|
+
# Decrementing is available if needed
|
43
|
+
Abaci['views'].decrement # => 4
|
44
|
+
|
45
|
+
# Get total number of views
|
46
|
+
Abaci['views'].get # => 4
|
47
|
+
|
48
|
+
# Get total number of views in year 2012
|
49
|
+
Abaci['views'].get(2012) # => 4
|
50
|
+
|
51
|
+
# Get total number of views in October of 2012
|
52
|
+
Abaci['views'].get(2012, 10) # => 4
|
53
|
+
|
54
|
+
# Get total number of views on October 20, 2012
|
55
|
+
Abaci['views'].get(2012, 10, 20) # => 4
|
56
|
+
|
57
|
+
# Get total views in the last 30 days
|
58
|
+
Abaci['views'].get_last_days(30) # => 4
|
59
|
+
|
60
|
+
# View all counter stat keys stored
|
61
|
+
Abaci['views'].keys
|
62
|
+
|
63
|
+
# Clear counters for views
|
64
|
+
Abaci['views'].del
|
65
|
+
```
|
66
|
+
|
67
|
+
## Issues
|
68
|
+
|
69
|
+
If you have any issues or find bugs running Abaci, please [report them on Github](https://github.com/jdtornow/abaci/issues). While most functions should be stable, Abaci is still in its infancy and certain issues may be present.
|
70
|
+
|
71
|
+
## License
|
72
|
+
|
73
|
+
Abaci is released under the [MIT license](http://www.opensource.org/licenses/MIT)
|
74
|
+
|
75
|
+
Contributions and pull-requests are more than welcome.
|
data/lib/abaci.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'abaci/version'
|
2
|
+
|
3
|
+
module Abaci
|
4
|
+
autoload :Counter, 'abaci/counter'
|
5
|
+
autoload :Store, 'abaci/store'
|
6
|
+
|
7
|
+
# Configuration options
|
8
|
+
class << self
|
9
|
+
def [](key)
|
10
|
+
Counter[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def options
|
14
|
+
@options ||= {
|
15
|
+
:redis => nil,
|
16
|
+
:prefix => 'ab'
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def prefix
|
21
|
+
options[:prefix]
|
22
|
+
end
|
23
|
+
|
24
|
+
def store
|
25
|
+
@store ||= Store.new(options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Abaci
|
2
|
+
class Counter
|
3
|
+
attr_reader :key
|
4
|
+
|
5
|
+
def initialize(key = nil)
|
6
|
+
@key = key
|
7
|
+
end
|
8
|
+
|
9
|
+
def decr(by = 1)
|
10
|
+
decrement_at(nil, by)
|
11
|
+
end
|
12
|
+
alias_method :decrement, :decr
|
13
|
+
|
14
|
+
def decrement_at(date = nil, by = 1)
|
15
|
+
date = Time.now unless date.respond_to?(:strftime)
|
16
|
+
run(:decrby, by, date)
|
17
|
+
end
|
18
|
+
|
19
|
+
def del
|
20
|
+
keys.each { |k| Abaci.store.del(k) }
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(year = nil, month = nil, day = nil, hour = nil, min = nil)
|
25
|
+
get_key = [ key, year, month, day, hour, min ].compact.join(':')
|
26
|
+
Abaci.store.get(get_key).to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_last_days(number_of_days = 30)
|
30
|
+
seconds = number_of_days.to_i * 86400
|
31
|
+
start = (Date.today - Rational(seconds, 86400)).to_date
|
32
|
+
dates = (start..Date.today).map { |d| d.strftime('%Y:%-m:%-d') }
|
33
|
+
dates.map { |date| Abaci.store.get("#{key}:#{date}" ).to_i }.reduce(:+)
|
34
|
+
end
|
35
|
+
|
36
|
+
def incr(by = 1)
|
37
|
+
increment_at(nil, by)
|
38
|
+
end
|
39
|
+
alias_method :increment, :incr
|
40
|
+
|
41
|
+
def increment_at(date = nil, by = 1)
|
42
|
+
date = Time.now unless date.respond_to?(:strftime)
|
43
|
+
run(:incrby, by, date)
|
44
|
+
end
|
45
|
+
|
46
|
+
def keys
|
47
|
+
Abaci.store.keys("#{key}*")
|
48
|
+
end
|
49
|
+
|
50
|
+
## Class methods ##
|
51
|
+
############################################################################
|
52
|
+
|
53
|
+
# Returns a hash of all current values
|
54
|
+
def self.all
|
55
|
+
keys.inject({}) { |hash, key| hash[key] = Abaci.store.get(key).to_i; hash }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Gets all currently logged stat keys
|
59
|
+
def self.keys(search = "*")
|
60
|
+
Abaci.store.keys(search).sort
|
61
|
+
end
|
62
|
+
|
63
|
+
# Alias for Counter#new(key)
|
64
|
+
def self.[](key)
|
65
|
+
Counter.new(key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.method_missing(method, *args)
|
69
|
+
ms = method.to_s.downcase.strip
|
70
|
+
|
71
|
+
if ms =~ /(incr|decr)(ement|ease)?_([a-z_]*)$/
|
72
|
+
return self[$3].send($1, *args)
|
73
|
+
elsif ms =~ /^(clear|reset|del)_([a-z_]*)!$/
|
74
|
+
return self[$2].del
|
75
|
+
elsif ms =~ /^last_(\d*)_days_of_([a-z_]*)$/
|
76
|
+
return self[$2].get_last_days($1)
|
77
|
+
elsif ms =~ /[a-z_]*/
|
78
|
+
return self[ms].get(*args)
|
79
|
+
end
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
## Protected methods ##
|
85
|
+
############################################################################
|
86
|
+
|
87
|
+
protected
|
88
|
+
def run(method, by, date)
|
89
|
+
now = date.strftime('%Y/%m/%d/%k/%M').split('/')
|
90
|
+
|
91
|
+
now.inject(key) do |memo, t|
|
92
|
+
memo = "#{memo}:#{t.to_i}"
|
93
|
+
Abaci.store.send(method, memo, by)
|
94
|
+
memo
|
95
|
+
end
|
96
|
+
|
97
|
+
Abaci.store.send(method, key, by)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/abaci/store.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Abaci
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
# Common interface for Redis. In the future this could be
|
5
|
+
# swapped out for an alternate datastore.
|
6
|
+
class Store
|
7
|
+
def initialize(options)
|
8
|
+
@redis = options[:redis] || Redis.current
|
9
|
+
@prefix = options[:prefix] || 'ab'
|
10
|
+
end
|
11
|
+
|
12
|
+
def decrby(key, by = 1)
|
13
|
+
exec(:decrby, key, by)
|
14
|
+
end
|
15
|
+
|
16
|
+
def del(key)
|
17
|
+
exec(:del, key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(key)
|
21
|
+
exec(:get, key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def incrby(key, by = 1)
|
25
|
+
exec(:incrby, key, by)
|
26
|
+
end
|
27
|
+
|
28
|
+
def keys(pattern)
|
29
|
+
sub = Regexp.new("^#{Abaci.prefix}:")
|
30
|
+
exec(:keys, pattern).map { |k| k.gsub(sub, '') }
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(key, value)
|
34
|
+
exec(:set, key, value)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def exec(command, key, *args)
|
39
|
+
if @redis and @redis.respond_to?(command)
|
40
|
+
@redis.send(command, prefixed_key(key), *args)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def prefixed_key(key)
|
45
|
+
[ @prefix, key ].compact.join(':')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abaci
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Tornow
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
description: Collect stats on events, activities and behaviors across time in Ruby
|
31
|
+
with a Redis backend.
|
32
|
+
email:
|
33
|
+
- john@johntornow.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/abaci/counter.rb
|
39
|
+
- lib/abaci/store.rb
|
40
|
+
- lib/abaci/version.rb
|
41
|
+
- lib/abaci.rb
|
42
|
+
- README.md
|
43
|
+
- CHANGELOG.md
|
44
|
+
homepage: http://github.com/jdtornow/abaci
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.8.7
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.8.23
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: A basic stats reporting and collection tool for Ruby.
|
68
|
+
test_files: []
|
69
|
+
has_rdoc:
|