Class: KitchenBlame::Blame
- Inherits:
-
Object
- Object
- KitchenBlame::Blame
- Defined in:
- lib/kitchen_blame.rb
Overview
Main class
Constant Summary collapse
- LOG_DIR =
(Pathname.getwd + Pathname.new('.kitchen/logs')).freeze
Class Method Summary collapse
- .analyze_create(key_path, log) ⇒ Object
-
.analyze_duration(log) ⇒ Object
rubocop:disable MethodLength.
-
.analyze_recipe(log) ⇒ Object
rubocop:disable MethodLength.
-
.extract_syslog_time(entry) ⇒ Object
Return the DateTime object from a regex match with a named capture of ‘time’.
-
.extract_time(match) ⇒ Object
Return the DateTime object from a regex match with a named capture of ‘time’.
- .image_create(log, server_log_entries) ⇒ Object
- .list(dir) ⇒ Object
-
.measure_duration(start, finish) ⇒ Object
Return the duration for two Datetime objects.
-
.measure_pair(pair) ⇒ Object
Return the duration for two objects that both have time fields in a hash.
-
.resolve_log(log) ⇒ Object
Determine the appropriate log file or raise an error.
- .server_log_entries(key_path, log) ⇒ Object
Class Method Details
.analyze_create(key_path, log) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/kitchen_blame.rb', line 76 def self.analyze_create(key_path, log) log = resolve_log(log) server_log_entries = server_log_entries(key_path, log) create_duration = image_create(log, server_log_entries) startup_finished_entry = server_log_entries.find { |entry| entry['MESSAGE'].include?('Startup finished') } boot_start = extract_syslog_time(server_log_entries.first) boot_finish = extract_syslog_time(startup_finished_entry) boot_duration = measure_duration(boot_start, boot_finish) puts "Image create took #{create_duration} seconds" puts "Boot took #{boot_duration} seconds" puts "Systemd timing: #{startup_finished_entry['MESSAGE']}" end |
.analyze_duration(log) ⇒ Object
rubocop:disable MethodLength
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/kitchen_blame.rb', line 123 def self.analyze_duration(log) log = resolve_log(log) log_lines = IO.readlines(log).map do |line| match_data = /.,\s\[(?<time>.*)\]\s+[^:]+:(?<log>.*)$/.match(line) next unless match_data time = extract_time(match_data) log = match_data[:log] { log: log, time: time } end max_duration = log_lines.compact.each_cons(2).map {|pair| measure_pair(pair)}.max puts max_duration log_lines.compact.each_cons(2) do |pair| log = pair.first[:log] duration = measure_pair(pair) puts "#{duration.to_s.ljust(max_duration.to_s.length)} seconds for entry #{log}" end end |
.analyze_recipe(log) ⇒ Object
rubocop:disable MethodLength
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/kitchen_blame.rb', line 104 def self.analyze_recipe(log) log = resolve_log(log) recipe_lines = IO.readlines(log).grep(/Recipe:/).map do |line| match_data = /\[(?<time>.*)\].*Recipe:\s(?<recipe>.*)$/.match(line) next unless match_data time = extract_time(match_data) recipe = match_data[:recipe] { recipe: recipe, time: time } end max_duration = recipe_lines.compact.each_cons(2).map {|pair| measure_pair(pair)}.max recipe_lines.compact.each_cons(2) do |pair| recipe = pair.first[:recipe] duration = measure_pair(pair) puts "#{duration.to_s.ljust(max_duration.to_s.length)} seconds for recipe #{recipe}" end end |
.extract_syslog_time(entry) ⇒ Object
Return the DateTime object from a regex match with a named capture of ‘time’
34 35 36 37 38 |
# File 'lib/kitchen_blame.rb', line 34 def self.extract_syslog_time(entry) time = entry['__REALTIME_TIMESTAMP'] time = (time.to_f / 1_000_000).to_s DateTime.strptime(time, '%s') end |
.extract_time(match) ⇒ Object
Return the DateTime object from a regex match with a named capture of ‘time’
27 28 29 30 31 |
# File 'lib/kitchen_blame.rb', line 27 def self.extract_time(match) time = match[:time] time = time.split('.').first DateTime.strptime("#{time} #{Time.now.getlocal.zone}", '%Y-%m-%dT%H:%M:%S %Z') end |
.image_create(log, server_log_entries) ⇒ Object
95 96 97 98 99 100 101 |
# File 'lib/kitchen_blame.rb', line 95 def self.image_create(log, server_log_entries) boot_start = extract_syslog_time(server_log_entries.first) create_line = File.foreach(log).grep(/Creating/).first match_data = /.,\s\[(?<time>.*)\]\s+[^:]+:(?<log>.*)$/.match(create_line) create_time = extract_time(match_data) measure_duration(create_time, boot_start) end |
.list(dir) ⇒ Object
50 51 52 53 54 55 56 57 |
# File 'lib/kitchen_blame.rb', line 50 def self.list(dir) return unless File.exist? dir Pathname.new(dir).each_child.select(&:file?).each do |log_dir_entry| next if log_dir_entry.basename.to_s == 'kitchen.log' next unless log_dir_entry.extname == '.log' puts log_dir_entry.basename('.log') end end |
.measure_duration(start, finish) ⇒ Object
Return the duration for two Datetime objects
41 42 43 |
# File 'lib/kitchen_blame.rb', line 41 def self.measure_duration(start, finish) ((finish - start) * 24 * 60 * 60).to_i end |
.measure_pair(pair) ⇒ Object
Return the duration for two objects that both have time fields in a hash
46 47 48 |
# File 'lib/kitchen_blame.rb', line 46 def self.measure_pair(pair) ((pair.last[:time] - pair.first[:time]) * 24 * 60 * 60).to_i end |
.resolve_log(log) ⇒ Object
Determine the appropriate log file or raise an error
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/kitchen_blame.rb', line 60 def self.resolve_log(log) log_path = Pathname.new(log) return log_path.to_path if log_path.exist? && log_path.file? log_path = LOG_DIR + log_path return log_path.to_path if log_path.exist? && log_path.file? fail "#{LOG_DIR} does not exist. Cannot find log file matching '#{log}'" unless LOG_DIR.exist? LOG_DIR.each_child.select(&:file?).each do |log_dir_entry| return log_dir_entry.to_path if log_dir_entry.basename.fnmatch("*#{log}*") end if log == 'latest' latest = LOG_DIR.each_child.select(&:file?).sort_by(&:mtime).last return latest if latest end fail "Unable to fully qualify or find test kitchen log #{log}" end |
.server_log_entries(key_path, log) ⇒ Object
89 90 91 92 93 |
# File 'lib/kitchen_blame.rb', line 89 def self.server_log_entries(key_path, log) ip = File.foreach(log).grep(/Attaching floating IP </).first[/\<(?<ip>.*)\>/, 'ip'] server_log_entries = `ssh -i #{key_path} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no vagrant@#{ip} 'sudo journalctl -o json --no-pager' 2>/dev/null`.split("\n") server_log_entries.map { |entry| JSON.parse(entry) } end |