async-service-supervisor 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/async/service/supervisor.rb +1 -1
- data/context/getting-started.md +5 -2
- data/context/index.yaml +5 -0
- data/context/utilization-monitor.md +229 -0
- data/lib/async/service/supervisor/client.rb +1 -1
- data/lib/async/service/supervisor/endpoint.rb +1 -1
- data/lib/async/service/supervisor/environment.rb +2 -1
- data/lib/async/service/supervisor/loop.rb +3 -0
- data/lib/async/service/supervisor/memory_monitor.rb +18 -3
- data/lib/async/service/supervisor/process_monitor.rb +18 -3
- data/lib/async/service/supervisor/server.rb +1 -1
- data/lib/async/service/supervisor/service.rb +1 -1
- data/lib/async/service/supervisor/supervised.rb +33 -2
- data/lib/async/service/supervisor/supervisor_controller.rb +1 -1
- data/lib/async/service/supervisor/utilization_monitor.rb +349 -0
- data/lib/async/service/supervisor/version.rb +2 -2
- data/lib/async/service/supervisor/worker.rb +35 -2
- data/lib/async/service/supervisor/worker_controller.rb +14 -1
- data/lib/async/service/supervisor.rb +2 -1
- data/license.md +1 -1
- data/readme.md +6 -4
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +18 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af7b7a67aaac7ed8eadf666de3a01345e949ba59829aa6c864e94af19f5d422a
|
|
4
|
+
data.tar.gz: 9765b0474d90eff9294e7865708a0c772a87e8ff7b0787baf0478f67e742b22b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b678aebfa3fe28e7930c4c2426daae4f18acd8da24aa423994d8271c6a2ef8674a0dcdaa138d40c2db98e15faf4d2aa1e85815d63faae3a4cecdd374784409bc
|
|
7
|
+
data.tar.gz: 118ab77fbf30089894f3537ba839e292d59b5263056efd07aa34af35f1dca599d087a0896a0b1dbe5b0bb1f6654751f87b2f66c98ff2fb5e15d68ac20f372637
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/getting-started.md
CHANGED
|
@@ -133,14 +133,17 @@ service "supervisor" do
|
|
|
133
133
|
Async::Service::Supervisor::MemoryMonitor.new(
|
|
134
134
|
interval: 10,
|
|
135
135
|
maximum_size_limit: 1024 * 1024 * 500 # 500MB limit per process
|
|
136
|
+
),
|
|
137
|
+
|
|
138
|
+
# Aggregate application-level metrics (connections, requests) from workers:
|
|
139
|
+
Async::Service::Supervisor::UtilizationMonitor.new(
|
|
140
|
+
interval: 10
|
|
136
141
|
)
|
|
137
142
|
]
|
|
138
143
|
end
|
|
139
144
|
end
|
|
140
145
|
```
|
|
141
146
|
|
|
142
|
-
See the {ruby Async::Service::Supervisor::MemoryMonitor Memory Monitor} and {ruby Async::Service::Supervisor::ProcessMonitor Process Monitor} guides for detailed configuration options and best practices.
|
|
143
|
-
|
|
144
147
|
### Collecting Diagnostics
|
|
145
148
|
|
|
146
149
|
The supervisor can collect various diagnostics from workers on demand:
|
data/context/index.yaml
CHANGED
|
@@ -23,3 +23,8 @@ files:
|
|
|
23
23
|
title: Process Monitor
|
|
24
24
|
description: This guide explains how to use the <code class="language-ruby">Async::Service::Supervisor::ProcessMonitor</code>
|
|
25
25
|
to log CPU and memory metrics for your worker processes.
|
|
26
|
+
- path: utilization-monitor.md
|
|
27
|
+
title: Utilization Monitor
|
|
28
|
+
description: This guide explains how to use the <code class="language-ruby">Async::Service::Supervisor::UtilizationMonitor</code>
|
|
29
|
+
to collect and aggregate application-level utilization metrics from your worker
|
|
30
|
+
processes.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Utilization Monitor
|
|
2
|
+
|
|
3
|
+
This guide explains how to use the {ruby Async::Service::Supervisor::UtilizationMonitor} to collect and aggregate application-level utilization metrics from your worker processes.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
While the {ruby Async::Service::Supervisor::ProcessMonitor} captures OS-level metrics (CPU, memory) and the {ruby Async::Service::Supervisor::MemoryMonitor} takes action when limits are exceeded, the `UtilizationMonitor` focuses on **application-level metrics**—connections, requests, queue depths, and other business-specific utilization data. Without it, you can't easily answer questions like "How many active connections do my workers have?" or "What is the total request throughput across all workers?"
|
|
8
|
+
|
|
9
|
+
The `UtilizationMonitor` solves this by using shared memory to efficiently collect metrics from workers and aggregate them by service name. Workers write metrics to a shared memory segment; the supervisor periodically reads and aggregates them without any IPC overhead during collection.
|
|
10
|
+
|
|
11
|
+
Use the `UtilizationMonitor` when you need:
|
|
12
|
+
|
|
13
|
+
- **Application observability**: Track connections, requests, queue depths, or custom metrics across workers.
|
|
14
|
+
- **Service-level aggregation**: See totals per service (e.g., "echo" service: 42 connections, 1000 messages).
|
|
15
|
+
- **Lightweight collection**: Avoid IPC or network calls—metrics are read directly from shared memory.
|
|
16
|
+
- **Integration with logging**: Emit aggregated metrics to your logging pipeline for dashboards and alerts.
|
|
17
|
+
|
|
18
|
+
The monitor uses the `async-utilization` gem for schema definition and shared memory layout. Workers must include {ruby Async::Service::Supervisor::Supervised} and define a `utilization_schema` to participate.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Supervisor Configuration
|
|
23
|
+
|
|
24
|
+
Add a utilization monitor to your supervisor service:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
service "supervisor" do
|
|
28
|
+
include Async::Service::Supervisor::Environment
|
|
29
|
+
|
|
30
|
+
monitors do
|
|
31
|
+
[
|
|
32
|
+
Async::Service::Supervisor::UtilizationMonitor.new(
|
|
33
|
+
path: File.expand_path("utilization.shm", root),
|
|
34
|
+
interval: 10 # Aggregate and emit metrics every 10 seconds
|
|
35
|
+
)
|
|
36
|
+
]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Worker Configuration
|
|
42
|
+
|
|
43
|
+
Workers must include {ruby Async::Service::Supervisor::Supervised} and define a `utilization_schema` that describes the metrics they expose:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
service "echo" do
|
|
47
|
+
include Async::Service::Managed::Environment
|
|
48
|
+
include Async::Service::Supervisor::Supervised
|
|
49
|
+
|
|
50
|
+
service_class EchoService
|
|
51
|
+
|
|
52
|
+
utilization_schema do
|
|
53
|
+
{
|
|
54
|
+
connections_total: :u64,
|
|
55
|
+
connections_active: :u32,
|
|
56
|
+
messages_total: :u64
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Emitting Metrics from Workers
|
|
63
|
+
|
|
64
|
+
Workers obtain a utilization registry from the evaluator and use it to update metrics:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
def run(instance, evaluator)
|
|
68
|
+
evaluator.prepare!(instance)
|
|
69
|
+
instance.ready!
|
|
70
|
+
|
|
71
|
+
registry = evaluator.utilization_registry
|
|
72
|
+
connections_total = registry.metric(:connections_total)
|
|
73
|
+
connections_active = registry.metric(:connections_active)
|
|
74
|
+
messages_total = registry.metric(:messages_total)
|
|
75
|
+
|
|
76
|
+
@bound_endpoint.accept do |peer|
|
|
77
|
+
connections_total.increment
|
|
78
|
+
connections_active.track do
|
|
79
|
+
peer.each_line do |line|
|
|
80
|
+
messages_total.increment
|
|
81
|
+
peer.write(line)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The supervisor aggregates these metrics by service name and emits them at the configured interval. For example:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"echo": {
|
|
93
|
+
"connections_total": 150,
|
|
94
|
+
"connections_active": 12,
|
|
95
|
+
"messages_total": 45000
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Configuration Options
|
|
101
|
+
|
|
102
|
+
### `path`
|
|
103
|
+
|
|
104
|
+
Path to the shared memory file used for worker metrics. Default: `"utilization.shm"` (relative to current working directory).
|
|
105
|
+
|
|
106
|
+
Be explicit about the path when using {ruby Async::Service::Supervisor::Environment} so supervisor and workers resolve the same file regardless of working directory:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
monitors do
|
|
110
|
+
[
|
|
111
|
+
Async::Service::Supervisor::UtilizationMonitor.new(
|
|
112
|
+
path: File.expand_path("utilization.shm", root),
|
|
113
|
+
interval: 10
|
|
114
|
+
)
|
|
115
|
+
]
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `interval`
|
|
120
|
+
|
|
121
|
+
The interval (in seconds) at which to aggregate and emit utilization metrics. Default: `10` seconds.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# Emit every second for high-frequency monitoring
|
|
125
|
+
Async::Service::Supervisor::UtilizationMonitor.new(interval: 1)
|
|
126
|
+
|
|
127
|
+
# Emit every 5 minutes for low-overhead monitoring
|
|
128
|
+
Async::Service::Supervisor::UtilizationMonitor.new(interval: 300)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `size`
|
|
132
|
+
|
|
133
|
+
Total size of the shared memory buffer. Default: `IO::Buffer::PAGE_SIZE * 8`. The buffer grows automatically when more workers are registered than segments available.
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
Async::Service::Supervisor::UtilizationMonitor.new(
|
|
137
|
+
size: IO::Buffer::PAGE_SIZE * 32 # Larger initial buffer for many workers
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `segment_size`
|
|
142
|
+
|
|
143
|
+
Size of each allocation segment per worker. Default: `512` bytes. Must accommodate your schema; the `async-utilization` gem lays out fields according to type (e.g., `u64` = 8 bytes, `u32` = 4 bytes).
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
Async::Service::Supervisor::UtilizationMonitor.new(
|
|
147
|
+
segment_size: 256 # Smaller segments if schema is compact
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Schema Types
|
|
152
|
+
|
|
153
|
+
The `utilization_schema` maps metric names to types supported by {ruby IO::Buffer}:
|
|
154
|
+
|
|
155
|
+
| Type | Size | Use case |
|
|
156
|
+
|------|------|----------|
|
|
157
|
+
| `:u32` | 4 bytes | Counters that may wrap (e.g., connections_active) |
|
|
158
|
+
| `:u64` | 8 bytes | Monotonically increasing counters (e.g., requests_total) |
|
|
159
|
+
| `:i32` | 4 bytes | Signed 32-bit values |
|
|
160
|
+
| `:i64` | 8 bytes | Signed 64-bit values |
|
|
161
|
+
| `:f32` | 4 bytes | Single-precision floats |
|
|
162
|
+
| `:f64` | 8 bytes | Double-precision floats |
|
|
163
|
+
|
|
164
|
+
Prefer `:u64` for totals that only increase; use `:u32` for gauges or values that may decrease.
|
|
165
|
+
|
|
166
|
+
## Default Schema
|
|
167
|
+
|
|
168
|
+
The {ruby Async::Service::Supervisor::Supervised} mixin provides a default schema if you don't override `utilization_schema`:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
{
|
|
172
|
+
connections_active: :u32,
|
|
173
|
+
connections_total: :u64,
|
|
174
|
+
requests_active: :u32,
|
|
175
|
+
requests_total: :u64
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Override it when your service has different metrics:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
utilization_schema do
|
|
183
|
+
{
|
|
184
|
+
connections_active: :u32,
|
|
185
|
+
connections_total: :u64,
|
|
186
|
+
messages_total: :u64,
|
|
187
|
+
queue_depth: :u32
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Metric API
|
|
193
|
+
|
|
194
|
+
The utilization registry provides methods to update metrics:
|
|
195
|
+
|
|
196
|
+
- **`increment`**: Increment a counter by 1.
|
|
197
|
+
- **`set(value)`**: Set a gauge to a specific value.
|
|
198
|
+
- **`track { ... }`**: Execute a block and increment/decrement a gauge around it (e.g., `connections_active` while handling a connection).
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
connections_total = registry.metric(:connections_total)
|
|
202
|
+
connections_active = registry.metric(:connections_active)
|
|
203
|
+
|
|
204
|
+
# Increment total connections when a client connects
|
|
205
|
+
connections_total.increment
|
|
206
|
+
|
|
207
|
+
# Track active connections for the duration of the block
|
|
208
|
+
connections_active.track do
|
|
209
|
+
handle_client(peer)
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Aggregation Behavior
|
|
214
|
+
|
|
215
|
+
Metrics are aggregated by service name (from `supervisor_worker_state[:name]`). Values are summed across workers of the same service. For example, with 4 workers each reporting `connections_active: 3`, the aggregated value is `12`.
|
|
216
|
+
|
|
217
|
+
## Best Practices
|
|
218
|
+
|
|
219
|
+
- **Define a minimal schema**: Only include metrics you need; each field consumes shared memory.
|
|
220
|
+
- **Use appropriate types**: `u64` for ever-increasing counters; `u32` for gauges.
|
|
221
|
+
- **Match schema across workers**: All workers of the same service should use the same schema for consistent aggregation.
|
|
222
|
+
- **Combine with other monitors**: Use `UtilizationMonitor` alongside `ProcessMonitor` and `MemoryMonitor` for full observability.
|
|
223
|
+
|
|
224
|
+
## Common Pitfalls
|
|
225
|
+
|
|
226
|
+
- **Workers without schema**: Workers that don't define `utilization_schema` (or return `nil`) are not registered. They won't contribute to utilization metrics.
|
|
227
|
+
- **Schema mismatch**: If workers of the same service use different schemas, aggregation may produce incorrect or partial results.
|
|
228
|
+
- **Path permissions**: Ensure the shared memory path is accessible to all worker processes (e.g., same user, or appropriate permissions).
|
|
229
|
+
- **Segment size**: If your schema is large, increase `segment_size` to avoid allocation failures.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "async/service/environment"
|
|
7
7
|
require "async/service/managed/environment"
|
|
8
8
|
|
|
9
9
|
require_relative "service"
|
|
10
|
+
require_relative "server"
|
|
10
11
|
|
|
11
12
|
module Async
|
|
12
13
|
module Service
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "memory/leak/cluster"
|
|
7
7
|
require "set"
|
|
@@ -85,11 +85,26 @@ module Async
|
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
# The key used when this monitor's status is aggregated with others.
|
|
89
|
+
def self.monitor_type
|
|
90
|
+
:memory_monitor
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Serialize memory cluster data for JSON.
|
|
94
|
+
def as_json
|
|
95
|
+
@cluster.as_json
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Serialize to JSON string.
|
|
99
|
+
def to_json(...)
|
|
100
|
+
as_json.to_json(...)
|
|
101
|
+
end
|
|
102
|
+
|
|
88
103
|
# Get status for the memory monitor.
|
|
89
104
|
#
|
|
90
|
-
# @returns [Hash]
|
|
105
|
+
# @returns [Hash] Hash with type and data keys.
|
|
91
106
|
def status
|
|
92
|
-
{
|
|
107
|
+
{type: self.class.monitor_type, data: as_json}
|
|
93
108
|
end
|
|
94
109
|
|
|
95
110
|
# Invoked when a memory leak is detected.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "process/metrics"
|
|
7
7
|
require_relative "loop"
|
|
@@ -57,11 +57,26 @@ module Async
|
|
|
57
57
|
Process::Metrics::General.capture(ppid: @ppid).transform_values!(&:as_json)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# The key used when this monitor's status is aggregated with others.
|
|
61
|
+
def self.monitor_type
|
|
62
|
+
:process_monitor
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Serialize process metrics for JSON.
|
|
66
|
+
def as_json
|
|
67
|
+
{ppid: @ppid, metrics: self.metrics}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Serialize to JSON string.
|
|
71
|
+
def to_json(...)
|
|
72
|
+
as_json.to_json(...)
|
|
73
|
+
end
|
|
74
|
+
|
|
60
75
|
# Get status for the process monitor.
|
|
61
76
|
#
|
|
62
|
-
# @returns [Hash]
|
|
77
|
+
# @returns [Hash] Hash with type and data keys.
|
|
63
78
|
def status
|
|
64
|
-
{
|
|
79
|
+
{type: self.class.monitor_type, data: as_json}
|
|
65
80
|
end
|
|
66
81
|
|
|
67
82
|
# Run the process monitor.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "async/service/environment"
|
|
7
|
+
require "async/utilization"
|
|
8
|
+
|
|
9
|
+
require_relative "worker"
|
|
7
10
|
|
|
8
11
|
module Async
|
|
9
12
|
module Service
|
|
@@ -30,10 +33,38 @@ module Async
|
|
|
30
33
|
{name: self.name}
|
|
31
34
|
end
|
|
32
35
|
|
|
36
|
+
# A default schema for utilization metrics.
|
|
37
|
+
# @returns [Hash | Nil] The utilization schema or nil if utilization is disabled.
|
|
38
|
+
def utilization_schema
|
|
39
|
+
{
|
|
40
|
+
connections_active: :u32,
|
|
41
|
+
connections_total: :u64,
|
|
42
|
+
requests_active: :u32,
|
|
43
|
+
requests_total: :u64,
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get the utilization registry for this service.
|
|
48
|
+
#
|
|
49
|
+
# Creates a new registry instance for tracking utilization metrics.
|
|
50
|
+
# This registry is used by workers to emit metrics that can be collected
|
|
51
|
+
# by the supervisor's utilization monitor.
|
|
52
|
+
#
|
|
53
|
+
# @returns [Async::Utilization::Registry] A new utilization registry instance.
|
|
54
|
+
def utilization_registry
|
|
55
|
+
Async::Utilization::Registry.new
|
|
56
|
+
end
|
|
57
|
+
|
|
33
58
|
# The supervised worker for the current process.
|
|
34
59
|
# @returns [Worker] The worker client.
|
|
35
60
|
def supervisor_worker
|
|
36
|
-
Worker.new(
|
|
61
|
+
Worker.new(
|
|
62
|
+
process_id: Process.pid,
|
|
63
|
+
endpoint: supervisor_endpoint,
|
|
64
|
+
state: self.supervisor_worker_state,
|
|
65
|
+
utilization_schema: self.utilization_schema,
|
|
66
|
+
utilization_registry: self.utilization_registry,
|
|
67
|
+
)
|
|
37
68
|
end
|
|
38
69
|
|
|
39
70
|
# Create a supervised worker for the given instance.
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "set"
|
|
7
|
+
|
|
8
|
+
require_relative "loop"
|
|
9
|
+
require "async/utilization"
|
|
10
|
+
|
|
11
|
+
module Async
|
|
12
|
+
module Service
|
|
13
|
+
module Supervisor
|
|
14
|
+
# Monitors worker utilization metrics aggregated by service name.
|
|
15
|
+
#
|
|
16
|
+
# Uses shared memory to efficiently collect utilization metrics from workers
|
|
17
|
+
# and aggregates them by service name for monitoring and reporting.
|
|
18
|
+
class UtilizationMonitor
|
|
19
|
+
# Allocates and manages shared memory segments for worker utilization data.
|
|
20
|
+
#
|
|
21
|
+
# Manages a shared memory file that workers can write utilization metrics to.
|
|
22
|
+
# Allocates segments to workers and maintains a free list for reuse.
|
|
23
|
+
# Each process (supervisor and workers) maps the shared memory file independently.
|
|
24
|
+
class SegmentAllocator
|
|
25
|
+
# Initialize a new shared memory manager.
|
|
26
|
+
#
|
|
27
|
+
# Creates and maps the shared memory file. Workers will map the same file
|
|
28
|
+
# independently using the provided path.
|
|
29
|
+
#
|
|
30
|
+
# @parameter path [String] Path to the shared memory file.
|
|
31
|
+
# @parameter size [Integer] Total size of the shared memory buffer.
|
|
32
|
+
# @parameter segment_size [Integer] Size of each allocation segment (default: 512 bytes).
|
|
33
|
+
# @parameter growth_factor [Integer, Float] Factor to grow by when resizing (default: 2, doubles the size).
|
|
34
|
+
# Can be less than 2 or a floating point value; the result will be page-aligned to an integer.
|
|
35
|
+
def initialize(path, size: IO::Buffer::PAGE_SIZE * 8, segment_size: 512, growth_factor: 2)
|
|
36
|
+
@path = path
|
|
37
|
+
@size = size
|
|
38
|
+
@segment_size = segment_size
|
|
39
|
+
@growth_factor = growth_factor
|
|
40
|
+
|
|
41
|
+
@file = File.open(path, "w+b")
|
|
42
|
+
@file.truncate(size)
|
|
43
|
+
# Supervisor maps the file for reading worker data
|
|
44
|
+
@buffer = IO::Buffer.map(@file, size)
|
|
45
|
+
|
|
46
|
+
# Track allocated segments: worker_id => {offset: Integer, schema: Array}
|
|
47
|
+
@allocations = {}
|
|
48
|
+
|
|
49
|
+
# Free list of segment offsets
|
|
50
|
+
@free_list = []
|
|
51
|
+
|
|
52
|
+
# Initialize free list with all segments
|
|
53
|
+
(0...(@size / @segment_size)).each do |segment_index|
|
|
54
|
+
@free_list << (segment_index * @segment_size)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Allocate a segment for a worker.
|
|
59
|
+
#
|
|
60
|
+
# Automatically resizes the shared memory file if no segments are available.
|
|
61
|
+
#
|
|
62
|
+
# @parameter worker_id [Integer] The ID of the worker.
|
|
63
|
+
# @parameter schema [Array] Array of [key, type, offset] tuples describing the data layout.
|
|
64
|
+
# @returns [Integer] The offset into the shared memory buffer, or nil if allocation fails.
|
|
65
|
+
def allocate(worker_id, schema)
|
|
66
|
+
# Try to resize if we're out of segments
|
|
67
|
+
if @free_list.empty?
|
|
68
|
+
unless resize(@size * @growth_factor)
|
|
69
|
+
return nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
offset = @free_list.shift
|
|
74
|
+
@allocations[worker_id] = {offset: offset, schema: schema}
|
|
75
|
+
|
|
76
|
+
return offset
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Free a segment allocated to a worker.
|
|
80
|
+
#
|
|
81
|
+
# @parameter worker_id [Integer] The ID of the worker.
|
|
82
|
+
def free(worker_id)
|
|
83
|
+
if allocation = @allocations.delete(worker_id)
|
|
84
|
+
@free_list << allocation[:offset]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get the allocation information for a worker.
|
|
89
|
+
#
|
|
90
|
+
# @parameter worker_id [Integer] The ID of the worker.
|
|
91
|
+
# @returns [Hash] Allocation info with :offset and :schema, or nil if not allocated.
|
|
92
|
+
def allocation(worker_id)
|
|
93
|
+
@allocations[worker_id]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get the current size of the shared memory file.
|
|
97
|
+
#
|
|
98
|
+
# @returns [Integer] The current size of the shared memory file.
|
|
99
|
+
def size
|
|
100
|
+
@size
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Update the schema for an existing allocation.
|
|
104
|
+
#
|
|
105
|
+
# @parameter worker_id [Integer] The ID of the worker.
|
|
106
|
+
# @parameter schema [Array] Array of [key, type, offset] tuples describing the data layout.
|
|
107
|
+
def update_schema(worker_id, schema)
|
|
108
|
+
if allocation = @allocations[worker_id]
|
|
109
|
+
allocation[:schema] = schema
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Read utilization data from a worker's allocated segment.
|
|
114
|
+
#
|
|
115
|
+
# @parameter worker_id [Integer] The ID of the worker.
|
|
116
|
+
# @returns [Hash] Hash mapping keys to their values, or nil if not allocated.
|
|
117
|
+
def read(worker_id)
|
|
118
|
+
allocation = @allocations[worker_id]
|
|
119
|
+
return nil unless allocation
|
|
120
|
+
|
|
121
|
+
offset = allocation[:offset]
|
|
122
|
+
schema = allocation[:schema]
|
|
123
|
+
|
|
124
|
+
result = {}
|
|
125
|
+
schema.each do |key, type, field_offset|
|
|
126
|
+
absolute_offset = offset + field_offset
|
|
127
|
+
|
|
128
|
+
# Use IO::Buffer type symbols directly (i32, u32, i64, u64, f32, f64)
|
|
129
|
+
# IO::Buffer accepts both lowercase and uppercase versions
|
|
130
|
+
begin
|
|
131
|
+
result[key] = @buffer.get_value(type, absolute_offset)
|
|
132
|
+
rescue => error
|
|
133
|
+
Console.warn(self, "Failed to read value", type: type, key: key, offset: absolute_offset, exception: error)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return result
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Resize the shared memory file.
|
|
141
|
+
#
|
|
142
|
+
# Extends the file to the new size, remaps the buffer, and adds new segments
|
|
143
|
+
# to the free list. The new size must be larger than the current size and should
|
|
144
|
+
# be page-aligned for optimal performance.
|
|
145
|
+
#
|
|
146
|
+
# @parameter new_size [Integer] The new size for the shared memory file.
|
|
147
|
+
# @returns [Boolean] True if resize was successful, false otherwise.
|
|
148
|
+
def resize(new_size)
|
|
149
|
+
old_size = @size
|
|
150
|
+
return false if new_size <= old_size
|
|
151
|
+
|
|
152
|
+
# Ensure new size is page-aligned (rounds up to nearest page boundary)
|
|
153
|
+
page_size = IO::Buffer::PAGE_SIZE
|
|
154
|
+
new_size = (((new_size + page_size - 1) / page_size) * page_size).to_i
|
|
155
|
+
|
|
156
|
+
begin
|
|
157
|
+
# Extend the file:
|
|
158
|
+
@file.truncate(new_size)
|
|
159
|
+
|
|
160
|
+
# Remap the buffer to the new size:
|
|
161
|
+
@buffer&.free
|
|
162
|
+
@buffer = IO::Buffer.map(@file, new_size)
|
|
163
|
+
|
|
164
|
+
# Calculate new segments to add to free list:
|
|
165
|
+
old_segment_count = old_size / @segment_size
|
|
166
|
+
new_segment_count = new_size / @segment_size
|
|
167
|
+
|
|
168
|
+
# Add new segments to free list:
|
|
169
|
+
(old_segment_count...new_segment_count).each do |segment_index|
|
|
170
|
+
@free_list << (segment_index * @segment_size)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@size = new_size
|
|
174
|
+
|
|
175
|
+
Console.info(self, "Resized shared memory", old_size: old_size, new_size: new_size, segments_added: new_segment_count - old_segment_count)
|
|
176
|
+
|
|
177
|
+
return true
|
|
178
|
+
rescue => error
|
|
179
|
+
Console.error(self, "Failed to resize shared memory", old_size: old_size, new_size: new_size, exception: error)
|
|
180
|
+
return false
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Close the shared memory file.
|
|
185
|
+
def close
|
|
186
|
+
@file&.close
|
|
187
|
+
@buffer = nil
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
# Initialize a new utilization monitor.
|
|
191
|
+
#
|
|
192
|
+
# @parameter path [String] Path to the shared memory file.
|
|
193
|
+
# @parameter interval [Integer] Interval in seconds to aggregate and update metrics.
|
|
194
|
+
# @parameter size [Integer] Total size of the shared memory buffer.
|
|
195
|
+
# @parameter segment_size [Integer] Size of each allocation segment (default: 512 bytes).
|
|
196
|
+
def initialize(path: "utilization.shm", interval: 10, size: IO::Buffer::PAGE_SIZE * 8, segment_size: 512)
|
|
197
|
+
@path = path
|
|
198
|
+
@interval = interval
|
|
199
|
+
@segment_size = segment_size
|
|
200
|
+
|
|
201
|
+
@allocator = SegmentAllocator.new(path, size: size, segment_size: segment_size)
|
|
202
|
+
|
|
203
|
+
# Track workers: worker_id => supervisor_controller
|
|
204
|
+
@workers = {}
|
|
205
|
+
|
|
206
|
+
@guard = Mutex.new
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Register a worker with the utilization monitor.
|
|
210
|
+
#
|
|
211
|
+
# Allocates a segment of shared memory and instructs the worker
|
|
212
|
+
# to map the shared memory file and expose utilization information at the allocated offset.
|
|
213
|
+
# The worker maps the file independently and returns its schema.
|
|
214
|
+
#
|
|
215
|
+
# @parameter supervisor_controller [SupervisorController] The supervisor controller for the worker.
|
|
216
|
+
def register(supervisor_controller)
|
|
217
|
+
@guard.synchronize do
|
|
218
|
+
worker_id = supervisor_controller.id
|
|
219
|
+
return unless worker_id
|
|
220
|
+
|
|
221
|
+
# Allocate a segment first (we'll get schema from worker)
|
|
222
|
+
offset = @allocator.allocate(worker_id, [])
|
|
223
|
+
|
|
224
|
+
unless offset
|
|
225
|
+
Console.warn(self, "Failed to allocate utilization segment", worker_id: worker_id)
|
|
226
|
+
return
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Inform worker of the shared memory path, size, and allocated offset
|
|
230
|
+
# The worker will map the file itself and return its schema
|
|
231
|
+
begin
|
|
232
|
+
worker = supervisor_controller.worker
|
|
233
|
+
|
|
234
|
+
if worker
|
|
235
|
+
# Pass the segment size - observer will handle page alignment and file mapping
|
|
236
|
+
schema = worker.setup_utilization_observer(@path, @segment_size, offset)
|
|
237
|
+
|
|
238
|
+
# Update the allocation with the actual schema
|
|
239
|
+
if schema && !schema.empty?
|
|
240
|
+
@allocator.update_schema(worker_id, schema)
|
|
241
|
+
@workers[worker_id] = supervisor_controller
|
|
242
|
+
|
|
243
|
+
Console.info(self, "Registered worker utilization", worker_id: worker_id, offset: offset, schema: schema)
|
|
244
|
+
else
|
|
245
|
+
# Worker didn't provide schema, free the allocation
|
|
246
|
+
@allocator.free(worker_id)
|
|
247
|
+
Console.info(self, "Worker did not provide utilization schema", worker_id: worker_id)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
rescue => error
|
|
251
|
+
Console.error(self, "Error setting up worker utilization", worker_id: worker_id, exception: error)
|
|
252
|
+
@allocator.free(worker_id)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Remove a worker from the utilization monitor.
|
|
258
|
+
#
|
|
259
|
+
# Returns the allocated segment back to the free list.
|
|
260
|
+
#
|
|
261
|
+
# @parameter supervisor_controller [SupervisorController] The supervisor controller for the worker.
|
|
262
|
+
def remove(supervisor_controller)
|
|
263
|
+
@guard.synchronize do
|
|
264
|
+
worker_id = supervisor_controller.id
|
|
265
|
+
return unless worker_id
|
|
266
|
+
|
|
267
|
+
@workers.delete(worker_id)
|
|
268
|
+
@allocator.free(worker_id)
|
|
269
|
+
|
|
270
|
+
Console.debug(self, "Freed utilization segment", worker_id: worker_id)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# The key used when this monitor's status is aggregated with others.
|
|
275
|
+
def self.monitor_type
|
|
276
|
+
:utilization_monitor
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Serialize utilization data for JSON.
|
|
280
|
+
#
|
|
281
|
+
# @returns [Hash] Hash mapping service names to aggregated utilization metrics.
|
|
282
|
+
def as_json
|
|
283
|
+
@guard.synchronize do
|
|
284
|
+
aggregated = {}
|
|
285
|
+
|
|
286
|
+
@workers.each do |worker_id, supervisor_controller|
|
|
287
|
+
service_name = supervisor_controller.state[:name] || "unknown"
|
|
288
|
+
|
|
289
|
+
data = @allocator.read(worker_id)
|
|
290
|
+
next unless data
|
|
291
|
+
|
|
292
|
+
# Initialize service aggregation if needed
|
|
293
|
+
aggregated[service_name] ||= {}
|
|
294
|
+
|
|
295
|
+
# Sum up all numeric fields
|
|
296
|
+
data.each do |key, value|
|
|
297
|
+
if value.is_a?(Numeric)
|
|
298
|
+
aggregated[service_name][key] ||= 0
|
|
299
|
+
aggregated[service_name][key] += value
|
|
300
|
+
else
|
|
301
|
+
# For non-numeric values, we could handle differently
|
|
302
|
+
# For now, just store the last value
|
|
303
|
+
aggregated[service_name][key] = value
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
aggregated
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Serialize to JSON string.
|
|
313
|
+
def to_json(...)
|
|
314
|
+
as_json.to_json(...)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Get aggregated utilization status by service name.
|
|
318
|
+
#
|
|
319
|
+
# Reads utilization data from all registered workers and aggregates it
|
|
320
|
+
# by service name (from supervisor_controller.state[:name]).
|
|
321
|
+
#
|
|
322
|
+
# @returns [Hash] Hash with type and data keys.
|
|
323
|
+
def status
|
|
324
|
+
{type: self.class.monitor_type, data: as_json}
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Emit the utilization metrics.
|
|
328
|
+
#
|
|
329
|
+
# @parameter status [Hash] The utilization metrics.
|
|
330
|
+
def emit(metrics)
|
|
331
|
+
Console.info(self, "Utilization:", metrics: metrics)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Run the utilization monitor.
|
|
335
|
+
#
|
|
336
|
+
# Periodically aggregates utilization data from all workers.
|
|
337
|
+
#
|
|
338
|
+
# @returns [Async::Task] The task that is running the utilization monitor.
|
|
339
|
+
def run
|
|
340
|
+
Async do
|
|
341
|
+
Loop.run(interval: @interval) do
|
|
342
|
+
self.emit(self.as_json)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
# @namespace
|
|
7
7
|
module Async
|
|
@@ -9,7 +9,7 @@ module Async
|
|
|
9
9
|
module Service
|
|
10
10
|
# @namespace
|
|
11
11
|
module Supervisor
|
|
12
|
-
VERSION = "0.
|
|
12
|
+
VERSION = "0.12.0"
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "client"
|
|
7
7
|
require_relative "worker_controller"
|
|
@@ -26,12 +26,17 @@ module Async
|
|
|
26
26
|
# @parameter process_id [Integer] The process ID to register with the supervisor.
|
|
27
27
|
# @parameter endpoint [IO::Endpoint] The supervisor endpoint to connect to.
|
|
28
28
|
# @parameter state [Hash] Optional state to associate with this worker (e.g., service name).
|
|
29
|
-
|
|
29
|
+
# @parameter utilization_schema [Hash | Nil] Optional utilization schema definition.
|
|
30
|
+
# @parameter utilization_registry [Registry, nil] Optional utilization registry. If nil, a new registry is created.
|
|
31
|
+
def initialize(process_id: Process.pid, endpoint: Supervisor.endpoint, state: {}, utilization_schema: nil, utilization_registry: nil)
|
|
30
32
|
super(endpoint: endpoint)
|
|
31
33
|
|
|
32
34
|
@id = nil
|
|
33
35
|
@process_id = process_id
|
|
34
36
|
@state = state
|
|
37
|
+
|
|
38
|
+
@utilization_schema = utilization_schema
|
|
39
|
+
@utilization_registry = utilization_registry || require("async/utilization") && Async::Utilization::Registry.new
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
# @attribute [Integer] The ID assigned by the supervisor.
|
|
@@ -43,6 +48,34 @@ module Async
|
|
|
43
48
|
# @attribute [Hash] State associated with this worker (e.g., service name).
|
|
44
49
|
attr_accessor :state
|
|
45
50
|
|
|
51
|
+
# @attribute [Hash | Nil] Utilization schema definition.
|
|
52
|
+
attr :utilization_schema
|
|
53
|
+
|
|
54
|
+
# @attribute [Registry] The utilization registry for this worker.
|
|
55
|
+
attr :utilization_registry
|
|
56
|
+
|
|
57
|
+
# Setup utilization observer for this worker.
|
|
58
|
+
#
|
|
59
|
+
# Maps the shared memory file and configures the utilization registry to write
|
|
60
|
+
# metrics to it. Called by the supervisor (via WorkerController) to inform the
|
|
61
|
+
# worker of the shared memory file path and allocated offset.
|
|
62
|
+
#
|
|
63
|
+
# @parameter path [String] Path to the shared memory file that the worker should map.
|
|
64
|
+
# @parameter size [Integer] Size of the shared memory region to map.
|
|
65
|
+
# @parameter offset [Integer] Offset into the shared memory buffer allocated for this worker.
|
|
66
|
+
# @returns [Array] Array of [key, type, offset] tuples describing the utilization schema.
|
|
67
|
+
# Returns empty array if no utilization schema is configured.
|
|
68
|
+
def setup_utilization_observer(path, size, offset)
|
|
69
|
+
return [] unless @utilization_schema
|
|
70
|
+
|
|
71
|
+
schema = Async::Utilization::Schema.build(@utilization_schema)
|
|
72
|
+
observer = Async::Utilization::Observer.open(schema, path, size, offset)
|
|
73
|
+
@utilization_registry.observer = observer
|
|
74
|
+
|
|
75
|
+
# Pass the schema back to the supervisor so it can be used to aggregate the metrics:
|
|
76
|
+
observer.schema.to_a
|
|
77
|
+
end
|
|
78
|
+
|
|
46
79
|
protected def connected!(connection)
|
|
47
80
|
super
|
|
48
81
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "async/bus/controller"
|
|
7
7
|
require "stringio"
|
|
@@ -101,6 +101,19 @@ module Async
|
|
|
101
101
|
ensure
|
|
102
102
|
GC::Profiler.disable
|
|
103
103
|
end
|
|
104
|
+
|
|
105
|
+
# Setup utilization observer for this worker.
|
|
106
|
+
#
|
|
107
|
+
# Delegates to the worker to map the shared memory file and configure its
|
|
108
|
+
# utilization registry. Called by the supervisor via RPC.
|
|
109
|
+
#
|
|
110
|
+
# @parameter path [String] Path to the shared memory file that the worker should map.
|
|
111
|
+
# @parameter size [Integer] Size of the shared memory region to map.
|
|
112
|
+
# @parameter offset [Integer] Offset into the shared memory buffer allocated for this worker.
|
|
113
|
+
# @returns [Array] Array of [key, type, offset] tuples describing the utilization schema.
|
|
114
|
+
def setup_utilization_observer(path, size, offset)
|
|
115
|
+
@worker.setup_utilization_observer(path, size, offset)
|
|
116
|
+
end
|
|
104
117
|
end
|
|
105
118
|
end
|
|
106
119
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Samuel Williams.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "supervisor/version"
|
|
7
7
|
|
|
@@ -12,6 +12,7 @@ require_relative "supervisor/client"
|
|
|
12
12
|
|
|
13
13
|
require_relative "supervisor/memory_monitor"
|
|
14
14
|
require_relative "supervisor/process_monitor"
|
|
15
|
+
require_relative "supervisor/utilization_monitor"
|
|
15
16
|
|
|
16
17
|
require_relative "supervisor/environment"
|
|
17
18
|
require_relative "supervisor/supervised"
|
data/license.md
CHANGED
data/readme.md
CHANGED
|
@@ -24,10 +24,16 @@ Please see the [project documentation](https://socketry.github.io/async-service-
|
|
|
24
24
|
|
|
25
25
|
- [Process Monitor](https://socketry.github.io/async-service-supervisor/guides/process-monitor/index) - This guide explains how to use the <code class="language-ruby">Async::Service::Supervisor::ProcessMonitor</code> to log CPU and memory metrics for your worker processes.
|
|
26
26
|
|
|
27
|
+
- [Utilization Monitor](https://socketry.github.io/async-service-supervisor/guides/utilization-monitor/index) - This guide explains how to use the <code class="language-ruby">Async::Service::Supervisor::UtilizationMonitor</code> to collect and aggregate application-level utilization metrics from your worker processes.
|
|
28
|
+
|
|
27
29
|
## Releases
|
|
28
30
|
|
|
29
31
|
Please see the [project releases](https://socketry.github.io/async-service-supervisor/releases/index) for all releases.
|
|
30
32
|
|
|
33
|
+
### v0.12.0
|
|
34
|
+
|
|
35
|
+
- Introduce `UtilizationMonitor`, that uses shared memory to track worker utilization metrics, like total and active requests, connections, etc.
|
|
36
|
+
|
|
31
37
|
### v0.11.0
|
|
32
38
|
|
|
33
39
|
- Add `state` attribute to `SupervisorController` to store per-worker metadata (e.g., service name).
|
|
@@ -73,10 +79,6 @@ Please see the [project releases](https://socketry.github.io/async-service-super
|
|
|
73
79
|
|
|
74
80
|
- Fix timed out RPCs and subsequent responses which should be ignored.
|
|
75
81
|
|
|
76
|
-
### v0.6.0
|
|
77
|
-
|
|
78
|
-
- Add `async:container:supervisor:reload` command to restart the container (blue/green deployment).
|
|
79
|
-
|
|
80
82
|
## Contributing
|
|
81
83
|
|
|
82
84
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.12.0
|
|
4
|
+
|
|
5
|
+
- Introduce `UtilizationMonitor`, that uses shared memory to track worker utilization metrics, like total and active requests, connections, etc.
|
|
6
|
+
|
|
3
7
|
## v0.11.0
|
|
4
8
|
|
|
5
9
|
- Add `state` attribute to `SupervisorController` to store per-worker metadata (e.g., service name).
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-service-supervisor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0.15'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: async-utilization
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.3'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.3'
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: io-endpoint
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -132,6 +146,7 @@ files:
|
|
|
132
146
|
- context/memory-monitor.md
|
|
133
147
|
- context/migration.md
|
|
134
148
|
- context/process-monitor.md
|
|
149
|
+
- context/utilization-monitor.md
|
|
135
150
|
- lib/async/service/supervisor.rb
|
|
136
151
|
- lib/async/service/supervisor/client.rb
|
|
137
152
|
- lib/async/service/supervisor/endpoint.rb
|
|
@@ -143,6 +158,7 @@ files:
|
|
|
143
158
|
- lib/async/service/supervisor/service.rb
|
|
144
159
|
- lib/async/service/supervisor/supervised.rb
|
|
145
160
|
- lib/async/service/supervisor/supervisor_controller.rb
|
|
161
|
+
- lib/async/service/supervisor/utilization_monitor.rb
|
|
146
162
|
- lib/async/service/supervisor/version.rb
|
|
147
163
|
- lib/async/service/supervisor/worker.rb
|
|
148
164
|
- lib/async/service/supervisor/worker_controller.rb
|
|
@@ -162,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
162
178
|
requirements:
|
|
163
179
|
- - ">="
|
|
164
180
|
- !ruby/object:Gem::Version
|
|
165
|
-
version: '3.
|
|
181
|
+
version: '3.3'
|
|
166
182
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
183
|
requirements:
|
|
168
184
|
- - ">="
|
metadata.gz.sig
CHANGED
|
Binary file
|