class Rcov::CallSiteAnalyzer
available.
information for those methods for which callsite information is
the inspection of the CallSiteAnalyzer, i.o.w. you will only have defsite
defsite
information is only available for methods that were called under
class.
provision is taken to ignore code executed “inside” the CallSiteAnalyzer
analyzer will manage its data separately. Note however that no special
possible to nest the #run_hooked / #install_hook/#remove_hook blocks: each
You can have several CallSiteAnalyzer objects at a time, and it is
defsite.line # => 2
defsite.file # => “example.rb”
defsite = analyzer.defsite(“X#f1”)
# associations
analyzer.callsites(“X#f2”) # => hash with CallSite => count
analyzer.defsite(“X#f1”) # => DefSite object
analyzer.methods_for_class(“X”) # => [“f1”, “f2”, “f3”]
analyzer.analyzed_classes # => [“X”, … ]
end
# to the previously recorded one
# the information generated in this run is aggregated
x.f3
analyzer.run_hooked do
# .…
end
x.f1
analyzer.run_hooked do
x = X.new
analyzer = Rcov::CallSiteAnalyzer.new
end
def f3; f1 end
def f2; 1 + 1 end
def f1; f2 end
class Xexample.rb
:
== Example
* where a method was called from (“callsite
”)
* where a method is defined (“defsite
”)
A CallSiteAnalyzer can be used to obtain information about:
def self.hook_level # :nodoc:
defined this way instead of attr_accessor so that it's covered
def self.hook_level # :nodoc: @hook_level end
def self.hook_level=(x) # :nodoc:
def self.hook_level=(x) # :nodoc: @hook_level = x end
def aggregate_data(aggregated_data, delta)
def aggregate_data(aggregated_data, delta) callsites1, defsites1 = aggregated_data callsites2, defsites2 = delta callsites2.each_pair do |(klass, method), hash| dest_hash = (callsites1[[klass, method]] ||= {}) hash.each_pair do |callsite, count| dest_hash[callsite] ||= 0 dest_hash[callsite] += count end end defsites1.update(defsites2) end
def analyzed_classes
each of them). Singleton classes are rendered as:
Returns an array of strings describing the classes (just klass.to_s for
Classes whose methods have been called.
def analyzed_classes raw_data_relative.first.keys.map{|klass, meth| klass}.uniq.sort end
def callsites(classname_or_fullname, methodname = nil)
analyzer.callsites("Foo", "f1")
or
analyzer.callsites("Foo.g1") # singleton method of the class
analyzer.callsites("Foo#f1") # instance method
Can be called in two ways:
Returns a hash with CallSite => call count associations or +nil+
def callsites(classname_or_fullname, methodname = nil) rawsites = raw_data_relative.first[expand_name(classname_or_fullname, methodname)] return nil unless rawsites ret = {} # could be a job for inject but it's slow and I don't mind the extra loc rawsites.each_pair do |backtrace, count| ret[CallSite.new(backtrace)] = count end ret end
def compute_raw_data_difference(first, last)
def compute_raw_data_difference(first, last) difference = {} default = Hash.new(0) callsites1, defsites1 = *first callsites2, defsites2 = *last callsites2.each_pair do |(klass, method), hash| old_hash = callsites1[[klass, method]] || default hash.each_pair do |callsite, count| diff = hash[callsite] - (old_hash[callsite] || 0) if diff > 0 difference[[klass, method]] ||= {} difference[[klass, method]][callsite] = diff end end end [difference, defsites1.update(defsites2)] end
def data_default; [{}, {}] end
def data_default; [{}, {}] end
def defsite(classname_or_fullname, methodname = nil)
analyzer.defsite("Foo", "f1")
or
analyzer.defsite("Foo.g1") # singleton method of the class
analyzer.defsite("Foo#f1") # instance method
Can be called in two ways:
Returns a DefSite object corresponding to the given method
def defsite(classname_or_fullname, methodname = nil) file, line = raw_data_relative[1][expand_name(classname_or_fullname, methodname)] return nil unless file && line DefSite.new(file, line) end
def expand_name(classname_or_fullname, methodname = nil)
def expand_name(classname_or_fullname, methodname = nil) if methodname.nil? case classname_or_fullname when /(.*)#(.*)/ then classname, methodname = $1, $2 when /(.*)\.(.*)/ then classname, methodname = "#<Class:#{$1}>", $2 else raise ArgumentError, "Incorrect method name" end return [classname, methodname] end [classname_or_fullname, methodname] end
def initialize
def initialize super(:install_callsite_hook, :remove_callsite_hook, :reset_callsite) end
def methods_for_class(classname)
the notation used for singleton classes.
Methods that were called for the given class. See #analyzed_classes for
def methods_for_class(classname) a = raw_data_relative.first.keys.select{|kl,_| kl == classname}.map{|_,meth| meth}.sort a.empty? ? nil : a end
def raw_data_absolute
def raw_data_absolute raw, method_def_site = RCOV__.generate_callsite_info ret1 = {} ret2 = {} raw.each_pair do |(klass, method), hash| begin key = [klass.to_s, method.to_s] ret1[key] = hash.clone #Marshal.load(Marshal.dump(hash)) ret2[key] = method_def_site[[klass, method]] #rescue Exception end end [ret1, ret2] end