Class: Rigor::Analysis::RunStats
- Inherits:
-
Object
- Object
- Rigor::Analysis::RunStats
- Defined in:
- lib/rigor/analysis/run_stats.rb,
sig/rigor.rbs
Overview
End-of-run telemetry for the rigor check CLI's --stats output. Captures four cheap-to-measure groups:
- Check targets — the Ruby files the analyser actually walks for diagnostics (
expand_pathsoutput). - Type universe — RBS class/module declarations the analyser had visibility of, broken down by
source:
project_sig(declarations whose source file lives under the configuredsignature_paths) vsbundled(RBS core, stdlib libraries, gem-bundled RBS — everything outside the project's ownsig/tree). - Gem source-walk — the ADR-10
dependencies.source_inferencecatalogue. Reports the class count and the number of opt-in gems contributing. - Process — wall-clock seconds + peak resident set size.
The split between "check targets" and "type universe" makes explicit that the analyser's diagnostic
surface is bounded by the user-controlled paths: configuration; the (typically much larger) RBS
class universe is symbol-discovery, not a diagnostic surface.
Stats collection is intentionally cheap: wall + RSS are single syscalls, target file count is already
in expand_paths, gem source-walk uses Index#class_to_gem.size, and the RBS class breakdown walks
class_decl_paths (a frozen Hash<String, String> populated once per environment by the RBS loader;
~1000-2000 entries × one String#start_with?).
Constant Summary collapse
- CACHED_SENTINEL =
Source-attribution sentinel produced by
RBS::Environmententries restored from a cached blob (Marshal-loadedRBS::Environmentloses real file-path attribution; every buffer reports"<cached>"). When every entry carries this sentinel the partition_classes routine returns[0, total]ANDattribution_available: false, which the format routine consumes to suppress the misleading breakdown row. "<cached>"
Instance Attribute Summary collapse
-
#gem_walk_classes ⇒ Integer
readonly
Returns the value of attribute gem_walk_classes.
-
#gem_walk_gems ⇒ Integer
readonly
Returns the value of attribute gem_walk_gems.
-
#peak_rss_bytes ⇒ Integer?
readonly
Returns the value of attribute peak_rss_bytes.
-
#rbs_attribution_available ⇒ Boolean
readonly
Returns the value of attribute rbs_attribution_available.
-
#rbs_classes_bundled ⇒ Integer
readonly
Returns the value of attribute rbs_classes_bundled.
-
#rbs_classes_project_sig ⇒ Integer
readonly
Returns the value of attribute rbs_classes_project_sig.
-
#rbs_classes_total ⇒ Integer
readonly
Returns the value of attribute rbs_classes_total.
-
#target_files ⇒ Integer
readonly
Returns the value of attribute target_files.
-
#wall_seconds ⇒ Float
readonly
Returns the value of attribute wall_seconds.
Class Method Summary collapse
-
.attribution_available?(class_decl_paths:) ⇒ Boolean
True when at least one entry in
class_decl_pathscarries a real source file path (i.e. not the cached-sentinel marker). -
.partition_classes(class_decl_paths:, signature_paths:) ⇒ [Integer, Integer]
Computes
(project_sig, bundled)counts from a frozenHash<class_name => source_path>snapshot and the configuredsignature_paths. -
.peak_rss_bytes ⇒ Integer?
Reports the process's resident set size in bytes.
- .read_rss_via_ps ⇒ Object
- .read_vmhwm_from_proc ⇒ Object
Instance Method Summary collapse
-
#format(out, prefix: "") ⇒ void
Writes a human-facing rendering of the stats to
out(typically$stderrfrom the CLI). -
#initialize(wall_seconds:, peak_rss_bytes:, target_files:, rbs_classes_total:, rbs_classes_project_sig:, rbs_classes_bundled:, gem_walk_classes:, gem_walk_gems:, rbs_attribution_available: true) ⇒ RunStats
constructor
rubocop:disable Metrics/ParameterLists.
- #to_h ⇒ Hash[Symbol, untyped]
Constructor Details
#initialize(wall_seconds:, peak_rss_bytes:, target_files:, rbs_classes_total:, rbs_classes_project_sig:, rbs_classes_bundled:, gem_walk_classes:, gem_walk_gems:, rbs_attribution_available: true) ⇒ RunStats
rubocop:disable Metrics/ParameterLists
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/rigor/analysis/run_stats.rb', line 32 def initialize(wall_seconds:, peak_rss_bytes:, # rubocop:disable Metrics/ParameterLists target_files:, rbs_classes_total:, rbs_classes_project_sig:, rbs_classes_bundled:, gem_walk_classes:, gem_walk_gems:, rbs_attribution_available: true) @wall_seconds = wall_seconds @peak_rss_bytes = peak_rss_bytes @target_files = target_files @rbs_classes_total = rbs_classes_total @rbs_classes_project_sig = rbs_classes_project_sig @rbs_classes_bundled = rbs_classes_bundled @gem_walk_classes = gem_walk_classes @gem_walk_gems = gem_walk_gems @rbs_attribution_available = rbs_attribution_available freeze end |
Instance Attribute Details
#gem_walk_classes ⇒ Integer (readonly)
Returns the value of attribute gem_walk_classes.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def gem_walk_classes @gem_walk_classes end |
#gem_walk_gems ⇒ Integer (readonly)
Returns the value of attribute gem_walk_gems.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def gem_walk_gems @gem_walk_gems end |
#peak_rss_bytes ⇒ Integer? (readonly)
Returns the value of attribute peak_rss_bytes.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def peak_rss_bytes @peak_rss_bytes end |
#rbs_attribution_available ⇒ Boolean (readonly)
Returns the value of attribute rbs_attribution_available.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def rbs_attribution_available @rbs_attribution_available end |
#rbs_classes_bundled ⇒ Integer (readonly)
Returns the value of attribute rbs_classes_bundled.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def rbs_classes_bundled @rbs_classes_bundled end |
#rbs_classes_project_sig ⇒ Integer (readonly)
Returns the value of attribute rbs_classes_project_sig.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def rbs_classes_project_sig @rbs_classes_project_sig end |
#rbs_classes_total ⇒ Integer (readonly)
Returns the value of attribute rbs_classes_total.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def rbs_classes_total @rbs_classes_total end |
#target_files ⇒ Integer (readonly)
Returns the value of attribute target_files.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def target_files @target_files end |
#wall_seconds ⇒ Float (readonly)
Returns the value of attribute wall_seconds.
27 28 29 |
# File 'lib/rigor/analysis/run_stats.rb', line 27 def wall_seconds @wall_seconds end |
Class Method Details
.attribution_available?(class_decl_paths:) ⇒ Boolean
True when at least one entry in class_decl_paths carries a real source file path (i.e. not the
cached-sentinel marker). Used by callers to decide whether the project_sig / bundled split is
meaningful.
112 113 114 115 116 |
# File 'lib/rigor/analysis/run_stats.rb', line 112 def self.attribution_available?(class_decl_paths:) return false if class_decl_paths.empty? class_decl_paths.each_value.any? { |path| path != CACHED_SENTINEL } end |
.partition_classes(class_decl_paths:, signature_paths:) ⇒ [Integer, Integer]
Computes (project_sig, bundled) counts from a frozen Hash<class_name => source_path> snapshot and
the configured signature_paths. project_sig is the count of classes whose source path begins
with any of the signature path prefixes (after expansion to absolute paths); bundled is the
remainder.
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rigor/analysis/run_stats.rb', line 97 def self.partition_classes(class_decl_paths:, signature_paths:) prefixes = Array(signature_paths).map { |p| File.(p.to_s) } return [0, class_decl_paths.size] if prefixes.empty? project = 0 class_decl_paths.each_value do |path| = File.(path) project += 1 if prefixes.any? { |prefix| .start_with?("#{prefix}/") || == prefix } end [project, class_decl_paths.size - project] end |
.peak_rss_bytes ⇒ Integer?
Reports the process's resident set size in bytes. Source ordering: /proc/self/status (Linux — reads
VmHWM:, the peak RSS the kernel records) first; otherwise ps -o rss= -p <pid> (macOS / BSD —
reports CURRENT RSS, the closest universally-available proxy). Returns nil when neither route works
so the formatter can render unavailable instead of misleading zero.
53 54 55 56 57 58 59 60 61 |
# File 'lib/rigor/analysis/run_stats.rb', line 53 def self.peak_rss_bytes from_proc = read_vmhwm_from_proc return from_proc unless from_proc.nil? from_ps = read_rss_via_ps return from_ps unless from_ps.nil? nil end |
.read_rss_via_ps ⇒ Object
77 78 79 80 81 82 83 84 |
# File 'lib/rigor/analysis/run_stats.rb', line 77 def self.read_rss_via_ps out = `ps -o rss= -p #{Process.pid} 2>/dev/null`.strip return nil if out.empty? Integer(out) * 1024 rescue StandardError nil end |
.read_vmhwm_from_proc ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rigor/analysis/run_stats.rb', line 63 def self.read_vmhwm_from_proc return nil unless File.readable?("/proc/self/status") File.foreach("/proc/self/status") do |line| next unless line.start_with?("VmHWM:") kb_token = line.split.find { |token| token.match?(/\A\d+\z/) } return Integer(kb_token) * 1024 if kb_token end nil rescue StandardError nil end |
Instance Method Details
#format(out, prefix: "") ⇒ void
This method returns an undefined value.
Writes a human-facing rendering of the stats to out (typically $stderr from the CLI). Format is
intentionally plain text — JSON consumers should parse the structured output of rigor check --format=json and consult stats there.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/rigor/analysis/run_stats.rb', line 121 def format(out, prefix: "") out.puts("#{prefix}Check targets") out.puts("#{prefix} Ruby source files: #{@target_files}") out.puts("#{prefix}Type universe (symbol discovery; not analyzed for diagnostics)") out.puts("#{prefix} RBS classes available: #{@rbs_classes_total}") if @rbs_classes_total.zero? # A normal run always loads the bundled core+stdlib RBS (~1300+ classes), so zero means the # environment failed to build (most often a duplicate declaration in `signature_paths:`) and fell # back to empty — type coverage is then near-useless but the run still "succeeds". Surface it # loudly so a broken setup is not read as a clean analysis (the 20260620 field trial: redmine # would otherwise wire a 0-coverage check into CI). out.puts("#{prefix} WARNING: the RBS environment is empty — it failed to build or loaded no") out.puts("#{prefix} signatures, so type coverage is severely limited (most diagnostics") out.puts("#{prefix} and coverage cannot fire). Usually a duplicate declaration in") out.puts("#{prefix} `signature_paths:` — fix it and re-run; the rigor-doctor skill helps.") elsif @rbs_attribution_available out.puts("#{prefix} project sig/: #{@rbs_classes_project_sig}") out.puts("#{prefix} bundled (core+stdlib+gems): #{@rbs_classes_bundled}") elsif @rbs_classes_total.positive? out.puts("#{prefix} (source attribution unavailable on cache-hit runs; --no-cache surfaces it)") end if @gem_walk_gems.positive? out.puts("#{prefix} Gem source-walk classes: #{@gem_walk_classes} " \ "(across #{@gem_walk_gems} #{@gem_walk_gems == 1 ? 'gem' : 'gems'} " \ "via dependencies.source_inference)") end out.puts("#{prefix}Process") out.puts("#{prefix} Wall time: #{Kernel.format('%.2fs', @wall_seconds)}") out.puts("#{prefix} Memory peak: #{format_bytes(@peak_rss_bytes)}") end |
#to_h ⇒ Hash[Symbol, untyped]
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/rigor/analysis/run_stats.rb', line 152 def to_h { target_files: @target_files, rbs_classes_total: @rbs_classes_total, rbs_classes_project_sig: @rbs_classes_project_sig, rbs_classes_bundled: @rbs_classes_bundled, rbs_attribution_available: @rbs_attribution_available, gem_walk_classes: @gem_walk_classes, gem_walk_gems: @gem_walk_gems, wall_seconds: @wall_seconds, peak_rss_bytes: @peak_rss_bytes } end |