Update mcollective.init according to OSCI-855
[packages/precise/mcollective.git] / lib / mcollective / rpc / stats.rb
1 module MCollective
2   module RPC
3     # Class to wrap all the stats and to keep track of some timings
4     class Stats
5       attr_accessor :noresponsefrom, :starttime, :discoverytime, :blocktime, :responses, :totaltime
6       attr_accessor :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom, :responsesfrom
7       attr_accessor :requestid, :aggregate_summary, :ddl, :aggregate_failures
8
9       def initialize
10         reset
11       end
12
13       # Resets stats, if discovery time is set we keep it as it was
14       def reset
15         @noresponsefrom = []
16         @responsesfrom = []
17         @responses = 0
18         @starttime = Time.now.to_f
19         @discoverytime = 0 unless @discoverytime
20         @blocktime = 0
21         @totaltime = 0
22         @discovered = 0
23         @discovered_nodes = []
24         @okcount = 0
25         @failcount = 0
26         @noresponsefrom = []
27         @requestid = nil
28         @aggregate_summary = []
29         @aggregate_failures = []
30       end
31
32       # returns a hash of our stats
33       def to_hash
34         {:noresponsefrom    => @noresponsefrom,
35          :starttime         => @starttime,
36          :discoverytime     => @discoverytime,
37          :blocktime         => @blocktime,
38          :responses         => @responses,
39          :totaltime         => @totaltime,
40          :discovered        => @discovered,
41          :discovered_nodes  => @discovered_nodes,
42          :noresponsefrom    => @noresponsefrom,
43          :okcount           => @okcount,
44          :requestid         => @requestid,
45          :failcount         => @failcount,
46          :aggregate_summary => @aggregate_summary,
47          :aggregate_failures => @aggregate_failures}
48       end
49
50       # Fake hash access to keep things backward compatible
51       def [](key)
52         to_hash[key]
53       rescue
54         nil
55       end
56
57       # increment the count of ok hosts
58       def ok
59         @okcount += 1
60       rescue
61         @okcount = 1
62       end
63
64       # increment the count of failed hosts
65       def fail
66         @failcount += 1
67       rescue
68         @failcount = 1
69       end
70
71       # Re-initializes the object with stats from the basic client
72       def client_stats=(stats)
73         @noresponsefrom = stats[:noresponsefrom]
74         @responses = stats[:responses]
75         @starttime = stats[:starttime]
76         @blocktime = stats[:blocktime]
77         @totaltime = stats[:totaltime]
78         @requestid = stats[:requestid]
79         @discoverytime = stats[:discoverytime] if @discoverytime == 0
80       end
81
82       # Utility to time discovery from :start to :end
83       def time_discovery(action)
84         if action == :start
85           @discovery_start = Time.now.to_f
86         elsif action == :end
87           @discoverytime = Time.now.to_f - @discovery_start
88         else
89           raise("Uknown discovery action #{action}")
90         end
91       rescue
92         @discoverytime = 0
93       end
94
95       # helper to time block execution time
96       def time_block_execution(action)
97         if action == :start
98           @block_start = Time.now.to_f
99         elsif action == :end
100           @blocktime += Time.now.to_f - @block_start
101         else
102           raise("Uknown block action #{action}")
103         end
104       rescue
105         @blocktime = 0
106       end
107
108       # Update discovered and discovered_nodes based on
109       # discovery results
110       def discovered_agents(agents)
111         @discovered_nodes = agents
112         @discovered = agents.size
113       end
114
115       # Helper to calculate total time etc
116       def finish_request
117         @totaltime = @blocktime + @discoverytime
118
119         # figures out who we had no responses from
120         dhosts = @discovered_nodes.clone
121         @responsesfrom.each {|r| dhosts.delete(r)}
122         @noresponsefrom = dhosts
123       rescue
124         @totaltime = 0
125         @noresponsefrom = []
126       end
127
128       # Helper to keep track of who we received responses from
129       def node_responded(node)
130         @responsesfrom << node
131       rescue
132         @responsesfrom = [node]
133       end
134
135       def text_for_aggregates
136         result = StringIO.new
137
138         @aggregate_summary.each do |aggregate|
139           output_item = aggregate.result[:output]
140
141           begin
142             action_interface = @ddl.action_interface(aggregate.action)
143             display_as = action_interface[:output][output_item][:display_as]
144           rescue
145             display_as = output_item
146           end
147
148           if aggregate.is_a?(Aggregate::Result::Base)
149             aggregate_report = aggregate.to_s
150           else
151             next
152           end
153
154           result.puts Util.colorize(:bold, "Summary of %s:" % display_as)
155           result.puts
156           unless aggregate_report == ""
157             result.puts aggregate.to_s.split("\n").map{|x| "   " + x}.join("\n")
158           else
159             result.puts Util.colorize(:yellow, "     No aggregate summary could be computed")
160           end
161           result.puts
162         end
163
164         @aggregate_failures.each do |failed|
165           case(failed[:type])
166           when :startup
167             message = "exception raised while processing startup hook"
168           when :create
169             message = "unspecified output '#{failed[:name]}' for the action"
170           when :process_result
171             message = "exception raised while processing result data"
172           when :summarize
173             message = "exception raised while summarizing"
174           end
175
176           result.puts Util.colorize(:bold, "Summary of %s:" % failed[:name])
177           result.puts
178           result.puts Util.colorize(:yellow, "     Could not compute summary - %s" % message)
179           result.puts
180         end
181
182         result.string
183       end
184
185       # Returns a blob of text representing the request status based on the
186       # stats contained in this class
187       def report(caption = "rpc stats", summarize = true, verbose = false)
188         result_text = []
189
190         if verbose
191             if @aggregate_summary.size > 0 && summarize
192               result_text << text_for_aggregates
193             else
194               result_text << ""
195             end
196
197           result_text << Util.colorize(:yellow, "---- #{caption} ----")
198
199           if @discovered
200             @responses < @discovered ? color = :red : color = :reset
201             result_text << "           Nodes: %s / %s" % [ Util.colorize(color, @discovered), Util.colorize(color, @responses) ]
202           else
203             result_text << "           Nodes: #{@responses}"
204           end
205
206           @failcount < 0 ? color = :red : color = :reset
207
208           result_text << "     Pass / Fail: %s / %s" % [Util.colorize(color, @okcount), Util.colorize(color, @failcount) ]
209           result_text << "      Start Time: %s"      % [Time.at(@starttime)]
210           result_text << "  Discovery Time: %.2fms"  % [@discoverytime * 1000]
211           result_text << "      Agent Time: %.2fms"  % [@blocktime * 1000]
212           result_text << "      Total Time: %.2fms"  % [@totaltime * 1000]
213         else
214           if @discovered
215             @responses < @discovered ? color = :red : color = :green
216
217             if @aggregate_summary.size + @aggregate_failures.size > 0 && summarize
218               result_text << text_for_aggregates
219             else
220               result_text << ""
221             end
222
223             result_text << "Finished processing %s / %s hosts in %.2f ms" % [Util.colorize(color, @responses), Util.colorize(color, @discovered), @blocktime * 1000]
224           else
225             result_text << "Finished processing %s hosts in %.2f ms" % [Util.colorize(:bold, @responses), @blocktime * 1000]
226           end
227         end
228
229         if no_response_report != ""
230           result_text << "" << no_response_report
231         end
232
233         result_text.join("\n")
234       end
235
236       # Returns a blob of text indicating what nodes did not respond
237       def no_response_report
238         result_text = StringIO.new
239
240         if @noresponsefrom.size > 0
241           result_text.puts
242           result_text.puts Util.colorize(:red, "No response from:")
243           result_text.puts
244
245           @noresponsefrom.sort.in_groups_of(3) do |c|
246             result_text.puts "   %-30s%-30s%-30s" % c
247           end
248
249           result_text.puts
250         end
251
252         result_text.string
253       end
254     end
255   end
256 end