app_profiler 0.2.8 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab22ed223e6cc5f20f3a2541a1ecc17a266591510c199d09ea7b05a91d5b2441
4
- data.tar.gz: 73e6a570e6a898170f8caa47c1f389a8d13063e39f46bfdcbef7ecac3cdec677
3
+ metadata.gz: e78035c478029d4ad4de636efab44240957c2bd05ea761dc9fe28b753b4fa1d3
4
+ data.tar.gz: c4d6659a31eaa7440c6c8a2550c9cf41fbadb567426cae7c6bd0cd4442e8039a
5
5
  SHA512:
6
- metadata.gz: 1c269a8514ab205bc7081f23e0b9cd914d7b1dbe252f21963e2ddc2cf7ddcf4c717e759eda03179136578b3c3a840f9253e22b21bc5415699ab2e30e32cc5d26
7
- data.tar.gz: e17b9a934254e1871f34b81c9c66c589aa218d24fc5b2cfa6b0fbc34945313b8e1a0b9866f431b27a6d484694b8a6222cd4739602233020541cc3288c9ee63e9
6
+ metadata.gz: 45610912af0b6952b2c104d27bfc3bf2b37b92f91d2c873bdf9e2bfc43498529decf9568af63b9ee47d84794dd921dbf6312de11aac272be296f99416d5f2b31
7
+ data.tar.gz: 29ee1a661cc8c81274fbdb7f6123ce90d7dbeaa351ca3b16a8a6bfe933ec3f68385bfe0f2d0afb2ae6c49ba59fa318d733bd23d6cf7724a3d03f1246e36b65ca
@@ -8,7 +8,7 @@ module AppProfiler
8
8
  private_constant :INTERNAL_METADATA_KEYS
9
9
  class UnsafeFilename < StandardError; end
10
10
 
11
- attr_reader :id, :context
11
+ attr_reader :context
12
12
 
13
13
  delegate :[], to: :@data
14
14
 
@@ -34,9 +34,17 @@ module AppProfiler
34
34
  # `data` is assumed to be a Hash for Stackprof,
35
35
  # a vernier "result" object for vernier
36
36
  def initialize(data, id: nil, context: nil)
37
- @id = id.presence || ProfileId.current
37
+ ProfileId.current = id if id.present?
38
+
38
39
  @context = context
39
40
  @data = data
41
+
42
+ metadata[PROFILE_BACKEND_METADATA_KEY] = self.class.backend_name
43
+ metadata[PROFILE_ID_METADATA_KEY] = ProfileId.current
44
+ end
45
+
46
+ def id
47
+ metadata[PROFILE_ID_METADATA_KEY]
40
48
  end
41
49
 
42
50
  def upload
@@ -7,6 +7,11 @@ require "app_profiler/middleware/view_action"
7
7
 
8
8
  module AppProfiler
9
9
  class Middleware
10
+ OTEL_PROFILE_ID = "profile.id"
11
+ OTEL_PROFILE_BACKEND = "profile.profiler"
12
+ OTEL_PROFILE_MODE = "profile.mode"
13
+ OTEL_PROFILE_CONTEXT = "profile.context"
14
+
10
15
  class_attribute :action, default: UploadAction
11
16
  class_attribute :disabled, default: false
12
17
 
@@ -29,8 +34,11 @@ module AppProfiler
29
34
  return yield unless app_profiler_params
30
35
 
31
36
  params_hash = app_profiler_params.to_h
37
+
32
38
  return yield unless before_profile(env, params_hash)
33
39
 
40
+ add_otel_instrumentation(env, params_hash) if AppProfiler.otel_instrumentation_enabled
41
+
34
42
  profile = AppProfiler.run(**params_hash) do
35
43
  response = yield
36
44
  end
@@ -47,6 +55,27 @@ module AppProfiler
47
55
  response
48
56
  end
49
57
 
