puma-plugin-telemetry 1.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.
@@ -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: []