prometheus-client-mmap 0.17.0 → 0.19.1

Sign up to get free protection for your applications and to get access to all the features.
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