leopard 0.2.4 → 0.2.5
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
- data/.release-please-manifest.json +1 -1
- data/.version.txt +1 -1
- data/CHANGELOG.md +7 -0
- data/lib/leopard/metrics_server.rb +90 -0
- data/lib/leopard/nats_api_server.rb +4 -0
- data/lib/leopard/templates/prometheus_metrics.erb +17 -0
- data/lib/leopard/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb81d09b4b0709f3f290009e42e9fa1080e6b33e1fa578473b789875cfa44054
|
|
4
|
+
data.tar.gz: 0f1d6d756f9a60cb9cd9f6a7072e6c9e2fcd1060faed0121ad6a3d27496a3ea6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c584384a406f264f2e3bb20c0a40fd75506e7f407985f7b4fb42166899fce3b233f99df795e6e75a4c973f6369c93cb10a9eb29f4453a4cd88c19680d9839ed2
|
|
7
|
+
data.tar.gz: 4bdbdfc0debc18a1bbd7ef8e636e4ad2019fa593fd083a47683f48bfb099302552e528eac530f99e464f5347c0475d1c49a874aa4021aaf5dcb2c4a4e321fe68
|
data/.version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.5](https://github.com/rubyists/leopard/compare/v0.2.4...v0.2.5) (2026-04-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add optional prometheus metrics endpoint with saturation metrics ([#42](https://github.com/rubyists/leopard/issues/42)) ([fcd767a](https://github.com/rubyists/leopard/commit/fcd767ab5438c92bf50800d50027b43b7d0f9f0e))
|
|
9
|
+
|
|
3
10
|
## [0.2.4](https://github.com/rubyists/leopard/compare/v0.2.3...v0.2.4) (2026-03-31)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'erb'
|
|
5
|
+
|
|
6
|
+
module Rubyists
|
|
7
|
+
module Leopard
|
|
8
|
+
module MetricsServer
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def start_metrics_server(workers)
|
|
12
|
+
port = ENV.fetch('LEOPARD_METRICS_PORT', '9394').to_i
|
|
13
|
+
Thread.new do
|
|
14
|
+
server = TCPServer.new(port)
|
|
15
|
+
logger.info "Metrics server listening on :#{port}"
|
|
16
|
+
loop { Thread.new(server.accept) { |client| handle_metrics_client(client, workers) } }
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
logger.error "Metrics server error: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def handle_metrics_client(client, workers)
|
|
23
|
+
request_line = client.gets
|
|
24
|
+
loop { break if (client.gets || '').chomp.empty? }
|
|
25
|
+
write_metrics_response(client, request_line, workers)
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
logger.warn "Metrics request error: #{e.message}"
|
|
28
|
+
ensure
|
|
29
|
+
close_client(client)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def close_client(client)
|
|
33
|
+
client.close
|
|
34
|
+
rescue StandardError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def write_metrics_response(client, request_line, workers)
|
|
39
|
+
if request_line&.start_with?('GET /metrics')
|
|
40
|
+
body = prometheus_metrics(workers)
|
|
41
|
+
client.write "HTTP/1.1 200 OK\r\n" \
|
|
42
|
+
"Content-Type: text/plain; version=0.0.4\r\n" \
|
|
43
|
+
"Content-Length: #{body.bytesize}\r\n\r\n#{body}"
|
|
44
|
+
else
|
|
45
|
+
client.write "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def prometheus_metrics(workers)
|
|
50
|
+
metrics = collect_prometheus_metrics(workers)
|
|
51
|
+
render_metrics_template(metrics)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def collect_prometheus_metrics(workers)
|
|
55
|
+
busy = Hash.new(0)
|
|
56
|
+
pending = Hash.new(0)
|
|
57
|
+
workers.each { |w| accumulate_worker_metrics(w, busy, pending) }
|
|
58
|
+
{
|
|
59
|
+
busy:,
|
|
60
|
+
pending:,
|
|
61
|
+
subjects: (busy.keys | pending.keys).sort,
|
|
62
|
+
total: workers.size,
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def accumulate_worker_metrics(worker, busy, pending)
|
|
67
|
+
service = worker.instance_variable_get(:@service)
|
|
68
|
+
return unless service
|
|
69
|
+
|
|
70
|
+
service.endpoints.each do |ep|
|
|
71
|
+
# TODO: use ep.handler once nats-pure.rb adds attr_reader :handler to NATS::Service::Endpoint
|
|
72
|
+
sub = ep.instance_variable_get(:@handler)
|
|
73
|
+
next unless sub
|
|
74
|
+
|
|
75
|
+
subj = ep.subject.to_s
|
|
76
|
+
busy[subj] += sub.concurrency_semaphore.available_permits.zero? ? 1 : 0
|
|
77
|
+
pending[subj] += sub.pending_queue&.size.to_i
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def render_metrics_template(metrics)
|
|
82
|
+
ERB.new(File.read(metrics_template_path), trim_mode: '-').result_with_hash(metrics)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def metrics_template_path
|
|
86
|
+
File.expand_path('templates/prometheus_metrics.erb', __dir__)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -6,6 +6,7 @@ require 'dry/configurable'
|
|
|
6
6
|
require 'concurrent'
|
|
7
7
|
require_relative '../leopard'
|
|
8
8
|
require_relative 'message_wrapper'
|
|
9
|
+
require_relative 'metrics_server'
|
|
9
10
|
|
|
10
11
|
module Rubyists
|
|
11
12
|
module Leopard
|
|
@@ -24,6 +25,8 @@ module Rubyists
|
|
|
24
25
|
Endpoint = Struct.new(:name, :subject, :queue, :group, :handler)
|
|
25
26
|
|
|
26
27
|
module ClassMethods
|
|
28
|
+
include MetricsServer
|
|
29
|
+
|
|
27
30
|
def endpoints = @endpoints ||= []
|
|
28
31
|
def groups = @groups ||= {}
|
|
29
32
|
def middleware = @middleware ||= []
|
|
@@ -78,6 +81,7 @@ module Rubyists
|
|
|
78
81
|
pool = spawn_instances(nats_url, service_opts, instances, workers, blocking)
|
|
79
82
|
logger.info 'Setting up signal trap...'
|
|
80
83
|
trap_signals(workers, pool)
|
|
84
|
+
start_metrics_server(workers) if ENV['LEOPARD_METRICS_PORT']
|
|
81
85
|
return pool unless blocking
|
|
82
86
|
|
|
83
87
|
sleep
|
|
@@ -0,0 +1,17 @@
|
|
|
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] %>
|
|
5
|
+
<% end -%>
|
|
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 %>
|
|
11
|
+
<% end -%>
|
|
12
|
+
|
|
13
|
+
# HELP leopard_subject_pending_messages Messages pending processing across all instances
|
|
14
|
+
# TYPE leopard_subject_pending_messages gauge
|
|
15
|
+
<% subjects.each do |subject| -%>
|
|
16
|
+
leopard_subject_pending_messages{subject="<%= subject %>"} <%= pending[subject] %>
|
|
17
|
+
<% end -%>
|
data/lib/leopard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leopard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- bougyman
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-04-16 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: concurrent-ruby
|
|
@@ -103,8 +103,10 @@ files:
|
|
|
103
103
|
- lib/leopard.rb
|
|
104
104
|
- lib/leopard/errors.rb
|
|
105
105
|
- lib/leopard/message_wrapper.rb
|
|
106
|
+
- lib/leopard/metrics_server.rb
|
|
106
107
|
- lib/leopard/nats_api_server.rb
|
|
107
108
|
- lib/leopard/settings.rb
|
|
109
|
+
- lib/leopard/templates/prometheus_metrics.erb
|
|
108
110
|
- lib/leopard/version.rb
|
|
109
111
|
homepage: https://github.com/rubyists/leopard
|
|
110
112
|
licenses:
|