Module: Rigor::Inference::BudgetTrace
- Defined in:
- lib/rigor/inference/budget_trace.rb
Overview
Opt-in counters for the hard-coded inference cutoffs — the “budget” guards that silently return ‘Dynamic` / `nil` / a fallback bound rather than emitting a diagnostic. These are the operative cutoffs in the engine today (the configurable `budgets:` table in docs/type-specification/inference-budgets.md is not yet wired); counting how often each fires on a real project is the only way to see where inference actually stops.
Three categories, one per guard site:
-
RECURSION_GUARD — ‘ExpressionTyper#infer_user_method_return` detected a `(receiver, method)` cycle and returned `Dynamic` (the de-facto recursion-depth budget, effective depth 1).
-
ANCESTOR_WALK_LIMIT — ‘resolve_user_def_through_ancestors` hit the 100-node BFS cap and gave up resolving the self-call.
-
HKT_FUEL_EXHAUSTED — ‘HktReducer` ran out of its reduction fuel budget and unwound to `app.bound`.
Enabled only when ‘RIGOR_BUDGET_TRACE` is set (to any non-empty value) in the environment, or via BudgetTrace.enable! in tests. When disabled, BudgetTrace.hit is a single boolean check and returns immediately, so normal runs pay nothing.
Counters are process-global (Mutex-guarded) so they aggregate across threads, but they do NOT cross ‘fork` boundaries — run `rigor check –workers 0` to keep all inference in one process when collecting a trace.
Constant Summary collapse
- RECURSION_GUARD =
:recursion_guard- ANCESTOR_WALK_LIMIT =
:ancestor_walk_limit- HKT_FUEL_EXHAUSTED =
:hkt_fuel_exhausted- CATEGORIES =
[RECURSION_GUARD, ANCESTOR_WALK_LIMIT, HKT_FUEL_EXHAUSTED].freeze
- UNION_ARITY =
Distribution (histogram) categories — read-only observations of a value’s size at a site, used to choose budget defaults from an observed tail rather than a guess (ADR-41 WD3 / Slice 2a). No cap is enforced; these only record. ‘UNION_ARITY` is the member count of every `Type::Union` that `Combinator.union` produces — the distribution the `union_size` budget default should be set from.
:union_arity- DISTRIBUTION_CATEGORIES =
[UNION_ARITY].freeze
Class Method Summary collapse
- .disable! ⇒ Object
-
.distribution(category) ⇒ Object
Frozen ‘=> count` histogram for a distribution category.
-
.enable! ⇒ Object
Test / programmatic toggles.
- .enabled? ⇒ Boolean
-
.hit(category) ⇒ Object
Records one firing of ‘category`.
-
.observe(category, value) ⇒ Object
Records one observation of ‘value` (an Integer size) into `category`’s histogram.
-
.percentile(hist, total, fraction) ⇒ Object
Nearest-rank percentile over a ‘=> count` histogram without materialising the full sample.
- .reset ⇒ Object
-
.snapshot ⇒ Object
Frozen snapshot of the current counts, every known category present (zero-filled) so consumers can render a stable table.
-
.summarize(category, over: []) ⇒ Object
Summary of a distribution category: total observation count, max observed value, selected percentiles, and how many observations met or exceeded each threshold in ‘over`.
Class Method Details
.disable! ⇒ Object
67 68 69 |
# File 'lib/rigor/inference/budget_trace.rb', line 67 def disable! @enabled = false end |
.distribution(category) ⇒ Object
Frozen ‘=> count` histogram for a distribution category.
96 97 98 |
# File 'lib/rigor/inference/budget_trace.rb', line 96 def distribution(category) @mutex.synchronize { @distributions[category].dup.freeze } end |
.enable! ⇒ Object
Test / programmatic toggles. Production enablement is the ‘RIGOR_BUDGET_TRACE` env var read once at load time.
63 64 65 |
# File 'lib/rigor/inference/budget_trace.rb', line 63 def enable! @enabled = true end |
.enabled? ⇒ Boolean
57 58 59 |
# File 'lib/rigor/inference/budget_trace.rb', line 57 def enabled? @enabled end |
.hit(category) ⇒ Object
Records one firing of ‘category`. No-op (one boolean check) when tracing is disabled.
73 74 75 76 77 |
# File 'lib/rigor/inference/budget_trace.rb', line 73 def hit(category) return unless @enabled @mutex.synchronize { @counts[category] += 1 } end |
.observe(category, value) ⇒ Object
Records one observation of ‘value` (an Integer size) into `category`’s histogram. No-op (one boolean check) when disabled.
89 90 91 92 93 |
# File 'lib/rigor/inference/budget_trace.rb', line 89 def observe(category, value) return unless @enabled @mutex.synchronize { @distributions[category][value] += 1 } end |
.percentile(hist, total, fraction) ⇒ Object
Nearest-rank percentile over a ‘=> count` histogram without materialising the full sample.
119 120 121 122 123 124 125 126 127 |
# File 'lib/rigor/inference/budget_trace.rb', line 119 def percentile(hist, total, fraction) rank = (fraction * total).ceil cumulative = 0 hist.keys.sort.each do |value| cumulative += hist[value] return value if cumulative >= rank end hist.keys.max end |
.reset ⇒ Object
129 130 131 132 133 134 |
# File 'lib/rigor/inference/budget_trace.rb', line 129 def reset @mutex.synchronize do @counts.clear @distributions.clear end end |
.snapshot ⇒ Object
Frozen snapshot of the current counts, every known category present (zero-filled) so consumers can render a stable table.
81 82 83 84 85 |
# File 'lib/rigor/inference/budget_trace.rb', line 81 def snapshot @mutex.synchronize do CATEGORIES.to_h { |category| [category, @counts[category]] }.freeze end end |
.summarize(category, over: []) ⇒ Object
Summary of a distribution category: total observation count, max observed value, selected percentiles, and how many observations met or exceeded each threshold in ‘over`. Percentiles use the nearest-rank method over the expanded sample.
104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/rigor/inference/budget_trace.rb', line 104 def summarize(category, over: []) hist = distribution(category) total = hist.values.sum return { count: 0, max: 0, percentiles: {}, over: over.to_h { |t| [t, 0] } } if total.zero? sorted = hist.keys.sort { count: total, max: sorted.last, percentiles: { p50: percentile(hist, total, 0.50), p90: percentile(hist, total, 0.90), p99: percentile(hist, total, 0.99) }, over: over.to_h { |t| [t, hist.sum { |value, n| value >= t ? n : 0 }] } } end |