prometheus-client-mmap 0.16.2 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -66,7 +66,7 @@ static int perform_mmap(mm_ipc *i_mm, size_t len) {
66
66
  return SUCCESS;
67
67
  }
68
68
 
69
- static int expand(mm_ipc *i_mm, size_t len) {
69
+ static int expand(VALUE self, mm_ipc *i_mm, size_t len) {
70
70
  if (len < i_mm->t->len) {
71
71
  return with_exception(rb_eArgError, "Can't reduce the size of mmap");
72
72
  }
@@ -88,6 +88,8 @@ static int expand(mm_ipc *i_mm, size_t len) {
88
88
  return with_exception_errno(rb_eArgError, "mlock(%d)", errno);
89
89
  }
90
90
 
91
+ mm_update(self);
92
+
91
93
  return SUCCESS;
92
94
  }
93
95
 
@@ -151,7 +153,7 @@ uint32_t load_used(mm_ipc *i_mm) {
151
153
 
152
154
  void save_used(mm_ipc *i_mm, uint32_t used) { *((uint32_t *)i_mm->t->addr) = used; }
153
155
 
154
- static VALUE initialize_entry(mm_ipc *i_mm, VALUE positions, VALUE key, VALUE value) {
156
+ static VALUE initialize_entry(VALUE self, mm_ipc *i_mm, VALUE positions, VALUE key, VALUE value) {
155
157
  if (i_mm->t->flag & MM_FROZEN) {
156
158
  rb_error_frozen("mmap");
157
159
  }
@@ -166,10 +168,11 @@ static VALUE initialize_entry(mm_ipc *i_mm, VALUE positions, VALUE key, VALUE va
166
168
 
167
169
  uint32_t used = load_used(i_mm);
168
170
  while (i_mm->t->len < (used + entry_length)) {
169
- if (!expand(i_mm, i_mm->t->len * 2)) {
171
+ if (!expand(self, i_mm, i_mm->t->len * 2)) {
170
172
  raise_last_exception();
171
173
  }
172
174
  }
175
+
173
176
  save_entry(i_mm, used, key, value);
174
177
  save_used(i_mm, used + entry_length);
175
178
 
@@ -189,7 +192,7 @@ VALUE method_fetch_entry(VALUE self, VALUE positions, VALUE key, VALUE default_v
189
192
  return load_value(i_mm, position);
190
193
  }
191
194
 
192
- position = initialize_entry(i_mm, positions, key, default_value);
195
+ position = initialize_entry(self, i_mm, positions, key, default_value);
193
196
  return load_value(i_mm, position);
194
197
  }
195
198
 
@@ -207,7 +210,7 @@ VALUE method_upsert_entry(VALUE self, VALUE positions, VALUE key, VALUE value) {
207
210
  return load_value(i_mm, position);
208
211
  }
209
212
 
210
- position = initialize_entry(i_mm, positions, key, value);
213
+ position = initialize_entry(self, i_mm, positions, key, value);
211
214
  return load_value(i_mm, position);
212
215
  }
213
216
 
@@ -229,7 +232,7 @@ VALUE method_save_used(VALUE self, VALUE value) {
229
232
  }
230
233
 
231
234
  if (i_mm->t->len < INITIAL_SIZE) {
232
- if (!expand(i_mm, INITIAL_SIZE)) {
235
+ if (!expand(self, i_mm, INITIAL_SIZE)) {
233
236
  raise_last_exception();
234
237
  }
235
238
  }
Binary file
data/lib/mmap.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'weak_ref'
2
+
3
+ class FastMmapedFile
4
+ def track_ref!
5
+
6
+ end
7
+ end
@@ -0,0 +1,58 @@
1
+ require 'prometheus/client/registry'
2
+ require 'prometheus/client/configuration'
3
+ require 'prometheus/client/mmaped_value'
4
+
5
+ module Prometheus
6
+ # Client is a ruby implementation for a Prometheus compatible client.
7
+ module Client
8
+ class << self
9
+ attr_writer :configuration
10
+
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def configure
16
+ yield(configuration)
17
+ end
18
+ o
19
+ # Returns a default registry object
20
+ def registry
21
+ @registry ||= Registry.new
22
+ end
23
+
24
+ def logger
25
+ configuration.logger
26
+ end
27
+
28
+ def pid
29
+ configuration.pid_provider.call
30
+ end
31
+
32
+ # Resets the registry and reinitializes all metrics files.
33
+ # Use case: clean up everything in specs `before` block,
34
+ # to prevent leaking the state between specs which are updating metrics.
35
+ def reset!
36
+ @registry = nil
37
+ ::Prometheus::Client::MmapedValue.reset_and_reinitialize
38
+ end
39
+
40
+ def cleanup!
41
+ Dir.glob("#{configuration.multiprocess_files_dir}/*.db").each { |f| File.unlink(f) if File.exist?(f) }
42
+ end
43
+
44
+ # With `force: false`: reinitializes metric files only for processes with the changed PID.
45
+ # With `force: true`: reinitializes all metrics files.
46
+ # Always keeps the registry.
47
+ # Use case (`force: false`): pick up new metric files on each worker start,
48
+ # without resetting already registered files for the master or previously initialized workers.
49
+ def reinitialize_on_pid_change(force: false)
50
+ if force
51
+ ::Prometheus::Client::MmapedValue.reset_and_reinitialize
52
+ else
53
+ ::Prometheus::Client::MmapedValue.reinitialize_on_pid_change
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -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.16.2'.freeze
3
+ VERSION = '0.18.0'.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.16.2
4
+ version: 0.18.0
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-07-08 00:00:00.000000000 Z
12
+ date: 2023-01-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fuzzbert
@@ -117,7 +117,9 @@ files:
117
117
  - ext/fast_mmaped_file/value_access.c
118
118
  - ext/fast_mmaped_file/value_access.h
119
119
  - lib/fast_mmaped_file.bundle
120
+ - lib/mmap.rb
120
121
  - lib/prometheus.rb
122
+ - lib/prometheus/#client.rb#
121
123
  - lib/prometheus/client.rb
122
124
  - lib/prometheus/client/configuration.rb
123
125
  - lib/prometheus/client/counter.rb
@@ -174,14 +176,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
176
  requirements:
175
177
  - - ">="
176
178
  - !ruby/object:Gem::Version
177
- version: '0'
179
+ version: 2.7.0
178
180
  required_rubygems_version: !ruby/object:Gem::Requirement
179
181
  requirements:
180
182
  - - ">="
181
183
  - !ruby/object:Gem::Version
182
184
  version: '0'
183
185
  requirements: []
184
- rubygems_version: 3.3.16
186
+ rubygems_version: 3.3.26
185
187
  signing_key:
186
188
  specification_version: 4
187
189
  summary: A suite of instrumentation metric primitivesthat can be exposed through a