abaci 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## Abaci 0.1.0
2
+
3
+ * Initial build
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Abaci
2
+
3
+ [![Build Status](https://secure.travis-ci.org/jdtornow/abaci.png)](http://travis-ci.org/jdtornow/abaci) [![Dependency Status](https://gemnasium.com/jdtornow/abaci.png?travis)](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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Abaci
2
+ VERSION = "0.1.0" unless defined?(::Abaci::VERSION)
3
+ 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: