class StackTracer
Public Class Methods
new(base_path)
click to toggle source
# File lib/stack_tracer.rb, line 11 def initialize(base_path) @git_client = GitClient.new({"git_dir" => File.join(base_path, ".git")}) @cache = ProjectCache.new(base_path) end
Public Instance Methods
trace(error_id, deployed_commit_id=nil, last_deployed_commit_id=nil)
click to toggle source
# File lib/stack_tracer.rb, line 16 def trace(error_id, deployed_commit_id=nil, last_deployed_commit_id=nil) error = @cache.read("errors")[error_id.to_s].deep_symbolize_keys commits = @cache.read("commits") deploys = @cache.read("deploys") deployed_commits = deploys .map { |deploy| commits[deploy["revision"]] } .reject(&:nil?) .sort_by { |commit| commit["date"] } deployed_commit_id ||= after_deployed_commit(error[:first_time], deployed_commits) last_deployed_commit_id ||= prior_deployed_commit(error[:first_time], deployed_commits) git_files = git_files(deployed_commit_id.to_s) stacktrace = stacktrace(error, git_files) trace_files = trace_files(stacktrace, git_files) trace_lines = trace_lines(stacktrace, trace_files, commits, last_deployed_commit_id.to_s) detail(error, trace_lines) end
Private Instance Methods
after_deployed_commit(timestamp, commits)
click to toggle source
# File lib/stack_tracer.rb, line 190 def after_deployed_commit(timestamp, commits) commit = commits .reverse() .detect { |commit| commit["date"] > timestamp } commit ||= commits.last commit["commit_id"] end
describe_line(line, commits, cutoff_time)
click to toggle source
# File lib/stack_tracer.rb, line 163 def describe_line(line, commits, cutoff_time) line = line.dup line[:updated_at] = DateTime.parse(commits[line[:commit]]["date"]) line[:after_cutoff] = line[:updated_at] > cutoff_time line[:author] = commits[line[:commit]]["author"] line[:score] = Line.score(line) line end
detail(error, trace_lines)
click to toggle source
# File lib/stack_tracer.rb, line 97 def detail(error, trace_lines) functions = trace_lines.reduce({}) do |acc, trace| function_id = trace[:function].nil? ? trace[:file] : "#{trace[:file]}:#{trace[:function][:name]}" acc[function_id] ||= trace depth = trace[:line][:depth] trace[:line][:trace_title] = trace[:title] if acc[function_id].has_key?(:lines) acc[function_id][:lines] << trace[:line] else acc[function_id][:lines] = [trace[:line]] acc[function_id].delete(:line) end acc[function_id][:function_lines].each_with_index do |line, index| function_line_num = trace[:line_num] - trace[:function][:start] line[:depth] = depth if index == function_line_num end if not acc.has_key?(function_id) acc[function_id] = trace end acc end details = functions .reduce(detail_context()) do |acc, (file_path, function)| function[:function_lines].each do |line| score_line(acc[:experts], line[:author]["email"], line) if line[:after_cutoff] score_line(acc[:suspects], line[:author]["email"], line) score_line(acc[:suspect_commits], line[:commit], line) end line[:revisions].each do |revision| score_line(acc[:experts], revision[:author]["email"], line) end end acc end details.merge({ message: error[:message], first_time: error[:first_time], last_time: error[:last_time], total_occurrences: error[:total_occurrences], functions: functions, }) end
detail_context()
click to toggle source
# File lib/stack_tracer.rb, line 155 def detail_context() { experts: {}, suspects: {}, suspect_commits: {} } end
function_lines(file, function)
click to toggle source
# File lib/stack_tracer.rb, line 181 def function_lines(file, function) function.nil? ? [] : file[:lines][(function[:start] - 1)..function[:end]] end
git_files(commit_id)
click to toggle source
# File lib/stack_tracer.rb, line 39 def git_files(commit_id) @git_client .file_tree(commit_id) .reduce({}) do |acc, blob| blob = blob.split(" ") acc[blob.last] = blob[2] acc end end
prior_deployed_commit(timestamp, commits)
click to toggle source
# File lib/stack_tracer.rb, line 185 def prior_deployed_commit(timestamp, commits) commits .detect { |commit| commit["date"] < timestamp }["commit_id"] end
score_line(hash, key, line)
click to toggle source
# File lib/stack_tracer.rb, line 149 def score_line(hash, key, line) hash[key] ||= 0 hash[key] += 1 hash[key] += 2.0 / (line[:depth] + 1) if line.has_key?(:depth) end
stacktrace(error, git_files)
click to toggle source
# File lib/stack_tracer.rb, line 172 def stacktrace(error, git_files) error[:stack_trace] .reduce([]) do |acc, trace| trace[:file] = git_files.keys.detect { |app_file| trace[:file].include?(app_file) } acc << trace if not trace[:file].nil? acc end end
trace_files(stacktrace, git_files)
click to toggle source
# File lib/stack_tracer.rb, line 49 def trace_files(stacktrace, git_files) stacktrace .map { |trace_file| trace_file[:file] } .uniq .reduce({}) do |acc, file| acc[file] = @cache.read_object(git_files[file]).deep_symbolize_keys acc end end
trace_lines(stacktrace, trace_files, commits, last_deployed_commit_id)
click to toggle source
# File lib/stack_tracer.rb, line 59 def trace_lines(stacktrace, trace_files, commits, last_deployed_commit_id) last_deployed_commit = commits[last_deployed_commit_id] cutoff_time = DateTime.parse(last_deployed_commit["date"]) stacktrace .each_with_index .map do |trace, depth| file = trace_files[trace[:file]] line_num = trace[:line] - 1 line = describe_line(file[:lines][line_num], commits, cutoff_time) line[:depth] = depth function = file[:functions].detect do |function| line_num >= function[:start] && line_num <= function[:end] end function_lines = function_lines(file, function) .each_with_index .map do |line, index| line = describe_line(line, commits, cutoff_time) line[:revisions] = line[:revisions].map do |revision| revision[:author] = commits[revision[:commit]]["author"] revision end line end { file: trace[:file], line_num: trace[:line], line: line, title: "#{trace[:file]}:#{trace[:line]} - #{trace[:function]}", function: function, function_lines: function_lines } end end