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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +104 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/CONTRIBUTING.md +13 -0
- data/LICENCE +674 -0
- data/README.md +58 -0
- data/frankenstein.gemspec +48 -0
- data/lib/frankenstein.rb +6 -0
- data/lib/frankenstein/error.rb +4 -0
- data/lib/frankenstein/request.rb +191 -0
- data/lib/frankenstein/server.rb +121 -0
- metadata +255 -0
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
|
data/lib/frankenstein.rb
ADDED
@@ -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: []
|