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

Reply via email to