sqreen 1.20.4 → 1.21.0.beta1

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -25
  3. data/lib/sqreen/actions/block_user.rb +1 -1
  4. data/lib/sqreen/actions/redirect_ip.rb +1 -1
  5. data/lib/sqreen/actions/redirect_user.rb +1 -1
  6. data/lib/sqreen/condition_evaluator.rb +2 -8
  7. data/lib/sqreen/configuration.rb +1 -1
  8. data/lib/sqreen/deferred_logger.rb +14 -50
  9. data/lib/sqreen/deliveries/batch.rb +8 -1
  10. data/lib/sqreen/ecosystem.rb +80 -0
  11. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  12. data/lib/sqreen/ecosystem/http/net_http.rb +51 -0
  13. data/lib/sqreen/ecosystem/http/rack_request.rb +38 -0
  14. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  15. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  16. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  17. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  18. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +26 -0
  19. data/lib/sqreen/ecosystem/module_api/tracing_push_down.rb +34 -0
  20. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  21. data/lib/sqreen/ecosystem/module_registry.rb +39 -0
  22. data/lib/sqreen/ecosystem/redis/redis_connection.rb +35 -0
  23. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  24. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  25. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  26. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  27. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  28. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  29. data/lib/sqreen/ecosystem_integration.rb +70 -0
  30. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +89 -0
  31. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +38 -0
  32. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +56 -0
  33. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  34. data/lib/sqreen/encoding_sanitizer.rb +27 -0
  35. data/lib/sqreen/events/request_record.rb +1 -0
  36. data/lib/sqreen/frameworks/generic.rb +15 -10
  37. data/lib/sqreen/frameworks/rails.rb +7 -0
  38. data/lib/sqreen/frameworks/request_recorder.rb +0 -2
  39. data/lib/sqreen/graft/call.rb +23 -72
  40. data/lib/sqreen/graft/callback.rb +1 -1
  41. data/lib/sqreen/graft/hook.rb +85 -187
  42. data/lib/sqreen/graft/hook_point.rb +1 -1
  43. data/lib/sqreen/legacy/instrumentation.rb +10 -22
  44. data/lib/sqreen/legacy/old_event_submission_strategy.rb +8 -3
  45. data/lib/sqreen/log.rb +2 -3
  46. data/lib/sqreen/log/loggable.rb +0 -1
  47. data/lib/sqreen/logger.rb +0 -24
  48. data/lib/sqreen/metrics_store.rb +0 -11
  49. data/lib/sqreen/null_logger.rb +0 -22
  50. data/lib/sqreen/remote_command.rb +3 -1
  51. data/lib/sqreen/rules.rb +4 -8
  52. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  53. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  54. data/lib/sqreen/rules/rule_cb.rb +0 -2
  55. data/lib/sqreen/rules/waf_cb.rb +3 -3
  56. data/lib/sqreen/runner.rb +21 -33
  57. data/lib/sqreen/session.rb +2 -0
  58. data/lib/sqreen/signals/conversions.rb +6 -1
  59. data/lib/sqreen/version.rb +1 -1
  60. data/lib/sqreen/weave/legacy/instrumentation.rb +103 -194
  61. data/lib/sqreen/worker.rb +2 -6
  62. metadata +35 -10
  63. data/lib/sqreen/deprecation.rb +0 -38
  64. data/lib/sqreen/weave/budget.rb +0 -46
