Create GraphService object to manage calls to forked gnuplot process
for chart generation. Also add Graph DTO for encapsulating svg or various
other chart description languages.
---
deltacloud-aggregator.spec.in | 1 +
src/app/models/graph.rb | 9 ++
src/app/services/graph_service.rb | 186 +++++++++++++++++++++++++++++++++++++
src/config/environment.rb | 1 +
4 files changed, 197 insertions(+), 0 deletions(-)
create mode 100644 src/app/models/graph.rb
create mode 100644 src/app/services/graph_service.rb
diff --git a/deltacloud-aggregator.spec.in b/deltacloud-aggregator.spec.in
index 6f91751..88ed70e 100644
--- a/deltacloud-aggregator.spec.in
+++ b/deltacloud-aggregator.spec.in
@@ -28,6 +28,7 @@ Requires: rubygem(will_paginate)
Requires: postgresql
Requires: postgresql-server
Requires: ruby-postgres
+Requires: gnuplot >= 4.2.6
%package daemons
Summary: Deltacloud Aggregator daemons config
diff --git a/src/app/models/graph.rb b/src/app/models/graph.rb
new file mode 100644
index 0000000..f520a82
--- /dev/null
+++ b/src/app/models/graph.rb
@@ -0,0 +1,9 @@
+class Graph
+ attr_accessor :svg
+
+ QOS_AVG_TIME_TO_SUBMIT = "qos_avg_time_to_submit"
+ QUOTA_INSTANCES_IN_USE = "quota_instances_in_use"
+ def initialize
+ @svg = ""
+ end
+end
diff --git a/src/app/services/graph_service.rb
b/src/app/services/graph_service.rb
new file mode 100644
index 0000000..9a2d12a
--- /dev/null
+++ b/src/app/services/graph_service.rb
@@ -0,0 +1,186 @@
+class GraphService
+ require 'gnuplot'
+ require 'nokogiri'
+
+ def self.dashboard_quota (user,opts = {})
+ #FIXME add permission checks to filter what graphs user can get
+ graphs = Hash.new
+
+ #if a specific cloud account is given, just return that cloud account's
graph.
+ #otherwise return all graphs user has permission to see.
+ if opts[:cloud_account]
+ cloud_account = opts[:cloud_account]
+ cloud_account_graphs = Hash.new
+ cloud_account_graphs[Graph::QUOTA_INSTANCES_IN_USE] =
quota_instances_in_use_graph(cloud_account,opts)
+ graphs[cloud_account] = cloud_account_graphs
+ else
+ CloudAccount.all.each do |cloud_account|
+ cloud_account_graphs = Hash.new
+ cloud_account_graphs[Graph::QUOTA_INSTANCES_IN_USE] =
quota_instances_in_use_graph(cloud_account,opts)
+ graphs[cloud_account] = cloud_account_graphs
+ end
+ end
+ graphs
+ end
+
+ def self.dashboard_qos (user,opts = {})
+ #FIXME add permission checks to filter what graphs user can get
+ graphs = Hash.new
+
+ #if a specific provider is given, just return that provider's graph.
+ #otherwise return all graphs user has permission to see.
+ if opts[:provider]
+ provider = opts[:provider]
+ provider_graphs = Hash.new
+ provider_graphs[Graph::QOS_AVG_TIME_TO_SUBMIT] =
qos_avg_time_to_submit_graph(provider,opts)
+ graphs[provider] = provider_graphs
+ else
+ Provider.all.each do |provider|
+ provider_graphs = Hash.new
+ provider_graphs[Graph::QOS_AVG_TIME_TO_SUBMIT] =
qos_avg_time_to_submit_graph(provider,opts)
+ graphs[provider] = provider_graphs
+ end
+ end
+ graphs
+ end
+
+ private
+ def self.gnuplot_open( persist=false )
+ cmd = Gnuplot.gnuplot( persist ) or raise 'gnuplot not found'
+ output_stream = IO::popen( cmd, "r+")
+ end
+
+ def self.quota_instances_in_use_graph (cloud_account, opts = {})
+ #things we're checking for in opts: :max_value, :height, :width
+
+ unless max_value = opts[:max_value]
+ max_value = 100 unless max_value =
Quota.maximum('maximum_running_instances')
+ end
+ height = 80 unless height = opts[:height]
+ width = 150 unless width = opts[:width]
+
+ raw_svg = ""
+ gp = gnuplot_open
+ Gnuplot::Plot.new( gp ) do |plot|
+ plot.terminal "svg size #{width},#{height}"
+ plot.arbitrary_lines << "unset xtics"
+ plot.arbitrary_lines << "unset x2tics"
+ plot.arbitrary_lines << "unset ytics"
+ plot.arbitrary_lines << "unset y2tics"
+ plot.arbitrary_lines << "unset border"
+
+ plot.set "bmargin","0"
+ plot.set "lmargin","0"
+ plot.set "rmargin","0"
+ plot.set "tmargin","0"
+ plot.set "boxwidth 0.9 relative"
+ plot.set "style fill solid 1.0"
+ plot.set "xrange [.25:2.75]"
+ plot.set "yrange [0:#{max_value * 1.25}]" #we want to scale 25% larger
to leave room for label
+
+ x = [1,2]
+ #we'll just have zero values for the unexpected case where cloud_account
has no quota
+ y = x.collect { |v| 0 }
+ if cloud_account.quota
+ quota = cloud_account.quota
+ y = [quota.running_instances,quota.maximum_running_instances]
+ end
+
+ #The two arrays above are three columns of data for gnuplot.
+ plot.data << Gnuplot::DataSet.new( [x, y] ) do |ds|
+ ds.using = "1:2"
+ ds.with = "boxes"
+ ds.notitle
+ end
+
+ plot.data << Gnuplot::DataSet.new( [x, y] ) do |ds|
+ ds.using = "1:2:2"
+ ds.with = "labels left offset 0,.15 rotate"
+ ds.notitle
+ end
+ end
+ gp.flush
+ gp.close_write
+ gp.read(nil,raw_svg)
+ gp.close_read
+
+ #massage the svg so that the histogram's bars are oriented horizontally
+ xml = Nokogiri::XML(raw_svg)
+
+ nodes = xml.root.children
+ wrapper = Nokogiri::XML::Node.new "g", xml.root
+
+ nodes.each do |node|
+ node.parent = wrapper if node.name != "desc" && node.name != "defs"
+ end
+
+ wrapper.parent = xml.root
+ wrapper.set_attribute 'transform',"translate(#{width},0) rotate(90)
scale(#{height * 1.0 / width},#{width * 1.0/ height})"
+
+ text_nodes = []
+ xml.root.traverse do |node|
+ if node.name == 'text' && node.inner_text.strip =~ /^\d+$/
+ if node.parent.name == 'g'
+ pparent = node.parent
+ text_nodes << pparent
+ end
+ end
+ end
+
+ text_nodes.each do |node|
+ attr = node.get_attribute 'transform'
+ node.remove_attribute 'transform'
+ node.set_attribute 'transform',"#{attr} scale (#{height * 1.0 /
width},#{width * 1.0/ height})"
+ end
+
+ modified_svg = xml.to_s
+ graph = Graph.new
+ graph.svg = modified_svg
+ graph
+
+ end
+
+ def self.qos_avg_time_to_submit_graph (provider, opts = {})
+ #things we're checking for in opts: :height, :width
+
+ height = 60 unless height = opts[:height]
+ width = 100 unless width = opts[:width]
+
+ graph = Graph.new
+ gp = gnuplot_open
+
+ Gnuplot::Plot.new( gp ) do |plot|
+ plot.terminal "svg size #{width},#{height}"
+ plot.arbitrary_lines << "unset xtics"
+ plot.arbitrary_lines << "unset x2tics"
+ plot.arbitrary_lines << "unset ytics"
+ plot.arbitrary_lines << "unset y2tics"
+ plot.set "bmargin","0"
+ plot.set "lmargin","0"
+ plot.set "rmargin","0"
+ plot.set "tmargin","0"
+
+ #FIXME: get data from DataService for the provider.
+ #For demo, plot a random walk for demo of graph display until we hook
into DataService
+ #First build two equal-length arrays
+ x = (0..500).collect { |v| v.to_f }
+
+ walk = 0
+ y = x.collect { |v| rand > 0.5 ? walk = walk + 1 : walk = walk - 1 }
+ plot.set "yrange [-50:50]"
+
+ #This type of plot takes two equal length arrays of numbers as input.
+ plot.data << Gnuplot::DataSet.new( [x, y] ) do |ds|
+ ds.using = "1:2"
+ ds.with = "lines"
+ ds.notitle
+ end
+ end
+ gp.flush
+ gp.close_write
+ gp.read(nil,graph.svg)
+ gp.close_read
+ graph
+ end
+
+end
diff --git a/src/config/environment.rb b/src/config/environment.rb
index 5efa7bb..53ed761 100644
--- a/src/config/environment.rb
+++ b/src/config/environment.rb
@@ -47,6 +47,7 @@ Rails::Initializer.run do |config|
config.gem "haml"
config.gem "will_paginate"
config.gem "nokogiri", :version => ">= 1.4.0"
+ config.gem "gnuplot"
config.active_record.observers = :instance_observer, :task_observer
# Only load the plugins named here, in the order given. By default, all
plugins
--
1.6.6.1
_______________________________________________
deltacloud-devel mailing list
[email protected]
https://fedorahosted.org/mailman/listinfo/deltacloud-devel