Class: Rigor::Analysis::RunStats
- Inherits:
-
Object
- Object
- Rigor::Analysis::RunStats
- Defined in:
- lib/rigor/analysis/run_stats.rb
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_paths` output).
-
**Type universe** — RBS class/module declarations the analyser had visibility of, broken down by source: ‘project_sig` (declarations whose source file lives under the configured `signature_paths`) vs `bundled` (RBS core, stdlib libraries, gem-bundled RBS — everything outside the project’s own ‘sig/` tree).
-
**Gem source-walk** — the ADR-10 ‘dependencies.source_inference` catalogue. 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::Environment` entries restored from a cached blob (Marshal-loaded `RBS::Environment` loses real file-path attribution; every buffer reports `“<cached>”`). When every entry carries this sentinel the partition_classes routine returns `[0, total]` AND `attribution_available: false`, which the format routine consumes to suppress the misleading breakdown row.
"<cached>"
Instance Attribute Summary collapse
-
#gem_walk_classes ⇒ Object
readonly
Returns the value of attribute gem_walk_classes.
-
#gem_walk_gems ⇒ Object
readonly
Returns the value of attribute gem_walk_gems.
-
#peak_rss_bytes ⇒ Object
readonly
Returns the value of attribute peak_rss_bytes.
-
#rbs_attribution_available ⇒ Object
readonly
Returns the value of attribute rbs_attribution_available.
-
#rbs_classes_bundled ⇒ Object
readonly
Returns the value of attribute rbs_classes_bundled.
-
#rbs_classes_project_sig ⇒ Object
readonly
Returns the value of attribute rbs_classes_project_sig.
-
#rbs_classes_total ⇒ Object
readonly
Returns the value of attribute rbs_classes_total.
-
#target_files ⇒ Object
readonly
Returns the value of attribute target_files.
-
#wall_seconds ⇒ Object
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_paths` carries a real source file path (i.e. not the cached-sentinel marker).
-
.partition_classes(class_decl_paths:, signature_paths:) ⇒ Object
Computes ‘(project_sig, bundled)` counts from a frozen `Hash<class_name => source_path>` snapshot and the configured `signature_paths`.
-
.peak_rss_bytes ⇒ Object
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: "") ⇒ Object
Writes a human-facing rendering of the stats to ‘out` (typically `$stderr` from 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 ⇒ Object
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
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/rigor/analysis/run_stats.rb', line 42 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 ⇒ Object (readonly)
Returns the value of attribute gem_walk_classes.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def gem_walk_classes @gem_walk_classes end |
#gem_walk_gems ⇒ Object (readonly)
Returns the value of attribute gem_walk_gems.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def gem_walk_gems @gem_walk_gems end |
#peak_rss_bytes ⇒ Object (readonly)
Returns the value of attribute peak_rss_bytes.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def peak_rss_bytes @peak_rss_bytes end |
#rbs_attribution_available ⇒ Object (readonly)
Returns the value of attribute rbs_attribution_available.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def rbs_attribution_available @rbs_attribution_available end |
#rbs_classes_bundled ⇒ Object (readonly)
Returns the value of attribute rbs_classes_bundled.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def rbs_classes_bundled @rbs_classes_bundled end |
#rbs_classes_project_sig ⇒ Object (readonly)
Returns the value of attribute rbs_classes_project_sig.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def rbs_classes_project_sig @rbs_classes_project_sig end |
#rbs_classes_total ⇒ Object (readonly)
Returns the value of attribute rbs_classes_total.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def rbs_classes_total @rbs_classes_total end |
#target_files ⇒ Object (readonly)
Returns the value of attribute target_files.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 def target_files @target_files end |
#wall_seconds ⇒ Object (readonly)
Returns the value of attribute wall_seconds.
37 38 39 |
# File 'lib/rigor/analysis/run_stats.rb', line 37 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.
131 132 133 134 135 |
# File 'lib/rigor/analysis/run_stats.rb', line 131 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:) ⇒ Object
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.
115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/rigor/analysis/run_stats.rb', line 115 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 ⇒ Object
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.
66 67 68 69 70 71 72 73 74 |
# File 'lib/rigor/analysis/run_stats.rb', line 66 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
90 91 92 93 94 95 96 97 |
# File 'lib/rigor/analysis/run_stats.rb', line 90 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
76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/rigor/analysis/run_stats.rb', line 76 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: "") ⇒ Object
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.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/rigor/analysis/run_stats.rb', line 142 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 ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/rigor/analysis/run_stats.rb', line 175 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 |