async-service-supervisor 0.15.0 → 0.17.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acb581fece1b2ca0364e8b7f3fee2893c15d788f022377c23ba810a71276beb7
4
- data.tar.gz: 68d38aa49e35ef1c63e6f214ff271daca6650dfa5bffb1d628eb0f429b5d6219
3
+ metadata.gz: df5a5c251964d3435c184c712d4ccc2aa8c74a255d78f3632669a1a527ce78af
4
+ data.tar.gz: ea832f3d1e9136ccca58f5c5025fe180de773389a739d9b0ad99aae453df2ee2
5
5
  SHA512:
6
- metadata.gz: b46f7d61a4ec984478e0fbc062ebb14dc48899a2c004dc23c1446a69f2d095e5614f4eb6fedc0a7d6f264318ea84ce2f12a69b99bff643c18b0b2ef35b13fa8d
7
- data.tar.gz: a836860985fc1753ca0b116a9d929562c8b24fd9ba474043c178e2dfa26dd59caf64106ce96a3b508762560c88d5ea39fb74f72d63559f91fab425e79c1869e1
6
+ metadata.gz: f54af527ecda8c37129a39f6061095cbaf66b47eec102fd21c1aecb9e5791f487c3d48f567f0a0898a7168ba33bc3a60eef18f7fc1beb656640ce53e3b5f02ed
7
+ data.tar.gz: a9e0b9c6e8a489665d72ed11b0af0eeca18fa7256bfbcdf9d7bef364554fcd219a15c1dbae49d073d8631a92ca023c689f91936444630198b596fcf2eae110dc
checksums.yaml.gz.sig CHANGED
Binary file
@@ -8,11 +8,20 @@ require "async/loop"
8
8
  module Async
9
9
  module Service
10
10
  module Supervisor
11
+ # Base class for supervisor monitors that run periodically within the supervisor process.
12
+ #
13
+ # Subclasses should override {#run_once} to implement specific monitoring logic.
11
14
  class Monitor
15
+ # Initialize a new monitor.
16
+ #
17
+ # @parameter interval [Numeric] The interval in seconds between each invocation of {#run_once}.
12
18
  def initialize(interval: 1.0)
13
19
  @interval = interval
14
20
  end
15
21
 
22
+ # Serialize the monitor state for JSON representation.
23
+ #
24
+ # @returns [Hash] An empty hash by default; subclasses should override to include relevant state.
16
25
  def as_json(...)
17
26
  {}
18
27
  end
@@ -67,15 +67,20 @@ module Async
67
67
  {ppid: @ppid, metrics: self.metrics}
68
68
  end
69
69
 
70
- # Run one iteration of the process monitor.
71
- def run_once
72
- metrics = self.metrics
73
-
70
+ # Emit the process metrics.
71
+ #
72
+ # @parameter metrics [Hash] The process metrics to emit.
73
+ def emit(metrics)
74
74
  # Log each process individually for better searchability in log platforms:
75
75
  metrics.each do |process_id, general|
76
76
  Console.info(self, "Process metrics captured.", general: general)
77
77
  end
78
78
  end
79
+
80
+ # Run one iteration of the process monitor.
81
+ def run_once
82
+ self.emit(self.metrics)
83
+ end
79
84
  end
80
85
  end
81
86
  end
@@ -277,10 +277,10 @@ module Async
277
277
  :utilization_monitor
278
278
  end
279
279
 
280
- # Serialize utilization data for JSON.
280
+ # Sample aggregated utilization data.
281
281
  #
282
282
  # @returns [Hash] Hash mapping service names to aggregated utilization metrics.
283
- def as_json
283
+ def sample
284
284
  @guard.synchronize do
285
285
  aggregated = {}
286
286
 
@@ -313,6 +313,13 @@ module Async
313
313
  end
314
314
  end
315
315
 
316
+ # Serialize utilization data for JSON.
317
+ #
318
+ # @returns [Hash] Hash mapping service names to aggregated utilization metrics.
319
+ def as_json
320
+ self.sample
321
+ end
322
+
316
323
  # Emit the utilization metrics.
317
324
  #
318
325
  # @parameter status [Hash] The utilization metrics.
@@ -9,7 +9,7 @@ module Async
9
9
  module Service
10
10
  # @namespace
11
11
  module Supervisor
