google-cloud-debugger 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +178 -0
  4. data/CHANGELOG.md +233 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/INSTRUMENTATION.md +115 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +266 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
  13. data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
  14. data/ext/google/cloud/debugger/debugger_c/evaluator.c +115 -0
  15. data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
  16. data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
  17. data/ext/google/cloud/debugger/debugger_c/tracer.c +542 -0
  18. data/ext/google/cloud/debugger/debugger_c/tracer.h +25 -0
  19. data/lib/google-cloud-debugger.rb +181 -0
  20. data/lib/google/cloud/debugger.rb +259 -0
  21. data/lib/google/cloud/debugger/agent.rb +255 -0
  22. data/lib/google/cloud/debugger/backoff.rb +70 -0
  23. data/lib/google/cloud/debugger/breakpoint.rb +443 -0
  24. data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1099 -0
  25. data/lib/google/cloud/debugger/breakpoint/source_location.rb +74 -0
  26. data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
  27. data/lib/google/cloud/debugger/breakpoint/status_message.rb +93 -0
  28. data/lib/google/cloud/debugger/breakpoint/validator.rb +92 -0
  29. data/lib/google/cloud/debugger/breakpoint/variable.rb +595 -0
  30. data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
  31. data/lib/google/cloud/debugger/breakpoint_manager.rb +311 -0
  32. data/lib/google/cloud/debugger/credentials.rb +50 -0
  33. data/lib/google/cloud/debugger/debuggee.rb +222 -0
  34. data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +76 -0
  35. data/lib/google/cloud/debugger/logpoint.rb +98 -0
  36. data/lib/google/cloud/debugger/middleware.rb +200 -0
  37. data/lib/google/cloud/debugger/project.rb +110 -0
  38. data/lib/google/cloud/debugger/rails.rb +174 -0
  39. data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
  40. data/lib/google/cloud/debugger/service.rb +88 -0
  41. data/lib/google/cloud/debugger/snappoint.rb +208 -0
  42. data/lib/google/cloud/debugger/tracer.rb +137 -0
  43. data/lib/google/cloud/debugger/transmitter.rb +199 -0
  44. data/lib/google/cloud/debugger/version.rb +22 -0
  45. metadata +353 -0
