tools/log_processing/ems_refresh_timings.rb
#!/usr/bin/env ruby
RAILS_ROOT = File.expand_path(File.join(__dir__, %w[.. ..]))
require 'manageiq/gems/pending'
require 'miq_logger_processor'
require 'active_support/core_ext/enumerable' # Pull in Enumerable sum method
require 'more_core_extensions/core_ext/array'
require 'optimist'
require 'time'
def parse_args(argv)
logfiles = []
opts = Optimist.options do
banner <<~EOS
Parse EMS Refreshes from a set of evm.log files and filter on a set of
provided conditions.
Usage:
ruby ems_refresh_timings.rb [OPTION]... <FILE>...
Options:
EOS
opt :sort_by, 'Column to sort by, options are start_time, end_time, ' \
'duration, ems, target_type, and target',
:type => :string, :default => 'start_time'
opt :target, 'Filter by specific targets, comma separated', :type => :strings
opt :target_type, 'Filter by specific target types, comma separated', :type => :strings
opt :ems, 'Filter by a specific ems, comma separted', :type => :strings
end
argv.each do |arg|
logfiles << File.expand_path(arg) if File.file?(arg)
end
# If no logs were given on command line default to main project evm.log
logfiles << File.join(RAILS_ROOT, 'log/evm.log') if logfiles.empty?
[opts, logfiles]
end
def filter(hash, opts = {})
return false unless opts[:target_type].nil? || opts[:target_type].include?(hash[:target_type])
return false unless opts[:target].nil? || opts[:target].include?(hash[:target])
return false unless opts[:ems].nil? || opts[:ems].include?(hash[:ems])
true
end
def parse_refresh_target(line)
if line =~ /EMS:? \[(.*?)\].+Refreshing target ([^\s]+).\[(.*?)\].+Complete/
{
:time => line.time,
:ems => Regexp.last_match[1],
:target_type => Regexp.last_match[2],
:target => Regexp.last_match[3]
}
end
end
def parse_refresh_timings(line, targets)
if line =~ /EMS:? \[(.*?)\].+Refreshing targets for EMS...Complete - Timings:? (\{.+)$/
ems = Regexp.last_match[1]
# Refresh timings are printed to the log as a hash, just eval it
refresh_timings = eval(Regexp.last_match[2])
# Find the most recent refresh target for our ems, since there is
# only one refresh worker this "has to be" the right one
# If this changes in the future we'll have to add a PID lookup here
refresh_target = targets[ems].last
refresh_target = {
:target => "unknown",
:target_type => "unknown"
} if refresh_target.nil?
# Add other useful information to the refresh timings
refresh_timings[:ems] = ems
refresh_timings[:end_time] = Time.parse(line.time + ' UTC').utc
refresh_timings[:duration] = refresh_timings[:total_time] || refresh_timings[:ems_refresh]
refresh_timings[:start_time] = refresh_timings[:end_time] - refresh_timings[:duration]
refresh_timings[:target] = refresh_target[:target]
refresh_timings[:target_type] = refresh_target[:target_type]
refresh_timings
end
end
def sort_timings(timings, sort_key)
timings.sort_by { |t| t[sort_key.to_sym] }
end
def print_results(all_timings, opts)
columns = [:start_time, :end_time, :duration, :ems, :target_type, :target]
puts sort_timings(all_timings, opts[:sort_by]).tableize(:columns => columns)
end
def mean(array)
array.sum.to_f / array.length
end
def print_stats(timings)
timings.each do |type, values|
puts "\nAverage refresh time per #{type}"
durations = []
values.each do |key, timing|
durations << {
type.to_sym => key,
:duration => mean(timing.collect { |t| t[:duration] })
}
end
puts durations.tableize(:columns => [type.to_sym, :duration])
end
end
options, logfiles = parse_args(ARGV)
puts 'Processing file...'
all_timings = []
all_targets = Hash.new { |k, v| k[v] = [] }
timings = {
:ems => Hash.new { |k, v| k[v] = [] },
:target => Hash.new { |k, v| k[v] = [] },
:target_type => Hash.new { |k, v| k[v] = [] }
}
logfiles.each do |logfile|
MiqLoggerProcessor.new(logfile).each do |line|
# Parse out the refresh target or refresh timings
if (target_hash = parse_refresh_target(line))
all_targets[target_hash[:ems]] << target_hash
elsif (refresh_timings = parse_refresh_timings(line, all_targets))
ems = refresh_timings[:ems]
target = refresh_timings[:target]
target_type = refresh_timings[:target_type]
if filter(refresh_timings, options)
all_timings << refresh_timings
timings[:ems][ems] << refresh_timings
timings[:target][target] << refresh_timings
timings[:target_type][target_type] << refresh_timings
end
end
end
end
puts "Found #{all_timings.length} refreshes from #{timings[:ems].keys.length} providers"
unless all_timings.empty?
print_results(all_timings, options)
print_stats(timings)
end