lib/rearview/templates/monitor.rb
#
# 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))