@@ -0,0 +1,38 @@
1
+ require 'sqreen/graft/hook'
2
+ require 'sqreen/ecosystem_integration/around_callbacks'
3
+
4
+ module Sqreen
5
+ class EcosystemIntegration
6
+ module InstrumentationService
7
+ class << self
8
+ # @param [String] module_name
9
+ # @param [String] method in form A::B#c or A::B.c
10
+ # @param [Hash{Symbol=>Proc}] spec
11
+ def instrument(module_name, method, spec)
12
+ hook = Sqreen::Graft::Hook[method].add do
13
+ if spec[:before]
14
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'pre', spec[:before])
15
+ before(nil, flow: true, &cb)
16
+ end
17
+
18
+ if spec[:after]
19
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'post', spec[:after])
20
+ after(nil, flow: true, &cb)
21
+ end
22
+
23
+ if spec[:raised]
24
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'failing', spec[:raised])
25
+ raised(nil, flow: true, &cb)
26
+ end
27
+
28
+ if spec[:ensured]
29
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'finally', spec[:ensured])
30
+ ensured(nil, flow: true, &cb)
31
+ end
32
+ end
33
+ hook.install
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,56 @@
1
+ require 'sqreen/log/loggable'
2
+
3
+ module Sqreen
4
+ class EcosystemIntegration
5
+ # This class gets notified of request start/end and
6
+ # 1) distributes such events to listeners (typically ecosystem modules;
7
+ # the method add_start_observer is exposed to ecosystem modules through
8
+ # +Sqreen::Ecosystem::ModuleApi::EventListener+ and the dispatch table).
9
+ # 2) keeps track of whether a request is active on this thread. This is
10
+ # so that users of this class can have this information without needing
11
+ # to subscribe to request start/events and keeping thread local state
12
+ # themselves.
13
+ # XXX: Since the Ecosystem is also notified of request start/end, it could
14
+ # notify its modules of request start without going through the dispatch
15
+ # table and call add_start_observer. We need to think if we want to keep
16
+ # the transaction / request distinction or if they should just be
17
+ # assumed to be the same, though.
18
+ class RequestLifecycleTracking
19
+ include Sqreen::Log::Loggable
20
+
21
+ def initialize
22
+ @start_observers = []
23
+ @tl_key = "#{object_id}_req_in_flight"
24
+ end
25
+
26
+ # API for classes needing to know the request state
27
+
28
+ # @param cb A callback taking a Rack::Request
29
+ def add_start_observer(cb)
30
+ @start_observers << cb
31
+ end
32
+
33
+ def in_request?
34
+ Thread.current[@tl_key] ? true : false
35
+ end
36
+
37
+ # API for classes notifying this one of request events
38
+
39
+ def notify_request_start(rack_req)
40
+ Thread.current[@tl_key] = true
41
+ return if @start_observers.empty?
42
+ @start_observers.each do |cb|
43
+ begin
44
+ cb.call(rack_req)
45
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
46
+ logger.warn { "Error calling #{cb} on request start: #{e.message}" }
47
+ end
48
+ end
49
+ end
50
+
51
+ def notify_request_end
52
+ Thread.current[@tl_key] = false
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,35 @@
1
+ require 'sqreen/log/loggable'
2
+
3
+ module Sqreen
4
+ class EcosystemIntegration
5
+ class SignalConsumption
6
+ include Sqreen::Log::Loggable
7
+
8
+ PAYLOAD_CREATOR_SECTIONS = %w[request response params headers].freeze
9
+
10
+ # @param [Sqreen::Frameworks::GenericFramework] framework
11
+ # @param [Sqreen::EcosystemIntegration::RequestLifecycleTracking]
12
+ # @param [Sqreen::CappedQueue]
13
+ def initialize(framework, req_lifecycle, queue)
14
+ @framework = framework
15
+ @req_lifecycle = req_lifecycle
16
+ @queue = queue
17
+ end
18
+
19
+ def consume_signal(signal)
20
+ # transitional
21
+ unless Sqreen.features.fetch('use_signals', DEFAULT_USE_SIGNALS)
22
+ logger.debug { "Discarding signal #{signal} (signals disabled)" }
23
+ return
24
+ end
25
+
26
+ if @req_lifecycle.in_request?
27
+ # add it to the request record
28
+ @framework.observe(:signals, signal, PAYLOAD_CREATOR_SECTIONS, true)
29
+ else
30
+ @queue.push signal
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ module Sqreen
7
+ class EncodingSanitizer
8
+ def self.sanitize(obj)
9
+ case obj
10
+ when String
11
+ sanitize_string(obj)
12
+ when Array
13
+ obj.map { |e| sanitize(e) }
14
+ when Hash
15
+ obj.each_with_object({}) { |(k, v), h| h[k] = sanitize(v) }
16
+ else
17
+ obj
18
+ end
19
+ end
20
+
21
+ def self.sanitize_string(s)
22
+ return s if s.encoding.name == 'UTF-8' && s.valid_encoding?
23
+
24
+ s.encode('UTF-16', :invalid => :replace, :undef => :replace).encode('UTF-8')
25
+ end
26
+ end
27
+ end
@@ -8,6 +8,7 @@
8
8
  require 'json'
