Class: Vanity::Metric

Inherits:
Object show all
Defined in:
lib/vanity/metric/base.rb,
lib/vanity/metric/remote.rb,
lib/vanity/metric/active_record.rb,
lib/vanity/metric/google_analytics.rb

Overview

A metric is an object that implements two methods: name and values. It can also respond to addition methods (track!, bounds, etc), these are optional.

This class implements a basic metric that tracks data and stores it in the database. You can use this as the basis for your metric, or as reference for the methods your metric must and can implement.

Since:

  • 1.1.0

Defined Under Namespace

Modules: ActiveRecord, Definition, GoogleAnalytics, Remote

Constant Summary collapse

AGGREGATES =

Since:

  • 1.1.0

[:average, :minimum, :maximum, :sum]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(playground, name, id = nil) ⇒ Metric

Takes playground (need this to access Redis), friendly name and optional id (can infer from name).

Since:

  • 1.1.0



120
121
122
123
124
# File 'lib/vanity/metric/base.rb', line 120

def initialize(playground, name, id = nil)
  @playground, @name = playground, name.to_s
  @id = (id || name.to_s.downcase.gsub(/\W+/, '_')).to_sym
  @hooks = []
end

Instance Attribute Details

#description(text = nil) ⇒ Object

Sets or returns description. For example

metric "Yawns/sec" do
  description "Most boring metric ever"
end

puts "Just defined: " + metric(:boring).description

Since:

  • 1.1.0



195
196
197
# File 'lib/vanity/metric/base.rb', line 195

def description
  @description
end

#nameObject (readonly) Also known as: to_s

Human readable metric name. All metrics must implement this method.

Since:

  • 1.1.0



191
192
193
# File 'lib/vanity/metric/base.rb', line 191

def name
  @name
end

Class Method Details

.bounds(metric) ⇒ Object

Helper method to return bounds for a metric.

A metric object may have a bounds method that returns lower and upper bounds. It may also have no bounds, or no bounds # method, in which case we return [nil, nil].

Examples:

upper = Vanity::Metric.bounds(metric).last

Since:

  • 1.1.0



71
72
73
# File 'lib/vanity/metric/base.rb', line 71

def bounds(metric)
  metric.respond_to?(:bounds) && metric.bounds || [nil, nil]
end

.data(metric, *args) ⇒ Object

Returns data set for a given date range. The data set is an array of date, value pairs.

First argument is the metric. Second argument is the start date, or number of days to go back in history, defaults to 90 days. Third argument is end date, defaults to today.

Examples:

These are all equivalent:

Vanity::Metric.data(my_metric) 
Vanity::Metric.data(my_metric, 90) 
Vanity::Metric.data(my_metric, Date.today - 89)
Vanity::Metric.data(my_metric, Date.today - 89, Date.today)

Since:

  • 1.1.0



87
88
89
90
91
92
# File 'lib/vanity/metric/base.rb', line 87

def data(metric, *args)
  first = args.shift || 90
  to = args.shift || Date.today
  from = first.respond_to?(:to_date) ? first.to_date : to - (first - 1)
  (from..to).zip(metric.values(from, to))
end

.description(metric) ⇒ Object

Helper method to return description for a metric.

A metric object may have a description method that returns a detailed description. It may also have no description, or no description method, in which case return nil.

Examples:

puts Vanity::Metric.description(metric)

Since:

  • 1.1.0



59
60
61
# File 'lib/vanity/metric/base.rb', line 59

def description(metric)
  metric.description if metric.respond_to?(:description)
end

.load(playground, stack, file) ⇒ Object

Playground uses this to load metric definitions.

Since:

  • 1.1.0



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/vanity/metric/base.rb', line 95

def load(playground, stack, file)
  fail "Circular dependency detected: #{stack.join('=>')}=>#{file}" if stack.include?(file)
  source = File.read(file)
  stack.push file
  id = File.basename(file, ".rb").downcase.gsub(/\W/, "_").to_sym
  context = Object.new
  context.instance_eval do
    extend Definition
    metric = eval(source, context.new_binding(playground, id), file)
    fail NameError.new("Expected #{file} to define metric #{id}", id) unless playground.metrics[id]
    metric
  end
rescue
  error = NameError.exception($!.message, id)
  error.set_backtrace $!.backtrace
  raise error
ensure
  stack.pop
end

Instance Method Details

#boundsObject

This method returns the acceptable bounds of a metric as an array with two values: low and high. Use nil for unbounded.

Alerts are created when metric values exceed their bounds. For example, a metric of user registration can use historical data to calculate expected range of new registration for the next day. If actual metric falls below the expected range, it could indicate registration process is broken. Going above higher bound could trigger opening a Champagne bottle.

The default implementation returns nil.

Since:

  • 1.1.0



184
185
# File 'lib/vanity/metric/base.rb', line 184

def bounds
end

#call_hooks(timestamp, identity, values) ⇒ Object

Since:

  • 1.1.0



237
238
239
240
241
# File 'lib/vanity/metric/base.rb', line 237

def call_hooks(timestamp, identity, values)
  @hooks.each do |hook|
    hook.call @id, timestamp, values.first || 1
  end
end

#connectionObject

Since:

  • 1.1.0



229
230
231
# File 'lib/vanity/metric/base.rb', line 229

def connection
  @playground.connection
end

