fast-prometheus
A fiber-native Prometheus client for modern Ruby, built on the socketry/async
ecosystem: HTTP middleware for Protocol::HTTP (Falcon-native) and Rack, safe
to share one Registry across OS threads and fibers by default, native
histograms as a first-class metric type, protobuf scrape exposition, and OTLP
export over gRPC (async-grpc) and HTTP.
Not a fork of prometheus/client_ruby — a new gem that uses it as the
reference for supported surface.
Installation
gem install fast-prometheusOr add to your Gemfile:
gem "fast-prometheus"require "fast/prometheus" loads only the core, with no IO dependencies. Protobuf exposition and OTLP export are built on fast-protowire rather than google-protobuf, so no native protobuf runtime is loaded either. The
opt-in surfaces and what each pulls in are listed in
the require-path reference.
Quickstart
require "fast/prometheus"
require "fast/prometheus/formats/text"
registry = Fast::Prometheus::Registry.new
requests = registry.counter(:http_requests_total, docstring: "Total HTTP requests", labels: %i[method path])
duration = registry.histogram(:request_duration_seconds, docstring: "Request duration", labels: [:method])
requests.increment(labels: { method: "GET", path: "/" })
requests.increment(by: 5, labels: { method: "POST", path: "/api" })
duration.observe(0.042, labels: { method: "GET" })
duration.observe(0.018, labels: { method: "GET" })
puts Fast::Prometheus::Formats::Text.render(registry.collect)Run with bundle exec ruby -I lib from the repo root. For running this
under Falcon and scraping it with a real Prometheus, see
the Falcon tutorial.
Documentation
Tutorials
- Tutorial: instrument a Falcon app — build a Falcon app instrumented with fast-prometheus and scrape it with Prometheus.
How-to guides
-
How to instrument a Rack app — add the two Rack middleware to a
config.ru, Rails app or Puma host. -
How to instrument a falcon.rb service — run the Falcon-native middleware under
falcon hostor a threaded launcher. -
How to serve metrics on a separate port — run
/metricson its ownAsync::HTTP::Server, off the app's port. - How to scrape native histograms — configure Prometheus to negotiate protobuf so native histograms aren't dropped.
-
How to share one registry across boot paths — use
fetch_or_registerso multiple boot paths can construct the same metric safely. - How to export over OTLP — push metrics to an OTLP collector over gRPC or HTTP.
- How to verify a release build — run the integration harness against a packaged gem before tagging.
Reference
-
Reference: registry and metrics — every public class and method on
Registryand the five metric types. -
Reference: exposition formats and HTTP middleware —
Formats::Text,Formats::Protobuf,Middleware::Exporter,Middleware::Instrumentation. -
Reference: OTLP export —
OTLP::HTTPExporter,OTLP::GRPCExporter,OTLP::Push,OTLP::Mapper. - Reference: require paths and dependencies — what each require path loads and what it pulls in.
Explanation
- Concurrency model — the locking contract and what it guarantees under threads and fibers.
- Native histograms — what a native histogram is and why it's protobuf-only.
-
Design: why a new gem — why fast-prometheus exists instead of a
client_rubyfork. - Benchmarks — scrape and observe benchmarks against prometheus-client and google-protobuf, and how to reproduce them.
Concurrency
A Registry is safe to share across every OS thread of a process, and
remains safe across fibers within a thread — correct under Falcon
--threaded --count N, where the N OS threads share one module-level
Fast::Prometheus.registry. Falcon --forked mode gives each process its
own registry, so a single scrape only sees the metrics of the process that
handled it; aggregating across processes is out of scope. See
the concurrency model for the full guarantee.
Performance
Single process, locked, thread-safe by default (benchmark-ips and allocation
comparisons against prometheus-client and google-protobuf; see
the benchmarks page for the full runs and conditions):
- A protobuf scrape of 36,000 series renders in 24 objects, where the
google-protobufencoder needed 3.5 million Ruby objects and 504k native arenas, and in a hundredth of the GC time; taking the snapshot it reads is 1 ms and one Hash per metric - The text renderer allocates 66x fewer objects than prometheus-client's formatter for the same body, and renders it 3.1x faster at 36,000 series
- Bound counter and gauge writes allocate nothing and are 1.45x faster than prometheus-client's; histogram observe is 2.9x faster and summary observe 3.1x
- Native histogram observe runs at 1.2M i/s with no prometheus-client equivalent
Development
bundle exec sus
bundle exec rubocop
E2E_REQUIRED=1 ./integration/run
BENCH_QUICK=1 bundle exec ruby benchmark/observe.rb # every metric operation vs prometheus-client
BENCH_QUICK=1 bundle exec ruby benchmark/exposition.rb # the scrape suiteSee how to verify a release build for the pre-release rule.