redis_analytics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Gemfile +4 -0
  2. data/Gemfile.lock +57 -0
  3. data/LICENSE +19 -0
  4. data/README.md +98 -0
  5. data/Rakefile +19 -0
  6. data/bin/GeoIP.dat +0 -0
  7. data/bin/redis_analytics_dashboard +17 -0
  8. data/lib/redis_analytics.rb +22 -0
  9. data/lib/redis_analytics/analytics.rb +231 -0
  10. data/lib/redis_analytics/config.ru +10 -0
  11. data/lib/redis_analytics/configuration.rb +71 -0
  12. data/lib/redis_analytics/dashboard.rb +114 -0
  13. data/lib/redis_analytics/dashboard/public/css/bootstrap-responsive.min.css +9 -0
  14. data/lib/redis_analytics/dashboard/public/css/bootstrap.min.css +9 -0
  15. data/lib/redis_analytics/dashboard/public/css/jquery-jvectormap-1.2.2.css +37 -0
  16. data/lib/redis_analytics/dashboard/public/css/morris.css +2 -0
  17. data/lib/redis_analytics/dashboard/public/favicon.ico +0 -0
  18. data/lib/redis_analytics/dashboard/public/img/glyphicons-halflings-white.png +0 -0
  19. data/lib/redis_analytics/dashboard/public/img/glyphicons-halflings.png +0 -0
  20. data/lib/redis_analytics/dashboard/public/javascripts/bootstrap.min.js +6 -0
  21. data/lib/redis_analytics/dashboard/public/javascripts/jquery-1.9.1.min.js +5 -0
  22. data/lib/redis_analytics/dashboard/public/javascripts/jquery-jvectormap-1.2.2.min.js +8 -0
  23. data/lib/redis_analytics/dashboard/public/javascripts/jquery-jvectormap-world-mill-en.js +1 -0
  24. data/lib/redis_analytics/dashboard/public/javascripts/morris.min.js +1 -0
  25. data/lib/redis_analytics/dashboard/public/javascripts/raphael-min.js +10 -0
  26. data/lib/redis_analytics/dashboard/views/activity.erb +0 -0
  27. data/lib/redis_analytics/dashboard/views/footer.erb +5 -0
  28. data/lib/redis_analytics/dashboard/views/header.erb +44 -0
  29. data/lib/redis_analytics/dashboard/views/layout.erb +33 -0
  30. data/lib/redis_analytics/dashboard/views/visits.erb +176 -0
  31. data/lib/redis_analytics/dashboard/views/visits_js.erb +265 -0
  32. data/lib/redis_analytics/helpers.rb +69 -0
  33. data/lib/redis_analytics/railtie.rb +13 -0
  34. data/lib/redis_analytics/time_ext.rb +19 -0
  35. data/lib/redis_analytics/tracker.rb +26 -0
  36. data/lib/redis_analytics/version.rb +5 -0
  37. data/redis_analytics.gemspec +37 -0
  38. data/screenshot.png +0 -0
  39. data/spec/redis_analytics_spec.rb +57 -0
  40. data/spec/spec_helper.rb +47 -0
  41. metadata +216 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in redis_analytics.gemspec