9
9
  require 'sqreen/log'
10
10
  require 'sqreen/event'
11
+ require 'sqreen/encoding_sanitizer'
11
12
  require 'sqreen/sensitive_data_redactor'
12
13
 
13
14
  module Sqreen
@@ -22,8 +22,17 @@ module Sqreen
22
22
  include RequestRecorder
23
23
  attr_accessor :sqreen_configuration
24
24
 
25
+ attr_writer :req_start_cb, :req_end_cb
26
+
25
27
  def initialize
26
28
  clean_request_record
29
+
30
+ # for notifying the ecosystem of request boundaries
31
+ # XXX: this should be refactored. It shouldn't be
32
+ # the framework doing these notifications to the ecosystem
33
+ # Probably the rule callback should do it itself
34
+ @req_start_cb = Proc.new {}
35
+ @req_end_cb = Proc.new {}
27
36
  end
28
37
 
29
38
  # What kind of database is this
@@ -209,16 +218,7 @@ module Sqreen
209
218
 
210
219
  # Should the agent not be starting up?
211
220
  def prevent_startup
212
- # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
213
- return :sidekiq_cli if defined?(Sidekiq::CLI)
214
- return :delayed_job if defined?(Delayed::Command)
215
-
216
- # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
217
- run_in_test = sqreen_configuration.get(:run_in_test)
218
- return :rake if !run_in_test && $0.end_with?('rake')
219
-
220
221
  return :irb if $0 == 'irb'
221
-
222
222
  return if sqreen_configuration.nil?
223
223
  disable = sqreen_configuration.get(:disable)
224
224
  return :config_disable if disable == true || disable.to_s.to_i == 1
@@ -260,8 +260,12 @@ module Sqreen
260
260
  # Nota: cleanup should be performed at end of request (see clean_request)
261
261
  def store_request(object)
262
262
  return unless ensure_rack_loaded
263
+
264
+ rack_req = Rack::Request.new(object)
265
+ @req_start_cb.call(rack_req)
266
+
263
267
  self.remaining_perf_budget = Sqreen.performance_budget
264
- SharedStorage.set(:request, Rack::Request.new(object))
268
+ SharedStorage.set(:request, rack_req)
265
269
  SharedStorage.set(:xss_params, nil)
266
270
  SharedStorage.set(:whitelisted, nil)
267
271
  SharedStorage.set(:request_overtime, nil)
@@ -290,6 +294,7 @@ module Sqreen
290
294
  SharedStorage.set(:xss_params, nil)
291
295
  SharedStorage.set(:whitelisted, nil)
292
296
  SharedStorage.set(:request_overtime, nil)
297
+ @req_end_cb.call
293
298
  end
294
299
 
295
300
  def remaining_perf_budget
@@ -103,6 +103,13 @@ module Sqreen
103
103
  run_in_test = sqreen_configuration.get(:run_in_test)
104
104
  return :rails_test if !run_in_test && (Rails.env.test? || Rails.env.cucumber?)
105
105
 
