leopard 0.2.7 → 0.2.8

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: b477f10d51aef7a541a2a08d615c4ab03c12393edee90ef4d46265df76727c3a
4
- data.tar.gz: 5ad3323e0fd8a1f3f09fc9dd648bc21c05b7b0d21a51bc46cef8fc27c379a745
3
+ metadata.gz: e28fb312a603ba19a413cdc3c9b62e702ec70daa6870bd833b182dd82a96b147
4
+ data.tar.gz: 5dcb6ade2032068fd4daaf15b7953ae8ad3e81d352d72a6945e3452fb16b762a
5
5
  SHA512:
6
- metadata.gz: 44c692417837a8d9e1c6e6ed1fa84e1c7eb096367b7617e729e8068d5e93c7a01d1ac5414a5d01945c37180e2c918c3aba08122af73b9ad2010773656d6a535f
7
- data.tar.gz: caff9b0df3beb7a16bd8d8c4830257f24ad1dec98fb57d3b5dba6847892f84cda089a7fe85d1368fb64834996ab69cc1831383b6d0c1d7ca3e7b44cb536a99b2
6
+ metadata.gz: a85035745ea9c0470764ea48c9127a60ae8459ba88d01158c33f22fab74aba81d5781d1c44bbbc09a2bcb53f0ae8787b2434c2ecf16063a0300ecfb3ce63bf7b
7
+ data.tar.gz: bcb763a8fc34511e78e9764b648ac75e24cf6370075550a958eeb69fb2cd0a738232c488fc5f06cf7ffe97b004a5d499f70e4a972c8e3aef5c09475d58135535
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.2.7"
2
+ ".": "0.2.8"
3
3
  }
