vinted-prometheus-client-mmap 1.5.0-x86_64-linux

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -0
  3. data/ext/fast_mmaped_file_rs/Cargo.toml +40 -0
  4. data/ext/fast_mmaped_file_rs/README.md +52 -0
  5. data/ext/fast_mmaped_file_rs/build.rs +7 -0
  6. data/ext/fast_mmaped_file_rs/extconf.rb +28 -0
  7. data/ext/fast_mmaped_file_rs/src/error.rs +174 -0
  8. data/ext/fast_mmaped_file_rs/src/exemplars.rs +25 -0
  9. data/ext/fast_mmaped_file_rs/src/file_entry.rs +1252 -0
  10. data/ext/fast_mmaped_file_rs/src/file_info.rs +240 -0
  11. data/ext/fast_mmaped_file_rs/src/lib.rs +89 -0
  12. data/ext/fast_mmaped_file_rs/src/macros.rs +14 -0
  13. data/ext/fast_mmaped_file_rs/src/map.rs +519 -0
  14. data/ext/fast_mmaped_file_rs/src/metrics.proto +153 -0
  15. data/ext/fast_mmaped_file_rs/src/mmap/inner.rs +775 -0
  16. data/ext/fast_mmaped_file_rs/src/mmap.rs +977 -0
  17. data/ext/fast_mmaped_file_rs/src/raw_entry.rs +547 -0
  18. data/ext/fast_mmaped_file_rs/src/testhelper.rs +222 -0
  19. data/ext/fast_mmaped_file_rs/src/util.rs +140 -0
  20. data/lib/.DS_Store +0 -0
  21. data/lib/2.7/fast_mmaped_file_rs.so +0 -0
  22. data/lib/3.0/fast_mmaped_file_rs.so +0 -0
  23. data/lib/3.1/fast_mmaped_file_rs.so +0 -0
  24. data/lib/3.2/fast_mmaped_file_rs.so +0 -0
  25. data/lib/3.3/fast_mmaped_file_rs.so +0 -0
  26. data/lib/prometheus/.DS_Store +0 -0
  27. data/lib/prometheus/client/configuration.rb +24 -0
  28. data/lib/prometheus/client/counter.rb +27 -0
  29. data/lib/prometheus/client/formats/protobuf.rb +93 -0
  30. data/lib/prometheus/client/formats/text.rb +85 -0
  31. data/lib/prometheus/client/gauge.rb +40 -0
  32. data/lib/prometheus/client/helper/entry_parser.rb +132 -0
  33. data/lib/prometheus/client/helper/file_locker.rb +50 -0
  34. data/lib/prometheus/client/helper/json_parser.rb +23 -0
  35. data/lib/prometheus/client/helper/metrics_processing.rb +45 -0
  36. data/lib/prometheus/client/helper/metrics_representation.rb +51 -0
  37. data/lib/prometheus/client/helper/mmaped_file.rb +64 -0
  38. data/lib/prometheus/client/helper/plain_file.rb +29 -0
  39. data/lib/prometheus/client/histogram.rb +80 -0
  40. data/lib/prometheus/client/label_set_validator.rb +85 -0
  41. data/lib/prometheus/client/metric.rb +80 -0
  42. data/lib/prometheus/client/mmaped_dict.rb +83 -0
  43. data/lib/prometheus/client/mmaped_value.rb +164 -0
  44. data/lib/prometheus/client/page_size.rb +17 -0
  45. data/lib/prometheus/client/push.rb +203 -0
  46. data/lib/prometheus/client/rack/collector.rb +88 -0
  47. data/lib/prometheus/client/rack/exporter.rb +102 -0
  48. data/lib/prometheus/client/registry.rb +65 -0
  49. data/lib/prometheus/client/simple_value.rb +31 -0
  50. data/lib/prometheus/client/summary.rb +69 -0
  51. data/lib/prometheus/client/support/puma.rb +44 -0
  52. data/lib/prometheus/client/support/unicorn.rb +35 -0
  53. data/lib/prometheus/client/uses_value_type.rb +20 -0
  54. data/lib/prometheus/client/version.rb +5 -0
  55. data/lib/prometheus/client.rb +58 -0
  56. data/lib/prometheus.rb +3 -0
  57. metadata +210 -0
