prom_multi_proc_rb 0.1.6 → 0.1.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 +4 -4
- data/.github/workflows/ci.yml +21 -0
- data/README.md +108 -51
- data/lib/prom_multi_proc/base.rb +24 -16
- data/lib/prom_multi_proc/collector.rb +23 -16
- data/lib/prom_multi_proc/counter.rb +4 -2
- data/lib/prom_multi_proc/gauge.rb +8 -6
- data/lib/prom_multi_proc/histogram.rb +3 -1
- data/lib/prom_multi_proc/proxy.rb +2 -0
- data/lib/prom_multi_proc/rails.rb +23 -39
- data/lib/prom_multi_proc/summary.rb +3 -1
- data/lib/prom_multi_proc/version.rb +3 -1
- data/lib/prom_multi_proc/writer.rb +36 -25
- data/lib/prom_multi_proc.rb +4 -3
- data/prom_multi_proc_rb.gemspec +11 -10
- metadata +28 -14
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a78925823a018eb95a68a84c00ff22a798689efd686d0b03e766499137b2cfd
|
|
4
|
+
data.tar.gz: e8f347f72c15e27fb4873d9bb0477385b179fe04b06bfb45c6e930d00bded430
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a21b01ee4305201ae371f81ccecf7fd3f6614d1ad90f9db0ab16ec93e57bf82114202bff8127fb02cf09c5a18bd6a91f20a0cf11edcffaba5dc417aef472ecbe
|
|
7
|
+
data.tar.gz: 71f3e7dad7f1045d59351be8e1661b20f1f220af6de3d3327b328cb443549ec5127d052921f3ea3dde7b151ecfe217f5882144014bf99fa1e16b00ac9897b9d4
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
ruby: ['3.2', '3.3']
|
|
14
|
+
name: Ruby ${{ matrix.ruby }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: ${{ matrix.ruby }}
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
- run: bundle exec rspec
|
data/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# prom_multi_proc_rb
|
|
2
2
|
|
|
3
|
-
[](https://github.com/atongen/prom_multi_proc_rb/actions/workflows/ci.yml)
|
|
4
4
|
[](https://badge.fury.io/rb/prom_multi_proc_rb)
|
|
5
5
|
|
|
6
|
-
Ruby client library for collecting
|
|
7
|
-
Designed for
|
|
8
|
-
|
|
9
|
-
by [prom_multi_proc](https://github.com/atongen/prom_multi_proc).
|
|
6
|
+
Ruby client library for collecting Prometheus metrics in multi-process applications.
|
|
7
|
+
Designed for forking servers (Unicorn, Puma). Writes metrics as JSON to a Unix socket
|
|
8
|
+
listened to by [prom_multi_proc](https://github.com/atongen/prom_multi_proc).
|
|
10
9
|
|
|
11
10
|
## Installation
|
|
12
11
|
|
|
@@ -28,93 +27,151 @@ Or install it yourself as:
|
|
|
28
27
|
|
|
29
28
|
### Define metrics
|
|
30
29
|
|
|
31
|
-
Create a
|
|
30
|
+
Create a JSON file to define the Prometheus metrics your application will track:
|
|
32
31
|
|
|
33
32
|
```json
|
|
34
33
|
[
|
|
35
34
|
{
|
|
36
35
|
"type": "counter",
|
|
37
|
-
"name": "
|
|
38
|
-
"help": "
|
|
39
|
-
"labels": [
|
|
40
|
-
"label1",
|
|
41
|
-
]
|
|
36
|
+
"name": "app_requests_total",
|
|
37
|
+
"help": "Total HTTP requests",
|
|
38
|
+
"labels": ["method", "status"]
|
|
42
39
|
},
|
|
43
40
|
{
|
|
44
41
|
"type": "gauge",
|
|
45
|
-
"name": "
|
|
46
|
-
"help": "
|
|
47
|
-
"labels": [
|
|
48
|
-
"label2",
|
|
49
|
-
]
|
|
42
|
+
"name": "app_workers_active",
|
|
43
|
+
"help": "Number of active workers"
|
|
50
44
|
},
|
|
51
45
|
{
|
|
52
46
|
"type": "histogram",
|
|
53
|
-
"name": "
|
|
54
|
-
"help": "
|
|
55
|
-
"labels": [
|
|
56
|
-
"label3",
|
|
57
|
-
]
|
|
47
|
+
"name": "app_request_duration_seconds",
|
|
48
|
+
"help": "Request duration in seconds",
|
|
49
|
+
"labels": ["method"]
|
|
58
50
|
},
|
|
59
51
|
{
|
|
60
52
|
"type": "summary",
|
|
61
|
-
"name": "
|
|
62
|
-
"help": "
|
|
63
|
-
"labels": [
|
|
64
|
-
"label4",
|
|
65
|
-
]
|
|
53
|
+
"name": "app_response_size_bytes",
|
|
54
|
+
"help": "Response size in bytes",
|
|
55
|
+
"labels": ["method"]
|
|
66
56
|
}
|
|
67
57
|
]
|
|
68
58
|
```
|
|
69
59
|
|
|
70
|
-
This file is
|
|
60
|
+
This file is shared by both the aggregator process and the Ruby client. Metric names
|
|
61
|
+
in the JSON file may or may not include the prefix — the client applies it automatically
|
|
62
|
+
if it is missing.
|
|
71
63
|
|
|
72
64
|
### Install and start the aggregator process
|
|
73
65
|
|
|
74
|
-
Download, install, and start
|
|
75
|
-
|
|
76
|
-
Make note of the socket location.
|
|
66
|
+
Download, install, and start [prom_multi_proc](https://github.com/atongen/prom_multi_proc)
|
|
67
|
+
using the metrics JSON definition file. Note the socket path.
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
The Ruby client functions normally if no aggregator is listening on the socket — failed
|
|
70
|
+
writes are logged and silently dropped so the host application is never interrupted.
|
|
80
71
|
|
|
81
|
-
### Collect metrics
|
|
72
|
+
### Collect metrics
|
|
82
73
|
|
|
83
|
-
Create a `PromMultiProc::Base` object
|
|
84
|
-
and begin collecting metrics:
|
|
74
|
+
Create a `PromMultiProc::Base` object and begin recording metrics:
|
|
85
75
|
|
|
86
76
|
```ruby
|
|
87
77
|
metrics = PromMultiProc::Base.new(
|
|
88
|
-
prefix:
|
|
89
|
-
socket:
|
|
90
|
-
metrics:
|
|
78
|
+
prefix: "app_",
|
|
79
|
+
socket: "/var/run/prom_multi_proc/metrics.sock",
|
|
80
|
+
metrics: "/etc/myapp/metrics.json",
|
|
91
81
|
batch_size: 10,
|
|
92
|
-
validate:
|
|
82
|
+
validate: true
|
|
93
83
|
)
|
|
94
84
|
|
|
95
|
-
metrics.
|
|
96
|
-
metrics.
|
|
85
|
+
metrics.requests_total.inc(method: "GET", status: "200")
|
|
86
|
+
metrics.request_duration_seconds.observe(0.042, method: "GET")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Configuration options
|
|
90
|
+
|
|
91
|
+
| Option | Default | Description |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `socket:` | _(required)_ | Path to the Unix socket of the aggregator daemon |
|
|
94
|
+
| `metrics:` | _(required)_ | Path to the JSON metric definition file |
|
|
95
|
+
| `prefix:` | `""` | Metric name prefix. Applied to names in the spec that don't already carry it. |
|
|
96
|
+
| `batch_size:` | `1` | Flush after accumulating this many messages. |
|
|
97
|
+
| `batch_timeout:` | `3` | Force-flush every N seconds even if batch is not full. |
|
|
98
|
+
| `validate:` | `false` | Raise `PromMultiProcError` on invalid labels/values instead of silently dropping |
|
|
99
|
+
|
|
100
|
+
#### Prefix behavior
|
|
101
|
+
|
|
102
|
+
The `prefix:` option works the same way as the `-metric-prefix` flag on the aggregator server:
|
|
103
|
+
if a metric name in the JSON spec already starts with the prefix, it is used as-is; otherwise
|
|
104
|
+
the prefix is prepended. This means you can use either full names or short names in your spec file.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# Both of these result in wire name "app_requests_total":
|
|
108
|
+
# spec: {"name": "app_requests_total", ...} → already has prefix, used as-is
|
|
109
|
+
# spec: {"name": "requests_total", ...} → prefix applied → "app_requests_total"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Multi / batch writes
|
|
113
|
+
|
|
114
|
+
Use `#multi` to record multiple metrics in a single atomic socket write:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
metrics.multi do |m|
|
|
118
|
+
m.requests_total.inc(method: "POST", status: "201")
|
|
119
|
+
m.workers_active.set(8)
|
|
120
|
+
m.request_duration_seconds.observe(0.015, method: "POST")
|
|
121
|
+
end
|
|
97
122
|
```
|
|
98
123
|
|
|
99
124
|
## Rails Usage
|
|
100
125
|
|
|
101
|
-
|
|
126
|
+
A Rails initializer helper is provided at `PromMultiProc::Rails.init`.
|
|
127
|
+
Create `config/initializers/prom_multi_proc.rb`:
|
|
102
128
|
|
|
103
129
|
```ruby
|
|
104
|
-
$
|
|
130
|
+
$prom = PromMultiProc::Rails.init
|
|
105
131
|
|
|
106
|
-
$
|
|
107
|
-
$
|
|
132
|
+
$prom.requests_total.inc(method: "GET", status: "200")
|
|
133
|
+
$prom.request_duration_seconds.observe(0.042, method: "GET")
|
|
108
134
|
```
|
|
109
135
|
|
|
110
|
-
|
|
136
|
+
`Rails.init` derives sensible defaults from the Rails environment (batch size, validate, socket
|
|
137
|
+
path, etc.) and accepts keyword arguments to override any of them:
|
|
111
138
|
|
|
112
|
-
|
|
139
|
+
```ruby
|
|
140
|
+
# Override prefix and bump batch size in production
|
|
141
|
+
$prom = PromMultiProc::Rails.init(prefix: "myapp_", batch_size: 50)
|
|
113
142
|
|
|
114
|
-
|
|
143
|
+
# Point at a non-default socket
|
|
144
|
+
$prom = PromMultiProc::Rails.init(socket: "/run/metrics/custom.sock")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The `PROM_MULTI_PROC_SOCKET` and `PROM_MULTI_PROC_DEFINITION_FILE` environment variables are
|
|
148
|
+
still respected as fallback defaults for `socket:` and `metrics:`, but any value passed directly
|
|
149
|
+
as a keyword argument takes precedence.
|
|
150
|
+
|
|
151
|
+
## Wire format
|
|
152
|
+
|
|
153
|
+
Each flush sends a JSON array over the Unix socket and then closes the connection:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
[
|
|
157
|
+
{"name":"app_requests_total","method":"inc","value":1.0,"label_values":["GET","200"]},
|
|
158
|
+
{"name":"app_workers_active","method":"set","value":8.0,"label_values":[]}
|
|
159
|
+
]
|
|
160
|
+
```
|
|
115
161
|
|
|
116
|
-
|
|
162
|
+
## Metric types and methods
|
|
117
163
|
|
|
118
|
-
|
|
164
|
+
| Type | Methods |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `counter` | `inc`, `add` |
|
|
167
|
+
| `gauge` | `set`, `inc`, `dec`, `add`, `sub`, `set_to_current_time` |
|
|
168
|
+
| `histogram` | `observe` |
|
|
169
|
+
| `summary` | `observe` |
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/atongen/prom_multi_proc_rb.
|
|
174
|
+
|
|
175
|
+
## License
|
|
119
176
|
|
|
120
|
-
|
|
177
|
+
Available as open source under the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/prom_multi_proc/base.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "logger"
|
|
2
4
|
require "concurrent"
|
|
3
5
|
|
|
@@ -5,8 +7,12 @@ module PromMultiProc
|
|
|
5
7
|
class Base
|
|
6
8
|
attr_reader :logger, :prefix, :writer
|
|
7
9
|
|
|
8
|
-
def initialize(socket:, metrics:, batch_size:
|
|
9
|
-
@prefix = prefix
|
|
10
|
+
def initialize(socket:, metrics:, batch_size: nil, batch_timeout: nil, logger: nil, validate: false, prefix: "")
|
|
11
|
+
@prefix = if prefix.empty? || prefix.end_with?("_")
|
|
12
|
+
prefix
|
|
13
|
+
else
|
|
14
|
+
"#{prefix}_"
|
|
15
|
+
end
|
|
10
16
|
@logger = logger || ::Logger.new(STDOUT)
|
|
11
17
|
|
|
12
18
|
unless File.socket?(socket)
|
|
@@ -36,9 +42,9 @@ module PromMultiProc
|
|
|
36
42
|
def multi
|
|
37
43
|
return unless block_given?
|
|
38
44
|
result = @multi_lock.synchronize do
|
|
39
|
-
Proxy.new(self)
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
proxy = Proxy.new(self)
|
|
46
|
+
yield(proxy)
|
|
47
|
+
proxy
|
|
42
48
|
end
|
|
43
49
|
@writer.write_multi(result.multis)
|
|
44
50
|
end
|
|
@@ -46,7 +52,7 @@ module PromMultiProc
|
|
|
46
52
|
private
|
|
47
53
|
|
|
48
54
|
def valid_metric?(name)
|
|
49
|
-
|
|
55
|
+
METRIC_RE.match?(name)
|
|
50
56
|
end
|
|
51
57
|
|
|
52
58
|
def get_specs(file)
|
|
@@ -68,36 +74,38 @@ module PromMultiProc
|
|
|
68
74
|
def process_spec!(spec)
|
|
69
75
|
klazz = TYPES[spec["type"].to_sym]
|
|
70
76
|
unless klazz
|
|
71
|
-
raise PromMultiProcError.new("
|
|
77
|
+
raise PromMultiProcError.new("Unknown type: #{spec.inspect}")
|
|
72
78
|
end
|
|
73
79
|
|
|
74
80
|
unless valid_metric?(spec["name"])
|
|
75
81
|
raise PromMultiProcError.new("Invalid name: #{spec.inspect}")
|
|
76
82
|
end
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
full_name = if prefix.empty? || spec["name"].start_with?(prefix)
|
|
85
|
+
spec["name"]
|
|
86
|
+
else
|
|
87
|
+
"#{prefix}#{spec["name"]}"
|
|
80
88
|
end
|
|
81
|
-
name =
|
|
89
|
+
name = full_name.sub(/\A#{Regexp.escape(prefix)}/, "").to_sym
|
|
82
90
|
|
|
83
91
|
unless spec["help"] && !spec["help"].strip.empty?
|
|
84
92
|
raise PromMultiProcError.new("Metric '#{spec['name']}' is missing help")
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
labels = (spec["labels"] || []).map(&:to_sym)
|
|
88
|
-
unless labels.all? { |l| valid_metric?(l)
|
|
96
|
+
unless labels.all? { |l| valid_metric?(l) }
|
|
89
97
|
raise PromMultiProcError.new("Invalid label: #{spec.inspect}")
|
|
90
98
|
end
|
|
91
99
|
|
|
92
|
-
if self.class.instance_methods(false).include?(name) || methods(false).include?(name)
|
|
93
|
-
raise PromMultiProcError.new("Metric method exists: #{name}")
|
|
94
|
-
end
|
|
95
|
-
|
|
96
100
|
if @metric_objects.key?(name)
|
|
97
101
|
raise PromMultiProcError.new("Metric already exists: #{name}")
|
|
98
102
|
end
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
if respond_to?(name)
|
|
105
|
+
raise PromMultiProcError.new("Metric method conflicts with existing method: #{name}")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@metric_objects[name] = klazz.new(full_name, labels, @writer)
|
|
101
109
|
|
|
102
110
|
define_singleton_method(name) do
|
|
103
111
|
@metric_objects[name]
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module PromMultiProc
|
|
2
4
|
class Collector
|
|
3
|
-
attr_reader :name
|
|
5
|
+
attr_reader :name
|
|
6
|
+
|
|
7
|
+
EXCLUDED_FROM_METRIC_METHODS = %i(validate! to_msg).freeze
|
|
8
|
+
|
|
9
|
+
def self.metric_methods
|
|
10
|
+
@metric_methods ||= (public_instance_methods(false) - EXCLUDED_FROM_METRIC_METHODS).map(&:to_s).freeze
|
|
11
|
+
end
|
|
4
12
|
|
|
5
13
|
def initialize(name, label_keys, writer)
|
|
6
14
|
@name = name
|
|
7
15
|
@label_keys = label_keys
|
|
8
16
|
@writer = writer
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
)).map(&:to_s)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def metric_methods
|
|
20
|
+
self.class.metric_methods
|
|
14
21
|
end
|
|
15
22
|
|
|
16
23
|
def validate!(method, value, labels)
|
|
@@ -21,13 +28,22 @@ module PromMultiProc
|
|
|
21
28
|
raise PromMultiProcError.new("Invalid label cardinality (#{name}): #{labels.keys.inspect}, need keys: #{@label_keys.inspect}")
|
|
22
29
|
end
|
|
23
30
|
unless valid_label_values?(labels)
|
|
24
|
-
raise PromMultiProcError.new("Invalid label values (#{name}): #{labels.values.inspect}")
|
|
31
|
+
raise PromMultiProcError.new("Invalid label values (#{name}): #{labels.values.inspect} (all values must be string or symbol)")
|
|
25
32
|
end
|
|
26
33
|
unless valid_value?(value)
|
|
27
34
|
raise PromMultiProcError.new("Invalid value (#{name}): #{value.inspect} (must be numeric)")
|
|
28
35
|
end
|
|
29
36
|
end
|
|
30
37
|
|
|
38
|
+
def to_msg(method, value, labels)
|
|
39
|
+
{ "name" => name,
|
|
40
|
+
"method" => method,
|
|
41
|
+
"value" => value.to_f,
|
|
42
|
+
"label_values" => labels.values }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
31
47
|
def valid_method?(method)
|
|
32
48
|
metric_methods.include?(method)
|
|
33
49
|
end
|
|
@@ -44,15 +60,6 @@ module PromMultiProc
|
|
|
44
60
|
value.is_a?(Numeric)
|
|
45
61
|
end
|
|
46
62
|
|
|
47
|
-
def to_msg(method, value, labels)
|
|
48
|
-
{ "name" => name,
|
|
49
|
-
"method" => method,
|
|
50
|
-
"value" => value.to_f,
|
|
51
|
-
"label_values" => labels.values }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private
|
|
55
|
-
|
|
56
63
|
def write(method, value, labels)
|
|
57
64
|
@writer.write(self, method, value, labels)
|
|
58
65
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module PromMultiProc
|
|
2
4
|
class Counter < Collector
|
|
3
5
|
def inc(labels = {})
|
|
4
|
-
write("inc"
|
|
6
|
+
write("inc", 1, labels)
|
|
5
7
|
end
|
|
6
8
|
|
|
7
9
|
def add(value, labels = {})
|
|
8
|
-
write("add"
|
|
10
|
+
write("add", value, labels)
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module PromMultiProc
|
|
2
4
|
class Gauge < Collector
|
|
3
5
|
def set(value, labels = {})
|
|
4
|
-
write("set"
|
|
6
|
+
write("set", value, labels)
|
|
5
7
|
end
|
|
6
8
|
|
|
7
9
|
def inc(labels = {})
|
|
8
|
-
write("inc"
|
|
10
|
+
write("inc", 1, labels)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def dec(labels = {})
|
|
12
|
-
write("dec"
|
|
14
|
+
write("dec", 1, labels)
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def add(value, labels = {})
|
|
16
|
-
write("add"
|
|
18
|
+
write("add", value, labels)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def sub(value, labels = {})
|
|
20
|
-
write("sub"
|
|
22
|
+
write("sub", value, labels)
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def set_to_current_time(labels = {})
|
|
24
|
-
write("set_to_current_time"
|
|
26
|
+
write("set_to_current_time", 1, labels)
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
end
|
|
@@ -1,54 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "logger"
|
|
2
4
|
|
|
3
5
|
module PromMultiProc
|
|
4
6
|
module Rails
|
|
5
|
-
def self.init(
|
|
6
|
-
metrics = ENV.fetch("PROM_MULTI_PROC_DEFINITION_FILE", ::Rails.root.join("config/metrics.json").to_s)
|
|
7
|
-
socket = ENV.fetch("PROM_MULTI_PROC_SOCKET", ::Rails.root.join("tmp/sockets/metrics.sock").to_s)
|
|
8
|
-
|
|
7
|
+
def self.init(**options)
|
|
9
8
|
program_name = File.basename($PROGRAM_NAME)
|
|
10
9
|
app_name = ::Rails.application.class.name.underscore.split("/").first
|
|
11
|
-
prefix ||= "#{app_name}_"
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
defaults = {
|
|
12
|
+
prefix: "#{app_name}_",
|
|
13
|
+
socket: ENV.fetch("PROM_MULTI_PROC_SOCKET", ::Rails.root.join("tmp/sockets/metrics.sock").to_s),
|
|
14
|
+
metrics: ENV.fetch("PROM_MULTI_PROC_DEFINITION_FILE", ::Rails.root.join("config/metrics.json").to_s),
|
|
15
|
+
batch_size: default_batch_size(program_name),
|
|
16
|
+
batch_timeout: 3,
|
|
17
|
+
validate: ::Rails.env.development? || ::Rails.env.test?,
|
|
18
|
+
logger: ::Rails.logger || ::Logger.new(STDOUT)
|
|
19
|
+
}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else
|
|
26
|
-
batch_timeout = 3
|
|
27
|
-
end
|
|
21
|
+
config = defaults.merge(options)
|
|
22
|
+
config[:logger].info("Setting up prom_multi_proc for #{app_name}-#{program_name}, batch_size: #{config[:batch_size]}, batch_timeout: #{config[:batch_timeout]}, validate: #{config[:validate]}")
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
else
|
|
32
|
-
validate = false
|
|
33
|
-
end
|
|
24
|
+
Base.new(**config)
|
|
25
|
+
end
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
def self.default_batch_size(program_name)
|
|
28
|
+
if %w(rails rake).include?(program_name) || ::Rails.env.development? || ::Rails.env.test?
|
|
29
|
+
1
|
|
30
|
+
elsif ::Rails.env.production?
|
|
31
|
+
100
|
|
37
32
|
else
|
|
38
|
-
|
|
33
|
+
5
|
|
39
34
|
end
|
|
40
|
-
|
|
41
|
-
logger.info("Setting up prom_multi_proc for #{app_name}-#{program_name}, batch size: #{batch_size}, batch timeout: #{batch_timeout} validate: #{validate}")
|
|
42
|
-
|
|
43
|
-
Base.new(
|
|
44
|
-
prefix: prefix,
|
|
45
|
-
socket: socket,
|
|
46
|
-
metrics: metrics,
|
|
47
|
-
batch_size: batch_size,
|
|
48
|
-
batch_timeout: batch_timeout,
|
|
49
|
-
validate: validate,
|
|
50
|
-
logger: logger
|
|
51
|
-
)
|
|
52
35
|
end
|
|
36
|
+
private_class_method :default_batch_size
|
|
53
37
|
end
|
|
54
38
|
end
|
|
@@ -1,53 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module PromMultiProc
|
|
2
4
|
class Writer
|
|
3
5
|
attr_reader :socket, :batch_size, :batch_timeout
|
|
4
6
|
|
|
5
|
-
def initialize(socket:, batch_size:
|
|
6
|
-
if
|
|
7
|
+
def initialize(socket:, batch_size: nil, batch_timeout: nil, validate: false)
|
|
8
|
+
batch_size = 1 if batch_size.nil?
|
|
9
|
+
batch_timeout = 3 if batch_timeout.nil?
|
|
10
|
+
unless batch_size.is_a?(Integer) && batch_size > 0
|
|
7
11
|
raise PromMultiProcError.new("Invalid batch size: #{batch_size}")
|
|
8
12
|
end
|
|
9
|
-
|
|
13
|
+
unless batch_timeout.is_a?(Integer) && batch_timeout > 0
|
|
10
14
|
raise PromMultiProcError.new("Invalid batch timeout: #{batch_timeout}")
|
|
11
15
|
end
|
|
16
|
+
|
|
12
17
|
@batch_size = batch_size
|
|
13
18
|
@batch_timeout = batch_timeout
|
|
14
|
-
@validate =
|
|
19
|
+
@validate = validate
|
|
20
|
+
@socket = socket
|
|
15
21
|
|
|
22
|
+
@messages = []
|
|
16
23
|
@lock = Mutex.new
|
|
17
|
-
@thread = Thread.new
|
|
24
|
+
@thread = Thread.new do
|
|
18
25
|
loop do
|
|
19
|
-
sleep(batch_timeout)
|
|
20
26
|
flush(force: true)
|
|
27
|
+
sleep(@batch_timeout)
|
|
21
28
|
end
|
|
22
|
-
|
|
23
|
-
@messages = []
|
|
24
|
-
|
|
25
|
-
@socket = socket
|
|
29
|
+
end
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
def validate?
|
|
29
33
|
@validate
|
|
30
34
|
end
|
|
31
35
|
|
|
32
|
-
def
|
|
33
|
-
@
|
|
34
|
-
|
|
35
|
-
@
|
|
36
|
+
def shutdown
|
|
37
|
+
if @thread&.alive?
|
|
38
|
+
flush
|
|
39
|
+
@thread.kill
|
|
36
40
|
end
|
|
41
|
+
end
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
def write(metric, method, value, labels)
|
|
44
|
+
write_multi([[metric, method, value, labels]])
|
|
39
45
|
end
|
|
40
46
|
|
|
41
47
|
# array of arrays where inner array is length 4 matching arguments
|
|
42
48
|
# for signature of #write
|
|
43
49
|
def write_multi(metrics)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
m.validate!(method, value, labels)
|
|
48
|
-
end
|
|
50
|
+
if validate?
|
|
51
|
+
metrics.each do |m, method, value, labels|
|
|
52
|
+
m.validate!(method, value, labels)
|
|
49
53
|
end
|
|
54
|
+
end
|
|
50
55
|
|
|
56
|
+
@lock.synchronize do
|
|
51
57
|
metrics.each do |m, method, value, labels|
|
|
52
58
|
@messages << m.to_msg(method, value, labels)
|
|
53
59
|
end
|
|
@@ -58,9 +64,13 @@ module PromMultiProc
|
|
|
58
64
|
|
|
59
65
|
def flush(force: false)
|
|
60
66
|
@lock.synchronize do
|
|
61
|
-
if force && @messages.length > 0 || @messages.length >= batch_size
|
|
67
|
+
if (force && @messages.length > 0) || (@messages.length >= batch_size)
|
|
62
68
|
begin
|
|
63
69
|
write_socket(JSON.generate(@messages))
|
|
70
|
+
rescue StandardError => e
|
|
71
|
+
# Never raise into host app; drop the batch
|
|
72
|
+
warn("prom_multi_proc_rb - flush: Failed to write batch to socket #{e}")
|
|
73
|
+
false
|
|
64
74
|
ensure
|
|
65
75
|
@messages.clear
|
|
66
76
|
end
|
|
@@ -71,7 +81,9 @@ module PromMultiProc
|
|
|
71
81
|
end
|
|
72
82
|
|
|
73
83
|
def socket?
|
|
74
|
-
|
|
84
|
+
File.socket?(@socket) && File.writable?(@socket)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
false
|
|
75
87
|
end
|
|
76
88
|
|
|
77
89
|
private
|
|
@@ -79,10 +91,9 @@ module PromMultiProc
|
|
|
79
91
|
def write_socket(msg)
|
|
80
92
|
s = UNIXSocket.new(@socket)
|
|
81
93
|
s.send(msg, 0)
|
|
82
|
-
s.close
|
|
83
94
|
true
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
ensure
|
|
96
|
+
s&.close
|
|
86
97
|
end
|
|
87
98
|
end
|
|
88
99
|
end
|
data/lib/prom_multi_proc.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "socket"
|
|
2
4
|
require "json"
|
|
3
|
-
require "thread"
|
|
4
5
|
|
|
5
6
|
require "prom_multi_proc/version"
|
|
6
7
|
|
|
@@ -22,9 +23,9 @@ module PromMultiProc
|
|
|
22
23
|
gauge: Gauge,
|
|
23
24
|
histogram: Histogram,
|
|
24
25
|
summary: Summary
|
|
25
|
-
}
|
|
26
|
+
}.freeze
|
|
26
27
|
|
|
27
|
-
METRIC_RE = /\A[a-z]+[0-9a-z_]+\Z
|
|
28
|
+
METRIC_RE = /\A[a-z]+[0-9a-z_]+\Z/.freeze
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
require "prom_multi_proc/rails" if defined?(::Rails)
|
data/prom_multi_proc_rb.gemspec
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
lib = File.expand_path("../lib", __FILE__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require "prom_multi_proc/version"
|
|
@@ -9,18 +10,17 @@ Gem::Specification.new do |spec|
|
|
|
9
10
|
spec.authors = ["Andrew Tongen"]
|
|
10
11
|
spec.email = ["atongen@gmail.com"]
|
|
11
12
|
|
|
12
|
-
spec.summary =
|
|
13
|
-
spec.description =
|
|
13
|
+
spec.summary = "A ruby library for collecting prometheus metrics within forking servers"
|
|
14
|
+
spec.description = "A ruby library for collecting prometheus metrics within forking servers"
|
|
14
15
|
spec.homepage = "https://github.com/atongen/prom_multi_proc_rb"
|
|
15
16
|
spec.license = "MIT"
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
spec.required_ruby_version = ">= 2.7"
|
|
19
|
+
|
|
19
20
|
if spec.respond_to?(:metadata)
|
|
20
21
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
21
22
|
else
|
|
22
|
-
raise "RubyGems 2.0 or newer is required to protect against "
|
|
23
|
-
"public gem pushes."
|
|
23
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -30,10 +30,11 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
31
31
|
spec.require_paths = ["lib"]
|
|
32
32
|
|
|
33
|
-
spec.add_development_dependency "bundler", ">=
|
|
34
|
-
spec.add_development_dependency "rake", ">=
|
|
35
|
-
spec.add_development_dependency "rspec", "~> 3.
|
|
33
|
+
spec.add_development_dependency "bundler", ">= 2.0"
|
|
34
|
+
spec.add_development_dependency "rake", ">= 13.0"
|
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
|
36
36
|
spec.add_development_dependency "rspec-collection_matchers", "~> 1.2"
|
|
37
37
|
|
|
38
38
|
spec.add_dependency "concurrent-ruby", "~> 1.1"
|
|
39
|
+
spec.add_dependency "logger", ">= 1.5"
|
|
39
40
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prom_multi_proc_rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Tongen
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-06-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -16,42 +16,42 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '2.0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '2.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rake
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
33
|
+
version: '13.0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
40
|
+
version: '13.0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rspec
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '3.
|
|
47
|
+
version: '3.13'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '3.
|
|
54
|
+
version: '3.13'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: rspec-collection_matchers
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '1.1'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: logger
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.5'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.5'
|
|
83
97
|
description: A ruby library for collecting prometheus metrics within forking servers
|
|
84
98
|
email:
|
|
85
99
|
- atongen@gmail.com
|
|
@@ -89,9 +103,9 @@ executables:
|
|
|
89
103
|
extensions: []
|
|
90
104
|
extra_rdoc_files: []
|
|
91
105
|
files:
|
|
106
|
+
- ".github/workflows/ci.yml"
|
|
92
107
|
- ".gitignore"
|
|
93
108
|
- ".rspec"
|
|
94
|
-
- ".travis.yml"
|
|
95
109
|
- CODE_OF_CONDUCT.md
|
|
96
110
|
- Gemfile
|
|
97
111
|
- LICENSE.txt
|
|
@@ -116,7 +130,7 @@ licenses:
|
|
|
116
130
|
- MIT
|
|
117
131
|
metadata:
|
|
118
132
|
allowed_push_host: https://rubygems.org
|
|
119
|
-
post_install_message:
|
|
133
|
+
post_install_message:
|
|
120
134
|
rdoc_options: []
|
|
121
135
|
require_paths:
|
|
122
136
|
- lib
|
|
@@ -124,15 +138,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
124
138
|
requirements:
|
|
125
139
|
- - ">="
|
|
126
140
|
- !ruby/object:Gem::Version
|
|
127
|
-
version: '
|
|
141
|
+
version: '2.7'
|
|
128
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
143
|
requirements:
|
|
130
144
|
- - ">="
|
|
131
145
|
- !ruby/object:Gem::Version
|
|
132
146
|
version: '0'
|
|
133
147
|
requirements: []
|
|
134
|
-
rubygems_version: 3.
|
|
135
|
-
signing_key:
|
|
148
|
+
rubygems_version: 3.5.16
|
|
149
|
+
signing_key:
|
|
136
150
|
specification_version: 4
|
|
137
151
|
summary: A ruby library for collecting prometheus metrics within forking servers
|
|
138
152
|
test_files: []
|