sqreen 1.20.4 → 1.21.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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