@@ -0,0 +1,83 @@
1
+ require 'prometheus/client/helper/mmaped_file'
2
+ require 'prometheus/client/helper/plain_file'
3
+ require 'prometheus/client'
4
+
5
+ module Prometheus
6
+ module Client
7
+ class ParsingError < StandardError
8
+ end
9
+
10
+ # A dict of doubles, backed by an mmapped file.
11
+ #
12
+ # The file starts with a 4 byte int, indicating how much of it is used.
13
+ # Then 4 bytes of padding.
14
+ # There's then a number of entries, consisting of a 4 byte int which is the
15
+ # size of the next field, a utf-8 encoded string key, padding to an 8 byte
16
+ # alignment, and then a 8 byte float which is the value.
17
+ class MmapedDict
18
+ attr_reader :m, :used, :positions
19
+
20
+ def self.read_all_values(f)
21
+ Helper::PlainFile.new(f).entries.map do |data, encoded_len, value_offset, _|
22
+ encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
23
+ [encoded, value]
24
+ end
25
+ end
26
+
27
+ def initialize(m)
28
+ @mutex = Mutex.new
29
+
30
+ @m = m
31
+ # @m.mlock # TODO: Ensure memory is locked to RAM
32
+
33
+ @positions = {}
34
+ read_all_positions.each do |key, pos|
35
+ @positions[key] = pos
36
+ end
37
+ rescue StandardError => e
38
+ raise ParsingError, "exception #{e} while processing metrics file #{path}"
39
+ end
40
+
41
+ def read_value(key)
42
+ @m.fetch_entry(@positions, key, 0.0)
43
+ end
44
+
45
+ def write_value(key, value)
46
+ @m.upsert_entry(@positions, key, value)
47
+ end
48
+
49
+ def write_exemplar(key, value, exemplar_id, exemplar_val)
50
+ @m.upsert_exemplar(@positions, key, value, exemplar_id, exemplar_val)
51
+ end
52
+
53
+ def path
54
+ @m.filepath if @m
55
+ end
56
+
57
+ def close
58
+ @m.sync
59
+ @m.close
60
+ rescue TypeError => e
61
+ Prometheus::Client.logger.warn("munmap raised error #{e}")
62
+ end
63
+
64
+ def inspect
65
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
66
+ end
67
+
68
+ private
69
+
70
+ def init_value(key)
71
+ @m.add_entry(@positions, key, 0.0)
72
+ end
73
+
74
+ # Yield (key, pos). No locking is performed.
75
+ def read_all_positions
76
+ @m.entries.map do |data, encoded_len, _, absolute_pos|
77
+ encoded, = data.unpack(format('@4A%d', encoded_len))
78
+ [encoded, absolute_pos]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,164 @@
1
+ require 'prometheus/client'
2
+ require 'prometheus/client/mmaped_dict'
3
+ require 'json'
4
+
5
+ module Prometheus
6
+ module Client
7
+ # A float protected by a mutex backed by a per-process mmaped file.
8
+ class MmapedValue
9
+ VALUE_LOCK = Mutex.new
10
+
11
+ @@files = {}
12
+ @@pid = -1
13
+
14
+ def initialize(type, metric_name, name, labels, multiprocess_mode = '')
15
+ @file_prefix = type.to_s
16
+ @metric_name = metric_name
17
+ @name = name
18
+ @labels = labels
19
+ if type == :gauge
20
+ @file_prefix += '_' + multiprocess_mode.to_s
21
+ end
22
+
23
+ @pid = -1
24
+
25
+ @mutex = Mutex.new
26
+ initialize_file
27
+ end
28
+
29
+ def increment(amount = 1, exemplar_name = '', exemplar_value = '')
30
+ @mutex.synchronize do
31
+ initialize_file if pid_changed?
32
+
33
+ @value += amount
34
+ write_value(@key, @value, exemplar_name, exemplar_value)
35
+ @value
36
+ end
37
+ end
38
+
39
+ def decrement(amount = 1)
40
+ increment(-amount)
41
+ end
42
+
43
+ def set(value)
44
+ @mutex.synchronize do
45
+ initialize_file if pid_changed?
46
+
47
+ @value = value
48
+ write_value(@key, @value)
49
+ @value
50
+ end
51
+ end
52
+
53
+ def get
54
+ @mutex.synchronize do
55
+ initialize_file if pid_changed?
56
+ return @value
57
+ end
58
+ end
59
+
60
+ def pid_changed?
61
+ @pid != Process.pid
62
+ end
63
+
64
+ # method needs to be run in VALUE_LOCK mutex
65
+ def unsafe_reinitialize_file(check_pid = true)
66
+ unsafe_initialize_file if !check_pid || pid_changed?
67
+ end
68
+
69
+ def self.reset_and_reinitialize
70
+ VALUE_LOCK.synchronize do
71
+ @@pid = Process.pid
72
+ @@files = {}
73
+
74
+ ObjectSpace.each_object(MmapedValue).each do |v|
75
+ v.unsafe_reinitialize_file(false)
76
+ end
77
+ end
78
+ end
79
+
80
+ def self.reset_on_pid_change
81
+ if pid_changed?
82
+ @@pid = Process.pid
83
+ @@files = {}
84
+ end
85
+ end
86
+
87
+ def self.reinitialize_on_pid_change
88
+ VALUE_LOCK.synchronize do
89
+ reset_on_pid_change
90
+
91
+ ObjectSpace.each_object(MmapedValue, &:unsafe_reinitialize_file)
92
+ end
93
+ end
94
+
95
+ def self.pid_changed?
96
+ @@pid != Process.pid
97
+ end
98
+
99
+ def self.multiprocess
100
+ true
101
+ end
102
+
103
+ private
104
+
105
+ def initialize_file
106
+ VALUE_LOCK.synchronize do
107
+ unsafe_initialize_file
108
+ end
109
+ end
110
+
111
+ def unsafe_initialize_file
112
+ self.class.reset_on_pid_change
113
+
114
+ @pid = Process.pid
115
+ unless @@files.has_key?(@file_prefix)
116
+ unless @file.nil?
117
+ @file.close
118
+ end
119
+ unless @exemplar_file.nil?
120
+ @exemplar_file.close
121
+ end
122
+ mmaped_file = Helper::MmapedFile.open_exclusive_file(@file_prefix)
123
+ exemplar_file = Helper::MmapedFile.open_exclusive_file('exemplar')
124
+
125
+ @@files[@file_prefix] = MmapedDict.new(mmaped_file)
126
+ @@files['exemplar'] = MmapedDict.new(exemplar_file)
127
+ end
128
+
129
+ @file = @@files[@file_prefix]
130
+ @exemplar_file = @@files['exemplar']
131
+ @key = rebuild_key
132
+
133
+ @value = read_value(@key)
134
+ end
135
+
136
+
137
+ def rebuild_key
138
+ keys = @labels.keys.sort
139
+ values = @labels.values_at(*keys)
140
+
141
+ [@metric_name, @name, keys, values].to_json
142
+ end
143
+
144
+ def write_value(key, val, exemplar_name = '', exemplar_value = '')
145
+ @file.write_value(key, val)
146
+ # Exemplars are only defined on counters or histograms.
147
+ if @file_prefix == 'counter' or @file_prefix == 'histogram' and exemplar_name != '' and exemplar_value != ''
148
+ @exemplar_file.write_exemplar(key, val, exemplar_name, exemplar_value)
149
+ end
150
+ rescue StandardError => e
151
+ Prometheus::Client.logger.warn("writing value to #{@file.path} failed with #{e}")
152
+ Prometheus::Client.logger.debug(e.backtrace.join("\n"))
153
+ end
154
+
155
+ def read_value(key)
156
+ @file.read_value(key)
157
+ rescue StandardError => e
158
+ Prometheus::Client.logger.warn("reading value from #{@file.path} failed with #{e}")
159
+ Prometheus::Client.logger.debug(e.backtrace.join("\n"))
160
+ 0
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,17 @@
1
+ require 'open3'
2
+
3
+ module Prometheus
4
+ module Client
5
+ module PageSize
6
+ def self.page_size(fallback_page_size: 4096)
7
+ stdout, status = Open3.capture2('getconf PAGESIZE')
8
+ return fallback_page_size if status.nil? || !status.success?
9
+
10
+ page_size = stdout.chomp.to_i
11
+ return fallback_page_size if page_size <= 0
12
+
13
+ page_size
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,203 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'base64'
4
+ require 'thread'
5
+ require 'net/http'
6
+ require 'uri'
7
+ require 'erb'
8
+ require 'set'
9
+
10
+ require 'prometheus/client'
11
+ require 'prometheus/client/formats/text'
12
+ require 'prometheus/client/label_set_validator'
13
+
14
+ module Prometheus
15
+ # Client is a ruby implementation for a Prometheus compatible client.
16
+ module Client
17
+ # Push implements a simple way to transmit a given registry to a given
18
+ # Pushgateway.
19
+ class Push
20
+ class HttpError < StandardError; end
21
+ class HttpRedirectError < HttpError; end
22
+ class HttpClientError < HttpError; end
23
+ class HttpServerError < HttpError; end
24
+
25
+ DEFAULT_GATEWAY = 'http://localhost:9091'.freeze
26
+ PATH = '/metrics/job/%s'.freeze
27
+ SUPPORTED_SCHEMES = %w(http https).freeze
28
+
29
+ attr_reader :job, :gateway, :path
30
+
31
+ def initialize(job:, gateway: DEFAULT_GATEWAY, grouping_key: {}, **kwargs)
32
+ raise ArgumentError, "job cannot be nil" if job.nil?
33
+ raise ArgumentError, "job cannot be empty" if job.empty?
34
+ @validator = LabelSetValidator.new()
35
+ @validator.validate(grouping_key)
36
+
37
+ @mutex = Mutex.new
38
+ @job = job
39
+ @gateway = gateway || DEFAULT_GATEWAY
40
+ @grouping_key = grouping_key
41
+ @path = build_path(job, grouping_key)
42
+
43
+ @uri = parse("#{@gateway}#{@path}")
44
+ validate_no_basic_auth!(@uri)
45
+
46
+ @http = Net::HTTP.new(@uri.host, @uri.port)
47
+ @http.use_ssl = (@uri.scheme == 'https')
48
+ @http.open_timeout = kwargs[:open_timeout] if kwargs[:open_timeout]
49
+ @http.read_timeout = kwargs[:read_timeout] if kwargs[:read_timeout]
50
+ end
51
+
52
+ def basic_auth(user, password)
53
+ @user = user
54
+ @password = password
55
+ end
56
+
57
+ def add(registry)
58
+ synchronize do
59
+ request(Net::HTTP::Post, registry)
60
+ end
61
+ end
62
+
63
+ def replace(registry)
64
+ synchronize do
65
+ request(Net::HTTP::Put, registry)
66
+ end
67
+ end
68
+
69
+ def delete
70
+ synchronize do
71
+ request(Net::HTTP::Delete)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def parse(url)
78
+ uri = URI.parse(url)
79
+
80
+ unless SUPPORTED_SCHEMES.include?(uri.scheme)
81
+ raise ArgumentError, 'only HTTP gateway URLs are supported currently.'
82
+ end
83
+
84
+ uri
85
+ rescue URI::InvalidURIError => e
86
+ raise ArgumentError, "#{url} is not a valid URL: #{e}"
87
+ end
88
+
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
113
+ end
114
+
115
+ path
116
+ end
117
+
118
+ def request(req_class, registry = nil)
119
+ validate_no_label_clashes!(registry) if registry
120
+
121
+ req = req_class.new(@uri)
122
+ req.content_type = Formats::Text::CONTENT_TYPE
123
+ req.basic_auth(@user, @password) if @user
124
+ req.body = Formats::Text.marshal(registry) if registry
125
+
126
+ response = @http.request(req)
127
+ validate_response!(response)
128
+
129
+ response
130
+ end
131
+
132
+ def synchronize
133
+ @mutex.synchronize { yield }
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
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'prometheus/client'
4
+
5
+ module Prometheus
6
+ module Client
7
+ module Rack
8
+ # Collector is a Rack middleware that provides a sample implementation of
9
+ # a HTTP tracer. The default label builder can be modified to export a
10
+ # different set of labels per recorded metric.
11
+ class Collector
12
+ attr_reader :app, :registry
13
+
14
+ def initialize(app, options = {}, &label_builder)
15
+ @app = app
16
+ @registry = options[:registry] || Client.registry
17
+ @label_builder = label_builder || DEFAULT_LABEL_BUILDER
18
+
19
+ init_request_metrics
20
+ init_exception_metrics
21
+ end
22
+
23
+ def call(env) # :nodoc:
24
+ trace(env) { @app.call(env) }
25
+ end
26
+
27
+ protected
28
+
29
+ DEFAULT_LABEL_BUILDER = proc do |env|
30
+ {
31
+ method: env['REQUEST_METHOD'].downcase,
32
+ host: env['HTTP_HOST'].to_s,
33
+ path: env['PATH_INFO'].to_s,
34
+ }
35
+ end
36
+
37
+ def init_request_metrics
38
+ @requests = @registry.counter(
39
+ :http_requests_total,
40
+ 'A counter of the total number of HTTP requests made.',
41
+ )
42
+ @durations = @registry.summary(
43
+ :http_request_duration_seconds,
44
+ 'A summary of the response latency.',
45
+ )
46
+ @durations_hist = @registry.histogram(
47
+ :http_req_duration_seconds,
48
+ 'A histogram of the response latency.',
49
+ )
50
+ end
51
+
52
+ def init_exception_metrics
53
+ @exceptions = @registry.counter(
54
+ :http_exceptions_total,
55
+ 'A counter of the total number of exceptions raised.',
56
+ )
57
+ end
58
+
59
+ def trace(env)
60
+ start = Time.now
61
+ yield.tap do |response|
62
+ duration = (Time.now - start).to_f
63
+ record(labels(env, response), duration)
64
+ end
65
+ rescue => exception
66
+ @exceptions.increment(exception: exception.class.name)
67
+ raise
68
+ end
69
+
70
+ def labels(env, response)
71
+ @label_builder.call(env).tap do |labels|
72
+ labels[:code] = response.first.to_s
73
+ end
74
+ end
75
+
76
+ def record(labels, duration)
77
+ @requests.increment(labels)
78
+ @durations.observe(labels, duration)
79
+ @durations_hist.observe(labels, duration)
80
+ rescue => exception
81
+ @exceptions.increment(exception: exception.class.name)
82
+ raise
83
+ nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'prometheus/client'
4
+ require 'prometheus/client/formats/text'
5
+ require 'prometheus/client/formats/protobuf'
6
+
7
+ module Prometheus
8
+ module Client
9
+ module Rack
10
+ # Exporter is a Rack middleware that provides a sample implementation of
11
+ # a Prometheus HTTP client API.
12
+ class Exporter
13
+ attr_reader :app, :registry, :path
14
+
15
+ FALLBACK = Formats::Text
16
+
17
+ def initialize(app, options = {})
18
+ @app = app
19
+ @registry = options[:registry] || Client.registry
20
+ @path = options[:path] || '/metrics'
21
+
22
+ if Prometheus::Client.configuration.enable_protobuf
23
+ @formats = [Formats::Text, Formats::Protobuf]
24
+ else
25
+ @formats = [Formats::Text]
26
+ end
27
+ @acceptable = build_dictionary(@formats, FALLBACK)
28
+ end
29
+
30
+ def call(env)
31
+ if env['PATH_INFO'] == @path
32
+ format = negotiate(env['HTTP_ACCEPT'], @acceptable)
33
+ format ? respond_with(format) : not_acceptable(@formats)
34
+ else
35
+ @app.call(env)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def negotiate(accept, formats)
42
+ accept = '*/*' if accept.to_s.empty?
43
+
44
+ parse(accept).each do |content_type, _|
45
+ return formats[content_type] if formats.key?(content_type)
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ def parse(header)
52
+ header.to_s.split(/\s*,\s*/).map do |type|
53
+ attributes = type.split(/\s*;\s*/)
54
+ quality = extract_quality(attributes)
55
+
56
+ [attributes.join('; '), quality]
57
+ end.sort_by(&:last).reverse
58
+ end
59
+
60
+ def extract_quality(attributes, default = 1.0)
61
+ quality = default
62
+
63
+ attributes.delete_if do |attr|
64
+ quality = attr.split('q=').last.to_f if attr.start_with?('q=')
65
+ end
66
+
67
+ quality
68
+ end
69
+
70
+ def respond_with(format)
71
+ response = if Prometheus::Client.configuration.value_class.multiprocess
72
+ format.marshal_multiprocess
73
+ else
74
+ format.marshal
75
+ end
76
+ [
77
+ 200,
78
+ { 'Content-Type' => format::CONTENT_TYPE },
79
+ [response],
80
+ ]
81
+ end
82
+
83
+ def not_acceptable(formats)
84
+ types = formats.map { |format| format::MEDIA_TYPE }
85
+
86
+ [
87
+ 406,
88
+ { 'Content-Type' => 'text/plain' },
89
+ ["Supported media types: #{types.join(', ')}"],
90
+ ]
91
+ end
92
+
93
+ def build_dictionary(formats, fallback)
94
+ formats.each_with_object('*/*' => fallback) do |format, memo|
95
+ memo[format::CONTENT_TYPE] = format
96
+ memo[format::MEDIA_TYPE] = format
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end