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,255 @@
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_manager"
17
+ require "google/cloud/debugger/debuggee"
18
+ require "google/cloud/debugger/debugger_c"
19
+ require "google/cloud/debugger/tracer"
20
+ require "google/cloud/debugger/transmitter"
21
+ require "google/cloud/logging"
22
+ require "stackdriver/core/async_actor"
23
+
24
+ module Google
25
+ module Cloud
26
+ module Debugger
27
+ ##
28
+ # # Agent
29
+ #
30
+ # The Stackdriver Debugger Agent runs on the same system where a debuggee
31
+ # application is running. The agent is responsible for sending state data,
32
+ # such as the value of program variables and the call stack, to
33
+ # Stackdriver Debugger when the code at a breakpoint location is executed.
34
+ #
35
+ # The Debugger Agent runs in its own child thread when started. It ensures
36
+ # the instrumented application is registered properly and constantly
37
+ # monitors for any active breakpoints. Once the agent gets updated with
38
+ # active breakpoints from Stackdriver Debugger service, it facilitates
39
+ # the breakpoints in application requests thread, then transport the
40
+ # result snapshot back to Stackdriver Debugger service asynchronously.
41
+ #
42
+ # @example
43
+ # require "google/cloud/debugger"
44
+ #
45
+ # debugger = Google::Cloud::Debugger.new
46
+ # agent = debugger.agent
47
+ # agent.start
48
+ #
49
+ class Agent
50
+ ##
51
+ # Name of the logpoints log file.
52
+ DEFAULT_LOG_NAME = "debugger_logpoints".freeze
53
+
54
+ ##
55
+ # @private Debugger Agent is an asynchronous actor
56
+ include Stackdriver::Core::AsyncActor
57
+
58
+ ##
59
+ # @private The gRPC Service object.
60
+ attr_reader :service
61
+
62
+ ##
63
+ # The gRPC Debuggee representation of the debuggee application. It
64
+ # contains identification information to match running application to
65
+ # specific Cloud Source Repository code base, and correctly group
66
+ # same versions of the debuggee application together through a generated
67
+ # unique identifier.
68
+ # @return [Google::Cloud::Debugger::Debuggee]
69
+ attr_reader :debuggee
70
+
71
+ ##
72
+ # It manages syncing breakpoints between the Debugger Agent and
73
+ # Stackdriver Debugger service
74
+ # @return [Google::Cloud::Debugger::BreakpointManager]
75
+ attr_reader :breakpoint_manager
76
+
77
+ ##
78
+ # It monitors the debuggee application and triggers breakpoint
79
+ # evaluation when breakpoints are set.
80
+ # @return [Google::Cloud::Debugger::Tracer]
81
+ attr_reader :tracer
82
+
83
+ ##
84
+ # It sends evaluated breakpoints snapshot back to Stackdriver Debugger
85
+ # Service.
86
+ # @return [Google::Cloud::Debugger::Transmiter]
87
+ attr_reader :transmitter
88
+
89
+ ##
90
+ # The logger used to write the results of Logpoints.
91
+ attr_accessor :logger
92
+
93
+ ##
94
+ # A quota tracking object helps tracking resource consumption during
95
+ # evaluations.
96
+ attr_accessor :quota_manager
97
+
98
+ ##
99
+ # Absolute path to the debuggee Ruby application root directory. The
100
+ # Stackdriver Debugger service creates canonical breakpoints with only
101
+ # relative path. So the debugger agent combines the relative path to
102
+ # the application directory to trace and evaluate breakpoints.
103
+ # @return [String]
104
+ attr_accessor :app_root
105
+
106
+ ##
107
+ # @private The last exception captured in the agent child thread
108
+ attr_reader :last_exception
109
+
110
+ ##
111
+ # Create a new Debugger Agent instance.
112
+ #
113
+ # @param [Google::Cloud::Debugger::Service] service The gRPC Service
114
+ # object
115
+ # @param [Google::Cloud::Logging::Logger] logger The logger used
116
+ # to write the results of Logpoints.
117
+ # @param [String] service_name Name for the debuggee application.
118
+ # @param [String] service_version Version identifier for the debuggee
119
+ # application.
120
+ # @param [String] app_root Absolute path to the root directory of
121
+ # the debuggee application. Default to Rack root.
122
+ #
123
+ def initialize service, logger: nil, service_name:, service_version:,
124
+ app_root: nil
125
+ super()
126
+
127
+ @service = service
128
+ @debuggee = Debuggee.new service, service_name: service_name,
129
+ service_version: service_version
130
+ @tracer = Debugger::Tracer.new self
131
+ @breakpoint_manager = BreakpointManager.new self, service
132
+ @breakpoint_manager.on_breakpoints_change =
133
+ method :breakpoints_change_callback
134
+
135
+ @transmitter = Transmitter.new self, service
136
+
137
+ @logger = logger || default_logger
138
+
139
+ init_app_root app_root
140
+
141
+ # Agent actor thread needs to force exit immediately.
142
+ set_cleanup_options timeout: 0
143
+ end
144
+
145
+ ##
146
+ # Starts the Debugger Agent in a child thread, where debuggee
147
+ # application registration and breakpoints querying will take place.
148
+ # It also starts the transmitter in another child thread.
149
+ #
150
+ def start
151
+ transmitter.start
152
+ async_start
153
+ end
154
+
155
+ ##
156
+ # Stops and terminates the Debugger Agent. It also properly shuts down
157
+ # transmitter and tracer.
158
+ #
159
+ # Once Debugger Agent is stopped, it cannot be started again.
160
+ #
161
+ def stop
162
+ transmitter.stop
163
+ async_stop
164
+ end
165
+
166
+ ##
167
+ # Stops the tracer regardless of whether any active breakpoints are
168
+ # present. Once the tracer stops monitoring the debuggee application,
169
+ # the application can return to normal performance.
170
+ def stop_tracer
171
+ tracer.stop
172
+ end
173
+
174
+ ##
175
+ # @private Callback function for AsyncActor module that kicks off
176
+ # the asynchronous job in a loop.
177
+ def run_backgrounder
178
+ sync_breakpoints = ensure_debuggee_registration
179
+
180
+ if sync_breakpoints
181
+ sync_result =
182
+ breakpoint_manager.sync_active_breakpoints debuggee.id
183
+ debuggee.revoke_registration unless sync_result
184
+ end
185
+ rescue StandardError => e
186
+ warn ["#{e.class}: #{e.message}", e.backtrace].join("\n\t")
187
+ @last_exception = e
188
+ end
189
+
190
+ ##
191
+ # @private Callback function when the async actor thread state changes
192
+ def on_async_state_change
193
+ if async_running?
194
+ tracer.start
195
+ else
196
+ tracer.stop
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ ##
203
+ # @private Initialize `@app_root` instance variable.
204
+ def init_app_root app_root = nil
205
+ app_root ||= Google::Cloud::Debugger.configure.app_root
206
+ app_root ||= Rack::Directory.new("").root if defined? Rack::Directory
207
+ app_root ||= Dir.pwd
208
+
209
+ @app_root = app_root
210
+ end
211
+
212
+ ##
213
+ # @private Callback function for breakpoint manager when any updates
214
+ # happens to the list of active breakpoints
215
+ def breakpoints_change_callback active_breakpoints
216
+ if active_breakpoints.empty?
217
+ tracer.stop
218
+ else
219
+ tracer.start
220
+ end
221
+ end
222
+
223
+ ##
224
+ # @private Helper method to register the debuggee application if not
225
+ # registered already.
226
+ def ensure_debuggee_registration
227
+ if debuggee.registered?
228
+ registration_result = true
229
+ else
230
+ registration_result = debuggee.register
231
+ if registration_result
232
+ puts "Debuggee #{debuggee.id} successfully registered"
233
+ end
234
+ end
235
+
236
+ registration_result
237
+ end
238
+
239
+ ##
240
+ # @private Create a default logger
241
+ def default_logger
242
+ project_id = @service.project
243
+ credentials = @service.credentials
244
+ logging = Google::Cloud::Logging::Project.new(
245
+ Google::Cloud::Logging::Service.new(project_id, credentials)
246
+ )
247
+ resource =
248
+ Google::Cloud::Logging::Middleware.build_monitored_resource
249
+
250
+ logging.logger DEFAULT_LOG_NAME, resource
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,70 @@
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
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ ##
20
+ # @private Helps keep tracking of backoff on calling APIs in loop
21
+ class Backoff
22
+ ##
23
+ # Small interval to start with
24
+ DEFAULT_START_INTERVAL = 1
25
+
26
+ ##
27
+ # Maximum interval value to use
28
+ DEFAULT_MAX_INTERVAL = 600
29
+
30
+ ##
31
+ # Interval incremental multiplier
32
+ DEFAULT_MULTIPLIER = 2
33
+
34
+ ##
35
+ # The current time interval should wait until next iteration
36
+ attr_reader :interval
37
+
38
+ def initialize start_interval = DEFAULT_START_INTERVAL,
39
+ max_interval = DEFAULT_MAX_INTERVAL,
40
+ multiplier = DEFAULT_MULTIPLIER
41
+ @start_interval = start_interval
42
+ @max_interval = max_interval
43
+ @multiplier = multiplier
44
+ end
45
+
46
+ ##
47
+ # Resets backoff
48
+ def succeeded
49
+ @interval = nil
50
+ end
51
+
52
+ ##
53
+ # Initialize backoff or increase backoff interval
54
+ def failed
55
+ @interval = if @interval
56
+ [@max_interval, @interval * @multiplier].min
57
+ else
58
+ @start_interval
59
+ end
60
+ end
61
+
62
+ ##
63
+ # Check if a backoff delay should be applied
64
+ def backing_off?
65
+ !@interval.nil?
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,443 @@
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 "time"
17
+ require "google/cloud/debugger/breakpoint/evaluator"
18
+ require "google/cloud/debugger/breakpoint/source_location"
19
+ require "google/cloud/debugger/breakpoint/stack_frame"
20
+ require "google/cloud/debugger/breakpoint/status_message"
21
+ require "google/cloud/debugger/breakpoint/validator"
22
+ require "google/cloud/debugger/breakpoint/variable"
23
+ require "google/cloud/debugger/breakpoint/variable_table"
24
+
25
+ module Google
26
+ module Cloud
27
+ module Debugger
28
+ ##
29
+ # # Breakpoint
30
+ #
31
+ # Abstract class that represents a breakpoint, which can be set and
32
+ # triggered in a debuggee application. Maps to gRPC struct
33
+ # {Google::Cloud::Debugger::V2::Breakpoint}.
34
+ #
35
+ class Breakpoint
36
+ include MonitorMixin
37
+
38
+ ##
39
+ # Breakpoint identifier, unique in the scope of the debuggee.
40
+ attr_accessor :id
41
+
42
+ ##
43
+ # Action to take when a breakpoint is hit. Either :CAPTURE or :LOG.
44
+ # @return [Symbol]
45
+ attr_accessor :action
46
+
47
+ ##
48
+ # Absolute path to the debuggee Ruby application root directory.
49
+ # @return [String]
50
+ attr_accessor :app_root
51
+
52
+ ##
53
+ # Only relevant when action is LOG. Defines the message to log when the
54
+ # breakpoint hits. The message may include parameter placeholders $0,
55
+ # $1, etc. These placeholders are replaced with the evaluated value of
56
+ # the appropriate expression. Expressions not referenced in
57
+ # logMessageFormat are not logged.
58
+ attr_accessor :log_message_format
59
+
60
+ ##
61
+ # Indicates the severity of the log. Only relevant when action is LOG.
62
+ attr_accessor :log_level
63
+
64
+ ##
65
+ # The evaluated log message when action is LOG.
66
+ attr_accessor :evaluated_log_message
67
+
68
+ ##
69
+ # Breakpoint source location.
70
+ # @return [Google::Cloud::Debugger::Breakpoint::SourceLocation]
71
+ attr_accessor :location
72
+
73
+ ##
74
+ # Condition that triggers the breakpoint. The condition is a compound
75
+ # boolean expression composed using expressions in a programming
76
+ # language at the source location.
77
+ attr_accessor :condition
78
+
79
+ ##
80
+ # When true, indicates that this is a final result and the breakpoint
81
+ # state will not change from here on.
82
+ # @return [Boolean]
83
+ attr_accessor :is_final_state
84
+
85
+ ##
86
+ # List of read-only expressions to evaluate at the breakpoint location.
87
+ # The expressions are composed using expressions in the programming
88
+ # language at the source location. If the breakpoint action is LOG, the
89
+ # evaluated expressions are included in log statements.
90
+ # @return [Array<String>]
91
+ attr_accessor :expressions
92
+
93
+ ##
94
+ # Values of evaluated expressions at breakpoint time. The evaluated
95
+ # expressions appear in exactly the same order they are listed in the
96
+ # expressions field. The name field holds the original expression text,
97
+ # the value or members field holds the result of the evaluated
98
+ # expression. If the expression cannot be evaluated, the status inside
99
+ # the Variable will indicate an error and contain the error text.
100
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
101
+ attr_accessor :evaluated_expressions
102
+
103
+ ##
104
+ # Time this breakpoint was created by the server in seconds resolution.
105
+ # @return [Time]
106
+ attr_accessor :create_time
107
+
108
+ ##
109
+ # Time this breakpoint was finalized as seen by the server in seconds
110
+ # resolution.
111
+ # @return [Time]
112
+ attr_accessor :final_time
113
+
114
+ ##
115
+ # E-mail address of the user that created this breakpoint
116
+ attr_accessor :user_email
117
+
118
+ ##
119
+ # Breakpoint status.
120
+ #
121
+ # The status includes an error flag and a human readable message. This
122
+ # field is usually unset. The message can be either informational or an
123
+ # error message. Regardless, clients should always display the text
124
+ # message back to the user.
125
+ #
126
+ # Error status indicates complete failure of the breakpoint.
127
+ attr_accessor :status
128
+
129
+ ##
130
+ # The variable_table exists to aid with computation, memory and network
131
+ # traffic optimization. It enables storing a variable once and reference
132
+ # it from multiple variables, including variables stored in the
133
+ # variable_table itself. For example, the same this object, which may
134
+ # appear at many levels of the stack, can have all of its data stored
135
+ # once in this table. The stack frame variables then would hold only a
136
+ # reference to it.
137
+ #
138
+ # The variable var_table_index field is an index into this repeated
139
+ # field. The stored objects are nameless and get their name from the
140
+ # referencing variable. The effective variable is a merge of the
141
+ # referencing variable and the referenced variable.
142
+ attr_accessor :variable_table
143
+
144
+ ##
145
+ # A set of custom breakpoint properties, populated by the agent, to be
146
+ # displayed to the user.
147
+ # @return [Hash<String, String>]
148
+ attr_accessor :labels
149
+
150
+ ##
151
+ # The stack at breakpoint time.
152
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::StackFrame>]
153
+ attr_accessor :stack_frames
154
+
155
+ ##
156
+ # @private Construct a new instance of Breakpoint.
157
+ def initialize id = nil, path = nil, line = nil
158
+ super()
159
+
160
+ @id = id
161
+ @action = :CAPTURE
162
+ # Use relative path for SourceLocation, because that's how the server
163
+ # side canonical breakpoints are defined.
164
+ @location = SourceLocation.new.tap do |sl|
165
+ sl.path = path
166
+ sl.line = line.to_i
167
+ end
168
+ @expressions = []
169
+ @evaluated_expressions = []
170
+ @stack_frames = []
171
+ @labels = {}
172
+ @variable_table = VariableTable.new
173
+ end
174
+
175
+ ##
176
+ # @private New Google::Cloud::Debugger::Breakpoint
177
+ # from a Google::Cloud::Debugger::V2::Breakpoint object.
178
+ def self.from_grpc grpc
179
+ return new if grpc.nil?
180
+
181
+ breakpoint = grpc.action == :LOG ? Logpoint.new : Snappoint.new
182
+ breakpoint.tap do |b|
183
+ b.id = grpc.id
184
+ b.action = grpc.action
185
+ b.condition = grpc.condition
186
+ b.expressions = grpc.expressions.to_a
187
+ b.labels = hashify_labels grpc.labels
188
+ b.log_message_format = grpc.log_message_format
189
+ b.log_level = grpc.log_level
190
+ b.is_final_state = grpc.is_final_state
191
+ b.user_email = grpc.user_email
192
+
193
+ assign_complex_grpc_fields grpc, b
194
+ end
195
+ end
196
+
197
+ ##
198
+ # @private Helper method that helps extracting complex fields from
199
+ # grpc struct into a breakpoint.
200
+ def self.assign_complex_grpc_fields grpc, breakpoint
201
+ breakpoint.create_time = timestamp_from_grpc grpc.create_time
202
+ breakpoint.evaluated_expressions =
203
+ Breakpoint::Variable.from_grpc_list grpc.evaluated_expressions
204
+ breakpoint.final_time = timestamp_from_grpc grpc.final_time
205
+ breakpoint.location =
206
+ Breakpoint::SourceLocation.from_grpc grpc.location
207
+ breakpoint.stack_frames = stack_frames_from_grpc grpc
208
+ breakpoint.status = Breakpoint::StatusMessage.from_grpc grpc.status
209
+ breakpoint.variable_table =
210
+ Breakpoint::VariableTable.from_grpc grpc.variable_table
211
+ end
212
+
213
+ ##
214
+ # @private Extract array of stack_frame from grpc
215
+ def self.stack_frames_from_grpc grpc
216
+ return nil if grpc.stack_frames.nil?
217
+ grpc.stack_frames.map { |sf| Breakpoint::StackFrame.from_grpc sf }
218
+ end
219
+
220
+ ##
221
+ # @private Get a Time object from a Google::Protobuf::Timestamp object.
222
+ def self.timestamp_from_grpc grpc_timestamp
223
+ return nil if grpc_timestamp.nil?
224
+ Time.at grpc_timestamp.seconds, Rational(grpc_timestamp.nanos, 1000)
225
+ end
226
+
227
+ ##
228
+ # @private Helper method to convert a gRPC map to Ruby Hash
229
+ def self.hashify_labels grpc_labels
230
+ if grpc_labels.respond_to? :to_h
231
+ grpc_labels.to_h
232
+ else
233
+ # Enumerable doesn't have to_h on ruby 2.0...
234
+ Hash[grpc_labels.to_a]
235
+ end
236
+ end
237
+
238
+ private_class_method :stack_frames_from_grpc,
239
+ :timestamp_from_grpc,
240
+ :hashify_labels,
241
+ :assign_complex_grpc_fields
242
+
243
+ ##
244
+ # Marks a breakpoint as complete if this breakpoint isn't completed
245
+ # already. Set @is_final_state to true and set @final_time.
246
+ def complete
247
+ synchronize do
248
+ return if complete?
249
+
250
+ @is_final_state = true
251
+ @final_time = Time.now
252
+ end
253
+ end
254
+
255
+ ##
256
+ # Check if the breakpoint has been evaluated or set to a final error
257
+ # state.
258
+ def complete?
259
+ is_final_state ? true : false
260
+ end
261
+
262
+ ##
263
+ # Check if the breakpoint is valid or not. Invoke validation function
264
+ # if breakpoint hasn't been finallized yet.
265
+ def valid?
266
+ Validator.validate self unless complete?
267
+
268
+ status && status.is_error ? false : true
269
+ end
270
+
271
+ ##
272
+ # Get the file path of this breakpoint
273
+ # @example
274
+ # breakpoint =
275
+ # Google::Cloud::Debugger::Breakpoint.new nil, "path/to/file.rb"
276
+ # breakpoint.path #=> "path/to/file.rb"
277
+ # @return [String] The file path for this breakpoint
278
+ def path
279
+ location.nil? ? nil : location.path
280
+ end
281
+
282
+ ##
283
+ # Get the line number of this breakpoint
284
+ # @example
285
+ # breakpoint =
286
+ # Google::Cloud::Debugger::Breakpoint.new nil, "path/to/file.rb", 11
287
+ # breakpoint.line #=> 11
288
+ # @return [Integer] The line number for this breakpoint
289
+ def line
290
+ location.nil? ? nil : location.line
291
+ end
292
+
293
+ ##
294
+ # Evaluate the breakpoint's condition expression against a given binding
295
+ # object. Returns true if the condition expression evalutes to true or
296
+ # there isn't a condition; otherwise false. Set breakpoint to error
297
+ # state if exception happens.
298
+ #
299
+ # @param [Binding] binding A Ruby Binding object
300
+ # @return [Boolean] True if condition evalutes to true or there isn't a
301
+ # condition. False if condition evaluates to false or error raised
302
+ # during evaluation.
303
+ def check_condition binding
304
+ return true if condition.nil? || condition.empty?
305
+ condition_result =
306
+ Evaluator.readonly_eval_expression binding, condition
307
+
308
+ if condition_result.is_a?(Exception) &&
309
+ condition_result.instance_variable_get(:@mutation_cause)
310
+ set_error_state "Error: #{condition_result.message}",
311
+ refers_to: StatusMessage::BREAKPOINT_CONDITION
312
+
313
+ return false
314
+ end
315
+
316
+
317
+ condition_result ? true : false
318
+ rescue StandardError => e
319
+ set_error_state "Error: #{e.message}",
320
+ refers_to: StatusMessage::BREAKPOINT_CONDITION
321
+ false
322
+ end
323
+
324
+ ##
325
+ # Check if two breakpoints are equal to each other
326
+ def eql? other
327
+ id == other.id &&
328
+ path == other.path &&
329
+ line == other.line
330
+ end
331
+
332
+ ##
333
+ # @private Override default hashing function
334
+ def hash
335
+ id.hash ^ path.hash ^ line.hash
336
+ end
337
+
338
+ ##
339
+ # @private Exports the Breakpoint to a
340
+ # Google::Cloud::Debugger::V2::Breakpoint object.
341
+ def to_grpc
342
+ Google::Cloud::Debugger::V2::Breakpoint.new(
343
+ id: id.to_s,
344
+ location: location.to_grpc,
345
+ condition: condition.to_s,
346
+ expressions: expressions || [],
347
+ is_final_state: is_final_state,
348
+ create_time: timestamp_to_grpc(create_time),
349
+ final_time: timestamp_to_grpc(final_time),
350
+ user_email: user_email.to_s,
351
+ stack_frames: stack_frames_to_grpc,
352
+ evaluated_expressions: evaluated_expressions_to_grpc,
353
+ status: status_to_grpc,
354
+ labels: labels_to_grpc,
355
+ variable_table: variable_table.to_grpc
356
+ )
357
+ end
358
+
359
+ ##
360
+ # Set breakpoint to an error state, which initializes the @status
361
+ # instance variable with the error message. Also mark this breakpoint as
362
+ # completed if is_final is true.
363
+ #
364
+ # @param [String] message The error message
365
+ # @param [Symbol] refers_to Enum that specifies what the error refers
366
+ # to. Defaults :UNSPECIFIED. See {Breakpoint::StatusMessage} class for
367
+ # list of possible values
368
+ # @param [Boolean] is_final Marks the breakpoint as final if true.
369
+ # Defaults true.
370
+ #
371
+ # @return [Google::Cloud::Debugger::Breakpoint::StatusMessage] The grpc
372
+ # StatusMessage object, which describes the breakpoint's error state.
373
+ def set_error_state message, refers_to: StatusMessage::UNSPECIFIED,
374
+ is_final: true
375
+ @status = StatusMessage.new.tap do |s|
376
+ s.is_error = true
377
+ s.refers_to = refers_to
378
+ s.description = message
379
+ end
380
+
381
+ complete if is_final
382
+
383
+ @status
384
+ end
385
+
386
+ ##
387
+ # Get full absolute file path by combining the relative file path
388
+ # with application root directory path.
389
+ def full_path
390
+ if app_root.nil? || app_root.empty?
391
+ path
392
+ else
393
+ File.join app_root, path
394
+ end
395
+ end
396
+
397
+ private
398
+
399
+ ##
400
+ # @private Formats the labels so they can be saved to a
401
+ # Google::Cloud::Debugger::V2::Breakpoint object.
402
+ def labels_to_grpc
403
+ # Coerce symbols to strings
404
+ Hash[labels.map do |k, v|
405
+ [String(k), String(v)]
406
+ end]
407
+ end
408
+
409
+ ##
410
+ # @private Exports the Breakpoint stack_frames to an array of
411
+ # Google::Cloud::Debugger::V2::StackFrame objects.
412
+ def stack_frames_to_grpc
413
+ stack_frames.nil? ? [] : stack_frames.map(&:to_grpc)
414
+ end
415
+
416
+ ##
417
+ # @private Exports the Breakpoint stack_frames to an array of
418
+ # Google::Cloud::Debugger::V2::Variable objects.
419
+ def evaluated_expressions_to_grpc
420
+ evaluated_expressions.nil? ? [] : evaluated_expressions.map(&:to_grpc)
421
+ end
422
+
423
+ ##
424
+ # @private Exports Breakpoint status to
425
+ # Google::Cloud::Debugger::V2::StatusMessage object
426
+ def status_to_grpc
427
+ status.nil? ? nil : status.to_grpc
428
+ end
429
+
430
+ ##
431
+ # @private Formats the timestamp as a Google::Protobuf::Timestamp
432
+ # object.
433
+ def timestamp_to_grpc time
434
+ return nil if time.nil?
435
+ Google::Protobuf::Timestamp.new(
436
+ seconds: time.to_i,
437
+ nanos: time.nsec
438
+ )
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end