frankenstein 0.1.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.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ Frankenstein, or the Modern Prometheus, is a collection of tools and
2
+ patterns to make instrumenting a Ruby application just a little bit simpler.
3
+
4
+
5
+ # Installation
6
+
7
+ It's a gem:
8
+
9
+ gem install frankenstein
10
+
11
+ There's also the wonders of [the Gemfile](http://bundler.io):
12
+
13
+ gem 'frankenstein'
14
+
15
+ If you're the sturdy type that likes to run from git:
16
+
17
+ rake install
18
+
19
+ Or, if you've eschewed the convenience of Rubygems entirely, then you
20
+ presumably know what to do already.
21
+
22
+
23
+ # Usage
24
+
25
+ The following classes are available; please see their documentation for more
26
+ details.
27
+
28
+ * **`Frankenstein::Server`**: a simple Webrick-based HTTP server you can
29
+ easily embed in your application to serve metrics requests.
30
+
31
+ * **`Frankenstein::Request`**: collect [basic
32
+ metrics](https://honeycomb.io/blog/2017/01/instrumentation-the-first-four-things-you-measure/)
33
+ about the requests your service receives and makes.
34
+
35
+
36
+ # Contributing
37
+
38
+ See CONTRIBUTING.md.
39
+
40
+
41
+ # Licence
42
+
43
+ Unless otherwise stated, everything in this repo is covered by the following
44
+ copyright notice:
45
+
46
+ Copyright (C) 2017 Civilized Discourse Contruction Kit Inc.
47
+
48
+ This program is free software: you can redistribute it and/or modify it
49
+ under the terms of the GNU General Public License version 3, as
50
+ published by the Free Software Foundation.
51
+
52
+ This program is distributed in the hope that it will be useful,
53
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
54
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55
+ GNU General Public License for more details.
56
+
57
+ You should have received a copy of the GNU General Public License
58
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,48 @@
1
+ begin
2
+ require 'git-version-bump'
3
+ rescue LoadError
4
+ nil
5
+ end
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "frankenstein"
9
+
10
+ s.version = GVB.version rescue "0.0.0.1.NOGVB"
11
+ s.date = GVB.date rescue Time.now.strftime("%Y-%m-%d")
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+
15
+ s.summary = "or, the Modern Prometheus"
16
+ s.description = <<~EOF
17
+ This is a collection of useful tools to help you in more easily
18
+ instrumenting Ruby applications for the Prometheus monitoring
19
+ system.
20
+ EOF
21
+
22
+ s.authors = ["Matt Palmer"]
23
+ s.email = ["matt.palmer@discourse.org"]
24
+ s.homepage = "https://github.com/discourse/frankenstein"
25
+
26
+ s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
27
+
28
+ s.required_ruby_version = ">= 2.3.0"
29
+
30
+ # prometheus-client provides no guaranteed backwards compatibility,
31
+ # and in fact happily breaks things with no notice, so we're stuck
32
+ # with hard-coding a specific version to avoid unexpected disaster.
33
+ s.add_runtime_dependency "prometheus-client", "0.7.1"
34
+ s.add_runtime_dependency "rack", "~> 2.0"
35
+
36
+ s.add_development_dependency 'bundler'
37
+ s.add_development_dependency 'github-release'
38
+ s.add_development_dependency 'git-version-bump'
39
+ s.add_development_dependency 'guard-rspec'
40
+ s.add_development_dependency 'guard-rubocop'
41
+ s.add_development_dependency 'rack-test'
42
+ s.add_development_dependency 'rake', "~> 12.0"
43
+ s.add_development_dependency 'redcarpet'
44
+ s.add_development_dependency 'rspec'
45
+ s.add_development_dependency 'rubocop'
46
+ s.add_development_dependency 'simplecov'
47
+ s.add_development_dependency 'yard'
48
+ end
@@ -0,0 +1,6 @@
1
+ # Base module for all Frankenstein functionality.
2
+ module Frankenstein; end
3
+
4
+ require 'frankenstein/errors'
5
+ require 'frankenstein/request'
6
+ require 'frankenstein/server'
@@ -0,0 +1,4 @@
1
+ module Frankenstein
2
+ # Base class of all exceptions raised by Frankenstein itself
3
+ class Error < StandardError; end
4
+ end
@@ -0,0 +1,191 @@
1
+ require 'prometheus/client'
2
+
3
+ require 'frankenstein/error'
4
+
5
+ module Frankenstein
6
+ # A common pattern for statistical instrumentation is to capture a few basic
7
+ # numbers for all incoming and outgoing requests to the service. Since this
8
+ # is a common pattern, we can abstract that behaviour into a common class,
9
+ # which simplifies the external interface for maintaining statistics in this
10
+ # common case.
11
+ #
12
+ # For more information on this pattern, see
13
+ # https://honeycomb.io/blog/2017/01/instrumentation-the-first-four-things-you-measure/
14
+ #
15
+ class Request
16
+ # No block was passed to #measure.
17
+ class NoBlockError < Frankenstein::Error; end
18
+
19
+ # Create a new request instrumentation package.
20
+ #
21
+ # A "request", for the purposes of this discussion, is a distinct
22
+ # interaction with an external system, typically either the receipt of some
23
+ # sort of communication from another system which needs a response by this
24
+ # system (the one being instrumented), or else the communication to another
25
+ # system from this one for which we are expecting an answer. Each instance
26
+ # of this class should be used to instrument all requests of a particular
27
+ # type.
28
+ #
29
+ # For each instance of this class, the following metrics will be created:
30
+ #
31
+ # * `<prefix>_requests_total` -- a counter indicating the total number
32
+ # of requests started (initiated or received by the system). Labels on
33
+ # this metric are taken from the label set passed to #measure.
34
+ #
35
+ # * `<prefix>_request_duration_seconds` -- a histogram for the response
36
+ # times of successful responses (that is, where no exception was raised).
37
+ # You can get the count of total successful responses from
38
+ # `<prefix>_request_duration_seconds_count`. Labels on this metric
39
+ # are taken from the labels set generated during the measured run (as
40
+ # generated by manipulating the hash yielded to your block).
41
+ #
42
+ # * `<prefix>_exceptions_total` -- a count of the number of exceptions
43
+ # raised during processing. A label, `class`, indicates the class of
44
+ # the exception raised. Labels on this metric are taken from the
45
+ # label set passed to #measure, along with a special label `class`
46
+ # to indicate the class of the exception raised.
47
+ #
48
+ # * `<prefix>_in_progress_count` -- a gauge indicating how many requests
49
+ # are currently in progress as at the time of the scrape. Labels on this
50
+ # metric are taken from the label set passed to #measure.
51
+ #
52
+ # @param prefix [#to_s] the string that will be prepended to all of the
53
+ # Prometheus metric names generated for this instrumentation. The prefix
54
+ # you choose should include both the application name (typically the
55
+ # first word) as well as a unique identifier for the request type itself.
56
+ # Multiple words should be underscore separated.
57
+ #
58
+ # @param outgoing [Boolean] whether this Request instance is collecting
59
+ # data on incoming requests or outgoing requests (the default, as usually
60
+ # there is one incoming request handler, but there can easily be several
61
+ # outgoing request types). It is only used to customise the metric
62
+ # description text for the metrics, so it's not crucially important.
63
+ #
64
+ # @param description [#to_s] a short explanation of what this is measuring.
65
+ # It should be a singular and indefinite noun phrase, to maximise the
66
+ # chances that it will fit neatly into the generated description text.
67
+ #
68
+ # @param registry [Prometheus::Client::Registry] the client registry in
69
+ # which all the metrics will be created. The default will put all the
70
+ # metrics in the Prometheus Client's default registry, which may or may
71
+ # not be what you're up for. If you're using Frankenstein::Server, you
72
+ # want `stats_server.registry`.
73
+ #
74
+ def initialize(prefix, outgoing: true, description: prefix, registry: Prometheus::Client.registry)
75
+ @requests = registry.counter(:"#{prefix}_requests_total", "Number of #{description} requests #{outgoing ? 'sent' : 'received'}")
76
+ @durations = registry.histogram(:"#{prefix}_request_duration_seconds", "Time taken to #{outgoing ? 'receive' : 'send'} a #{description} response")
77
+ @exceptions = registry.counter(:"#{prefix}_exceptions_total", "Number of exceptions raised by the #{description} code")
78
+ @current = registry.gauge(:"#{prefix}_in_progress_count", "Number of #{description} requests currently in progress")
79
+
80
+ # Prometheus::Client::Gauge doesn't (yet) have a built-in way to
81
+ # atomically "adjust" a gauge, only get the current value and set a
82
+ # new value. To avoid the resulting textbook race condition, we
83
+ # need to wrap the get/set pair of operations in this handy-dandy
84
+ # mutex.
85
+ @mutex = Mutex.new
86
+ end
87
+
88
+ # Instrument an instance of the request.
89
+ #
90
+ # Each time a particular external communication occurs, it should be
91
+ # wrapped by a call to this method. Request-related statistics (that
92
+ # the request has been made or received) are updated before the passed
93
+ # block is executed, and then after the block completes,
94
+ # response-related statistics (duration or exception) are recorded. The
95
+ # number of currently-in-progress instances of the request are also kept
96
+ # track of.
97
+ #
98
+ # @param labels [Hash] a set of labels that can help to differentiate
99
+ # different sorts of requests. These labels are applied to the
100
+ # `<prefix>_requests_total` and `<prefix>_in_progress_count` metrics, as
101
+ # well as the `<prefix>_exceptions_total` metric, if an exception is
102
+ # raised.
103
+ #
104
+ # Don't get too fancy with this label set -- it's unusual that this is
105
+ # actually useful in practice. However it is provided for those unusual
106
+ # cases where it isn't a bad idea. Your go-to solution should be to
107
+ # label the `<prefix>_request_duration_seconds` metric (by modifying the
108
+ # hash yielded to the block you pass to #measure), rather than using
109
+ # this parameter with wild abandon.
110
+ #
111
+ # Serious talk time: I've been there. It seems like a great idea at
112
+ # first, to differentiate requests with lots of labels, but it usually
113
+ # just ends up turning into a giant mess. Primarily, due to the way that
114
+ # Prometheus deals with label sets, if you *ever* use a label on *any* of
115
+ # your requests, you need to set the same label to some value on *all* of
116
+ # your requests. So, unless you can say with certainty that every
117
+ # request you receive will logically have some meaningful value for a
118
+ # given label, you shouldn't use it.
119
+ #
120
+ # **NOTE**: the labelset you specify here will be the default labelset
121
+ # applied to the `<prefix>_request_duration_seconds` metric. If you need
122
+ # to remove a label from the response, use `labels.replace` or
123
+ # `labels.delete` to remove the key.
124
+ #
125
+ # @yield [Hash] the labels that will be applied to the
126
+ # `<Prefix>_request_duration_seconds` metric.
127
+ #
128
+ # In order for your label set to be applied, you must *mutate the
129
+ # hash that is yielded*, rather than overwriting it. That means,
130
+ # for example, that the following code **will not work**:
131
+ #
132
+ # req_stats.measure do |labels|
133
+ # labels = {foo: 'bar', baz: 'wombat'}
134
+ # ...
135
+ #
136
+ # Instead, you need to either set each key one by one, or use the
137
+ # handy-dandy Hash#replace method, like this:
138
+ #
139
+ # req_stats.measure do |labels|
140
+ # labels.replace(foo: 'bar', baz: 'wombat')
141
+ # ...
142
+ #
143
+ # If your labels are not being applied to your response histogram,
144
+ # check for any assignment to the yielded variable. It's *really* easy
145
+ # to do by mistake.
146
+ #
147
+ # **NOTE WELL**: The Prometheus specification (assuming it exists)
148
+ # apparently requires that all of the instances of a given metric
149
+ # have the same set of labels. If you fail to do this, an exception
150
+ # will be raised by Prometheus after the block is executed.
151
+ #
152
+ # @raise [Prometheus::Request::NoBlockError] if you didn't pass a block to
153
+ # call. There's nothing to instrument!
154
+ #
155
+ # @raise [Prometheus::Client::LabelSetValidator::LabelSetError] if you
156
+ # violate any written or unwritten rules about how Prometheus label
157
+ # sets should be constructed.
158
+ #
159
+ # @raise [Exception] any exception raised by the executed block will
160
+ # be re-raised by this method after statistics collection is
161
+ # complete.
162
+ #
163
+ # @return [Object] whatever was returned by the block passed.
164
+ #
165
+ def measure(labels = {})
166
+ start_time = Time.now
167
+
168
+ unless block_given?
169
+ raise NoBlockError,
170
+ "No block passed to #{self.class}#measure"
171
+ end
172
+
173
+ @requests.increment(labels, 1)
174
+ @mutex.synchronize { @current.set(labels, (@current.get(labels) || 0) + 1) }
175
+
176
+ res_labels = labels.dup
177
+
178
+ begin
179
+ yield(res_labels).tap do
180
+ elapsed_time = Time.now - start_time
181
+ @durations.observe(res_labels, elapsed_time)
182
+ end
183
+ rescue Exception => ex
184
+ @exceptions.increment(labels.merge(class: ex.class.to_s), 1)
185
+ raise
186
+ ensure
187
+ @mutex.synchronize { @current.set(labels, @current.get(labels) - 1) }
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,121 @@
1
+ require 'logger'
2
+ require 'prometheus/client'
3
+ require 'prometheus/middleware/collector'
4
+ require 'prometheus/middleware/exporter'
5
+ require 'rack'
6
+ require 'rack/builder'
7
+ require 'rack/handler/webrick'
8
+ require 'rack/deflater'
9
+
10
+ require 'frankenstein/error'
11
+
12
+ module Frankenstein
13
+ # A straightforward Prometheus metrics server.
14
+ #
15
+ # When you're looking to instrument your application which isn't, itself, a
16
+ # HTTP service, you need this class. It spawns a WEBrick server on a port you
17
+ # specify, and gives you a registry to put your metrics into. That's pretty
18
+ # much it.
19
+ #
20
+ # The simplest example possible:
21
+ #
22
+ # stats_server = Frankenstein::Server.new
23
+ # stats_server.run # We are now serving stats on port 8080!
24
+ # counter = stats_server.registry.counter(:seconds_count, "Number of seconds")
25
+ # # Give the counter something to count
26
+ # loop { counter.increment({}) }
27
+ #
28
+ # Now if you hit http://localhost:8080/metrics you should see a counter
29
+ # gradually going up, along with stats about the Frankenstein HTTP server
30
+ # itself. Neato!
31
+ #
32
+ # You can change how the webserver logs, and the port it listens on, with options
33
+ # to #new. If for some reason you need to shut down the webserver manually, use
34
+ # #shutdown.
35
+ #
36
+ class Server
37
+ # Indicate that the server is already running
38
+ class AlreadyRunningError < Frankenstein::Error; end
39
+
40
+ # The instance of Prometheus::Client::Registry that contains the metrics
41
+ # that will be presented by this instance of Frankenstein::Server.
42
+ attr_reader :registry
43
+
44
+ # Create a new server instance.
45
+ #
46
+ # @param port [Integer] the TCP to listen on.
47
+ #
48
+ # @param logger [Logger] send log messages from WEBrick to this logger.
49
+ # If not specified, all log messages will be silently eaten.
50
+ #
51
+ # @param metrics_prefix [#to_s] The prefix to apply to the metrics exposed
52
+ # instrumenting the metrics server itself.
53
+ #
54
+ # @param registry [Prometheus::Client::Registry] if you want to use an existing
55
+ # metrics registry for this server, pass it in here. Otherwise, a new one
56
+ # will be created for you, and be made available via #registry.
57
+ #
58
+ def initialize(port: 8080, logger: nil, metrics_prefix: "frankenstein_server", registry: Prometheus::Client::Registry.new)
59
+ @port = port
60
+ @logger = logger || Logger.new(RbConfig::CONFIG['host_os'] =~ /mingw|mswin/ ? 'NUL' : '/dev/null')
61
+ @metrics_prefix = metrics_prefix
62
+ @registry = registry
63
+
64
+ @op_mutex = Mutex.new
65
+ @op_cv = ConditionVariable.new
66
+ end
67
+
68
+ # Start the server instance running.
69
+ #
70
+ # This method returns once the server is just about ready to start serving
71
+ # requests.
72
+ #
73
+ def run
74
+ @op_mutex.synchronize do
75
+ return AlreadyRunningError if @server
76
+
77
+ @server_thread = Thread.new do
78
+ @op_mutex.synchronize do
79
+ @server = WEBrick::HTTPServer.new(Logger: @logger, BindAddress: nil, Port: @port)
80
+ @server.mount "/", Rack::Handler::WEBrick, app
81
+ @op_cv.signal
82
+ end
83
+ @server.start
84
+ end
85
+ end
86
+
87
+ @op_mutex.synchronize { @op_cv.wait(@op_mutex) until @server }
88
+ end
89
+
90
+ # Terminate a running server instance.
91
+ #
92
+ # If the server isn't currently running, this call is a no-op.
93
+ #
94
+ def shutdown
95
+ @op_mutex.synchronize do
96
+ return nil if @server.nil?
97
+ @server.shutdown
98
+ @server = nil
99
+ @server_thread.join
100
+ @server_thread = nil
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def app
107
+ @app ||= begin
108
+ builder = Rack::Builder.new
109
+ builder.use Rack::Deflater, if: ->(_, _, _, body) { body.any? && body[0].length > 512 }
110
+ builder.use Prometheus::Middleware::Collector,
111
+ registry: @registry,
112
+ metrics_prefix: @metrics_prefix,
113
+ counter_label_builder: ->(_, _) { {} },
114
+ duration_label_builder: ->(_, _) { {} }
115
+ builder.use Prometheus::Middleware::Exporter, registry: @registry
116
+ builder.run ->(_) { [301, { 'Location' => "/metrics", 'Content-Type' => 'text/plain' }, ["Try /metrics"]] }
117
+ builder.to_app
118
+ end
119
+ end
120
+ end
121
+ end
metadata ADDED
@@ -0,0 +1,255 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: frankenstein
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Palmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: prometheus-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: github-release
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: git-version-bump
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rack-test
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '12.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '12.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: redcarpet
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: yard
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ description: |
210
+ This is a collection of useful tools to help you in more easily
211
+ instrumenting Ruby applications for the Prometheus monitoring
212
+ system.
213
+ email:
214
+ - matt.palmer@discourse.org
215
+ executables: []
216
+ extensions: []
217
+ extra_rdoc_files: []
218
+ files:
219
+ - ".gitignore"
220
+ - ".rubocop.yml"
221
+ - ".travis.yml"
222
+ - ".yardopts"
223
+ - CODE_OF_CONDUCT.md
224
+ - CONTRIBUTING.md
225
+ - LICENCE
226
+ - README.md
227
+ - frankenstein.gemspec
228
+ - lib/frankenstein.rb
229
+ - lib/frankenstein/error.rb
230
+ - lib/frankenstein/request.rb
231
+ - lib/frankenstein/server.rb
232
+ homepage: https://github.com/discourse/frankenstein
233
+ licenses: []
234
+ metadata: {}
235
+ post_install_message:
236
+ rdoc_options: []
237
+ require_paths:
238
+ - lib
239
+ required_ruby_version: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: 2.3.0
244
+ required_rubygems_version: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ requirements: []
250
+ rubyforge_project:
251
+ rubygems_version: 2.5.2
252
+ signing_key:
253
+ specification_version: 4
254
+ summary: or, the Modern Prometheus
255
+ test_files: []