livingsocial/rearview-engine

View on GitHub
lib/rearview/templates/monitor.rb

Summary

Maintainability
A
1 hr
Test Coverage
#
# Do not edit unless you know what you're doing!!! This code affects the sandbox behavior
# and monitor functionality.
#

require 'rubygems'
require 'bundler'
require 'stringio'

Bundler.setup

require 'json'
%{utilities}

class Metric
  attr_reader :timestamp, :value

  def initialize(label_fn, timestamp, value)
    @label_fn  = label_fn
    @timestamp = timestamp
    @value     = value
  end

  def label
     @label_fn.call
   end

  def to_s
    "{ label: #{label}, timestamp: #{timestamp}, value: #{value.nil? ? "nil" : value.to_f} }"
  end
end


class TimeSeries
  attr_accessor :label
  attr_reader :entries

  def initialize(ts)
    @label   = ts.first["metric"]
    @entries = ts.map { |t| Metric.new(lambda { self.label }, t["timestamp"], t["value"]) }
    def @entries.to_s
      "[ #{self.join ", "} ]"
    end
  end

  def values
    @entries.map { |e| e.value }
  end

  def to_s
    "{ label: #{@label}, entries: [ #{@entries.join ", "} ] }"
  end
end


class Scoped
  include MonitorUtilities

  def initialize(timeout)
    @graph_data         = {}
    @graph_data.default = [] # make the default value an empty array
    @timeout            = timeout
  end

  def graph_value(name, timestamp, value)
    @graph_data[name] += [[timestamp, value]]
  end

  def fold_metrics(initial, &block)
    iterations = @timeseries[0].values.length
    iterations.times.inject(initial) do |accum, i|
      entries = @timeseries.map { |series| series.entries[i] }
      block.call accum, *entries
    end
  end

  def with_metrics(&block)
    iterations = @timeseries[0].values.length
    iterations.times.each do |i|
      entries = @timeseries.map { |series| series.entries[i] }
      block.call *entries
    end
  end

  def scoped_eval(namespace)
    # Copy instance variables from top-level class into current scope
    namespace.keys.each do |v|
      instance_variable_set "#{v}".to_sym, namespace[v]
    end

    # Bolt on a to_s to @timeseries to pretty print the instances
    def @timeseries.to_s
      "[ #{self.join ", "} ]"
    end

    out = StringIO.new

    t = Thread.start do
      $stdout = out
      $SAFE=3
      begin
        graph_value = lambda { |name, timestamp, value| graph_value(name, timestamp, value) }
        %{expression}
      rescue RuntimeError => e
        out.write e
        @error = e.to_s
      end
    end

    t.abort_on_exception = false

    begin
      t.join
    rescue => e
      out.write e
      @error = e.to_s
    ensure
      $SAFE = 0
      $stdout = STDOUT
      $stderr = STDERR
    end

    { :graph_data => @graph_data, :output => out.string, :error => @error}.to_json
  end
end


class Wrapper
  def create_timeseries(ts)
    ns   = {}
    ts   = ts.to_a.map { |t| TimeSeries.new(t) }
    vars = (0...ts.length).map do |i|
      varname = variable_by_offset(i)
      ns["@#{varname}"] = ts[i]
    end
    ns["@timeseries"] = ts
    ns
  end

  #
  # Return a variable name (e.g. a,b,c) from an ordinal
  #
  def variable_by_offset(index)
    offset = 'a'.ord
    dv  = index.divmod(26)
    ext = dv[0] > 0 ? dv[1].to_s : ""
    (offset + dv[1]).chr + ext
  end


  # Binding needs to be relative to the class, NOT top level
  def secure_eval(namespace)
    scoped = Scoped.new(%{timeout})
    ts = create_timeseries(namespace.to_hash.delete "@timeseries")
    ns = namespace.to_hash.merge(ts)
    puts scoped.scoped_eval(ns)
  end
end

json = <<-'EOF'
%{namespace}
EOF

Wrapper.new.secure_eval(JSON.parse(json))