@@ -0,0 +1,208 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/debugger/breakpoint"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ ##
22
+ # # Snappoint
23
+ #
24
+ # A kind of {Google::Cloud::Debugger::Breakpoint} that can be evaluated
25
+ # to capture the state of the program at time of evaluation. This is
26
+ # essentially a {Google::Cloud::Debugger::Breakpoint} with action attrubte
27
+ # set to `:CAPTURE`
28
+ #
29
+ class Snappoint < Breakpoint
30
+ ##
31
+ # Max number of top stacks to collect local variables information
32
+ STACK_EVAL_DEPTH = 5
33
+
34
+ ##
35
+ # Max size of payload a Snappoint collects
36
+ MAX_PAYLOAD_SIZE = 32768 # 32KB
37
+
38
+ ##
39
+ # @private Max size an evaluated expression variable is allowed to be
40
+ MAX_EXPRESSION_LIMIT = 32768 # 32KB
41
+
42
+ ##
43
+ # @private Max size a normal evaluated variable is allowed to be
44
+ MAX_VAR_LIMIT = 1024 # 1KB
45
+
46
+ ##
47
+ # @private Construct a new Snappoint instance.
48
+ def initialize *args
49
+ super
50
+
51
+ init_var_table
52
+ end
53
+
54
+ ##
55
+ # @private Initialize the variable table by inserting a buffer full
56
+ # variable at index 0. This variable will be shared by other variable
57
+ # evaluations if this Snappoint exceeds size limit.
58
+ def init_var_table
59
+ return if @variable_table[0] &&
60
+ @variable_table[0].buffer_full_variable?
61
+
62
+ buffer_full_var = Variable.buffer_full_variable
63
+ @variable_table.variables.unshift buffer_full_var
64
+ end
65
+
66
+ ##
67
+ # Evaluate the breakpoint unless it's already marked as completed.
68
+ # Store evaluted expressions and stack frame variables in
69
+ # @evaluated_expressions, @stack_frames. Mark breakpoint complete if
70
+ # successfully evaluated.
71
+ #
72
+ # @param [Array<Binding>] call_stack_bindings An array of Ruby Binding
73
+ # objects, from the call stack that leads to the triggering of the
74
+ # breakpoints.
75
+ #
76
+ # @return [Boolean] True if evaluated successfully; false otherwise.
77
+ #
78
+ def evaluate call_stack_bindings
79
+ synchronize do
80
+ top_binding = call_stack_bindings[0]
81
+
82
+ return false if complete? || !check_condition(top_binding)
83
+
84
+ begin
85
+ eval_expressions top_binding
86
+ eval_call_stack call_stack_bindings
87
+
88
+ complete
89
+ rescue StandardError
90
+ return false
91
+ end
92
+ end
93
+
94
+ true
95
+ end
96
+
97
+ ##
98
+ # @private Evaluates the breakpoint expressions at the point that
99
+ # triggered the breakpoint. The expressions subject to the read-only
100
+ # rules. If the expressions do any write operations, the evaluations
101
+ # abort and show an error message in place of the real result.
102
+ #
103
+ # @param [Binding] bind The binding object from the context
104
+ #
105
+ def eval_expressions bind
106
+ @evaluated_expressions = []
107
+
108
+ expressions.each do |expression|
109
+ eval_result = Evaluator.readonly_eval_expression bind, expression
110
+
111
+ if eval_result.is_a?(Exception) &&
112
+ eval_result.instance_variable_get(:@mutation_cause)
113
+ evaluated_var = Variable.new
114
+ evaluated_var.name = expression
115
+ evaluated_var.set_error_state \
116
+ "Error: #{eval_result.message}",
117
+ refers_to: StatusMessage::VARIABLE_VALUE
118
+ else
119
+ evaluated_var = convert_variable eval_result,
120
+ name: expression,
121
+ limit: MAX_EXPRESSION_LIMIT
122
+ end
123
+
124
+ @evaluated_expressions << evaluated_var
125
+ end
126
+ end
127
+
128
+ ##
129
+ # @private Evaluates call stack. Collects function name and location of
130
+ # each frame from given binding objects. Collects local variable
131
+ # information from top frames.
132
+ #
133
+ # @param [Array<Binding>] call_stack_bindings A list of binding
134
+ # objects that come from each of the call stack frames.
135
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::StackFrame>]
136
+ # A list of StackFrame objects that represent state of the
137
+ # call stack
138
+ #
139
+ def eval_call_stack call_stack_bindings
140
+ @stack_frames = []
141
+
142
+ call_stack_bindings.each_with_index do |frame_binding, i|
143
+ frame_info = StackFrame.new.tap do |sf|
144
+ sf.function = frame_binding.eval("__method__").to_s
145
+ sf.location = SourceLocation.new.tap do |l|
146
+ l.path =
147
+ frame_binding.eval "::File.absolute_path(__FILE__)"
148
+ l.line = frame_binding.eval "__LINE__"
149
+ end
150
+ end
151
+
152
+ @stack_frames << frame_info
153
+
154
+ next if i >= STACK_EVAL_DEPTH
155
+
156
+ frame_binding.local_variables.each do |local_var_name|
157
+ local_var = frame_binding.local_variable_get local_var_name
158
+ var = convert_variable local_var, name: local_var_name,
159
+ limit: MAX_VAR_LIMIT
160
+
161
+ frame_info.locals << var
162
+ end
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ ##
169
+ # @private Compute the total size of all the evaluated variables in
170
+ # this breakpoint.
171
+ def calculate_total_size
172
+ result = evaluated_expressions.inject 0 do |sum, exp|
173
+ sum + exp.payload_size
174
+ end
175
+
176
+ stack_frames.each do |stack_frame|
177
+ result = stack_frame.locals.inject result do |sum, local|
178
+ sum + local.payload_size
179
+ end
180
+ end
181
+
182
+ result = variable_table.variables.inject result do |sum, var|
183
+ sum + var.payload_size
184
+ end
185
+
186
+ result
187
+ end
188
+
189
+ ##
190
+ # @private Translate a Ruby variable into a {Breakpoint::Variable}.
191
+ # If the existing evaluated variables already exceed maximum allowed
192
+ # size, then return a buffer full warning variable instead.
193
+ def convert_variable source, name: nil, limit: nil
194
+ current_total_size = calculate_total_size
195
+ var = Variable.from_rb_var source, name: name, limit: limit,
196
+ var_table: variable_table
197
+
198
+ if (current_total_size + var.payload_size <= MAX_PAYLOAD_SIZE) ||
199
+ var.reference_variable?
200
+ var
201
+ else
202
+ Breakpoint::Variable.buffer_full_variable variable_table, name: name
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,137 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "binding_of_caller"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ ##
22
+ # # Tracer
23
+ #
24
+ # When active breakpoints are set for the debugger, the tracer monitors
25
+ # the running Ruby application and triggers evaluation when the code is
26
+ # executed at the breakpoint locations.
27
+ #
28
+ # The tracer tracks the running application using several Ruby TracePoints
29
+ # and C level Ruby debugging API.
30
+ #
31
+ class Tracer
32
+ ##
33
+ # The debugger agent this tracer belongs to
34
+ # @return [Google::Cloud::Debugger::Agent]
35
+ attr_reader :agent
36
+
37
+ ##
38
+ # @private File tracing point that enables line tracing when program
39
+ # counter enters a file that contains breakpoints
40
+ attr_reader :file_tracepoint
41
+
42
+ ##
43
+ # @private Fiber tracing point that enables line tracing when program
44
+ # counter enters a file that contains breakpoints through fiber
45
+ # switching
46
+ attr_reader :fiber_tracepoint
47
+
48
+ ##
49
+ # @private A nested hash structure represent all the active breakpoints.
50
+ # The structure is optimized for fast access. For example:
51
+ # {
52
+ # "path/to/file.rb" => { # The absolute file path
53
+ # 123 => [ # The line number in file
54
+ # <Google::Cloud::Debugger::Breakpoint> # List of breakpoints
55
+ # ]
56
+ # }
57
+ # }
58
+ attr_reader :breakpoints_cache
59
+
60
+ ##
61
+ # @private Construct a new instance of Tracer
62
+ def initialize agent
63
+ @agent = agent
64
+ @file_tracepoint = nil
65
+ @fiber_tracepoint = nil
66
+ @breakpoints_cache = {}
67
+ end
68
+
69
+ ##
70
+ # Update tracer's private breakpoints cache with the list of active
71
+ # breakpoints from BreakpointManager.
72
+ #
73
+ # This methood is atomic for thread safety purpose.
74
+ def update_breakpoints_cache
75
+ active_breakpoints = agent.breakpoint_manager.active_breakpoints.dup
76
+ breakpoints_hash = {}
77
+
78
+ active_breakpoints.each do |active_breakpoint|
79
+ breakpoint_line = active_breakpoint.line
80
+ breakpoint_path = active_breakpoint.full_path
81
+ breakpoints_hash[breakpoint_path] ||= {}
82
+ breakpoints_hash[breakpoint_path][breakpoint_line] ||= []
83
+ breakpoints_hash[breakpoint_path][breakpoint_line].push(
84
+ active_breakpoint
85
+ )
86
+ end
87
+
88
+ # Tracer is explicitly designed to not have a lock. This should be the
89
+ # only place writing @breakpoints_cache to ensure thread safety.
90
+ @breakpoints_cache = breakpoints_hash
91
+ end
92
+
93
+ ##
94
+ # Callback function when a set of breakpoints are hit. Handover the hit
95
+ # breakpoint to breakpoint_manager to be evaluated.
96
+ def breakpoints_hit breakpoints, call_stack_bindings
97
+ breakpoints.each do |breakpoint|
98
+ # Stop evaluating breakpoints if we have quotas and the quotas are
99
+ # met.
100
+ break if agent.quota_manager && !agent.quota_manager.more?
101
+
102
+ next if breakpoint.nil? || breakpoint.complete?
103
+
104
+ time_begin = Time.now
105
+
106
+ agent.breakpoint_manager.breakpoint_hit breakpoint,
107
+ call_stack_bindings
108
+
109
+ # Report time and resource consumption to quota manager
110
+ if agent.quota_manager.respond_to? :consume
111
+ agent.quota_manager.consume time: Time.now - time_begin
112
+ end
113
+ end
114
+
115
+ update_breakpoints_cache
116
+
117
+ # Disable all trace points and tracing if all breakpoints are complete
118
+ disable_traces if @breakpoints_cache.empty?
119
+ end
120
+
121
+ ##
122
+ # Get the sync the breakpoints cache with BreakpointManager. Start
123
+ # tracing and monitoring if there are any breakpoints.
124
+ def start
125
+ update_breakpoints_cache
126
+ enable_traces unless breakpoints_cache.empty?
127
+ end
128
+
129
+ ##
130
+ # Stops all tracing.
131
+ def stop
132
+ disable_traces
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,199 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "concurrent"
17
+ require "google/cloud/errors"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Debugger
22
+ ##
23
+ # # TransmitterError
24
+ #
25
+ # Used to indicate a problem submitting breakpoints. This can occur when
26
+ # there are not enough resources allocated for the amount of usage, or
27
+ # when the calling the API returns an error.
28
+ #
29
+ class TransmitterError < Google::Cloud::Error
30
+ # @!attribute [r] breakpoint
31
+ # @return [Array<Google::Cloud::Debugger::Breakpoint>] The
32
+ # individual error event that was not submitted to Stackdriver
33
+ # Debugger service.
34
+ attr_reader :breakpoint
35
+
36
+ def initialize message, breakpoint = nil
37
+ super message
38
+ @breakpoint = breakpoint
39
+ end
40
+ end
41
+
42
+ ##
43
+ # # Transmitter
44
+ #
45
+ # Responsible for submit evaluated breakpoints back to Stackdriver
46
+ # Debugger service asynchronously. It maintains a thread pool.
47
+ #
48
+ # The transmitter is controlled by the debugger agent it belongs to.
49
+ # Debugger agent submits evaluated breakpoint asynchronously, and the
50
+ # transmitter submits the breakpoints to Stackdriver Debugger service.
51
+ #
52
+ class Transmitter
53
+ ##
54
+ # @private The gRPC Service object and thread pool.
55
+ attr_reader :service, :thread_pool
56
+
57
+ ##
58
+ # The debugger agent this transmiter belongs to
59
+ # @return [Google::Cloud::Debugger::Agent]
60
+ attr_accessor :agent
61
+
62
+ ##
63
+ # Maximum backlog size for this transmitter's queue
64
+ attr_accessor :max_queue
65
+ alias max_queue_size max_queue
66
+ alias max_queue_size= max_queue=
67
+
68
+ ##
69
+ # Maximum threads used in the thread pool
70
+ attr_accessor :threads
71
+
72
+ ##
73
+ # @private Creates a new Transmitter instance.
74
+ def initialize agent, service, max_queue: 1000, threads: 10
75
+ @agent = agent
76
+ @service = service
77
+
78
+ @max_queue = max_queue
79
+ @threads = threads
80
+
81
+ @thread_pool = Concurrent::ThreadPoolExecutor.new \
82
+ max_threads: @threads, max_queue: @max_queue
83
+
84
+ @error_callbacks = []
85
+
86
+ # Make sure all queued calls are completed when process exits.
87
+ at_exit { stop }
88
+ end
89
+
90
+ ##
91
+ # Enqueue an evaluated breakpoint to be submitted by the transmitter.
92
+ #
93
+ # @raise [TransmitterError] if there are no resources available to make
94
+ # queue the API call on the thread pool.
95
+ def submit breakpoint
96
+ Concurrent::Promises.future_on @thread_pool, breakpoint do |bp|
97
+ submit_sync bp
98
+ end
99
+ rescue Concurrent::RejectedExecutionError => e
100
+ raise TransmitterError.new(
101
+ "Error asynchronously submitting breakpoint: #{e.message}",
102
+ breakpoint
103
+ )
104
+ end
105
+
106
+ ##
107
+ # Starts the transmitter and its thread pool.
108
+ #
109
+ # @return [Transmitter] returns self so calls can be chained.
110
+ def start
111
+ # no-op
112
+ self
113
+ end
114
+
115
+ ##
116
+ # Stops the transmitter and its thread pool. Once stopped, cannot be
117
+ # started again.
118
+ #
119
+ # @return [Transmitter] returns self so calls can be chained.
120
+ def stop timeout = nil
121
+ if @thread_pool
122
+ @thread_pool.shutdown
123
+ @thread_pool.wait_for_termination timeout
124
+ end
125
+
126
+ self
127
+ end
128
+
129
+ ##
130
+ # Whether the transmitter has been started.
131
+ #
132
+ # @return [boolean] `true` when started, `false` otherwise.
133
+ #
134
+ def started?
135
+ @thread_pool.running? if @thread_pool
136
+ end
137
+
138
+ ##
139
+ # Whether the transmitter has been stopped.
140
+ #
141
+ # @return [boolean] `true` when stopped, `false` otherwise.
142
+ #
143
+ def stopped?
144
+ !started?
145
+ end
146
+
147
+ ##
148
+ # Register to be notified of errors when raised.
149
+ #
150
+ # If an unhandled error has occurred the transmitter will attempt to
151
+ # recover from the error and resume submitting breakpoints.
152
+ #
153
+ # Multiple error handlers can be added.
154
+ #
155
+ # @yield [callback] The block to be called when an error is raised.
156
+ # @yieldparam [Exception] error The error raised.
157
+ #
158
+ def on_error &block
159
+ @error_callbacks << block
160
+ end
161
+
162
+ protected
163
+
164
+ # Calls all error callbacks.
165
+ def error! error
166
+ error_callbacks = @error_callbacks
167
+ error_callbacks = default_error_callbacks if error_callbacks.empty?
168
+ error_callbacks.each { |error_callback| error_callback.call error }
169
+ end
170
+
171
+ def default_error_callbacks
172
+ # This is memoized to reduce calls to the configuration.
173
+ @default_error_callbacks ||= begin
174
+ error_cb = Google::Cloud::Debugger.configure.on_error
175
+ error_cb ||= Google::Cloud.configure.on_error
176
+ if error_cb
177
+ [error_cb]
178
+ else
179
+ []
180
+ end
181
+ end
182
+ end
183
+
184
+ def submit_sync breakpoint
185
+ service.update_active_breakpoint agent.debuggee.id, breakpoint
186
+ rescue StandardError => e
187
+ sync_error = TransmitterError.new(
188
+ "Error asynchronously transmitting breakpoint: #{e.message}",
189
+ breakpoint
190
+ )
191
+ # Manually set backtrace so we don't have to raise
192
+ sync_error.set_backtrace caller
193
+
194
+ error! sync_error
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end