Module: RightSupport::Stats
- Defined in:
- lib/right_support/stats.rb,
lib/right_support/stats/helpers.rb,
lib/right_support/stats/activity.rb,
lib/right_support/stats/exceptions.rb
Overview
Copyright © 2009-2012 RightScale Inc
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Defined Under Namespace
Classes: Activity, Exceptions
Constant Summary collapse
- MAX_STAT_NAME_WIDTH =
Maximum characters in stat name
11
- MAX_SUB_STAT_NAME_WIDTH =
Maximum characters in sub-stat name
17
- MAX_SUB_STAT_VALUE_WIDTH =
Maximum characters in sub-stat value line
80
- MAX_EXCEPTION_MESSAGE_WIDTH =
Maximum characters displayed for exception message
60
- SEPARATOR =
Separator between stat name and stat value
" : "
- MINUTE =
Time constants
60
- HOUR =
60 * MINUTE
- DAY =
24 * HOUR
Class Method Summary collapse
-
.activity_str(value) ⇒ Object
Convert activity information to displayable format.
-
.brokers_str(brokers, options = {}) ⇒ Object
Convert broker information to displayable format.
-
.elapsed(time) ⇒ Object
Convert elapsed time in seconds to displayable format.
-
.enough_precision(value) ⇒ Object
Determine enough precision for floating point value(s) so that all have at least two significant digits and then convert each value to a decimal digit string of that precision after applying rounding When precision is wide ranging, limit precision of the larger numbers.
-
.exceptions_str(exceptions, indent, options = {}) ⇒ Object
Convert exception information to displayable format.
-
.hash_str(hash) ⇒ Object
Convert arbitrary nested hash to displayable format Sort hash by key, numerically if possible, otherwise as is Display any floating point values with one decimal place precision Display any empty values as “none”.
-
.last_activity_str(last, single_item = false) ⇒ Object
Convert last activity information to displayable format.
-
.nil_if_zero(value) ⇒ Object
Convert 0 value to nil This is in support of displaying “none” rather than 0.
-
.percentage(values) ⇒ Object
Convert values hash into percentages.
-
.sort_key(hash) ⇒ Object
Sort hash elements by key in ascending order into array of key/value pairs Sort keys numerically if possible, otherwise as is.
-
.sort_value(hash) ⇒ Object
Sort hash elements by value in ascending order into array of key/value pairs.
-
.stats_str(stats, options = {}) ⇒ Object
Converts server statistics to a displayable format.
-
.sub_stats_str(name, value, options = {}) ⇒ Object
Convert grouped set of statistics to displayable format Provide special formatting for stats named “exceptions” Break out percentages and total count for stats containing “percent” hash value sorted in descending percent order and followed by total count Convert to elapsed time for stats with name ending in “last” Add “/sec” to values with name ending in “rate” Add “ sec” to values with name ending in “time” Add “%” to values with name ending in “percent” and drop “percent” from name Use elapsed time formatting for values with name ending in “age” Display any nil value, empty hash, or hash with a “total” value of 0 as “none” Display any floating point value or hash of values with at least two significant digits of precision.
-
.time_at(time) ⇒ Object
Format UTC time value.
-
.wrap(string, max_length, indent, separator) ⇒ Object
Wrap string by breaking it into lines at the specified separator.
Class Method Details
.activity_str(value) ⇒ Object
Convert activity information to displayable format
Parameters
- value(Hash|nil)
-
Information about activity, or nil if the total is 0
- “total”(Integer)
-
Total activity count
- “percent”(Hash)
-
Percentage for each type of activity if tracking type, otherwise omitted
- “last”(Hash)
-
Information about last activity
- “elapsed”(Integer)
-
Seconds since last activity started
- “type”(String)
-
Type of activity if tracking type, otherwise omitted
- “active”(Boolean)
-
Whether activity still active if tracking whether active, otherwise omitted
- “rate”(Float)
-
Recent average rate if measuring rate, otherwise omitted
- “duration”(Float)
-
Average duration of activity if tracking duration, otherwise omitted
Return
- str(String)
-
Activity stats in displayable format without any line separators
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/right_support/stats/helpers.rb', line 381 def self.activity_str(value) str = "" str += enough_precision(sort_value(value["percent"]).reverse).map { |k, v| "#{k}: #{v}%" }.join(", ") + ", total: " if value["percent"] str += "#{value['total']}" str += ", last: #{last_activity_str(value['last'], single_item = true)}" if value["last"] str += ", rate: #{enough_precision(value['rate'])}/sec" if value["rate"] str += ", duration: #{enough_precision(value['duration'])} sec" if value["duration"] value.each do |name, data| unless ["total", "percent", "last", "rate", "duration"].include?(name) str += ", #{name}: #{data.is_a?(String) ? data : data.inspect}" end end str end |
.brokers_str(brokers, options = {}) ⇒ Object
Convert broker information to displayable format
Parameter
- brokers(Hash)
-
Broker stats with keys
- “brokers”(Array)
-
Stats for each broker in priority order as hash with keys
- “alias”(String)
-
Broker alias
- “identity”(String)
-
Broker identity
- “status”(Symbol)
-
Status of connection
- “disconnect last”(Hash|nil)
-
Last disconnect information with key “elapsed”, or nil if none
- “disconnects”(Integer|nil)
-
Number of times lost connection, or nil if none
- “failure last”(Hash|nil)
-
Last connect failure information with key “elapsed”, or nil if none
- “failures”(Integer|nil)
-
Number of failed attempts to connect to broker, or nil if none
- “retries”(Integer|nil)
-
Number of attempts to connect after failure, or nil if none
- “exceptions”(Hash|nil)
-
Exceptions raised per category, or nil if none
- “total”(Integer)
-
Total exceptions for this category
- “recent”(Array)
-
Most recent as a hash of “count”, “type”, “message”, “when”, and “where”
- “heartbeat”(Integer|nil)
-
Number of seconds between AMQP heartbeats, or nil if heartbeat disabled
- “returns”(Hash|nil)
-
Message return activity stats with keys “total”, “percent”, “last”, and “rate”
with percentage breakdown per request type, or nil if none
- options(Hash)
-
Formatting options
- :name_width(Integer)
-
Fixed width for left-justified name display
- :sub_name_width(Integer)
-
Maximum characters in displayed sub-stat name
- :sub_stat_value_width(Integer)
-
Maximum characters in displayed sub-stat value line
- :exception_message_width(Integer)
-
Maximum characters displayed for exception message
Return
- str(String)
-
Broker display with one line per broker plus exceptions
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/right_support/stats/helpers.rb', line 263 def self.brokers_str(brokers, = {}) name_width = [:name_width] || MAX_STAT_NAME_WIDTH sub_name_width = [:sub_name_width] || MAX_SUB_STAT_NAME_WIDTH sub_stat_value_width = [:sub_stat_value_width] || MAX_SUB_STAT_VALUE_WIDTH value_indent = " " * (name_width + SEPARATOR.size) sub_value_indent = " " * (name_width + sub_name_width + (SEPARATOR.size * 2)) str = sprintf("%-#{name_width}s#{SEPARATOR}", "brokers") brokers["brokers"].each do |b| disconnects = if b["disconnects"] "#{b["disconnects"]} (#{elapsed(b["disconnect last"]["elapsed"])} ago)" else "none" end failures = if b["failures"] retries = b["retries"] retries = " w/ #{retries} #{retries != 1 ? 'retries' : 'retry'}" if retries "#{b["failures"]} (#{elapsed(b["failure last"]["elapsed"])} ago#{retries})" else "none" end str += "#{b["alias"]}: #{b["identity"]} #{b["status"]}, disconnects: #{disconnects}, failures: #{failures}\n" str += value_indent end str += sprintf("%-#{sub_name_width}s#{SEPARATOR}", "exceptions") str += if brokers["exceptions"].nil? || brokers["exceptions"].empty? "none\n" else exceptions_str(brokers["exceptions"], sub_value_indent, ) + "\n" end str += value_indent str += sprintf("%-#{sub_name_width}s#{SEPARATOR}", "heartbeat") str += if [nil, 0].include?(brokers["heartbeat"]) "none\n" else "#{brokers["heartbeat"]} sec\n" end str += value_indent str += sprintf("%-#{sub_name_width}s#{SEPARATOR}", "returns") str += if brokers["returns"].nil? || brokers["returns"].empty? "none\n" else wrap(activity_str(brokers["returns"]), sub_stat_value_width, sub_value_indent, ", ") + "\n" end end |
.elapsed(time) ⇒ Object
Convert elapsed time in seconds to displayable format
Parameters
- time(Integer|Float)
-
Elapsed time
Return
- (String)
-
Display string
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/right_support/stats/helpers.rb', line 81 def self.elapsed(time) time = time.to_i if time <= MINUTE "#{time} sec" elsif time <= HOUR minutes = time / MINUTE seconds = time - (minutes * MINUTE) "#{minutes} min #{seconds} sec" elsif time <= DAY hours = time / HOUR minutes = (time - (hours * HOUR)) / MINUTE "#{hours} hr #{minutes} min" else days = time / DAY hours = (time - (days * DAY)) / HOUR minutes = (time - (days * DAY) - (hours * HOUR)) / MINUTE "#{days} day#{days == 1 ? '' : 's'} #{hours} hr #{minutes} min" end end |
.enough_precision(value) ⇒ Object
Determine enough precision for floating point value(s) so that all have at least two significant digits and then convert each value to a decimal digit string of that precision after applying rounding When precision is wide ranging, limit precision of the larger numbers
Parameters
- value(Float|Array|Hash)
-
Value(s) to be converted
Return
- (String|Array|Hash)
-
Value(s) converted to decimal digit string
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/right_support/stats/helpers.rb', line 111 def self.enough_precision(value) scale = [1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0] enough = lambda { |v| (v >= 10.0 ? 0 : (v >= 1.0 ? 1 : (v >= 0.1 ? 2 : (v >= 0.01 ? 3 : (v > 0.001 ? 4 : (v > 0.0 ? 5 : 0)))))) } digit_str = lambda { |p, v| sprintf("%.#{p}f", (v * scale[p]).round / scale[p])} if value.is_a?(Float) digit_str.call(enough.call(value), value) elsif value.is_a?(Array) min, max = value.map { |_, v| enough.call(v) }.minmax precision = (max - min) > 1 ? min + 1 : max value.map { |k, v| [k, digit_str.call([precision, enough.call(v)].max, v)] } elsif value.is_a?(Hash) min, max = value.to_a.map { |_, v| enough.call(v) }.minmax precision = (max - min) > 1 ? min + 1 : max value.to_a.inject({}) { |s, v| s[v[0]] = digit_str.call([precision, enough.call(v[1])].max, v[1]); s } else value.to_s end end |
.exceptions_str(exceptions, indent, options = {}) ⇒ Object
Convert exception information to displayable format
Parameters
- exceptions(Hash)
-
Exceptions raised per category
- “total”(Integer)
-
Total exceptions for this category
- “recent”(Array)
-
Most recent as a hash of “count”, “type”, “message”, “when”, and “where”
- indent(String)
-
Indentation for each line
- options(Hash)
-
Formatting options
- :exception_message_width(Integer)
-
Maximum characters displayed for exception message
Return
- (String)
-
Exceptions in displayable format with line separators
434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'lib/right_support/stats/helpers.rb', line 434 def self.exceptions_str(exceptions, indent, = {}) = [:exception_message_width] || MAX_EXCEPTION_MESSAGE_WIDTH indent2 = indent + (" " * 4) exceptions.to_a.sort.map do |k, v| sprintf("%s total: %d, most recent:\n", k, v["total"]) + v["recent"].reverse.map do |e| = e["message"] if && .size > ( - 3) = e["message"][0, - 3] + "..." end indent + "(#{e["count"]}) #{time_at(e["when"])} #{e["type"]}: #{}\n" + indent2 + "#{e["where"]}" end.join("\n") end.join("\n" + indent) end |
.hash_str(hash) ⇒ Object
Convert arbitrary nested hash to displayable format Sort hash by key, numerically if possible, otherwise as is Display any floating point values with one decimal place precision Display any empty values as “none”
Parameters
- hash(Hash)
-
Hash to be displayed
Return
- (String)
-
Single line hash display
458 459 460 461 462 463 464 465 466 467 468 469 |
# File 'lib/right_support/stats/helpers.rb', line 458 def self.hash_str(hash) str = "" sort_key(hash).map do |k, v| "#{k}: " + if v.is_a?(Float) enough_precision(v) elsif v.is_a?(Hash) "[ " + hash_str(v) + " ]" else "#{v || "none"}" end end.join(", ") end |
.last_activity_str(last, single_item = false) ⇒ Object
Convert last activity information to displayable format
Parameters
- last(Hash)
-
Information about last activity
- “elapsed”(Integer)
-
Seconds since last activity started
- “type”(String)
-
Type of activity if tracking type, otherwise omitted
- “active”(Boolean)
-
Whether activity still active if tracking whether active, otherwise omitted
- single_item
-
Whether this is to appear as a single item in a comma-separated list
in which case there should be no ':' in the formatted string
Return
- str(String)
-
Last activity in displayable format without any line separators
409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/right_support/stats/helpers.rb', line 409 def self.last_activity_str(last, single_item = false) str = "#{elapsed(last['elapsed'])} ago" str += " and still active" if last["active"] if last["type"] if single_item str = "#{last['type']} (#{str})" else str = "#{last['type']}: #{str}" end end str end |
.nil_if_zero(value) ⇒ Object
Convert 0 value to nil This is in support of displaying “none” rather than 0
Parameters
- value(Integer|Float)
-
Value to be converted
Returns
- (Integer|Float|nil)
-
nil if value is 0, otherwise the original value
55 56 57 |
# File 'lib/right_support/stats/helpers.rb', line 55 def self.nil_if_zero(value) value == 0 ? nil : value end |
.percentage(values) ⇒ Object
Convert values hash into percentages
Parameters
- values(Hash)
-
Values to be converted whose sum is the total for calculating percentages
Return
- (Hash)
-
Converted values with keys “total” and “percent” with latter being a hash with values as percentages
66 67 68 69 70 71 72 |
# File 'lib/right_support/stats/helpers.rb', line 66 def self.percentage(values) total = 0 values.each_value { |v| total += v } percent = {} values.each { |k, v| percent[k] = (v / total.to_f) * 100.0 } if total > 0 {"percent" => percent, "total" => total} end |
.sort_key(hash) ⇒ Object
Sort hash elements by key in ascending order into array of key/value pairs Sort keys numerically if possible, otherwise as is
Parameters
- hash(Hash)
-
Data to be sorted
Return
- (Array)
-
Key/value pairs from hash in key sorted order
177 178 179 |
# File 'lib/right_support/stats/helpers.rb', line 177 def self.sort_key(hash) hash.to_a.map { |k, v| [k =~ /^\d+$/ ? k.to_i : k, v] }.sort end |
.sort_value(hash) ⇒ Object
Sort hash elements by value in ascending order into array of key/value pairs
Parameters
- hash(Hash)
-
Data to be sorted
Return
- (Array)
-
Key/value pairs from hash in value sorted order
188 189 190 |
# File 'lib/right_support/stats/helpers.rb', line 188 def self.sort_value(hash) hash.to_a.sort { |a, b| a[1] <=> b[1] } end |
.stats_str(stats, options = {}) ⇒ Object
Converts server statistics to a displayable format
Parameters
- stats(Hash)
-
Statistics with generic keys “name”, “identity”, “hostname”, “service uptime”,
"machine uptime", "memory KB", "stat time", "last reset time", "version", and "broker" with
the latter two and "machine uptime", "memory KB", "version", and "broker" being optional;
any other keys ending with "stats" have an associated hash value that is displayed in sorted
key order, unless "stats" is preceded by a non-blank, in which case that character is prepended
to the key to drive the sort order
- options(Hash)
-
Formatting options
- :name_width(Integer)
-
Maximum characters in displayed stat name
- :sub_name_width(Integer)
-
Maximum characters in displayed sub-stat name
- :sub_stat_value_width(Integer)
-
Maximum characters in displayed sub-stat value line
- :exception_message_width(Integer)
-
Maximum characters displayed for exception message
Return
- (String)
-
Display string
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/right_support/stats/helpers.rb', line 209 def self.stats_str(stats, = {}) name_width = [:name_width] || MAX_STAT_NAME_WIDTH str = stats["name"] ? sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "name", stats["name"]) : "" str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "identity", stats["identity"]) + sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "hostname", stats["hostname"]) + sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "stat time", time_at(stats["stat time"])) + sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "last reset", time_at(stats["last reset time"])) + sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "service up", elapsed(stats["service uptime"])) if stats.has_key?("machine uptime") str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "machine up", elapsed(stats["machine uptime"])) end if stats.has_key?("memory") str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "memory KB", stats["memory"]) end if stats.has_key?("version") str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "version", stats["version"].to_i) end if stats.has_key?("brokers") str += brokers_str(stats["brokers"], ) end stats.to_a.sort_by { |(k, v)| k.to_s =~ /(.)stats$/ ? ($1 == ' ' ? '~' : $1) + k : k }.each do |k, v| str += sub_stats_str(k[0..-7], v, ) if k.to_s =~ /stats$/ end str end |
.sub_stats_str(name, value, options = {}) ⇒ Object
Convert grouped set of statistics to displayable format Provide special formatting for stats named “exceptions” Break out percentages and total count for stats containing “percent” hash value sorted in descending percent order and followed by total count Convert to elapsed time for stats with name ending in “last” Add “/sec” to values with name ending in “rate” Add “ sec” to values with name ending in “time” Add “%” to values with name ending in “percent” and drop “percent” from name Use elapsed time formatting for values with name ending in “age” Display any nil value, empty hash, or hash with a “total” value of 0 as “none” Display any floating point value or hash of values with at least two significant digits of precision
Parameters
- name(String)
-
Display name for the stat
- value(Object)
-
Value of this stat
- options(Hash)
-
Formatting options
- :name_width(Integer)
-
Fixed width for left-justified name display
- :sub_name_width(Integer)
-
Maximum characters in displayed sub-stat name
- :sub_stat_value_width(Integer)
-
Maximum characters in displayed sub-stat value line
- :exception_message_width(Integer)
-
Maximum characters displayed for exception message
Return
- (String)
-
Single line display of stat
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/right_support/stats/helpers.rb', line 332 def self.sub_stats_str(name, value, = {}) name_width = [:name_width] || MAX_STAT_NAME_WIDTH sub_name_width = [:sub_name_width] || MAX_SUB_STAT_NAME_WIDTH sub_stat_value_width = [:sub_stat_value_width] || MAX_SUB_STAT_VALUE_WIDTH value_indent = " " * (name_width + SEPARATOR.size) sub_value_indent = " " * (name_width + sub_name_width + (SEPARATOR.size * 2)) sprintf("%-#{name_width}s#{SEPARATOR}", name) + value.to_a.sort.map do |attr| k, v = attr name = k =~ /percent$/ ? k[0..-9] : k sprintf("%-#{sub_name_width}s#{SEPARATOR}", name) + if v.is_a?(Float) || v.is_a?(Integer) str = k =~ /age$/ ? elapsed(v) : enough_precision(v) str += "/sec" if k =~ /rate$/ str += " sec" if k =~ /time$/ str += "%" if k =~ /percent$/ str elsif v.is_a?(Hash) if v.empty? || v["total"] == 0 "none" elsif v["total"] wrap(activity_str(v), sub_stat_value_width, sub_value_indent, ", ") elsif k =~ /last$/ last_activity_str(v) elsif k == "exceptions" exceptions_str(v, sub_value_indent, ) else wrap(hash_str(v), sub_stat_value_width, sub_value_indent, ", ") end else "#{v || "none"}" end + "\n" end.join(value_indent) end |
.time_at(time) ⇒ Object
Format UTC time value
Parameters
- time(Integer)
-
Time in seconds in Unix-epoch to be formatted
- (String)
-
Formatted time string
165 166 167 |
# File 'lib/right_support/stats/helpers.rb', line 165 def self.time_at(time) Time.at(time).strftime("%a %b %d %H:%M:%S") end |
.wrap(string, max_length, indent, separator) ⇒ Object
Wrap string by breaking it into lines at the specified separator
Parameters
- string(String)
-
String to be wrapped
- max_length(Integer)
-
Maximum length of a line excluding indentation
- indent(String)
-
Indentation for each line
- separator(String)
-
Separator at which to make line breaks
Return
- (String)
-
Multi-line string
146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/right_support/stats/helpers.rb', line 146 def self.wrap(string, max_length, indent, separator) all = [] line = "" for l in string.split(separator) if (line + l).length >= max_length all.push(line) line = "" end line += line == "" ? l : separator + l end all.push(line).join(separator + "\n" + indent) end |