Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [OpenAPI] Add `openapi:validate` Rake task for OpenAPI tags validation (#163).
- [Logger] Add `config.log_redact_keys=` for redacting structured log context.
- [Telemetry] Add `Rage::Telemetry::Capacity` to provide read-only access to metrics describing the server's current load and resource utilization.

### Fixed

Expand Down
47 changes: 47 additions & 0 deletions lib/rage/telemetry/capacity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module Rage::Telemetry
##
# The `Rage::Telemetry::Capacity` module provides read-only access to
# metrics describing the server's current load and resource utilization.
# Example: how much work is queued up, and how close the server is to its limits.
#
# Unlike spans, capacity metrics aren't tied to a specific operation or
# event, they reflect the server state at the moment they are read, and
# are meant to be sampled periodically rather than triggered by handlers.
#
# Combine with {Rage::Telemetry.every} to report a metric on a fixed
# interval:
#
# Rage::Telemetry.every(1000) do
# MyMetrics.gauge("server.queued_connections", Rage::Telemetry::Capacity.queued_connections)
# end
#
# # Available Metrics
#
# | ---------- Method -------|--------Description-------- |
# | `.queued_connections` | The number of established connections currently waiting in the kernel's accept queue, across the server listening sockets |
#
# @see Rage::Telemetry.every
#
module Capacity
class << self
# Returns the number of established connections currently waiting in the
# kernel accept queue for the server listening sockets. This is the
# count of clients that have already completed the TCP handshake but
# haven't yet been picked up by the application via `accept()`.
#
# A value that stays close to the configured backlog limit is a sign the
# server isn't accepting connections fast enough to keep up with incoming
# traffic.
#
# @return [Integer] the accept-queue depth
def queued_connections
accept_queue_depth = Iodine::Perf.queued_connections
raise NotImplementedError if accept_queue_depth.nil?

accept_queue_depth
end
end
end
end
1 change: 1 addition & 0 deletions lib/rage/telemetry/telemetry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ def success?

require_relative "tracer"
require_relative "handler"
require_relative "capacity"
Dir["#{__dir__}/spans/*.rb"].each { |span| require_relative span }
2 changes: 1 addition & 1 deletion rage.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Gem::Specification.new do |spec|

spec.add_dependency "thor", "~> 1.0"
spec.add_dependency "rack", "< 4"
spec.add_dependency "rage-iodine", "~> 5.5"
spec.add_dependency "rage-iodine", "~> 5.6"
spec.add_dependency "zeitwerk", "~> 2.6"
spec.add_dependency "rack-test", "~> 2.1"
spec.add_dependency "rake", ">= 12.0"
Expand Down
56 changes: 56 additions & 0 deletions spec/telemetry/capacity_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

RSpec.describe Rage::Telemetry::Capacity do
describe ".queued_connections" do
before do
Fiber.set_scheduler(Rage::FiberScheduler.new)
end

after do
Fiber.set_scheduler(nil)
end

it "reports the accept-queue depth of the server's listeners" do
require "socket"

s = TCPServer.new("127.0.0.1", 0)
port = s.addr[1]
s.close
result = nil

Iodine.workers = 1
Iodine.on_state(:on_start) do
Iodine.listen(port: port, handler: -> { [200, {}, ["ok"]] })
socks = 5.times.map { TCPSocket.new("127.0.0.1", port) }
Iodine.run { result = described_class.queued_connections }
socks.each(&:close)
Iodine.run { Iodine.stop }
end
Iodine.start

expect(result).to eq(5)
end

it "is callable from within the reactor via .every" do
require "socket"

s = TCPServer.new("127.0.0.1", 0)
port = s.addr[1]
s.close
result = nil

Iodine.workers = 1
Iodine.on_state(:on_start) do
Iodine.listen(port: port, handler: -> { [200, {}, ["ok"]] })
Rage::Telemetry.every(5) do
Iodine.run { result = described_class.queued_connections }
Iodine.stop
end
Iodine.run_after(200) { Iodine.stop }
end
Iodine.start

expect(result).to be_a(Integer)
end
end
end