prometheus_exporter 2.0.2 → 2.0.4
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 +4 -4
- data/CHANGELOG +10 -0
- data/README.md +29 -1
- data/examples/custom_collector.rb +1 -1
- data/lib/prometheus_exporter/instrumentation/method_profiler.rb +63 -24
- data/lib/prometheus_exporter/instrumentation/sidekiq.rb +20 -12
- data/lib/prometheus_exporter/metric/histogram.rb +3 -3
- data/lib/prometheus_exporter/middleware.rb +24 -7
- data/lib/prometheus_exporter/server/web_collector.rb +1 -1
- data/lib/prometheus_exporter/server/web_server.rb +3 -1
- data/lib/prometheus_exporter/version.rb +1 -1
- data/prometheus_exporter.gemspec +3 -1
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4f6fca67df8cdcd10a86edb00a89cc61a807af54f80794a6573bea5a8805bc0
|
4
|
+
data.tar.gz: 66e4867922235be2bb18df90b8ecbf3ad6b3d08d4ad92f62219ea26d80ca2515
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a31e11d9ba63709048ccebe7352f636f601800dd19d00aa3e29448a22d1e0de6436882d32ecb2e5911e16df0409acf1b1fb71d351f67afd38b3fb6e39976099
|
7
|
+
data.tar.gz: ba2c7fb01384299fa8f16656dd106ba99e82c721aca7d6442cd8f8a19a0b0ce82ff2b39935eeb0d8e19c096e5436c564ee1defe813dcf93bd3c41e6c0fc8dedc
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
2.0.4 - 2022-11-10
|
2
|
+
|
3
|
+
- FIX/FEATURE: support for Redis 5 gem instrumentation
|
4
|
+
|
5
|
+
2.0.3 - 2022-05-23
|
6
|
+
|
7
|
+
- FEATURE: new ping endpoint for keepalive checks
|
8
|
+
- FIX: order histogram correctly for GCP support
|
9
|
+
- FIX: improve sidekiq instrumentation
|
10
|
+
|
1
11
|
2.0.2 - 2022-02-25
|
2
12
|
|
3
13
|
- FIX: runner was not requiring unicorn integration correctly leading to a crash
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@ Prometheus Exporter allows you to aggregate custom metrics from multiple process
|
|
5
5
|
To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/archive/2018/02/02/instrumenting-rails-with-prometheus) (it has pretty pictures!)
|
6
6
|
|
7
7
|
* [Requirements](#requirements)
|
8
|
-
* [Migrating from v0.x](#migrating-from-
|
8
|
+
* [Migrating from v0.x](#migrating-from-v0x)
|
9
9
|
* [Installation](#installation)
|
10
10
|
* [Usage](#usage)
|
11
11
|
* [Single process mode](#single-process-mode)
|
@@ -201,6 +201,14 @@ Ensure you run the exporter in a monitored background process:
|
|
201
201
|
$ bundle exec prometheus_exporter
|
202
202
|
```
|
203
203
|
|
204
|
+
#### Choosing the style of method patching
|
205
|
+
|
206
|
+
By default, `prometheus_exporter` uses `alias_method` to instrument methods used by SQL and Redis as it is the fastest approach (see [this article](https://samsaffron.com/archive/2017/10/18/fastest-way-to-profile-a-method-in-ruby)). You may desire to add additional instrumentation libraries beyond `prometheus_exporter` to your app. This can become problematic if these other libraries instead use `prepend` to instrument methods. To resolve this, you can tell the middleware to instrument using `prepend` by passing an `instrument` option like so:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
Rails.application.middleware.unshift PrometheusExporter::Middleware, instrument: :prepend
|
210
|
+
```
|
211
|
+
|
204
212
|
#### Metrics collected by Rails integration middleware
|
205
213
|
|
206
214
|
| Type | Name | Description |
|
@@ -952,6 +960,26 @@ You can also pass a log level (default is [`Logger::WARN`](https://ruby-doc.org/
|
|
952
960
|
PrometheusExporter::Client.new(log_level: Logger::DEBUG)
|
953
961
|
```
|
954
962
|
|
963
|
+
## Docker/Kubernetes Healthcheck
|
964
|
+
|
965
|
+
A `/ping` endpoint which only returns `PONG` is available so you can run container healthchecks :
|
966
|
+
|
967
|
+
Example:
|
968
|
+
|
969
|
+
```yml
|
970
|
+
services:
|
971
|
+
rails-exporter:
|
972
|
+
command:
|
973
|
+
- bin/prometheus_exporter
|
974
|
+
- -b
|
975
|
+
- 0.0.0.0
|
976
|
+
healthcheck:
|
977
|
+
test: ["CMD", "curl", "--silent", "--show-error", "--fail", "--max-time", "3", "http://0.0.0.0:9394/ping"]
|
978
|
+
timeout: 3s
|
979
|
+
interval: 10s
|
980
|
+
retries: 5
|
981
|
+
```
|
982
|
+
|
955
983
|
## Contributing
|
956
984
|
|
957
985
|
Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/prometheus_exporter. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class MyCustomCollector < PrometheusExporter::Server::
|
3
|
+
class MyCustomCollector < PrometheusExporter::Server::BaseCollector
|
4
4
|
def initialize
|
5
5
|
@gauge1 = PrometheusExporter::Metric::Gauge.new("thing1", "I am thing 1")
|
6
6
|
@gauge2 = PrometheusExporter::Metric::Gauge.new("thing2", "I am thing 2")
|
@@ -4,30 +4,14 @@
|
|
4
4
|
module PrometheusExporter::Instrumentation; end
|
5
5
|
|
6
6
|
class PrometheusExporter::Instrumentation::MethodProfiler
|
7
|
-
def self.patch(klass, methods, name)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
return #{method_name}__mp_unpatched(*args, &blk)
|
16
|
-
end
|
17
|
-
begin
|
18
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
19
|
-
#{method_name}__mp_unpatched(*args, &blk)
|
20
|
-
ensure
|
21
|
-
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
22
|
-
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
23
|
-
data[:calls] += 1
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
RUBY
|
28
|
-
end.join("\n")
|
29
|
-
|
30
|
-
klass.class_eval patches, __FILE__, patch_source_line
|
7
|
+
def self.patch(klass, methods, name, instrument:)
|
8
|
+
if instrument == :alias_method
|
9
|
+
patch_using_alias_method(klass, methods, name)
|
10
|
+
elsif instrument == :prepend
|
11
|
+
patch_using_prepend(klass, methods, name)
|
12
|
+
else
|
13
|
+
raise ArgumentError, "instrument must be :alias_method or :prepend"
|
14
|
+
end
|
31
15
|
end
|
32
16
|
|
33
17
|
def self.transfer
|
@@ -55,4 +39,59 @@ class PrometheusExporter::Instrumentation::MethodProfiler
|
|
55
39
|
end
|
56
40
|
data
|
57
41
|
end
|
42
|
+
|
43
|
+
def self.define_methods_on_module(klass, methods, name)
|
44
|
+
patch_source_line = __LINE__ + 3
|
45
|
+
patches = methods.map do |method_name|
|
46
|
+
<<~RUBY
|
47
|
+
def #{method_name}(*args, &blk)
|
48
|
+
unless prof = Thread.current[:_method_profiler]
|
49
|
+
return super
|
50
|
+
end
|
51
|
+
begin
|
52
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
53
|
+
super
|
54
|
+
ensure
|
55
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
56
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
57
|
+
data[:calls] += 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
RUBY
|
61
|
+
end.join("\n")
|
62
|
+
|
63
|
+
klass.module_eval patches, __FILE__, patch_source_line
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.patch_using_prepend(klass, methods, name)
|
67
|
+
prepend_instument = Module.new
|
68
|
+
define_methods_on_module(klass, methods, name)
|
69
|
+
klass.prepend(prepend_instument)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.patch_using_alias_method(klass, methods, name)
|
73
|
+
patch_source_line = __LINE__ + 3
|
74
|
+
patches = methods.map do |method_name|
|
75
|
+
<<~RUBY
|
76
|
+
unless defined?(#{method_name}__mp_unpatched)
|
77
|
+
alias_method :#{method_name}__mp_unpatched, :#{method_name}
|
78
|
+
def #{method_name}(*args, &blk)
|
79
|
+
unless prof = Thread.current[:_method_profiler]
|
80
|
+
return #{method_name}__mp_unpatched(*args, &blk)
|
81
|
+
end
|
82
|
+
begin
|
83
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
84
|
+
#{method_name}__mp_unpatched(*args, &blk)
|
85
|
+
ensure
|
86
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
87
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
88
|
+
data[:calls] += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
RUBY
|
93
|
+
end.join("\n")
|
94
|
+
|
95
|
+
klass.class_eval patches, __FILE__, patch_source_line
|
96
|
+
end
|
58
97
|
end
|
@@ -16,12 +16,12 @@ module PrometheusExporter::Instrumentation
|
|
16
16
|
job_is_fire_and_forget = job["retry"] == false
|
17
17
|
|
18
18
|
worker_class = Object.const_get(job["class"])
|
19
|
-
worker_custom_labels = self.get_worker_custom_labels(worker_class)
|
19
|
+
worker_custom_labels = self.get_worker_custom_labels(worker_class, job)
|
20
20
|
|
21
21
|
unless job_is_fire_and_forget
|
22
22
|
PrometheusExporter::Client.default.send_json(
|
23
23
|
type: "sidekiq",
|
24
|
-
name: job["class"],
|
24
|
+
name: get_name(job["class"], job),
|
25
25
|
dead: true,
|
26
26
|
custom_labels: worker_custom_labels
|
27
27
|
)
|
@@ -29,12 +29,21 @@ module PrometheusExporter::Instrumentation
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def self.get_worker_custom_labels(worker_class)
|
33
|
-
worker_class.respond_to?(:custom_labels)
|
32
|
+
def self.get_worker_custom_labels(worker_class, msg)
|
33
|
+
return {} unless worker_class.respond_to?(:custom_labels)
|
34
|
+
|
35
|
+
# TODO remove when version 3.0.0 is released
|
36
|
+
method_arity = worker_class.method(:custom_labels).arity
|
37
|
+
|
38
|
+
if method_arity > 0
|
39
|
+
worker_class.custom_labels(msg)
|
40
|
+
else
|
41
|
+
worker_class.custom_labels
|
42
|
+
end
|
34
43
|
end
|
35
44
|
|
36
|
-
def initialize(client: nil)
|
37
|
-
@client = client || PrometheusExporter::Client.default
|
45
|
+
def initialize(options = { client: nil })
|
46
|
+
@client = options.fetch(:client, nil) || PrometheusExporter::Client.default
|
38
47
|
end
|
39
48
|
|
40
49
|
def call(worker, msg, queue)
|
@@ -51,19 +60,18 @@ module PrometheusExporter::Instrumentation
|
|
51
60
|
duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
|
52
61
|
@client.send_json(
|
53
62
|
type: "sidekiq",
|
54
|
-
name: get_name(worker, msg),
|
63
|
+
name: self.class.get_name(worker.class.to_s, msg),
|
55
64
|
queue: queue,
|
56
65
|
success: success,
|
57
66
|
shutdown: shutdown,
|
58
67
|
duration: duration,
|
59
|
-
custom_labels: self.class.get_worker_custom_labels(worker.class)
|
68
|
+
custom_labels: self.class.get_worker_custom_labels(worker.class, msg)
|
60
69
|
)
|
61
70
|
end
|
62
71
|
|
63
72
|
private
|
64
73
|
|
65
|
-
def get_name(
|
66
|
-
class_name = worker.class.to_s
|
74
|
+
def self.get_name(class_name, msg)
|
67
75
|
if class_name == JOB_WRAPPER_CLASS_NAME
|
68
76
|
get_job_wrapper_name(msg)
|
69
77
|
elsif DELAYED_CLASS_NAMES.include?(class_name)
|
@@ -73,11 +81,11 @@ module PrometheusExporter::Instrumentation
|
|
73
81
|
end
|
74
82
|
end
|
75
83
|
|
76
|
-
def get_job_wrapper_name(msg)
|
84
|
+
def self.get_job_wrapper_name(msg)
|
77
85
|
msg['wrapped']
|
78
86
|
end
|
79
87
|
|
80
|
-
def get_delayed_name(msg, class_name)
|
88
|
+
def self.get_delayed_name(msg, class_name)
|
81
89
|
begin
|
82
90
|
# fallback to class_name since we're relying on the internal implementation
|
83
91
|
# of the delayed extensions
|
@@ -19,7 +19,7 @@ module PrometheusExporter::Metric
|
|
19
19
|
|
20
20
|
def initialize(name, help, opts = {})
|
21
21
|
super(name, help)
|
22
|
-
@buckets = (opts[:buckets] || self.class.default_buckets).sort
|
22
|
+
@buckets = (opts[:buckets] || self.class.default_buckets).sort
|
23
23
|
reset!
|
24
24
|
end
|
25
25
|
|
@@ -57,11 +57,11 @@ module PrometheusExporter::Metric
|
|
57
57
|
first = false
|
58
58
|
count = @counts[labels]
|
59
59
|
sum = @sums[labels]
|
60
|
-
text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, "+Inf"))} #{count}\n"
|
61
60
|
@buckets.each do |bucket|
|
62
61
|
value = @observations[labels][bucket]
|
63
62
|
text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, bucket.to_s))} #{value}\n"
|
64
63
|
end
|
64
|
+
text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, "+Inf"))} #{count}\n"
|
65
65
|
text << "#{prefix(@name)}_count#{labels_text(labels)} #{count}\n"
|
66
66
|
text << "#{prefix(@name)}_sum#{labels_text(labels)} #{sum}"
|
67
67
|
end
|
@@ -91,7 +91,7 @@ module PrometheusExporter::Metric
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def fill_buckets(value, buckets)
|
94
|
-
@buckets.each do |b|
|
94
|
+
@buckets.reverse.each do |b|
|
95
95
|
break if value > b
|
96
96
|
buckets[b] += 1
|
97
97
|
end
|
@@ -6,23 +6,30 @@ require 'prometheus_exporter/client'
|
|
6
6
|
class PrometheusExporter::Middleware
|
7
7
|
MethodProfiler = PrometheusExporter::Instrumentation::MethodProfiler
|
8
8
|
|
9
|
-
def initialize(app, config = { instrument:
|
9
|
+
def initialize(app, config = { instrument: :alias_method, client: nil })
|
10
10
|
@app = app
|
11
11
|
@client = config[:client] || PrometheusExporter::Client.default
|
12
12
|
|
13
13
|
if config[:instrument]
|
14
|
-
if defined?
|
15
|
-
|
14
|
+
if defined?(RedisClient)
|
15
|
+
apply_redis_client_middleware!
|
16
|
+
end
|
17
|
+
if defined?(Redis::VERSION) && (Gem::Version.new(Redis::VERSION) >= Gem::Version.new('5.0.0'))
|
18
|
+
# redis 5 support handled via RedisClient
|
19
|
+
elsif defined? Redis::Client
|
20
|
+
MethodProfiler.patch(Redis::Client, [
|
21
|
+
:call, :call_pipeline
|
22
|
+
], :redis, instrument: config[:instrument])
|
16
23
|
end
|
17
24
|
if defined? PG::Connection
|
18
25
|
MethodProfiler.patch(PG::Connection, [
|
19
26
|
:exec, :async_exec, :exec_prepared, :send_query_prepared, :query
|
20
|
-
], :sql)
|
27
|
+
], :sql, instrument: config[:instrument])
|
21
28
|
end
|
22
29
|
if defined? Mysql2::Client
|
23
|
-
MethodProfiler.patch(Mysql2::Client, [:query], :sql)
|
24
|
-
MethodProfiler.patch(Mysql2::Statement, [:execute], :sql)
|
25
|
-
MethodProfiler.patch(Mysql2::Result, [:each], :sql)
|
30
|
+
MethodProfiler.patch(Mysql2::Client, [:query], :sql, instrument: config[:instrument])
|
31
|
+
MethodProfiler.patch(Mysql2::Statement, [:execute], :sql, instrument: config[:instrument])
|
32
|
+
MethodProfiler.patch(Mysql2::Result, [:each], :sql, instrument: config[:instrument])
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
@@ -114,4 +121,14 @@ class PrometheusExporter::Middleware
|
|
114
121
|
|
115
122
|
end
|
116
123
|
|
124
|
+
private
|
125
|
+
|
126
|
+
module RedisInstrumenter
|
127
|
+
MethodProfiler.define_methods_on_module(self, ["call", "call_pipelined"], "redis")
|
128
|
+
end
|
129
|
+
|
130
|
+
def apply_redis_client_middleware!
|
131
|
+
RedisClient.register(RedisInstrumenter)
|
132
|
+
end
|
133
|
+
|
117
134
|
end
|
@@ -60,7 +60,7 @@ module PrometheusExporter::Server
|
|
60
60
|
custom_labels = obj['custom_labels']
|
61
61
|
labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
|
62
62
|
|
63
|
-
@http_requests_total.observe(1, labels.merge(status
|
63
|
+
@http_requests_total.observe(1, labels.merge("status" => obj["status"]))
|
64
64
|
|
65
65
|
if timings = obj["timings"]
|
66
66
|
@http_request_duration_seconds.observe(timings["total_duration"], labels)
|
@@ -73,9 +73,11 @@ module PrometheusExporter::Server
|
|
73
73
|
end
|
74
74
|
elsif req.path == '/send-metrics'
|
75
75
|
handle_metrics(req, res)
|
76
|
+
elsif req.path == '/ping'
|
77
|
+
res.body = 'PONG'
|
76
78
|
else
|
77
79
|
res.status = 404
|
78
|
-
res.body = "Not Found! The Prometheus Ruby Exporter only listens on /metrics and /send-metrics"
|
80
|
+
res.body = "Not Found! The Prometheus Ruby Exporter only listens on /ping, /metrics and /send-metrics"
|
79
81
|
end
|
80
82
|
end
|
81
83
|
end
|
data/prometheus_exporter.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency "rubocop", ">= 0.69"
|
30
30
|
spec.add_development_dependency "bundler", ">= 2.1.4"
|
31
31
|
spec.add_development_dependency "rake", "~> 13.0"
|
32
|
-
spec.add_development_dependency "minitest", "~> 5.0"
|
32
|
+
spec.add_development_dependency "minitest", "~> 5.15.0" # https://github.com/qrush/m/issues/93
|
33
33
|
spec.add_development_dependency "guard", "~> 2.0"
|
34
34
|
spec.add_development_dependency "mini_racer", "~> 0.5.0"
|
35
35
|
spec.add_development_dependency "guard-minitest", "~> 2.0"
|
@@ -39,6 +39,8 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency "rubocop-discourse", ">2"
|
40
40
|
spec.add_development_dependency "appraisal", "~> 2.3"
|
41
41
|
spec.add_development_dependency "activerecord", "~> 6.0.0"
|
42
|
+
spec.add_development_dependency "redis", "> 5"
|
43
|
+
spec.add_development_dependency "m"
|
42
44
|
if !RUBY_ENGINE == 'jruby'
|
43
45
|
spec.add_development_dependency "raindrops", "~> 0.19"
|
44
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prometheus_exporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: webrick
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 5.15.0
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 5.15.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: guard
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +206,34 @@ dependencies:
|
|
206
206
|
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: 6.0.0
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: redis
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">"
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '5'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">"
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '5'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: m
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
209
237
|
description: Prometheus metric collector and exporter for Ruby
|
210
238
|
email:
|
211
239
|
- sam.saffron@gmail.com
|