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-2013 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
- 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) ⇒ String
Convert activity information to displayable format.
-
.brokers_str(brokers, options = {}) ⇒ String
Convert broker information to displayable format.
-
.elapsed(time) ⇒ String
Convert elapsed time in seconds to displayable format.
-
.enough_precision(value) ⇒ String, ...
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 = {}) ⇒ String
Convert exception information to displayable format.
-
.hash_str(hash) ⇒ String
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) ⇒ Integer, ...
Convert 0 value to nil This is in support of displaying “none” rather than 0.
-
.percentage(values) ⇒ Hash
Convert values hash into percentages.
-
.service_up_str(stats) ⇒ String
Converts service uptime stats to displayable format.
-
.sort_key(hash) ⇒ Array
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) ⇒ Array
Sort hash elements by value in ascending order into array of key/value pairs.
-
.stats_str(stats, options = {}) ⇒ String
Converts server statistics to a displayable format.
-
.sub_stats_str(name, value, options = {}) ⇒ String
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, with_year = false) ⇒ String
Format time value in local time.
-
.wrap(string, max_length, indent, separators) ⇒ String, Array
Wrap string by breaking it into lines at the specified separators Convert all whitespace sequences to single space before wrapping Allow for presence of color encoding when measuring string length.
Class Method Details
.activity_str(value) ⇒ String
Convert activity information to displayable format
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/right_support/stats/helpers.rb', line 426 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 += ", latency: #{enough_precision(value['latency'])} sec" if value["latency"] value.each do |name, data| unless ["total", "percent", "last", "rate", "latency"].include?(name) str += ", #{name}: #{data.is_a?(String) ? data : data.inspect}" end end str end |
.brokers_str(brokers, options = {}) ⇒ String
Convert broker information to displayable format
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/right_support/stats/helpers.rb', line 301 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 # Exceptions are accumulated globally for agents at right_agent 2.3 or above if brokers.has_key?("exceptions") 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 end 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_stat_value_width + sub_value_indent.size], sub_value_indent, /, /) + "\n" end end |
.elapsed(time) ⇒ String
Convert elapsed time in seconds to displayable format
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/right_support/stats/helpers.rb', line 72 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) ⇒ String, ...
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
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/right_support/stats/helpers.rb', line 100 def self.enough_precision(value) scale = [1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0] enough = lambda { |v| v = v.abs (v >= 10.0 ? 0 : (v >= 1.0 ? 1 : (v >= 0.1 ? 2 : (v >= 0.01 ? 3 : (v >= 0.001 ? 4 : (v >= 0.0001 ? 5 : (v >= 0.0000005 ? 6 : 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 = {}) ⇒ String
Convert exception information to displayable format
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/right_support/stats/helpers.rb', line 475 def self.exceptions_str(exceptions, indent, = {}) sub_stat_value_width = [:sub_stat_value_width] || MAX_SUB_STAT_VALUE_WIDTH indent2 = indent + (" " * 4) max_length = [sub_stat_value_width, sub_stat_value_width + indent2.size] separators = / |\/\/|\/|::|\.|-/ exceptions.to_a.sort.map do |k, v| sprintf("%s total: %d, most recent:\n", k, v["total"]) + v["recent"].reverse.map do |e| head = "(#{e["count"]}) #{time_at(e["when"])} #{e["type"]}: " = e["message"] if (i = (/(:[0-9]+:in `[^']+')/ =~ )) # Dropping anything after the first line of an embedded backtrace to reduce bulk = [0..i-1] + $1 + "..." end tail = " IN #{e["where"]}" if e["where"] indent + wrap("#{head}#{}#{tail}", max_length, indent2, separators) end.join("\n") end.join("\n" + indent) end |
.hash_str(hash) ⇒ String
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”
502 503 504 505 506 507 508 509 510 511 512 513 |
# File 'lib/right_support/stats/helpers.rb', line 502 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
- str(String)
-
Last activity in displayable format without any line separators
452 453 454 455 456 457 458 459 460 461 462 463 |
# File 'lib/right_support/stats/helpers.rb', line 452 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) ⇒ Integer, ...
Convert 0 value to nil This is in support of displaying “none” rather than 0
50 51 52 |
# File 'lib/right_support/stats/helpers.rb', line 50 def self.nil_if_zero(value) value == 0 ? nil : value end |
.percentage(values) ⇒ Hash
Convert values hash into percentages
59 60 61 62 63 64 65 |
# File 'lib/right_support/stats/helpers.rb', line 59 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 |
.service_up_str(stats) ⇒ String
Converts service uptime stats to displayable format
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/right_support/stats/helpers.rb', line 257 def self.service_up_str(stats) if stats.is_a?(Integer) elapsed(stats) else str = elapsed(stats["uptime"]) if (r = stats["restarts"]) && r > 0 non_graceful = (n = r - stats["graceful_exits"]) > 0 ? "#{n} non-graceful, " : "" str += ", restarts: #{r} (#{non_graceful}up #{elapsed(stats["total_uptime"])} total)" end if (c = stats["crashes"]) && c > 0 str += ", crashes: #{c} (last #{elapsed(Time.now.to_i - stats["last_crash_time"])} ago)" end str end end |
.sort_key(hash) ⇒ Array
Sort hash elements by key in ascending order into array of key/value pairs Sort keys numerically if possible, otherwise as is
191 192 193 |
# File 'lib/right_support/stats/helpers.rb', line 191 def self.sort_key(hash) hash.to_a.map { |k, v| [k =~ /^\d+$/ ? k.to_i : k, v] }.sort end |
.sort_value(hash) ⇒ Array
Sort hash elements by value in ascending order into array of key/value pairs
200 201 202 |
# File 'lib/right_support/stats/helpers.rb', line 200 def self.sort_value(hash) hash.to_a.sort { |a, b| a[1] <=> b[1] } end |
.stats_str(stats, options = {}) ⇒ String
Converts server statistics to a displayable format
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/right_support/stats/helpers.rb', line 221 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"]) if stats.has_key?("revision") str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "revision", stats["revision"]) end str += sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "stat time", time_at(stats["stat time"], with_year = true)) + sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "last reset", time_at(stats["last reset time"], with_year = true)) + sprintf("%-#{name_width}s#{SEPARATOR}%s\n", "service up", service_up_str(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 = {}) ⇒ String
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
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/right_support/stats/helpers.rb', line 376 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?(Numeric) str = k =~ /age$/ ? elapsed(v) : enough_precision(v) str += "/sec" if k =~ /rate$/ str += " sec" if k =~ /time$|delay$/ 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_stat_value_width + sub_value_indent.size], 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_stat_value_width + sub_value_indent.size], sub_value_indent, /, /) end else "#{v || "none"}" end + "\n" end.join(value_indent) end |
.time_at(time, with_year = false) ⇒ String
Format time value in local time
177 178 179 180 181 182 183 |
# File 'lib/right_support/stats/helpers.rb', line 177 def self.time_at(time, with_year = false) if with_year Time.at(time).strftime("%a %b %d %H:%M:%S %Y") else Time.at(time).strftime("%a %b %d %H:%M:%S") end end |
.wrap(string, max_length, indent, separators) ⇒ String, Array
Wrap string by breaking it into lines at the specified separators Convert all whitespace sequences to single space before wrapping Allow for presence of color encoding when measuring string length
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/right_support/stats/helpers.rb', line 139 def self.wrap(string, max_length, indent, separators) # Strip color encoding from string strip = lambda { |str| str.gsub(/\e\[[0-9]*m/, "") } # Convert all whitespace (especially \n) to space # to provide a clean slate for wrapping string = string.gsub(/\s+/, " ") # Split string apart using separators one step at a time # while building lines of the requested length lines = [] line = ["", last_separator = ""] limit = max_length.is_a?(Array) ? max_length[0] : max_length length = 0 separators = /#{separators}/ if separators.is_a?(String) while !string.empty? do head, separator, tail = string.partition(separators) if (length + strip.call(head).size + last_separator.size + separator.size) > limit lines.push(line) unless line[0] == "" line = [indent, last_separator = ""] length = indent.size limit = max_length.is_a?(Array) ? max_length[1] : max_length end line[0] += (head = last_separator + head) line[1] = last_separator = separator length += strip.call(head).size string = tail end lines.push(line) lines[0..-2].inject("") { |a, (str, separator)| a + str + separator + "\n" } + lines[-1][0] end |