bc-prometheus-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +30 -0
  3. data/CODE_OF_CONDUCT.md +49 -0
  4. data/README.md +67 -0
  5. data/bc-prometheus-ruby.gemspec +46 -0
  6. data/lib/bigcommerce/prometheus.rb +62 -0
  7. data/lib/bigcommerce/prometheus/client.rb +80 -0
  8. data/lib/bigcommerce/prometheus/collectors/resque.rb +85 -0
  9. data/lib/bigcommerce/prometheus/configuration.rb +127 -0
  10. data/lib/bigcommerce/prometheus/instrumentors/hutch.rb +72 -0
  11. data/lib/bigcommerce/prometheus/instrumentors/resque.rb +72 -0
  12. data/lib/bigcommerce/prometheus/instrumentors/web.rb +84 -0
  13. data/lib/bigcommerce/prometheus/integrations/puma.rb +55 -0
  14. data/lib/bigcommerce/prometheus/integrations/railtie.rb +31 -0
  15. data/lib/bigcommerce/prometheus/integrations/resque.rb +41 -0
  16. data/lib/bigcommerce/prometheus/loggable.rb +32 -0
  17. data/lib/bigcommerce/prometheus/server.rb +117 -0
  18. data/lib/bigcommerce/prometheus/servers/thin/controllers/base_controller.rb +63 -0
  19. data/lib/bigcommerce/prometheus/servers/thin/controllers/error_controller.rb +36 -0
  20. data/lib/bigcommerce/prometheus/servers/thin/controllers/metrics_controller.rb +87 -0
  21. data/lib/bigcommerce/prometheus/servers/thin/controllers/not_found_controller.rb +36 -0
  22. data/lib/bigcommerce/prometheus/servers/thin/controllers/send_metrics_controller.rb +92 -0
  23. data/lib/bigcommerce/prometheus/servers/thin/rack_app.rb +88 -0
  24. data/lib/bigcommerce/prometheus/servers/thin/server.rb +48 -0
  25. data/lib/bigcommerce/prometheus/servers/thin/server_metrics.rb +98 -0
  26. data/lib/bigcommerce/prometheus/type_collectors/resque.rb +82 -0
  27. data/lib/bigcommerce/prometheus/version.rb +22 -0
  28. metadata +209 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Servers
