google-cloud-debugger 0.26.1 → 0.27.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.
@@ -0,0 +1,96 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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 "forwardable"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ class Breakpoint
22
+ ##
23
+ # # VariableTable
24
+ #
25
+ # The variable_table exists to aid with computation, memory and network
26
+ # traffic optimization. It enables storing a variable once and reference
27
+ # it from multiple variables, including variables stored in the
28
+ # variable_table itself. For example, the same this object, which may
29
+ # appear at many levels of the stack, can have all of its data stored
30
+ # once in this table. The stack frame variables then would hold only a
31
+ # reference to it.
32
+ #
33
+ # The variable var_table_index field is an index into this repeated
34
+ # field. The stored objects are nameless and get their name from the
35
+ # referencing variable. The effective variable is a merge of the
36
+ # referencing variable and the referenced variable.
37
+ #
38
+ # See also {Breakpoint#variable_table}.
39
+ #
40
+ class VariableTable
41
+ extend Forwardable
42
+
43
+ ##
44
+ # @private Array to store variables.
45
+ attr_accessor :variables
46
+
47
+ ##
48
+ # @private Create a new VariableTable instance
49
+ def initialize
50
+ @variables = []
51
+ end
52
+
53
+ ##
54
+ # @private Create a new VariableTable instance from a variable table
55
+ # gRPC struct
56
+ def self.from_grpc grpc_table
57
+ return if grpc_table.nil?
58
+
59
+ new.tap do |vt|
60
+ vt.variables = grpc_table.map do |grpc_var|
61
+ Breakpoint::Variable.from_grpc grpc_var
62
+ end
63
+ end
64
+ end
65
+
66
+ ##
67
+ # @private Search a variable in this VariableTable by matching
68
+ # object_id, return the array index if found.
69
+ def rb_var_index rb_var
70
+ variables.each_with_index do |var, i|
71
+ return i if var.source_var.object_id == rb_var.object_id
72
+ end
73
+
74
+ nil
75
+ end
76
+
77
+ ##
78
+ # @private Add a Breakpoint::Variable to this VariableTable
79
+ def add var
80
+ return unless var.is_a? Breakpoint::Variable
81
+
82
+ variables << var
83
+ end
84
+
85
+ ##
86
+ # @private Export this VariableTable as a gRPC struct
87
+ def to_grpc
88
+ variables.map(&:to_grpc).compact
89
+ end
90
+
91
+ def_instance_delegators :@variables, :size, :first, :[]
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -13,7 +13,8 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
- require "google/cloud/debugger/breakpoint"
16
+ require "google/cloud/debugger/snappoint"
17
+ require "google/cloud/debugger/logpoint"
17
18
 
18
19
  module Google
19
20
  module Cloud
@@ -92,10 +93,8 @@ module Google
92
93
 
93
94
  @wait_token = response.next_wait_token
94
95
 
95
- server_breakpoints = response.breakpoints || []
96
- server_breakpoints = server_breakpoints.map do |grpc_b|
97
- Breakpoint.from_grpc grpc_b
98
- end
96
+ server_breakpoints =
97
+ convert_grpc_breakpoints response.breakpoints || []
99
98
 
100
99
  update_breakpoints server_breakpoints
101
100
 
@@ -117,9 +116,9 @@ module Google
117
116
  def update_breakpoints server_breakpoints
118
117
  synchronize do
119
118
  new_breakpoints =
120
- server_breakpoints - @active_breakpoints - @completed_breakpoints
121
- before_breakpoints_count =
122
- @active_breakpoints.size + @completed_breakpoints.size
119
+ filter_breakpoints server_breakpoints - breakpoints
120
+
121
+ before_breakpoints_count = breakpoints.size
123
122
 
124
123
  # Remember new active breakpoints from server
125
124
  @active_breakpoints += new_breakpoints unless new_breakpoints.empty?
@@ -127,8 +126,7 @@ module Google
127
126
  # Forget old breakpoints
128
127
  @completed_breakpoints &= server_breakpoints
129
128
  @active_breakpoints &= server_breakpoints
130
- after_breakpoints_acount =
131
- @active_breakpoints.size + @completed_breakpoints.size
129
+ after_breakpoints_acount = breakpoints.size
132
130
 
133
131
  breakpoints_updated =
134
132
  !new_breakpoints.empty? ||
@@ -159,6 +157,7 @@ module Google
159
157
  # Take this completed breakpoint off manager's active breakpoints
160
158
  # list, submit the breakpoint snapshot, and update Tracer's
161
159
  # breakpoints_cache.
