require "deforest/engine" require "deforest/version" require "active_support" require "active_record" module Deforest mattr_accessor :write_logs_to_db_every, :current_admin_method_name, :most_used_percentile_threshold, :least_used_percentile_threshold @@last_saved_log_file_at = nil @@saving_log_file = false def self.initialize! if block_given? yield self end self.initialize_db_sync_file() Dir["#{Rails.root}/app/models/**/*.rb"].map do |f| idx = f.index("app/models") models_heirarchy = f[idx..-1].gsub("app/models/","") exec_str = "" loop do parent, *children = models_heirarchy.split("/") if children.any? exec_str += "#{parent.camelize}::" else exec_str += parent.chomp(".rb").camelize break end models_heirarchy = children.join("/") end begin model = exec_str.constantize rescue puts "Deforest warning: could not track #{exec_str}" end if model.present? model.instance_methods(false).each do |mname| model.instance_eval do alias_method "old_#{mname}", mname define_method mname do |*args, &block| old_method = self.class.instance_method("old_#{mname}") file_name, line_no = old_method.source_location if file_name.include?("/app/models") Deforest.insert_into_logs(mname, file_name, line_no) end Deforest.insert_into_logs(mname, file_name, line_no) if @@last_saved_log_file_at < Deforest.write_logs_to_db_every.ago && !@@saving_log_file Deforest.parse_and_save_log_file() t = Time.zone.now @@last_saved_log_file_at = t File.open("deforest_db_sync.txt", "w") { |fl| fl.write(t.to_i) } end old_method.bind(self).call(*args, &block) end end end model.singleton_methods(false).each do |mname| model.singleton_class.send(:alias_method, "old_#{mname}", mname) model.define_singleton_method mname do |*args, &block| old_method = self.singleton_method("old_#{mname}") file_name, line_no = old_method.source_location if file_name.include?("/app/models") Deforest.insert_into_logs(mname, file_name, line_no) end if @@last_saved_log_file_at < Deforest.write_logs_to_db_every.ago && !@@saving_log_file Deforest.parse_and_save_log_file() t = Time.zone.now @@last_saved_log_file_at = t File.open("deforest_db_sync.txt", "w") { |fl| fl.write(t.to_i) } end old_method.unbind.bind(self).call(*args, &block) end end end end end def self.initialize_db_sync_file if File.exists?("deforest_db_sync.txt") @@last_saved_log_file_at = Time.at(File.open("deforest_db_sync.txt").read.to_i) else File.open("deforest_db_sync.txt", "w") do |f| current_time = Time.zone.now.to_i @@last_saved_log_file_at = current_time f.write(current_time) end end end def self.insert_into_logs(method_name, file_name, line_no) key = "#{file_name}|#{line_no}|#{method_name}\n" log_file_name = @@saving_log_file ? "deforest_tmp.log" : "deforest.log" File.open(log_file_name, "a") do |f| f.write(key) end end def self.parse_and_save_log_file @@saving_log_file = true sql_stmt = "INSERT INTO deforest_logs (file_name, line_no, method_name, count, created_at, updated_at) VALUES " hash = {} File.foreach("deforest.log") do |line| line = line.chomp("\n") if hash.has_key?(line) hash[line] += 1 else hash[line] = 1 end end hash.each do |loc, count| sql_stmt += "(#{loc.split("|").map { |s| "'#{s}'" }.join(",")}, #{count}, current_timestamp, current_timestamp)," end sql_stmt.chomp!(",") sql_stmt += ";" ActiveRecord::Base.connection.execute(sql_stmt) if File.exists?("deforest_tmp.log") File.delete("deforest.log") File.rename("deforest_tmp.log", "deforest.log") else File.delete("deforest.log") end @@saving_log_file = false end def self.prepare_file_for_render(file) line_no_count = Log.where(file_name: file).group(:line_no).select("line_no,SUM(count) AS count_sum").inject({}) { |h, el| h.merge!(el.line_no => el.count_sum) } stack = [] current_highlight_color = nil highlight = Log.get_highlight_colors_for_file(file) prepare_for_render(File.open(file).read) do |line, idx| idx += 1 if line_no_count.has_key?(idx) stack = [1] current_highlight_color = highlight[idx] last_log_for_current_line = Log.where(file_name: file).where(line_no: idx).order("created_at DESC").limit(1).first "" + line + "  " + "#{line_no_count[idx]}" + "last called at: #{last_log_for_current_line.created_at.strftime('%m/%d/%Y')}" else "#{line}" end end end def self.prepare_for_render(source_code, add_line_number = true) source_code.split("\n").map.with_index do |line, index| first_letter_idx = line.chars.index { |ch| ch != " " } if first_letter_idx && first_letter_idx >= 0 && first_letter_idx < line.size leading_nbsp = (0...first_letter_idx).map { " " }.join("") prepared_line = leading_nbsp.present? ? leading_nbsp + line.lstrip : line.strip result_line = if block_given? yield prepared_line + "\n", index else "#{prepared_line}" end "#{index + 1}" + result_line end end.join("
") end end