Module: Insika::Vitals
- Defined in:
- lib/insika/vitals.rb
Overview
Process vitals. One cheap read-only answer to "which process is this, how long has it been up, how much memory does it hold, and what is the Ruby heap doing?" It touches NO store, allocates nothing meaningful, and knows nothing about soaks — a general operator surface the soak happens to be the first consumer of. Exposed at GET /v1/vitals (operator only).
Constant Summary collapse
- STARTED_AT =
Stamped when this file is first required: the process's own start, which is what "uptime" has to mean (Falcon's controller start is not this worker's). Per-process, not per-App, so a worker respawn under a live container keeps its real age and a new pid reads as a new clock.
Time.now.utc
- GC_KEYS =
The subset of GC.stat that answers "is the Ruby heap growing, or is this the allocator?" — the difference between a leak and fragmentation, which is the first fork of any leak hunt .
%i[heap_live_slots heap_free_slots heap_allocated_pages total_allocated_objects total_freed_objects major_gc_count minor_gc_count malloc_increase_bytes oldmalloc_increase_bytes].freeze
Class Method Summary collapse
-
.db_bytes(path) ⇒ Object
SQLite file + WAL + shm bytes.
- .gc_stat ⇒ Object
-
.rss_bytes ⇒ Object
Resident set size in bytes.
-
.snapshot(executor: nil, db_path: nil, env: ENV) ⇒ Object
-> Hash with STRING keys (it is a JSON body, not an internal value object).
Class Method Details
.db_bytes(path) ⇒ Object
SQLite file + WAL + shm bytes. -> { "db" =>, "wal" =>, "shm" => } | nil.
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/insika/vitals.rb', line 72 def db_bytes(path) return nil if path.nil? || path.to_s.empty? { "db" => File.size(path.to_s), "wal" => File.exist?("#{path}-wal") ? File.size("#{path}-wal") : 0, "shm" => File.exist?("#{path}-shm") ? File.size("#{path}-shm") : 0 } rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR nil end |
.gc_stat ⇒ Object
66 67 68 69 |
# File 'lib/insika/vitals.rb', line 66 def gc_stat stat = GC.stat GC_KEYS.each_with_object({}) { |key, acc| acc[key.to_s] = stat[key] } end |
.rss_bytes ⇒ Object
Resident set size in bytes. Linux first (/proc/self/status VmRSS) —
production is a Linux container; macOS/dev fall back to ps. Neither
readable -> nil, NEVER a guess: a fabricated RSS would silently pass an
envelope.
52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/insika/vitals.rb', line 52 def rss_bytes if File.file?("/proc/self/status") match = File.read("/proc/self/status", encoding: "ASCII-8BIT")[/^VmRSS:\s+(\d+)\s*kB/, 1] return Integer(match) * 1024 if match end out = `ps -o rss= -p #{Process.pid} 2>/dev/null`.strip return Integer(out) * 1024 unless out.empty? nil rescue StandardError nil end |
.snapshot(executor: nil, db_path: nil, env: ENV) ⇒ Object
-> Hash with STRING keys (it is a JSON body, not an internal value
object). executor:/db_path: are optional — absent, the body simply
omits those readings. env: is injected for specs.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/insika/vitals.rb', line 30 def snapshot(executor: nil, db_path: nil, env: ENV) { "boot_id" => EnvSchema.read("INSIKA_BOOT_ID", env).to_s, "pid" => Process.pid, "started_at" => STARTED_AT.iso8601, "uptime_s" => (Time.now.utc - STARTED_AT).round, "version" => Insika::VERSION, "ruby" => RUBY_DESCRIPTION, "yjit" => (defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?), "rss_bytes" => rss_bytes, "gc" => gc_stat, "threads" => Thread.list.size, "in_flight" => executor&.in_flight&.size, "db_bytes" => db_bytes(db_path), "at" => Time.now.utc.iso8601 } end |