160
+
162
161
  return unless breakpoint.complete?
163
162
 
164
163
  # Remove this breakpoint from active list
@@ -269,6 +268,42 @@ module Google
269
268
  @completed_breakpoints.clear
270
269
  end
271
270
  end
271
+
272
+ private
273
+
274
+ ##
275
+ # @private Convert the list of grpc breakpoints from Debugger service to
276
+ # {Google::Cloud::Debugger::Breakpoint}.
277
+ def convert_grpc_breakpoints grpc_breakpoints
278
+ grpc_breakpoints.map do |grpc_b|
279
+ breakpoint = Breakpoint.from_grpc grpc_b
280
+ breakpoint.app_root = agent.app_root
281
+ breakpoint.init_var_table if breakpoint.is_a? Debugger::Snappoint
282
+ breakpoint
283
+ end
284
+ end
285
+
286
+ ##
287
+ # @private Varify a list of given breakpoints. Filter out those
288
+ # aren't valid and submit them directly.
289
+ def filter_breakpoints breakpoints
290
+ valid_breakpoints = []
291
+ invalid_breakpoints = []
292
+
293
+ breakpoints.each do |breakpoint|
294
+ if breakpoint.valid?
295
+ valid_breakpoints << breakpoint
296
+ else
297
+ invalid_breakpoints << breakpoint
298
+ end
299
+ end
300
+
301
+ invalid_breakpoints.each do |breakpoint|
302
+ agent.transmitter.submit breakpoint if breakpoint.complete?
303
+ end
304
+
305
+ valid_breakpoints
306
+ end
272
307
  end
273
308
  end
274
309
  end
@@ -21,7 +21,8 @@ module Google
21
21
  ##
22
22
  # @private Represents the OAuth 2.0 signing logic for Debugger.
23
23
  class Credentials < Google::Cloud::Credentials
24
- SCOPE = ["https://www.googleapis.com/auth/cloud_debugger"]
24
+ SCOPE = ["https://www.googleapis.com/auth/cloud_debugger"] +
25
+ Google::Cloud::Logging::Credentials::SCOPE
25
26
  PATH_ENV_VARS = %w(DEBUGGER_KEYFILE GOOGLE_CLOUD_KEYFILE GCLOUD_KEYFILE)
26
27
  JSON_ENV_VARS = %w(DEBUGGER_KEYFILE_JSON GOOGLE_CLOUD_KEYFILE_JSON
27
28
  GCLOUD_KEYFILE_JSON)
@@ -0,0 +1,97 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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
+ # # Logpoint
23
+ #
24
+ # A kind of {Google::Cloud::Debugger::Breakpoint} that can be evaluated
25
+ # to generate a formatted log string, which later can be submitted to
26
+ # Stackdriver Logging service
27
+ #
28
+ class Logpoint < Breakpoint
29
+ ##
30
+ # Evaluate the breakpoint unless it's already marked as completed.
31
+ # Store evaluted expressions and stack frame variables in
32
+ # @evaluated_expressions and @evaluated_log_message.
33
+ #
34
+ # @param [Array<Binding>] call_stack_bindings An array of Ruby Binding
35
+ # objects, from the call stack that leads to the triggering of the
36
+ # breakpoints.
37
+ #
38
+ # @return [Boolean] True if evaluated successfully; false otherwise.
39
+ #
40
+ def evaluate call_stack_bindings
41
+ synchronize do
42
+ binding = call_stack_bindings[0]
43
+
44
+ return false if complete? || !check_condition(binding)
45
+
46
+ begin
47
+ evaluate_log_message binding
48
+ rescue
49
+ return false
50
+ end
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ ##
57
+ # @private Evaluate the expressions and log message. Store the result
58
+ # in @evaluated_log_message
59
+ def evaluate_log_message binding
60
+ evaluated_expressions = expressions.map do |expression|
61
+ Evaluator.readonly_eval_expression binding, expression
62
+ end
63
+
64
+ @evaluated_log_message =
65
+ format_message log_message_format, evaluated_expressions
66
+ end
67
+
68
+ ##
69
+ # @private Format log message by interpolate expressions.
70
+ #
71
+ # @example
72
+ # Evaluator.format_log_message("Hello $0",
73
+ # ["World"]) #=> "Hello World"
74
+ #
75
+ # @param [String] message_format The message with with
76
+ # expression placeholders such as `$0`, `$1`, etc.
77
+ # @param [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
78
+ # expressions An array of evaluated expression variables to be
79
+ # placed into message_format's placeholders. The variables need
80
+ # to have type equal String.
81
+ #
82
+ # @return [String] The formatted message string
83
+ #
84
+ def format_message message_format, expressions
85
+ # Substitute placeholders with expressions
86
+ message = message_format.gsub(/(?<!\$)\$\d+/) do |placeholder|
87
+ index = placeholder.match(/\$(\d+)/)[1].to_i
88
+ index < expressions.size ? expressions[index].inspect : ""
89
+ end
90
+
91
+ # Unescape "$" characters
92
+ message.gsub(/\$\$/, "$")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -14,6 +14,7 @@
14
14
 
