turnstile-rb 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.travis.yml +22 -0
- data/Gemfile +6 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +197 -0
- data/Rakefile +1 -0
- data/bin/turnstile +91 -0
- data/lib/turnstile.rb +19 -0
- data/lib/turnstile/adapter.rb +60 -0
- data/lib/turnstile/collector.rb +8 -0
- data/lib/turnstile/collector/formats.rb +46 -0
- data/lib/turnstile/collector/log_reader.rb +81 -0
- data/lib/turnstile/collector/matcher.rb +21 -0
- data/lib/turnstile/collector/runner.rb +71 -0
- data/lib/turnstile/collector/updater.rb +86 -0
- data/lib/turnstile/configuration.rb +38 -0
- data/lib/turnstile/logger.rb +47 -0
- data/lib/turnstile/nad.rb +16 -0
- data/lib/turnstile/observer.rb +33 -0
- data/lib/turnstile/rb.rb +1 -0
- data/lib/turnstile/sampler.rb +20 -0
- data/lib/turnstile/tracker.rb +17 -0
- data/lib/turnstile/version.rb +3 -0
- data/spec/fixtures/sample-production.log +4 -0
- data/spec/fixtures/sample-production.log.json +37 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/turnstile/adapter_spec.rb +71 -0
- data/spec/turnstile/collector/log_reader_spec.rb +114 -0
- data/spec/turnstile/configuration_spec.rb +78 -0
- data/spec/turnstile/nad_spec.rb +31 -0
- data/spec/turnstile/observer_spec.rb +65 -0
- data/spec/turnstile/sampler_spec.rb +35 -0
- data/spec/turnstile/tracker_spec.rb +25 -0
- data/spec/turnstile_spec.rb +68 -0
- data/turnstile-rb.gemspec +36 -0
- metadata +277 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3cd7be07aee113fee488b2839dc8eadc611a3be257e6c37a7de50618e55fce94
|
4
|
+
data.tar.gz: f13265ed097e7bb4f4625ed6fcf7da9e45208f33e382c6af0df7157345af3efa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d68205747142bb284446c06221046a8746c7cf301e4d0306624872fc9ecc8a443386325a00581a22add14816822ac08fa98356ae36246bad49e1080001f04f02
|
7
|
+
data.tar.gz: 3f84e37343fd99ae8bed3fad5182b5f66c716d945db21e2c1d8be27258a1b97fd26aa5f8696a1f52226ad3a07b303b56ff5ccdf678bef8f2089c8b9dd128253f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
rvm:
|
2
|
+
- 2.2.7
|
3
|
+
- 2.3.4
|
4
|
+
- 2.4.1
|
5
|
+
services:
|
6
|
+
- redis-server
|
7
|
+
env:
|
8
|
+
global:
|
9
|
+
- CC_TEST_REPORTER_ID=d17e91219ca5c271ca9bdd9eb447611f1be6b750abe8e4bc48146f5dec1154d4
|
10
|
+
sudo: false
|
11
|
+
language: ruby
|
12
|
+
cache: bundler
|
13
|
+
before_install: gem install bundler -v 1.15.4
|
14
|
+
before_script:
|
15
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
16
|
+
- chmod +x ./cc-test-reporter
|
17
|
+
- ./cc-test-reporter before-build
|
18
|
+
script:
|
19
|
+
- bundle exec rspec
|
20
|
+
after_script:
|
21
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
22
|
+
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#^syntax detection
|
3
|
+
|
4
|
+
# A sample Guardfile
|
5
|
+
# More info at https://github.com/guard/guard#readme
|
6
|
+
|
7
|
+
guard 'rspec' do
|
8
|
+
watch(%r{^lib/(.+)\.rb$}) { "spec" }
|
9
|
+
watch(%r{^spec/.+_spec\.rb$})
|
10
|
+
watch('spec/spec_helper.rb') { "spec" }
|
11
|
+
watch(%r{spec/support/.*}) { "spec" }
|
12
|
+
end
|
13
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Wanelo, Inc
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/kigster/turnstile.svg?branch=master)](https://travis-ci.org/kigster/turnstile)
|
2
|
+
[![Test Coverage](https://codeclimate.com/github/wanelo/turnstile/badges/coverage.svg)](https://codeclimate.com/github/kigster/turnstile/coverage)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/wanelo/turnstile/badges/gpa.svg)](https://codeclimate.com/github/kigster/turnstile)
|
4
|
+
[![Issue Count](https://codeclimate.com/github/wanelo/turnstile/badges/issue_count.svg)](https://codeclimate.com/github/kigster/turnstile)
|
5
|
+
|
6
|
+
# Turnstile
|
7
|
+
|
8
|
+
The goal of this gem is to provide near real time tracking and reporting on the number of users currently online and accessing given application. It requires that the reporting layer is able to uniquely identify each user and provide a unique identifier. It may also optionally assign another dimension to the users accessing, such as, for example, _platform_ -- which in our case denotes how the user is accessing our application: from desktop browser, iOS app, Android app, mobile web, etc. But any other partitioning schemee can be used, or none at all.
|
9
|
+
|
10
|
+
The gem uses (and depends on) a [Redis](http://redis.io/) instance in order to keep track of _unique_ users, and can operate in **online* mode (tracking users from a Rack Middleware) or **offline**, by taling a file log.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile (Note that another gem with a competing name is on RubyGems, so you **must** specify the path below):
|
15
|
+
|
16
|
+
gem 'turnstile-rb'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install turnstile
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Tracking
|
29
|
+
|
30
|
+
Turnstile contains two primary parts: data collection and reporting.
|
31
|
+
|
32
|
+
Data collection may happen in two way:
|
33
|
+
|
34
|
+
1. Synchronously — in real time — i.e. from a web request
|
35
|
+
2. Or asynchronously — by "tailing" the logs on your servers
|
36
|
+
|
37
|
+
Synchronous tracking is more accurate, supports sampling, but introduces a run-time dependency into your application middleware stack that might not be desirable.
|
38
|
+
|
39
|
+
Asynchronous tracking has a slight initial setup overhead, but has zero run-time overhead, as the data collection happens outside of the web request.
|
40
|
+
|
41
|
+
#### Real Time Tracking API
|
42
|
+
|
43
|
+
With Real Time tracking you can use sampling to _estimate_ the number of online users.
|
44
|
+
|
45
|
+
* To do sampling, use the ```Turnstile::Tracker#track``` method
|
46
|
+
* To store and analyze 100% of your data use ```Turnstile::Adapter#add```.
|
47
|
+
|
48
|
+
**Example:**
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
@turnstile = Turnstile::Adapter.new
|
52
|
+
|
53
|
+
user_id = 12345
|
54
|
+
platform = 'desktop'
|
55
|
+
ip = '224.247.12.4'
|
56
|
+
|
57
|
+
# register user
|
58
|
+
@turnstile.add(user_id, platform, ip)
|
59
|
+
```
|
60
|
+
|
61
|
+
Without any further calls to ```track()``` method for this particular user/platform/ip combination, the user is considered _online_ for 60 seconds. Each subsequent call to ```track()``` resets the TTL.
|
62
|
+
|
63
|
+
#### Offline Log Parsing by "Tailing Logs"
|
64
|
+
|
65
|
+
If adding latency to a web request is not desirable, another option is to run Turnstile ```turnstile``` process as a daemon on each application server. The daemon "tails" the `production.log` file (or any other file), while scanning for lines matching a particular configurable pattern, and then extracting `user id`, `IP` and `platform` based on another configurable regular expression setting.
|
66
|
+
|
67
|
+
##### Data Structure
|
68
|
+
|
69
|
+
The logging approach expects that you print a special token into your log file, which contains three fields separated by a delimiter:
|
70
|
+
|
71
|
+
* `x-turnstile` is a hardcoded string used in finding this token,
|
72
|
+
* `platform` — ideally a short string, such as 'desktop', 'ios', 'android', etc
|
73
|
+
* `remote IP` — is the IP address of the request
|
74
|
+
* `user_id` — can be encoded, i.e. digest of user_id)
|
75
|
+
|
76
|
+
for example, a token such as this:
|
77
|
+
|
78
|
+
```
|
79
|
+
x-turnstile:desktop:125.4.5.13:3456
|
80
|
+
```
|
81
|
+
|
82
|
+
is colon-separated, and easily extractable from the log.
|
83
|
+
|
84
|
+
##### Log File Formats
|
85
|
+
|
86
|
+
Turnstile supports two primary formats:
|
87
|
+
|
88
|
+
1. JSON format, where each **log line** contains the following JSON fields:
|
89
|
+
|
90
|
+
```json
|
91
|
+
{ "user_id" :17344742,
|
92
|
+
"platform" :"iphone",
|
93
|
+
"session_id" :"4eKMZJ4nggzvkix29zpS",
|
94
|
+
"ip_address" :"70.210.128.241",
|
95
|
+
.... }
|
96
|
+
```
|
97
|
+
|
98
|
+
2. Plain text format, where lines are space delimited, and the token is one of the fields of your log line, itself delimited using a configurable character.
|
99
|
+
|
100
|
+
```
|
101
|
+
2017-10-06 11:03:21 x-turnstile|desktop|124.5.4.3|234324 GET /...
|
102
|
+
```
|
103
|
+
|
104
|
+
You can specify the file format using the `-t | --file-type` switch.
|
105
|
+
|
106
|
+
Possible values are:
|
107
|
+
|
108
|
+
* `json_formatted`
|
109
|
+
* `pipe_delimited`
|
110
|
+
* `colon_delimited`
|
111
|
+
* `comma_delimited`
|
112
|
+
|
113
|
+
You can also pass the token delimiter on the command line, `-D | --delimiter "," ` in which case the `delimited` file type is used, with your custom delimiter.
|
114
|
+
|
115
|
+
> NOTE: Default format is **`pipe_delimited`**.
|
116
|
+
|
117
|
+
### Examples
|
118
|
+
|
119
|
+
For example:
|
120
|
+
|
121
|
+
```bash
|
122
|
+
> gem install turnstile
|
123
|
+
|
124
|
+
> turnstile -v -f log/production/log -t json_formatted | \
|
125
|
+
tee -a /var/log/turnstile.log
|
126
|
+
|
127
|
+
> turnstile -v -f log/production/log -h 127.0.0.1 -p 6432 | \
|
128
|
+
tee -a /var/log/turnstile.log
|
129
|
+
|
130
|
+
2014-04-12 05:16:41 -0700: updater:flush - nothing to flush, sleeping 6s..
|
131
|
+
2014-04-12 05:16:41 -0700: updater:queue - nothing in the queue, sleeping 5s...
|
132
|
+
2014-04-12 05:16:41 -0700: log-reader - starting to tail file log....
|
133
|
+
2014-04-12 05:16:46 -0700: updater:queue - nothing in the queue, sleeping 5s...
|
134
|
+
2014-04-12 05:16:53 -0700: updater:flush - nothing to flush, sleeping 6s..
|
135
|
+
2014-04-12 05:16:56 -0700: updater:queue - ( 0.65ms) caching [746] keys locally
|
136
|
+
2014-04-12 05:16:59 -0700: updater:flush - ( 91.73ms) flushing cache with [602] keys
|
137
|
+
2014-04-12 05:17:05 -0700: updater:flush - nothing to flush, sleeping 6s..
|
138
|
+
^Ctrl-C
|
139
|
+
```
|
140
|
+
|
141
|
+
Note that ideally you should run ```turnstile``` on all app servers, for completeness, and because
|
142
|
+
this does not incur any additional cost for the application (as user tracking is happening outside web request).
|
143
|
+
|
144
|
+
### Reporting
|
145
|
+
|
146
|
+
Once the tracking information is sent, the data can be queried.
|
147
|
+
|
148
|
+
If you used sampling, then you should query using ```Turnstile::Observer``` class that provides
|
149
|
+
exprapolation of the results based on sample size configuration.
|
150
|
+
```ruby
|
151
|
+
# Return data for sampled users and the summary
|
152
|
+
Turnstile::Observer.new.stats
|
153
|
+
# => { stats: { total: 3, platforms: 2 }, users: [ { uid: 1, platform: 'desktop', ip: '123.2.4.54' }, ... ]
|
154
|
+
```
|
155
|
+
If you did not use sampling, you can get some answers from the ```Turnstile::Adapter``` class:
|
156
|
+
```ruby
|
157
|
+
Turntstile::Adapter.new.fetch
|
158
|
+
# => [ { uid: 213, :platform: 'desktop', '123.2.4.54' }, { uid: 215, ... } ]
|
159
|
+
```
|
160
|
+
You can also request an aggregate results, suitable for sending to graphing systems or displaying on a dashboard:
|
161
|
+
```ruby
|
162
|
+
Turntstile::Adapter.new.aggregate
|
163
|
+
# => { 'desktop' => 234, 'ios' => 3214, ..., 'total' => 4566 }
|
164
|
+
```
|
165
|
+
|
166
|
+
## Circonus NAD Integration
|
167
|
+
|
168
|
+
We use Circonus to collect and graph data. You can use ```turnstile```
|
169
|
+
to dump the current aggregate statistics from redis to standard output,
|
170
|
+
which is a tab-delimited format consumable by the nad daemon.
|
171
|
+
|
172
|
+
(below output is formatted to show tabs as aligned for readability).
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
> turnstile --summary
|
176
|
+
|
177
|
+
turnstile.iphone n 383
|
178
|
+
turnstile.ipad n 34
|
179
|
+
turnstile.android n 108
|
180
|
+
turnstile.ipod_touch n 34
|
181
|
+
turnstile.unknown n 36
|
182
|
+
turnstile.total n 595
|
183
|
+
```
|
184
|
+
|
185
|
+
## TODO:
|
186
|
+
|
187
|
+
* Allow users of the gem to easier customize log reader to fit their own custom log files
|
188
|
+
* Export configuration into a YAML file and load from there by defaul
|
189
|
+
* Refactor commands to have a single ```turnstile``` CLI with sub-commands ```watch``` and ```report```.
|
190
|
+
|
191
|
+
## Contributing
|
192
|
+
|
193
|
+
1. Fork it ( http://github.com/<my-github-username>/turnstile/fork )
|
194
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
195
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
196
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
197
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/turnstile
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
|
5
|
+
require 'turnstile'
|
6
|
+
require 'optparse'
|
7
|
+
require 'colored2'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
ARGV << '-?' if ARGV.empty?
|
11
|
+
|
12
|
+
Colored2.disable! unless STDOUT.tty?
|
13
|
+
|
14
|
+
DESCRIPTION = <<-EOF
|
15
|
+
Turnstile consists of two components:
|
16
|
+
|
17
|
+
1. A daemon process started with #{'turnstile -f <log-file> [ --daemon ]'.bold.green}
|
18
|
+
This process should run on every server that generates a log
|
19
|
+
file, and they should all connect to the same Redis server.
|
20
|
+
|
21
|
+
2. Query command, ran as #{'turnstile --summary'.bold.green}
|
22
|
+
|
23
|
+
In the first form, turnstile can tail a given log file,
|
24
|
+
parsing and tallying and periodically syncing to the master
|
25
|
+
Redis server.
|
26
|
+
|
27
|
+
When the query is invoked, data from all reporters is shown on STDOUT.
|
28
|
+
EOF
|
29
|
+
|
30
|
+
OptionParser.new do |opts|
|
31
|
+
opts.banner = "Usage:\n".bold.magenta +
|
32
|
+
" turnstile -f <file> [ --daemon ] [options]\n".green +
|
33
|
+
" turnstile --summary [options]\n".green
|
34
|
+
|
35
|
+
opts.separator "Description:".bold.magenta
|
36
|
+
opts.separator ' ' + DESCRIPTION.gsub(/\n/, "\n ")
|
37
|
+
|
38
|
+
opts.separator "Log File Specification:".bold.magenta
|
39
|
+
opts.on('-f', '--file FILE', 'File to monitor') do |file|
|
40
|
+
options[:file] = file
|
41
|
+
end
|
42
|
+
opts.on('-t', '--file-type TYPE',
|
43
|
+
'Either: json_formatted, pipe_delimited,',
|
44
|
+
'or comma_delimited (default).') do |type|
|
45
|
+
options[:filetype] = type
|
46
|
+
end
|
47
|
+
opts.on('-D', '--delimiter CHAR',
|
48
|
+
'Forces "delimited" file type, and uses ',
|
49
|
+
'the character in the argument as the delimiter') do |v|
|
50
|
+
options[:delimiter] = v
|
51
|
+
end
|
52
|
+
opts.separator "\nRedis Server:".bold.magenta
|
53
|
+
opts.on('-h', '--redis-host HOST', 'Redis server host') do |host|
|
54
|
+
Turnstile.config.redis_host = host
|
55
|
+
end
|
56
|
+
opts.on('-p', '--redis-port PORT', 'Redis server port') do |port|
|
57
|
+
Turnstile.config.redis_port = port
|
58
|
+
end
|
59
|
+
opts.on('-n', '--redis-db DB', 'Redis server db') do |db|
|
60
|
+
Turnstile.config.redis_db = db
|
61
|
+
end
|
62
|
+
opts.separator "\nMode of Operation:".bold.magenta
|
63
|
+
opts.on('-d', '--daemonize', 'Daemonize to watch the logs') do |v|
|
64
|
+
options[:daemonize] = true
|
65
|
+
end
|
66
|
+
opts.on('-s', '--summary', 'Print current stats (using NAD format) and exit') do |v|
|
67
|
+
options[:summary] = true
|
68
|
+
end
|
69
|
+
opts.separator "\nTiming Adjustments:".bold.magenta
|
70
|
+
opts.on('-b', '--buffer-interval INTERVAL', 'Buffer for this many seconds') do |v|
|
71
|
+
options[:buffer_interval] = v.to_i
|
72
|
+
end
|
73
|
+
opts.on('-i', '--flush-interval INTERVAL', 'Flush then sleep for this many seconds') do |v|
|
74
|
+
options[:flush_interval] = v.to_i
|
75
|
+
end
|
76
|
+
opts.separator "\nMiscellaneous:".bold.magenta
|
77
|
+
opts.on('-v', '--verbose', 'Print status to stdout') do |v|
|
78
|
+
options[:debug] = true
|
79
|
+
end
|
80
|
+
opts.on_tail('-?', '--help', 'Show this message') do
|
81
|
+
puts opts
|
82
|
+
exit
|
83
|
+
end
|
84
|
+
end.parse!
|
85
|
+
|
86
|
+
if options[:summary]
|
87
|
+
STDOUT.puts Turnstile::Nad.new.data
|
88
|
+
else
|
89
|
+
Turnstile::Collector::Runner.new(options).run
|
90
|
+
end
|
91
|
+
|
data/lib/turnstile.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'turnstile/version'
|
2
|
+
require 'turnstile/configuration'
|
3
|
+
require 'turnstile/sampler'
|
4
|
+
require 'turnstile/adapter'
|
5
|
+
require 'turnstile/tracker'
|
6
|
+
require 'turnstile/observer'
|
7
|
+
require 'turnstile/logger'
|
8
|
+
require 'turnstile/collector'
|
9
|
+
require 'turnstile/nad'
|
10
|
+
|
11
|
+
module Turnstile
|
12
|
+
def self.configure(&block)
|
13
|
+
@configuration = Turnstile::Configuration.new.configure(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.config
|
17
|
+
@configuration ||= Turnstile::Configuration.new
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Turnstile
|
5
|
+
class Adapter
|
6
|
+
attr_accessor :redis
|
7
|
+
include Timeout
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.redis = ::Redis.new(host: config.redis.host,
|
11
|
+
port: config.redis.port,
|
12
|
+
db: config.redis.db)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(uid, platform, ip)
|
16
|
+
key = compose_key(uid, platform, ip)
|
17
|
+
timeout(config.redis.timeout) do
|
18
|
+
redis.setex(key, config.activity_interval, 1)
|
19
|
+
end
|
20
|
+
rescue StandardError => e
|
21
|
+
Turnstile::Logger.log "exception while writing to redis: #{e.inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch
|
25
|
+
redis.keys('t:*').map do |key|
|
26
|
+
fields = key.split(':')
|
27
|
+
{
|
28
|
+
uid: fields[1],
|
29
|
+
platform: fields[2],
|
30
|
+
ip: fields[3],
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def aggregate
|
36
|
+
redis.keys('t:*').inject({}) { |hash, key| increment_platform(hash, key) }.tap do |h|
|
37
|
+
h['total'] = h.values.inject(&:+) || 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def increment_platform(hash, key)
|
44
|
+
platform = key.split(':')[2]
|
45
|
+
hash[platform] ||= 0
|
46
|
+
hash[platform] += 1
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def config
|
51
|
+
Turnstile.config
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def compose_key(uid, platform = nil, ip = nil)
|
56
|
+
"t:#{uid}:#{platform}:#{ip}"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|