class RequestLogAnalyzer::Tracker::NumericValue
Attributes
Public Instance Methods
Returns the average of the lower and upper bound of the bucket.
# File lib/request_log_analyzer/tracker/numeric_value.rb 147 def bucket_average_value(index) 148 (bucket_lower_bound(index) + bucket_upper_bound(index)) / 2 149 end
Returns the bucket index for a value
# File lib/request_log_analyzer/tracker/numeric_value.rb 129 def bucket_index(value) 130 return 0 if value < @min_bucket_value 131 return @number_of_buckets - 1 if value >= @max_bucket_value 132 133 ((Math.log(value) - Math.log(@min_bucket_value)) / @bucket_size).floor 134 end
Returns the range of values for a bucket.
# File lib/request_log_analyzer/tracker/numeric_value.rb 161 def bucket_interval(index) 162 Range.new(bucket_lower_bound(index), bucket_upper_bound(index), true) 163 end
Returns the lower value of a bucket given its index
# File lib/request_log_analyzer/tracker/numeric_value.rb 137 def bucket_lower_bound(index) 138 Math.exp((index * @bucket_size) + Math.log(@min_bucket_value)) 139 end
Returns the upper value of a bucket given its index
# File lib/request_log_analyzer/tracker/numeric_value.rb 142 def bucket_upper_bound(index) 143 bucket_lower_bound(index + 1) 144 end
Returns a single value representing a bucket.
# File lib/request_log_analyzer/tracker/numeric_value.rb 152 def bucket_value(index, type = nil) 153 case type 154 when :begin, :start, :lower, :lower_bound then bucket_lower_bound(index) 155 when :end, :finish, :upper, :upper_bound then bucket_upper_bound(index) 156 else bucket_average_value(index) 157 end 158 end
Records a hit on a bucket that includes the given value.
# File lib/request_log_analyzer/tracker/numeric_value.rb 166 def bucketize(category, value) 167 @categories[category][:buckets][bucket_index(value)] += 1 168 end
Display a value
# File lib/request_log_analyzer/tracker/numeric_value.rb 73 def display_value(value) 74 return '- ' if value.nil? 75 return '0 ' if value.zero? 76 77 case [Math.log10(value.abs).floor, 0].max 78 when 0...4 then '%d ' % value 79 when 4...7 then '%dk' % (value / 1000) 80 when 7...10 then '%dM' % (value / 1_000_000) 81 when 10...13 then '%dG' % (value / 1_000_000_000) 82 when 13...16 then '%dT' % (value / 1_000_000_000_000) 83 else '%dP' % (value / 1_000_000_000_000_000) 84 end 85 end
Get the number of hits of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 244 def hits(cat) 245 @categories[cat][:hits] 246 end
Get the total hits of a all categories.
# File lib/request_log_analyzer/tracker/numeric_value.rb 296 def hits_overall 297 @categories.reduce(0) { |sum, (_, cat)| sum + cat[:hits] } 298 end
Get the maximum duration of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 262 def max(cat) 263 @categories[cat][:max] 264 end
Get the average duration of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 268 def mean(cat) 269 @categories[cat][:mean] 270 end
Get the average duration of a all categories.
# File lib/request_log_analyzer/tracker/numeric_value.rb 286 def mean_overall 287 sum_overall / hits_overall 288 end
# File lib/request_log_analyzer/tracker/numeric_value.rb 199 def median(category) 200 percentile(category, 50, :average) 201 end
Get the minimal duration of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 256 def min(cat) 257 @categories[cat][:min] 258 end
# File lib/request_log_analyzer/tracker/numeric_value.rb 195 def percentile(category, x, type = nil) 196 bucket_value(percentile_index(category, x, type == :upper), type) 197 end
Returns the upper bound value that would include x% of the hits.
# File lib/request_log_analyzer/tracker/numeric_value.rb 171 def percentile_index(category, x, inclusive = false) 172 total_encountered = 0 173 @categories[category][:buckets].each_with_index do |count, index| 174 total_encountered += count 175 percentage = ((total_encountered.to_f / hits(category).to_f) * 100).floor 176 return index if (inclusive && percentage >= x) || (!inclusive && percentage > x) 177 end 178 end
# File lib/request_log_analyzer/tracker/numeric_value.rb 180 def percentile_indices(category, start, finish) 181 result = [nil, nil] 182 total_encountered = 0 183 @categories[category][:buckets].each_with_index do |count, index| 184 total_encountered += count 185 percentage = ((total_encountered.to_f / hits(category).to_f) * 100).floor 186 if !result[0] && percentage > start 187 result[0] = index 188 elsif !result[1] && percentage >= finish 189 result[1] = index 190 return result 191 end 192 end 193 end
Returns a percentile interval, i.e. the lower bound and the upper bound of the values that represent the x%-interval for the bucketized dataset.
A 90% interval means that 5% of the values would have been lower than the lower bound and 5% would have been higher than the upper bound, leaving 90% of the values within the bounds. You can also provide a Range to specify the lower bound and upper bound percentages (e.g. 5..95).
# File lib/request_log_analyzer/tracker/numeric_value.rb 209 def percentile_interval(category, x) 210 case x 211 when Range 212 lower, upper = percentile_indices(category, x.begin, x.end) 213 Range.new(bucket_lower_bound(lower), bucket_upper_bound(upper)) 214 when Numeric 215 percentile_interval(category, Range.new((100 - x) / 2, (100 - (100 - x) / 2))) 216 else 217 fail 'What does it mean?' 218 end 219 end
Sets up the numeric value tracker. It will check whether the value and category options are set that are used to extract and categorize the values during parsing. Two lambda procedures are created for these tasks
# File lib/request_log_analyzer/tracker/numeric_value.rb 8 def prepare 9 fail "No value field set up for numeric tracker #{inspect}" unless options[:value] 10 fail "No categorizer set up for numeric tracker #{inspect}" unless options[:category] 11 12 unless options[:multiple] 13 @categorizer = create_lambda(options[:category]) 14 @valueizer = create_lambda(options[:value]) 15 end 16 17 @number_of_buckets = options[:number_of_buckets] || 1000 18 @min_bucket_value = options[:min_bucket_value] ? options[:min_bucket_value].to_f : 0.000001 19 @max_bucket_value = options[:max_bucket_value] ? options[:max_bucket_value].to_f : 1_000_000_000 20 21 # precalculate the bucket size 22 @bucket_size = (Math.log(@max_bucket_value) - Math.log(@min_bucket_value)) / @number_of_buckets.to_f 23 24 @categories = {} 25 end
Generate a request report to the given output object By default colulative and average duration are generated. Any options for the report should have been set during initialize. output
The output object
# File lib/request_log_analyzer/tracker/numeric_value.rb 91 def report(output) 92 sortings = output.options[:sort] || [:sum, :mean] 93 sortings.each do |sorting| 94 report_table(output, sorting, title: "#{title} - by #{sorting}") 95 end 96 97 if options[:total] 98 output.puts 99 output.puts "#{output.colorize(title, :white, :bold)} - total: " + output.colorize(display_value(sum_overall), :brown, :bold) 100 end 101 end
Block function to build a result table using a provided sorting function. output
The output object. amount
The number of rows in the report table (default 10).
Options¶ ↑
* </tt>:title</tt> The title of the table * </tt>:sort</tt> The key to sort on (:hits, :cumulative, :average, :min or :max)
# File lib/request_log_analyzer/tracker/numeric_value.rb 62 def report_table(output, sort, options = {}, &_block) 63 output.puts 64 top_categories = output.slice_results(sorted_by(sort)) 65 output.with_style(top_line: true) do 66 output.table(*statistics_header(title: options[:title], highlight: sort)) do |rows| 67 top_categories.each { |(category, _)| rows << statistics_row(category) } 68 end 69 end 70 end
Return categories sorted by a given key. by
The key to sort on. This parameter can be omitted if a sorting block is provided instead
# File lib/request_log_analyzer/tracker/numeric_value.rb 302 def sorted_by(by = nil) 303 if block_given? 304 categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) } 305 else 306 categories.sort { |a, b| send(by, b[0]) <=> send(by, a[0]) } 307 end 308 end
Returns the column header for a statistics table to report on the statistics result
# File lib/request_log_analyzer/tracker/numeric_value.rb 311 def statistics_header(options) 312 [ 313 { title: options[:title], width: :rest }, 314 { title: 'Hits', align: :right, highlight: (options[:highlight] == :hits), min_width: 4 }, 315 { title: 'Sum', align: :right, highlight: (options[:highlight] == :sum), min_width: 6 }, 316 { title: 'Mean', align: :right, highlight: (options[:highlight] == :mean), min_width: 6 }, 317 { title: 'StdDev', align: :right, highlight: (options[:highlight] == :stddev), min_width: 6 }, 318 { title: 'Min', align: :right, highlight: (options[:highlight] == :min), min_width: 6 }, 319 { title: 'Max', align: :right, highlight: (options[:highlight] == :max), min_width: 6 }, 320 { title: '95 %tile', align: :right, highlight: (options[:highlight] == :percentile_interval), min_width: 11 } 321 ] 322 end
Returns a row of statistics information for a report table, given a category
# File lib/request_log_analyzer/tracker/numeric_value.rb 325 def statistics_row(cat) 326 [cat, hits(cat), display_value(sum(cat)), display_value(mean(cat)), display_value(stddev(cat)), 327 display_value(min(cat)), display_value(max(cat)), 328 display_value(percentile_interval(cat, 95).begin) + '-' + display_value(percentile_interval(cat, 95).end)] 329 end
Get the standard deviation of the duration of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 274 def stddev(cat) 275 Math.sqrt(variance(cat)) 276 end
Get the total duration of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 250 def sum(cat) 251 @categories[cat][:sum] 252 end
Get the cumlative duration of a all categories.
# File lib/request_log_analyzer/tracker/numeric_value.rb 291 def sum_overall 292 @categories.reduce(0.0) { |sum, (_, cat)| sum + cat[:sum] } 293 end
Returns the title of this tracker for reports
# File lib/request_log_analyzer/tracker/numeric_value.rb 104 def title 105 @title ||= begin 106 if options[:title] 107 options[:title] 108 else 109 title_builder = '' 110 title_builder << "#{options[:value]} " if options[:value].is_a?(Symbol) 111 title_builder << (options[:category].is_a?(Symbol) ? "per #{options[:category]}" : 'per request') 112 title_builder 113 end 114 end 115 end
Returns all the categories and the tracked duration as a hash than can be exported to YAML
# File lib/request_log_analyzer/tracker/numeric_value.rb 118 def to_yaml_object 119 return nil if @categories.empty? 120 @categories.each do |cat, info| 121 info[:stddev] = stddev(cat) 122 info[:median] = median(cat) if info[:buckets] 123 info[:interval_95_percent] = percentile_interval(cat, 95) if info[:buckets] 124 end 125 @categories 126 end
Get the value information from the request and store it in the respective categories.
If a request can contain multiple usable values for this tracker, the :multiple option should be set to true. In this case, all the values and respective categories will be read from the request using the every method from the fields given in the :value and :category option.
If the request contains only one suitable value and the :multiple is not set, it will read the single value and category from the fields provided in the :value and :category option, or calculate it with any lambda procedure that is assigned to these options. The request will be passed to procedure as input for the calculation.
@param [RequestLogAnalyzer::Request] request The request to get the information from.
# File lib/request_log_analyzer/tracker/numeric_value.rb 40 def update(request) 41 if options[:multiple] 42 found_categories = request.every(options[:category]) 43 found_values = request.every(options[:value]) 44 fail 'Capture mismatch for multiple values in a request' unless found_categories.length == found_values.length 45 46 found_categories.each_with_index do |cat, index| 47 update_statistics(cat, found_values[index]) if cat && found_values[index].is_a?(Numeric) 48 end 49 else 50 category = @categorizer.call(request) 51 value = @valueizer.call(request) 52 update_statistics(category, value) if (value.is_a?(Numeric) || value.is_a?(Array)) && category 53 end 54 end
Update the running calculation of statistics with the newly found numeric value.
category
-
The category for which to update the running statistics calculations
number
-
The numeric value to update the calculations with.
# File lib/request_log_analyzer/tracker/numeric_value.rb 224 def update_statistics(category, number) 225 return number.map { |n| update_statistics(category, n) } if number.is_a?(Array) 226 227 @categories[category] ||= { hits: 0, sum: 0, mean: 0.0, sum_of_squares: 0.0, min: number, max: number, 228 buckets: Array.new(@number_of_buckets, 0) } 229 230 delta = number - @categories[category][:mean] 231 232 @categories[category][:hits] += 1 233 @categories[category][:mean] += (delta / @categories[category][:hits]) 234 @categories[category][:sum_of_squares] += delta * (number - @categories[category][:mean]) 235 @categories[category][:sum] += number 236 @categories[category][:min] = number if number < @categories[category][:min] 237 @categories[category][:max] = number if number > @categories[category][:max] 238 239 bucketize(category, number) 240 end
Get the variance of the duration of a specific category. cat
The category
# File lib/request_log_analyzer/tracker/numeric_value.rb 280 def variance(cat) 281 return 0.0 if @categories[cat][:hits] <= 1 282 (@categories[cat][:sum_of_squares] / (@categories[cat][:hits] - 1)) 283 end