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.
- data/Gemfile +4 -0
- data/Gemfile.lock +57 -0
- data/LICENSE +19 -0
- data/README.md +98 -0
- data/Rakefile +19 -0
- data/bin/GeoIP.dat +0 -0
- data/bin/redis_analytics_dashboard +17 -0
- data/lib/redis_analytics.rb +22 -0
- data/lib/redis_analytics/analytics.rb +231 -0
- data/lib/redis_analytics/config.ru +10 -0
- data/lib/redis_analytics/configuration.rb +71 -0
- data/lib/redis_analytics/dashboard.rb +114 -0
- data/lib/redis_analytics/dashboard/public/css/bootstrap-responsive.min.css +9 -0
- data/lib/redis_analytics/dashboard/public/css/bootstrap.min.css +9 -0
- data/lib/redis_analytics/dashboard/public/css/jquery-jvectormap-1.2.2.css +37 -0
- data/lib/redis_analytics/dashboard/public/css/morris.css +2 -0
- data/lib/redis_analytics/dashboard/public/favicon.ico +0 -0
- data/lib/redis_analytics/dashboard/public/img/glyphicons-halflings-white.png +0 -0
- data/lib/redis_analytics/dashboard/public/img/glyphicons-halflings.png +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/bootstrap.min.js +6 -0
- data/lib/redis_analytics/dashboard/public/javascripts/jquery-1.9.1.min.js +5 -0
- data/lib/redis_analytics/dashboard/public/javascripts/jquery-jvectormap-1.2.2.min.js +8 -0
- data/lib/redis_analytics/dashboard/public/javascripts/jquery-jvectormap-world-mill-en.js +1 -0
- data/lib/redis_analytics/dashboard/public/javascripts/morris.min.js +1 -0
- data/lib/redis_analytics/dashboard/public/javascripts/raphael-min.js +10 -0
- data/lib/redis_analytics/dashboard/views/activity.erb +0 -0
- data/lib/redis_analytics/dashboard/views/footer.erb +5 -0
- data/lib/redis_analytics/dashboard/views/header.erb +44 -0
- data/lib/redis_analytics/dashboard/views/layout.erb +33 -0
- data/lib/redis_analytics/dashboard/views/visits.erb +176 -0
- data/lib/redis_analytics/dashboard/views/visits_js.erb +265 -0
- data/lib/redis_analytics/helpers.rb +69 -0
- data/lib/redis_analytics/railtie.rb +13 -0
- data/lib/redis_analytics/time_ext.rb +19 -0
- data/lib/redis_analytics/tracker.rb +26 -0
- data/lib/redis_analytics/version.rb +5 -0
- data/redis_analytics.gemspec +37 -0
- data/screenshot.png +0 -0
- data/spec/redis_analytics_spec.rb +57 -0
- data/spec/spec_helper.rb +47 -0
- metadata +216 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# redis_analytics [](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
|
+

|
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.
|
data/Rakefile
ADDED
@@ -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
|
data/bin/GeoIP.dat
ADDED
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
|