data/.version.txt CHANGED
@@ -1 +1 @@
1
- 0.2.7
1
+ 0.2.8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.8](https://github.com/rubyists/leopard/compare/v0.2.7...v0.2.8) (2026-05-19)
4
+
5
+
6
+ ### Features
7
+
8
+ * improved leopard saturation metrics ([2303ef2](https://github.com/rubyists/leopard/commit/2303ef223b6b8150d728d827592744bbc75fc1a3))
9
+ * improved leopard saturation metrics ([c143056](https://github.com/rubyists/leopard/commit/c143056683f694eba1704acb84139095887515f4))
10
+
3
11
  ## [0.2.7](https://github.com/rubyists/leopard/compare/v0.2.6...v0.2.7) (2026-04-29)
4
12
 
5
13
 
@@ -80,45 +80,62 @@ module Rubyists
80
80
  render_metrics_template(metrics)
81
81
  end
82
82
 
83
- # Aggregates per-subject worker utilization metrics.
83
+ # Aggregates per-worker, per-subject saturation metrics and per-worker executor metrics.
84
84
  #
85
85
  # @param workers [Array<Object>] Active Leopard worker instances to observe.
86
86
  #
87
87
  # @return [Hash{Symbol => Object}] Metric hashes for the Prometheus template.
88
88
  def collect_prometheus_metrics(workers)
89
- busy = Hash.new(0)
90
- pending = Hash.new(0)
91
- workers.each { |w| accumulate_worker_metrics(w, busy, pending) }
92
- {
93
- busy:,
94
- pending:,
95
- subjects: (busy.keys | pending.keys).sort,
96
- total: workers.size,
97
- }
89
+ subject_metrics = []
90
+ executors = []
91
+
92
+ workers.each_with_index do |w, i|
93
+ accumulate_worker_metrics(w, i, subject_metrics)
94
+ ex = w.instance_variable_get(:@client)&.subscription_executor
95
+ executors << { worker: i, executor: ex } if ex
96
+ end
97
+
98
+ { subject_metrics:, executors: }
98
99
  end
99
100
 
100
- # Adds one worker's endpoint saturation metrics to the aggregate hashes.
101
+ # Appends one worker's per-subject slot metrics to the subject_metrics array.
101
102
  #
102
103
  # @param worker [Object] A Leopard worker instance.
103
- # @param busy [Hash{String => Integer}] Subject-to-busy-worker counts.
104
- # @param pending [Hash{String => Integer}] Subject-to-pending-message counts.
104
+ # @param worker_index [Integer] Position of this worker in the workers array.
105
+ # @param subject_metrics [Array<Hash>] Accumulator for per-worker-per-subject metric rows.
105
106
  #
106
107
  # @return [void]
107
- def accumulate_worker_metrics(worker, busy, pending)
108
+ def accumulate_worker_metrics(worker, worker_index, subject_metrics)
108
109
  service = worker.instance_variable_get(:@service)
109
110
  return unless service
110
111
 
111
- service.endpoints.each do |ep|
112
- # TODO: use ep.handler once nats-pure.rb adds attr_reader :handler to NATS::Service::Endpoint
113
- sub = ep.instance_variable_get(:@handler)
114
- next unless sub
115
-
116
- subj = ep.subject.to_s
117
- busy[subj] += sub.concurrency_semaphore.available_permits.zero? ? 1 : 0
118
- pending[subj] += sub.pending_queue&.size.to_i
112
+ service.endpoints.each do |endpoint|
113
+ row = endpoint_subject_metrics(endpoint, worker_index)
114
+ subject_metrics << row if row
119
115
  end
120
116
  end
121
117
 
118
+ # Builds a per-worker-per-subject metric row from a single endpoint, or nil if not yet active.
119
+ #
120
+ # @param endpoint [Object] A NATS service endpoint.
121
+ # @param worker_index [Integer] Position of the owning worker in the workers array.
122
+ #
123
+ # @return [Hash, nil]
124
+ def endpoint_subject_metrics(endpoint, worker_index)
125
+ # TODO: use endpoint.handler once nats-pure.rb adds attr_reader :handler to NATS::Service::Endpoint
126
+ sub = endpoint.instance_variable_get(:@handler)
127
+ return unless sub
128
+
129
+ concurrency = sub.instance_variable_get(:@processing_concurrency).to_i
130
+ {
131
+ worker: worker_index,
132
+ subject: endpoint.subject.to_s,
133
+ busy_slots: concurrency - sub.concurrency_semaphore.available_permits,
134
+ capacity_slots: concurrency,
135
+ pending: sub.pending_queue&.size.to_i,
136
+ }
137
+ end
138
+
122
139
  # Renders the metrics ERB template with aggregated metric data.
123
140
  #
124
141
  # @param metrics [Hash{Symbol => Object}] Aggregated metric data for template rendering.
@@ -1,17 +1,35 @@
1
- # HELP leopard_subject_busy_instances Instances currently processing a message on this subject
2
- # TYPE leopard_subject_busy_instances gauge
3
- <% subjects.each do |subject| -%>
4
- leopard_subject_busy_instances{subject="<%= subject %>"} <%= busy[subject] %>
1
+ # HELP leopard_subject_busy_slots Thread slots actively processing a message for this subject on this worker
2
+ # TYPE leopard_subject_busy_slots gauge
3
+ <% subject_metrics.each do |m| -%>
4
+ leopard_subject_busy_slots{subject="<%= m[:subject] %>",worker="<%= m[:worker] %>"} <%= m[:busy_slots] %>
5
5
  <% end -%>
6
6
 
7
- # HELP leopard_subject_total_instances Total Leopard instances in this process
8
- # TYPE leopard_subject_total_instances gauge
9
- <% subjects.each do |subject| -%>
10
- leopard_subject_total_instances{subject="<%= subject %>"} <%= total %>
7
+ # HELP leopard_subject_capacity_slots Total thread slots allocated for this subject on this worker
8
+ # TYPE leopard_subject_capacity_slots gauge
9
+ <% subject_metrics.each do |m| -%>
10
+ leopard_subject_capacity_slots{subject="<%= m[:subject] %>",worker="<%= m[:worker] %>"} <%= m[:capacity_slots] %>
11
11
  <% end -%>
12
12
 
13
- # HELP leopard_subject_pending_messages Messages pending processing across all instances
13
+ # HELP leopard_subject_pending_messages Messages waiting to acquire a processing slot for this subject on this worker
14
14
  # TYPE leopard_subject_pending_messages gauge
15
- <% subjects.each do |subject| -%>
16
- leopard_subject_pending_messages{subject="<%= subject %>"} <%= pending[subject] %>
15
+ <% subject_metrics.each do |m| -%>
16
+ leopard_subject_pending_messages{subject="<%= m[:subject] %>",worker="<%= m[:worker] %>"} <%= m[:pending] %>
17
+ <% end -%>
18
+
19
+ # HELP leopard_executor_active_threads Approximate number of active threads in the subscription executor (concurrent-ruby active_count is approximate)
20
+ # TYPE leopard_executor_active_threads gauge
21
+ <% executors.each do |e| -%>
22
+ leopard_executor_active_threads{worker="<%= e[:worker] %>"} <%= e[:executor].active_count %>
23
+ <% end -%>
24
+
25
+ # HELP leopard_executor_max_threads Maximum threads in the subscription executor; queued_tasks goes positive when sum of capacity_slots across subjects exceeds this value
26
+ # TYPE leopard_executor_max_threads gauge
27
+ <% executors.each do |e| -%>
28
+ leopard_executor_max_threads{worker="<%= e[:worker] %>"} <%= e[:executor].max_length %>
29
+ <% end -%>
30
+
31
+ # HELP leopard_executor_queued_tasks Tasks holding a semaphore permit but waiting for a free executor thread; nonzero only when the executor pool is fully saturated
32
+ # TYPE leopard_executor_queued_tasks gauge
33
+ <% executors.each do |e| -%>
34
+ leopard_executor_queued_tasks{worker="<%= e[:worker] %>"} <%= e[:executor].queue_length %>
17
35
  <% end -%>
@@ -3,7 +3,7 @@
3
3
  module Rubyists
4
4
  module Leopard
5
5
  # x-release-please-start-version
6
- VERSION = '0.2.7'
6
+ VERSION = '0.2.8'
7
7
  # x-release-please-end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leopard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - bougyman