#destroy!Object

– Storage –

Since:

  • 1.1.0



225
226
227
# File 'lib/vanity/metric/base.rb', line 225

def destroy!
  connection.destroy_metric @id
end

#google_analytics(web_property_id, *args) ⇒ Object

Use Google Analytics metric. Note: you must require “garb” before vanity.

Examples:

Page views

metric "Page views" do
  google_analytics "UA-1828623-6"
end

Visits

metric "Visits" do
  google_analytics "UA-1828623-6", :visits
end

See Also:

Since:

  • 1.3.0



18
19
20
21
22
23
24
25
26
27
# File 'lib/vanity/metric/google_analytics.rb', line 18

def google_analytics(web_property_id, *args)
  require "garb"
  options = Hash === args.last ? args.pop : {}
  metric = args.shift || :pageviews
  @ga_resource = Vanity::Metric::GoogleAnalytics::Resource.new(web_property_id, metric)
  @ga_mapper = options[:mapper] ||= lambda { |entry| entry.send(@ga_resource.metrics.elements.first).to_i }
  extend GoogleAnalytics
rescue LoadError
  fail LoadError, "Google Analytics metrics require Garb, please gem install garb first"
end

#hook(&block) ⇒ Object

Metric definitions use this to introduce tracking hook. The hook is called with metric identifier, timestamp, count and possibly additional arguments.

For example:

hook do |metric_id, timestamp, count|
  syslog.info metric_id
end

Since:

  • 1.1.0



169
170
171
# File 'lib/vanity/metric/base.rb', line 169

def hook(&block)
  @hooks << block
end

#key(*args) ⇒ Object

Since:

  • 1.1.0



233
234
235
# File 'lib/vanity/metric/base.rb', line 233

def key(*args)
  "metrics:#{@id}:#{args.join(':')}"
end

#last_update_atObject

Returns date/time of the last update to this metric.

Since:

  • 1.4.0



218
219
220
# File 'lib/vanity/metric/base.rb', line 218

def last_update_at
  connection.get_metric_last_update_at(@id)
end

#model(class_or_scope, options = nil) ⇒ Object

Use an ActiveRecord model to get metric data from database table. Also forwards after_create callbacks to hooks (updating experiments).

Supported options: :conditions – Only select records that match this condition :average – Metric value is average of this column :minimum – Metric value is minimum of this column :maximum – Metric value is maximum of this column :sum – Metric value is sum of this column :timestamp – Use this column to filter/group records (defaults to created_at)

Examples:

Track sign ups using User model

metric "Signups" do
  model Account
end

Track satisfaction using Survey model

metric "Satisfaction" do
  model Survey, :average=>:rating
end

Track only high ratings

metric "High ratings" do
  model Rating, :conditions=>["stars >= 4"]
end

Track only high ratings (using scope)

metric "High ratings" do
  model Rating.high
end

See Also:

Since:

  • 1.2.0



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/vanity/metric/active_record.rb', line 37

def model(class_or_scope, options = nil)
  options = options || {}
  conditions = options.delete(:conditions)
  @ar_scoped = conditions ? class_or_scope.scoped(:conditions=>conditions) : class_or_scope
  @ar_aggregate = AGGREGATES.find { |key| options.has_key?(key) }
  @ar_column = options.delete(@ar_aggregate)
  fail "Cannot use multiple aggregates in a single metric" if AGGREGATES.find { |key| options.has_key?(key) }
  @ar_timestamp = options.delete(:timestamp) || :created_at
  @ar_timestamp, @ar_timestamp_table = @ar_timestamp.to_s.split('.').reverse
  @ar_timestamp_table ||= @ar_scoped.table_name
  fail "Unrecognized options: #{options.keys * ", "}" unless options.empty?
  @ar_scoped.after_create self
  extend ActiveRecord
end

#remote(url = nil) ⇒ Object

Specifies the base URL to use for a remote metric. For example:

metric :sandbox do
  remote "http://api.vanitydash.com/metrics/sandbox"
end

Since:

  • 1.1.0



11
12
13
14
15
16
# File 'lib/vanity/metric/remote.rb', line 11

def remote(url = nil)
  @remote_url = URI.parse(url) if url
  @mutex ||= Mutex.new
  extend Remote
  @remote_url
end

#track!(args = nil) ⇒ Object

Called to track an action associated with this metric. Most common is not passing an argument, and it tracks a count of 1. You can pass a different value as the argument, or array of value (for multi-series metrics), or hash with the optional keys timestamp, identity and values.

Example:

hits.track!
foo_and_bar.track! [5,11]

Since:

  • 1.1.0



137
138
139
140
141
142
143
# File 'lib/vanity/metric/base.rb', line 137

def track!(args = nil)
  return unless @playground.collecting?
  timestamp, identity, values = track_args(args)
  connection.metric_track @id, timestamp, identity, values
  @playground.logger.info "vanity: #{@id} with value #{values.join(", ")}"
  call_hooks timestamp, identity, values
end

#values(from, to) ⇒ Object

Given two arguments, a start date and an end date (inclusive), returns an array of measurements. All metrics must implement this method.

Since:

  • 1.1.0



210
211
212
213
# File 'lib/vanity/metric/base.rb', line 210

def values(from, to)
  values = connection.metric_values(@id, from, to)
  values.map { |row| row.first.to_i }
end