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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d37f8295264d7bce0f624c278e59d421ae44908bf1111435741011c42154e2fe
4
- data.tar.gz: 6a887ad172e0b78965b1cb0f9994faed63552ad29db0b6aa607f533d80d84ccb
3
+ metadata.gz: 5a78925823a018eb95a68a84c00ff22a798689efd686d0b03e766499137b2cfd
4
+ data.tar.gz: e8f347f72c15e27fb4873d9bb0477385b179fe04b06bfb45c6e930d00bded430
5
5
  SHA512:
6
- metadata.gz: 865c47f29f5d5757969144c9131079481e64147b6f3e6cde9ee0109b1b84d33cbef792199cbb7965738160d56218b29035c372b21f04b307c67ad90eede37ba7
7
- data.tar.gz: 84c11b31077019cbb073ec74a5e02cf4b4d3db16a479169b713152ba4d8b40747c86852e3db8bae2890e1e0217d9603d97da8e7e9dd68b9e670e3c082341e235
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
- [![Build Status](https://travis-ci.org/atongen/prom_multi_proc_rb.svg?branch=master)](https://travis-ci.org/atongen/prom_multi_proc_rb)
3
+ [![CI](https://github.com/atongen/prom_multi_proc_rb/actions/workflows/ci.yml/badge.svg)](https://github.com/atongen/prom_multi_proc_rb/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/prom_multi_proc_rb.svg)](https://badge.fury.io/rb/prom_multi_proc_rb)
5
5
 
6
- Ruby client library for collecting prometheus metrics.
7
- Designed for use in applications running under forking servers (unicorn, puma).
8
- Writes metrics in json format to unix socket being listened to
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 json file to define the prometheus metrics that your application will track, for example:
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": "app_test_counter_total",
38
- "help": "A test counter",
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": "app_test_gauge_total",
46
- "help": "A test gauge",
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": "app_test_histogram_seconds",
54
- "help": "A test histogram",
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": "app_test_summary_seconds",
62
- "help": "A test summary",
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 intened to be shared by both the aggregator process and this ruby library.
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 the [prom_multi_proc](https://github.com/atongen/prom_multi_proc)
75
- aggregator application using the metrics json definition file created earlier.
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
- Note that in development, the ruby client will funtion normally if there is no aggregator process listening
79
- on the socket.
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 in ruby app
72
+ ### Collect metrics
82
73
 
83
- Create a `PromMultiProc::Base` object for collecting metrics
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: "app_",
89
- socket: "path/to/aggregator/socket.sock",
90
- metrics: "path/to/metrics/definition.json",
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: true
82
+ validate: true
93
83
  )
94
84
 
95
- metrics.test_counter_total.inc(label1: "my-label-value")
96
- metrics.test_histogram_seconds.observe(2.3, label3: "my-other-label-value")
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
- This helper class can be used to simplify a rails initializer, for example, in `config/initializers/prom_multi_proc.rb`:
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
- $prom_multi_proc = PromMultiProc::Rails.init
130
+ $prom = PromMultiProc::Rails.init
105
131
 
106
- $prom_multi_proc.test_counter_total.inc(label1: "my-label-value")
107
- $prom_multi_proc.test_histogram_seconds.observe(2.3, label3: "my-other-label-value")
132
+ $prom.requests_total.inc(method: "GET", status: "200")
133
+ $prom.request_duration_seconds.observe(0.042, method: "GET")
108
134
  ```
109
135
 
110
- ## Contributing
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/atongen/prom_multi_proc_rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
139
+ ```ruby
140
+ # Override prefix and bump batch size in production
141
+ $prom = PromMultiProc::Rails.init(prefix: "myapp_", batch_size: 50)
113
142
 
114
- ## License
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
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
162
+ ## Metric types and methods
117
163
 
118
- ## Code of Conduct
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
- Everyone interacting in the prom_multi_proc_rb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/atongen/prom_multi_proc_rb/blob/master/CODE_OF_CONDUCT.md).
177
+ Available as open source under the [MIT License](http://opensource.org/licenses/MIT).
@@ -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: 1, batch_timeout: 3, logger: nil, validate: false, prefix: "")
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).tap do |proxy|
40
- yield(proxy)
41
- end
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
- !!METRIC_RE.match(name)
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("Unkown type: #{spec.inspect}")
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
- unless spec["name"].start_with?(prefix)
79
- raise PromMultiProcError.new("Metric '#{spec['name']}' must start with prefix '#{prefix}'")
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 = spec["name"].sub(/\A#{prefix}/, "").to_sym
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
- @metric_objects[name] = klazz.new(spec["name"], labels, @writer)
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, :metric_methods
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
- @metric_methods = (public_methods(false) - %i(
10
- validate!
11
- valid_method? valid_label_keys? valid_label_values? valid_value?
12
- to_msg
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".freeze, 1, labels)
6
+ write("inc", 1, labels)
5
7
  end
6
8
 
7
9
  def add(value, labels = {})
8
- write("add".freeze, value, labels)
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".freeze, value, labels)
6
+ write("set", value, labels)
5
7
  end
6
8
 
7
9
  def inc(labels = {})
8
- write("inc".freeze, 1, labels)
10
+ write("inc", 1, labels)
9
11
  end
10
12
 
11
13
  def dec(labels = {})
12
- write("dec".freeze, 1, labels)
14
+ write("dec", 1, labels)
13
15
  end
14
16
 
15
17
  def add(value, labels = {})
16
- write("add".freeze, value, labels)
18
+ write("add", value, labels)
17
19
  end
18
20
 
19
21
  def sub(value, labels = {})
20
- write("sub".freeze, value, labels)
22
+ write("sub", value, labels)
21
23
  end
22
24
 
23
25
  def set_to_current_time(labels = {})
24
- write("set_to_current_time".freeze, 1, labels)
26
+ write("set_to_current_time", 1, labels)
25
27
  end
26
28
  end
27
29
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PromMultiProc
2
4
  class Histogram < Collector
3
5
  def observe(value, labels = {})
4
- write("observe".freeze, value, labels)
6
+ write("observe", value, labels)
5
7
  end
6
8
  end
7
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PromMultiProc
2
4
  class Proxy
3
5
  attr_reader :multis
@@ -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(prefix = nil)
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
- if ENV.key?("PROM_MULTI_PROC_BATCH_SIZE")
14
- batch_size = ENV["PROM_MULTI_PROC_BATCH_SIZE"].to_i
15
- elsif %w(rails rake).include?(program_name) || ::Rails.env.development? || ::Rails.env.test?
16
- batch_size = 1
17
- elsif ::Rails.env.production?
18
- batch_size = 100
19
- else
20
- batch_size = 5
21
- end
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
- if ENV.key?("PROM_MULTI_PROC_BATCH_TIMEOUT")
24
- batch_timeout = ENV["PROM_MULTI_PROC_BATCH_TIMEOUT"].to_i
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
- if ::Rails.env.development? || ::Rails.env.test?
30
- validate = true
31
- else
32
- validate = false
33
- end
24
+ Base.new(**config)
25
+ end
34
26
 
35
- if ::Rails.logger
36
- logger = ::Rails.logger
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
- logger = ::Logger.new(STDOUT)
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,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PromMultiProc
2
4
  class Summary < Collector
3
5
  def observe(value, labels = {})
4
- write("observe".freeze, value, labels)
6
+ write("observe", value, labels)
5
7
  end
6
8
  end
7
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PromMultiProc
2
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
3
5
  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: 1, batch_timeout: 3, validate: false)
6
- if !batch_size.is_a?(Integer) || batch_size <= 0
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
- if !batch_timeout.is_a?(Integer) || batch_timeout <= 0
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 = !!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 write(metric, method, value, labels)
33
- @lock.synchronize do
34
- metric.validate!(method, value, labels) if validate?
35
- @messages << metric.to_msg(method, value, labels)
36
+ def shutdown
37
+ if @thread&.alive?
38
+ flush
39
+ @thread.kill
36
40
  end
41
+ end
37
42
 
38
- flush
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
- @lock.synchronize do
45
- if validate?
46
- metrics.each do |m, method, value, labels|
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
- !!write_socket("\n")
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
- rescue StandardError
85
- false
95
+ ensure
96
+ s&.close
86
97
  end
87
98
  end
88
99
  end
@@ -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)
@@ -1,4 +1,5 @@
1
- # coding: utf-8
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 = %q{A ruby library for collecting prometheus metrics within forking servers}
13
- spec.description = %q{A ruby library for collecting prometheus metrics within forking servers}
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
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
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", ">= 1.15"
34
- spec.add_development_dependency "rake", ">= 12.3.3"
35
- spec.add_development_dependency "rspec", "~> 3.9"
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.6
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: 2020-04-16 00:00:00.000000000 Z
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: '1.15'
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: '1.15'
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: 12.3.3
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: 12.3.3
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.9'
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.9'
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: '0'
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.0.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: []
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.3.4
5
- - 2.4.1
6
- - 2.6.5
7
- before_install: gem install bundler -v 1.15.3