15
15
 
16
16
  require "google/cloud/logging/logger"
17
+ require "google/cloud/debugger/request_quota_manager"
17
18
 
18
19
  module Google
19
20
  module Cloud
@@ -44,11 +45,18 @@ module Google
44
45
 
45
46
  load_config kwargs
46
47
 
47
- @debugger = debugger ||
48
- Debugger.new(project: configuration.project_id,
49
- keyfile: configuration.keyfile,
50
- module_name: configuration.module_name,
51
- module_version: configuration.module_version)
48
+ if debugger
49
+ @debugger = debugger
50
+ else
51
+ @debugger =
52
+ Debugger.new(project: configuration.project_id,
53
+ keyfile: configuration.keyfile,
54
+ module_name: configuration.module_name,
55
+ module_version: configuration.module_version)
56
+
57
+ @debugger.agent.quota_manager =
58
+ Google::Cloud::Debugger::RequestQuotaManager.new
59
+ end
52
60
 
53
61
  # Immediately start the debugger agent
54
62
  @debugger.start
@@ -77,6 +85,9 @@ module Google
77
85
  ensure
78
86
  # Stop breakpoints tracing beyond this point
79
87
  @debugger.agent.tracer.disable_traces_for_thread
88
+
89
+ # Reset quotas after each request finishes.
90
+ @debugger.agent.quota_manager.reset if @debugger.agent.quota_manager
80
91
  end
81
92
 
82
93
  private
@@ -0,0 +1,95 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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
+ # # RequestQuotaManager
21
+ #
22
+ # Tracking object used by debugger agent to manage quota in
23
+ # request-based applications. This class tracks the amount of time
24
+ # and number of breakpoints to evaluation in a single session.
25
+ #
26
+ # The debugger agent doesn't have use a quota manager by default, which
27
+ # means it will evaluate all breakpoints encountered and takes as much
28
+ # time as needed. This class is utilized by
29
+ # {Google::Cloud::Debugger::Middleware} class to limit latency overhead
30
+ # when used in Rack-based applications.
31
+ #
32
+ class RequestQuotaManager
33
+ # Default Total time allowed to consume, in seconds
34
+ DEFAULT_TIME_QUOTA = 0.05
35
+
36
+ # Default max number of breakpoints to evaluate
37
+ DEFAULT_COUNT_QUOTA = 10
38
+
39
+ ##
40
+ # The time quota for this manager
41
+ attr_accessor :time_quota
42
+
43
+ ##
44
+ # The count quota for this manager
45
+ attr_accessor :count_quota
46
+
47
+ ##
48
+ # The time quota used
49
+ attr_accessor :time_used
50
+
51
+ ##
52
+ # The count quota used
53
+ attr_accessor :count_used
54
+
55
+ ##
56
+ # Construct a new RequestQuotaManager instance
57
+ #
58
+ # @param [Float] time_quota The max quota for time consumed.
59
+ # @param [Integer] count_quota The max quota for count usage.
60
+ def initialize time_quota: DEFAULT_TIME_QUOTA,
61
+ count_quota: DEFAULT_COUNT_QUOTA
62
+ @time_quota = time_quota
63
+ @time_used = 0
64
+ @count_quota = count_quota
65
+ @count_used = 0
66
+ end
67
+
68
+ ##
69
+ # Check if there's more quota left.
70
+ #
71
+ # @return [Boolean] True if there's more quota; false otherwise.
72
+ def more?
73
+ (time_used < time_quota) && (count_used < count_quota)
74
+ end
75
+
76
+ ##
77
+ # Reset all the quota usage.
78
+ def reset
79
+ @time_used = 0
80
+ @count_used = 0
81
+ end
82
+
83
+ ##
84
+ # Notify the quota manager some resource has been consumed. Each time
85
+ # called increases the count quota usage.
86
+ #
87
+ # @param [Float] time Amount of time to deduct from the time quota.
88
+ def consume time: 0
89
+ @time_used += time
90
+ @count_used += 1
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,208 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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
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] binding 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