summaryrefslogtreecommitdiff
path: root/tool/lib/tracepointchecker.rb
diff options
context:
space:
mode:
Diffstat (limited to 'tool/lib/tracepointchecker.rb')
-rw-r--r--tool/lib/tracepointchecker.rb126
1 files changed, 126 insertions, 0 deletions
diff --git a/tool/lib/tracepointchecker.rb b/tool/lib/tracepointchecker.rb
new file mode 100644
index 0000000000..47822ecef5
--- /dev/null
+++ b/tool/lib/tracepointchecker.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+module TracePointChecker
+ STATE = {
+ count: 0,
+ running: false,
+ }
+
+ module ZombieTraceHunter
+ def tracepoint_capture_stat_get
+ TracePoint.stat.map{|k, (activated, deleted)|
+ deleted = 0 unless @tracepoint_captured_singlethread
+ [k, activated, deleted]
+ }
+ end
+
+ def before_setup
+ @tracepoint_captured_singlethread = (Thread.list.size == 1)
+ @tracepoint_captured_stat = tracepoint_capture_stat_get()
+ super
+ end
+
+ def after_teardown
+ super
+
+ # detect zombie traces.
+ assert_equal(
+ @tracepoint_captured_stat,
+ tracepoint_capture_stat_get(),
+ "The number of active/deleted trace events was changed"
+ )
+ # puts "TracePoint - deleted: #{deleted}" if deleted > 0
+
+ TracePointChecker.check if STATE[:running]
+ end
+ end
+
+ MAIN_THREAD = Thread.current
+ TRACES = []
+
+ def self.prefix event
+ case event
+ when :call, :return
+ :n
+ when :c_call, :c_return
+ :c
+ when :b_call, :b_return
+ :b
+ end
+ end
+
+ def self.clear_call_stack
+ Thread.current[:call_stack] = []
+ end
+
+ def self.call_stack
+ stack = Thread.current[:call_stack]
+ stack = clear_call_stack unless stack
+ stack
+ end
+
+ def self.verbose_out label, method
+ puts label => call_stack, :count => STATE[:count], :method => method
+ end
+
+ def self.method_label tp
+ "#{prefix(tp.event)}##{tp.method_id}"
+ end
+
+ def self.start verbose: false, stop_at_failure: false
+ call_events = %i(a_call)
+ return_events = %i(a_return)
+ clear_call_stack
+
+ STATE[:running] = true
+
+ TRACES << TracePoint.new(*call_events){|tp|
+ next if Thread.current != MAIN_THREAD
+
+ method = method_label(tp)
+ call_stack.push method
+ STATE[:count] += 1
+
+ verbose_out :psuh, method if verbose
+ }
+
+ TRACES << TracePoint.new(*return_events){|tp|
+ next if Thread.current != MAIN_THREAD
+ STATE[:count] += 1
+
+ method = "#{prefix(tp.event)}##{tp.method_id}"
+ verbose_out :pop1, method if verbose
+
+ stored_method = call_stack.pop
+ next if stored_method.nil?
+
+ verbose_out :pop2, method if verbose
+
+ if stored_method != method
+ stop if stop_at_failure
+ RubyVM::SDR() if defined? RubyVM::SDR()
+ call_stack.clear
+ raise "#{stored_method} is expected, but #{method} (count: #{STATE[:count]})"
+ end
+ }
+
+ TRACES.each{|trace| trace.enable}
+ end
+
+ def self.stop
+ STATE[:running] = true
+ TRACES.each{|trace| trace.disable}
+ TRACES.clear
+ end
+
+ def self.check
+ TRACES.each{|trace|
+ raise "trace #{trace} should not be deactivated" unless trace.enabled?
+ }
+ end
+end if defined?(TracePoint.stat)
+
+class ::Test::Unit::TestCase
+ include TracePointChecker::ZombieTraceHunter
+end if defined?(TracePointChecker)
+
+# TracePointChecker.start verbose: false