106
+ # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
107
+ return :sidekiq_cli if defined?(Sidekiq::CLI)
108
+ return :delayed_job if defined?(Delayed::Command)
109
+
110
+ # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
111
+ return :rake if !run_in_test && $0.end_with?('rake')
112
+
106
113
  return nil unless defined?(Rails::CommandsTasks)
107
114
  return nil if defined?(Rails::Server)
108
115
  return :rails_console if defined?(Rails::Console)
@@ -69,8 +69,6 @@ module Sqreen
69
69
 
70
70
  # signals require request section to be present
71
71
  payload_requests << 'request'
72
- # for signals, response is optional, but the backend team wants them
73
- payload_requests << 'response'
74
72
  payload = payload_creator.payload(payload_requests)
75
73
  payload[:observed] = observed_items
76
74
 
@@ -93,116 +93,67 @@ module Sqreen
93
93
  end
94
94
  end
95
95
 
96
- class TimerError < StandardError; end
97
-
98
96
  class Timer
99
97
  def self.read
100
98
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
101
99
  end
102
100
 
103
- attr_reader :tag, :size
101
+ attr_reader :tag
104
102
 
105
103
  def initialize(tag, &block)
106
104
  @tag = tag
105
+ @blips = []
107
106
  @block = block
108
- @tally = 0
109
- @size = 0
110
- end
111
-
112
- def elapsed
113
- raise(TimerError, 'Timer#elapsed when paused') if @size.even?
114
-
115
- @tally + Timer.read
116
107
  end
117
108
 
118
109
  def duration
119
- raise(TimerError, 'Timer#duration when running') if @size.odd?
110
+ @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e }
111
+ end
120
112
 
121
- @tally
113
+ def elapsed
114
+ @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e } + Timer.read
122
115
  end
123
116
 
124
117
  def ignore
125
- raise(TimerError, 'Timer#ignore when paused') if @size.even?
126
-
127
- @size += 1
128
- @tally += Timer.read
118
+ @blips << Timer.read
129
119
  yield(self)
130
120
  ensure
131
- @size += 1
132
- @tally -= Timer.read
121
+ @blips << Timer.read
133
122
  end
134
123
 
135
- def measure(opts = nil)
136
- raise(TimerError, 'Timer#measure when running') if @size.odd?
137
-
138
- now = Timer.read
139
-
140
- ignore = opts[:ignore] if opts
141
- if ignore
142
- ignore.size += 1
143
- ignore.tally += now
144
- end
145
-
146
- @size += 1
147
- @tally -= now
148
-
124
+ def measure
125
+ @blips << Timer.read
149
126
  yield(self)
150
127
  ensure
151
- now = Timer.read
152
-
153
- if ignore
154
- ignore.size += 1
155
- ignore.tally -= now
156
- end
157
-
158
- @size += 1
159
- @tally += now
160
-
128
+ @blips << Timer.read
161
129
  @block.call(self) if @block
130
+ Sqreen::Graft.logger.debug { "#{@tag}: time=%.03fus" % (duration * 1_000_000) }
162
131
  end
163
132
 
164
- def start(at = Timer.read)
165
- raise(TimerError, 'Timer#start when started') unless @size.even?
166
-
167
- @size += 1
168
- @tally -= at
169
-
170
- at
171
- end
172
-
173
- def stop(at = Timer.read)
174
- raise(TimerError, 'Timer#stop when unstarted') unless @size.odd?
175
-
176
- @size += 1
177
- @tally += at
178
-
179
- at
133
+ def start
134
+ @blips << Timer.read
180
135
  end
181
136
 
182
- def started?
183
- @size != 0 && @size.odd?
137
+ def stop
138
+ @blips << Timer.read
184
139
  end
185
140
 
186
- def stopped?
187
- @size != 0 && @size.even?
141
+ def include_measurements(another_timer)
142
+ @blips += another_timer.instance_variable_get(:@blips)
188
143
  end
189
144
 
