Class: Bulldogger::Probe::MethodStats

Inherits:
Object
  • Object
show all
Defined in:
lib/bulldogger/probe/method_stats.rb

Overview

Aggregates every :call/:return pair observed for one probe target into the "methods"[label] shape the evidence JSON publishes: call count, per-parameter and return-value shape, the raise-exit count, and the set of call sites.

record_call/record_return take no lock: each caller writes only into its own ThreadLocal (below), reached through Thread.current -- fiber-local, like RaiseTracker's own storage, so two fibers on the same OS thread still get separate slots -- keyed uniquely to this MethodStats instance, so two threads calling the same probed method concurrently never touch the same mutable state. A per-call Mutex#synchronize was measured to be on the hot path for every single call and return; a fiber only pays a lock once -- the first time it is ever seen by this target, to register its ThreadLocal in @locals -- and #to_h folds every one of them into the published totals exactly once, at finish time.

Defined Under Namespace

Classes: ThreadLocal

Instance Method Summary collapse

Constructor Details

#initialize(target:, formatter:, redactor:, max_samples:) ⇒ MethodStats

Returns a new instance of MethodStats.



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
# File 'lib/bulldogger/probe/method_stats.rb', line 29

def initialize(target:, formatter:, redactor:, max_samples:)
  @formatter = formatter
  @redactor = redactor
  @max_samples = max_samples
  @parameters = declared_parameters(target.unbound_method)

  @calls = 0
  @raised_exits = 0
  @param_buckets = build_param_buckets
  @returns = new_returns_bucket
  @raised = Hash.new(0)
  # key => [count, one representative Thread::Backtrace::Location].
  # One Hash, not two: a second hash keyed the same way would be
  # a second lookup on every single call for no benefit -- see
  # #record_caller.
  @callers = {}

  # Unique per instance (not a fixed name): two targets probed
  # in the same session must not share one thread-local slot.
  @thread_key = :"bulldogger_probe_method_stats_#{object_id}"
  @locals = []
  @locals_mutex = Mutex.new
  @merge_mutex = Mutex.new
  @merged = false
end

Instance Method Details

#record_call(tp, caller_location) ⇒ Object



55
56
57
58
59
60
# File 'lib/bulldogger/probe/method_stats.rb', line 55

def record_call(tp, caller_location)
  local = thread_local
  local.calls += 1
  record_param_samples(local, tp.binding)
  record_caller(local, caller_location)
end

#record_return(tp, raised:) ⇒ Object

raised: true means contract-verbs.md's raise-exit discriminator fired for this return -- this call must not be counted as a nil return (that would fabricate a "returned nil" the method never actually did), so it updates the thread-local @raised instead of @returns and never touches the returns bucket at all.



68
69
70
71
72
73
74
75
76
77
# File 'lib/bulldogger/probe/method_stats.rb', line 68

def record_return(tp, raised:)
  local = thread_local
  if raised
    local.raised_exits += 1
    klass = RaiseTracker.instance.current_exception_class_name || "Object"
    local.raised[klass] += 1
  else
    local.returns.record(tp.return_value)
  end
end

#to_hObject



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/bulldogger/probe/method_stats.rb', line 79

def to_h
  merge!
  {
    "calls" => @calls,
    "raised_exits" => @raised_exits,
    "parameters" => @parameters,
    "params" => params_to_h,
    "returns" => @returns.to_h,
    "raised" => @raised,
    "callers" => callers_to_h
  }
end