module MCollective module RPC # Class to wrap all the stats and to keep track of some timings class Stats attr_accessor :noresponsefrom, :unexpectedresponsefrom, :starttime, :discoverytime, :blocktime, :responses attr_accessor :totaltime, :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom attr_accessor :responsesfrom, :requestid, :aggregate_summary, :ddl, :aggregate_failures def initialize reset end # Resets stats, if discovery time is set we keep it as it was def reset @noresponsefrom = [] @unexpectedresponsefrom = [] @responsesfrom = [] @responses = 0 @starttime = Time.now.to_f @discoverytime = 0 unless @discoverytime @blocktime = 0 @totaltime = 0 @discovered = 0 @discovered_nodes = [] @okcount = 0 @failcount = 0 @requestid = nil @aggregate_summary = [] @aggregate_failures = [] end # returns a hash of our stats def to_hash {:noresponsefrom => @noresponsefrom, :unexpectedresponsefrom => @unexpectedresponsefrom, :starttime => @starttime, :discoverytime => @discoverytime, :blocktime => @blocktime, :responses => @responses, :totaltime => @totaltime, :discovered => @discovered, :discovered_nodes => @discovered_nodes, :okcount => @okcount, :requestid => @requestid, :failcount => @failcount, :aggregate_summary => @aggregate_summary, :aggregate_failures => @aggregate_failures} end # Fake hash access to keep things backward compatible def [](key) to_hash[key] rescue nil end # increment the count of ok hosts def ok @okcount += 1 rescue @okcount = 1 end # increment the count of failed hosts def fail @failcount += 1 rescue @failcount = 1 end # Re-initializes the object with stats from the basic client def client_stats=(stats) @noresponsefrom = stats[:noresponsefrom] @unexpectedresponsefrom = stats[:unexpectedresponsefrom] @responses = stats[:responses] @starttime = stats[:starttime] @blocktime = stats[:blocktime] @totaltime = stats[:totaltime] @requestid = stats[:requestid] @discoverytime = stats[:discoverytime] if @discoverytime == 0 end # Utility to time discovery from :start to :end def time_discovery(action) if action == :start @discovery_start = Time.now.to_f elsif action == :end @discoverytime = Time.now.to_f - @discovery_start else raise("Uknown discovery action #{action}") end rescue @discoverytime = 0 end # helper to time block execution time def time_block_execution(action) if action == :start @block_start = Time.now.to_f elsif action == :end @blocktime += Time.now.to_f - @block_start else raise("Uknown block action #{action}") end rescue @blocktime = 0 end # Update discovered and discovered_nodes based on # discovery results def discovered_agents(agents) @discovered_nodes = agents @discovered = agents.size end # Helper to calculate total time etc def finish_request @totaltime = @blocktime + @discoverytime # figure out who responded unexpectedly @unexpectedresponsefrom = @responsesfrom - @discovered_nodes # figure out who we had no responses from @noresponsefrom = @discovered_nodes - @responsesfrom rescue @totaltime = 0 @noresponsefrom = [] @unexpectedresponsefrom = [] end # Helper to keep track of who we received responses from def node_responded(node) @responsesfrom << node rescue @responsesfrom = [node] end def text_for_aggregates result = StringIO.new @aggregate_summary.each do |aggregate| output_item = aggregate.result[:output] begin action_interface = @ddl.action_interface(aggregate.action) display_as = action_interface[:output][output_item][:display_as] rescue display_as = output_item end if aggregate.is_a?(Aggregate::Result::Base) aggregate_report = aggregate.to_s else next end result.puts Util.colorize(:bold, "Summary of %s:" % display_as) result.puts unless aggregate_report == "" result.puts aggregate.to_s.split("\n").map{|x| " " + x}.join("\n") else result.puts Util.colorize(:yellow, " No aggregate summary could be computed") end result.puts end @aggregate_failures.each do |failed| case(failed[:type]) when :startup message = "exception raised while processing startup hook" when :create message = "unspecified output '#{failed[:name]}' for the action" when :process_result message = "exception raised while processing result data" when :summarize message = "exception raised while summarizing" end result.puts Util.colorize(:bold, "Summary of %s:" % failed[:name]) result.puts result.puts Util.colorize(:yellow, " Could not compute summary - %s" % message) result.puts end result.string end # Returns a blob of text representing the request status based on the # stats contained in this class def report(caption = "rpc stats", summarize = true, verbose = false) result_text = [] if verbose if @aggregate_summary.size > 0 && summarize result_text << text_for_aggregates else result_text << "" end result_text << Util.colorize(:yellow, "---- #{caption} ----") if @discovered @responses < @discovered ? color = :red : color = :reset result_text << " Nodes: %s / %s" % [ Util.colorize(color, @discovered), Util.colorize(color, @responses) ] else result_text << " Nodes: #{@responses}" end @failcount < 0 ? color = :red : color = :reset result_text << " Pass / Fail: %s / %s" % [Util.colorize(color, @okcount), Util.colorize(color, @failcount) ] result_text << " Start Time: %s" % [Time.at(@starttime)] result_text << " Discovery Time: %.2fms" % [@discoverytime * 1000] result_text << " Agent Time: %.2fms" % [@blocktime * 1000] result_text << " Total Time: %.2fms" % [@totaltime * 1000] else if @discovered @responses < @discovered ? color = :red : color = :green if @aggregate_summary.size + @aggregate_failures.size > 0 && summarize result_text << text_for_aggregates else result_text << "" end result_text << "Finished processing %s / %s hosts in %.2f ms" % [Util.colorize(color, @responses), Util.colorize(color, @discovered), @blocktime * 1000] else result_text << "Finished processing %s hosts in %.2f ms" % [Util.colorize(:bold, @responses), @blocktime * 1000] end end no_response_r = no_response_report unexpected_response_r = unexpected_response_report if no_response_r || unexpected_response_r result_text << "" end if no_response_r != "" result_text << "" << no_response_r end if unexpected_response_r != "" result_text << "" << unexpected_response_r end if no_response_r || unexpected_response_r result_text << "" end result_text.join("\n") end # Returns a blob of text indicating what nodes did not respond def no_response_report result_text = StringIO.new if @noresponsefrom.size > 0 result_text.puts Util.colorize(:red, "No response from:") result_text.puts field_size = Util.field_size(@noresponsefrom, 30) fields_num = Util.field_number(field_size) format = " " + ( " %-#{field_size}s" * fields_num ) @noresponsefrom.sort.in_groups_of(fields_num) do |c| result_text.puts format % c end end result_text.string end # Returns a blob of text indicating what nodes responded but weren't discovered def unexpected_response_report result_text = StringIO.new if @unexpectedresponsefrom.size > 0 result_text.puts Util.colorize(:red, "Unexpected response from:") result_text.puts field_size = Util.field_size(@unexpectedresponsefrom, 30) fields_num = Util.field_number(field_size) format = " " + ( " %-#{field_size}s" * fields_num ) @unexpectedresponsefrom.sort.in_groups_of(fields_num) do |c| result_text.puts format % c end end result_text.string end end end end