async-utilization 0.1.0 → 0.3.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: 5ddd4fc21308eeba6b47fbd44df20d4694ee4bd5b78488a44395d0ab5e2da500
4
- data.tar.gz: 8699da59ebaa0497ca112cf4f1ca65805935d11061b9ed6e1475a56f05a58795
3
+ metadata.gz: 10d5277f8a02e1f72a8466b86dae5245d8beacf60f7310b5f4b2fbbe829ad2b2
4
+ data.tar.gz: 8e1519b41206468ffcd4c5197aecd0843882e5c637db9db24fce67f87fd34386
5
5
  SHA512:
6
- metadata.gz: '079cbc3d3a93cda55ebcce48f93c23e5460db7ae8019336b442d0a24ffd783ec6e614cd2d36fc7c937f388cf729ce7d4d6944c5aefae754c4165560a4f353a98'
7
- data.tar.gz: '049b0ea1ae9d8bc15c12aa69c3d595e32424d1d200003425067f70111f804079fda1f23ef21cc3ed0a0a0d698b36e3b00cbe0cb9ce3803dde197c8e03c8599b1'
6
+ metadata.gz: aff8c89a734ee90bb716186f4a9133373bc222c26a0b4bb089152a9bce6bd2d76105a91a6c99f3a5333be4afef83ba3c88637e64b69a2629ab185e94b6035a4a
7
+ data.tar.gz: 853481053488156d6baa7be3cff2f7c19e9bad60028e44649f96bfc978c07c173065bb0d8bd17105ae1248204d569bc115fcc0e146ce3ee25886b8efcc2580e9
checksums.yaml.gz.sig CHANGED
Binary file
@@ -43,30 +43,38 @@ module Async
43
43
  @cached_buffer = nil
44
44
  end
45
45
 
46
- # Increment the metric value, optionally with a block that auto-decrements.
46
+ # Increment the metric value.
47
47
  #
48
48
  # Uses the fast path (direct buffer write) when cache is valid and observer is available.
49
49
  #
50
- # @yield Optional block - if provided, decrements the field after the block completes.
51
50
  # @returns [Integer] The new value of the field.
52
- def increment(&block)
51
+ def increment
53
52
  @guard.synchronize do
54
53
  @value += 1
55
54
  write_direct(@value)
56
55
  end
57
56
 
58
- if block_given?
59
- begin
60
- yield
61
- ensure
62
- # Decrement after block completes
63
- decrement
64
- end
65
- end
66
-
67
57
  @value
68
58
  end
69
59
 
60
+ # Track an operation: increment before the block, decrement after it completes.
61
+ #
62
+ # Returns the block's return value. Use for active/count metrics that should
63
+ # reflect the number of operations currently in progress.
64
+ #
65
+ # @yield The operation to track.
66
+ # @returns [Object] The block's return value.
67
+ def track(&block)
68
+ raise ArgumentError, "block required" unless block_given?
69
+
70
+ increment
71
+ begin
72
+ yield
73
+ ensure
74
+ decrement
75
+ end
76
+ end
77
+
70
78
  # Decrement the metric value.
71
79
  #
72
80
  # Uses the fast path (direct buffer write) when cache is valid and observer is available.
@@ -3,8 +3,6 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
- require "thread/local"
7
-
8
6
  module Async
9
7
  module Utilization
10
8
  # Registry for emitting utilization metrics.
@@ -12,8 +10,9 @@ module Async
12
10
  # The registry tracks values directly and notifies a registered observer
13
11
  # when values change. The observer (like Observer) can write to its backend.
14
12
  #
15
- # Each thread gets its own instance of the registry, providing
16
- # thread-local behavior through the thread-local gem.
13
+ # Registries should be explicitly created and passed to components that need them.
14
+ # In service contexts, registries are typically created via the evaluator and
15
+ # shared across components within the same service instance.
17
16
  #
18
17
  # When an observer is added, it is immediately notified of all current values
19
18
  # so it can sync its state. When values change, the observer is notified.
@@ -23,7 +22,7 @@ module Async
23
22
  #
24
23
  # # Emit metrics - values tracked in registry
25
24
  # registry.increment(:total_requests)
26
- # registry.increment(:active_requests) do
25
+ # registry.track(:active_requests) do
27
26
  # # Handle request - auto-decrements when block completes
28
27
  # end
29
28
  #
@@ -36,7 +35,6 @@ module Async
36
35
  # observer = Async::Utilization::Observer.open(schema, "/path/to/shm", 4096, 0)
