Class: Frankenstein::Request
- Inherits:
-
Object
- Object
- Frankenstein::Request
- Defined in:
- lib/frankenstein/request.rb
Overview
A common pattern for statistical instrumentation is to capture a few basic numbers for all incoming and outgoing requests to the service. Since this is a common pattern, we can abstract that behaviour into a common class, which simplifies the external interface for maintaining statistics in this common case.
For more information on this pattern, see https://honeycomb.io/blog/2017/01/instrumentation-the-first-four-things-you-measure/
Defined Under Namespace
Classes: NoBlockError
Instance Method Summary collapse
-
#initialize(prefix, labels: [], duration_labels: nil, outgoing: true, description: prefix, registry: Prometheus::Client.registry) ⇒ Request
constructor
Create a new request instrumentation package.
-
#measure(labels = {}) {|Hash| ... } ⇒ Object
Instrument an instance of the request.
Constructor Details
#initialize(prefix, labels: [], duration_labels: nil, outgoing: true, description: prefix, registry: Prometheus::Client.registry) ⇒ Request
Create a new request instrumentation package.
A "request", for the purposes of this discussion, is a distinct interaction with an external system, typically either the receipt of some sort of communication from another system which needs a response by this system (the one being instrumented), or else the communication to another system from this one for which we are expecting an answer. Each instance of this class should be used to instrument all requests of a particular type.
For each instance of this class, the following metrics will be created:
<prefix>_requests_total
-- a counter indicating the total number of requests started (initiated or received by the system). Labels on this metric are taken from the label set passed to #measure.<prefix>_request_duration_seconds
-- a histogram for the response times of successful responses (that is, where no exception was raised). You can get the count of total successful responses from<prefix>_request_duration_seconds_count
. Labels on this metric are taken from the labels set generated during the measured run (as generated by manipulating the hash yielded to your block).<prefix>_exceptions_total
-- a count of the number of exceptions raised during processing. A label,class
, indicates the class of the exception raised. Labels on this metric are taken from the label set passed to #measure, along with a special labelclass
to indicate the class of the exception raised.<prefix>_in_progress_count
-- a gauge indicating how many requests are currently in progress as at the time of the scrape. Labels on this metric are taken from the label set passed to #measure.
82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/frankenstein/request.rb', line 82 def initialize(prefix, labels: [], duration_labels: nil, outgoing: true, description: prefix, registry: Prometheus::Client.registry) @requests = registry.get(:"#{prefix}_requests_total") || registry.counter(:"#{prefix}_requests_total", docstring: "Number of #{description} requests #{outgoing ? 'sent' : 'received'}", labels: labels) @durations = registry.get(:"#{prefix}_request_duration_seconds") || registry.histogram(:"#{prefix}_request_duration_seconds", docstring: "Time taken to #{outgoing ? 'receive' : 'send'} a #{description} response", labels: duration_labels || labels) @exceptions = registry.get(:"#{prefix}_exceptions_total") || registry.counter(:"#{prefix}_exceptions_total", docstring: "Number of exceptions raised by the #{description} code", labels: labels + [:class]) @current = registry.get(:"#{prefix}_in_progress_count") || registry.gauge(:"#{prefix}_in_progress_count", docstring: "Number of #{description} requests currently in progress", labels: labels) # Prometheus::Client::Gauge doesn't (yet) have a built-in way to # atomically "adjust" a gauge, only get the current value and set a # new value. To avoid the resulting textbook race condition, we # need to wrap the get/set pair of operations in this handy-dandy # mutex. @mutex = Mutex.new end |
Instance Method Details
#measure(labels = {}) {|Hash| ... } ⇒ Object
Instrument an instance of the request.
Each time a particular external communication occurs, it should be wrapped by a call to this method. Request-related statistics (that the request has been made or received) are updated before the passed block is executed, and then after the block completes, response-related statistics (duration or exception) are recorded. The number of currently-in-progress instances of the request are also kept track of.
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/frankenstein/request.rb', line 173 def measure(labels = {}) start_time = Time.now unless block_given? raise NoBlockError, "No block passed to #{self.class}#measure" end @requests.increment(labels: labels) @mutex.synchronize { @current.set((@current.get(labels: labels) || 0) + 1, labels: labels) } res_labels = labels.dup begin yield(res_labels).tap do elapsed_time = Time.now - start_time @durations.observe(elapsed_time, labels: res_labels) end rescue Exception => ex @exceptions.increment(labels: labels.merge(class: ex.class.to_s)) raise ensure @mutex.synchronize { @current.set(@current.get(labels: labels) - 1, labels: labels) } end end |