google-cloud-debugger 0.40.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.
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