From 72c42ef93c77cc127695ddc4d9a50b5d9852a826 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 11 Mar 2026 15:17:14 +1300 Subject: [PATCH] Add support for `async-utilization`. --- examples/utilization/falcon.rb | 54 ++++++++++++++++++++++++++++++++ examples/utilization/gems.rb | 7 +++++ falcon.gemspec | 1 + lib/falcon/environment/server.rb | 2 +- lib/falcon/server.rb | 44 +++++++++++++------------- releases.md | 1 + 6 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 examples/utilization/falcon.rb create mode 100644 examples/utilization/gems.rb diff --git a/examples/utilization/falcon.rb b/examples/utilization/falcon.rb new file mode 100644 index 00000000..2382197b --- /dev/null +++ b/examples/utilization/falcon.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env async-service +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Samuel Williams. + +require "falcon/environment/server" +require "async/service/supervisor/supervised" +require "async/service/supervisor/environment" +require "async/service/supervisor/utilization_monitor" + +# A simple Rack application that demonstrates utilization monitoring. +class SimpleApp + def call(env) + # Simulate some work + sleep(rand * 0.1) + + return [200, {"content-type" => "text/plain"}, ["Hello, World!"]] + end +end + +service "web" do + include Falcon::Environment::Server + include Async::Service::Supervisor::Supervised + + # Define the middleware stack for this server + middleware do + Falcon::Server.middleware(SimpleApp.new, verbose: false, cache: false) + end + + # Define the utilization schema for this service + utilization_schema do + { + connections_total: :u64, + connections_active: :u32, + requests_total: :u64, + requests_active: :u32, + } + end +end + +service "supervisor" do + include Async::Service::Supervisor::Environment + + # Configure the utilization monitor + monitors do + [ + Async::Service::Supervisor::UtilizationMonitor.new( + path: File.expand_path("utilization.shm", __dir__), + interval: 5.0 + ) + ] + end +end diff --git a/examples/utilization/gems.rb b/examples/utilization/gems.rb new file mode 100644 index 00000000..a00791b6 --- /dev/null +++ b/examples/utilization/gems.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "falcon", path: "../../" +gem "async-service-supervisor", "~> 0.12" +gem "async-utilization", "~> 0.3" diff --git a/falcon.gemspec b/falcon.gemspec index c9768acf..b15bc10b 100644 --- a/falcon.gemspec +++ b/falcon.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-http", "~> 0.75" spec.add_dependency "async-http-cache", "~> 0.4" spec.add_dependency "async-service", "~> 0.19" + spec.add_dependency "async-utilization", "~> 0.3" spec.add_dependency "bundler" spec.add_dependency "localhost", "~> 1.1" spec.add_dependency "openssl", ">= 3.0" diff --git a/lib/falcon/environment/server.rb b/lib/falcon/environment/server.rb index 43140959..628a8393 100644 --- a/lib/falcon/environment/server.rb +++ b/lib/falcon/environment/server.rb @@ -72,7 +72,7 @@ def client_endpoint # @parameter endpoint [IO::Endpoint] The endpoint to bind to. # @returns [Falcon::Server] The server instance. def make_server(endpoint) - Falcon::Server.new(self.middleware, endpoint, protocol: self.endpoint.protocol, scheme: self.endpoint.scheme) + Falcon::Server.new(self.middleware, endpoint, protocol: self.endpoint.protocol, scheme: self.endpoint.scheme, utilization_registry: self[:utilization_registry]) end end end diff --git a/lib/falcon/server.rb b/lib/falcon/server.rb index c8db443b..87725041 100644 --- a/lib/falcon/server.rb +++ b/lib/falcon/server.rb @@ -9,6 +9,7 @@ require "protocol/http/content_encoding" require "async/http/cache" +require "async/utilization" require_relative "middleware/verbose" require "protocol/rack" @@ -37,38 +38,35 @@ def self.middleware(rack_app, verbose: false, cache: true) end # Initialize the server and set up statistics tracking. - def initialize(...) - super + # + # @parameter utilization_registry [Registry, nil] The utilization registry to use for metrics tracking. + # If nil, a new registry instance is created. + def initialize(*arguments, utilization_registry: nil, **options) + super(*arguments, **options) - @accept_count = 0 - @connection_count = 0 + utilization_registry ||= Async::Utilization::Registry.new - @request_count = 0 - @active_count = 0 + # Get metric references for utilization tracking: + @connections_total_metric = utilization_registry.metric(:connections_total) + @connections_active_metric = utilization_registry.metric(:connections_active) + @requests_total_metric = utilization_registry.metric(:requests_total) + @requests_active_metric = utilization_registry.metric(:requests_active) end - attr :request_count - attr :accept_count - attr :connect_count - # Accept a new connection and track connection statistics. def accept(...) - @accept_count += 1 - @connection_count += 1 - - super - ensure - @connection_count -= 1 + @connections_total_metric.increment + @connections_active_metric.track do + super + end end # Handle a request and track request statistics. def call(...) - @request_count += 1 - @active_count += 1 - - super - ensure - @active_count -= 1 + @requests_total_metric.increment + @requests_active_metric.track do + super + end end # Generates a human-readable string representing the current statistics. @@ -83,7 +81,7 @@ def call(...) # # @returns [String] A string representing the current statistics. def statistics_string - "C=#{format_count @connection_count}/#{format_count @accept_count} R=#{format_count @active_count}/#{format_count @request_count}" + "C=#{format_count @connections_active_metric.value}/#{format_count @connections_total_metric.value} R=#{format_count @requests_active_metric.value}/#{format_count @requests_total_metric.value}" end private diff --git a/releases.md b/releases.md index e1a7fde0..6d711688 100644 --- a/releases.md +++ b/releases.md @@ -5,6 +5,7 @@ - **Breaking**: Drop dependency on `async-container-supervisor`, you should migrate to `async-service-supervisor` instead. - **Breaking**: Remove support for legacy environments, including `Falcon::Configuration`, now using `Async::Service::Configuration` directly. - **Breaking**: `bake falcon:supervisor:restart` removed – superceeded by `async:service:supervisor:restart`. + - Add support for `async-utilization` metrics. ## v0.54.1