class Vanity::Metric

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

Constants

AGGREGATES

Attributes

description[W]

Human readable description. Use two newlines to break paragraphs.

name[R]

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

to_s[R]

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

Public Class Methods

bounds(metric) click to toggle source

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]+.

@example

upper = Vanity::Metric.bounds(metric).last
# File lib/vanity/metric/base.rb, line 70
def bounds(metric)
  metric.respond_to?(:bounds) && metric.bounds || [nil, nil]
end
data(metric, *args) click to toggle source

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.

@example 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)
# File lib/vanity/metric/base.rb, line 86
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) click to toggle source

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.

@example

puts Vanity::Metric.description(metric)
# File lib/vanity/metric/base.rb, line 58
def description(metric)
  metric.description if metric.respond_to?(:description)
end
load(playground, stack, file) click to toggle source

Playground uses this to load metric definitions.

# File lib/vanity/metric/base.rb, line 94
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
new(playground, name, id = nil) click to toggle source

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

# File lib/vanity/metric/base.rb, line 119
def initialize(playground, name, id = nil)
  @playground, @name = playground, name.to_s
  @id = (id || name.to_s.downcase.gsub(/\W+/, '_')).to_sym
  @hooks = []
end

Public Instance Methods

bounds() click to toggle source

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.

# File lib/vanity/metric/base.rb, line 183
def bounds
end
call_hooks(timestamp, identity, values) click to toggle source
# File lib/vanity/metric/base.rb, line 236
def call_hooks(timestamp, identity, values)
  @hooks.each do |hook|
    hook.call @id, timestamp, values.first || 1, :identity=>identity
  end
end
connection() click to toggle source
# File lib/vanity/metric/base.rb, line 228
def connection
  @playground.connection
end
description(text = nil) click to toggle source

Sets or returns description. For example

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

puts "Just defined: " + metric(:boring).description
# File lib/vanity/metric/base.rb, line 202
def description(text = nil)
  @description = text if text
  @description if defined?(@description)
end
destroy!() click to toggle source

– Storage –

# File lib/vanity/metric/base.rb, line 224
def destroy!
  connection.destroy_metric @id
end
google_analytics(web_property_id, *args) click to toggle source

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

@example Page views

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

@example Visits

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

@since 1.3.0 @see Vanity::Metric::GoogleAnalytics

# 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) click to toggle source

Metric definitions use this to introduce tracking hooks. 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
# File lib/vanity/metric/base.rb, line 168
def hook(&block)
  @hooks << block
end
key(*args) click to toggle source
# File lib/vanity/metric/base.rb, line 232
def key(*args)
  "metrics:#{@id}:#{args.join(':')}"
end
last_update_at() click to toggle source

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

@since 1.4.0

# File lib/vanity/metric/base.rb, line 217
def last_update_at
  connection.get_metric_last_update_at(@id)
end
model(class_or_scope, options = nil) click to toggle source

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)

@example Track sign ups using User model

metric "Signups" do
  model Account
end

@example Track satisfaction using Survey model

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

@example Track only high ratings

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

@example Track only high ratings (using scope)

metric "High ratings" do
  model Rating.high
end

@since 1.2.0 @see Vanity::Metric::ActiveRecord

# File lib/vanity/metric/active_record.rb, line 37
def model(class_or_scope, options = nil)
  ActiveSupport.on_load(:active_record, :yield=>true) do
    class_or_scope = class_or_scope.constantize if class_or_scope.is_a?(String)
    options = options || {}
    conditions = options.delete(:conditions)

    @ar_scoped = conditions ? class_or_scope.where(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

    @ar_identity_block = options.delete(:identity)

    fail "Unrecognized options: #{options.keys * ", "}" unless options.empty?

    @ar_scoped.after_create(self)
    extend ActiveRecord
  end
end
remote(url = nil) click to toggle source

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

metric :sandbox do
  remote "http://api.vanitydash.com/metrics/sandbox"
end
# 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) click to toggle source

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]
# File lib/vanity/metric/base.rb, line 136
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) click to toggle source

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

# File lib/vanity/metric/base.rb, line 209
def values(from, to)
  values = connection.metric_values(@id, from, to)
  values.map { |row| row.first.to_i }
end

Protected Instance Methods

track_args(args) click to toggle source

Parses arguments to track! method and return array with timestamp, identity and array of values.

# File lib/vanity/metric/base.rb, line 146
def track_args(args)
  case args
  when Hash
    timestamp, identity, values = args.values_at(:timestamp, :identity, :values)
  when Array
    values = args
  when Numeric
    values = [args]
  end
  identity ||= Vanity.context.vanity_identity rescue nil
  [timestamp || Time.now, identity, values || [1]]
end