1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
# frozen_string_literal: true
require 'get_process_mem'
require 'derailed_benchmarks/require_tree'
ENV['CUT_OFF'] ||= "0.3"
# This file contains classes and monkey patches to measure the amount of memory
# useage requiring an individual file adds.
# Monkey patch kernel to ensure that all `require` calls call the same
# method
module Kernel
REQUIRE_STACK = []
module_function
alias_method :original_require, :require
alias_method :original_require_relative, :require_relative
def require(file)
measure_memory_impact(file) do |file|
# "source_annotation_extractor" is deprecated in Rails 6
# # if we don't skip the library it leads to a crash
# next if file == "rails/source_annotation_extractor" && Rails.version >= '6.0'
original_require(file)
end
end
def require_relative(file)
if Pathname.new(file).absolute?
require file
else
require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
end
end
private
# The core extension we use to measure require time of all requires
# When a file is required we create a tree node with its file name.
# We then push it onto a stack, this is because requiring a file can
# require other files before it is finished.
#
# When a child file is required, a tree node is created and the child file
# is pushed onto the parents tree. We then repeat the process as child
# files may require additional files.
#
# When a require returns we remove it from the require stack so we don't
# accidentally push additional children nodes to it. We then store the
# memory cost of the require in the tree node.
def measure_memory_impact(file, &block)
mem = GetProcessMem.new
node = DerailedBenchmarks::RequireTree.new(file)
parent = REQUIRE_STACK.last
parent << node
REQUIRE_STACK.push(node)
begin
before = mem.mb
block.call file
ensure
REQUIRE_STACK.pop # node
after = mem.mb
end
node.cost = after - before
end
end
# Top level node that will store all require information for the entire app
TOP_REQUIRE = DerailedBenchmarks::RequireTree.new("TOP")
REQUIRE_STACK.push(TOP_REQUIRE)
class Object
private
def require(path)
Kernel.require(path)
end
end
# Don't forget to assign a cost to the top level
cost_before_requiring_anything = GetProcessMem.new.mb
TOP_REQUIRE.cost = cost_before_requiring_anything
def TOP_REQUIRE.print_sorted_children(*args)
self.cost = GetProcessMem.new.mb - self.cost
super
end
|