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,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