prometheus-client-mmap 0.17.0 → 0.19.1

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: 98e93b1f6cdd34ff4621cc7401fa1b02b6e81b069313899aabd28d1a1650cf7d
4
- data.tar.gz: d0ceb3c1535fa17e609baef147217593366bd3e0351254d231848ec18653c06d
3
+ metadata.gz: a1696ef06da9827e278d689a18f5f2accbdbb7ef1c893de81b538acb75c141af
4
+ data.tar.gz: 7bc559ef0cf4913378ab3cea8661013584d5dd4bc136d4d12adedd7e4884cfc6
5
5
  SHA512:
6
- metadata.gz: 7cc06bff586223b764f098d18c213e59f072a3ab906c7f4c2e844bcc419c9b1037d39b564d2a441a8fac50fc913107f2c5864f06fe37b15e54eb1c828cf8f118
7
- data.tar.gz: 72c42e4bb89b7b0bfff4f5855ca3b42eff07c5dc1b8317940739fa0304e79eafca131c9909a80588d6ce1362313175c8a21bacb46b2ed2066114a49f8f3fdb31
6
+ metadata.gz: aead55eab5d73783daf0b4d831ab85b046f057a21b0f3becf7556276d1a9a0e9ad051ef777c11a1ff07bf3bc452c3958b33b53e23b2695a1d3389bfecb1a679a
7
+ data.tar.gz: 37273dce63fa8b8b9ab4b07381dfd59da1ce3ff902a463a20c3629888f6f591cfe04120bbd4ba03d87bcecee08adb3ffba8fa1255d7219ee3718b3c81773fe8c
data/README.md CHANGED
@@ -68,29 +68,41 @@ integrated [example application](examples/rack/README.md).
68
68
  The Ruby client can also be used to push its collected metrics to a
69
69
  [Pushgateway][8]. This comes in handy with batch jobs or in other scenarios
70
70
  where it's not possible or feasible to let a Prometheus server scrape a Ruby
71
- process.
71
+ process. TLS and HTTP basic authentication are supported.
72
72
 
73
73
  ```ruby
74
74
  require 'prometheus/client'
75
75
  require 'prometheus/client/push'
76
76
 
77
- prometheus = Prometheus::Client.registry
77
+ registry = Prometheus::Client.registry
78
78
  # ... register some metrics, set/increment/observe/etc. their values
79
79
 
80
80
  # push the registry state to the default gateway
81
- Prometheus::Client::Push.new('my-batch-job').add(prometheus)
81
+ Prometheus::Client::Push.new(job: 'my-batch-job').add(registry)
82
82
 
83
- # optional: specify the instance name (instead of IP) and gateway
83
+ # optional: specify a grouping key that uniquely identifies a job instance, and gateway.
84
+ #
85
+ # Note: the labels you use in the grouping key must not conflict with labels set on the
86
+ # metrics being pushed. If they do, an error will be raised.
84
87
  Prometheus::Client::Push.new(
85
- 'my-job', 'instance-name', 'http://example.domain:1234').add(prometheus)
88
+ job: 'my-batch-job',
89
+ gateway: 'https://example.domain:1234',
90
+ grouping_key: { instance: 'some-instance', extra_key: 'foobar' }
91
+ ).add(registry)
86
92
 
87
- # If you want to replace any previously pushed metrics for a given instance,
93
+ # If you want to replace any previously pushed metrics for a given grouping key,
88
94
  # use the #replace method.
89
- Prometheus::Client::Push.new('my-batch-job', 'instance').replace(prometheus)
90
-
91
- # If you want to delete all previously pushed metrics for a given instance,
95
+ #
96
+ # Unlike #add, this will completely replace the metrics under the specified grouping key
97
+ # (i.e. anything currently present in the pushgateway for the specified grouping key, but
98
+ # not present in the registry for that grouping key will be removed).
99
+ #
100
+ # See https://github.com/prometheus/pushgateway#put-method for a full explanation.
101
+ Prometheus::Client::Push.new(job: 'my-batch-job').replace(registry)
102
+
103
+ # If you want to delete all previously pushed metrics for a given grouping key,
92
104
  # use the #delete method.
93
- Prometheus::Client::Push.new('my-batch-job', 'instance').delete
105
+ Prometheus::Client::Push.new(job: 'my-batch-job').delete
94
106
  ```
95
107
 
96
108
  ## Metrics
@@ -409,6 +409,15 @@ VALUE mm_unmap(VALUE obj) {
409
409
  }
