CpuInspectCore

A Ruby gem that monitors CPU usage per core in the background and displays results as ASCII progress bars — designed for profiling Heroku dynos.

Runs a lightweight background thread, writes a rotating 1 MB JSON log, and exposes a single method you can call from a Rails console or bash at any time. On multi-dyno Heroku deployments the optional Redis backend aggregates all dynos into one view.


Contents


How it works

A background thread samples /proc/stat on Linux (Heroku) or top on macOS at a configurable interval (default 2 s). Each sample is:

  • stored in an in-process ring buffer for instant status reads
  • appended as a JSON line to a rotating local log (1 MB cap, keeps newest 50%)
  • optionally written to Redis tagged with the Heroku $DYNO identifier

CpuInspectCore.status reads the ring buffer and prints one progress bar per core:

=== web.1 @ 2026-04-15T10:00:02.341Z ===
cpu0  ||||||||||||||||||||||||                            48.2%
cpu1  idle
cpu2  ||||||||||||||||||||||||||||||||||||||||||||||||    95.4%
cpu3  |||                                                 6.1%

On a scaled Heroku deployment with the Redis backend enabled, calling status shows every live dyno in one shot — no SSH needed.


Installation

Add to your Gemfile:

gem "cpu-inspect-core"

For multi-dyno Redis support also add:

gem "redis", "~> 5.0"

Then:

bundle install

Or install globally:

gem install cpu-inspect-core

Requires Ruby 3.1 or higher. No other runtime dependencies.


Quick start

require "cpu_inspect_core"

CpuInspectCore.start          # launches background thread

sleep 5                       # let it collect a few samples

CpuInspectCore.status         # print progress bars
CpuInspectCore.tail(lines: 10) # print last 10 raw JSON log lines

CpuInspectCore.stop

From a Rails console:

CpuInspectCore.status

The gem auto-starts via its Railtie when Rails boots, so status is ready as soon as your app is up.


Rails integration

Drop a file at config/initializers/cpu_inspect_core.rb:

CpuInspectCore.configure do |c|
  c.interval       = 2.0    # seconds between samples
  c.idle_threshold = 2.0    # cores below this % are shown as "idle"
  c.bar_width      = 50     # character width of the progress bar
end

The Railtie calls CpuInspectCore.start automatically after the Rails logger is ready. Set CPU_INSPECT_CORE_DISABLED=1 to opt out (useful in test environments or during asset precompilation).

Puma with preload_app!

Threads do not survive fork. If you use preload_app! in config/puma.rb, add:

on_worker_boot do
  CpuInspectCore.start
end

Multi-dyno on Heroku (Redis backend)

By default each dyno writes only to its local /tmp file. To see all dynos in one status call, enable the Redis backend:

# config/initializers/cpu_inspect_core.rb
CpuInspectCore.configure do |c|
  c.backend = :redis
  # c.redis_url defaults to ENV["REDIS_URL"] — set automatically by Heroku Redis add-on
end

Each dyno writes its latest sample to a Redis key named cpu_inspect_core:<dyno> (e.g. cpu_inspect_core:web.1) with a 30-second TTL. When a dyno scales down or restarts its key expires automatically.

Viewing all dynos

From a one-off dyno or any Rails console session:

CpuInspectCore.status

Output on a 5-dyno deployment:

=== web.1 @ 2026-04-15T10:00:02.341Z ===
cpu0  ||||||||||||||||||||||||                            48.2%
cpu1  idle

=== web.2 @ 2026-04-15T10:00:02.198Z ===
cpu0  ||||||||||||||||||||||||||||||||||||||||||||||||    95.4%
cpu1  |||||||||||||||||||||||||||||||                     61.8%

=== web.3 @ 2026-04-15T10:00:02.512Z ===
cpu0  idle
cpu1  idle

=== web.4 @ 2026-04-15T10:00:02.089Z ===
cpu0  |||||||||||||||||||                                 38.7%
cpu1  ||||||||||||                                        24.1%

=== web.5 @ 2026-04-15T10:00:02.763Z ===
cpu0  |||||||||||||||||||||||||||||||||||||               73.0%
cpu1  |||||||||||||||||||||||||||||||||||||||||||||||     93.2%

This tells you how many physical vCPU cores each dyno has and how hard they are working — useful for choosing the right dyno type and size.

Redis configuration options

Option Default Description
redis_url ENV["REDIS_URL"] Redis connection URL
redis_key_prefix "cpu_inspect_core" Namespace for all Redis keys
redis_ttl 30 Seconds before a dead dyno's key expires

CLI

The cpu-inspect-core executable is available after installation.

bundle exec cpu-inspect-core [options]
Flag Description
--once Print status once and exit
--watch Refresh status continuously (clears screen each cycle)
--tail [N] Print last N lines from the local log file (default 20)
--interval SECS Override the sample interval
--log PATH Override the log file path

Examples:

# Print current CPU snapshot
bundle exec cpu-inspect-core --once

# Live dashboard that refreshes every 2 seconds
bundle exec cpu-inspect-core --watch

# Inspect the raw JSON log
bundle exec cpu-inspect-core --tail 30

# Run at a faster rate
bundle exec cpu-inspect-core --watch --interval 0.5

Configuration reference

All options are set via CpuInspectCore.configure:

CpuInspectCore.configure do |c|
  c.interval         = 2.0                         # sample interval in seconds
  c.log_path         = "/tmp/cpu_inspect_core.log"  # local log file (per dyno)
  c.log_max_bytes    = 1_048_576                   # rotate when log exceeds this (1 MB)
  c.idle_threshold   = 2.0                         # % below which a core shows as "idle"
  c.bar_width        = 50                          # progress bar width in characters
  c.backend          = :file                       # :file or :redis
  c.redis_url        = ENV["REDIS_URL"]            # Redis connection URL
  c.redis_key_prefix = "cpu_inspect_core"           # Redis key namespace
  c.redis_ttl        = 30                          # seconds before dyno key expires
end

The log path can also be set via environment variable: CPU_INSPECT_CORE_LOG=/path/to/log.


Development

git clone https://github.com/mykbren/cpu-inspect-core
cd cpu-inspect-core
bundle install
bundle exec rake test

Run a single test file:

bundle exec ruby -Ilib -Itest test/test_renderer.rb

Run a single test by name:

bundle exec ruby -Ilib -Itest test/test_renderer.rb -n test_cores_are_sorted_numerically_not_lexicographically

The test suite targets 100% line and branch coverage (verified by SimpleCov on every run).


License

MIT — Copyright (c) 2026 Mykyta Bren