vanity 1.3.0 → 1.4.0.beta
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/CHANGELOG +61 -3
- data/Gemfile +22 -14
- data/README.rdoc +9 -4
- data/Rakefile +72 -12
- data/bin/vanity +16 -4
- data/lib/vanity.rb +7 -5
- data/lib/vanity/adapters/abstract_adapter.rb +135 -0
- data/lib/vanity/adapters/mock_adapter.rb +157 -0
- data/lib/vanity/adapters/mongo_adapter.rb +162 -0
- data/lib/vanity/adapters/redis_adapter.rb +154 -0
- data/lib/vanity/backport.rb +0 -17
- data/lib/vanity/commands/upgrade.rb +34 -0
- data/lib/vanity/experiment/ab_test.rb +46 -41
- data/lib/vanity/experiment/base.rb +13 -15
- data/lib/vanity/frameworks/rails.rb +5 -9
- data/lib/vanity/metric/active_record.rb +10 -4
- data/lib/vanity/metric/base.rb +46 -23
- data/lib/vanity/metric/google_analytics.rb +7 -0
- data/lib/vanity/metric/remote.rb +53 -0
- data/lib/vanity/playground.rb +133 -49
- data/test/{ab_test_test.rb → experiment/ab_test.rb} +47 -3
- data/test/{experiment_test.rb → experiment/base_test.rb} +8 -8
- data/test/metric/active_record_test.rb +253 -0
- data/test/metric/base_test.rb +293 -0
- data/test/metric/google_analytics_test.rb +104 -0
- data/test/metric/remote_test.rb +108 -0
- data/test/myapp/app/controllers/application_controller.rbc +66 -0
- data/test/myapp/app/controllers/main_controller.rb +3 -3
- data/test/myapp/app/controllers/main_controller.rbc +347 -0
- data/test/myapp/config/boot.rbc +2534 -0
- data/test/myapp/config/environment.rbc +403 -0
- data/test/myapp/config/routes.rbc +174 -0
- data/test/myapp/log/production.log +2601 -0
- data/test/passenger_test.rb +14 -5
- data/test/passenger_test.rbc +0 -0
- data/test/playground_test.rbc +256 -0
- data/test/rails_test.rb +75 -22
- data/test/rails_test.rbc +4086 -0
- data/test/test_helper.rb +30 -7
- data/test/test_helper.rbc +4297 -0
- data/vanity.gemspec +6 -2
- metadata +74 -73
- data/lib/vanity/commands.rb +0 -2
- data/lib/vanity/mock_redis.rb +0 -76
- data/test/metric_test.rb +0 -622
- data/vendor/cache/RedCloth-4.2.2.gem +0 -0
- data/vendor/cache/actionmailer-2.3.5.gem +0 -0
- data/vendor/cache/actionpack-2.3.5.gem +0 -0
- data/vendor/cache/activerecord-2.3.5.gem +0 -0
- data/vendor/cache/activeresource-2.3.5.gem +0 -0
- data/vendor/cache/activesupport-2.3.5.gem +0 -0
- data/vendor/cache/autotest-4.2.7.gem +0 -0
- data/vendor/cache/autotest-fsevent-0.2.1.gem +0 -0
- data/vendor/cache/autotest-growl-0.2.0.gem +0 -0
- data/vendor/cache/bundler-0.9.7.gem +0 -0
- data/vendor/cache/classifier-1.3.1.gem +0 -0
- data/vendor/cache/directory_watcher-1.3.1.gem +0 -0
- data/vendor/cache/fastthread-1.0.7.gem +0 -0
- data/vendor/cache/garb-0.7.0.gem +0 -0
- data/vendor/cache/happymapper-0.3.0.gem +0 -0
- data/vendor/cache/jekyll-0.5.7.gem +0 -0
- data/vendor/cache/libxml-ruby-1.1.3.gem +0 -0
- data/vendor/cache/liquid-2.0.0.gem +0 -0
- data/vendor/cache/maruku-0.6.0.gem +0 -0
- data/vendor/cache/mocha-0.9.8.gem +0 -0
- data/vendor/cache/open4-1.0.1.gem +0 -0
- data/vendor/cache/passenger-2.2.9.gem +0 -0
- data/vendor/cache/rack-1.0.1.gem +0 -0
- data/vendor/cache/rails-2.3.5.gem +0 -0
- data/vendor/cache/rake-0.8.7.gem +0 -0
- data/vendor/cache/rubygems-update-1.3.5.gem +0 -0
- data/vendor/cache/shoulda-2.10.3.gem +0 -0
- data/vendor/cache/sqlite3-ruby-1.2.5.gem +0 -0
- data/vendor/cache/stemmer-1.0.1.gem +0 -0
- data/vendor/cache/syntax-1.0.0.gem +0 -0
- data/vendor/cache/sys-uname-0.8.4.gem +0 -0
- data/vendor/cache/timecop-0.3.4.gem +0 -0
- data/vendor/redis-rb/LICENSE +0 -20
- data/vendor/redis-rb/README.markdown +0 -36
- data/vendor/redis-rb/Rakefile +0 -62
- data/vendor/redis-rb/bench.rb +0 -44
- data/vendor/redis-rb/benchmarking/suite.rb +0 -24
- data/vendor/redis-rb/benchmarking/worker.rb +0 -71
- data/vendor/redis-rb/bin/distredis +0 -33
- data/vendor/redis-rb/examples/basic.rb +0 -16
- data/vendor/redis-rb/examples/incr-decr.rb +0 -18
- data/vendor/redis-rb/examples/list.rb +0 -26
- data/vendor/redis-rb/examples/sets.rb +0 -36
- data/vendor/redis-rb/lib/dist_redis.rb +0 -124
- data/vendor/redis-rb/lib/hash_ring.rb +0 -128
- data/vendor/redis-rb/lib/pipeline.rb +0 -21
- data/vendor/redis-rb/lib/redis.rb +0 -370
- data/vendor/redis-rb/lib/redis/raketasks.rb +0 -1
- data/vendor/redis-rb/profile.rb +0 -22
- data/vendor/redis-rb/redis-rb.gemspec +0 -30
- data/vendor/redis-rb/spec/redis_spec.rb +0 -637
- data/vendor/redis-rb/spec/spec_helper.rb +0 -4
- data/vendor/redis-rb/speed.rb +0 -16
- data/vendor/redis-rb/tasks/redis.tasks.rb +0 -140
data/lib/vanity/backport.rb
CHANGED
|
@@ -24,20 +24,3 @@ class Date
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
class Symbol
|
|
28
|
-
unless method_defined?(:to_proc)
|
|
29
|
-
# Backported from Ruby 1.9.
|
|
30
|
-
def to_proc
|
|
31
|
-
Proc.new { |*args| args.shift.__send__(self, *args) }
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
class Array
|
|
37
|
-
unless method_defined?(:minmax)
|
|
38
|
-
# Backported from Ruby 1.9.
|
|
39
|
-
def minmax
|
|
40
|
-
[min, max]
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Vanity
|
|
2
|
+
module Commands
|
|
3
|
+
class << self
|
|
4
|
+
# Upgrade to newer version of Vanity (this usually means doing magic in
|
|
5
|
+
# the database)
|
|
6
|
+
def upgrade
|
|
7
|
+
if Vanity.playground.connection.respond_to?(:redis)
|
|
8
|
+
redis = Vanity.playground.connection.redis
|
|
9
|
+
# Upgrade metrics from 1.3 to 1.4
|
|
10
|
+
keys = redis.keys("metrics:*")
|
|
11
|
+
if keys.empty?
|
|
12
|
+
puts "No metrics to upgrade"
|
|
13
|
+
else
|
|
14
|
+
puts "Updating #{keys.map { |name| name.split(":")[1] }.uniq.length} metrics"
|
|
15
|
+
keys.each do |key|
|
|
16
|
+
key << ":value:0" if key[/\d{4}-\d{2}-\d{2}$/]
|
|
17
|
+
redis.renamenx key, "vanity:#{key}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
# Upgrade experiments from 1.3 to 1.4
|
|
21
|
+
keys = redis.keys("vanity:1:*")
|
|
22
|
+
if keys.empty?
|
|
23
|
+
puts "No experiments to upgrade"
|
|
24
|
+
else
|
|
25
|
+
puts "Updating #{keys.map { |name| name.split(":")[2] }.uniq.length} experiments"
|
|
26
|
+
keys.each do |key|
|
|
27
|
+
redis.renamenx key, key.gsub(":1:", ":experiments:")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -93,7 +93,6 @@ module Vanity
|
|
|
93
93
|
|
|
94
94
|
def initialize(*args)
|
|
95
95
|
super
|
|
96
|
-
@alternatives = [false, true]
|
|
97
96
|
end
|
|
98
97
|
|
|
99
98
|
|
|
@@ -130,22 +129,18 @@ module Vanity
|
|
|
130
129
|
# alts = experiment(:background_color).alternatives
|
|
131
130
|
# puts "#{alts.count} alternatives, with the colors: #{alts.map(&:value).join(", ")}"
|
|
132
131
|
def alternatives(*args)
|
|
133
|
-
|
|
134
|
-
@alternatives = args.clone
|
|
135
|
-
end
|
|
132
|
+
@alternatives = args.empty? ? [true, false] : args.clone
|
|
136
133
|
class << self
|
|
137
134
|
define_method :alternatives, instance_method(:_alternatives)
|
|
138
135
|
end
|
|
139
|
-
|
|
136
|
+
nil
|
|
140
137
|
end
|
|
141
138
|
|
|
142
139
|
def _alternatives
|
|
143
140
|
alts = []
|
|
144
141
|
@alternatives.each_with_index do |value, i|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
conversions = redis[key("alts:#{i}:conversions")].to_i
|
|
148
|
-
alts << Alternative.new(self, i, value, participants, converted, conversions)
|
|
142
|
+
counts = @playground.collecting? ? connection.ab_counts(@id, i) : Hash.new(0)
|
|
143
|
+
alts << Alternative.new(self, i, value, counts[:participants], counts[:converted], counts[:conversions])
|
|
149
144
|
end
|
|
150
145
|
alts
|
|
151
146
|
end
|
|
@@ -186,16 +181,22 @@ module Vanity
|
|
|
186
181
|
# @example
|
|
187
182
|
# color = experiment(:which_blue).choose
|
|
188
183
|
def choose
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
index
|
|
194
|
-
|
|
195
|
-
|
|
184
|
+
if @playground.collecting?
|
|
185
|
+
if active?
|
|
186
|
+
identity = identity()
|
|
187
|
+
index = connection.ab_showing(@id, identity)
|
|
188
|
+
unless index
|
|
189
|
+
index = alternative_for(identity)
|
|
190
|
+
connection.ab_add_participant @id, index, identity
|
|
191
|
+
check_completion!
|
|
192
|
+
end
|
|
193
|
+
else
|
|
194
|
+
index = connection.ab_get_outcome(@id) || alternative_for(identity)
|
|
196
195
|
end
|
|
197
196
|
else
|
|
198
|
-
|
|
197
|
+
identity = identity()
|
|
198
|
+
@showing ||= {}
|
|
199
|
+
@showing[identity] ||= alternative_for(identity)
|
|
199
200
|
end
|
|
200
201
|
@alternatives[index.to_i]
|
|
201
202
|
end
|
|
@@ -227,12 +228,17 @@ module Vanity
|
|
|
227
228
|
# experiment(:green_button).select(nil)
|
|
228
229
|
# end
|
|
229
230
|
def chooses(value)
|
|
230
|
-
if
|
|
231
|
-
|
|
231
|
+
if @playground.collecting?
|
|
232
|
+
if value.nil?
|
|
233
|
+
connection.ab_not_showing @id, identity
|
|
234
|
+
else
|
|
235
|
+
index = @alternatives.index(value)
|
|
236
|
+
raise ArgumentError, "No alternative #{value.inspect} for #{name}" unless index
|
|
237
|
+
connection.ab_show @id, identity, index
|
|
238
|
+
end
|
|
232
239
|
else
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
redis[key("participant:#{identity}:show")] = index
|
|
240
|
+
@showing ||= {}
|
|
241
|
+
@showing[identity] = value.nil? ? nil : @alternatives.index(value)
|
|
236
242
|
end
|
|
237
243
|
self
|
|
238
244
|
end
|
|
@@ -240,8 +246,12 @@ module Vanity
|
|
|
240
246
|
# True if this alternative is currently showing (see #chooses).
|
|
241
247
|
def showing?(alternative)
|
|
242
248
|
identity = identity()
|
|
243
|
-
|
|
244
|
-
|
|
249
|
+
if @playground.collecting?
|
|
250
|
+
connection.ab_showing(@id, identity) == alternative.id
|
|
251
|
+
else
|
|
252
|
+
@showing ||= {}
|
|
253
|
+
@showing[identity] == alternative.id
|
|
254
|
+
end
|
|
245
255
|
end
|
|
246
256
|
|
|
247
257
|
|
|
@@ -358,12 +368,13 @@ module Vanity
|
|
|
358
368
|
|
|
359
369
|
# Alternative chosen when this experiment completed.
|
|
360
370
|
def outcome
|
|
361
|
-
|
|
362
|
-
outcome
|
|
371
|
+
return unless @playground.collecting?
|
|
372
|
+
outcome = connection.ab_get_outcome(@id)
|
|
373
|
+
outcome && _alternatives[outcome]
|
|
363
374
|
end
|
|
364
375
|
|
|
365
376
|
def complete!
|
|
366
|
-
return unless active?
|
|
377
|
+
return unless @playground.collecting? && active?
|
|
367
378
|
super
|
|
368
379
|
if @outcome_is
|
|
369
380
|
begin
|
|
@@ -377,24 +388,20 @@ module Vanity
|
|
|
377
388
|
outcome = best.id if best
|
|
378
389
|
end
|
|
379
390
|
# TODO: logging
|
|
380
|
-
|
|
391
|
+
connection.ab_set_outcome @id, outcome || 0
|
|
381
392
|
end
|
|
382
393
|
|
|
383
394
|
|
|
384
395
|
# -- Store/validate --
|
|
385
396
|
|
|
386
397
|
def destroy
|
|
387
|
-
|
|
388
|
-
redis.del key("alts:#{i}:participants")
|
|
389
|
-
redis.del key("alts:#{i}:converted")
|
|
390
|
-
redis.del key("alts:#{i}:conversions")
|
|
391
|
-
end
|
|
392
|
-
redis.del key(:outcome)
|
|
398
|
+
connection.destroy_experiment @id
|
|
393
399
|
super
|
|
394
400
|
end
|
|
395
401
|
|
|
396
402
|
def save
|
|
397
|
-
|
|
403
|
+
true_false unless @alternatives
|
|
404
|
+
fail "Experiment #{name} needs at least two alternatives" unless @alternatives.size >= 2
|
|
398
405
|
super
|
|
399
406
|
if @metrics.nil? || @metrics.empty?
|
|
400
407
|
warn "Please use metrics method to explicitly state which metric you are measuring against."
|
|
@@ -411,10 +418,9 @@ module Vanity
|
|
|
411
418
|
return unless active?
|
|
412
419
|
identity = identity() rescue nil
|
|
413
420
|
if identity
|
|
414
|
-
return if
|
|
421
|
+
return if connection.ab_showing(@id, identity)
|
|
415
422
|
index = alternative_for(identity)
|
|
416
|
-
|
|
417
|
-
redis.incrby key("alts:#{index}:conversions"), count
|
|
423
|
+
connection.ab_add_conversion @id, index, identity, count
|
|
418
424
|
check_completion!
|
|
419
425
|
end
|
|
420
426
|
end
|
|
@@ -432,13 +438,12 @@ module Vanity
|
|
|
432
438
|
participants.times do |identity|
|
|
433
439
|
index = @alternatives.index(value)
|
|
434
440
|
raise ArgumentError, "No alternative #{value.inspect} for #{name}" unless index
|
|
435
|
-
|
|
441
|
+
connection.ab_add_participant @id, index, "#{index}:#{identity}"
|
|
436
442
|
end
|
|
437
443
|
conversions.times do |identity|
|
|
438
444
|
index = @alternatives.index(value)
|
|
439
445
|
raise ArgumentError, "No alternative #{value.inspect} for #{name}" unless index
|
|
440
|
-
|
|
441
|
-
redis.incr key("alts:#{index}:conversions")
|
|
446
|
+
connection.ab_add_conversion @id, index, "#{index}:#{identity}"
|
|
442
447
|
end
|
|
443
448
|
end
|
|
444
449
|
end
|
|
@@ -68,7 +68,6 @@ module Vanity
|
|
|
68
68
|
@playground = playground
|
|
69
69
|
@id, @name = id.to_sym, name
|
|
70
70
|
@options = options || {}
|
|
71
|
-
@namespace = "#{@playground.namespace}:#{@id}"
|
|
72
71
|
@identify_block = method(:default_identify)
|
|
73
72
|
end
|
|
74
73
|
|
|
@@ -139,35 +138,35 @@ module Vanity
|
|
|
139
138
|
|
|
140
139
|
# Force experiment to complete.
|
|
141
140
|
def complete!
|
|
142
|
-
redis.setnx key(:completed_at), Time.now.to_i
|
|
143
|
-
@completed_at = redis[key(:completed_at)]
|
|
144
141
|
@playground.logger.info "vanity: completed experiment #{id}"
|
|
142
|
+
return unless @playground.collecting?
|
|
143
|
+
connection.set_experiment_completed_at @id, Time.now
|
|
144
|
+
@completed_at = connection.get_experiment_completed_at(@id)
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
# Time stamp when experiment was completed.
|
|
148
148
|
def completed_at
|
|
149
|
-
@completed_at ||=
|
|
150
|
-
@completed_at && Time.at(@completed_at.to_i)
|
|
149
|
+
@completed_at ||= connection.get_experiment_completed_at(@id)
|
|
151
150
|
end
|
|
152
151
|
|
|
153
152
|
# Returns true if experiment active, false if completed.
|
|
154
153
|
def active?
|
|
155
|
-
!
|
|
154
|
+
!@playground.collecting? || !connection.is_experiment_completed?(@id)
|
|
156
155
|
end
|
|
157
156
|
|
|
158
157
|
# -- Store/validate --
|
|
159
158
|
|
|
160
159
|
# Get rid of all experiment data.
|
|
161
160
|
def destroy
|
|
162
|
-
|
|
163
|
-
redis.del key(:completed_at)
|
|
161
|
+
connection.destroy_experiment @id
|
|
164
162
|
@created_at = @completed_at = nil
|
|
165
163
|
end
|
|
166
164
|
|
|
167
165
|
# Called by Playground to save the experiment definition.
|
|
168
166
|
def save
|
|
169
|
-
|
|
170
|
-
@
|
|
167
|
+
return unless @playground.collecting?
|
|
168
|
+
connection.set_experiment_created_at @id, Time.now
|
|
169
|
+
@created_at = connection.get_experiment_created_at(@id)
|
|
171
170
|
end
|
|
172
171
|
|
|
173
172
|
protected
|
|
@@ -199,15 +198,14 @@ module Vanity
|
|
|
199
198
|
# key => "vanity:experiments:green_button"
|
|
200
199
|
# key("participants") => "vanity:experiments:green_button:participants"
|
|
201
200
|
def key(name = nil)
|
|
202
|
-
|
|
201
|
+
"#{@id}:#{name}"
|
|
203
202
|
end
|
|
204
203
|
|
|
205
|
-
# Shortcut for Vanity.playground.
|
|
206
|
-
def
|
|
207
|
-
@playground.
|
|
204
|
+
# Shortcut for Vanity.playground.connection
|
|
205
|
+
def connection
|
|
206
|
+
@playground.connection
|
|
208
207
|
end
|
|
209
208
|
|
|
210
209
|
end
|
|
211
210
|
end
|
|
212
211
|
end
|
|
213
|
-
|
|
@@ -193,14 +193,10 @@ if defined?(Rails)
|
|
|
193
193
|
# Use Rails logger by default.
|
|
194
194
|
Vanity.playground.logger ||= Rails.logger
|
|
195
195
|
Vanity.playground.load_path = Rails.root + Vanity.playground.load_path
|
|
196
|
-
|
|
197
|
-
if !Vanity.playground.connected? && config_file.exist?
|
|
198
|
-
config = YAML.load_file(config_file)[Rails.env.to_s]
|
|
199
|
-
Vanity.playground.redis = config if config
|
|
200
|
-
end
|
|
196
|
+
Vanity.playground.collecting = Rails.env.production?
|
|
201
197
|
|
|
202
|
-
# Do this at the very end of initialization, allowing
|
|
203
|
-
#
|
|
198
|
+
# Do this at the very end of initialization, allowing you to change
|
|
199
|
+
# connection adapter, turn collection on/off, etc.
|
|
204
200
|
Rails.configuration.after_initialize do
|
|
205
201
|
Vanity.playground.load!
|
|
206
202
|
end
|
|
@@ -213,9 +209,9 @@ if defined?(PhusionPassenger)
|
|
|
213
209
|
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
|
214
210
|
if forked
|
|
215
211
|
begin
|
|
216
|
-
Vanity.playground.
|
|
212
|
+
Vanity.playground.establish_connection if Vanity.playground.collecting?
|
|
217
213
|
rescue Exception=>ex
|
|
218
|
-
Rails.logger.error "Error reconnecting
|
|
214
|
+
Rails.logger.error "Error reconnecting: #{ex.to_s}"
|
|
219
215
|
end
|
|
220
216
|
end
|
|
221
217
|
end
|
|
@@ -62,15 +62,21 @@ module Vanity
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# This track! method stores nothing, but calls the hooks.
|
|
65
|
-
def track!(
|
|
66
|
-
|
|
67
|
-
call_hooks
|
|
65
|
+
def track!(args = nil)
|
|
66
|
+
return unless @playground.collecting?
|
|
67
|
+
call_hooks *track_args(args)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def last_update_at
|
|
71
|
+
record = @ar_scoped.find(:first, :order=>"#@ar_timestamp DESC", :limit=>1, :select=>@ar_timestamp)
|
|
72
|
+
record && record.send(@ar_timestamp)
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
# AR model after_create callback notifies all the hooks.
|
|
71
76
|
def after_create(record)
|
|
77
|
+
return unless @playground.collecting?
|
|
72
78
|
count = @ar_column ? (record.send(@ar_column) || 0) : 1
|
|
73
|
-
call_hooks record.send(@ar_timestamp), count if count > 0 && @ar_scoped.exists?(record)
|
|
79
|
+
call_hooks record.send(@ar_timestamp), nil, [count] if count > 0 && @ar_scoped.exists?(record)
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
82
|
end
|
data/lib/vanity/metric/base.rb
CHANGED
|
@@ -4,9 +4,9 @@ module Vanity
|
|
|
4
4
|
# can also respond to addition methods (+track!+, +bounds+, etc), these are
|
|
5
5
|
# optional.
|
|
6
6
|
#
|
|
7
|
-
# This class implements a basic metric that tracks data and stores it in
|
|
8
|
-
#
|
|
9
|
-
# the methods your metric must and can implement.
|
|
7
|
+
# This class implements a basic metric that tracks data and stores it in the
|
|
8
|
+
# database. You can use this as the basis for your metric, or as reference
|
|
9
|
+
# for the methods your metric must and can implement.
|
|
10
10
|
#
|
|
11
11
|
# @since 1.1.0
|
|
12
12
|
class Metric
|
|
@@ -121,23 +121,42 @@ module Vanity
|
|
|
121
121
|
@playground, @name = playground, name.to_s
|
|
122
122
|
@id = (id || name.to_s.downcase.gsub(/\W+/, '_')).to_sym
|
|
123
123
|
@hooks = []
|
|
124
|
-
redis.setnx key(:created_at), Time.now.to_i
|
|
125
|
-
@created_at = Time.at(redis[key(:created_at)].to_i)
|
|
126
124
|
end
|
|
127
125
|
|
|
128
126
|
|
|
129
127
|
# -- Tracking --
|
|
130
128
|
|
|
131
|
-
# Called to track an action associated with this metric.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
# Called to track an action associated with this metric. Most common is not
|
|
130
|
+
# passing an argument, and it tracks a count of 1. You can pass a different
|
|
131
|
+
# value as the argument, or array of value (for multi-series metrics), or
|
|
132
|
+
# hash with the optional keys timestamp, identity and values.
|
|
133
|
+
#
|
|
134
|
+
# Example:
|
|
135
|
+
# hits.track!
|
|
136
|
+
# foo_and_bar.track! [5,11]
|
|
137
|
+
def track!(args = nil)
|
|
138
|
+
return unless @playground.collecting?
|
|
139
|
+
timestamp, identity, values = track_args(args)
|
|
140
|
+
connection.metric_track @id, timestamp, identity, values
|
|
141
|
+
@playground.logger.info "vanity: #{@id} with value #{values.join(", ")}"
|
|
142
|
+
call_hooks timestamp, identity, values
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Parses arguments to track! method and return array with timestamp,
|
|
146
|
+
# identity and array of values.
|
|
147
|
+
def track_args(args)
|
|
148
|
+
case args
|
|
149
|
+
when Hash
|
|
150
|
+
timestamp, identity, values = args.values_at(:timestamp, :identity, :values)
|
|
151
|
+
when Array
|
|
152
|
+
values = args
|
|
153
|
+
when Numeric
|
|
154
|
+
values = [args]
|
|
139
155
|
end
|
|
156
|
+
identity = Vanity.context.vanity_identity rescue nil
|
|
157
|
+
[timestamp || Time.now, identity, values || [1]]
|
|
140
158
|
end
|
|
159
|
+
protected :track_args
|
|
141
160
|
|
|
142
161
|
# Metric definitions use this to introduce tracking hook. The hook is
|
|
143
162
|
# called with metric identifier, timestamp, count and possibly additional
|
|
@@ -172,9 +191,6 @@ module Vanity
|
|
|
172
191
|
attr_reader :name
|
|
173
192
|
alias :to_s :name
|
|
174
193
|
|
|
175
|
-
# Time stamp when metric was created.
|
|
176
|
-
attr_reader :created_at
|
|
177
|
-
|
|
178
194
|
# Human readable description. Use two newlines to break paragraphs.
|
|
179
195
|
attr_accessor :description
|
|
180
196
|
|
|
@@ -192,28 +208,35 @@ module Vanity
|
|
|
192
208
|
# Given two arguments, a start date and an end date (inclusive), returns an
|
|
193
209
|
# array of measurements. All metrics must implement this method.
|
|
194
210
|
def values(from, to)
|
|
195
|
-
|
|
211
|
+
values = connection.metric_values(@id, from, to)
|
|
212
|
+
values.map { |row| row.first.to_i }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Returns date/time of the last update to this metric.
|
|
216
|
+
#
|
|
217
|
+
# @since 1.4.0
|
|
218
|
+
def last_update_at
|
|
219
|
+
connection.get_metric_last_update_at(@id)
|
|
196
220
|
end
|
|
197
221
|
|
|
198
222
|
|
|
199
223
|
# -- Storage --
|
|
200
224
|
|
|
201
225
|
def destroy!
|
|
202
|
-
|
|
226
|
+
connection.destroy_metric @id
|
|
203
227
|
end
|
|
204
228
|
|
|
205
|
-
def
|
|
206
|
-
@playground.
|
|
229
|
+
def connection
|
|
230
|
+
@playground.connection
|
|
207
231
|
end
|
|
208
232
|
|
|
209
233
|
def key(*args)
|
|
210
234
|
"metrics:#{@id}:#{args.join(':')}"
|
|
211
235
|
end
|
|
212
236
|
|
|
213
|
-
def call_hooks(timestamp,
|
|
214
|
-
count ||= 1
|
|
237
|
+
def call_hooks(timestamp, identity, values)
|
|
215
238
|
@hooks.each do |hook|
|
|
216
|
-
hook.call @id, timestamp,
|
|
239
|
+
hook.call @id, timestamp, values.first || 1
|
|
217
240
|
end
|
|
218
241
|
end
|
|
219
242
|
|