turnstile-rb 2.0.0 → 2.0.1
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.
- 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
|
-
[](https://badge.fury.io/rb/turnstile-rb)
|
2
|
+
[](https://travis-ci.org/kigster/turnstile-rb)
|
3
|
+
[](https://codeclimate.com/github/kigster/turnstile-rb/maintainability)
|
4
|
+
[](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
|