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 |
# 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_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
163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/rigor/analysis/run_stats.rb', line 163 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 |