190
- def running?
191
- @size.odd?
145
+ def start_and_end
146
+ raise 'Not exactly two measurements recorded' unless size == 2
147
+ @blips
192
148
  end
193
149
 
194
- def paused?
195
- @size.even?
150
+ def size
151
+ @blips.size
196
152
  end
197
153
 
198
154
  def to_s
199
155
  "#{@tag}: time=%.03fus" % (duration * 1_000_000)
200
156
  end
201
-
202
- protected
203
-
204
- attr_reader :tally
205
- attr_writer :size, :tally
206
157
  end
207
158
  end
208
159
  end
@@ -21,7 +21,7 @@ module Sqreen
21
21
  end
22
22
 
23
23
  def call(*args, &block)
24
- # Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" } if Sqreen::Graft.logger.debug?
24
+ Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" }
25
25
  return if @disabled
26
26
  @block.call(*args, &block)
27
27
  end
@@ -21,13 +21,6 @@ module Sqreen
21
21
  self[hook_point, strategy].add(&block)
22
22
  end
23
23
 
24
- def self.ignore
25
- Thread.current[:sqreen_hook_entered] = true
26
- yield
27
- ensure
28
- Thread.current[:sqreen_hook_entered] = false
29
- end
30
-
31
24
  attr_reader :point
32
25
 
33
26
  def initialize(hook_point, dependency_test = nil, strategy = :chain)
@@ -53,31 +46,27 @@ module Sqreen
53
46
  end
54
47
 
55
48
  def before(tag = nil, opts = {}, &block)
56
- return @before if block.nil?
49
+ return @before.sort_by(&:rank) if block.nil?
57
50
 
58
51
  @before << Callback.new(callback_name(:before, tag), opts, &block)
59
- @before.sort_by!(&:rank)
60
52
  end
61
53
 
62
54
  def after(tag = nil, opts = {}, &block)
63
- return @after if block.nil?
55
+ return @after.sort_by(&:rank) if block.nil?
64
56
 
65
57
  @after << Callback.new(callback_name(:after, tag), opts, &block)
66
- @after.sort_by!(&:rank)
67
58
  end
68
59
 
69
60
  def raised(tag = nil, opts = {}, &block)
70
- return @raised if block.nil?
61
+ return @raised.sort_by(&:rank) if block.nil?
71
62
 
72
63
  @raised << Callback.new(callback_name(:raised, tag), opts, &block)
73
- @raised.sort_by!(&:rank)
74
64
  end
75
65
 
76
66
  def ensured(tag = nil, opts = {}, &block)
77
- return @ensured if block.nil?
67
+ return @ensured.sort_by(&:rank) if block.nil?
78
68
 
79
69
  @ensured << Callback.new(callback_name(:ensured, tag), opts, &block)
80
- @ensured.sort_by!(&:rank)
81
70
  end
82
71
 
83
72
  def depends_on(&block)
@@ -120,25 +109,11 @@ module Sqreen
120
109
  @before = []
121
110
  @after = []
122
111
  @raised = []
123
- @ensured = []
124
112
  end
125
113
 
126
114
  def self.wrapper(hook)
127
- timed_hooks_proc = proc do |t|
128
- if (request = Thread.current[:sqreen_http_request])
129
- request[:timed_hooks] << t if request[:timed_level] >= 1
130
- end
131
- end
132
- timed_callbacks_proc = proc do |t|
133
- if (request = Thread.current[:sqreen_http_request])
134
- request[:timed_callbacks] << t if request[:timed_level] >= 1
135
- end
136
- end
137
-
138
115
  Proc.new do |*args, &block|
139
- request = Thread.current[:sqreen_http_request]
140
-
141
- if Thread.current[:sqreen_hook_entered]
116
+ if Thread.current[:sqreen_hook_entered] || Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget_expended]
142
117
  if hook.point.super?
143
118
  return super(*args, &block)
144
119
  else
@@ -146,88 +121,49 @@ module Sqreen
146
121
  end