37
36
  # registry.observer = observer
38
37
  class Registry
39
- extend Thread::Local
40
38
 
41
39
  # Initialize a new registry.
42
40
  def initialize
@@ -95,15 +93,25 @@ module Async
95
93
  metric(field).set(value)
96
94
  end
97
95
 
98
- # Increment a field value, optionally with a block that auto-decrements.
96
+ # Increment a field value.
99
97
  #
100
98
  # Delegates to the metric instance for the given field.
101
99
  #
102
100
  # @parameter field [Symbol] The field name to increment.
103
- # @yield Optional block - if provided, decrements the field after the block completes.
104
101
  # @returns [Integer] The new value of the field.
105
- def increment(field, &block)
106
- metric(field).increment(&block)
102
+ def increment(field)
103
+ metric(field).increment
104
+ end
105
+
106
+ # Track an operation: increment before the block, decrement after it completes.
107
+ #
108
+ # Delegates to the metric instance for the given field.
109
+ #
110
+ # @parameter field [Symbol] The field name to track.
111
+ # @yield The operation to track.
112
+ # @returns [Object] The block's return value.
113
+ def track(field, &block)
114
+ metric(field).track(&block)
107
115
  end
108
116
 
109
117
  # Decrement a field value.
@@ -7,6 +7,6 @@
7
7
  module Async
8
8
  # @namespace
9
9
  module Utilization
10
- VERSION = "0.1.0"
10
+ VERSION = "0.3.0"
11
11
  end
12
12
  end
@@ -15,42 +15,9 @@ module Async
15
15
  #
16
16
  # This module provides a convenient interface for tracking utilization metrics
17
17
  # that can be synchronized to shared memory for inter-process communication.
18
- # Each thread gets its own instance of the underlying {Registry}, providing
19
- # thread-local behavior.
18
+ # Registries should be explicitly created and passed to components that need them.
20
19
  #
21
20
  # See the {file:guides/getting-started/readme.md Getting Started} guide for usage examples.
22
21
  module Utilization
23
- # Set the observer for utilization metrics.
24
- #
25
- # When an observer is set, it is notified of all current metric values
26
- # so it can sync its state. The observer must implement `set(field, value)`.
27
- #
28
- # Delegates to the thread-local {Registry} instance.
29
- #
30
- # @parameter observer [#set] The observer to set.
31
- def self.observer=(observer)
32
- Registry.instance.observer = observer
33
- end
34
-
35
- # Get a cached metric reference for a field.
36
- #
37
- # Returns a {Metric} instance that caches all details needed for fast writes
38
- # to shared memory, avoiding hash lookups on the fast path.
39
- #
40
- # This is the recommended way to access metrics for optimal performance.
41
- #
42
- # Delegates to the thread-local {Registry} instance.
43
- #
44
- # @parameter field [Symbol] The field name to get a metric for.
45
- # @returns [Metric] A metric instance for the given field.
46
- # @example Get a metric and increment it:
47
- # current_requests = Async::Utilization.metric(:current_requests)
48
- # current_requests.increment
49
- # current_requests.increment do
50
- # # Handle request - auto-decrements when block completes
51
- # end
52
- def self.metric(field)
53
- Registry.instance.metric(field)
54
- end
55
22
  end
56
23
  end
