Skip to content
Merged
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
22 changes: 22 additions & 0 deletions guides/getting-started/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ Assign the handle from the registry to an instance variable (or replace it when

Each call to `registry.metric(:field)` returns the **same** cached instance for that field. Setting `registry.observer = …` updates every metric the registry already holds, so you normally keep using the same handle. Fetch a new metric only when you use a different registry or a different field name.

## Namespaces

Use namespaces when several components expose the same generic field names but need distinct fields in the shared registry:

```ruby
registry = Async::Utilization::Registry.new

socket_accept = registry.namespace(:socket_accept)
long_task = registry.namespace(:long_task)

socket_accept.metric(:acquired_count).increment
long_task.metric(:acquired_count).increment

registry.values
# => {
# socket_accept_acquired_count: 1,
# long_task_acquired_count: 1
# }
```

A namespace is registry-like: pass it to code that expects an object responding to `metric(name)`. Nested namespaces compose names with underscores.

## With Shared Memory Observer

When you need to share metrics with other processes (like a supervisor monitoring worker health), you can set up a shared memory observer:
Expand Down
1 change: 1 addition & 0 deletions lib/async/utilization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require_relative "utilization/version"
require_relative "utilization/schema"
require_relative "utilization/namespace"
require_relative "utilization/registry"
require_relative "utilization/observer"
require_relative "utilization/metric"
Expand Down
3 changes: 3 additions & 0 deletions lib/async/utilization/metric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def initialize(name)
# @attribute [Symbol] The field name for this metric.
attr :name

# Get the current in-memory metric value.
#
# @returns [Numeric] The last value written to this metric.
def value
@guard.synchronize do
@value
Expand Down
51 changes: 51 additions & 0 deletions lib/async/utilization/namespace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

module Async
module Utilization
# A registry-like view that prefixes metric names.
#
# Namespaces let components use generic metric names while applications decide
# how those names are composed in the shared registry.
class Namespace
# Initialize a new namespace.
#
# @parameter registry [Registry] The underlying registry.
# @parameter name [Symbol] The namespace name.
def initialize(registry, name)
@registry = registry
@name = name.to_sym
end

# @attribute [Registry] The underlying registry.
attr :registry

# @attribute [Symbol] The namespace name.
attr :name

# Get a metric in this namespace.
#
# @parameter name [Symbol] The metric name.
# @returns [Metric] A metric instance for the namespaced field.
def metric(name)
@registry.metric(metric_name(name))
end

# Get a nested namespace.
#
# @parameter name [Symbol] The nested namespace name.
# @returns [Namespace] A namespace view with the composed name.
def namespace(name)
self.class.new(@registry, metric_name(name))
end

private

def metric_name(name)
:"#{@name}_#{name}"
end
end
end
end
9 changes: 9 additions & 0 deletions lib/async/utilization/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Copyright, 2026, by Samuel Williams.

require "console"
require_relative "namespace"

module Async
module Utilization
Expand Down Expand Up @@ -93,6 +94,14 @@ def metric(field)
@metrics[field] ||= Metric.for(field, @observer)
end
end

# Get a namespace view of this registry.
#
# @parameter name [Symbol] The namespace name.
# @returns [Namespace] A registry-like namespace view.
def namespace(name)
Namespace.new(self, name)
end
end
end
end
1 change: 1 addition & 0 deletions releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Add `Async::Utilization::Namespace` for composing registry metric names.
- `Async::Utilization::Metric` is the primary interface, remove `#set`, `#increment`, `#decrement` and `#track` from `Registry`.

## v0.3.2
Expand Down
53 changes: 53 additions & 0 deletions test/async/utilization/namespace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

require "sus"
require "async/utilization"

describe Async::Utilization::Namespace do
let(:registry) {Async::Utilization::Registry.new}
let(:namespace) {registry.namespace(:socket_accept)}

it "uses namespaced metric names" do
metric = namespace.metric(:acquired_count)

expect(metric).to be_a(Async::Utilization::Metric)
expect(metric.name).to be == :socket_accept_acquired_count

metric.set(2)
expect(registry.values).to have_keys(socket_accept_acquired_count: be == 2)
end

it "returns the same metric instance for the same namespaced field" do
metric1 = namespace.metric(:waiting_count)
metric2 = namespace.metric(:waiting_count)

expect(metric1).to be == metric2
end

it "supports nested namespaces" do
metric = namespace.namespace(:long_task).metric(:waiting_count)

expect(metric.name).to be == :socket_accept_long_task_waiting_count

metric.increment
expect(registry.values).to have_keys(socket_accept_long_task_waiting_count: be == 1)
end

it "writes namespaced metrics to an observer" do
schema = Async::Utilization::Schema.build(socket_accept_acquired_count: :u64)
buffer = IO::Buffer.new(8)

observer = Object.new
observer.define_singleton_method(:schema){schema}
observer.define_singleton_method(:buffer){buffer}

registry.observer = observer

namespace.metric(:acquired_count).set(5)

expect(buffer.get_value(:u64, 0)).to be == 5
end
end
8 changes: 8 additions & 0 deletions test/async/utilization/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@
expect(registry.values).to have_keys(module_test: be == 2)
end

it "can create a namespace" do
namespace = registry.namespace(:socket_accept)

expect(namespace).to be_a(Async::Utilization::Namespace)
expect(namespace.registry).to be == registry
expect(namespace.name).to be == :socket_accept
end

it "can use metric for decrement" do
registry.metric(:module_decrement_test).increment
registry.metric(:module_decrement_test).increment
Expand Down
Loading