turnstile-rb 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.travis.yml +1 -1
- data/Gemfile +1 -1
- data/README.md +51 -28
- data/Rakefile +20 -0
- data/bin/turnstile +3 -83
- data/lib/turnstile/adapter.rb +5 -4
- data/lib/turnstile/collector/matcher.rb +1 -1
- data/lib/turnstile/collector/runner.rb +2 -1
- data/lib/turnstile/configuration.rb +1 -0
- data/lib/turnstile/parser.rb +107 -0
- data/lib/turnstile/runner.rb +54 -0
- data/lib/turnstile/summary.rb +57 -0
- data/lib/turnstile/tracker.rb +12 -1
- data/lib/turnstile/version.rb +19 -1
- data/lib/turnstile.rb +1 -1
- data/spec/turnstile/summary_spec.rb +41 -0
- data/turnstile-rb.gemspec +5 -3
- metadata +16 -26
- data/Guardfile +0 -13
- data/lib/turnstile/nad.rb +0 -16
- data/lib/turnstile/rb.rb +0 -1
- data/spec/turnstile/nad_spec.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 64573d2b1f394251482822e130a7d9ca777554d3
|
4
|
+
data.tar.gz: ef96805bb0b4dc31541894c950b102e389a6cc05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a66e638ee0a591f9ea4acc1c72f9b20e59a35f682bb147e0bf688f57b7d4e2ee36b0e97e540168218351fc875f22f253a1bad00c4fcc414b689d4e238be6c09
|
7
|
+
data.tar.gz: 139bf132ff6ab3f18f31f9f4196484889974a1eb7137556f1963e044c1cbffb3c61cfc731d5b4b204b7a4ae8ad51a77a2067279155feab5974f37355b57aac44
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -6,7 +6,7 @@ services:
|
|
6
6
|
- redis-server
|
7
7
|
env:
|
8
8
|
global:
|
9
|
-
- CC_TEST_REPORTER_ID=
|
9
|
+
- CC_TEST_REPORTER_ID=c6e51bc4755c4602fccb935a436625bbac4be498193c4f40ba8c8e2ee0745182
|
10
10
|
sudo: false
|
11
11
|
language: ruby
|
12
12
|
cache: bundler
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
[![
|
2
|
-
[![
|
3
|
-
[![
|
4
|
-
[![
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/turnstile-rb.svg)](https://badge.fury.io/rb/turnstile-rb)
|
2
|
+
[![Build Status](https://travis-ci.org/kigster/turnstile-rb.svg?branch=master)](https://travis-ci.org/kigster/turnstile-rb)
|
3
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/8031931b7924461f6a90/maintainability)](https://codeclimate.com/github/kigster/turnstile-rb/maintainability)
|
4
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/8031931b7924461f6a90/test_coverage)](https://codeclimate.com/github/kigster/turnstile-rb/test_coverage)
|
5
5
|
|
6
6
|
# Turnstile
|
7
7
|
|
@@ -11,17 +11,9 @@ The gem uses (and depends on) a [Redis](http://redis.io/) instance in order to k
|
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
14
|
-
Add this line to your application's Gemfile
|
14
|
+
Add this line to your application's Gemfile:
|
15
15
|
|
16
|
-
gem
|
17
|
-
|
18
|
-
And then execute:
|
19
|
-
|
20
|
-
$ bundle
|
21
|
-
|
22
|
-
Or install it yourself as:
|
23
|
-
|
24
|
-
$ gem install turnstile
|
16
|
+
$ gem install turnstile-rb
|
25
17
|
|
26
18
|
## Usage
|
27
19
|
|
@@ -42,20 +34,27 @@ Asynchronous tracking has a slight initial setup overhead, but has zero run-time
|
|
42
34
|
|
43
35
|
With Real Time tracking you can use sampling to _estimate_ the number of online users.
|
44
36
|
|
45
|
-
* To
|
46
|
-
* To store and analyze 100% of your data use ```Turnstile::
|
37
|
+
* To possibly use sampling, use the ```Turnstile::Tracker#track``` method.
|
38
|
+
* To store and analyze 100% of your data use ```Turnstile::Tracker#add```.
|
47
39
|
|
48
40
|
**Example:**
|
49
41
|
|
50
42
|
```ruby
|
51
|
-
@
|
43
|
+
@tracker = Turnstile::Tracker.new
|
52
44
|
|
53
45
|
user_id = 12345
|
54
46
|
platform = 'desktop'
|
55
47
|
ip = '224.247.12.4'
|
56
48
|
|
57
49
|
# register user
|
58
|
-
@
|
50
|
+
@tracker.add(user_id, platform, ip)
|
51
|
+
|
52
|
+
# or you can add a colon-delimited string token:
|
53
|
+
@tracker.add_token("ios:172.2.5.3:39898098098")
|
54
|
+
|
55
|
+
# or you with a custom delimiter:
|
56
|
+
@tracker.add_token("ios|172.2.5.3|39898098098", '|')
|
57
|
+
|
59
58
|
```
|
60
59
|
|
61
60
|
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.
|
@@ -145,25 +144,48 @@ this does not incur any additional cost for the application (as user tracking is
|
|
145
144
|
|
146
145
|
Once the tracking information is sent, the data can be queried.
|
147
146
|
|
148
|
-
If you used sampling, then you should query using ```Turnstile::Observer``` class that provides
|
149
|
-
|
147
|
+
If you used sampling, then you should query using ```Turnstile::Observer``` class that provides exprapolation of the results based on sample size configuration.
|
148
|
+
|
150
149
|
```ruby
|
151
150
|
# Return data for sampled users and the summary
|
152
151
|
Turnstile::Observer.new.stats
|
153
|
-
# => { stats: {
|
152
|
+
# => { stats: {
|
153
|
+
total: 3,
|
154
|
+
platforms: 2 },
|
155
|
+
users: [ { uid: 1, platform: 'desktop', ip: '123.2.4.54' }, ... ]
|
154
156
|
```
|
155
|
-
|
157
|
+
|
158
|
+
If you did not use sampling, you can get some answers from the`Turnstile::Adapter` class:
|
159
|
+
|
156
160
|
```ruby
|
157
161
|
Turntstile::Adapter.new.fetch
|
158
162
|
# => [ { uid: 213, :platform: 'desktop', '123.2.4.54' }, { uid: 215, ... } ]
|
159
163
|
```
|
164
|
+
|
160
165
|
You can also request an aggregate results, suitable for sending to graphing systems or displaying on a dashboard:
|
166
|
+
|
161
167
|
```ruby
|
162
168
|
Turntstile::Adapter.new.aggregate
|
163
169
|
# => { 'desktop' => 234, 'ios' => 3214, ..., 'total' => 4566 }
|
164
170
|
```
|
165
171
|
|
166
|
-
|
172
|
+
### Summary Printing
|
173
|
+
|
174
|
+
### JSON and CSV
|
175
|
+
|
176
|
+
Use the following syntax:
|
177
|
+
|
178
|
+
```bash
|
179
|
+
# To see JSON summary:
|
180
|
+
turnstile -s json
|
181
|
+
|
182
|
+
# Or, for CSV
|
183
|
+
turnstile -s csv
|
184
|
+
```
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
#### Circonus NAD
|
167
189
|
|
168
190
|
We use Circonus to collect and graph data. You can use ```turnstile```
|
169
191
|
to dump the current aggregate statistics from redis to standard output,
|
@@ -171,17 +193,18 @@ which is a tab-delimited format consumable by the nad daemon.
|
|
171
193
|
|
172
194
|
(below output is formatted to show tabs as aligned for readability).
|
173
195
|
|
174
|
-
```
|
175
|
-
> turnstile
|
196
|
+
```bash
|
197
|
+
> turnstile -s
|
176
198
|
|
177
|
-
turnstile.iphone
|
199
|
+
turnstile.iphone n 383
|
178
200
|
turnstile.ipad n 34
|
179
|
-
turnstile.android n
|
201
|
+
turnstile.android n 108
|
180
202
|
turnstile.ipod_touch n 34
|
181
203
|
turnstile.unknown n 36
|
182
|
-
turnstile.total n
|
204
|
+
turnstile.total n 595
|
183
205
|
```
|
184
206
|
|
207
|
+
|
185
208
|
## TODO:
|
186
209
|
|
187
210
|
* Allow users of the gem to easier customize log reader to fit their own custom log files
|
data/Rakefile
CHANGED
@@ -1 +1,21 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
require 'yard'
|
3
|
+
|
4
|
+
def shell(*args)
|
5
|
+
puts "running: #{args.join(' ')}"
|
6
|
+
system(args.join(' '))
|
7
|
+
end
|
8
|
+
|
9
|
+
task :permissions do
|
10
|
+
shell('rm -rf pkg/')
|
11
|
+
shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
|
12
|
+
shell("find . -type d -exec chmod o+x,g+x {} \\;")
|
13
|
+
end
|
14
|
+
|
15
|
+
task :build => :permissions
|
16
|
+
|
17
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
18
|
+
t.files = %w(lib/**/*.rb exe/*.rb - README.md LICENSE)
|
19
|
+
t.options.unshift('--title','"Turnstile — Active User Counter"')
|
20
|
+
t.after = ->() { exec('open doc/index.html') }
|
21
|
+
end
|
data/bin/turnstile
CHANGED
@@ -1,91 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
3
|
+
|
3
4
|
require 'rubygems'
|
4
5
|
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
|
5
6
|
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
7
|
|
38
|
-
|
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!
|
8
|
+
require 'turnstile/runner'
|
85
9
|
|
86
|
-
|
87
|
-
STDOUT.puts Turnstile::Nad.new.data
|
88
|
-
else
|
89
|
-
Turnstile::Collector::Runner.new(options).run
|
90
|
-
end
|
10
|
+
Turnstile::Runner.new(ARGV).execute!
|
91
11
|
|
data/lib/turnstile/adapter.rb
CHANGED
@@ -7,9 +7,11 @@ module Turnstile
|
|
7
7
|
include Timeout
|
8
8
|
|
9
9
|
def initialize
|
10
|
-
self.redis =
|
11
|
-
|
12
|
-
|
10
|
+
self.redis = config.redis.url ?
|
11
|
+
::Redis.new(url: config.redis.url) :
|
12
|
+
::Redis.new(host: config.redis.host,
|
13
|
+
port: config.redis.port,
|
14
|
+
db: config.redis.db)
|
13
15
|
end
|
14
16
|
|
15
17
|
def add(uid, platform, ip)
|
@@ -51,7 +53,6 @@ module Turnstile
|
|
51
53
|
Turnstile.config
|
52
54
|
end
|
53
55
|
|
54
|
-
|
55
56
|
def compose_key(uid, platform = nil, ip = nil)
|
56
57
|
"t:#{uid}:#{platform}:#{ip}"
|
57
58
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'daemons/daemonize'
|
3
|
+
require 'colored2'
|
3
4
|
|
4
5
|
|
5
6
|
module Turnstile
|
@@ -26,7 +27,7 @@ module Turnstile
|
|
26
27
|
def wait_for_file(file)
|
27
28
|
sleep_period = 1
|
28
29
|
while !File.exist?(file)
|
29
|
-
STDERR.puts "File #{file} does not exist, waiting for it to appear..."
|
30
|
+
STDERR.puts "File #{file.bold.yellow} does not exist, waiting for it to appear..."
|
30
31
|
STDERR.puts 'Press Ctrl-C to abort.' if sleep_period == 1
|
31
32
|
|
32
33
|
sleep sleep_period
|
@@ -5,6 +5,7 @@ module Turnstile
|
|
5
5
|
class RedisConfig < ::Hashie::Dash
|
6
6
|
include Hashie::Extensions::Dash::PropertyTranslation
|
7
7
|
|
8
|
+
property :url, required: false
|
8
9
|
property :host, default: '127.0.0.1', required: true
|
9
10
|
property :port, default: 6379, required: true, transform_with: ->(value) { value.to_i }
|
10
11
|
property :db, default: 1, required: true, transform_with: ->(value) { value.to_i }
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'colored2'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'turnstile/version'
|
6
|
+
|
7
|
+
module Turnstile
|
8
|
+
class Parser
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :@system, :stdout, :stdin, :stderr
|
11
|
+
|
12
|
+
attr_accessor :options, :argv, :system
|
13
|
+
|
14
|
+
def initialize(argv, system)
|
15
|
+
self.system = system
|
16
|
+
self.argv = argv.dup
|
17
|
+
self.options = Hashie::Mash.new
|
18
|
+
self.argv << '-h' if argv.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse
|
22
|
+
OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage:\n".bold.magenta +
|
24
|
+
" turnstile -f <file> [ --daemon ] [ options ]\n".yellow +
|
25
|
+
" turnstile -s [ json | csv | nad ] [ options ]\n".yellow +
|
26
|
+
" turnstile -a 'platform:ip:user' [ options ]\n".yellow
|
27
|
+
|
28
|
+
opts.separator 'Description:'.bold.magenta
|
29
|
+
opts.separator ' ' + ::Turnstile::DESCRIPTION.gsub(/\n/, "\n ")
|
30
|
+
|
31
|
+
opts.separator 'Log File Specification:'.bold.magenta
|
32
|
+
opts.on('-f', '--file FILE', 'File to monitor') do |file|
|
33
|
+
options[:file] = file
|
34
|
+
end
|
35
|
+
opts.on('-t', '--file-type TYPE',
|
36
|
+
'Either: json_formatted, pipe_delimited,',
|
37
|
+
'or comma_delimited (default).') do |type|
|
38
|
+
options[:filetype] = type
|
39
|
+
end
|
40
|
+
opts.on('-D', '--delimiter CHAR',
|
41
|
+
'Forces "delimited" file type, and uses ',
|
42
|
+
'the character in the argument as the delimiter') do |v|
|
43
|
+
options[:delimiter] = v
|
44
|
+
end
|
45
|
+
opts.separator "\nRedis Server:".bold.magenta
|
46
|
+
opts.on('-r', '--redis-url URL', 'Redis server URL') do |host|
|
47
|
+
Turnstile.config.redis_url = host
|
48
|
+
end
|
49
|
+
opts.on('--redis-host HOST', 'Redis server host') do |host|
|
50
|
+
Turnstile.config.redis_host = host
|
51
|
+
end
|
52
|
+
opts.on('--redis-port PORT', 'Redis server port') do |port|
|
53
|
+
Turnstile.config.redis_port = port
|
54
|
+
end
|
55
|
+
opts.on('--redis-db DB', 'Redis server db') do |db|
|
56
|
+
Turnstile.config.redis_db = db
|
57
|
+
end
|
58
|
+
opts.separator "\nMode of Operation:".bold.magenta
|
59
|
+
opts.on('-d', '--daemonize', 'Daemonize to watch the logs') do |v|
|
60
|
+
options[:daemonize] = true
|
61
|
+
end
|
62
|
+
opts.on('-s', '--summary [FORMAT]',
|
63
|
+
'Print current stats and exit. Optional format can be',
|
64
|
+
'json (default), nad, yaml, or csv') do |v|
|
65
|
+
options[:summary] = true
|
66
|
+
options[:summary_format] = (v || 'json').to_sym
|
67
|
+
end
|
68
|
+
opts.on('-a', '--add TOKEN',
|
69
|
+
'Registers an event from the token, such as ',
|
70
|
+
'"ios:123.4.4.4:32442". Use -d to customize delimiter.') do |v|
|
71
|
+
options[:add] = v
|
72
|
+
end
|
73
|
+
opts.separator "\nTiming Adjustments:".bold.magenta
|
74
|
+
opts.on('-b', '--buffer-interval INTERVAL', 'Buffer for this many seconds') do |v|
|
75
|
+
options[:buffer_interval] = v.to_i
|
76
|
+
end
|
77
|
+
opts.on('-i', '--flush-interval INTERVAL', 'Flush then sleep for this many seconds') do |v|
|
78
|
+
options[:flush_interval] = v.to_i
|
79
|
+
end
|
80
|
+
opts.separator "\nMiscellaneous:".bold.magenta
|
81
|
+
opts.on('-v', '--verbose', 'Print status to stdout') do |v|
|
82
|
+
options[:debug] = true
|
83
|
+
end
|
84
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
85
|
+
puts opts
|
86
|
+
return
|
87
|
+
end
|
88
|
+
end.parse!(argv)
|
89
|
+
|
90
|
+
if options[:summary]
|
91
|
+
Turnstile::Summary.print(options[:summary_format] || :json, options[:delimiter])
|
92
|
+
elsif options[:add]
|
93
|
+
Turnstile::Tracker.new.add_token(options[:add], options[:delimiter] || ':')
|
94
|
+
Turnstile::Summary.print(options[:summary_format] || :json)
|
95
|
+
else
|
96
|
+
Turnstile::Collector::Runner.new(options).run
|
97
|
+
end
|
98
|
+
|
99
|
+
rescue OptionParser::MissingArgument => e
|
100
|
+
STDERR.puts e.message.bold.red
|
101
|
+
rescue Exception => e
|
102
|
+
STDERR.puts e.message.bold.red
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'colored2'
|
3
|
+
|
4
|
+
require 'turnstile/version'
|
5
|
+
require 'turnstile/parser'
|
6
|
+
|
7
|
+
module Turnstile
|
8
|
+
|
9
|
+
|
10
|
+
class Runner
|
11
|
+
attr_reader :argv, :stdin, :stdout, :stderr, :kernel
|
12
|
+
|
13
|
+
# Allow everything fun to be injected from the outside while defaulting to normal implementations.
|
14
|
+
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
|
15
|
+
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute!
|
19
|
+
exit_code = begin
|
20
|
+
|
21
|
+
Colored2.disable! unless stdout.tty?
|
22
|
+
|
23
|
+
$stderr = stderr
|
24
|
+
$stdin = stdin
|
25
|
+
$stdout = stdout
|
26
|
+
|
27
|
+
Turnstile::Parser.new(argv, self).parse
|
28
|
+
|
29
|
+
# Thor::Base#start does not have a return value, assume success if no exception is raised.
|
30
|
+
0
|
31
|
+
rescue StandardError => e
|
32
|
+
# The ruby interpreter would pipe this to STDERR and exit 1 in the case of an unhandled exception
|
33
|
+
b = e.backtrace
|
34
|
+
@stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
|
35
|
+
@stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
|
36
|
+
1
|
37
|
+
rescue SystemExit => e
|
38
|
+
e.status
|
39
|
+
ensure
|
40
|
+
$stderr = STDERR
|
41
|
+
$stdin = STDIN
|
42
|
+
$stdout = STDOUT
|
43
|
+
end
|
44
|
+
|
45
|
+
# Proxy our exit code back to the injected kernel.
|
46
|
+
@kernel.exit(exit_code)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Turnstile
|
52
|
+
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'csv'
|
2
|
+
module Turnstile
|
3
|
+
class Summary
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def print(method, delimiter = nil)
|
7
|
+
summary = new
|
8
|
+
if summary.respond_to?(method)
|
9
|
+
STDOUT.puts summary.send(method, delimiter)
|
10
|
+
else
|
11
|
+
STDERR.puts "don't know how to use format '#{method}'mac"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def json(*)
|
17
|
+
out = "{\n"
|
18
|
+
first = true
|
19
|
+
aggregate.each_pair do |key, value|
|
20
|
+
out << ",\n" unless first
|
21
|
+
first = false
|
22
|
+
out << json_row(key, value)
|
23
|
+
end
|
24
|
+
out << "\n}"
|
25
|
+
out
|
26
|
+
end
|
27
|
+
|
28
|
+
def nad(*)
|
29
|
+
out = ''
|
30
|
+
aggregate.each_pair do |key, value|
|
31
|
+
out << nad_row(key, value)
|
32
|
+
end
|
33
|
+
out
|
34
|
+
end
|
35
|
+
|
36
|
+
def csv(delimiter = nil)
|
37
|
+
out = CSV.generate do |csv|
|
38
|
+
aggregate.each_pair do |key, value|
|
39
|
+
csv << [key, value]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
delimiter ? out.gsub(/,/m, delimiter) : out
|
43
|
+
end
|
44
|
+
|
45
|
+
def nad_row(key, value)
|
46
|
+
%Q(turnstile:#{key}#{"\tn\t"}#{value}\n)
|
47
|
+
end
|
48
|
+
|
49
|
+
def json_row(key, value)
|
50
|
+
%Q( "#{key}": #{value})
|
51
|
+
end
|
52
|
+
|
53
|
+
def aggregate
|
54
|
+
Turnstile::Adapter.new.aggregate
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/turnstile/tracker.rb
CHANGED
@@ -1,9 +1,20 @@
|
|
1
1
|
module Turnstile
|
2
2
|
class Tracker
|
3
|
-
def
|
3
|
+
def sample(uid, platform = 'unknown', ip = nil)
|
4
4
|
adapter.add(uid, platform, ip) if sampler.sample(uid)
|
5
5
|
end
|
6
6
|
|
7
|
+
alias track sample
|
8
|
+
|
9
|
+
def add(uid, platform = 'unknown', ip = nil)
|
10
|
+
adapter.add(uid, platform, ip)
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_token(token, delimiter = ':')
|
14
|
+
platform, ip, uid = token.split(delimiter)
|
15
|
+
adapter.add(uid, platform, ip) if uid
|
16
|
+
end
|
17
|
+
|
7
18
|
private
|
8
19
|
|
9
20
|
def adapter
|
data/lib/turnstile/version.rb
CHANGED
@@ -1,3 +1,21 @@
|
|
1
1
|
module Turnstile
|
2
|
-
VERSION
|
2
|
+
VERSION = '2.0.1'
|
3
|
+
|
4
|
+
GEM_DESCRIPTION = <<-EOF
|
5
|
+
Turnstile is a Redis-based library that can accurately track total number of concurrent
|
6
|
+
users accessing a web/API based server application. It can break it down by "platform"
|
7
|
+
or a device type, and returns data in JSON, CSV of NAD formats. While user tracking
|
8
|
+
may happen synchronously using a Rack middleware, another method is provided that is
|
9
|
+
based on log file analysis, and can therefore be performed outside web server process.
|
10
|
+
EOF
|
11
|
+
|
12
|
+
DESCRIPTION = <<-EOF
|
13
|
+
Turnstile can run as a daemon, in which mode it monitors a given log file.
|
14
|
+
Alternatively, turnstile binary can be used to print current stats, and even
|
15
|
+
add new data into the registry.
|
16
|
+
|
17
|
+
If you are using Turnstile to tail log files, make sure you run on each app sever
|
18
|
+
that's generating log files.
|
19
|
+
EOF
|
20
|
+
|
3
21
|
end
|
data/lib/turnstile.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Turnstile::Summary do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
let(:aggregate) {
|
7
|
+
{
|
8
|
+
'android' => 3,
|
9
|
+
'ios' => 2,
|
10
|
+
'total' => 5
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
before { expect(subject).to receive(:aggregate).once.and_return(aggregate) }
|
15
|
+
|
16
|
+
describe '#nad' do
|
17
|
+
context 'have some data' do
|
18
|
+
let(:expected_string) {
|
19
|
+
<<-EOF
|
20
|
+
turnstile:android#{"\t"}n#{"\t"}3
|
21
|
+
turnstile:ios#{"\t"}n#{"\t"}2
|
22
|
+
turnstile:total#{"\t"}n#{"\t"}5
|
23
|
+
EOF
|
24
|
+
}
|
25
|
+
|
26
|
+
it 'return data in NAD format' do
|
27
|
+
expect(subject.nad).to eql(expected_string)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#json' do
|
33
|
+
context 'have some data' do
|
34
|
+
let(:json) { subject.json }
|
35
|
+
let(:hash) { JSON.load(json) }
|
36
|
+
it 'return data in NAD format' do
|
37
|
+
expect(hash).to eql(aggregate)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/turnstile-rb.gemspec
CHANGED
@@ -8,8 +8,11 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Turnstile::VERSION
|
9
9
|
spec.authors = ['Konstantin Gredeskoul']
|
10
10
|
spec.email = %w(kigster@gmail.com)
|
11
|
+
|
11
12
|
spec.summary = %q{Asynchronous and non-invasive concurrent user tracking with Redis, by scanning application logs across all servers.}
|
12
|
-
|
13
|
+
|
14
|
+
spec.description = Turnstile::GEM_DESCRIPTION
|
15
|
+
|
13
16
|
spec.homepage = 'https://github.com/kigster/turnstile-rb'
|
14
17
|
spec.license = 'MIT'
|
15
18
|
|
@@ -27,10 +30,9 @@ Gem::Specification.new do |spec|
|
|
27
30
|
|
28
31
|
spec.add_development_dependency 'bundler'
|
29
32
|
spec.add_development_dependency 'rake'
|
33
|
+
spec.add_development_dependency 'yard'
|
30
34
|
|
31
35
|
spec.add_development_dependency 'rspec'
|
32
36
|
spec.add_development_dependency 'rspec-its'
|
33
|
-
spec.add_development_dependency 'guard-rspec'
|
34
|
-
spec.add_development_dependency 'rb-fsevent'
|
35
37
|
spec.add_development_dependency 'simplecov'
|
36
38
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turnstile-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Konstantin Gredeskoul
|
@@ -123,7 +123,7 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: yard
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
@@ -137,21 +137,7 @@ dependencies:
|
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
|
-
name: rspec
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: guard-rspec
|
140
|
+
name: rspec
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
156
142
|
requirements:
|
157
143
|
- - ">="
|
@@ -165,7 +151,7 @@ dependencies:
|
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: '0'
|
167
153
|
- !ruby/object:Gem::Dependency
|
168
|
-
name:
|
154
|
+
name: rspec-its
|
169
155
|
requirement: !ruby/object:Gem::Requirement
|
170
156
|
requirements:
|
171
157
|
- - ">="
|
@@ -192,8 +178,12 @@ dependencies:
|
|
192
178
|
- - ">="
|
193
179
|
- !ruby/object:Gem::Version
|
194
180
|
version: '0'
|
195
|
-
description:
|
196
|
-
|
181
|
+
description: |2
|
182
|
+
Turnstile is a Redis-based library that can accurately track total number of concurrent
|
183
|
+
users accessing a web/API based server application. It can break it down by "platform"
|
184
|
+
or a device type, and returns data in JSON, CSV of NAD formats. While user tracking
|
185
|
+
may happen synchronously using a Rack middleware, another method is provided that is
|
186
|
+
based on log file analysis, and can therefore be performed outside web server process.
|
197
187
|
email:
|
198
188
|
- kigster@gmail.com
|
199
189
|
executables:
|
@@ -205,7 +195,6 @@ files:
|
|
205
195
|
- ".rspec"
|
206
196
|
- ".travis.yml"
|
207
197
|
- Gemfile
|
208
|
-
- Guardfile
|
209
198
|
- LICENSE.txt
|
210
199
|
- README.md
|
211
200
|
- Rakefile
|
@@ -220,10 +209,11 @@ files:
|
|
220
209
|
- lib/turnstile/collector/updater.rb
|
221
210
|
- lib/turnstile/configuration.rb
|
222
211
|
- lib/turnstile/logger.rb
|
223
|
-
- lib/turnstile/nad.rb
|
224
212
|
- lib/turnstile/observer.rb
|
225
|
-
- lib/turnstile/
|
213
|
+
- lib/turnstile/parser.rb
|
214
|
+
- lib/turnstile/runner.rb
|
226
215
|
- lib/turnstile/sampler.rb
|
216
|
+
- lib/turnstile/summary.rb
|
227
217
|
- lib/turnstile/tracker.rb
|
228
218
|
- lib/turnstile/version.rb
|
229
219
|
- spec/fixtures/sample-production.log
|
@@ -232,9 +222,9 @@ files:
|
|
232
222
|
- spec/turnstile/adapter_spec.rb
|
233
223
|
- spec/turnstile/collector/log_reader_spec.rb
|
234
224
|
- spec/turnstile/configuration_spec.rb
|
235
|
-
- spec/turnstile/nad_spec.rb
|
236
225
|
- spec/turnstile/observer_spec.rb
|
237
226
|
- spec/turnstile/sampler_spec.rb
|
227
|
+
- spec/turnstile/summary_spec.rb
|
238
228
|
- spec/turnstile/tracker_spec.rb
|
239
229
|
- spec/turnstile_spec.rb
|
240
230
|
- turnstile-rb.gemspec
|
@@ -258,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
258
248
|
version: '0'
|
259
249
|
requirements: []
|
260
250
|
rubyforge_project:
|
261
|
-
rubygems_version: 2.6.
|
251
|
+
rubygems_version: 2.6.11
|
262
252
|
signing_key:
|
263
253
|
specification_version: 4
|
264
254
|
summary: Asynchronous and non-invasive concurrent user tracking with Redis, by scanning
|
@@ -270,8 +260,8 @@ test_files:
|
|
270
260
|
- spec/turnstile/adapter_spec.rb
|
271
261
|
- spec/turnstile/collector/log_reader_spec.rb
|
272
262
|
- spec/turnstile/configuration_spec.rb
|
273
|
-
- spec/turnstile/nad_spec.rb
|
274
263
|
- spec/turnstile/observer_spec.rb
|
275
264
|
- spec/turnstile/sampler_spec.rb
|
265
|
+
- spec/turnstile/summary_spec.rb
|
276
266
|
- spec/turnstile/tracker_spec.rb
|
277
267
|
- spec/turnstile_spec.rb
|
data/Guardfile
DELETED
@@ -1,13 +0,0 @@
|
|
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/lib/turnstile/nad.rb
DELETED
data/lib/turnstile/rb.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require_relative '../turnstile'
|
data/spec/turnstile/nad_spec.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe 'Turnstile::Nad' do
|
4
|
-
|
5
|
-
subject { Turnstile::Nad.new }
|
6
|
-
|
7
|
-
describe '#data' do
|
8
|
-
context 'have some data' do
|
9
|
-
let(:aggregate) { {
|
10
|
-
'android' => 3,
|
11
|
-
'ios' => 2,
|
12
|
-
'total' => 5
|
13
|
-
}
|
14
|
-
}
|
15
|
-
|
16
|
-
let(:expected_string) {
|
17
|
-
<<-EOF
|
18
|
-
turnstile:android#{"\t"}n#{"\t"}3
|
19
|
-
turnstile:ios#{"\t"}n#{"\t"}2
|
20
|
-
turnstile:total#{"\t"}n#{"\t"}5
|
21
|
-
EOF
|
22
|
-
}
|
23
|
-
|
24
|
-
it "return data in NAD tab dilimited format" do
|
25
|
-
expect(subject).to receive(:aggregate).once.and_return(aggregate)
|
26
|
-
expect(subject.data).to eql(expected_string)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|