promenade 0.5.0 → 0.6.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 +4 -4
 - data/Gemfile.lock +2 -2
 - data/README.md +18 -0
 - data/lib/promenade/client/rack/http_request_duration_collector.rb +92 -0
 - data/lib/promenade/client/rack/http_request_queue_time_collector.rb +55 -0
 - data/lib/promenade/client/rack/middleware_base.rb +36 -0
 - data/lib/promenade/client/rack/queue_time_duration.rb +50 -0
 - data/lib/promenade/configuration.rb +4 -1
 - data/lib/promenade/railtie.rb +5 -2
 - data/lib/promenade/version.rb +1 -1
 - metadata +7 -4
 - data/lib/promenade/client/rack/collector.rb +0 -113
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 235f95d0b77870a9975ff9870428ed5a995b9174380b981c2df7d8a338519ffb
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 7d6e327e2d602fd2b07342fb45c9be29e36ad266212ad4ed98a72eafee58bc9e
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: e8596f53e3110b636696cb38f2d3bfa872715de3e331fdf260da62e30c31949a1753702b935c3343545c7be19da56f0e30a65d89568eefd419b94a5ef80fe2f9
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 7e65f494c50b2bc25b05b169794f73c02c64ad1548c3425399b1d2cb4e7077d930e0e7d9c99ea88088c76e1026bb64cfbda246ab1a6b09c057b4f2b717b130d8
         
     | 
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
2 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
3 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                promenade (0. 
     | 
| 
      
 4 
     | 
    
         
            +
                promenade (0.6.0)
         
     | 
| 
       5 
5 
     | 
    
         
             
                  actionpack
         
     | 
| 
       6 
6 
     | 
    
         
             
                  activesupport (> 6.0, < 8.0)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  prometheus-client-mmap (~> 0.16.0)
         
     | 
| 
         @@ -140,7 +140,7 @@ GEM 
     | 
|
| 
       140 
140 
     | 
    
         
             
                parallel (1.22.1)
         
     | 
| 
       141 
141 
     | 
    
         
             
                parser (3.1.2.0)
         
     | 
| 
       142 
142 
     | 
    
         
             
                  ast (~> 2.4.1)
         
     | 
| 
       143 
     | 
    
         
            -
                prometheus-client-mmap (0.16. 
     | 
| 
      
 143 
     | 
    
         
            +
                prometheus-client-mmap (0.16.2)
         
     | 
| 
       144 
144 
     | 
    
         
             
                pry (0.14.1)
         
     | 
| 
       145 
145 
     | 
    
         
             
                  coderay (~> 1.1)
         
     | 
| 
       146 
146 
     | 
    
         
             
                  method_source (~> 1.0)
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -181,6 +181,20 @@ Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions, 
     | 
|
| 
       181 
181 
     | 
    
         
             
                    exception_handler: exception_handler
         
     | 
| 
       182 
182 
     | 
    
         
             
            ```
         
     | 
| 
       183 
183 
     | 
    
         | 
| 
      
 184 
     | 
    
         
            +
            #### Customising the histogram buckets
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
            The default buckets cover a range of latencies from 5 ms to 10s see [Promenade::Configuration::DEFAULT_RACK_LATENCY_BUCKETS](https://github.com/errm/promenade/blob/ea7eb54c04257770a601b7e28b3e13db5d2430bb/lib/promenade/configuration.rb#L5). This is intended to capture the typical range of latencies for a web application. However, this might not be suitable for your Service-Level Agreements (SLAs), and other bucket size intervals may be required (see [histogram bins](https://en.wikipedia.org/wiki/Histogram#Number_of_bins_and_width)).
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
            If you would like to customise the histogram buckets, you can do so by configuring Promenade in an initializer:
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 191 
     | 
    
         
            +
            # config/initializers/promenade.rb
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            Promenade.configure do |config|
         
     | 
| 
      
 194 
     | 
    
         
            +
              config.rack_latency_buckets = [0.25, 0.350, 0.5, 1, 1.5, 2.5, 5, 10, 15, 19]
         
     | 
| 
      
 195 
     | 
    
         
            +
            end
         
     | 
| 
      
 196 
     | 
    
         
            +
            ```
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
       184 
198 
     | 
    
         
             
            ### Configuration
         
     | 
| 
       185 
199 
     | 
    
         | 
| 
       186 
200 
     | 
    
         
             
            If you are using rails it should load a railtie and configure promenade.
         
     | 
| 
         @@ -205,6 +219,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/errm/p 
     | 
|
| 
       205 
219 
     | 
    
         | 
| 
       206 
220 
     | 
    
         
             
            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.
         
     | 
| 
       207 
221 
     | 
    
         | 
| 
      
 222 
     | 
    
         
            +
            ## Acknowledgements
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            The original code for the Rack middleware collector class was copied from [Prometheus Client MMap](https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb).
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
       208 
226 
     | 
    
         
             
            ## License
         
     | 
| 
       209 
227 
     | 
    
         | 
| 
       210 
228 
     | 
    
         
             
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         
     | 
| 
         @@ -0,0 +1,92 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "prometheus/client"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative "middleware_base"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative "request_labeler"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "exception_handler"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative "queue_time_duration"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Promenade
         
     | 
| 
      
 8 
     | 
    
         
            +
              module Client
         
     | 
| 
      
 9 
     | 
    
         
            +
                module Rack
         
     | 
| 
      
 10 
     | 
    
         
            +
                  class HTTPRequestDurationCollector < MiddlwareBase
         
     | 
| 
      
 11 
     | 
    
         
            +
                    REQUEST_DURATION_HISTOGRAM_NAME = :http_req_duration_seconds
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    REQUESTS_COUNTER_NAME = :http_requests_total
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    EXCEPTIONS_COUNTER_NAME = :http_exceptions_total
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    private_constant :REQUEST_DURATION_HISTOGRAM_NAME,
         
     | 
| 
      
 18 
     | 
    
         
            +
                      :REQUESTS_COUNTER_NAME,
         
     | 
| 
      
 19 
     | 
    
         
            +
                      :EXCEPTIONS_COUNTER_NAME
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    def initialize(app,
         
     | 
| 
      
 22 
     | 
    
         
            +
                                   registry: ::Prometheus::Client.registry,
         
     | 
| 
      
 23 
     | 
    
         
            +
                                   label_builder: RequestLabeler,
         
     | 
| 
      
 24 
     | 
    
         
            +
                                   exception_handler: nil)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                      @latency_buckets = Promenade.configuration.rack_latency_buckets
         
     | 
| 
      
 27 
     | 
    
         
            +
                      @_exception_handler = exception_handler
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                      super(app, registry: registry, label_builder: label_builder)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    private
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                      attr_reader :latency_buckets, :queue_time_buckets
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      def trace(env)
         
     | 
| 
      
 37 
     | 
    
         
            +
                        start = current_time
         
     | 
| 
      
 38 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 39 
     | 
    
         
            +
                          response = yield
         
     | 
| 
      
 40 
     | 
    
         
            +
                          record_request_duration(labels(env, response), duration_since(start))
         
     | 
| 
      
 41 
     | 
    
         
            +
                          response
         
     | 
| 
      
 42 
     | 
    
         
            +
                        rescue StandardError => e
         
     | 
| 
      
 43 
     | 
    
         
            +
                          exception_handler.call(e, env, duration_since(start))
         
     | 
| 
      
 44 
     | 
    
         
            +
                        end
         
     | 
| 
      
 45 
     | 
    
         
            +
                      end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                      def record_request_duration(labels, duration)
         
     | 
| 
      
 48 
     | 
    
         
            +
                        requests_counter.increment(labels)
         
     | 
| 
      
 49 
     | 
    
         
            +
                        durations_histogram.observe(labels, duration)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                      def duration_since(start_time)
         
     | 
| 
      
 53 
     | 
    
         
            +
                        current_time - start_time
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                      def current_time
         
     | 
| 
      
 57 
     | 
    
         
            +
                        Process.clock_gettime(Process::CLOCK_MONOTONIC)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                      def durations_histogram
         
     | 
| 
      
 61 
     | 
    
         
            +
                        registry.get(REQUEST_DURATION_HISTOGRAM_NAME)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                      def requests_counter
         
     | 
| 
      
 65 
     | 
    
         
            +
                        registry.get(REQUESTS_COUNTER_NAME)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                      def register_metrics!
         
     | 
| 
      
 69 
     | 
    
         
            +
                        registry.counter(REQUESTS_COUNTER_NAME,
         
     | 
| 
      
 70 
     | 
    
         
            +
                          "A counter of the total number of HTTP requests made.")
         
     | 
| 
      
 71 
     | 
    
         
            +
                        registry.histogram(REQUEST_DURATION_HISTOGRAM_NAME,
         
     | 
| 
      
 72 
     | 
    
         
            +
                          "A histogram of the response latency.", {}, latency_buckets)
         
     | 
| 
      
 73 
     | 
    
         
            +
                        registry.counter(EXCEPTIONS_COUNTER_NAME,
         
     | 
| 
      
 74 
     | 
    
         
            +
                          "A counter of the total number of exceptions raised.")
         
     | 
| 
      
 75 
     | 
    
         
            +
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                      def exception_handler
         
     | 
| 
      
 78 
     | 
    
         
            +
                        @_exception_handler ||= default_exception_handler
         
     | 
| 
      
 79 
     | 
    
         
            +
                      end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                      def default_exception_handler
         
     | 
| 
      
 82 
     | 
    
         
            +
                        ExceptionHandler.initialize_singleton(
         
     | 
| 
      
 83 
     | 
    
         
            +
                          histogram_name: REQUEST_DURATION_HISTOGRAM_NAME,
         
     | 
| 
      
 84 
     | 
    
         
            +
                          requests_counter_name: REQUESTS_COUNTER_NAME,
         
     | 
| 
      
 85 
     | 
    
         
            +
                          exceptions_counter_name: EXCEPTIONS_COUNTER_NAME,
         
     | 
| 
      
 86 
     | 
    
         
            +
                          registry: registry,
         
     | 
| 
      
 87 
     | 
    
         
            +
                        )
         
     | 
| 
      
 88 
     | 
    
         
            +
                      end
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
              end
         
     | 
| 
      
 92 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "prometheus/client"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative "middleware_base"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative "request_labeler"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "queue_time_duration"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Promenade
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Client
         
     | 
| 
      
 8 
     | 
    
         
            +
                module Rack
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class HTTPRequestQueueTimeCollector < MiddlwareBase
         
     | 
| 
      
 10 
     | 
    
         
            +
                    REQUEST_QUEUE_TIME_HISTOGRAM_NAME = :http_req_queue_time_seconds
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    private_constant :REQUEST_QUEUE_TIME_HISTOGRAM_NAME
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                    def initialize(app,
         
     | 
| 
      
 15 
     | 
    
         
            +
                                   registry: ::Prometheus::Client.registry,
         
     | 
| 
      
 16 
     | 
    
         
            +
                                   label_builder: RequestLabeler)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                      @queue_time_buckets = Promenade.configuration.queue_time_buckets
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                      super(app, registry: registry, label_builder: label_builder)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    private
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      attr_reader :queue_time_buckets
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      def trace(env)
         
     | 
| 
      
 28 
     | 
    
         
            +
                        start_timestamp = Time.now.utc
         
     | 
| 
      
 29 
     | 
    
         
            +
                        response = yield
         
     | 
| 
      
 30 
     | 
    
         
            +
                        record_request_queue_time(labels: labels(env, response),
         
     | 
| 
      
 31 
     | 
    
         
            +
                          env: env,
         
     | 
| 
      
 32 
     | 
    
         
            +
                          request_received_time: start_timestamp)
         
     | 
| 
      
 33 
     | 
    
         
            +
                        response
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      def record_request_queue_time(labels:, env:, request_received_time:)
         
     | 
| 
      
 37 
     | 
    
         
            +
                        request_queue_duration = QueueTimeDuration.new(env: env,
         
     | 
| 
      
 38 
     | 
    
         
            +
                          request_received_time: request_received_time)
         
     | 
| 
      
 39 
     | 
    
         
            +
                        return unless request_queue_duration.valid_header_present?
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                        queue_time_histogram.observe(labels, request_queue_duration.queue_time_seconds)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                      def register_metrics!
         
     | 
| 
      
 45 
     | 
    
         
            +
                        registry.histogram(REQUEST_QUEUE_TIME_HISTOGRAM_NAME,
         
     | 
| 
      
 46 
     | 
    
         
            +
                          "A histogram of request queue time", {}, queue_time_buckets)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                      def queue_time_histogram
         
     | 
| 
      
 50 
     | 
    
         
            +
                        registry.get(REQUEST_QUEUE_TIME_HISTOGRAM_NAME)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Promenade
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Client
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Rack
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class MiddlwareBase
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def initialize(app, registry:, label_builder:)
         
     | 
| 
      
 6 
     | 
    
         
            +
                      @app = app
         
     | 
| 
      
 7 
     | 
    
         
            +
                      @registry = registry
         
     | 
| 
      
 8 
     | 
    
         
            +
                      @label_builder = label_builder
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                      register_metrics!
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def call(env)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      trace(env) { app.call(env) }
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                      attr_reader :app, :label_builder, :registry
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      def trace(env)
         
     | 
| 
      
 22 
     | 
    
         
            +
                        raise NotImplementedError,
         
     | 
| 
      
 23 
     | 
    
         
            +
                          "Please define #{__method__} in #{self.class}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                      end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                      def labels(env, response)
         
     | 
| 
      
 27 
     | 
    
         
            +
                        label_builder.call(env).merge!(code: response.first.to_s)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                      def register_metrics!
         
     | 
| 
      
 31 
     | 
    
         
            +
                        # :noop:
         
     | 
| 
      
 32 
     | 
    
         
            +
                      end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Promenade
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Client
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Rack
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class QueueTimeDuration
         
     | 
| 
      
 5 
     | 
    
         
            +
                    REQUEST_START_HEADER = "HTTP_X_REQUEST_START".freeze
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    QUEUE_START_HEADER = "HTTP_X_QUEUE_START".freeze
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    HEADER_VALUE_MATCHER = /^(?:t=)(?<timestamp>\d{10}(?:\.\d+))$/.freeze
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    def initialize(env:, request_received_time:)
         
     | 
| 
      
 12 
     | 
    
         
            +
                      @env = env
         
     | 
| 
      
 13 
     | 
    
         
            +
                      @request_queued_time_ms = extract_request_queued_time_from_env(env)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @valid_header_present = @request_queued_time_ms.is_a?(Float)
         
     | 
| 
      
 15 
     | 
    
         
            +
                      @request_received_time_ms = request_received_time.utc.to_f
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                      freeze
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    def valid_header_present?
         
     | 
| 
      
 21 
     | 
    
         
            +
                      @valid_header_present
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    def queue_time_seconds
         
     | 
| 
      
 25 
     | 
    
         
            +
                      return unless valid_header_present?
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      queue_time.round(3)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    private
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                      attr_reader :env, :request_queued_time_ms, :request_received_time_ms
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                      def queue_time
         
     | 
| 
      
 35 
     | 
    
         
            +
                        request_received_time_ms - request_queued_time_ms
         
     | 
| 
      
 36 
     | 
    
         
            +
                      end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                      def extract_request_queued_time_from_env(env_hash)
         
     | 
| 
      
 39 
     | 
    
         
            +
                        header_value = env_hash[REQUEST_START_HEADER] || env_hash[QUEUE_START_HEADER]
         
     | 
| 
      
 40 
     | 
    
         
            +
                        return if header_value.nil?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                        header_time_match = header_value.to_s.match(HEADER_VALUE_MATCHER)
         
     | 
| 
      
 43 
     | 
    
         
            +
                        return unless header_time_match
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                        header_time_match[:timestamp].to_f
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,11 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Promenade
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Configuration
         
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor :rack_latency_buckets
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :queue_time_buckets, :rack_latency_buckets
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
                DEFAULT_RACK_LATENCY_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10].freeze
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
                DEFAULT_QUEUE_TIME_BUCKETS = [0.01, 0.5, 1.0, 10.0, 30.0].freeze
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
       7 
9 
     | 
    
         
             
                def initialize
         
     | 
| 
       8 
10 
     | 
    
         
             
                  @rack_latency_buckets = DEFAULT_RACK_LATENCY_BUCKETS
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @queue_time_buckets = DEFAULT_QUEUE_TIME_BUCKETS
         
     | 
| 
       9 
12 
     | 
    
         
             
                end
         
     | 
| 
       10 
13 
     | 
    
         
             
              end
         
     | 
| 
       11 
14 
     | 
    
         
             
            end
         
     | 
    
        data/lib/promenade/railtie.rb
    CHANGED
    
    | 
         @@ -1,13 +1,16 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "promenade/setup"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require "promenade/engine"
         
     | 
| 
       3 
     | 
    
         
            -
            require "promenade/client/rack/ 
     | 
| 
      
 3 
     | 
    
         
            +
            require "promenade/client/rack/http_request_duration_collector"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "promenade/client/rack/http_request_queue_time_collector"
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
6 
     | 
    
         
             
            module Promenade
         
     | 
| 
       6 
7 
     | 
    
         
             
              class Railtie < ::Rails::Railtie
         
     | 
| 
       7 
8 
     | 
    
         
             
                initializer "promenade.configure_rails_initialization" do
         
     | 
| 
       8 
9 
     | 
    
         
             
                  Promenade.setup
         
     | 
| 
       9 
10 
     | 
    
         
             
                  Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
         
     | 
| 
       10 
     | 
    
         
            -
                    Promenade::Client::Rack:: 
     | 
| 
      
 11 
     | 
    
         
            +
                    Promenade::Client::Rack::HTTPRequestDurationCollector
         
     | 
| 
      
 12 
     | 
    
         
            +
                  Rails.application.config.middleware.insert 0,
         
     | 
| 
      
 13 
     | 
    
         
            +
                    Promenade::Client::Rack::HTTPRequestQueueTimeCollector
         
     | 
| 
       11 
14 
     | 
    
         
             
                end
         
     | 
| 
       12 
15 
     | 
    
         
             
              end
         
     | 
| 
       13 
16 
     | 
    
         
             
            end
         
     | 
    
        data/lib/promenade/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: promenade
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.6.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Ed Robinson
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2022- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2022-08-23 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: actionpack
         
     | 
| 
         @@ -271,8 +271,11 @@ files: 
     | 
|
| 
       271 
271 
     | 
    
         
             
            - bin/setup
         
     | 
| 
       272 
272 
     | 
    
         
             
            - exe/promenade
         
     | 
| 
       273 
273 
     | 
    
         
             
            - lib/promenade.rb
         
     | 
| 
       274 
     | 
    
         
            -
            - lib/promenade/client/rack/collector.rb
         
     | 
| 
       275 
274 
     | 
    
         
             
            - lib/promenade/client/rack/exception_handler.rb
         
     | 
| 
      
 275 
     | 
    
         
            +
            - lib/promenade/client/rack/http_request_duration_collector.rb
         
     | 
| 
      
 276 
     | 
    
         
            +
            - lib/promenade/client/rack/http_request_queue_time_collector.rb
         
     | 
| 
      
 277 
     | 
    
         
            +
            - lib/promenade/client/rack/middleware_base.rb
         
     | 
| 
      
 278 
     | 
    
         
            +
            - lib/promenade/client/rack/queue_time_duration.rb
         
     | 
| 
       276 
279 
     | 
    
         
             
            - lib/promenade/client/rack/request_labeler.rb
         
     | 
| 
       277 
280 
     | 
    
         
             
            - lib/promenade/client/rack/singleton_caller.rb
         
     | 
| 
       278 
281 
     | 
    
         
             
            - lib/promenade/configuration.rb
         
     | 
| 
         @@ -312,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       312 
315 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       313 
316 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       314 
317 
     | 
    
         
             
            requirements: []
         
     | 
| 
       315 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
      
 318 
     | 
    
         
            +
            rubygems_version: 3.3.18
         
     | 
| 
       316 
319 
     | 
    
         
             
            signing_key:
         
     | 
| 
       317 
320 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       318 
321 
     | 
    
         
             
            summary: Promenade makes it simple to instrument Ruby apps for prometheus scraping
         
     | 
| 
         @@ -1,113 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require "prometheus/client"
         
     | 
| 
       2 
     | 
    
         
            -
            require_relative "request_labeler"
         
     | 
| 
       3 
     | 
    
         
            -
            require_relative "exception_handler"
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
            module Promenade
         
     | 
| 
       6 
     | 
    
         
            -
              module Client
         
     | 
| 
       7 
     | 
    
         
            -
                module Rack
         
     | 
| 
       8 
     | 
    
         
            -
                  # Original code taken from Prometheus Client MMap
         
     | 
| 
       9 
     | 
    
         
            -
                  # https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb
         
     | 
| 
       10 
     | 
    
         
            -
                  #
         
     | 
| 
       11 
     | 
    
         
            -
                  # Collector is a Rack middleware that provides a sample implementation of
         
     | 
| 
       12 
     | 
    
         
            -
                  # a HTTP tracer. The default label builder can be modified to export a
         
     | 
| 
       13 
     | 
    
         
            -
                  # different set of labels per recorded metric.
         
     | 
| 
       14 
     | 
    
         
            -
                  class Collector
         
     | 
| 
       15 
     | 
    
         
            -
                    REQUEST_METHOD = "REQUEST_METHOD".freeze
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                    HTTP_HOST = "HTTP_HOST".freeze
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                    PATH_INFO = "PATH_INFO".freeze
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                    HISTOGRAM_NAME = :http_req_duration_seconds
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
                    REQUESTS_COUNTER_NAME = :http_requests_total
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                    EXCEPTIONS_COUNTER_NAME = :http_exceptions_total
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                    private_constant *%i(
         
     | 
| 
       28 
     | 
    
         
            -
                      REQUEST_METHOD
         
     | 
| 
       29 
     | 
    
         
            -
                      HTTP_HOST
         
     | 
| 
       30 
     | 
    
         
            -
                      PATH_INFO
         
     | 
| 
       31 
     | 
    
         
            -
                      HISTOGRAM_NAME
         
     | 
| 
       32 
     | 
    
         
            -
                      REQUESTS_COUNTER_NAME
         
     | 
| 
       33 
     | 
    
         
            -
                      EXCEPTIONS_COUNTER_NAME
         
     | 
| 
       34 
     | 
    
         
            -
                    )
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                    def initialize(app,
         
     | 
| 
       37 
     | 
    
         
            -
                                   registry: ::Prometheus::Client.registry,
         
     | 
| 
       38 
     | 
    
         
            -
                                   label_builder: RequestLabeler,
         
     | 
| 
       39 
     | 
    
         
            -
                                   exception_handler: nil)
         
     | 
| 
       40 
     | 
    
         
            -
                      @app = app
         
     | 
| 
       41 
     | 
    
         
            -
                      @registry = registry
         
     | 
| 
       42 
     | 
    
         
            -
                      @label_builder = label_builder
         
     | 
| 
       43 
     | 
    
         
            -
                      @latency_buckets = Promenade.configuration.rack_latency_buckets
         
     | 
| 
       44 
     | 
    
         
            -
                      @exception_handler = exception_handler || default_exception_handler
         
     | 
| 
       45 
     | 
    
         
            -
                      register_metrics!
         
     | 
| 
       46 
     | 
    
         
            -
                    end
         
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
                    def call(env)
         
     | 
| 
       49 
     | 
    
         
            -
                      trace(env) { app.call(env) }
         
     | 
| 
       50 
     | 
    
         
            -
                    end
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                    private
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
                      attr_reader :app,
         
     | 
| 
       55 
     | 
    
         
            -
                        :registry,
         
     | 
| 
       56 
     | 
    
         
            -
                        :label_builder,
         
     | 
| 
       57 
     | 
    
         
            -
                        :latency_buckets,
         
     | 
| 
       58 
     | 
    
         
            -
                        :exception_handler
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                      def trace(env)
         
     | 
| 
       61 
     | 
    
         
            -
                        start = current_time
         
     | 
| 
       62 
     | 
    
         
            -
                        begin
         
     | 
| 
       63 
     | 
    
         
            -
                          response = yield
         
     | 
| 
       64 
     | 
    
         
            -
                          record(labels(env, response), duration_since(start))
         
     | 
| 
       65 
     | 
    
         
            -
                          response
         
     | 
| 
       66 
     | 
    
         
            -
                        rescue StandardError => e
         
     | 
| 
       67 
     | 
    
         
            -
                          exception_handler.call(e, env, duration_since(start))
         
     | 
| 
       68 
     | 
    
         
            -
                        end
         
     | 
| 
       69 
     | 
    
         
            -
                      end
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                      def labels(env, response)
         
     | 
| 
       72 
     | 
    
         
            -
                        label_builder.call(env).merge!(code: response.first.to_s)
         
     | 
| 
       73 
     | 
    
         
            -
                      end
         
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
                      def record(labels, duration)
         
     | 
| 
       76 
     | 
    
         
            -
                        requests_counter.increment(labels)
         
     | 
| 
       77 
     | 
    
         
            -
                        durations_histogram.observe(labels, duration)
         
     | 
| 
       78 
     | 
    
         
            -
                      end
         
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
                      def duration_since(start_time)
         
     | 
| 
       81 
     | 
    
         
            -
                        current_time - start_time
         
     | 
| 
       82 
     | 
    
         
            -
                      end
         
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
     | 
    
         
            -
                      def current_time
         
     | 
| 
       85 
     | 
    
         
            -
                        Process.clock_gettime(Process::CLOCK_MONOTONIC)
         
     | 
| 
       86 
     | 
    
         
            -
                      end
         
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
                      def durations_histogram
         
     | 
| 
       89 
     | 
    
         
            -
                        registry.get(HISTOGRAM_NAME)
         
     | 
| 
       90 
     | 
    
         
            -
                      end
         
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
                      def requests_counter
         
     | 
| 
       93 
     | 
    
         
            -
                        registry.get(REQUESTS_COUNTER_NAME)
         
     | 
| 
       94 
     | 
    
         
            -
                      end
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
                      def register_metrics!
         
     | 
| 
       97 
     | 
    
         
            -
                        registry.counter(REQUESTS_COUNTER_NAME, "A counter of the total number of HTTP requests made.")
         
     | 
| 
       98 
     | 
    
         
            -
                        registry.histogram(HISTOGRAM_NAME, "A histogram of the response latency.", {}, latency_buckets)
         
     | 
| 
       99 
     | 
    
         
            -
                        registry.counter(EXCEPTIONS_COUNTER_NAME, "A counter of the total number of exceptions raised.")
         
     | 
| 
       100 
     | 
    
         
            -
                      end
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                      def default_exception_handler
         
     | 
| 
       103 
     | 
    
         
            -
                        ExceptionHandler.initialize_singleton(
         
     | 
| 
       104 
     | 
    
         
            -
                          histogram_name: HISTOGRAM_NAME,
         
     | 
| 
       105 
     | 
    
         
            -
                          requests_counter_name: REQUESTS_COUNTER_NAME,
         
     | 
| 
       106 
     | 
    
         
            -
                          exceptions_counter_name: EXCEPTIONS_COUNTER_NAME,
         
     | 
| 
       107 
     | 
    
         
            -
                          registry: registry,
         
     | 
| 
       108 
     | 
    
         
            -
                        )
         
     | 
| 
       109 
     | 
    
         
            -
                      end
         
     | 
| 
       110 
     | 
    
         
            -
                  end
         
     | 
| 
       111 
     | 
    
         
            -
                end
         
     | 
| 
       112 
     | 
    
         
            -
              end
         
     | 
| 
       113 
     | 
    
         
            -
            end
         
     |