4
+ gemspec
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redis_analytics (0.1.0)
5
+ activesupport (>= 3.2.0)
6
+ browser (>= 0.1.6)
7
+ geoip (>= 1.2.1)
8
+ json (>= 1.7.7)
9
+ rack (>= 1.4.0)
10
+ redis (>= 3.0.2)
11
+ sinatra (>= 1.3.3)
12
+
13
+ GEM
14
+ remote: http://rubygems.org/
15
+ specs:
16
+ activesupport (3.2.13)
17
+ i18n (= 0.6.1)
18
+ multi_json (~> 1.0)
19
+ browser (0.1.6)
20
+ diff-lcs (1.1.3)
21
+ geoip (1.2.1)
22
+ i18n (0.6.1)
23
+ json (1.7.7)
24
+ metaclass (0.0.1)
25
+ mocha (0.12.7)
26
+ metaclass (~> 0.0.1)
27
+ multi_json (1.7.1)
28
+ rack (1.5.2)
29
+ rack-protection (1.5.0)
30
+ rack
31
+ rack-test (0.6.2)
32
+ rack (>= 1.0)
33
+ rake (10.0.3)
34
+ redis (3.0.2)
35
+ rspec (2.11.0)
36
+ rspec-core (~> 2.11.0)
37
+ rspec-expectations (~> 2.11.0)
38
+ rspec-mocks (~> 2.11.0)
39
+ rspec-core (2.11.1)
40
+ rspec-expectations (2.11.3)
41
+ diff-lcs (~> 1.1.3)
42
+ rspec-mocks (2.11.3)
43
+ sinatra (1.3.3)
44
+ rack (~> 1.3, >= 1.3.6)
45
+ rack-protection (~> 1.2)
46
+ tilt (~> 1.3, >= 1.3.3)
47
+ tilt (1.3.6)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ mocha (>= 0.12.7)
54
+ rack-test (>= 0.6.2)
55
+ rake (>= 10.0.3)
56
+ redis_analytics!
57
+ rspec (>= 2.11.0)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Schubert Cardozo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,98 @@
1
+ # redis_analytics [![Build Status](https://travis-ci.org/saturnine/redis_analytics.png?branch=master)](https://travis-ci.org/saturnine/redis_analytics)
2
+
3
+ ## What is redis_analytics?
4
+
5
+ A gem that provides a Redis based web analytics solution for your rack-compliant apps
6
+
7
+ ## Why should I use it?
8
+
9
+ It gives you detailed analytics about visitors, unique visitors, browsers, OS, visitor recency, traffic sources and more
10
+
11
+ ## Does it have a cool dashboard?
12
+
13
+ Yes, It uses the excellent [Morris.js](http://www.oesmith.co.uk/morris.js/) for the main dashboard and [Highcharts](http://www.highcharts.com) for drawing the various detailed graphs
14
+
15
+ ![Screenshot](https://github.com/saturnine/redis_analytics/raw/master/screenshot.png)
16
+
17
+ ## OK, so how do I install it?
18
+
19
+ `gem install redis_analytics`
20
+
21
+ or in your `Gemfile`
22
+
23
+ ```ruby
24
+ gem 'redis_analytics'
25
+ ```
26
+
27
+ Make sure your redis server is running! Redis configuration is outside the scope of this README, but
28
+ check out the [Redis documentation](http://redis.io/documentation).
29
+
30
+ ## How do I enable tracking in my rack-compliant app?
31
+
32
+ ### Step 1: Load the redis_analytics library and configure it
33
+
34
+ ```ruby
35
+ # this is not required unless you use :require => false in your Gemfile
36
+ require 'redis_analytics'
37
+
38
+ # configure your redis connection (this is mandatory) and namespace (this is optional)
39
+ Rack::RedisAnalytics.configure do |configuration|
40
+ configuration.redis_connection = Redis.new(:host => 'localhost', :port => '6379')
41
+ configuration.redis_namespace = 'ra'
42
+ end
43
+ ```
44
+ ### Step 2: Use the Tracker rack middleware (NOT REQUIRED FOR RAILS)
45
+
46
+ ```ruby
47
+ # in Sinatra you would do...
48
+ use Rack::RedisAnalytics::Tracker
49
+ ```
50
+
51
+ For rails the middleware is added automatically, so you do not need to add it manually using `config.middleware.use`
52
+
53
+ ## Where do I view the dashboard?
54
+
55
+ ### Option 1: Set a dashboard endpoint in your configuration
56
+
57
+ ```ruby
58
+ Rack::RedisAnalytics.configure do |configuration|
59
+ configuration.dashboard_endpoint = '/dashboard'
60
+ end
61
+ ```
62
+
63
+ and navigate to [http://localhost:3000/dashboard](http://localhost:3000/dashboard) assuming your rack-compliant app is hosted at [http://localhost:3000](http://localhost:3000)
64
+
65
+ ### Option 2: Simply run the bundled Sinatra application binary
66
+
67
+ `redis_analytics_dashboard --redis-host 127.0.0.1 --redis-port 6379 --redis-namespace ra`
68
+
69
+ and navigate to [http://localhost:4567](http://localhost:4567)
70
+
71
+ ## What if I have multiple rails apps that I want to track as one?
72
+
73
+ In the configuration, keep the value of redis_namespace the same across all your rails apps
74
+
75
+ ```ruby
76
+ Rack::RedisAnalytics.configure do |configuration|
77
+ configuration.redis_connection = Redis.new(:host => 'localhost', :port => '6379')
78
+ configuration.redis_namespace = 'mywebsite.org'
79
+ end
80
+ ```
81
+
82
+ ## Why is the Geolocation tracking giving me wrong results?
83
+
84
+ IP based Geolocation works using [MaxMind's](http://www.maxmind.com) GeoLite database. The free version is not as accurate as their commercial version.
85
+ Also it is recommended to regularly get an updated binary of 'GeoLite Country' database from [here](http://dev.maxmind.com/geoip/geolite) and extract the GeoIP.dat file into a local directory.
86
+ You will then need to point to the GeoIP.dat file in your configuration.
87
+
88
+ ```ruby
89
+ Rack::RedisAnalytics.configure do |configuration|
90
+ configuration.redis_connection = Redis.new(:host => 'localhost', :port => '6379')
91
+ configuration.redis_namespace = 'mywebsite.org'
92
+ configuration.geo_ip_data_path = '/path/to/GeoIP.dat'
93
+ end
94
+ ```
95
+
96
+ ## Copyright
97
+
98
+ Copyright (c) 2012-2013 Schubert Cardozo. See LICENSE for further details.
@@ -0,0 +1,19 @@
1
+ # require 'bundler/gem_tasks'
2
+ require 'rake'
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+
6
+ # encoding: UTF-8
7
+ require 'rubygems'
8
+ begin
9
+ require 'bundler/setup'
10
+ rescue LoadError
11
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new(:spec) do |spec|
15
+ spec.pattern = 'spec/**/*_spec.rb'
16
+ spec.rspec_opts = ['--backtrace']
17
+ end
18
+
19
+ task :default => :spec
Binary file
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ path = File.expand_path '../../lib', __FILE__
5
+ $:.unshift(path) if File.directory?(path) && !$:.include?(path)
6
+ require 'redis_analytics/dashboard'
7
+ args = Hash[*ARGV] rescue {}
8
+ if args.key?('--redis-host') and args.key?('--redis-port')
9
+ Rack::RedisAnalytics.configure do |c|
10
+ c.redis_connection = Redis.new(:host => args['--redis-host'], :port => args['--redis-port'], :db => args['--redis-db'])
11
+ c.redis_namespace = args['--redis-namespace'] if args['--redis-namespace']
12
+ end
13
+ Rack::RedisAnalytics::Dashboard.set(:port, args['-p']) if args['-p']
14
+ Rack::RedisAnalytics::Dashboard.run!
15
+ else
16
+ puts "Usage: redis_analytics_dashboard --redis-host 127.0.0.1 --redis-port 6379 --redis-db 0 --redis-namespace test_namespace"
17
+ end
@@ -0,0 +1,22 @@
1
+ require 'rack'
2
+ require 'redis'
3
+ require 'browser'
4
+ require 'sinatra'
5
+ require 'geoip'
6
+
7
+ require 'redis_analytics'
8
+ require 'redis_analytics/version'
9
+ require 'redis_analytics/configuration'
10
+ require 'redis_analytics/analytics'
11
+ require 'redis_analytics/time_ext'
12
+ require 'redis_analytics/helpers'
13
+ require 'redis_analytics/dashboard'
14
+
15
+ require 'redis_analytics/tracker'
16
+ require 'redis_analytics/railtie' if defined? Rails::Railtie
17
+
18
+ module Rack
19
+ module RedisAnalytics
20
+ extend Configuration
21
+ end
22
+ end
@@ -0,0 +1,231 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'digest/md5'
3
+ module Rack
4
+ module RedisAnalytics
5
+
6
+ class Analytics
7
+
8
+ PAGEVIEWS = [['/health', 'o', 'd', 't'], ['/track', 'o', 'd', 't']]
9
+
10
+ TRANSACTIONS = {
11
+ "flights_search" => ["/flights/search", :o, :d],
12
+ "flights_results" => ["/flights/results", :o, :d],
13
+ "flights_book_step_1" => ["/flights/itinerary/:itinerary_id/info"],
14
+ "flights_book_step_2" => ["/flights/itinerary/:itinerary_id/traveller"],
15
+ "flights_book_step_3" => ["/flights/itinerary/:itinerary_id/pay"],
16
+ "flights_book_done" => ["/flights/itinerary/:itinerary_id/confirmation"],
17
+ "products" => ["/products/:id/review", :user_id]
18
+ }
19
+
20
+ REFERRERS = ['google', 'bing', 'yahoo', 'cleartrip', 'github']
21
+
22
+ def initialize(app)
23
+ @app = app
24
+ @redis_key_prefix = "#{RedisAnalytics.redis_namespace}:"
25
+ end
26
+
27
+ # def call(env)
28
+ # dup.call!(env)
29
+ # end
30
+
31
+ def call(env)
32
+ @env = env
33
+ @request = Request.new(env)
34
+ status, headers, body = @app.call(env)
35
+ @response = Rack::Response.new(body, status, headers)
36
+ record if @response.ok? and @response.content_type =~ /^text\/html/
37
+ @response.finish
38
+ end
39
+
40
+ def parse_path(path)
41
+ keys = []
42
+ spc = %w{. + ( )}
43
+ pattern =
44
+ path.to_str.gsub(/((:\w+)|[\*#{spc.join}])/) do |match|
45
+ case match
46
+ # when "*"
47
+ # keys << 'splat'
48
+ # "(.*?)"
49
+ when *spc
50
+ Regexp.escape(match)
51
+ else
52
+ keys << $2[1..-1]
53
+ "([^/?&#]+)"
54
+ end
55
+ end
56
+ [/^#{pattern}$/, keys]
57
+ end
58
+
59
+ def for_each_time_range(t)
60
+ RedisAnalytics.redis_key_timestamps.map{|x, y| [t.strftime(x), y]}.each do |ts, expire|
61
+ yield(ts, expire) # returns an array of the redis methods to call
62
+ # r.each do |method, args|
63
+ # RedisAnalytics.redis_connection.send(method, *args)
64
+ # RedisAnalytics.redis_connection.expire(args[1]) if expire # assuming args[1] is always the key that is being operated on.. will this always work?
65
+ # end
66
+ end
67
+ end
68
+
69
+ def record
70
+ t = Time.now
71
+
72
+ # record pageviews
73
+ path = @request.path
74
+ params = @request.params
75
+ if i = PAGEVIEWS.index{|x| x[0] == path}
76
+ page = PAGEVIEWS[i]
77
+ params.select{|x, y| page[1..-1].include?(x)}.each do |k, v|
78
+ for_each_time_range(t) do |ts, expire|
79
+ h = Digest::MD5.hexdigest(v)
80
+ RedisAnalytics.redis_connection.hset("#{@redis_key_prefix}page", h, v)
81
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}page", expire) if expire
82
+
83
+ RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}page_#{i}_#{page.index(k)}_#{h}:#{ts}")
84
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}page_#{i}_#{page.index(k)}_#{h}:#{ts}", expire) if expire
85
+ end
86
+ end
87
+ end
88
+
89
+ returning_visitor = @request.cookies[RedisAnalytics.returning_user_cookie_name]
90
+ recent_visitor = @request.cookies[RedisAnalytics.visit_cookie_name]
91
+
92
+ vcn_seq, rucn_seq = nil
93
+ visit_start_time =visit_end_time = first_visit_time = t.to_i
94
+
95
+ if not returning_visitor and not recent_visitor
96
+ rucn_seq = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}unique_visits")
97
+ vcn_seq = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}visits")
98
+ visit(t, :rucn_seq => rucn_seq)
99
+ new_visit(t)
100
+ visit_time(t, t.to_i)
101
+ page_view(t)
102
+ elsif returning_visitor and not recent_visitor
103
+ vcn_seq = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}visits")
104
+ rucn_seq, first_visit_time, last_visit_time = returning_visitor.split('.')
105
+ visit(t, :last_visit_time => last_visit_time.to_i, :rucn_seq => rucn_seq)
106
+ page_view(t)
107
+ elsif returning_visitor and recent_visitor
108
+ rucn_seq, vcn_seq, visit_start_time, visit_end_time = recent_visitor.split('.')
109
+ rucn_seq, first_visit_time = returning_visitor.split('.')
110
+ visit_time(t, visit_end_time.to_i)
111
+ page_view(t, visit_start_time.to_i == visit_end_time.to_i)
112
+ elsif not returning_visitor and recent_visitor
113
+ rucn_seq, vcn_seq, visit_start_time, visit_end_time = recent_visitor.split('.')
114
+ visit_time(t, visit_end_time.to_i)
115
+ page_view(t, visit_start_time.to_i == visit_end_time.to_i)
116
+ end
117
+
118
+ # create the recent visit cookie
119
+ @response.set_cookie(RedisAnalytics.visit_cookie_name, {:value => "#{rucn_seq}.#{vcn_seq}.#{visit_start_time}.#{t.to_i}", :expires => t + (RedisAnalytics.visit_timeout.to_i * 60 )})
120
+
121
+ # create the permanent cookie (2 years)
122
+ @response.set_cookie(RedisAnalytics.returning_user_cookie_name, {:value => "#{rucn_seq}.#{first_visit_time}.#{t.to_i}", :expires => t + (60 * 60 * 24 * 5)}) # 5 hours temp
123
+
124
+ puts "TIME = [#{t}]"
125
+ puts "VISIT = #{vcn_seq}"
126
+ puts "UNIQUE VISIT = #{rucn_seq}"
127
+ end
128
+
129
+ def new_visit(t)
130
+ for_each_time_range(t) do |ts, expire|
131
+ RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}new_visits:#{ts}")
132
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}new_visits:#{ts}",expire) if expire
133
+ end
134
+ end
135
+
136
+ def visit(t, options = {})
137
+ last_visit_time = options[:last_visit_time] || nil
138
+ rucn_seq = options[:rucn_seq] || nil
139
+ geo_country_code = nil
140
+ referrer = nil
141
+ ua = nil
142
+
143
+ # Geo IP Country code fetch
144
+ if defined?(GeoIP)
145
+ begin
146
+ g = GeoIP.new(RedisAnalytics.geo_ip_data_path)
147
+ geo_country_code = g.country(@request.ip).to_hash[:country_code2]
148
+ puts "Tracking IP #{@request.ip} using #{g.inspect} => GOT #{geo_country_code}"
149
+ rescue Exception => e
150
+ puts "Warning: Unable to fetch country info #{e}"
151
+ end
152
+ end
153
+
154
+ # Referrer regex decode
155
+ if @request.referrer
156
+ REFERRERS.each do |referrer|
157
+ # this will track x.google.mysite.com as google so its buggy, fix the regex
158
+ if m = @request.referrer.match(/^(https?:\/\/)?([a-zA-Z0-9\.\-]+\.)?(#{referrer})\.([a-zA-Z\.]+)(:[0-9]+)?(\/.*)?$/)
159
+ "REFERRER => #{m.to_a[3]}"
160
+ referrer = m.to_a[3]
161
+ else
162
+ referrer = 'other'
163
+ end
164
+ end
165
+ else
166
+ referrer = 'organic'
167
+ end
168
+
169
+ # User agent
170
+ ua = Browser.new(:ua => @request.user_agent, :accept_language => 'en-us')
171
+
172
+ for_each_time_range(t) do |ts, expire|
173
+
174
+ # increment the total visits
175
+ RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}visits:#{ts}")
176
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}visits:#{ts}", expire) if expire
177
+
178
+ if rucn_seq
179
+ unique = RedisAnalytics.redis_connection.sadd("#{@redis_key_prefix}unique_visits:#{ts}", rucn_seq)
180
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}unique_visits:#{ts}", expire) if expire
181
+
182
+ # geo ip tracking
183
+ if geo_country_code and geo_country_code =~ /^[A-Z]{2}$/
184
+ RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_country:#{ts}", 1, geo_country_code)
185
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_country:#{ts}", expire) if expire
186
+ end
187
+
188
+ # referrer tracking
189
+ RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_referrers:#{ts}", 1, referrer)
190
+ end
191
+
192
+ # tracking for visitor recency
193
+ if last_visit_time
194
+ days_since_last_visit = ((t.to_i - last_visit_time)/(24*3600)).round
195
+ RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_recency:#{ts}", 1, days_since_last_visit)
196
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_recency:#{ts}", expire) if expire
197
+ end
198
+
199
+ RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_browsers:#{ts}", 1, ua.name)
200
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_browsers:#{ts}", expire) if expire
201
+
202
+ RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_platforms:#{ts}", 1, ua.platform.to_s)
203
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_platforms:#{ts}", expire) if expire
204
+
205
+ RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_devices:#{ts}", 1, ua.mobile? ? 'M' : 'D')
206
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_devices:#{ts}", expire) if expire
207
+ end
208
+ end
209
+
210
+ def visit_time(t, visit_end_time = nil)
211
+ for_each_time_range(t) do |ts, expire|
212
+ RedisAnalytics.redis_connection.incrby("#{@redis_key_prefix}visit_time:#{ts}", t.to_i - visit_end_time)
213
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}visit_time:#{ts}", expire) if expire
214
+ end
215
+ end
216
+
217
+ # 2nd pageview in a visit
218
+ def page_view(t, second_page_view = false)
219
+ for_each_time_range(t) do |ts, expire|
220
+ RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}page_views:#{ts}")
221
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}page_views:#{ts}", expire) if expire
222
+
223
+ RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}second_page_views:#{ts}") if second_page_view
224
+ RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}second_page_views:#{ts}", expire) if second_page_view and expire
225
+ end
226
+ end
227
+
228
+
229
+ end
230
+ end
231
+ end