puma-plugin-telemetry 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma'
4
+ require 'puma/plugin'
5
+
6
+ require 'puma/plugin/telemetry/version'
7
+ require 'puma/plugin/telemetry/data'
8
+ require 'puma/plugin/telemetry/targets/datadog_statsd_target'
9
+ require 'puma/plugin/telemetry/targets/io_target'
10
+ require 'puma/plugin/telemetry/config'
11
+
12
+ module Puma
13
+ class Plugin
14
+ # Telemetry plugin for puma, supporting:
15
+ #
16
+ # - multiple targets, decide where to push puma telemetry information, i.e. datadog, cloudwatch, logs
17
+ # - filtering, select which metrics are interesting for you, extend when necessery
18
+ #
19
+ module Telemetry
20
+ class Error < StandardError; end
21
+
22
+ class << self
23
+ attr_writer :config
24
+
25
+ def config
26
+ @config ||= Config.new
27
+ end
28
+
29
+ def configure
30
+ yield(config)
31
+ end
32
+
33
+ def build(launcher = nil)
34
+ socket_telemetry(puma_telemetry, launcher)
35
+ end
36
+
37
+ private
38
+
39
+ def puma_telemetry
40
+ stats = ::Puma.stats_hash
41
+ data_class = if stats.key?(:workers)
42
+ ClusteredData
43
+ else
44
+ WorkerData
45
+ end
46
+ data_class
47
+ .new(stats)
48
+ .metrics(config.puma_telemetry)
49
+ end
50
+
51
+ def socket_telemetry(telemetry, launcher)
52
+ return telemetry if launcher.nil?
53
+ return telemetry unless config.socket_telemetry?
54
+
55
+ telemetry.merge! SocketData.new(launcher.binder.ios, config.socket_parser)
56
+ .metrics
57
+
58
+ telemetry
59
+ end
60
+ end
61
+
62
+ # Contents of actual Puma Plugin
63
+ #
64
+ module PluginInstanceMethods
65
+ def start(launcher)
66
+ unless Puma::Plugin::Telemetry.config.enabled?
67
+ launcher.events.log 'plugin=telemetry msg="disabled, exiting..."'
68
+ return
69
+ end
70
+
71
+ @launcher = launcher
72
+ @launcher.events.log 'plugin=telemetry msg="enabled, setting up runner..."'
73
+
74
+ in_background do
75
+ sleep Puma::Plugin::Telemetry.config.initial_delay
76
+ run!
77
+ end
78
+ end
79
+
80
+ def run!
81
+ loop do
82
+ @launcher.events.debug 'plugin=telemetry msg="publish"'
83
+
84
+ call(Puma::Plugin::Telemetry.build(@launcher))
85
+ rescue Errno::EPIPE
86
+ # Occurs when trying to output to STDOUT while puma is shutting down
87
+ rescue StandardError => e
88
+ @launcher.events.error "plugin=telemetry err=#{e.class} msg=#{e.message.inspect}"
89
+ ensure
90
+ sleep Puma::Plugin::Telemetry.config.frequency
91
+ end
92
+ end
93
+
94
+ def call(telemetry)
95
+ Puma::Plugin::Telemetry.config.targets.each do |target|
96
+ target.call(telemetry)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ Puma::Plugin.create do
105
+ include Puma::Plugin::Telemetry::PluginInstanceMethods
106
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Measures the queue time (= time between receiving the request in downstream
4
+ # load balancer and starting request in ruby process)
5
+ class RequestQueueTimeMiddleware
6
+ ENV_KEY = 'rack.request_queue_time'
7
+
8
+ def initialize(app, statsd:, process: Process)
9
+ @app = app
10
+ @statsd = statsd
11
+ @process = process
12
+ end
13
+
14
+ def call(env)
15
+ queue_time = measure_queue_time(env)
16
+
17
+ report_queue_time(queue_time)
18
+
19
+ env[ENV_KEY] = queue_time
20
+
21
+ @app.call(env)
22
+ end
23
+
24
+ private
25
+
26
+ def measure_queue_time(env)
27
+ start_time = queue_start(env)
28
+
29
+ return unless start_time
30
+
31
+ queue_time = request_start.to_f - start_time.to_f
32
+
33
+ queue_time unless queue_time.negative?
34
+ end
35
+
36
+ # Get the content of the x-amzn-trace-id header, the epoch time in seconds.
37
+ # see also: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html
38
+ def queue_start(env)
39
+ value = env['HTTP_X_AMZN_TRACE_ID']
40
+ value&.split('Root=')&.last&.split('-')&.fetch(1)&.to_i(16)
41
+ end
42
+
43
+ def request_start
44
+ @process.clock_gettime(Process::CLOCK_REALTIME)
45
+ end
46
+
47
+ def report_queue_time(queue_time)
48
+ return if queue_time.nil?
49
+
50
+ @statsd.timing('queue.time', queue_time)
51
+
52
+ return unless defined?(Datadog) && Datadog.respond_to?(:tracer)
53
+
54
+ span = Datadog.tracer.active_root_span
55
+ span&.set_tag('request.queue_time', queue_time)
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/puma/plugin/telemetry/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'puma-plugin-telemetry'
7
+ spec.version = Puma::Plugin::Telemetry::VERSION
8
+ spec.authors = ['Leszek Zalewski']
9
+ spec.email = ['tnt@babbel.com']
10
+
11
+ spec.license = 'MIT'
12
+
13
+ spec.summary = 'Puma plugin, adding ability to publish various metrics to your prefered targets.'
14
+ spec.homepage = 'https://github.com/babbel/puma-plugin-telemetry'
15
+
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = spec.homepage
20
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
21
+ spec.metadata['github_repo'] = 'ssh://github.com/babbel/puma-plugin-telemetry'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_dependency 'puma', '>= 5.0'
34
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puma-plugin-telemetry
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leszek Zalewski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: puma
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ description:
28
+ email:
29
+ - tnt@babbel.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/CODEOWNERS"
35
+ - ".github/workflows/build.yml"
36
+ - ".gitignore"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - ".tool-versions"
40
+ - CHANGELOG.md
41
+ - CODE_OF_CONDUCT.md
42
+ - Gemfile
43
+ - Gemfile.lock
44
+ - LICENSE.txt
45
+ - README.md
46
+ - Rakefile
47
+ - bin/console
48
+ - bin/setup
49
+ - docs/example-datadog_backlog_size.png
50
+ - docs/example-datadog_queue_time.png
51
+ - docs/examples.md
52
+ - lib/puma/plugin/telemetry.rb
53
+ - lib/puma/plugin/telemetry/config.rb
54
+ - lib/puma/plugin/telemetry/data.rb
55
+ - lib/puma/plugin/telemetry/targets/datadog_statsd_target.rb
56
+ - lib/puma/plugin/telemetry/targets/io_target.rb
57
+ - lib/puma/plugin/telemetry/version.rb
58
+ - lib/rack/request_queue_time_middleware.rb
59
+ - puma-plugin-telemetry.gemspec
60
+ homepage: https://github.com/babbel/puma-plugin-telemetry
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ homepage_uri: https://github.com/babbel/puma-plugin-telemetry
65
+ source_code_uri: https://github.com/babbel/puma-plugin-telemetry
66
+ changelog_uri: https://github.com/babbel/puma-plugin-telemetry/blob/main/CHANGELOG.md
67
+ github_repo: ssh://github.com/babbel/puma-plugin-telemetry
68
+ rubygems_mfa_required: 'true'
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.6.0
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.2.27
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Puma plugin, adding ability to publish various metrics to your prefered targets.
88
+ test_files: []