58
+ def add_otel_instrumentation(env, params)
59
+ rack_span = OpenTelemetry::Instrumentation::Rack.current_span
60
+ return unless rack_span.recording?
61
+
62
+ metadata = params[:metadata]
63
+ profile_id = if metadata[:id].present?
64
+ metadata[:id]
65
+ else
66
+ AppProfiler::ProfileId.current
67
+ end
68
+
69
+ attributes = {
70
+ OTEL_PROFILE_ID => profile_id,
71
+ OTEL_PROFILE_BACKEND => params[:backend].to_s,
72
+ OTEL_PROFILE_MODE => params[:mode].to_s,
73
+ OTEL_PROFILE_CONTEXT => AppProfiler.context,
74
+ }
75
+
76
+ rack_span.add_attributes(attributes)
77
+ end
78
+
50
79
  def profile_params(params)
51
80
  return params if params.valid?
52
81
  return unless AppProfiler.profile_sampler_enabled
@@ -1,19 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/current_attributes"
4
3
  require "securerandom"
5
4
 
6
5
  module AppProfiler
7
6
  class ProfileId
8
- class Current < ActiveSupport::CurrentAttributes
9
- attribute :id
7
+ class Current
8
+ PROFILE_ID_KEY = :__app_profiler_profile_id__
9
+ class << self
10
+ # This is a thread local variable which gets reset by the middleware at the end of the request.
11
+ # Need to be mindful of the middleware order. If we try to access ProfileId after the middleware has finished,
12
+ # lets say in a middleware which runs before Profiling middleware, it will return a different value,
13
+ # as the middleware has already reset.
14
+
15
+ def id
16
+ Thread.current[PROFILE_ID_KEY] ||= SecureRandom.hex
17
+ end
18
+
19
+ def id=(id)
20
+ Thread.current[PROFILE_ID_KEY] = id
21
+ end
22
+
23
+ def reset
24
+ Thread.current[PROFILE_ID_KEY] = nil
25
+ end
26
+ end
10
27
  end
11
28
 
12
29
  class << self
13
30
  def current
14
- Current.id ||= SecureRandom.hex
15
31
  Current.id
16
32
  end
33
+
34
+ def current=(id)
35
+ Current.id = id
36
+ end
17
37
  end
18
38
  end
19
39
  end
@@ -51,6 +51,8 @@ module AppProfiler
51
51
  AppProfiler.profile_sampler_enabled = app.config.app_profiler.profile_sampler_enabled || false
52
52
  AppProfiler.profile_sampler_config = app.config.app_profiler.profile_sampler_config ||
53
53
  AppProfiler::Sampler::Config.new
54
+
55
+ AppProfiler.otel_instrumentation_enabled = app.config.app_profiler.otel_instrumentation_enabled || false
54
56
  end
55
57
 
56
58
  initializer "app_profiler.add_middleware" do |app|
@@ -33,7 +33,7 @@ module AppProfiler
33
33
 
34
34
  return false if backend != AppProfiler::Backend::StackprofBackend.name && !AppProfiler.vernier_supported?
35
35
 
36
- if AppProfiler.vernier_supported? && backend == AppProfiler::Backend::VernierBackend.name &&
36
+ if AppProfiler.vernier_supported? && backend == AppProfiler::VernierProfile::BACKEND_NAME &&
37
37
  !AppProfiler::Backend::VernierBackend::AVAILABLE_MODES.include?(mode.to_sym)
38
38
  AppProfiler.logger.info("[AppProfiler] unsupported profiling mode=#{mode} for backend #{backend}")
39
39
  return false
@@ -4,6 +4,17 @@ module AppProfiler
4
4
  class StackprofProfile < BaseProfile
5
5
  FILE_EXTENSION = ".stackprof.json"
6
6
 
7
+ class << self
8
+ def backend_name
9
+ Backend::StackprofBackend.name.to_s
10
+ end
11
+ end
12
+
13
+ def initialize(data, id: nil, context: nil)
14
+ data[:metadata] ||= {}
15
+ super(data, id: id, context: context)
16
+ end
17
+
7
18
  def mode
