Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / lib / mcollective / rpc / stats.rb
diff --git a/lib/mcollective/rpc/stats.rb b/lib/mcollective/rpc/stats.rb
new file mode 100644 (file)
index 0000000..ae909d2
--- /dev/null
@@ -0,0 +1,256 @@
+module MCollective
+  module RPC
+    # Class to wrap all the stats and to keep track of some timings
+    class Stats
+      attr_accessor :noresponsefrom, :starttime, :discoverytime, :blocktime, :responses, :totaltime
+      attr_accessor :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom, :responsesfrom
+      attr_accessor :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 = []
+        @responsesfrom = []
+        @responses = 0
+        @starttime = Time.now.to_f
+        @discoverytime = 0 unless @discoverytime
+        @blocktime = 0
+        @totaltime = 0
+        @discovered = 0
+        @discovered_nodes = []
+        @okcount = 0
+        @failcount = 0
+        @noresponsefrom = []
+        @requestid = nil
+        @aggregate_summary = []
+        @aggregate_failures = []
+      end
+
+      # returns a hash of our stats
+      def to_hash
+        {:noresponsefrom    => @noresponsefrom,
+         :starttime         => @starttime,
+         :discoverytime     => @discoverytime,
+         :blocktime         => @blocktime,
+         :responses         => @responses,
+         :totaltime         => @totaltime,
+         :discovered        => @discovered,
+         :discovered_nodes  => @discovered_nodes,
+         :noresponsefrom    => @noresponsefrom,
+         :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]
+        @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
+
+        # figures out who we had no responses from
+        dhosts = @discovered_nodes.clone
+        @responsesfrom.each {|r| dhosts.delete(r)}
+        @noresponsefrom = dhosts
+      rescue
+        @totaltime = 0
+        @noresponsefrom = []
+      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
+
+        if no_response_report != ""
+          result_text << "" << no_response_report
+        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
+          result_text.puts Util.colorize(:red, "No response from:")
+          result_text.puts
+
+          @noresponsefrom.sort.in_groups_of(3) do |c|
+            result_text.puts "   %-30s%-30s%-30s" % c
+          end
+
+          result_text.puts
+        end
+
+        result_text.string
+      end
+    end
+  end
+end