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
Human readable description. Use two newlines to break paragraphs.
Human readable metric name. All metrics must implement this method.
Human readable metric name. All metrics must implement this method.
Public Class Methods
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
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
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
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
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
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
# 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
# File lib/vanity/metric/base.rb, line 228 def connection @playground.connection end
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
– Storage –
# File lib/vanity/metric/base.rb, line 224 def destroy! connection.destroy_metric @id end
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
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
# File lib/vanity/metric/base.rb, line 232 def key(*args) "metrics:#{@id}:#{args.join(':')}" end
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
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
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
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
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
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