147
122
  end
148
123
 
149
- if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
150
- if request[:timed_level] >= 2
151
- begin
152
- request[:skipped_callbacks].concat(hook.before)
153
-
154
- if hook.point.super?
155
- return super(*args, &block)
156
- else
157
- return hook.point.apply(self, 'sqreen_hook', *args, &block)
158
- end
159
- rescue ::Exception # rubocop:disable Lint/RescueException
160
- request[:skipped_callbacks].concat(hook.raised)
161
- raise
162
- else
163
- request[:skipped_callbacks].concat(hook.after)
164
- ensure
165
- request[:skipped_callbacks].concat(hook.ensured)
166
- end
167
- else
168
- if hook.point.super? # rubocop:disable Style/IfInsideElse
169
- return super(*args, &block)
170
- else
171
- return hook.point.apply(self, 'sqreen_hook', *args, &block)
172
- end
173
- end
174
- end
175
-
176
- hook_point_super = hook.point.super?
177
- logger = Sqreen::Graft.logger
178
- logger_debug = Sqreen::Graft.logger.debug?
179
-
180
- Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
181
- # budget implies request
182
- # TODO: make budget depend on a generic context (currently "request")
183
- budget = request[:time_budget] if request
184
- if budget
185
- budget_threshold = request[:time_budget_threshold]
186
- budget_ratio = request[:time_budget_ratio]
187
- sqreen_timer = request[:sqreen_timer]
188
- request_timer = request[:request_timer]
189
- end
124
+ Timer.new(hook.point) do |t|
125
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks] << t
126
+ end.measure do |chrono|
127
+ Sqreen::Graft.logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} caller:#{Kernel.caller[2].inspect}" }
190
128
 
129
+ budget = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget]
130
+ timer = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timer] if budget
191
131
  hooked_call = HookedCall.new(self, args)
192
132
 
193
133
  begin
194
- begin
195
- sqreen_timer.start if budget
196
- Thread.current[:sqreen_hook_entered] = true
197
-
198
- # TODO: make Call have #ball to throw by cb
199
- # TODO: can Call be the ball? r = catch(Call.new, &c)
200
- # TODO: is catch return value a Call? a #dispatch?
201
- # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
202
- # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
203
- # TODO: HookCall x CallbackCollection#each_with_call x Flow
204
- # TODO: TimedHookCall TimedCallbackCall
205
- # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
206
-
207
- request_elapsed = request_timer.elapsed if budget
208
-
134
+ timer.start if timer
135
+ Thread.current[:sqreen_hook_entered] = true
136
+
137
+ # TODO: make Call have #ball to throw by cb
138
+ # TODO: can Call be the ball? r = catch(Call.new, &c)
139
+ # TODO: is catch return value a Call? a #dispatch?
140
+ # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
141
+ # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
142
+ # TODO: HookCall x CallbackCollection#each_with_call x Flow
143
+ # TODO: TimedHookCall TimedCallbackCall
144
+ # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
145
+
146
+ Timer.new("#{hook.point}@before") do |t|
147
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_before] << t
148
+ end.measure do |before_chrono|
209
149
  hook.before.each do |c|
210
150
  next if c.ignore && c.ignore.call
211
151
 
212
- if budget && !c.mandatory
213
- sqreen_elapsed = sqreen_timer.elapsed
214
- if budget_ratio && !request[:time_budget_expended]
215
- fixed_budget = budget_threshold
216
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
217
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
218
- else
219
- remaining = budget_threshold - sqreen_elapsed
220
- end
152
+ if timer && !c.mandatory
153
+ remaining = budget - timer.elapsed
221
154
  unless remaining > 0
222
- request[:skipped_callbacks] << c
223
- request[:time_budget_expended] = true
155
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
224
156
  next
225
157
  end
226
158
  end
227
159
 
228
160
  flow = catch(Ball.new) do |ball|
