redis_analytics 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.
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