class Arachni::Support::Profiler

@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Public Class Methods

write_samples_to_disk( file, options = {} ) click to toggle source
# File lib/arachni/support/profiler.rb, line 20
def self.write_samples_to_disk( file, options = {} )
    profiler = Support::Profiler.new
    profiler.trace_allocations

    Thread.new do
        begin
            loop do
                profiler.write_object_space( file, options )
                sleep options[:interval] || 1
            end
        rescue => e
            ap e
            ap e.backtrace
        end
    end
end

Public Instance Methods

count_objects( klass ) click to toggle source
# File lib/arachni/support/profiler.rb, line 212
def count_objects( klass )
    ObjectSpace.each_object( klass ){}
end
find_dependencies( _object_id, _mapped = {} ) click to toggle source
# File lib/arachni/support/profiler.rb, line 58
def find_dependencies( _object_id, _mapped = {} )
    mapped = _mapped
    points_to_object = find_references( Mass[_object_id] )
    ids = points_to_object.keys.map { |x| /\#(\d*)/.match(x).captures.first.to_i }
    mapped[_object_id] = points_to_object#ids

    unmapped = ids - mapped.keys
    unmapped.each do |x|
        new_deps = find_dependencies( x, mapped )
        mapped.merge( new_deps )
    end
    mapped
end
find_references( o ) click to toggle source
# File lib/arachni/support/profiler.rb, line 54
def find_references( o )
    Mass.references( o )
end
object_space( options = {} ) click to toggle source
# File lib/arachni/support/profiler.rb, line 82
def object_space( options = {} )
    klass      = options[:class]
    namespaces = options[:namespaces] || [
        Arachni,
        Ethon,
        Typhoeus,
        Watir,
        Selenium,
        Addressable,
        Nokogiri,
        String,
        Hash,
        Array,
        Set,
        Thread
    ]

    namespaces = Set.new( namespaces )

    max_entries = options[:max_entries] || 50

    object_space    = {}
    @object_space ||= {}

    ObjectSpace.each_object do |o|
        next if o.class != klass && !object_within_namespace?( o, namespaces )

        # if o.class == Page
        #     # print_object_allocations( o )
        #
        #     # ap ObjectSpace.allocation_class_path( o ).to_s
        #     # ap "#{ObjectSpace.allocation_sourcefile( o )}:#{ObjectSpace.allocation_sourceline( o )}"
        #     ap find_references( o )
        # end

        # if o.class == String && o.size > 1_000_000
        #     print_object_allocations( o )
        #     # print_references( o )
        #     # print_dependencies( o )
        #
        #     Process.kill 'KILL', Process.pid
        # end

        # if o.class == Thread
        #     print_object_allocations( o )
        #     # print_references( o )
        #     # print_dependencies( o )
        #
        #     # Process.kill 'KILL', Process.pid
        # end

        object_space[o.class] ||= {
            memsize: 0,
            count:   0
        }

        if o.class == String
            object_space[o.class][:memsize] += o.bytesize
        else
            object_space[o.class][:memsize] += ObjectSpace.memsize_of(o)
        end

        object_space[o.class][:count] += 1
    end

    ap '-' * 120

    object_space['All'] = {
        memsize: ObjectSpace.memsize_of_all,
        count:   ObjectSpace.count_objects_size[:TOTAL]
    }

    object_space = Hash[object_space.sort_by { |_, v| v[:count] }.reverse[0..max_entries]]

    with_deltas = {}
    object_space.each do |k, v|
        @object_space[k] ||= {
            memsize: 0,
            count:   0
        }

        if v[:count].is_a? Numeric
            with_deltas[k] = "#{v[:count]} (#{v[:count] - @object_space[k][:count]})"
            with_deltas[k] << " -- #{Utilities.bytes_to_megabytes v[:memsize]}"
            with_deltas[k] << " (#{Utilities.bytes_to_megabytes v[:memsize] - @object_space[k][:memsize]})"
        else
            with_deltas[k] = "#{v[:count]} -- #{Utilities.bytes_to_megabytes v[:memsize]}"
        end
    end

    @object_space = object_space.dup
    with_deltas

rescue => e
    ap e
    ap e.backtrace
    {}
end
print_dependencies( o ) click to toggle source
print_object_allocations( o ) click to toggle source
print_object_space( options = {} ) click to toggle source
print_references( o ) click to toggle source
resource_consumption() click to toggle source
# File lib/arachni/support/profiler.rb, line 216
def resource_consumption
    procinfo = ::Sys::ProcTable.ps( Process.pid )
    {
        cpu_utilization:    procinfo[:pctcpu],
        memory_utilization: procinfo[:pctmem],
        memory_usage:       rss_to_mb( procinfo[:rss] )
    }
end
rss_to_mb( rss ) click to toggle source
# File lib/arachni/support/profiler.rb, line 225
def rss_to_mb( rss )
    rss * 4096.0 / 1024.0 / 1024.0
end
trace_allocations() click to toggle source
# File lib/arachni/support/profiler.rb, line 37
def trace_allocations
    require 'objspace'
    ObjectSpace.trace_object_allocations_start
end
write_object_space( file, options = {} ) click to toggle source
# File lib/arachni/support/profiler.rb, line 181
def write_object_space( file, options = {} )
    consumption = resource_consumption

    mem = consumption[:memory_usage].round(3)

    delta_mem = 0
    if @consumption
        delta_mem = (mem - @consumption[:memory_usage]).round(3)
    end

    str = "RAM: #{mem}MB (#{delta_mem}MB)"
    str << " (#{consumption[:memory_utilization]}%)"
    str << " - CPU: #{consumption[:cpu_utilization]}%\n\n"

    @consumption = consumption

    os      = object_space( options )
    maxsize = os.keys.map(&:to_s).map(&:size).sort.reverse.first

    os.each do |klass, info|
        offset = maxsize - klass.to_s.size
        str << "#{klass}: #{' ' * offset}#{info}\n"
    end

    IO.write( file, str )
end

Private Instance Methods

object_within_namespace?( object, namespaces ) click to toggle source
# File lib/arachni/support/profiler.rb, line 231
def object_within_namespace?( object, namespaces )
    return true if namespaces.empty?

    namespaces.each do |namespace|
        return true if object.class.to_s.start_with?( namespace.to_s )
    end
    false
end