229
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
230
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
161
+ Timer.new(c.name) do |t|
162
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
163
+ end.measure do
164
+ before_chrono.ignore do
165
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
166
+ end
231
167
  end
232
168
  end
233
169
 
@@ -237,59 +173,52 @@ module Sqreen
237
173
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
238
174
  break if flow.break?
239
175
  end unless hook.disabled?
240
- rescue StandardError => e
241
- Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
242
- Sqreen::RemoteException.record(e) if Sqreen.queue
243
176
  end
244
177
 
245
178
  raise hooked_call.raise if hooked_call.raising
246
179
  return hooked_call.return if hooked_call.returning
247
180
  ensure
248
181
  Thread.current[:sqreen_hook_entered] = false
249
- sqreen_timer.stop if budget
250
- end unless hook.before.empty?
182
+ timer.stop if timer
183
+ end
251
184
 
252
185
  begin
253
186
  chrono.ignore do
254
- if hook_point_super
187
+ if hook.point.super?
255
188
  hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
256
189
  else
257
190
  hooked_call.returned = hook.point.apply(hooked_call.instance, 'sqreen_hook', *(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
258
191
  end
259
192
  end
260
193
  rescue ::Exception => e # rubocop:disable Lint/RescueException
261
- begin
262
- sqreen_timer.start if budget
263
- Thread.current[:sqreen_hook_entered] = true
264
- hooked_call.raised = e
194
+ timer.start if timer
195
+ Thread.current[:sqreen_hook_entered] = true
196
+ hooked_call.raised = e
265
197
 
266
- logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
267
- raise if hook.raised.empty?
268
-
269
- request_elapsed = request_timer.elapsed if budget
198
+ Sqreen::Graft.logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" }
199
+ raise if hook.raised.empty?
270
200
 
201
+ Timer.new("#{hook.point}@raised") do |t|
202
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_raised] << t
203
+ end.measure do |raised_chrono|
271
204
  hook.raised.each do |c|
272
205
  next if c.ignore && c.ignore.call
273
206
 
274
- if budget && !c.mandatory
275
- sqreen_elapsed = sqreen_timer.elapsed
276
- if budget_ratio && !request[:time_budget_expended]
277
- fixed_budget = budget_threshold
278
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
279
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
280
- else
281
- remaining = budget_threshold - sqreen_elapsed
282
- end
207
+ if timer && !c.mandatory
208
+ remaining = budget - timer.elapsed
283
209
  unless remaining > 0
284
- request[:skipped_callbacks] << c
285
- request[:time_budget_expended] = true
210
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
286
211
  next
287
212
  end
288
213
  end
289
214
 
290
215
  flow = catch(Ball.new) do |ball|
291
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
292
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, hooked_call.raised), ball)
216
+ Timer.new(c.name) do |t|
217
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
218
+ end.measure do
219
+ raised_chrono.ignore do
220
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, hooked_call.raised), ball)
221
+ end
293
222
  end
294
223
  end
295
224
 
@@ -299,9 +228,6 @@ module Sqreen
299
228
  hooked_call.retrying = true if flow.retry?
300
229
  break if flow.break?
301
230
  end unless hook.disabled?
302
- rescue StandardError => e
303
- Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
304
- Sqreen::RemoteException.record(e) if Sqreen.queue
305
231
  end
306
232
 
307
233
  retry if hooked_call.retrying
@@ -309,37 +235,30 @@ module Sqreen
309
235
  return hooked_call.return if hooked_call.returning
310
236
  raise
311
237
  else
312
- begin
313
- sqreen_timer.start if budget
314
- Thread.current[:sqreen_hook_entered] = true
315
-
316
- # TODO: hooked_call.returning should be always false here?
317
- return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
318
-
319
- request_elapsed = request_timer.elapsed if budget
238
+ timer.start if timer
239
+ Thread.current[:sqreen_hook_entered] = true
320
240
 
