From: martyntaylor mtaylor@redhat.com
--- src/app/controllers/dashboard_controller.rb | 18 +- src/app/models/graph.rb | 28 +++ src/app/services/graph_service.rb | 320 +++++++++++++++++---------- src/app/views/dashboard/summary.haml | 8 +- 4 files changed, 255 insertions(+), 119 deletions(-)
diff --git a/src/app/controllers/dashboard_controller.rb b/src/app/controllers/dashboard_controller.rb index bcd2073..757598d 100644 --- a/src/app/controllers/dashboard_controller.rb +++ b/src/app/controllers/dashboard_controller.rb @@ -31,17 +31,25 @@ class DashboardController < ApplicationController return params[:ajax] == "true" end
- def provider_qos_graph + def provider_qos_avg_time_to_submit_graph params[:provider] = Provider.find(params[:id]) - graph = GraphService.dashboard_qos(current_user, params)[params[:provider]][Graph::QOS_AVG_TIME_TO_SUBMIT] + graph = GraphService.dashboard_qos_avg_time_to_submit_graph(current_user, params)[params[:provider]][Graph::QOS_AVG_TIME_TO_SUBMIT] respond_to do |format| format.svg { render :xml => graph.svg} end end
- def account_quota_graph - params[:account] = CloudAccount.find(params[:id]) - graph = GraphService.dashboard_quota(current_user, params)[params[:account]][Graph::QUOTA_INSTANCES_IN_USE] + def quota_usage_graph + if params[:cloud_account_id] + params[:parent] = CloudAccount.find(params[:cloud_account_id]) + elsif params[:pool_id] + params[:parent] = Pool.find(params[:pool_id]) + else + return nil + end + + graphs = GraphService.dashboard_quota_usage(current_user, params) + graph = graphs[params[:parent]][Graph.get_quota_usage_graph_name(params[:resource_name])] respond_to do |format| format.svg { render :xml => graph.svg} end diff --git a/src/app/models/graph.rb b/src/app/models/graph.rb index 1c20dc3..cf1b498 100644 --- a/src/app/models/graph.rb +++ b/src/app/models/graph.rb @@ -4,7 +4,35 @@ class Graph QOS_AVG_TIME_TO_SUBMIT = "qos_avg_time_to_submit" QUOTA_INSTANCES_IN_USE = "quota_instances_in_use" INSTANCES_BY_PROVIDER_PIE = "instances_by_provider_pie" + + # Quota Usage Graphs + QUOTA_USAGE_RUNNING_INSTANCES = "quota_utilization_running_instances" + QUOTA_USAGE_RUNNING_MEMORY = "quota_utilization_running_memory" + QUOTA_USAGE_RUNNING_CPUS = "quota_utilization_running_cpus" + QUOTA_USAGE_TOTAL_INSTANCES = "quota_utilization_total_instances" + QUOTA_USAGE_TOTAL_STORAGE = "quota_utilization_total_storage" + QUOTA_USAGE_OVERALL = "quota_utilization_overall" + def initialize @svg = "" end + + def self.get_quota_usage_graph_name(resource_name) + case resource_name + when Quota::RESOURCE_RUNNING_INSTANCES + return QUOTA_USAGE_RUNNING_INSTANCES + when Quota::RESOURCE_RUNNING_MEMORY + return QUOTA_USAGE_RUNNING_MEMORY + when Quota::RESOURCE_RUNNING_CPUS + return QUOTA_USAGE_RUNNING_CPUS + when Quota::RESOURCE_TOTAL_INSTANCES + return QUOTA_USAGE_TOTAL_INSTANCES + when Quota::RESOURCE_TOTAL_STORAGE + return QUOTA_USAGE_TOTAL_STORAGE + when Quota::RESOURCE_OVERALL + return QUOTA_USAGE_OVERALL + else + return nil + end + end end diff --git a/src/app/services/graph_service.rb b/src/app/services/graph_service.rb index 11da01c..6c17743 100644 --- a/src/app/services/graph_service.rb +++ b/src/app/services/graph_service.rb @@ -3,6 +3,8 @@ class GraphService require 'nokogiri' require 'scruffy'
+ DATA_SERVICE = DataServiceActiveRecord + def self.dashboard_quota (user,opts = {}) #FIXME add permission checks to filter what graphs user can get graphs = Hash.new @@ -12,7 +14,7 @@ class GraphService 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) + cloud_account_graphs[Graph::QUOTA_INSTANCES_IN_USE] = qos_failure_rate_graph(parent, opts = {}) graphs[cloud_account] = cloud_account_graphs else CloudAccount.all.each do |cloud_account| @@ -24,7 +26,16 @@ class GraphService graphs end
- def self.dashboard_qos (user,opts = {}) + def self.dashboard_quota_usage(user, opts = {}) + parent = opts[:parent] + + graphs = Hash.new + graphs[parent] = quota_usage_graph(parent, opts) + + return graphs + end + + def self.dashboard_qos_avg_time_to_submit_graph(user, opts = {}) #FIXME add permission checks to filter what graphs user can get graphs = Hash.new
@@ -58,7 +69,198 @@ class GraphService output_stream = IO::popen( cmd, "r+") end
- def self.quota_instances_in_use_graph (cloud_account, opts = {}) + def self.quota_usage_graph (parent, opts = {}) + 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 parent.quota + quota = parent.quota + data_point = DataServiceActiveRecord.quota_usage(parent, opts[:resource_name]) + #Handle No Limit case + if data_point.max == Quota::NO_LIMIT + y = [data_point.used, nil] + else + y = [data_point.used, data_point.max] + end + end + + chart_opts = {:x => x, :y => y} + + graphs = Hash.new + graphs[Graph.get_quota_usage_graph_name(opts[:resource_name])] = draw_bar_chart(opts, chart_opts) + return graphs + end + + def self.qos_avg_time_to_submit_graph(parent, opts = {}) + start_time = Time.parse(opts[:start_time]) + end_time = Time.parse(opts[:end_time]) + interval_length = opts[:interval_length].to_f + action = opts[:task_action] + + stats = DATA_SERVICE.qos_task_submission_stats(parent, start_time, end_time, interval_length, action) + data = get_data_from_stats(stats, "average") + draw_line_graph(opts, data) + end + + def self.qos_failure_rate_graph(parent, opts = {}) + start_time = Time.parse(opts[:start_time]) + end_time = Time.parse(opts[:end_time]) + interval_length = opts[:interval_length].to_f + failure_code = opts[:failure_code] + + stats = DATA_SERVICE.qos_failure_rate_stats(parent, start_time, end_time, interval_length, failure_code) + data = get_data_from_stats(stats, "failure_rate") + data[:y_range] = "[0:100]" + draw_line_graph(opts, data) + end + + def self.qos_avg_time_to_complete_life_cycle_event(parent, opts = {}) + start_time = Time.parse(opts[:start_time]) + end_time = Time.parse(opts[:end_time]) + interval_length = opts[:interval_length].to_f + action = opts[:task_action] + + stats = DATA_SERVICE.qos_task_completion_stats(parent, start_time, end_time, interval_length, action) + data = get_data_from_stats(stats, "average") + draw_line_graph(opts, data) + end + + def self.instances_by_provider_pie (opts = {}) + pie_opts = {} + providers = Provider.all + providers.each do |provider| + running_instances = 0 + provider.cloud_accounts.each do |account| + running_instances = running_instances + account.quota.running_instances if account.quota + end + if running_instances > 0 + pie_opts[:"#{provider.name}"] = running_instances + end + end + + return draw_pie_chart(opts, pie_opts) + end + + def self.get_data_from_stats(stats, type) + x = [] + y = [] + y_max = 0 + for i in 0...stats.length do + x << i + y_value = stats[i][type] + if y_value + y << y_value + if y_value > y_max + y_max = y_value + end + else + y << 0 + end + end + + if y_max == 0 + y_max = 1 + else + y_max = y_max * 1.1 + end + + y_range = "[0:" + y_max.to_s + "]" + return { :x => x, :y => y, :y_range => y_range } + end + + def self.draw_pie_chart(opts, pie_opts) + #things we're checking for in opts: :height, :width + height = 200 unless opts[:height].nil? ? nil : height = opts[:height].to_i + width = 300 unless opts[:width].nil? ? nil : width = opts[:width].to_i + + graph = Graph.new + + mytheme = Scruffy::Themes::Keynote.new + mytheme.background = :white + mytheme.marker = :black #sets the label text color + mytheme.colors = %w(#00689a #00b0e0) + + scruffy_graph = Scruffy::Graph.new({:theme => mytheme}) + scruffy_graph.renderer = Scruffy::Renderers::Pie.new + scruffy_graph.add :pie, '', pie_opts + + raw_svg = scruffy_graph.render :width => width, :height => height + + xml = Nokogiri::XML(raw_svg) + svg = xml.css 'svg' + svg.each do |node| + node.set_attribute 'viewBox',"0 0 #{width} #{height}" + end + + xml.root.traverse do |node| + if node.name == 'text' + if node.has_attribute? 'font-family' + node.set_attribute 'font-family','sans-serif' + end + if (node.has_attribute? 'font-size') && node.get_attribute('font-size').length > 0 + size = node.get_attribute('font-size').to_f + size = size * 1.5 + node.set_attribute 'font-size',size.to_s + end + end + end + + graph.svg = xml.to_s + graph + end + + def self.draw_line_graph(opts, data) + #things we're checking for in opts: :height, :width + + height = 60 unless opts[:height].nil? ? nil : height = opts[:height].to_i + width = 100 unless opts[:width].nil? ? nil : width = opts[:width].to_i + + 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","1" + 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 } + + x = data[:x] + y = data[:y] + y_range = data[:y_range] + + #plot.set "yrange [-50:50]" + plot.set "yrange " + y_range + + #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 + + def self.draw_bar_chart(opts, chart_opts) + #things we're checking for in opts: :max_value, :height, :width
unless max_value = opts[:max_value] @@ -67,7 +269,6 @@ class GraphService height = 80 unless opts[:height].nil? ? nil : height = opts[:height].to_i width = 150 unless opts[:width].nil? ? nil : width = opts[:width].to_i
- raw_svg = "" gp = gnuplot_open Gnuplot::Plot.new( gp ) do |plot| @@ -87,14 +288,8 @@ class GraphService plot.set "xrange [.25:2.75]" plot.set "yrange [0:#{max_value * 1.5}]" #we want to scale maxvalue 50% 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 - + x = chart_opts[:x] + y = chart_opts[:y]
#The two arrays above are three columns of data for gnuplot. plot.data << Gnuplot::DataSet.new( [[x[0]], [y[0]]] ) do |ds| @@ -155,105 +350,6 @@ class GraphService 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 opts[:height].nil? ? nil : height = opts[:height].to_i - width = 100 unless opts[:width].nil? ? nil : width = opts[:width].to_i - - 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","1" - 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 - - def self.instances_by_provider_pie (opts = {}) - #things we're checking for in opts: :height, :width - - height = 200 unless opts[:height].nil? ? nil : height = opts[:height].to_i - width = 300 unless opts[:width].nil? ? nil : width = opts[:width].to_i - - graph = Graph.new - - mytheme = Scruffy::Themes::Keynote.new - mytheme.background = :white - mytheme.marker = :black #sets the label text color - mytheme.colors = %w(#00689a #00b0e0) - - scruffy_graph = Scruffy::Graph.new({:theme => mytheme}) - scruffy_graph.renderer = Scruffy::Renderers::Pie.new - - pie_opts = {} - providers = Provider.all - providers.each do |provider| - running_instances = 0 - provider.cloud_accounts.each do |account| - running_instances = running_instances + account.quota.running_instances if account.quota - end - if running_instances > 0 - pie_opts[:"#{provider.name}"] = running_instances - end - end - - scruffy_graph.add :pie, '', pie_opts - - raw_svg = scruffy_graph.render :width => width, :height => height - - xml = Nokogiri::XML(raw_svg) - svg = xml.css 'svg' - svg.each do |node| - node.set_attribute 'viewBox',"0 0 #{width} #{height}" - end - - xml.root.traverse do |node| - if node.name == 'text' - if node.has_attribute? 'font-family' - node.set_attribute 'font-family','sans-serif' - end - if (node.has_attribute? 'font-size') && node.get_attribute('font-size').length > 0 - size = node.get_attribute('font-size').to_f - size = size * 1.5 - node.set_attribute 'font-size',size.to_s - end - end - end - - graph.svg = xml.to_s - graph end
-end +end \ No newline at end of file diff --git a/src/app/views/dashboard/summary.haml b/src/app/views/dashboard/summary.haml index 66aedde..f3a21d3 100644 --- a/src/app/views/dashboard/summary.haml +++ b/src/app/views/dashboard/summary.haml @@ -60,7 +60,11 @@ %div{ :style => "clear:both"} - @providers.each do |provider| .provider_service_quality_graph - = "<object data='" + url_for(:action => :provider_qos_graph, :id => provider.id, :width => 100, :height => 50) + "' type='image/svg+xml' />" + -end_time = Time.now + -start_time = end_time - (24 * 60 * 60) + -interval_length = 3600 + -task_action = "create" + = "<object data='" + url_for(:action => :provider_qos_avg_time_to_submit_graph, :id => provider.id, :start_time => start_time, :end_time => end_time, :interval_length => interval_length, :task_action => task_action, :width => 100, :height => 50) + "' type='image/svg+xml' />" .provider_service_quality_graph_summary = provider.name <!-- FIXME 'good/poor/average service... --> @@ -109,7 +113,7 @@ .account_quota_usage_graph_summary = account.provider.name + ": " + account.name .account_quota_usage_current_graph - %object{ :data => url_for(:action => :account_quota_graph, :id => account.id, :width => 100, :height => 50), :type => 'image/svg+xml'} + %object{ :data => url_for(:action => :quota_usage_graph, :cloud_account_id => account.id, :resource_name => Quota::RESOURCE_RUNNING_INSTANCES, :width => 100, :height => 50), :type => 'image/svg+xml'} <div style="clear: both;" />
:javascript