410
410
  free(i_mm->t->path);
411
411
  }
412
+
413
+ // Ensure any lingering RString values get a length of zero. We
414
+ // can't zero out the address since GET_MMAP() inside
415
+ // mm_update_obj_i() expects a non-null address and path.
416
+ i_mm->t->len = 0;
417
+ i_mm->t->real = 0;
418
+ mm_update(obj);
419
+
420
+ i_mm->t->addr = NULL;
412
421
  i_mm->t->path = NULL;
413
422
  }
414
423
 
@@ -1,12 +1,15 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require 'base64'
3
4
  require 'thread'
4
5
  require 'net/http'
5
6
  require 'uri'
6
7
  require 'erb'
8
+ require 'set'
7
9
 
8
10
  require 'prometheus/client'
9
11
  require 'prometheus/client/formats/text'
12
+ require 'prometheus/client/label_set_validator'
10
13
 
11
14
  module Prometheus
12
15
  # Client is a ruby implementation for a Prometheus compatible client.
@@ -14,23 +17,31 @@ module Prometheus
14
17
  # Push implements a simple way to transmit a given registry to a given
15
18
  # Pushgateway.
16
19
  class Push
20
+ class HttpError < StandardError; end
21
+ class HttpRedirectError < HttpError; end
22
+ class HttpClientError < HttpError; end
23
+ class HttpServerError < HttpError; end
24
+
17
25
  DEFAULT_GATEWAY = 'http://localhost:9091'.freeze
18
26
  PATH = '/metrics/job/%s'.freeze
19
- INSTANCE_PATH = '/metrics/job/%s/instance/%s'.freeze
20
27
  SUPPORTED_SCHEMES = %w(http https).freeze
21
28
 
22
- attr_reader :job, :instance, :gateway, :path
29
+ attr_reader :job, :gateway, :path
23
30
 
24
- def initialize(job:, instance: nil, gateway: DEFAULT_GATEWAY, **kwargs)
31
+ def initialize(job:, gateway: DEFAULT_GATEWAY, grouping_key: {}, **kwargs)
25
32
  raise ArgumentError, "job cannot be nil" if job.nil?
26
33
  raise ArgumentError, "job cannot be empty" if job.empty?
34
+ @validator = LabelSetValidator.new()
35
+ @validator.validate(grouping_key)
27
36
 
28
37
  @mutex = Mutex.new
29
38
  @job = job
30
- @instance = instance
31
39
  @gateway = gateway || DEFAULT_GATEWAY
32
- @path = build_path(job, instance)
40
+ @grouping_key = grouping_key
41
+ @path = build_path(job, grouping_key)
42
+
33
43
  @uri = parse("#{@gateway}#{@path}")
44
+ validate_no_basic_auth!(@uri)
34
45
 
35
46
  @http = Net::HTTP.new(@uri.host, @uri.port)
36
47
  @http.use_ssl = (@uri.scheme == 'https')
@@ -38,6 +49,11 @@ module Prometheus
38
49
  @http.read_timeout = kwargs[:read_timeout] if kwargs[:read_timeout]
39
50
  end
40
51
 
52
+ def basic_auth(user, password)
53
+ @user = user
54
+ @password = password
55
+ end
56
+
41
57
  def add(registry)
42
58
  synchronize do
43
59
  request(Net::HTTP::Post, registry)
@@ -70,26 +86,118 @@ module Prometheus
70
86
  raise ArgumentError, "#{url} is not a valid URL: #{e}"
71
87
  end
72
88
 
73
- def build_path(job, instance)
74
- if instance && !instance.empty?
75
- format(INSTANCE_PATH, ERB::Util::url_encode(job), ERB::Util::url_encode(instance))
76
- else
77
- format(PATH, ERB::Util::url_encode(job))
89
+ def build_path(job, grouping_key)
90
+ path = format(PATH, ERB::Util::url_encode(job))
91
+
92
+ grouping_key.each do |label, value|
93
+ if value.include?('/')
94
+ encoded_value = Base64.urlsafe_encode64(value)
95
+ path += "/#{label}@base64/#{encoded_value}"
96
+ # While it's valid for the urlsafe_encode64 function to return an
97
+ # empty string when the input string is empty, it doesn't work for
98
+ # our specific use case as we're putting the result into a URL path
99
+ # segment. A double slash (`//`) can be normalised away by HTTP
100
+ # libraries, proxies, and web servers.
101
+ #
102
+ # For empty strings, we use a single padding character (`=`) as the
103
+ # value.
104
+ #
105
+ # See the pushgateway docs for more details:
106
+ #
107
+ # https://github.com/prometheus/pushgateway/blob/6393a901f56d4dda62cd0f6ab1f1f07c495b6354/README.md#url
108
+ elsif value.empty?
109
+ path += "/#{label}@base64/="
110
+ else
111
+ path += "/#{label}/#{ERB::Util::url_encode(value)}"
112
+ end
78
113
  end