241
+ Timer.new("#{hook.point}@after") do |t|
242
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_after] << t
243
+ end.measure do |after_chrono|
321
244
  hook.after.each do |c|
322
245
  next if c.ignore && c.ignore.call
323
246
 
324
- if budget && !c.mandatory
325
- sqreen_elapsed = sqreen_timer.elapsed
326
- if budget_ratio && !request[:time_budget_expended]
327
- fixed_budget = budget_threshold
328
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
329
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
330
- else
331
- remaining = budget_threshold - sqreen_elapsed
332
- end
247
+ if timer && !c.mandatory
248
+ remaining = budget - timer.elapsed
333
249
  unless remaining > 0
334
- request[:skipped_callbacks] << c
335
- request[:time_budget_expended] = true
250
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
336
251
  next
337
252
  end
338
253
  end
339
254
 
340
255
  flow = catch(Ball.new) do |ball|
341
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
342
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
256
+ Timer.new(c.name) do |t|
257
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
258
+ end.measure do
259
+ after_chrono.ignore do
260
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
261
+ end
343
262
  end
344
263
  end
345
264
 
@@ -348,48 +267,34 @@ module Sqreen
348
267
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
349
268
  break if flow.break?
350
269
  end unless hook.disabled?
351
- rescue StandardError => e
352
- Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
353
- Sqreen::RemoteException.record(e) if Sqreen.queue
354
270
  end
355
271
 
356
272
  raise hooked_call.raise if hooked_call.raising
357
273
  return hooked_call.returning ? hooked_call.return : hooked_call.returned
358
274
  ensure
359
- begin
360
- # TODO: sqreen_timer.start if someone has thrown?
361
- # TODO: sqreen_timer.stop at end of rescue+else?
362
- # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
363
- # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
364
-
365
- # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
366
- # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
367
-
368
- # done at either rescue or else
369
- # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
275
+ # TODO: timer.start if someone has thrown?
370
276
 
277
+ Timer.new("#{hook.point}@ensured") do |t|
278
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_ensured] << t
279
+ end.measure do |ensured_chrono|
371
280
  hook.ensured.each do |c|
372
281
  next if c.ignore && c.ignore.call
373
282
 
374
- if budget && !c.mandatory
375
- sqreen_elapsed = sqreen_timer.elapsed
376
- if budget_ratio && !request[:time_budget_expended]
377
- fixed_budget = budget_threshold
378
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
379
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
380
- else
381
- remaining = budget_threshold - sqreen_elapsed
382
- end
283
+ if timer && !c.mandatory
284
+ remaining = budget - timer.elapsed
383
285
  unless remaining > 0
384
- request[:skipped_callbacks] << c
385
- request[:time_budget_expended] = true
286
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
386
287
  next
387
288
  end
388
289
  end
389
290
 
390
291
  flow = catch(Ball.new) do |ball|
391
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
392
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
292
+ Timer.new(c.name) do |t|
293
+ Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
294
+ end.measure do
295
+ ensured_chrono.ignore do
296
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
297
+ end
393
298
  end
394
299
  end
395
300
 
@@ -397,18 +302,11 @@ module Sqreen
397
302
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
398
303
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
399
304
  break if flow.break?
400
- end unless hook.ensured.empty? || hook.disabled?
401
-
402
- Thread.current[:sqreen_hook_entered] = false
403
- sqreen_timer.stop if budget
404
- rescue StandardError => e
405
- Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
406
- Sqreen::RemoteException.record(e) if Sqreen.queue
305
+ end unless hook.disabled?
407
306
  end
408
307
 
409
- # TODO: should we run the following?
410
- # raise hooked_call.raise if hooked_call.raising
411
- # return hooked_call.returning ? hooked_call.return : hooked_call.returned
308
+ Thread.current[:sqreen_hook_entered] = false
309
+ timer.stop if timer
412
310
  end
413
311
  end # chrono
414
312
  end