8
19
  @data[:mode]
9
20
  end
@@ -3,6 +3,19 @@
3
3
  module AppProfiler
4
4
  class VernierProfile < BaseProfile
5
5
  FILE_EXTENSION = ".vernier.json"
6
+ BACKEND_NAME = :vernier
7
+
8
+ class << self
9
+ def backend_name
10
+ # cannot reference Backend::VernierBackend because of different ruby versions we have to support
11
+ BACKEND_NAME.to_s
12
+ end
13
+ end
14
+
15
+ def initialize(data, id: nil, context: nil)
16
+ data[:meta] ||= {}
17
+ super(data, id: id, context: context)
18
+ end
6
19
 
7
20
  def mode
8
21
  @data[:meta][:mode]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppProfiler
4
- VERSION = "0.2.8"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/app_profiler.rb CHANGED
@@ -7,6 +7,9 @@ require "logger"
7
7
  require "app_profiler/version"
8
8
 
9
9
  module AppProfiler
10
+ PROFILE_ID_METADATA_KEY = :profile_id
11
+ PROFILE_BACKEND_METADATA_KEY = :profiler
12
+
10
13
  class ConfigurationError < StandardError
11
14
  end
12
15
 
@@ -73,6 +76,8 @@ module AppProfiler
73
76
  mattr_reader :profile_file_name
74
77
 
75
78
  class << self
79
+ attr_reader :otel_instrumentation_enabled
80
+
76
81
  def deprecator # :nodoc:
77
82
  @deprecator ||= ActiveSupport::Deprecation.new("in future releases", "app_profiler")
78
83
  end
@@ -90,6 +95,7 @@ module AppProfiler
90
95
  yield
91
96
  ensure
92
97
  self.backend = original_backend if backend
98
+ ProfileId::Current.reset
93
99
  end
94
100
 
95
101
  def start(*args, backend: nil, **kwargs)
@@ -158,9 +164,18 @@ module AppProfiler
158
164
  false
159
165
  end
160
166
 
167
+ def otel_instrumentation_enabled=(value)
168
+ if value
169
+ gem("opentelemetry-instrumentation-rack")
170
+ require("opentelemetry/instrumentation/rack")
171
+ end
172
+
173
+ @otel_instrumentation_enabled = value
174
+ end
175
+
161
176
  def backend_for(backend_name)
162
177
  if vernier_supported? &&
163
- backend_name&.to_sym == AppProfiler::Backend::VernierBackend.name
178
+ backend_name&.to_sym == AppProfiler::VernierProfile::BACKEND_NAME
164
179
  AppProfiler::Backend::VernierBackend
165
180
  elsif backend_name&.to_sym == AppProfiler::Backend::StackprofBackend.name
166
181
  AppProfiler::Backend::StackprofBackend
@@ -174,7 +189,7 @@ module AppProfiler
174
189
  end
175
190
 
176
191
  def vernier_supported?
177
- RUBY_VERSION >= "3.2.1" && defined?(AppProfiler::Backend::VernierBackend.name)
192
+ RUBY_VERSION >= "3.2.1" && defined?(AppProfiler::VernierProfile::BACKEND_NAME)
178
193
  end
179
194
 
180
195
  def profile_header=(profile_header)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: app_profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gannon McGibbon
@@ -12,7 +12,7 @@ authors:
12
12
  - Scott Francis
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2025-02-04 00:00:00.000000000 Z
15
+ date: 2025-02-24 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activesupport
@@ -112,6 +112,20 @@ dependencies:
112
112
  - - ">="
113
113
  - !ruby/object:Gem::Version
114
114
  version: '0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: opentelemetry-instrumentation-rack
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
115
129
  - !ruby/object:Gem::Dependency
116
130
  name: rake
117
131
  requirement: !ruby/object:Gem::Requirement