114
+
115
+ path
79
116
  end
80
117
 
81
118
  def request(req_class, registry = nil)
119
+ validate_no_label_clashes!(registry) if registry
120
+
82
121
  req = req_class.new(@uri)
83
122
  req.content_type = Formats::Text::CONTENT_TYPE
84
- req.basic_auth(@uri.user, @uri.password) if @uri.user
123
+ req.basic_auth(@user, @password) if @user
85
124
  req.body = Formats::Text.marshal(registry) if registry
86
125
 
87
- @http.request(req)
126
+ response = @http.request(req)
127
+ validate_response!(response)
128
+
129
+ response
88
130
  end
89
131
 
90
132
  def synchronize
91
133
  @mutex.synchronize { yield }
92
134
  end
135
+
136
+ def validate_no_basic_auth!(uri)
137
+ if uri.user || uri.password
138
+ raise ArgumentError, <<~EOF
139
+ Setting Basic Auth credentials in the gateway URL is not supported, please call the `basic_auth` method.
140
+
141
+ Received username `#{uri.user}` in gateway URL. Instead of passing
142
+ Basic Auth credentials like this:
143
+
144
+ ```
145
+ push = Prometheus::Client::Push.new(job: "my-job", gateway: "http://user:password@localhost:9091")
146
+ ```
147
+
148
+ please pass them like this:
149
+
150
+ ```
151
+ push = Prometheus::Client::Push.new(job: "my-job", gateway: "http://localhost:9091")
152
+ push.basic_auth("user", "password")
153
+ ```
154
+
155
+ While URLs do support passing Basic Auth credentials using the
156
+ `http://user:password@example.com/` syntax, the username and
157
+ password in that syntax have to follow the usual rules for URL
158
+ encoding of characters per RFC 3986
159
+ (https://datatracker.ietf.org/doc/html/rfc3986#section-2.1).
160
+
161
+ Rather than place the burden of correctly performing that encoding
162
+ on users of this gem, we decided to have a separate method for
163
+ supplying Basic Auth credentials, with no requirement to URL encode
164
+ the characters in them.
165
+ EOF
166
+ end
167
+ end
168
+
169
+ def validate_no_label_clashes!(registry)
170
+ # There's nothing to check if we don't have a grouping key
171
+ return if @grouping_key.empty?
172
+
173
+ # We could be doing a lot of comparisons, so let's do them against a
174
+ # set rather than an array
175
+ grouping_key_labels = @grouping_key.keys.to_set
176
+
177
+ registry.metrics.each do |metric|
178
+ metric.values.keys.first.keys.each do |label|
179
+ if grouping_key_labels.include?(label)
180
+ raise LabelSetValidator::InvalidLabelSetError,
181
+ "label :#{label} from grouping key collides with label of the " \
182
+ "same name from metric :#{metric.name} and would overwrite it"
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def validate_response!(response)
189
+ status = Integer(response.code)
190
+ if status >= 300
191
+ message = "status: #{response.code}, message: #{response.message}, body: #{response.body}"
192
+ if status <= 399
193
+ raise HttpRedirectError, message
194
+ elsif status <= 499
195
+ raise HttpClientError, message
196
+ else
197
+ raise HttpServerError, message
198
+ end
199
+ end
200
+ end
93
201
  end
94
202
  end
95
203
  end
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.17.0'.freeze
3
+ VERSION = '0.19.1'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prometheus-client-mmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.19.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Schmidt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-12-12 00:00:00.000000000 Z
12
+ date: 2023-03-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fuzzbert
@@ -178,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
178
  - !ruby/object:Gem::Version
179
179
  version: '0'
180
180
  requirements: []
181
- rubygems_version: 3.2.33
181
+ rubygems_version: 3.4.8
182
182
  signing_key:
183
183
  specification_version: 4
184
184
  summary: A suite of instrumentation metric primitivesthat can be exposed through a