data/readme.md CHANGED
@@ -14,6 +14,10 @@ Please see the [project documentation](https://socketry.github.io/async-utilizat
14
14
 
15
15
  Please see the [project releases](https://socketry.github.io/async-utilization/releases/index) for all releases.
16
16
 
17
+ ### v0.3.0
18
+
19
+ - Introduce `Metric#track{...}` for increment -\> decrement.
20
+
17
21
  ### v0.1.0
18
22
 
19
23
  - Initial implementation.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.3.0
4
+
5
+ - Introduce `Metric#track{...}` for increment -\> decrement.
6
+
3
7
  ## v0.1.0
4
8
 
5
9
  - Initial implementation.
@@ -29,28 +29,24 @@ describe Async::Utilization::Metric do
29
29
  Async::Utilization::Observer.open(schema, shm_path, segment_size, offset)
30
30
  end
31
31
 
32
+ let(:registry) {Async::Utilization::Registry.new}
33
+
32
34
  before do
33
35
  File.open(shm_path, "w+b") do |file|
34
36
  file.truncate(file_size)
35
37
  end
36
-
37
- # Reset the registry to ensure clean state between tests
38
- registry = Async::Utilization::Registry.instance
39
- registry.instance_variable_set(:@values, Hash.new(0))
40
- registry.instance_variable_set(:@metrics, {})
41
- registry.instance_variable_set(:@observer, nil)
42
38
  end
43
39
 
44
40
  it "can create a metric from a field name" do
45
- metric = Async::Utilization.metric(:total_requests)
41
+ metric = registry.metric(:total_requests)
46
42
 
47
43
  expect(metric).to be_a(Async::Utilization::Metric)
48
44
  expect(metric.name).to be == :total_requests
49
45
  end
50
46
 
51
47
  it "can increment a metric" do
52
- Async::Utilization.observer = observer
53
- metric = Async::Utilization.metric(:total_requests)
48
+ registry.observer = observer
49
+ metric = registry.metric(:total_requests)
54
50
 
55
51
  value = metric.increment
56
52
  expect(value).to be == 1
@@ -62,8 +58,8 @@ describe Async::Utilization::Metric do
62
58
  end
63
59
 
64
60
  it "can decrement a metric" do
65
- Async::Utilization.observer = observer
66
- metric = Async::Utilization.metric(:total_requests)
61
+ registry.observer = observer
62
+ metric = registry.metric(:total_requests)
67
63
 
68
64
  metric.increment
69
65
  metric.increment
@@ -74,8 +70,8 @@ describe Async::Utilization::Metric do
74
70
  end
75
71
 
76
72
  it "can set a metric value" do
77
- Async::Utilization.observer = observer
78
- metric = Async::Utilization.metric(:total_requests)
73
+ registry.observer = observer
74
+ metric = registry.metric(:total_requests)
79
75
 
80
76
  metric.set(42)
81
77
  expect(metric.value).to be == 42
@@ -84,23 +80,62 @@ describe Async::Utilization::Metric do
84
80
  expect(metric.value).to be == 100
85
81
  end
86
82
 
87
- it "can increment with auto-decrement block" do
88
- Async::Utilization.observer = observer
89
- metric = Async::Utilization.metric(:active_requests)
83
+ it "can track an operation with auto-decrement" do
84
+ registry.observer = observer
85
+ metric = registry.metric(:active_requests)
90
86
 
91
- metric.increment do
87
+ metric.track do
92
88
  expect(metric.value).to be == 1
93
89
  end
94
90
 
95
91
  expect(metric.value).to be == 0
96
92
  end
97
93
 
98
- it "decrements even if block raises an error" do
99
- Async::Utilization.observer = observer
100
- metric = Async::Utilization.metric(:active_requests)
94
+ it "returns the metric value when increment is called" do
95
+ registry.observer = observer
96
+ metric = registry.metric(:total_requests)
97
+
98
+ result = metric.increment
99
+ expect(result).to be == 1
100
+ expect(result).to be == metric.value
101
+
102
+ result = metric.increment
103
+ expect(result).to be == 2
104
+ expect(result).to be == metric.value
105
+ end
106
+
107
+ it "returns the block's return value when track is called" do
108
+ registry.observer = observer
109
+ metric = registry.metric(:active_requests)
110
+
111
+ # Block returns a string
112
+ result = metric.track do
113
+ "connection_object"
114
+ end
115
+ expect(result).to be == "connection_object"
116
+ expect(metric.value).to be == 0 # Should be decremented after block
117
+
118
+ # Block returns an integer
119
+ result = metric.track do
120
+ 42
121
+ end
122
+ expect(result).to be == 42
123
+ expect(metric.value).to be == 0 # Should be decremented after block
124
+
125
+ # Block returns nil
126
+ result = metric.track do
127
+ nil
128
+ end
129
+ expect(result).to be == nil
130
+ expect(metric.value).to be == 0 # Should be decremented after block
131
+ end
132
+
133
+ it "decrements even if track block raises an error" do
134
+ registry.observer = observer
135
+ metric = registry.metric(:active_requests)
101
136
 
102
137
  begin
103
- metric.increment do
138
+ metric.track do
104
139
  raise "Test error"
105
140
  end
106
141
  rescue => error
@@ -110,9 +145,17 @@ describe Async::Utilization::Metric do
110
145
  expect(metric.value).to be == 0
111
146
  end
112
147
 
148
+ it "raises ArgumentError when track is called without a block" do
149
+ metric = registry.metric(:active_requests)
150
+
151
+ expect do
152
+ metric.track
153
+ end.to raise_exception(ArgumentError, message: be == "block required")
154
+ end
155
+
113
156
  it "writes directly to shared memory when observer is set" do
114
- Async::Utilization.observer = observer
115
- metric = Async::Utilization.metric(:total_requests)
157
+ registry.observer = observer
158
+ metric = registry.metric(:total_requests)
116
159
 
117
160
  metric.set(42)
118
161
 
@@ -122,8 +165,8 @@ describe Async::Utilization::Metric do
122
165
  end
123
166
 
124
167
  it "invalidates cache when observer changes" do
125
- Async::Utilization.observer = observer
126
- metric = Async::Utilization.metric(:total_requests)
168
+ registry.observer = observer
169
+ metric = registry.metric(:total_requests)
127
170
 
128
171
  # Set a value - cache should be built
129
172
  metric.set(10)
@@ -139,7 +182,7 @@ describe Async::Utilization::Metric do
139
182
  new_observer = Async::Utilization::Observer.open(new_schema, new_shm_path, segment_size, 0)
140
183
 
141
184
  # Change observer - cache should be invalidated
142
- Async::Utilization.observer = new_observer
185
+ registry.observer = new_observer
143
186
 
144
187
  # Set a new value - cache should be rebuilt
145
188
  metric.set(20)
@@ -151,7 +194,7 @@ describe Async::Utilization::Metric do
151
194
  end
152
195
 
153
196
  it "works without an observer" do
154
- metric = Async::Utilization.metric(:total_requests)
197
+ metric = registry.metric(:total_requests)
155
198
 
156
199
  # Should work fine without observer (uses fallback path)
157
200
  metric.increment
@@ -161,21 +204,21 @@ describe Async::Utilization::Metric do
161
204
  expect(metric.value).to be == 5
162
205
 
163
206
  # Set observer and verify it works with fast path
164
- Async::Utilization.observer = observer
207
+ registry.observer = observer
165
208
  metric.set(10)
166
209
  expect(metric.value).to be == 10
167
210
  end
168
211
 
169
212
  it "returns the same metric instance for the same field" do
170
- metric1 = Async::Utilization.metric(:total_requests)
171
- metric2 = Async::Utilization.metric(:total_requests)
213
+ metric1 = registry.metric(:total_requests)
214
+ metric2 = registry.metric(:total_requests)
172
215
 
173
216
  expect(metric1).to be == metric2
174
217
  end
175
218
 
176
219
  it "falls back to observer.set when write_direct fails" do
177
- Async::Utilization.observer = observer
178
- metric = Async::Utilization.metric(:total_requests)
220
+ registry.observer = observer
221
+ metric = registry.metric(:total_requests)
179
222
 
180
223
  # Force cache to be invalid by invalidating it
181
224
  metric.invalidate
@@ -190,8 +233,8 @@ describe Async::Utilization::Metric do
190
233
  end
191
234
 
192
235
  it "handles write errors gracefully" do
193
- Async::Utilization.observer = observer
194
- metric = Async::Utilization.metric(:total_requests)
236
+ registry.observer = observer
237
+ metric = registry.metric(:total_requests)
195
238
 
196
239
  # Set a value first to build the cache
197
240
  metric.set(10)
@@ -37,17 +37,17 @@ describe Async::Utilization::Registry do
37
37
  expect(registry.values[:test_field]).to be == 42
38
38
  end
39
39
 
40
- it "can auto-decrement with a block" do
41
- registry.increment(:test_field) do
40
+ it "can track an operation with auto-decrement" do
41
+ registry.track(:test_field) do
42
42
  expect(registry.values[:test_field]).to be == 1
43
43
  end
44
44
 
45
45
  expect(registry.values[:test_field]).to be == 0
46
46
  end
47
47
 
48
- it "decrements even if block raises an error" do
48
+ it "decrements even if track block raises an error" do
49
49
  begin
50
- registry.increment(:test_field) do
50
+ registry.track(:test_field) do
51
51
  raise "Error!"
52
52
  end
53
53
  rescue
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-utilization
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -97,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
97
  requirements:
98
98
  - - ">="
99
99
  - !ruby/object:Gem::Version
100
- version: '3.2'
100
+ version: '3.3'
101
101
  required_rubygems_version: !ruby/object:Gem::Requirement
102
102
  requirements:
103
103
  - - ">="
metadata.gz.sig CHANGED
Binary file