prometheus-client-mmap 0.16.2 → 0.18.0

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.
@@ -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