class Pencil::Models::Graph
Public Class Methods
new(name, params={})
click to toggle source
Calls superclass method
Pencil::Models::Base::new
# File lib/pencil/models/graph.rb, line 7 def initialize(name, params={}) super @params["hosts"] ||= ["*"] @params["title"] ||= name if not @params["targets"] raise ArgumentError, "graph #{name} needs a 'targets' map" end end
Public Instance Methods
expand()
click to toggle source
return an array of all metrics matching the specifications in @params metrics are arrays of fields (once delimited by periods)
# File lib/pencil/models/graph.rb, line 301 def expand url = URI.join(@params[:graphite_url], "/metrics/expand/?query=").to_s metrics = [] require "json" @params["targets"].each do |k, v| # temporary hack for Map class complaining block parameters metric = [k, v] unless metric.first.instance_of?(Array) # wrap it metric[0] = [{metric[0] => nil}] end metric.first.each do |m| composed = compose_metric(m.first.first, "*", "*") composed2 = compose_metric2(m.first.first, "*", "*") query = open("#{url}#{composed}").read query2 = open("#{url}#{composed2}").read results = JSON.parse(query)["results"] results2 = JSON.parse(query2)["results"].map {|x| x.split('.')[0..-2].join('.')} metrics << results - results2 end end return metrics.flatten.map { |x| x.split(".") } end
handle_metric(name, opts, inner=false)
click to toggle source
FIXME Map().keys => Array of strings inner means we’re dealing with a complex key; @params will be applied make sure to apply alias and color arguments last, if applicable
# File lib/pencil/models/graph.rb, line 88 def handle_metric(name, opts, inner=false) later = [] ret = name.dup if inner @params.each do |k, v| ret = translate(k, ret, v, true) end end (opts||{}).each do |k, v| if k == "color" || k == "key" later << [k, v] else ret = translate(k, ret, v) end end later.each do |k, v| ret = translate(k, ret, v) end ret end
hosts_clusters()
click to toggle source
# File lib/pencil/models/graph.rb, line 327 def hosts_clusters metrics = expand clusters = Set.new # find the indicies of the clusters and hosts f = @params[:metric_format].dup.split("%m") first = f.first.split(".") last = f.last.split(".") ci = hi = nil first.each_with_index do |v, i| ci = i if v.match("%c") hi = i if v.match("%h") end unless ci && hi last.reverse.each_with_index do |v, i| ci = (-1) -i if v.match("%c") hi = (-1) -i if v.match("%h") end end hosts = metrics.map do |m| Host.new(m[hi], m[ci], @params) end.uniq # filter by what matches the graph definition hosts = hosts.select { |h| h.multi_match(@params["hosts"]) } hosts.each { |h| clusters << h.cluster } return hosts, clusters end
render_url(hosts, clusters, opts={})
click to toggle source
# File lib/pencil/models/graph.rb, line 109 def render_url(hosts, clusters, opts={}) opts = { :sum => nil, :title => @params["title"], }.merge(opts) if ! [:global, :cluster, nil].member?(opts[:sum]) raise ArgumentError, "render graph #{name}: invalid :sum - #{opts[:sum]}" end sym_hash = {} (opts[:dynamic_url_opts]||[]).each do |k,v| sym_hash[k.to_sym] = v end # fixme key checking may be necessary url_opts = { :title => opts[:title], }.merge(@params[:url_opts]).merge(sym_hash) url_opts[:from] = url_opts.delete(:stime) || "" url_opts[:until] = url_opts.delete(:etime) || "" url_opts.delete(:start) url_opts.delete(:duration) graphite_opts = [ "vtitle", "yMin", "yMax", "lineWidth", "areaMode", "template", "lineMode", "bgcolor", "graphOnly", "hideAxes", "hideGrid", "hideLegend", "fgcolor", "fontSize", "fontName", "fontItalic", "fontBold", "logBase" ] @params.each do |k, v| if graphite_opts.member?(k) url_opts[k.to_sym] = v end end target = [] colors = [] #FIXME code duplication if opts[:sum] == :global @params["targets"].each do |stat_name, o| z = new_map(o) z[:key] ||= stat_name ####################### if stat_name.instance_of?(Array) metric = stat_name.map do |m| mm = compose_metric(m.keys.first, "{#{clusters.to_a.join(',')}}", "{#{hosts.to_a.join(',')}}") if z.keys.member?("divideSeries") handle_metric(translate(:sumSeries, mm), m[m.keys.first], true) else handle_metric(mm, m[m.keys.first], true) end end.join(",") else metric = compose_metric(stat_name, "{#{clusters.to_a.join(',')}}", "{#{hosts.to_a.join(',')}}") metric = handle_metric(metric, {}, true) end ####################### z[:key] = "global #{z[:key]}" # target << handle_metric(translate(:sumSeries, metric), z) if z.keys.member?('divideSeries') # special case # apply divideSeries, sumSeries then other options res = translate(:divideSeries, metric) res = translate(:sumSeries, res) z.delete(:divideSeries) h = YAML::Omap.new z.each { |k,v| h[k] = v unless k == 'divideSeries' } target << handle_metric(res, h) else target << handle_metric(translate(:sumSeries, metric), z) end if !@params[:use_color] || (!z[:color] && @params[:use_color]) colors << next_color(colors, z[:color]) end end # @params["targets"].each elsif opts[:sum] == :cluster # one line per cluster/metric clusters.each do |cluster| @params["targets"].each do |stat_name, o| z = new_map(o) metrics = [] ####################### h = "{#{hosts.to_a.join(',')}}" if stat_name.instance_of?(Array) metrics << stat_name.map do |m| mm = compose_metric(m.keys.first, cluster, h) # note: we take the ratio of the sums in this case, instead of # the sums of the ratios if z.keys.member?('divideSeries') # divideSeries is picky about the number of series given as # arguments, so sum them in this case handle_metric(translate(:sumSeries, mm), m[m.keys.first], true) else handle_metric(mm, m[m.keys.first], true) end end.join(",") else metrics << handle_metric(compose_metric(stat_name, cluster, h), {}, true) end ####################### z[:key] = "#{cluster} #{z[:key]}" if z.keys.member?('divideSeries') # special case # apply divideSeries, sumSeries then other options res = translate(:divideSeries, metrics.join(',')) res = translate(:sumSeries, res) z.delete(:divideSeries) h = Map.new z.each { |k,v| h[k] = v unless k == 'divideSeries' } target << handle_metric(res, h) else target << handle_metric(translate(:sumSeries, metrics.join(',')), z) end if !@params[:use_color] || (!z[:color] && @params[:use_color]) colors << next_color(colors, z[:color]) end end # metrics.each end # clusters.each else # one line per {metric,host,colo} @params["targets"].each do |stat_name, o| z = new_map(o) clusters.each do |cluster| hosts.each do |host| label = "#{host} #{z[:key]}" ################# if stat_name.instance_of?(Array) metric = stat_name.map do |m| mm = compose_metric(m.keys.first, cluster, host) handle_metric(mm, m[m.keys.first], true) end.join(",") else metric = handle_metric(compose_metric(stat_name, cluster, host), {}, true) end ################# if label =~ /\*/ # for this particular type of graph, don't display a legend, # and color with abandon url_opts[:hideLegend] = true z.delete(:color) # fixme proper labeling... maybe # With wildcards let graphite construct the legend (or not). # Since we're handling wildcards we don't know how many # hosts will match, so just put in the default color list. # technically we do know, so this can be fixed z.delete(:key) target << handle_metric(metric, z) colors.concat(@params[:default_colors]) if colors.empty? else z[:key] = "#{host}/#{cluster} #{z[:key]}" target << handle_metric(metric, z) if !@params[:use_color] || (!z[:color] && @params[:use_color]) colors << next_color(colors, z[:color]) end end end end end # @params["targets"].each end # if opts[:sum] url_opts[:target] = target url_opts[:colorList] = colors.join(",") url = URI.join(@params[:graphite_url], "/render/?").to_s url_parts = [] url_opts.each do |k, v| [v].flatten.each do |v2| url_parts << "#{URI.escape(k.to_s)}=#{URI.escape(v2.to_s)}" end end url += url_parts.join("&") return url end
translate(func, str, arg=nil, pass=false)
click to toggle source
translate STR into graphite-speak for applying FUNC to STR graphite functions take zero or one argument pass passes STR through, instead of raising an error if FUNC isn’t recognized
# File lib/pencil/models/graph.rb, line 26 def translate(func, str, arg=nil, pass=false) # puts "calling translate" # puts "func => #{func}" # puts "str => #{str}" # puts "arg => #{arg}" # procs and lambdas don't support default arguments in 1.8, so I have to # do this z = lambda { |*body| "#{func}(#{body[0]||str})" } y = "#{str}, #{arg}" x = lambda { z.call(y) } return \ case func.to_s # comb when "sumSeries", "averageSeries", "minSeries", "maxSeries", "group" z.call # transform when "scale", "offset" # perhaps .to_f x.call when "derivative", "integral" z.call when "nonNegativeDerivative" z.call("#{str}#{', ' + arg if arg}") when "log", "timeShift", "summarize", "hitcount", # calculate "movingAverage", "stdev", "asPercent" x.call when "diffSeries", "divideSeries" z.call # filters when "mostDeviant" z.call("#{arg}, #{str}") when "highestCurrent", "lowestCurrent", "nPercentile", "currentAbove", "currentBelow", "highestAverage", "lowestAverage", "averageAbove", "averageBelow", "maximumAbove", "maximumBelow" x.call when "sortByMaxima", "minimalist" z.call when "limit", "exclude" x.call when "key", "alias" "alias(#{str}, \"#{arg}\")" when "cumulative", "drawAsInfinite" z.call when "lineWidth" x.call when "dashed", "keepLastValue" z.call when "substr", "threshold" x.call when "color" @params[:use_color] ? "color(#{str}, \"#{arg}\")" : str else raise "BAD FUNC #{func}" unless pass str end end
width(opts={})
click to toggle source
# File lib/pencil/models/graph.rb, line 18 def width(opts={}) opts["width"] || @params[:url_opts][:width] end
Private Instance Methods
new_map(opts)
click to toggle source
if Map() in config.rb converted a YAML::Omap
into an array, we need to turn it back into a Map (which is ordered, conveniently)
# File lib/pencil/models/graph.rb, line 385 def new_map (opts) if opts.is_a?(Array) z = Map(opts.flatten) else z = Map(opts) end end
next_color(colors, preferred_color=nil)
click to toggle source
# File lib/pencil/models/graph.rb, line 358 def next_color(colors, preferred_color=nil) default_colors = @params[:default_colors].clone if preferred_color and !colors.member?(preferred_color) return preferred_color end if preferred_color and ! default_colors.member?(preferred_color) default_colors << preferred_color end weights = Hash.new(0) colors.each do |c| weights[c] += 1 end i = 0 loop do default_colors.each do |c| return c if weights[c] == i end i += 1 end end