21
+ module Thin
22
+ module Controllers
23
+ ##
24
+ # Base thin controller for prometheus metrics
25
+ #
26
+ class BaseController
27
+ ##
28
+ # @param [Rack::Request] request
29
+ # @param [Rack::Response] response
30
+ # @param [Bigcommerce::Prometheus::Servers::Thin::ServerMetrics]
31
+ # @param [PrometheusExporter::Server::Collector] collector
32
+ # @param [Logger] logger
33
+ #
34
+ def initialize(request:, response:, server_metrics:, collector:, logger:)
35
+ @request = request
36
+ @response = response
37
+ @collector = collector
38
+ @server_metrics = server_metrics
39
+ @logger = logger
40
+ end
41
+
42
+ def handle
43
+ call
44
+ @response.finish
45
+ end
46
+
47
+ ##
48
+ # @param [String] key
49
+ # @param [String] value
50
+ #
51
+ def set_header(key, value)
52
+ if @response.respond_to?(:add_header) # rack 2.0+
53
+ @response.add_header(key.to_s, value.to_s)
54
+ else
55
+ @response[key.to_s] = value.to_s
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Servers
21
+ module Thin
22
+ module Controllers
23
+ ##
24
+ # Handle 500s
25
+ #
26
+ class ErrorController < BaseController
27
+ def call
28
+ @response.body << ''
29
+ @response.status = 500
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ require 'timeout'
19
+ require 'zlib'
20
+ require 'stringio'
21
+
22
+ module Bigcommerce
23
+ module Prometheus
24
+ module Servers
25
+ module Thin
26
+ module Controllers
27
+ ##
28
+ # GET /metrics
29
+ #
30
+ class MetricsController < BaseController
31
+ ##
32
+ # Handle outputting of metrics
33
+ #
34
+ def call
35
+ collected_metrics = metrics
36
+ if @request.accept_encoding.to_s.include?('gzip')
37
+ write_gzip(metrics)
38
+ else
39
+ @response.write(collected_metrics)
40
+ end
41
+ @response
42
+ end
43
+
44
+ private
45
+
46
+ ##
47
+ # Output via gzip
48
+ #
49
+ def write_gzip(metrics)
50
+ sio = ::StringIO.new
51
+ begin
52
+ writer = ::Zlib::GzipWriter.new(sio)
53
+ writer.write(metrics)
54
+ ensure
55
+ writer.close
56
+ end
57
+ @response.body << sio.string
58
+ set_header('Content-Encoding', 'gzip')
59
+ end
60
+
61
+ ##
62
+ # Gather all metrics
63
+ #
64
+ def metrics
65
+ metric_text = ''
66
+ working = true
67
+
68
+ begin
69
+ ::Timeout.timeout(@timeout) do
70
+ metric_text = @collector.prometheus_metrics_text
71
+ end
72
+ rescue ::Timeout::Error
73
+ working = false
74
+ @logger.error 'Generating Prometheus metrics text timed out'
75
+ end
76
+
77
+ output = []
78
+ output << @server_metrics.to_prometheus_text(working: working)
79
+ output << metric_text
80
+ output.join("\n")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Servers
21
+ module Thin
22
+ module Controllers
23
+ ##
24
+ # Handle invalid requests to server
25
+ #
26
+ class NotFoundController < BaseController
27
+ def call
28
+ @response.status = 404
29
+ @response.body << 'Not Found! The Prometheus Ruby Exporter only listens on /metrics and /send-metrics'
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Servers
21
+ module Thin
22
+ module Controllers
23
+ ##
24
+ # POST /send-metrics
25
+ #
26
+ class SendMetricsController < BaseController
27
+ class BadMetricsError < StandardError; end
28
+ class InvalidRequestError < StandardError; end
29
+
30
+ ##
31
+ # Handle incoming metrics
32
+ #
33
+ def call
34
+ raise InvalidRequestError unless @request.post?
35
+
36
+ @server_metrics.add_session
37
+ process_metrics
38
+ succeed!
39
+ rescue InvalidRequestError => _e
40
+ fail!('Invalid request type. Only POST is supported.')
41
+ rescue BadMetricsError => e
42
+ fail!(e.message)
43
+ end
44
+
45
+ private
46
+
47
+ ##
48
+ # Succeed the request
49
+ #
50
+ def succeed!
51
+ @response['Content-Type'] = 'text/plain'
52
+ @response.write('OK')
53
+ @response.status = 200
54
+ @response
55
+ end
56
+
57
+ ##
58
+ # Fail the request
59
+ #
60
+ # @param [String]
61
+ #
62
+ def fail!(message)
63
+ @response['Content-Type'] = 'application/json'
64
+ @response.write([message].to_json)
65
+ @response.status = 500
66
+ @response
67
+ end
68
+
69
+ ##
70
+ # Process the metrics
71
+ #
72
+ def process_metrics
73
+ @server_metrics.add_metric
74
+ @collector.process(body)
75
+ rescue StandardError => e
76
+ @logger.error "[bigcommerce-prometheus] Error collecting metrics: #{e.inspect} - #{e.backtrace[0..4].join("\n")}"
77
+ @server_metrics.add_bad_metric
78
+ raise BadMetricsError, e.message
79
+ end
80
+
81
+ ##
82
+ # @return [String]
83
+ #
84
+ def body
85
+ @request.body.read
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Servers
21
+ module Thin
22
+ ##
23
+ # Handles metrics requests as a Rack App on the Thin server
24
+ #
25
+ class RackApp
26
+ ##
27
+ #
28
+ def initialize(collector: nil, timeout: nil, logger: nil)
29
+ @timeout = timeout || ::Bigcommerce::Prometheus.server_timeout
30
+ @collector = collector || ::PrometheusExporter::Server::Collector.new
31
+ @logger = logger || ::Bigcommerce::Prometheus.logger
32
+ @server_metrics = ::Bigcommerce::Prometheus::Servers::Thin::ServerMetrics.new(logger: @logger)
33
+ end
34
+
35
+ def call(env)
36
+ request = ::Rack::Request.new(env)
37
+ response = ::Rack::Response.new
38
+ controller = route(request)
39
+ handle(controller: controller, request: request, response: response)
40
+ rescue StandardError => e
41
+ @logger.error "Error: #{e.message}"
42
+ handle(controller: ::Bigcommerce::Prometheus::Servers::Thin::Controllers::ErrorController, request: request, response: response)
43
+ end
44
+
45
+ ##
46
+ # Add a type collector to this server
47
+ #
48
+ # @param [PrometheusExporter::Server::TypeCollector] collector
49
+ #
50
+ def add_type_collector(collector)
51
+ @collector.register_collector(collector)
52
+ end
53
+
54
+ private
55
+
56
+ ##
57
+ # Determine the controller route
58
+ #
59
+ # @param [Rack::Request] request
60
+ #
61
+ def route(request)
62
+ if request.fullpath == '/metrics' && request.request_method.to_s.downcase == 'get'
63
+ Bigcommerce::Prometheus::Servers::Thin::Controllers::MetricsController
64
+ elsif request.fullpath == '/send-metrics' && request.request_method.to_s.downcase == 'post'
65
+ Bigcommerce::Prometheus::Servers::Thin::Controllers::SendMetricsController
66
+ else
67
+ Bigcommerce::Prometheus::Servers::Thin::Controllers::NotFoundController
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Handle a controller request
73
+ #
74
+ def handle(controller:, request:, response:)
75
+ con = controller.new(
76
+ request: request,
77
+ response: response,
78
+ server_metrics: @server_metrics,
79
+ collector: @collector,
80
+ logger: @logger
81
+ )
82
+ con.handle
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Servers
21
+ module Thin
22
+ ##
23
+ # Thin adapter for server
24
+ #
25
+ class Server < ::Thin::Server
26
+ def initialize(port:, host: nil, timeout: nil, logger: nil)
27
+ @port = port || ::PrometheusExporter::DEFAULT_PORT
28
+ @host = host || '0.0.0.0'
29
+ @timeout = timeout || ::PrometheusExporter::DEFAULT_TIMEOUT
30
+ @logger = logger || ::Bigcommerce::Prometheus.logger
31
+ @rack_app = ::Bigcommerce::Prometheus::Servers::Thin::RackApp.new(timeout: timeout, logger: logger)
32
+ super(@host, @port, @rack_app)
33
+ ::Thin::Logging.logger = @logger
34
+ end
35
+
36
+ ##
37
+ # Add a type collector to this server
38
+ #
39
+ # @param [PrometheusExporter::Server::TypeCollector] collector
40
+ #
41
+ def add_type_collector(collector)
42
+ @rack_app.add_type_collector(collector)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end