12
- VERSION = "0.15.0"
12
+ VERSION = "0.17.0"
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "metrics/provider"
7
+
8
+ require_relative "../../../../../async/service/supervisor/process_monitor"
9
+
10
+ class Async::Service::Supervisor::ProcessMonitor
11
+ Metrics::Provider(self) do
12
+ PROCESS_METRICS_GENERAL_PROCESSOR_UTILIZATION = Metrics.metric("process.metrics.general.processor_utilization", :gauge, description: "Processor utilization for a supervised process.")
13
+ PROCESS_METRICS_GENERAL_TOTAL_SIZE = Metrics.metric("process.metrics.general.total_size", :gauge, description: "Total memory size for a supervised process.", unit: "bytes")
14
+ PROCESS_METRICS_GENERAL_VIRTUAL_SIZE = Metrics.metric("process.metrics.general.virtual_size", :gauge, description: "Virtual memory size for a supervised process.", unit: "bytes")
15
+ PROCESS_METRICS_GENERAL_RESIDENT_SIZE = Metrics.metric("process.metrics.general.resident_size", :gauge, description: "Resident memory size for a supervised process.", unit: "bytes")
16
+
17
+ def emit(metrics)
18
+ metrics.each_value do |general|
19
+ if value = general[:processor_utilization]
20
+ PROCESS_METRICS_GENERAL_PROCESSOR_UTILIZATION.emit(value)
21
+ end
22
+
23
+ if value = general[:total_size]
24
+ PROCESS_METRICS_GENERAL_TOTAL_SIZE.emit(value)
25
+ end
26
+
27
+ if value = general[:virtual_size]
28
+ PROCESS_METRICS_GENERAL_VIRTUAL_SIZE.emit(value)
29
+ end
30
+
31
+ if value = general[:resident_size]
32
+ PROCESS_METRICS_GENERAL_RESIDENT_SIZE.emit(value)
33
+ end
34
+ end
35
+
36
+ super
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "metrics/provider"
7
+ require "metrics/tags"
8
+
9
+ require_relative "../../../../../async/service/supervisor/utilization_monitor"
10
+
11
+ class Async::Service::Supervisor::UtilizationMonitor
12
+ Metrics::Provider(self) do
13
+ UTILIZATION = Metrics.metric("async.utilization", :gauge, description: "Active requests per worker.")
14
+ UTILIZATION_CONNECTIONS_ACTIVE = Metrics.metric("async.utilization.connections.active", :gauge, description: "The number of active connections.")
15
+ UTILIZATION_CONNECTIONS_TOTAL = Metrics.metric("async.utilization.connections.total", :gauge, description: "The total number of connections.")
16
+ UTILIZATION_REQUESTS_ACTIVE = Metrics.metric("async.utilization.requests.active", :gauge, description: "The number of active requests.")
17
+ UTILIZATION_REQUESTS_TOTAL = Metrics.metric("async.utilization.requests.total", :gauge, description: "The total number of requests.")
18
+ UTILIZATION_WORKERS = Metrics.metric("async.utilization.workers", :gauge, description: "The number of workers contributing utilization metrics.")
19
+
20
+ def emit(metrics)
21
+ metrics.each do |service_name, fields|
22
+ tags = Metrics::Tags.normalize(service: service_name)
23
+
24
+ if connections_active = fields[:connections_active]
25
+ UTILIZATION_CONNECTIONS_ACTIVE.emit(connections_active, tags: tags)
26
+ end
27
+
28
+ if connections_total = fields[:connections_total]
29
+ UTILIZATION_CONNECTIONS_TOTAL.emit(connections_total, tags: tags)
30
+ end
31
+
32
+ if requests_active = fields[:requests_active]
33
+ UTILIZATION_REQUESTS_ACTIVE.emit(requests_active, tags: tags)
34
+ end
35
+
36
+ if requests_total = fields[:requests_total]
37
+ UTILIZATION_REQUESTS_TOTAL.emit(requests_total, tags: tags)
38
+ end
39
+
40
+ if worker_count = fields[:worker_count]
41
+ UTILIZATION_WORKERS.emit(worker_count, tags: tags)
42
+
43
+ if worker_count > 0 and requests_active = fields[:requests_active]
44
+ UTILIZATION.emit(requests_active.to_f / worker_count, tags: tags)
45
+ end
46
+ end
47
+ end
48
+
49
+ super
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "supervisor/process_monitor"
7
+ require_relative "supervisor/utilization_monitor"
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "traces/provider"
7
+
8
+ require_relative "../../../../../async/service/supervisor/server"
9
+
10
+ Traces::Provider(Async::Service::Supervisor::Server) do
11
+ def remove(controller)
12
+ return super unless controller.id
13
+
14
+ attributes = {
15
+ "worker.id" => controller.id,
16
+ "worker.process_id" => controller.process_id,
17
+ "service.name" => controller.state[:name],
18
+ "monitor.count" => @monitors.size,
19
+ }
20
+
21
+ Traces.trace("async.service.supervisor.worker.remove", attributes: attributes) do
22
+ super
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "traces/provider"
7
+
8
+ require_relative "../../../../../async/service/supervisor/supervisor_controller"
9
+
10
+ Traces::Provider(Async::Service::Supervisor::SupervisorController) do
11
+ def register(worker, process_id:, state: {})
12
+ attributes = {
13
+ "worker.process_id" => process_id,
14
+ "service.name" => state[:name],
15
+ "monitor.count" => @server.monitors.size,
16
+ }
17
+
18
+ Traces.trace("async.service.supervisor.worker.register", attributes: attributes) do |span|
19
+ super.tap do |id|
20
+ span["worker.id"] = id
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "supervisor/server"
7
+ require_relative "supervisor/supervisor_controller"
data/readme.md CHANGED
@@ -28,6 +28,14 @@ Please see the [project documentation](https://socketry.github.io/async-service-
28
28
 
29
29
  Please see the [project releases](https://socketry.github.io/async-service-supervisor/releases/index) for all releases.
30
30
 
31
+ ### v0.17.0
32
+
33
+ - Add opt-in `metrics` and `traces` providers for supervisor process metrics, utilization metrics, and worker lifecycle tracing.
34
+
35
+ ### v0.16.0
36
+
37
+ - Add `ProcessMonitor#emit(metrics)` as an override point for subclasses to consume captured process metrics (e.g. emitting StatsD gauges).
38
+
31
39
  ### v0.15.0
32
40
 
33
41
  - Improve robustness and error handling of default monitors and server loop, ensuring that monitor failures either completely crash the server or retry appropriately, rather than leaving the server in a broken state.
@@ -65,18 +73,6 @@ Please see the [project releases](https://socketry.github.io/async-service-super
65
73
 
66
74
  - Close `Call` queue if asynchronous call fails during dispatch - further messages will fail with `ClosedQueueError`.
67
75
 
68
- ### v0.9.0
69
-
70
- - Better handling of write failures in `Connection::Call.dispatch`, ensuring we don't leak calls.
71
- - Robust monitor loop handling - restart on failure, and align loop iterations.
72
- - Disable memory sampler by default and use text output format.
73
- - Introduce support for redirecting dump output to logs.
74
-
75
- ### v0.8.0
76
-
77
- - Add `Async::Service::Supervisor::ProcessMonitor` for logging CPU and memory metrics periodically.
78
- - Fix documentation to use correct `maximum_size_limit:` parameter name for `MemoryMonitor` (was incorrectly documented as `limit:`).
79
-
80
76
  ## Contributing
81
77
 
82
78
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.17.0
4
+
5
+ - Add opt-in `metrics` and `traces` providers for supervisor process metrics, utilization metrics, and worker lifecycle tracing.
6
+
7
+ ## v0.16.0
8
+
9
+ - Add `ProcessMonitor#emit(metrics)` as an override point for subclasses to consume captured process metrics (e.g. emitting StatsD gauges).
10
+
3
11
  ## v0.15.0
4
12
 
5
13
  - Improve robustness and error handling of default monitors and server loop, ensuring that monitor failures either completely crash the server or retry appropriately, rather than leaving the server in a broken state.
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.15.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -176,6 +176,12 @@ files:
176
176
  - lib/async/service/supervisor/version.rb
177
177
  - lib/async/service/supervisor/worker.rb
178
178
  - lib/async/service/supervisor/worker_controller.rb
179
+ - lib/metrics/provider/async/service/supervisor.rb
180
+ - lib/metrics/provider/async/service/supervisor/process_monitor.rb
181
+ - lib/metrics/provider/async/service/supervisor/utilization_monitor.rb
182
+ - lib/traces/provider/async/service/supervisor.rb
183
+ - lib/traces/provider/async/service/supervisor/server.rb
184
+ - lib/traces/provider/async/service/supervisor/supervisor_controller.rb
179
185
  - license.md
180
186
  - readme.md
181
187
  - releases.md
@@ -199,7 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
205
  - !ruby/object:Gem::Version
200
206
  version: '0'
201
207
  requirements: []
202
- rubygems_version: 4.0.6
208
+ rubygems_version: 4.0.10
203
209
  specification_version: 4
204
210
  summary: A supervisor for managing multiple container processes.
205
211
  test_files: []
metadata.gz.sig CHANGED
Binary file