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.
- checksums.yaml +7 -0
- data/.yardopts +18 -0
- data/AUTHENTICATION.md +178 -0
- data/CHANGELOG.md +233 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +188 -0
- data/INSTRUMENTATION.md +115 -0
- data/LICENSE +201 -0
- data/LOGGING.md +32 -0
- data/OVERVIEW.md +266 -0
- data/TROUBLESHOOTING.md +31 -0
- data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
- data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
- data/ext/google/cloud/debugger/debugger_c/evaluator.c +115 -0
- data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
- data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.c +542 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.h +25 -0
- data/lib/google-cloud-debugger.rb +181 -0
- data/lib/google/cloud/debugger.rb +259 -0
- data/lib/google/cloud/debugger/agent.rb +255 -0
- data/lib/google/cloud/debugger/backoff.rb +70 -0
- data/lib/google/cloud/debugger/breakpoint.rb +443 -0
- data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1099 -0
- data/lib/google/cloud/debugger/breakpoint/source_location.rb +74 -0
- data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
- data/lib/google/cloud/debugger/breakpoint/status_message.rb +93 -0
- data/lib/google/cloud/debugger/breakpoint/validator.rb +92 -0
- data/lib/google/cloud/debugger/breakpoint/variable.rb +595 -0
- data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
- data/lib/google/cloud/debugger/breakpoint_manager.rb +311 -0
- data/lib/google/cloud/debugger/credentials.rb +50 -0
- data/lib/google/cloud/debugger/debuggee.rb +222 -0
- data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +76 -0
- data/lib/google/cloud/debugger/logpoint.rb +98 -0
- data/lib/google/cloud/debugger/middleware.rb +200 -0
- data/lib/google/cloud/debugger/project.rb +110 -0
- data/lib/google/cloud/debugger/rails.rb +174 -0
- data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
- data/lib/google/cloud/debugger/service.rb +88 -0
- data/lib/google/cloud/debugger/snappoint.rb +208 -0
- data/lib/google/cloud/debugger/tracer.rb +137 -0
- data/lib/google/cloud/debugger/transmitter.rb +199 -0
- data/lib/google/cloud/debugger/version.rb +22 -0
- 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
|